Files
sub2api/backend/internal/service/admin_service_composite_group_test.go
李建琦 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

215 lines
7.1 KiB
Go

//go:build unit
package service
import (
"context"
"testing"
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
"github.com/stretchr/testify/require"
)
type accountRepoStubForCompositeModelsList struct {
accountRepoStub
accounts []Account
}
func (s *accountRepoStubForCompositeModelsList) ListSchedulableByGroupID(_ context.Context, _ int64) ([]Account, error) {
return s.accounts, nil
}
func TestAdminService_CreateCompositeGroupCopiesAccountsFromConcreteGroups(t *testing.T) {
var copiedFrom []int64
var boundGroupID int64
var boundAccountIDs []int64
groupRepo := &groupRepoStubForAdmin{
createID: 99,
getByIDByID: map[int64]*Group{
10: {ID: 10, Platform: PlatformOpenAI},
20: {ID: 20, Platform: PlatformGemini},
},
getAccountIDsByGroupIDsFn: func(groupIDs []int64) ([]int64, error) {
copiedFrom = append([]int64{}, groupIDs...)
return []int64{101, 202}, nil
},
bindAccountsToGroupFn: func(groupID int64, accountIDs []int64) error {
boundGroupID = groupID
boundAccountIDs = append([]int64{}, accountIDs...)
return nil
},
}
svc := &adminServiceImpl{groupRepo: groupRepo}
group, err := svc.CreateGroup(context.Background(), &CreateGroupInput{
Name: "Composite",
Platform: PlatformComposite,
RateMultiplier: 1,
MaxReasoningEffort: "medium",
ReasoningEffortMappings: []ReasoningEffortMapping{
{From: "max", To: "xhigh"},
},
CopyAccountsFromGroupIDs: []int64{10, 20, 10},
})
require.NoError(t, err)
require.Equal(t, PlatformComposite, groupRepo.created.Platform)
require.Equal(t, "medium", groupRepo.created.MaxReasoningEffort)
require.Equal(t, []ReasoningEffortMapping{{From: "max", To: "xhigh"}}, groupRepo.created.ReasoningEffortMappings)
require.Equal(t, int64(99), group.ID)
require.Equal(t, int64(2), group.AccountCount)
require.ElementsMatch(t, []int64{10, 20}, copiedFrom)
require.Equal(t, int64(99), boundGroupID)
require.ElementsMatch(t, []int64{101, 202}, boundAccountIDs)
}
func TestAdminService_UpdateCompositeGroupCopiesAccountsFromConcreteGroups(t *testing.T) {
var clearedGroupID int64
var copiedFrom []int64
var boundGroupID int64
var boundAccountIDs []int64
groupRepo := &groupRepoStubForAdmin{
getByIDByID: map[int64]*Group{
10: {ID: 10, Platform: PlatformOpenAI},
20: {ID: 20, Platform: PlatformGrok},
99: {ID: 99, Platform: PlatformComposite, RateMultiplier: 1, SubscriptionType: SubscriptionTypeStandard},
},
deleteAccountGroupsByGroupIDFn: func(groupID int64) (int64, error) {
clearedGroupID = groupID
return 2, nil
},
getAccountIDsByGroupIDsFn: func(groupIDs []int64) ([]int64, error) {
copiedFrom = append([]int64{}, groupIDs...)
return []int64{301, 302}, nil
},
bindAccountsToGroupFn: func(groupID int64, accountIDs []int64) error {
boundGroupID = groupID
boundAccountIDs = append([]int64{}, accountIDs...)
return nil
},
}
svc := &adminServiceImpl{groupRepo: groupRepo}
maxReasoningEffort := "low"
reasoningEffortMappings := []ReasoningEffortMapping{{From: "max", To: "high"}}
group, err := svc.UpdateGroup(context.Background(), 99, &UpdateGroupInput{
MaxReasoningEffort: &maxReasoningEffort,
ReasoningEffortMappings: &reasoningEffortMappings,
CopyAccountsFromGroupIDs: []int64{10, 20},
})
require.NoError(t, err)
require.Equal(t, PlatformComposite, group.Platform)
require.Equal(t, "low", group.MaxReasoningEffort)
require.Equal(t, reasoningEffortMappings, group.ReasoningEffortMappings)
require.Equal(t, int64(99), clearedGroupID)
require.ElementsMatch(t, []int64{10, 20}, copiedFrom)
require.Equal(t, int64(99), boundGroupID)
require.ElementsMatch(t, []int64{301, 302}, boundAccountIDs)
}
func TestAdminService_CreateAccountAllowsCompositeGroupAssignment(t *testing.T) {
accountRepo := &accountRepoStubForBulkUpdate{createID: 7}
groupRepo := &groupRepoStubForAdmin{
getByIDByID: map[int64]*Group{
99: {ID: 99, Platform: PlatformComposite},
},
}
svc := &adminServiceImpl{accountRepo: accountRepo, groupRepo: groupRepo}
account, err := svc.CreateAccount(context.Background(), &CreateAccountInput{
Name: "OpenAI account",
Platform: PlatformOpenAI,
Type: AccountTypeAPIKey,
Concurrency: 1,
GroupIDs: []int64{99},
SkipDefaultGroupBind: true,
SkipMixedChannelCheck: true,
})
require.NoError(t, err)
require.Equal(t, int64(7), account.ID)
require.Equal(t, PlatformOpenAI, accountRepo.createAccount.Platform)
require.ElementsMatch(t, []int64{99}, accountRepo.bindGroupsByAccount[7])
}
func TestAdminService_UpdateAccountAllowsCompositeGroupAssignment(t *testing.T) {
accountRepo := &accountRepoStubForBulkUpdate{
getByIDAccounts: map[int64]*Account{
7: {ID: 7, Platform: PlatformGemini, Type: AccountTypeAPIKey, Status: StatusActive, Extra: map[string]any{}},
},
}
groupRepo := &groupRepoStubForAdmin{
getByIDByID: map[int64]*Group{
99: {ID: 99, Platform: PlatformComposite},
},
}
svc := &adminServiceImpl{accountRepo: accountRepo, groupRepo: groupRepo}
groupIDs := []int64{99}
account, err := svc.UpdateAccount(context.Background(), 7, &UpdateAccountInput{
GroupIDs: &groupIDs,
SkipMixedChannelCheck: true,
})
require.NoError(t, err)
require.Equal(t, int64(7), account.ID)
require.Len(t, accountRepo.updatedAccounts, 1)
require.ElementsMatch(t, []int64{99}, accountRepo.bindGroupsByAccount[7])
}
func TestAdminService_CompositeModelsListCandidatesIncludeConcreteAccountMappings(t *testing.T) {
accountRepo := &accountRepoStubForCompositeModelsList{
accounts: []Account{
{
ID: 1,
Platform: PlatformOpenAI,
Credentials: map[string]any{
"model_mapping": map[string]any{"gpt-custom": "gpt-5"},
},
},
{
ID: 2,
Platform: PlatformGemini,
Credentials: map[string]any{
"model_mapping": map[string]any{"gemini-custom": "gemini-2.5-flash"},
},
},
{
ID: 3,
Platform: PlatformKimi,
Credentials: map[string]any{
"model_mapping": map[string]any{"kimi-custom": "kimi-k2"},
},
},
},
}
groupRepo := &groupRepoStubForAdmin{
getByIDByID: map[int64]*Group{
99: {ID: 99, Platform: PlatformComposite},
},
}
svc := &adminServiceImpl{accountRepo: accountRepo, groupRepo: groupRepo}
candidates, err := svc.GetGroupModelsListCandidates(context.Background(), 99, PlatformComposite)
require.NoError(t, err)
require.Contains(t, candidates, "gpt-custom")
require.Contains(t, candidates, "gemini-custom")
require.Contains(t, candidates, "kimi-custom")
require.Contains(t, candidates, "gpt-5.5")
require.Contains(t, candidates, "gemini-2.5-flash")
}
// 独立 CN 分组的模型列表候选沿用 default 分支的 Claude 默认列表;
// composite 支持不得改变独立分组的候选语义。
func TestAdminService_CNProviderModelsListCandidatesKeepClaudeDefaults(t *testing.T) {
want := make([]string, 0, len(claude.DefaultModels))
for _, model := range claude.DefaultModels {
want = append(want, model.ID)
}
for _, platform := range []string{PlatformKimi, PlatformZhipu, PlatformDeepseek} {
require.Equal(t, want, defaultModelsListCandidateIDs(platform), "platform=%s", platform)
}
}