mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 07:33:06 +08:00
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 不受限(上游可能兼容多种协议格式)。
74 lines
2.8 KiB
Go
74 lines
2.8 KiB
Go
//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)
|
||
}
|