1
0

external_subscription_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package sub
  2. import (
  3. "errors"
  4. "net/http"
  5. "net/http/httptest"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "sync/atomic"
  10. "testing"
  11. "time"
  12. )
  13. func resetSubscriptionCache(t *testing.T) {
  14. t.Helper()
  15. subscriptionCache.Lock()
  16. previousEntries := subscriptionCache.m
  17. previousInflight := subscriptionCache.inflight
  18. subscriptionCache.m = make(map[string]subscriptionCacheEntry)
  19. subscriptionCache.inflight = make(map[string]*subscriptionFetch)
  20. subscriptionCache.Unlock()
  21. t.Cleanup(func() {
  22. subscriptionCache.Lock()
  23. subscriptionCache.m = previousEntries
  24. subscriptionCache.inflight = previousInflight
  25. subscriptionCache.Unlock()
  26. })
  27. }
  28. func TestFetchSubscriptionLinksSharesConcurrentRefresh(t *testing.T) {
  29. resetSubscriptionCache(t)
  30. var requests atomic.Int32
  31. release := make(chan struct{})
  32. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  33. requests.Add(1)
  34. <-release
  35. _, _ = w.Write([]byte("vless://[email protected]:443"))
  36. }))
  37. defer srv.Close()
  38. const callers = 16
  39. results := make(chan []string, callers)
  40. var wg sync.WaitGroup
  41. for range callers {
  42. wg.Add(1)
  43. go func() {
  44. defer wg.Done()
  45. results <- fetchSubscriptionLinks(srv.URL)
  46. }()
  47. }
  48. time.Sleep(100 * time.Millisecond)
  49. close(release)
  50. wg.Wait()
  51. close(results)
  52. for links := range results {
  53. if len(links) != 1 || links[0] != "vless://[email protected]:443" {
  54. t.Fatalf("links = %#v", links)
  55. }
  56. }
  57. if got := requests.Load(); got != 1 {
  58. t.Fatalf("requests = %d, want 1", got)
  59. }
  60. }
  61. func TestFetchSubscriptionLinksBoundsCacheSize(t *testing.T) {
  62. resetSubscriptionCache(t)
  63. var requests atomic.Int32
  64. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  65. requests.Add(1)
  66. _, _ = w.Write([]byte("vless://[email protected]:443"))
  67. }))
  68. defer srv.Close()
  69. for i := range subscriptionCacheCapacity + 1 {
  70. links := fetchSubscriptionLinks(srv.URL + "?id=" + strconv.Itoa(i))
  71. if len(links) != 1 {
  72. t.Fatalf("links at %d = %#v", i, links)
  73. }
  74. }
  75. subscriptionCache.Lock()
  76. entries := len(subscriptionCache.m)
  77. subscriptionCache.Unlock()
  78. if entries != subscriptionCacheCapacity {
  79. t.Fatalf("cache entries = %d, want %d", entries, subscriptionCacheCapacity)
  80. }
  81. if got := requests.Load(); got != subscriptionCacheCapacity+1 {
  82. t.Fatalf("requests = %d, want %d", got, subscriptionCacheCapacity+1)
  83. }
  84. }
  85. func TestFetchSubscriptionLinksSharesStaleResultAfterRefreshFailure(t *testing.T) {
  86. resetSubscriptionCache(t)
  87. stale := []string{"vless://[email protected]:443"}
  88. release := make(chan struct{})
  89. var staleRequests atomic.Int32
  90. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  91. if r.URL.Path == "/stale" {
  92. staleRequests.Add(1)
  93. <-release
  94. w.WriteHeader(http.StatusBadGateway)
  95. return
  96. }
  97. _, _ = w.Write([]byte("vless://[email protected]:443"))
  98. }))
  99. defer srv.Close()
  100. staleURL := srv.URL + "/stale"
  101. subscriptionCache.Lock()
  102. subscriptionCache.m[staleURL] = subscriptionCacheEntry{
  103. links: stale,
  104. fetchedAt: time.Now().Add(-subscriptionCacheTTL),
  105. }
  106. for i := range subscriptionCacheCapacity - 1 {
  107. subscriptionCache.m["cached-"+strconv.Itoa(i)] = subscriptionCacheEntry{fetchedAt: time.Now()}
  108. }
  109. subscriptionCache.Unlock()
  110. const callers = 16
  111. results := make(chan []string, callers)
  112. var wg sync.WaitGroup
  113. for range callers {
  114. wg.Add(1)
  115. go func() {
  116. defer wg.Done()
  117. results <- fetchSubscriptionLinks(staleURL)
  118. }()
  119. }
  120. time.Sleep(100 * time.Millisecond)
  121. if links := fetchSubscriptionLinks(srv.URL + "/fresh"); len(links) != 1 || links[0] != "vless://[email protected]:443" {
  122. t.Fatalf("fresh links = %#v", links)
  123. }
  124. close(release)
  125. wg.Wait()
  126. close(results)
  127. for links := range results {
  128. if len(links) != 1 || links[0] != stale[0] {
  129. t.Fatalf("links = %#v, want %#v", links, stale)
  130. }
  131. }
  132. if got := staleRequests.Load(); got != 1 {
  133. t.Fatalf("requests = %d, want 1", got)
  134. }
  135. }
  136. func TestDoFetchSubscriptionLinks_RejectsOversizedBody(t *testing.T) {
  137. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  138. _, _ = w.Write([]byte(strings.Repeat("a", subscriptionMaxBytes+1)))
  139. }))
  140. defer srv.Close()
  141. links, err := doFetchSubscriptionLinks(srv.URL)
  142. if !errors.Is(err, errSubscriptionBodyTooLarge) {
  143. t.Fatalf("err = %v, want errSubscriptionBodyTooLarge", err)
  144. }
  145. if links != nil {
  146. t.Fatalf("links = %v, want nil", links)
  147. }
  148. }
  149. func TestDoFetchSubscriptionLinks_AcceptsBodyAtLimit(t *testing.T) {
  150. link := "vless://example"
  151. body := link + "\n" + strings.Repeat("#", subscriptionMaxBytes-len(link)-1)
  152. if len(body) != subscriptionMaxBytes {
  153. t.Fatalf("fixture size = %d, want %d", len(body), subscriptionMaxBytes)
  154. }
  155. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  156. _, _ = w.Write([]byte(body))
  157. }))
  158. defer srv.Close()
  159. links, err := doFetchSubscriptionLinks(srv.URL)
  160. if err != nil {
  161. t.Fatalf("unexpected err: %v", err)
  162. }
  163. if len(links) != 1 || links[0] != link {
  164. t.Fatalf("links = %v, want [%q]", links, link)
  165. }
  166. }