Files
sub2api/backend/internal/service/cn_provider_foraccount_test.go
T
李建琦 6d655c9903
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
Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
2026-08-21 18:30:13 +08:00

126 lines
4.5 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.
package service
// ForAccount 直传入口(P2-6)的校验回归测试:
// QueryUsageForAccount / QueryBalanceForAccount 接受已加载的 *Account
// 但必须复用与 ID 入口相同的加载后校验——直传不能绕过平台/模式检查,
// 且校验在 singleflight 之前完成(无效账号不得发起任何上游请求)。
import (
"context"
"testing"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/stretchr/testify/require"
)
func codingAccount(platform string) *Account {
return &Account{
ID: 1, Platform: platform, Type: AccountTypeAPIKey, Status: StatusActive,
Credentials: map[string]any{"account_mode": AccountModeCoding, "api_key": "sk-test"},
}
}
func paygAccount(platform string) *Account {
return &Account{
ID: 2, Platform: platform, Type: AccountTypeAPIKey, Status: StatusActive,
Credentials: map[string]any{"account_mode": AccountModePayG, "api_key": "sk-test"},
}
}
func requireReason(t *testing.T, err error, reason string) {
t.Helper()
require.Error(t, err)
var appErr *infraerrors.ApplicationError
require.ErrorAs(t, err, &appErr)
require.Equal(t, reason, appErr.Reason)
}
func TestValidateCodingPlanAccount_Matrix(t *testing.T) {
cases := []struct {
name string
account *Account
wantReason string
}{
{name: "nil", account: nil, wantReason: "CN_QUOTA_ACCOUNT_NOT_FOUND"},
{name: "non cn provider", account: &Account{ID: 3, Platform: PlatformAnthropic}, wantReason: "CN_QUOTA_INVALID_PLATFORM"},
{name: "payg has no quota endpoint", account: paygAccount(PlatformKimi), wantReason: "CN_QUOTA_NOT_CODING_PLAN"},
{name: "kimi coding ok", account: codingAccount(PlatformKimi)},
{name: "zhipu coding ok", account: codingAccount(PlatformZhipu)},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := validateCodingPlanAccount(tc.account)
if tc.wantReason == "" {
require.NoError(t, err)
return
}
requireReason(t, err, tc.wantReason)
})
}
}
func TestValidatePayGAccount_Matrix(t *testing.T) {
cases := []struct {
name string
account *Account
wantReason string
}{
{name: "nil", account: nil, wantReason: "CN_BALANCE_ACCOUNT_NOT_FOUND"},
{name: "non cn provider", account: &Account{ID: 3, Platform: PlatformAnthropic}, wantReason: "CN_BALANCE_INVALID_PLATFORM"},
{name: "coding has no balance endpoint", account: codingAccount(PlatformKimi), wantReason: "CN_BALANCE_CODING_PLAN"},
{name: "kimi payg ok", account: paygAccount(PlatformKimi)},
{name: "deepseek payg ok", account: paygAccount(PlatformDeepseek)},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := validatePayGAccount(tc.account)
if tc.wantReason == "" {
require.NoError(t, err)
return
}
requireReason(t, err, tc.wantReason)
})
}
}
// 直传入口的校验在 singleflight/上游请求之前:无效账号必须零出站请求。
func TestCNProviderQuotaService_QueryUsageForAccount_RejectsInvalidAccount(t *testing.T) {
repo := &fakeCNProbeAccountRepo{}
upstream := &recordingHTTPUpstream{}
svc := NewCNProviderQuotaService(repo, nil, upstream, nil)
_, err := svc.QueryUsageForAccount(context.Background(), paygAccount(PlatformKimi))
requireReason(t, err, "CN_QUOTA_NOT_CODING_PLAN")
require.Zero(t, upstream.calls)
_, err = svc.QueryUsageForAccount(context.Background(), nil)
requireReason(t, err, "CN_QUOTA_ACCOUNT_NOT_FOUND")
require.Zero(t, upstream.calls)
}
func TestCNProviderBalanceService_QueryBalanceForAccount_RejectsInvalidAccount(t *testing.T) {
repo := &fakeCNProbeAccountRepo{}
upstream := &recordingHTTPUpstream{}
svc := NewCNProviderBalanceService(repo, nil, upstream, nil)
_, err := svc.QueryBalanceForAccount(context.Background(), codingAccount(PlatformKimi))
requireReason(t, err, "CN_BALANCE_CODING_PLAN")
require.Zero(t, upstream.calls)
_, err = svc.QueryBalanceForAccount(context.Background(), &Account{ID: 9, Platform: PlatformAnthropic})
requireReason(t, err, "CN_BALANCE_INVALID_PLATFORM")
require.Zero(t, upstream.calls)
}
// ID 入口与 ForAccount 入口对同一账号的行为一致(loadCodingPlanAccount 的
// 加载后校验 = validateCodingPlanAccount;余额侧对称)。
func TestCNProviderServices_IDEntryAppliesSameValidation(t *testing.T) {
repo := &fakeCNProbeAccountRepo{account: paygAccount(PlatformKimi)}
upstream := &recordingHTTPUpstream{}
svc := NewCNProviderQuotaService(repo, nil, upstream, nil)
_, err := svc.QueryUsage(context.Background(), 2)
requireReason(t, err, "CN_QUOTA_NOT_CODING_PLAN")
require.Zero(t, upstream.calls)
}