gateway_pool_mode_retry_test.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. package service
  2. import (
  3. "context"
  4. "io"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "github.com/Wei-Shaw/sub2api/internal/config"
  9. "github.com/gin-gonic/gin"
  10. "github.com/stretchr/testify/require"
  11. )
  12. func TestGatewayCompatPoolMode429AllowsSameAccountRetry(t *testing.T) {
  13. gin.SetMode(gin.TestMode)
  14. tests := []struct {
  15. name string
  16. path string
  17. body []byte
  18. call func(*GatewayService, context.Context, *gin.Context, *Account, []byte) (*ForwardResult, error)
  19. }{
  20. {
  21. name: "chat completions",
  22. path: "/v1/chat/completions",
  23. body: []byte(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hello"}]}`),
  24. call: func(svc *GatewayService, ctx context.Context, c *gin.Context, account *Account, body []byte) (*ForwardResult, error) {
  25. return svc.ForwardAsChatCompletions(ctx, c, account, body, nil)
  26. },
  27. },
  28. {
  29. name: "responses",
  30. path: "/v1/responses",
  31. body: []byte(`{"model":"claude-sonnet-4-5","input":"hello"}`),
  32. call: func(svc *GatewayService, ctx context.Context, c *gin.Context, account *Account, body []byte) (*ForwardResult, error) {
  33. return svc.ForwardAsResponses(ctx, c, account, body, nil)
  34. },
  35. },
  36. }
  37. for _, tt := range tests {
  38. t.Run(tt.name, func(t *testing.T) {
  39. upstream := &queuedHTTPUpstreamStub{responses: []*http.Response{{
  40. StatusCode: http.StatusTooManyRequests,
  41. Header: http.Header{"X-Request-Id": []string{"pool-429"}},
  42. Body: io.NopCloser(http.NoBody),
  43. }}}
  44. svc := &GatewayService{
  45. cfg: &config.Config{},
  46. httpUpstream: upstream,
  47. tlsFPProfileService: &TLSFingerprintProfileService{},
  48. }
  49. recorder := httptest.NewRecorder()
  50. c, _ := gin.CreateTestContext(recorder)
  51. c.Request = httptest.NewRequest(http.MethodPost, tt.path, nil)
  52. account := &Account{
  53. ID: 1,
  54. Name: "pool-account",
  55. Platform: PlatformAnthropic,
  56. Type: AccountTypeAPIKey,
  57. Credentials: map[string]any{
  58. "api_key": "test-key",
  59. "pool_mode": true,
  60. },
  61. }
  62. result, err := tt.call(svc, context.Background(), c, account, tt.body)
  63. require.Nil(t, result)
  64. var failoverErr *UpstreamFailoverError
  65. require.ErrorAs(t, err, &failoverErr)
  66. require.Equal(t, http.StatusTooManyRequests, failoverErr.StatusCode)
  67. require.True(t, failoverErr.RetryableOnSameAccount)
  68. require.Equal(t, 1, upstream.callCount)
  69. require.Empty(t, recorder.Body.String())
  70. })
  71. }
  72. }