Files
sub2api/backend/internal/service/gateway_upstream_request_test.go
T
shaw d19265ddb2 fix(gateway): 拒绝含 OpenAI 网关 OAuth 的 composite 歧义路由,防止凭据误入 Anthropic 管线
composite 分组中 openai OAuth 账号与 deepseek API Key 账号同时显式映射同一
gpt 模型时,模型归属判为歧义、路由层不解析平台,请求落到默认 Anthropic
GatewayHandler;handler 再按模型名前缀猜回 openai 并选中 OpenAI OAuth 账号,
ChatGPT token 被发往 api.anthropic.com,上游 401 "Invalid bearer token"
经 failover 逐号打成临时不可调度,单请求即可扫空号池。

- 归属记录声明方是否含 OpenAI 网关族 OAuth / setup-token;当模型名兜底也
  落在 OpenAI 网关族时判为配置错误,路由中间件与 WS 入口直接 400 拒绝并
  提示配置显式路由。纯 API Key 歧义及 Anthropic/Antigravity/Gemini OAuth
  歧义保持原行为。
- 模型归属改按配置计算(ListModelAvailabilityCandidates),不再随限流 /
  临时不可调度在「唯一归属」与「歧义」之间翻转。
- Anthropic 请求构建前校验凭据:非 Anthropic 网关族的 OAuth 凭据不出站,
  返回仅换号、不处罚账号的 failover;count_tokens 返回 404 不支持。
  API Key 不受限(上游可能兼容多种协议格式)。
2026-10-15 14:57:03 +08:00

74 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build unit
package service
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
// Scenario: OAuth / setup-token 凭据只能走签发方协议,API Key 与其他凭据类型不受限
func TestAnthropicMessagesCredentialAllowed(t *testing.T) {
cases := []struct {
platform string
typ string
allowed bool
}{
{PlatformAnthropic, AccountTypeOAuth, true},
{PlatformAnthropic, AccountTypeSetupToken, true},
{PlatformAnthropic, AccountTypeServiceAccount, true},
{PlatformAnthropic, AccountTypeBedrock, true},
{PlatformTypeSafe, AccountTypeOAuth, true},
{PlatformOpenAI, AccountTypeAPIKey, true},
{PlatformDeepseek, AccountTypeAPIKey, true},
{PlatformOpenAI, AccountTypeOAuth, false},
{PlatformOpenAI, AccountTypeSetupToken, false},
{PlatformGrok, AccountTypeOAuth, false},
{PlatformGemini, AccountTypeOAuth, false},
{PlatformAntigravity, AccountTypeOAuth, false},
}
for _, tc := range cases {
allowed := anthropicMessagesCredentialAllowed(&Account{ID: 1, Platform: tc.platform, Type: tc.typ})
require.Equal(t, tc.allowed, allowed, "%s/%s", tc.platform, tc.typ)
}
}
// Scenario: 回归 —— OpenAI OAuth 账号误入 Anthropic 管线时,在构建请求前即被拒绝
// (ChatGPT token 不会发往 api.anthropic.com),且只让 handler 换号、不处罚账号
func TestGatewayBuildUpstreamRequestRejectsForeignOAuthWithoutPenalty(t *testing.T) {
account := &Account{ID: 25, Platform: PlatformOpenAI, Type: AccountTypeOAuth}
body := []byte(`{"model":"gpt-6-astra","messages":[]}`)
req, _, err := (&GatewayService{}).buildUpstreamRequest(context.Background(), nil, account, body, "chatgpt-token", "oauth", "gpt-6-astra", true, false)
require.Nil(t, req)
var failoverErr *UpstreamFailoverError
require.True(t, errors.As(err, &failoverErr))
require.True(t, failoverErr.ShouldRetryNextAccount())
require.False(t, failoverErr.RetryableOnSameAccount, "same-account retry would end in temp-unschedulable")
require.False(t, failoverErr.IsCredentialFailure())
require.Equal(t, anthropicMessagesForeignOAuthReason, failoverErr.Reason)
}
// Scenario: count_tokens 对不匹配凭据按「平台不支持」返回 404,不取 token、不出站
func TestForwardCountTokensRejectsForeignOAuthAsUnsupported(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages/count_tokens", nil)
account := &Account{ID: 25, Platform: PlatformOpenAI, Type: AccountTypeOAuth}
parsed := &ParsedRequest{Model: "gpt-6-astra", Body: NewRequestBodyRef([]byte(`{"model":"gpt-6-astra","messages":[]}`))}
err := (&GatewayService{}).ForwardCountTokens(context.Background(), c, account, parsed)
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, w.Code)
}