openai_account_runtime_transient_test.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package service
  2. import (
  3. "context"
  4. "net/http"
  5. "testing"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/config"
  8. "github.com/stretchr/testify/require"
  9. )
  10. type transientCooldownAccountRepo struct {
  11. AccountRepository
  12. }
  13. func (transientCooldownAccountRepo) SetOverloaded(context.Context, int64, time.Time) error {
  14. return nil
  15. }
  16. func TestHandleOpenAITransientError_BlocksOnlyRequestedModel(t *testing.T) {
  17. svc := &OpenAIGatewayService{}
  18. svc.rateLimitService = NewRateLimitService(transientCooldownAccountRepo{}, nil, &config.Config{}, nil, nil)
  19. account := &Account{
  20. ID: 5105,
  21. Platform: PlatformOpenAI,
  22. Type: AccountTypeAPIKey,
  23. }
  24. firstShouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, http.StatusBadGateway, http.Header{}, []byte(`{"error":{"message":"Upstream request failed","type":"upstream_error"}}`), "gpt-5.5")
  25. secondShouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, http.StatusBadGateway, http.Header{}, []byte(`{"error":{"message":"Upstream request failed","type":"upstream_error"}}`), "gpt-5.5")
  26. require.False(t, firstShouldDisable)
  27. require.False(t, secondShouldDisable)
  28. require.False(t, svc.isOpenAIAccountRuntimeBlocked(account))
  29. require.True(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "gpt-5.5"))
  30. require.False(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "gpt-5.6-terra"))
  31. }
  32. func TestHandleOpenAITransientError_TransientStatusesUseModelScope(t *testing.T) {
  33. for _, statusCode := range []int{http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout, 520, 521, 522, 523, 524} {
  34. t.Run(http.StatusText(statusCode), func(t *testing.T) {
  35. svc := &OpenAIGatewayService{}
  36. svc.rateLimitService = NewRateLimitService(transientCooldownAccountRepo{}, nil, &config.Config{}, nil, nil)
  37. account := &Account{
  38. ID: int64(5100 + statusCode),
  39. Platform: PlatformOpenAI,
  40. Type: AccountTypeAPIKey,
  41. }
  42. firstShouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, statusCode, http.Header{}, []byte(`{"error":{"message":"temporary upstream failure"}}`), "gpt-5.5")
  43. secondShouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, statusCode, http.Header{}, []byte(`{"error":{"message":"temporary upstream failure"}}`), "gpt-5.5")
  44. require.False(t, firstShouldDisable)
  45. require.False(t, secondShouldDisable)
  46. require.False(t, svc.isOpenAIAccountRuntimeBlocked(account), "status %d must not block the whole account", statusCode)
  47. require.True(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "gpt-5.5"), "status %d should block the failing model", statusCode)
  48. })
  49. }
  50. }
  51. func TestHandleOpenAITransientError_529RemainsOverloadOnly(t *testing.T) {
  52. require.False(t, shouldCooldownOpenAITransientUpstreamError(529, []byte(`{"error":{"message":"overloaded"}}`)))
  53. }
  54. func TestHandleOpenAITransientError_CanonicalModelIsNotMappedTwice(t *testing.T) {
  55. svc := &OpenAIGatewayService{}
  56. svc.rateLimitService = NewRateLimitService(transientCooldownAccountRepo{}, nil, &config.Config{}, nil, nil)
  57. account := &Account{
  58. ID: 5107,
  59. Platform: PlatformOpenAI,
  60. Type: AccountTypeAPIKey,
  61. Credentials: map[string]any{
  62. "model_mapping": map[string]any{
  63. "public-alias": "upstream-a",
  64. "upstream-a": "upstream-b",
  65. },
  66. },
  67. }
  68. canonicalModel := account.GetMappedModel("public-alias")
  69. require.Equal(t, "upstream-a", canonicalModel)
  70. for range 2 {
  71. svc.handleOpenAIAccountUpstreamError(context.Background(), account, http.StatusBadGateway, http.Header{}, []byte(`{"error":{"message":"temporary upstream failure"}}`), canonicalModel)
  72. }
  73. require.True(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "public-alias"))
  74. svc.ReportOpenAIAccountScheduleResult(account, canonicalModel, true, nil)
  75. require.False(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "public-alias"))
  76. }
  77. func TestHandleOpenAITransientError_DoesNotBlockParameter400(t *testing.T) {
  78. svc := &OpenAIGatewayService{}
  79. svc.rateLimitService = NewRateLimitService(transientCooldownAccountRepo{}, nil, &config.Config{}, nil, nil)
  80. account := &Account{
  81. ID: 5103,
  82. Platform: PlatformOpenAI,
  83. Type: AccountTypeAPIKey,
  84. }
  85. shouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, http.StatusBadRequest, http.Header{}, []byte(`{"error":{"message":"Invalid type for input[0].arguments"}}`), "gpt-5.5")
  86. require.False(t, shouldDisable)
  87. require.False(t, svc.isOpenAIAccountRuntimeBlocked(account))
  88. require.False(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "gpt-5.5"))
  89. }
  90. func TestHandleOpenAITransientError_HardDisableStillBlocksWholeAccount(t *testing.T) {
  91. svc := &OpenAIGatewayService{}
  92. account := &Account{ID: 5106, Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
  93. svc.BlockAccountScheduling(account, time.Now().Add(time.Minute), "upstream_disable")
  94. require.True(t, svc.isOpenAIAccountRuntimeBlocked(account))
  95. require.False(t, svc.isOpenAIAccountRequestRuntimeBlocked(account, "gpt-5.5"))
  96. require.False(t, svc.isOpenAIAccountRequestRuntimeBlocked(account, "gpt-5.6-sol"))
  97. require.False(t, svc.isOpenAIAccountRuntimeBlocked(account))
  98. }