Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
117 lines
5.0 KiB
Go
117 lines
5.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type transientCooldownAccountRepo struct {
|
|
AccountRepository
|
|
}
|
|
|
|
func (transientCooldownAccountRepo) SetOverloaded(context.Context, int64, time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func TestHandleOpenAITransientError_BlocksOnlyRequestedModel(t *testing.T) {
|
|
svc := &OpenAIGatewayService{}
|
|
svc.rateLimitService = NewRateLimitService(transientCooldownAccountRepo{}, nil, &config.Config{}, nil, nil)
|
|
account := &Account{
|
|
ID: 5105,
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeAPIKey,
|
|
}
|
|
|
|
firstShouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, http.StatusBadGateway, http.Header{}, []byte(`{"error":{"message":"Upstream request failed","type":"upstream_error"}}`), "gpt-5.5")
|
|
secondShouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, http.StatusBadGateway, http.Header{}, []byte(`{"error":{"message":"Upstream request failed","type":"upstream_error"}}`), "gpt-5.5")
|
|
|
|
require.False(t, firstShouldDisable)
|
|
require.False(t, secondShouldDisable)
|
|
require.False(t, svc.isOpenAIAccountRuntimeBlocked(account))
|
|
require.True(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "gpt-5.5"))
|
|
require.False(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "gpt-5.6-terra"))
|
|
}
|
|
|
|
func TestHandleOpenAITransientError_TransientStatusesUseModelScope(t *testing.T) {
|
|
for _, statusCode := range []int{http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout, 520, 521, 522, 523, 524} {
|
|
t.Run(http.StatusText(statusCode), func(t *testing.T) {
|
|
svc := &OpenAIGatewayService{}
|
|
svc.rateLimitService = NewRateLimitService(transientCooldownAccountRepo{}, nil, &config.Config{}, nil, nil)
|
|
account := &Account{
|
|
ID: int64(5100 + statusCode),
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeAPIKey,
|
|
}
|
|
|
|
firstShouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, statusCode, http.Header{}, []byte(`{"error":{"message":"temporary upstream failure"}}`), "gpt-5.5")
|
|
secondShouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, statusCode, http.Header{}, []byte(`{"error":{"message":"temporary upstream failure"}}`), "gpt-5.5")
|
|
|
|
require.False(t, firstShouldDisable)
|
|
require.False(t, secondShouldDisable)
|
|
require.False(t, svc.isOpenAIAccountRuntimeBlocked(account), "status %d must not block the whole account", statusCode)
|
|
require.True(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "gpt-5.5"), "status %d should block the failing model", statusCode)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleOpenAITransientError_529RemainsOverloadOnly(t *testing.T) {
|
|
require.False(t, shouldCooldownOpenAITransientUpstreamError(529, []byte(`{"error":{"message":"overloaded"}}`)))
|
|
}
|
|
|
|
func TestHandleOpenAITransientError_CanonicalModelIsNotMappedTwice(t *testing.T) {
|
|
svc := &OpenAIGatewayService{}
|
|
svc.rateLimitService = NewRateLimitService(transientCooldownAccountRepo{}, nil, &config.Config{}, nil, nil)
|
|
account := &Account{
|
|
ID: 5107,
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeAPIKey,
|
|
Credentials: map[string]any{
|
|
"model_mapping": map[string]any{
|
|
"public-alias": "upstream-a",
|
|
"upstream-a": "upstream-b",
|
|
},
|
|
},
|
|
}
|
|
canonicalModel := account.GetMappedModel("public-alias")
|
|
require.Equal(t, "upstream-a", canonicalModel)
|
|
|
|
for range 2 {
|
|
svc.handleOpenAIAccountUpstreamError(context.Background(), account, http.StatusBadGateway, http.Header{}, []byte(`{"error":{"message":"temporary upstream failure"}}`), canonicalModel)
|
|
}
|
|
|
|
require.True(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "public-alias"))
|
|
svc.ReportOpenAIAccountScheduleResult(account.ID, canonicalModel, true, nil)
|
|
require.False(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "public-alias"))
|
|
}
|
|
|
|
func TestHandleOpenAITransientError_DoesNotBlockParameter400(t *testing.T) {
|
|
svc := &OpenAIGatewayService{}
|
|
svc.rateLimitService = NewRateLimitService(transientCooldownAccountRepo{}, nil, &config.Config{}, nil, nil)
|
|
account := &Account{
|
|
ID: 5103,
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeAPIKey,
|
|
}
|
|
|
|
shouldDisable := svc.handleOpenAIAccountUpstreamError(context.Background(), account, http.StatusBadRequest, http.Header{}, []byte(`{"error":{"message":"Invalid type for input[0].arguments"}}`), "gpt-5.5")
|
|
|
|
require.False(t, shouldDisable)
|
|
require.False(t, svc.isOpenAIAccountRuntimeBlocked(account))
|
|
require.False(t, svc.isOpenAIAccountModelRuntimeBlocked(account, "gpt-5.5"))
|
|
}
|
|
|
|
func TestHandleOpenAITransientError_HardDisableStillBlocksWholeAccount(t *testing.T) {
|
|
svc := &OpenAIGatewayService{}
|
|
account := &Account{ID: 5106, Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
|
|
|
|
svc.BlockAccountScheduling(account, time.Now().Add(time.Minute), "upstream_disable")
|
|
|
|
require.True(t, svc.isOpenAIAccountRequestRuntimeBlocked(account, "gpt-5.5"))
|
|
require.True(t, svc.isOpenAIAccountRequestRuntimeBlocked(account, "gpt-5.6-sol"))
|
|
}
|