external_subscription_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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.Go(func() {
  43. results <- fetchSubscriptionLinks(srv.URL)
  44. })
  45. }
  46. time.Sleep(100 * time.Millisecond)
  47. close(release)
  48. wg.Wait()
  49. close(results)
  50. for links := range results {
  51. if len(links) != 1 || links[0] != "vless://[email protected]:443" {
  52. t.Fatalf("links = %#v", links)
  53. }
  54. }
  55. if got := requests.Load(); got != 1 {
  56. t.Fatalf("requests = %d, want 1", got)
  57. }
  58. }
  59. func TestFetchSubscriptionLinksBoundsCacheSize(t *testing.T) {
  60. resetSubscriptionCache(t)
  61. var requests atomic.Int32
  62. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  63. requests.Add(1)
  64. _, _ = w.Write([]byte("vless://[email protected]:443"))
  65. }))
  66. defer srv.Close()
  67. for i := range subscriptionCacheCapacity + 1 {
  68. links := fetchSubscriptionLinks(srv.URL + "?id=" + strconv.Itoa(i))
  69. if len(links) != 1 {
  70. t.Fatalf("links at %d = %#v", i, links)
  71. }
  72. }
  73. subscriptionCache.Lock()
  74. entries := len(subscriptionCache.m)
  75. subscriptionCache.Unlock()
  76. if entries != subscriptionCacheCapacity {
  77. t.Fatalf("cache entries = %d, want %d", entries, subscriptionCacheCapacity)
  78. }
  79. if got := requests.Load(); got != subscriptionCacheCapacity+1 {
  80. t.Fatalf("requests = %d, want %d", got, subscriptionCacheCapacity+1)
  81. }
  82. }
  83. func TestFetchSubscriptionLinksSharesStaleResultAfterRefreshFailure(t *testing.T) {
  84. resetSubscriptionCache(t)
  85. stale := []string{"vless://[email protected]:443"}
  86. release := make(chan struct{})
  87. var staleRequests atomic.Int32
  88. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  89. if r.URL.Path == "/stale" {
  90. staleRequests.Add(1)
  91. <-release
  92. w.WriteHeader(http.StatusBadGateway)
  93. return
  94. }
  95. _, _ = w.Write([]byte("vless://[email protected]:443"))
  96. }))
  97. defer srv.Close()
  98. staleURL := srv.URL + "/stale"
  99. subscriptionCache.Lock()
  100. subscriptionCache.m[staleURL] = subscriptionCacheEntry{
  101. links: stale,
  102. fetchedAt: time.Now().Add(-subscriptionCacheTTL),
  103. }
  104. for i := range subscriptionCacheCapacity - 1 {
  105. subscriptionCache.m["cached-"+strconv.Itoa(i)] = subscriptionCacheEntry{fetchedAt: time.Now()}
  106. }
  107. subscriptionCache.Unlock()
  108. const callers = 16
  109. results := make(chan []string, callers)
  110. var wg sync.WaitGroup
  111. for range callers {
  112. wg.Go(func() {
  113. results <- fetchSubscriptionLinks(staleURL)
  114. })
  115. }
  116. time.Sleep(100 * time.Millisecond)
  117. if links := fetchSubscriptionLinks(srv.URL + "/fresh"); len(links) != 1 || links[0] != "vless://[email protected]:443" {
  118. t.Fatalf("fresh links = %#v", links)
  119. }
  120. close(release)
  121. wg.Wait()
  122. close(results)
  123. for links := range results {
  124. if len(links) != 1 || links[0] != stale[0] {
  125. t.Fatalf("links = %#v, want %#v", links, stale)
  126. }
  127. }
  128. if got := staleRequests.Load(); got != 1 {
  129. t.Fatalf("requests = %d, want 1", got)
  130. }
  131. }
  132. func TestDoFetchSubscriptionLinks_RejectsOversizedBody(t *testing.T) {
  133. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  134. _, _ = w.Write([]byte(strings.Repeat("a", subscriptionMaxBytes+1)))
  135. }))
  136. defer srv.Close()
  137. links, err := doFetchSubscriptionLinks(srv.URL)
  138. if !errors.Is(err, errSubscriptionBodyTooLarge) {
  139. t.Fatalf("err = %v, want errSubscriptionBodyTooLarge", err)
  140. }
  141. if links != nil {
  142. t.Fatalf("links = %v, want nil", links)
  143. }
  144. }
  145. func TestDoFetchSubscriptionLinks_AcceptsBodyAtLimit(t *testing.T) {
  146. link := "vless://example"
  147. body := link + "\n" + strings.Repeat("#", subscriptionMaxBytes-len(link)-1)
  148. if len(body) != subscriptionMaxBytes {
  149. t.Fatalf("fixture size = %d, want %d", len(body), subscriptionMaxBytes)
  150. }
  151. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  152. _, _ = w.Write([]byte(body))
  153. }))
  154. defer srv.Close()
  155. links, err := doFetchSubscriptionLinks(srv.URL)
  156. if err != nil {
  157. t.Fatalf("unexpected err: %v", err)
  158. }
  159. if len(links) != 1 || links[0] != link {
  160. t.Fatalf("links = %v, want [%q]", links, link)
  161. }
  162. }