Highly-opinionated (ex-bullshit-free) MTPROTO proxy for Telegram. If you use v1.0 or upgrade broke you proxy, please read the chapter Version 2
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

proxy_stats_test.go 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. package mtglib
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "net/http/httptest"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func TestNewProxyStats(t *testing.T) {
  12. t.Parallel()
  13. stats := NewProxyStats()
  14. assert.NotNil(t, stats)
  15. assert.NotNil(t, stats.users)
  16. assert.False(t, stats.startedAt.IsZero())
  17. }
  18. func TestPreRegister(t *testing.T) {
  19. t.Parallel()
  20. stats := NewProxyStats()
  21. stats.PreRegister("alice")
  22. stats.PreRegister("bob")
  23. stats.mu.RLock()
  24. defer stats.mu.RUnlock()
  25. assert.Contains(t, stats.users, "alice")
  26. assert.Contains(t, stats.users, "bob")
  27. assert.Equal(t, int64(0), stats.users["alice"].connections.Load())
  28. }
  29. func TestOnConnectDisconnect(t *testing.T) {
  30. t.Parallel()
  31. stats := NewProxyStats()
  32. stats.PreRegister("alice")
  33. stats.OnConnect("alice")
  34. assert.Equal(t, int64(1), stats.users["alice"].connections.Load())
  35. stats.OnConnect("alice")
  36. assert.Equal(t, int64(2), stats.users["alice"].connections.Load())
  37. stats.OnDisconnect("alice")
  38. assert.Equal(t, int64(1), stats.users["alice"].connections.Load())
  39. stats.OnDisconnect("alice")
  40. assert.Equal(t, int64(0), stats.users["alice"].connections.Load())
  41. }
  42. func TestAddBytes(t *testing.T) {
  43. t.Parallel()
  44. stats := NewProxyStats()
  45. stats.PreRegister("alice")
  46. stats.AddBytesIn("alice", 100)
  47. stats.AddBytesIn("alice", 200)
  48. stats.AddBytesOut("alice", 50)
  49. st := stats.users["alice"]
  50. assert.Equal(t, int64(300), st.bytesIn.Load())
  51. assert.Equal(t, int64(50), st.bytesOut.Load())
  52. }
  53. func TestUpdateLastSeen(t *testing.T) {
  54. t.Parallel()
  55. stats := NewProxyStats()
  56. stats.PreRegister("alice")
  57. before := time.Now()
  58. stats.UpdateLastSeen("alice")
  59. after := time.Now()
  60. lastSeen := stats.users["alice"].lastSeen.Load().(time.Time)
  61. assert.False(t, lastSeen.Before(before))
  62. assert.False(t, lastSeen.After(after))
  63. }
  64. func TestGetOrCreateLazy(t *testing.T) {
  65. t.Parallel()
  66. stats := NewProxyStats()
  67. // getOrCreate should create a new entry on first access.
  68. stats.OnConnect("new-user")
  69. assert.Equal(t, int64(1), stats.users["new-user"].connections.Load())
  70. }
  71. func TestServeHTTPBasic(t *testing.T) {
  72. t.Parallel()
  73. stats := NewProxyStats()
  74. stats.PreRegister("alice")
  75. stats.PreRegister("bob")
  76. stats.OnConnect("alice")
  77. stats.OnConnect("alice")
  78. stats.OnConnect("bob")
  79. stats.AddBytesIn("alice", 1024)
  80. stats.AddBytesOut("alice", 512)
  81. stats.UpdateLastSeen("alice")
  82. rec := httptest.NewRecorder()
  83. req := httptest.NewRequest(http.MethodGet, "/stats", nil)
  84. stats.ServeHTTP(rec, req)
  85. assert.Equal(t, http.StatusOK, rec.Code)
  86. assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
  87. var resp StatsResponse
  88. err := json.Unmarshal(rec.Body.Bytes(), &resp)
  89. require.NoError(t, err)
  90. assert.Equal(t, int64(3), resp.TotalConnections)
  91. assert.False(t, resp.StartedAt.IsZero())
  92. assert.GreaterOrEqual(t, resp.UptimeSeconds, int64(0))
  93. alice, ok := resp.Users["alice"]
  94. require.True(t, ok)
  95. assert.Equal(t, int64(2), alice.Connections)
  96. assert.Equal(t, int64(1024), alice.BytesIn)
  97. assert.Equal(t, int64(512), alice.BytesOut)
  98. assert.NotNil(t, alice.LastSeen)
  99. bob, ok := resp.Users["bob"]
  100. require.True(t, ok)
  101. assert.Equal(t, int64(1), bob.Connections)
  102. assert.Equal(t, int64(0), bob.BytesIn)
  103. assert.Equal(t, int64(0), bob.BytesOut)
  104. assert.Nil(t, bob.LastSeen)
  105. }
  106. func TestServeHTTPEmpty(t *testing.T) {
  107. t.Parallel()
  108. stats := NewProxyStats()
  109. rec := httptest.NewRecorder()
  110. req := httptest.NewRequest(http.MethodGet, "/stats", nil)
  111. stats.ServeHTTP(rec, req)
  112. assert.Equal(t, http.StatusOK, rec.Code)
  113. var resp StatsResponse
  114. err := json.Unmarshal(rec.Body.Bytes(), &resp)
  115. require.NoError(t, err)
  116. assert.Empty(t, resp.Users)
  117. assert.Equal(t, int64(0), resp.TotalConnections)
  118. }
  119. func TestComputeFairCaps_NoThrottle(t *testing.T) {
  120. t.Parallel()
  121. caps := computeFairCaps(map[string]int64{
  122. "a": 10,
  123. "b": 20,
  124. }, 100)
  125. assert.Nil(t, caps)
  126. }
  127. func TestComputeFairCaps_ExactLimit(t *testing.T) {
  128. t.Parallel()
  129. caps := computeFairCaps(map[string]int64{
  130. "a": 50,
  131. "b": 50,
  132. }, 100)
  133. assert.Nil(t, caps)
  134. }
  135. func TestComputeFairCaps_UserExample(t *testing.T) {
  136. t.Parallel()
  137. // The user's exact example: limit=100, users=[1, 1, 90, 110]
  138. // Small users keep their 1+1=2, remaining budget=98, split among 2 → 49 each
  139. caps := computeFairCaps(map[string]int64{
  140. "a": 1,
  141. "b": 1,
  142. "c": 90,
  143. "d": 110,
  144. }, 100)
  145. assert.Len(t, caps, 2)
  146. assert.Equal(t, int64(49), caps["c"])
  147. assert.Equal(t, int64(49), caps["d"])
  148. // "a" and "b" should not appear in caps (they're under the fair share)
  149. _, hasA := caps["a"]
  150. _, hasB := caps["b"]
  151. assert.False(t, hasA)
  152. assert.False(t, hasB)
  153. }
  154. func TestComputeFairCaps_AllOverLimit(t *testing.T) {
  155. t.Parallel()
  156. caps := computeFairCaps(map[string]int64{
  157. "a": 100,
  158. "b": 100,
  159. }, 50)
  160. assert.Len(t, caps, 2)
  161. assert.Equal(t, int64(25), caps["a"])
  162. assert.Equal(t, int64(25), caps["b"])
  163. }
  164. func TestComputeFairCaps_SingleHeavyUser(t *testing.T) {
  165. t.Parallel()
  166. caps := computeFairCaps(map[string]int64{
  167. "light": 5,
  168. "heavy": 200,
  169. }, 100)
  170. // light(5) < fairShare(50), keeps 5. Budget = 95. Heavy capped to 95.
  171. assert.Len(t, caps, 1)
  172. assert.Equal(t, int64(95), caps["heavy"])
  173. }
  174. func TestCanConnect_NoThrottle(t *testing.T) {
  175. t.Parallel()
  176. stats := NewProxyStats()
  177. // throttleLimit = 0 (default), so CanConnect always returns true
  178. assert.True(t, stats.CanConnect("anyone"))
  179. }
  180. func TestCanConnect_WithCap(t *testing.T) {
  181. t.Parallel()
  182. stats := NewProxyStats()
  183. stats.PreRegister("heavy")
  184. stats.SetThrottle(100, 5*time.Second)
  185. // Simulate 50 connections
  186. for range 50 {
  187. stats.OnConnect("heavy")
  188. }
  189. // Set cap to 50
  190. stats.throttleMu.Lock()
  191. stats.throttleCaps = map[string]int64{"heavy": 50}
  192. stats.throttleActive.Store(true)
  193. stats.throttleMu.Unlock()
  194. // At exactly the cap → reject
  195. assert.False(t, stats.CanConnect("heavy"))
  196. // Disconnect one → allow
  197. stats.OnDisconnect("heavy")
  198. assert.True(t, stats.CanConnect("heavy"))
  199. }
  200. func TestCanConnect_NoCap(t *testing.T) {
  201. t.Parallel()
  202. stats := NewProxyStats()
  203. stats.SetThrottle(100, 5*time.Second)
  204. // User not in caps map → always allowed
  205. assert.True(t, stats.CanConnect("uncapped-user"))
  206. }
  207. func TestServeHTTPThrottleInfo(t *testing.T) {
  208. t.Parallel()
  209. stats := NewProxyStats()
  210. stats.PreRegister("alice")
  211. stats.SetThrottle(100, 5*time.Second)
  212. stats.throttleMu.Lock()
  213. stats.throttleCaps = map[string]int64{"alice": 50}
  214. stats.throttleActive.Store(true)
  215. stats.throttleMu.Unlock()
  216. rec := httptest.NewRecorder()
  217. req := httptest.NewRequest(http.MethodGet, "/stats", nil)
  218. stats.ServeHTTP(rec, req)
  219. var resp StatsResponse
  220. err := json.Unmarshal(rec.Body.Bytes(), &resp)
  221. require.NoError(t, err)
  222. require.NotNil(t, resp.Throttle)
  223. assert.True(t, resp.Throttle.Active)
  224. assert.Equal(t, int64(100), resp.Throttle.Limit)
  225. assert.Equal(t, int64(50), resp.Throttle.Caps["alice"])
  226. }
  227. func TestServeHTTPNoThrottle(t *testing.T) {
  228. t.Parallel()
  229. stats := NewProxyStats()
  230. rec := httptest.NewRecorder()
  231. req := httptest.NewRequest(http.MethodGet, "/stats", nil)
  232. stats.ServeHTTP(rec, req)
  233. var resp StatsResponse
  234. err := json.Unmarshal(rec.Body.Bytes(), &resp)
  235. require.NoError(t, err)
  236. assert.Nil(t, resp.Throttle)
  237. }
  238. func TestServeHTTPLastSeenZeroIsNull(t *testing.T) {
  239. t.Parallel()
  240. stats := NewProxyStats()
  241. stats.PreRegister("alice")
  242. rec := httptest.NewRecorder()
  243. req := httptest.NewRequest(http.MethodGet, "/stats", nil)
  244. stats.ServeHTTP(rec, req)
  245. var raw map[string]json.RawMessage
  246. require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw))
  247. var users map[string]json.RawMessage
  248. require.NoError(t, json.Unmarshal(raw["users"], &users))
  249. var aliceRaw map[string]json.RawMessage
  250. require.NoError(t, json.Unmarshal(users["alice"], &aliceRaw))
  251. assert.Equal(t, "null", string(aliceRaw["last_seen"]))
  252. }