pool_test.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package httpclient
  2. import (
  3. "errors"
  4. "io"
  5. "net/http"
  6. "strings"
  7. "sync/atomic"
  8. "testing"
  9. "time"
  10. "github.com/stretchr/testify/require"
  11. )
  12. type roundTripFunc func(*http.Request) (*http.Response, error)
  13. func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
  14. return f(req)
  15. }
  16. func TestValidatedTransport_CacheHostValidation(t *testing.T) {
  17. originalValidate := validateResolvedIP
  18. defer func() { validateResolvedIP = originalValidate }()
  19. var validateCalls int32
  20. validateResolvedIP = func(host string) error {
  21. atomic.AddInt32(&validateCalls, 1)
  22. require.Equal(t, "api.openai.com", host)
  23. return nil
  24. }
  25. var baseCalls int32
  26. base := roundTripFunc(func(_ *http.Request) (*http.Response, error) {
  27. atomic.AddInt32(&baseCalls, 1)
  28. return &http.Response{
  29. StatusCode: http.StatusOK,
  30. Body: io.NopCloser(strings.NewReader(`{}`)),
  31. Header: make(http.Header),
  32. }, nil
  33. })
  34. now := time.Unix(1730000000, 0)
  35. transport := newValidatedTransport(base)
  36. transport.now = func() time.Time { return now }
  37. req, err := http.NewRequest(http.MethodGet, "https://api.openai.com/v1/responses", nil)
  38. require.NoError(t, err)
  39. _, err = transport.RoundTrip(req)
  40. require.NoError(t, err)
  41. _, err = transport.RoundTrip(req)
  42. require.NoError(t, err)
  43. require.Equal(t, int32(1), atomic.LoadInt32(&validateCalls))
  44. require.Equal(t, int32(2), atomic.LoadInt32(&baseCalls))
  45. }
  46. func TestValidatedTransport_ExpiredCacheTriggersRevalidation(t *testing.T) {
  47. originalValidate := validateResolvedIP
  48. defer func() { validateResolvedIP = originalValidate }()
  49. var validateCalls int32
  50. validateResolvedIP = func(_ string) error {
  51. atomic.AddInt32(&validateCalls, 1)
  52. return nil
  53. }
  54. base := roundTripFunc(func(_ *http.Request) (*http.Response, error) {
  55. return &http.Response{
  56. StatusCode: http.StatusOK,
  57. Body: io.NopCloser(strings.NewReader(`{}`)),
  58. Header: make(http.Header),
  59. }, nil
  60. })
  61. now := time.Unix(1730001000, 0)
  62. transport := newValidatedTransport(base)
  63. transport.now = func() time.Time { return now }
  64. req, err := http.NewRequest(http.MethodGet, "https://api.openai.com/v1/responses", nil)
  65. require.NoError(t, err)
  66. _, err = transport.RoundTrip(req)
  67. require.NoError(t, err)
  68. now = now.Add(validatedHostTTL + time.Second)
  69. _, err = transport.RoundTrip(req)
  70. require.NoError(t, err)
  71. require.Equal(t, int32(2), atomic.LoadInt32(&validateCalls))
  72. }
  73. func TestValidatedTransport_ValidationErrorStopsRoundTrip(t *testing.T) {
  74. originalValidate := validateResolvedIP
  75. defer func() { validateResolvedIP = originalValidate }()
  76. expectedErr := errors.New("dns rebinding rejected")
  77. validateResolvedIP = func(_ string) error {
  78. return expectedErr
  79. }
  80. var baseCalls int32
  81. base := roundTripFunc(func(_ *http.Request) (*http.Response, error) {
  82. atomic.AddInt32(&baseCalls, 1)
  83. return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(`{}`))}, nil
  84. })
  85. transport := newValidatedTransport(base)
  86. req, err := http.NewRequest(http.MethodGet, "https://api.openai.com/v1/responses", nil)
  87. require.NoError(t, err)
  88. _, err = transport.RoundTrip(req)
  89. require.ErrorIs(t, err, expectedErr)
  90. require.Equal(t, int32(0), atomic.LoadInt32(&baseCalls))
  91. }