| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- //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",
- MaxReasoningEffortOverLimit: ReasoningEffortOverLimitDeny,
- 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, ReasoningEffortOverLimitDeny, groupRepo.created.MaxReasoningEffortOverLimit)
- 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"
- maxReasoningEffortOverLimit := ReasoningEffortOverLimitDeny
- reasoningEffortMappings := []ReasoningEffortMapping{{From: "max", To: "high"}}
- group, err := svc.UpdateGroup(context.Background(), 99, &UpdateGroupInput{
- MaxReasoningEffort: &maxReasoningEffort,
- MaxReasoningEffortOverLimit: &maxReasoningEffortOverLimit,
- 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, ReasoningEffortOverLimitDeny, group.MaxReasoningEffortOverLimit)
- 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, PlatformMiniMax} {
- require.Equal(t, want, defaultModelsListCandidateIDs(platform), "platform=%s", platform)
- }
- }
|