proxy_probe_service_test.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. package repository
  2. import (
  3. "context"
  4. "io"
  5. "net/http"
  6. "net/http/httptest"
  7. "strings"
  8. "testing"
  9. "github.com/stretchr/testify/require"
  10. "github.com/stretchr/testify/suite"
  11. )
  12. type ProxyProbeServiceSuite struct {
  13. suite.Suite
  14. ctx context.Context
  15. proxySrv *httptest.Server
  16. prober *proxyProbeService
  17. }
  18. func (s *ProxyProbeServiceSuite) SetupTest() {
  19. s.ctx = context.Background()
  20. s.prober = &proxyProbeService{
  21. allowPrivateHosts: true,
  22. }
  23. }
  24. func (s *ProxyProbeServiceSuite) TearDownTest() {
  25. if s.proxySrv != nil {
  26. s.proxySrv.Close()
  27. s.proxySrv = nil
  28. }
  29. }
  30. func (s *ProxyProbeServiceSuite) setupProxyServer(handler http.HandlerFunc) {
  31. s.proxySrv = newLocalTestServer(s.T(), handler)
  32. }
  33. func (s *ProxyProbeServiceSuite) TestProbeProxy_InvalidProxyURL() {
  34. _, _, err := s.prober.ProbeProxy(s.ctx, "://bad")
  35. require.Error(s.T(), err)
  36. require.ErrorContains(s.T(), err, "failed to create proxy client")
  37. }
  38. func (s *ProxyProbeServiceSuite) TestProbeProxy_UnsupportedProxyScheme() {
  39. _, _, err := s.prober.ProbeProxy(s.ctx, "ftp://127.0.0.1:1")
  40. require.Error(s.T(), err)
  41. require.ErrorContains(s.T(), err, "failed to create proxy client")
  42. }
  43. func (s *ProxyProbeServiceSuite) TestProbeProxy_Success_IPAPI() {
  44. s.setupProxyServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  45. // 检查是否是 ip-api 请求
  46. if strings.Contains(r.RequestURI, "ip-api.com") {
  47. w.Header().Set("Content-Type", "application/json")
  48. _, _ = io.WriteString(w, `{"status":"success","query":"1.2.3.4","city":"c","regionName":"r","country":"cc","countryCode":"CC"}`)
  49. return
  50. }
  51. // 其他请求返回错误
  52. w.WriteHeader(http.StatusServiceUnavailable)
  53. }))
  54. info, latencyMs, err := s.prober.ProbeProxy(s.ctx, s.proxySrv.URL)
  55. require.NoError(s.T(), err, "ProbeProxy")
  56. require.GreaterOrEqual(s.T(), latencyMs, int64(0), "unexpected latency")
  57. require.Equal(s.T(), "1.2.3.4", info.IP)
  58. require.Equal(s.T(), "c", info.City)
  59. require.Equal(s.T(), "r", info.Region)
  60. require.Equal(s.T(), "cc", info.Country)
  61. require.Equal(s.T(), "CC", info.CountryCode)
  62. }
  63. func (s *ProxyProbeServiceSuite) TestProbeProxy_Success_IPifyFallback() {
  64. s.setupProxyServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  65. // ip-api 失败
  66. if strings.Contains(r.RequestURI, "ip-api.com") {
  67. w.WriteHeader(http.StatusServiceUnavailable)
  68. return
  69. }
  70. // ipify 成功
  71. if strings.Contains(r.RequestURI, "api64.ipify.org") {
  72. w.Header().Set("Content-Type", "application/json")
  73. _, _ = io.WriteString(w, `{"ip": "5.6.7.8"}`)
  74. return
  75. }
  76. w.WriteHeader(http.StatusServiceUnavailable)
  77. }))
  78. info, latencyMs, err := s.prober.ProbeProxy(s.ctx, s.proxySrv.URL)
  79. require.NoError(s.T(), err, "ProbeProxy should fallback to ipify")
  80. require.GreaterOrEqual(s.T(), latencyMs, int64(0), "unexpected latency")
  81. require.Equal(s.T(), "5.6.7.8", info.IP)
  82. }
  83. func (s *ProxyProbeServiceSuite) TestProbeProxy_AllFailed() {
  84. s.setupProxyServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  85. w.WriteHeader(http.StatusServiceUnavailable)
  86. }))
  87. _, _, err := s.prober.ProbeProxy(s.ctx, s.proxySrv.URL)
  88. require.Error(s.T(), err)
  89. require.ErrorContains(s.T(), err, "all probe URLs failed")
  90. }
  91. func (s *ProxyProbeServiceSuite) TestProbeProxy_InvalidJSON() {
  92. s.setupProxyServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  93. if strings.Contains(r.RequestURI, "ip-api.com") {
  94. w.Header().Set("Content-Type", "application/json")
  95. _, _ = io.WriteString(w, "not-json")
  96. return
  97. }
  98. // ipify 也返回无效响应
  99. if strings.Contains(r.RequestURI, "api64.ipify.org") {
  100. w.Header().Set("Content-Type", "application/json")
  101. _, _ = io.WriteString(w, "not-json")
  102. return
  103. }
  104. w.WriteHeader(http.StatusServiceUnavailable)
  105. }))
  106. _, _, err := s.prober.ProbeProxy(s.ctx, s.proxySrv.URL)
  107. require.Error(s.T(), err)
  108. require.ErrorContains(s.T(), err, "all probe URLs failed")
  109. }
  110. func (s *ProxyProbeServiceSuite) TestProbeProxy_ProxyServerClosed() {
  111. s.setupProxyServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
  112. s.proxySrv.Close()
  113. _, _, err := s.prober.ProbeProxy(s.ctx, s.proxySrv.URL)
  114. require.Error(s.T(), err, "expected error when proxy server is closed")
  115. }
  116. func (s *ProxyProbeServiceSuite) TestParseIPAPI_Success() {
  117. body := []byte(`{"status":"success","query":"1.2.3.4","city":"Beijing","regionName":"Beijing","country":"China","countryCode":"CN"}`)
  118. info, latencyMs, err := s.prober.parseIPAPI(body, 100)
  119. require.NoError(s.T(), err)
  120. require.Equal(s.T(), int64(100), latencyMs)
  121. require.Equal(s.T(), "1.2.3.4", info.IP)
  122. require.Equal(s.T(), "Beijing", info.City)
  123. require.Equal(s.T(), "Beijing", info.Region)
  124. require.Equal(s.T(), "China", info.Country)
  125. require.Equal(s.T(), "CN", info.CountryCode)
  126. }
  127. func (s *ProxyProbeServiceSuite) TestParseIPAPI_Failure() {
  128. body := []byte(`{"status":"fail","message":"rate limited"}`)
  129. _, _, err := s.prober.parseIPAPI(body, 100)
  130. require.Error(s.T(), err)
  131. require.ErrorContains(s.T(), err, "rate limited")
  132. }
  133. func (s *ProxyProbeServiceSuite) TestParseIPify_Success() {
  134. body := []byte(`{"ip": "2001:db8::1"}`)
  135. info, latencyMs, err := s.prober.parseIPify(body, 50)
  136. require.NoError(s.T(), err)
  137. require.Equal(s.T(), int64(50), latencyMs)
  138. require.Equal(s.T(), "2001:db8::1", info.IP)
  139. }
  140. func (s *ProxyProbeServiceSuite) TestParseIPify_NoIP() {
  141. body := []byte(`{"ip": ""}`)
  142. _, _, err := s.prober.parseIPify(body, 50)
  143. require.Error(s.T(), err)
  144. require.ErrorContains(s.T(), err, "no IP found")
  145. }
  146. func (s *ProxyProbeServiceSuite) TestParseChatGPTTrace_Success() {
  147. body := []byte("fl=abc\nh=chatgpt.com\nip=203.0.113.5\nts=1700000000\nloc=US\ntz=UTC\n")
  148. info, latencyMs, err := s.prober.parseChatGPTTrace(body, 320)
  149. require.NoError(s.T(), err)
  150. require.Equal(s.T(), int64(320), latencyMs)
  151. require.Equal(s.T(), "203.0.113.5", info.IP)
  152. require.Equal(s.T(), "US", info.CountryCode)
  153. }
  154. func (s *ProxyProbeServiceSuite) TestParseChatGPTTrace_NoIP() {
  155. body := []byte("fl=abc\nh=chatgpt.com\nloc=US\n")
  156. _, _, err := s.prober.parseChatGPTTrace(body, 100)
  157. require.Error(s.T(), err)
  158. require.ErrorContains(s.T(), err, "chatgpt-trace: no ip= found")
  159. }
  160. func TestProxyProbeServiceSuite(t *testing.T) {
  161. suite.Run(t, new(ProxyProbeServiceSuite))
  162. }