| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- //go:build unit
- package service
- import (
- "context"
- "errors"
- "net/http"
- "reflect"
- "testing"
- infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
- "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
- "github.com/stretchr/testify/require"
- )
- type accountRepoStubForBulkUpdate struct {
- accountRepoStub
- bulkUpdateErr error
- bulkUpdateIDs []int64
- bulkUpdateCalls int
- lastBulkUpdate AccountBulkUpdate
- bindGroupErrByID map[int64]error
- bindGroupsCalls []int64
- bindGroupsByAccount map[int64][]int64
- createAccount *Account
- createID int64
- createErr error
- updatedAccounts []*Account
- updateErr error
- getByIDsAccounts []*Account
- getByIDsErr error
- getByIDsCalled bool
- getByIDsIDs []int64
- getByIDAccounts map[int64]*Account
- getByIDErrByID map[int64]error
- getByIDCalled []int64
- listByGroupData map[int64][]Account
- listByGroupErr map[int64]error
- listData []Account
- listResult *pagination.PaginationResult
- listErr error
- listCalled bool
- lastListParams pagination.PaginationParams
- lastListFilters struct {
- platform string
- accountType string
- status string
- search string
- groupID int64
- privacyMode string
- }
- }
- func (s *accountRepoStubForBulkUpdate) BulkUpdate(_ context.Context, ids []int64, updates AccountBulkUpdate) (int64, error) {
- s.bulkUpdateCalls++
- s.bulkUpdateIDs = append([]int64{}, ids...)
- s.lastBulkUpdate = updates
- if s.bulkUpdateErr != nil {
- return 0, s.bulkUpdateErr
- }
- return int64(len(ids)), nil
- }
- func requireApplicationErrorReason(t *testing.T, err error, reason string) {
- t.Helper()
- var appErr *infraerrors.ApplicationError
- require.ErrorAs(t, err, &appErr)
- require.Equal(t, reason, appErr.Reason)
- }
- func (s *accountRepoStubForBulkUpdate) Create(_ context.Context, account *Account) error {
- s.createAccount = account
- if s.createID > 0 {
- account.ID = s.createID
- }
- return s.createErr
- }
- func (s *accountRepoStubForBulkUpdate) Update(_ context.Context, account *Account) error {
- s.updatedAccounts = append(s.updatedAccounts, account)
- return s.updateErr
- }
- func (s *accountRepoStubForBulkUpdate) BindGroups(_ context.Context, accountID int64, groupIDs []int64) error {
- s.bindGroupsCalls = append(s.bindGroupsCalls, accountID)
- if s.bindGroupsByAccount == nil {
- s.bindGroupsByAccount = make(map[int64][]int64)
- }
- s.bindGroupsByAccount[accountID] = append([]int64{}, groupIDs...)
- if err, ok := s.bindGroupErrByID[accountID]; ok {
- return err
- }
- return nil
- }
- func (s *accountRepoStubForBulkUpdate) GetByIDs(_ context.Context, ids []int64) ([]*Account, error) {
- s.getByIDsCalled = true
- s.getByIDsIDs = append([]int64{}, ids...)
- if s.getByIDsErr != nil {
- return nil, s.getByIDsErr
- }
- return s.getByIDsAccounts, nil
- }
- func (s *accountRepoStubForBulkUpdate) GetByID(_ context.Context, id int64) (*Account, error) {
- s.getByIDCalled = append(s.getByIDCalled, id)
- if err, ok := s.getByIDErrByID[id]; ok {
- return nil, err
- }
- if account, ok := s.getByIDAccounts[id]; ok {
- return account, nil
- }
- return nil, errors.New("account not found")
- }
- func (s *accountRepoStubForBulkUpdate) ListByGroup(_ context.Context, groupID int64) ([]Account, error) {
- if err, ok := s.listByGroupErr[groupID]; ok {
- return nil, err
- }
- if rows, ok := s.listByGroupData[groupID]; ok {
- return rows, nil
- }
- return nil, nil
- }
- func (s *accountRepoStubForBulkUpdate) ListAllWithFilters(context.Context, string, string, string, string, int64, string) ([]Account, error) {
- return nil, nil
- }
- func (s *accountRepoStubForBulkUpdate) ListWithFilters(_ context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, *pagination.PaginationResult, error) {
- s.listCalled = true
- s.lastListParams = params
- s.lastListFilters.platform = platform
- s.lastListFilters.accountType = accountType
- s.lastListFilters.status = status
- s.lastListFilters.search = search
- s.lastListFilters.groupID = groupID
- s.lastListFilters.privacyMode = privacyMode
- if s.listErr != nil {
- return nil, nil, s.listErr
- }
- if s.listResult != nil {
- return s.listData, s.listResult, nil
- }
- return s.listData, &pagination.PaginationResult{Total: int64(len(s.listData))}, nil
- }
- // TestAdminService_BulkUpdateAccounts_AllSuccessIDs 验证批量更新成功时返回 success_ids/failed_ids。
- func TestAdminService_BulkUpdateAccounts_AllSuccessIDs(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{}
- svc := &adminServiceImpl{accountRepo: repo}
- schedulable := true
- input := &BulkUpdateAccountsInput{
- AccountIDs: []int64{1, 2, 3},
- Schedulable: &schedulable,
- }
- result, err := svc.BulkUpdateAccounts(context.Background(), input)
- require.NoError(t, err)
- require.Equal(t, 3, result.Success)
- require.Equal(t, 0, result.Failed)
- require.ElementsMatch(t, []int64{1, 2, 3}, result.SuccessIDs)
- require.Empty(t, result.FailedIDs)
- require.Len(t, result.Results, 3)
- }
- func TestAdminService_BulkUpdateAccounts_RejectsRateChangeForSyncedAccounts(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{
- getByIDsAccounts: []*Account{
- {
- ID: 1,
- Extra: map[string]any{
- UpstreamBillingProbeEnabledExtraKey: true,
- UpstreamBillingRateSyncEnabledExtraKey: true,
- },
- },
- {ID: 2, Extra: map[string]any{}},
- },
- }
- svc := &adminServiceImpl{accountRepo: repo}
- rateMultiplier := 0.5
- result, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{1, 2},
- RateMultiplier: &rateMultiplier,
- })
- require.Nil(t, result)
- require.Error(t, err)
- var appErr *infraerrors.ApplicationError
- require.ErrorAs(t, err, &appErr)
- require.Equal(t, int32(http.StatusConflict), appErr.Code)
- require.Equal(t, "UPSTREAM_BILLING_RATE_SYNC_BULK_CONFLICT", appErr.Reason)
- require.Equal(t, "1", appErr.Metadata["count"])
- require.True(t, repo.getByIDsCalled)
- require.Empty(t, repo.bulkUpdateIDs, "rate conflict must be rejected before any write")
- }
- // TestAdminService_BulkUpdateAccounts_PartialFailureIDs 验证部分失败时 success_ids/failed_ids 正确。
- func TestAdminService_BulkUpdateAccounts_PartialFailureIDs(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{
- bindGroupErrByID: map[int64]error{
- 2: errors.New("bind failed"),
- },
- }
- svc := &adminServiceImpl{
- accountRepo: repo,
- groupRepo: &groupRepoStubForAdmin{getByID: &Group{ID: 10, Name: "g10"}},
- }
- groupIDs := []int64{10}
- schedulable := false
- input := &BulkUpdateAccountsInput{
- AccountIDs: []int64{1, 2, 3},
- GroupIDs: &groupIDs,
- Schedulable: &schedulable,
- SkipMixedChannelCheck: true,
- }
- result, err := svc.BulkUpdateAccounts(context.Background(), input)
- require.NoError(t, err)
- require.Equal(t, 2, result.Success)
- require.Equal(t, 1, result.Failed)
- require.ElementsMatch(t, []int64{1, 3}, result.SuccessIDs)
- require.ElementsMatch(t, []int64{2}, result.FailedIDs)
- require.Len(t, result.Results, 3)
- }
- func TestAdminService_BulkUpdateAccounts_NilGroupRepoReturnsError(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{}
- svc := &adminServiceImpl{accountRepo: repo}
- groupIDs := []int64{10}
- input := &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- GroupIDs: &groupIDs,
- }
- result, err := svc.BulkUpdateAccounts(context.Background(), input)
- require.Nil(t, result)
- require.Error(t, err)
- require.Contains(t, err.Error(), "group repository not configured")
- }
- // TestAdminService_BulkUpdateAccounts_MixedChannelPreCheckBlocksOnExistingConflict verifies
- // that the global pre-check detects a conflict with existing group members and returns an
- // error before any DB write is performed.
- func TestAdminService_BulkUpdateAccounts_MixedChannelPreCheckBlocksOnExistingConflict(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{
- getByIDsAccounts: []*Account{
- {ID: 1, Platform: PlatformAntigravity},
- },
- // Group 10 already contains an Anthropic account.
- listByGroupData: map[int64][]Account{
- 10: {{ID: 99, Platform: PlatformAnthropic}},
- },
- }
- svc := &adminServiceImpl{
- accountRepo: repo,
- groupRepo: &groupRepoStubForAdmin{getByID: &Group{ID: 10, Name: "target-group"}},
- }
- groupIDs := []int64{10}
- input := &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- GroupIDs: &groupIDs,
- }
- result, err := svc.BulkUpdateAccounts(context.Background(), input)
- require.Nil(t, result)
- require.Error(t, err)
- require.Contains(t, err.Error(), "mixed channel")
- // No BindGroups should have been called since the check runs before any write.
- require.Empty(t, repo.bindGroupsCalls)
- }
- func TestAdminServiceBulkUpdateAccounts_ResolvesIDsFromFilters(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{
- listData: []Account{
- {ID: 7},
- {ID: 11},
- },
- listResult: &pagination.PaginationResult{Total: 2},
- }
- svc := &adminServiceImpl{accountRepo: repo}
- schedulable := true
- input := &BulkUpdateAccountsInput{
- Schedulable: &schedulable,
- }
- filtersField := reflect.ValueOf(input).Elem().FieldByName("Filters")
- require.True(t, filtersField.IsValid(), "BulkUpdateAccountsInput should expose Filters for filter-target bulk update")
- require.Equal(t, reflect.Ptr, filtersField.Kind(), "BulkUpdateAccountsInput.Filters should be a pointer field")
- filtersValue := reflect.New(filtersField.Type().Elem())
- filtersValue.Elem().FieldByName("Platform").SetString(PlatformOpenAI)
- filtersValue.Elem().FieldByName("Type").SetString(AccountTypeOAuth)
- filtersValue.Elem().FieldByName("Status").SetString(StatusActive)
- filtersValue.Elem().FieldByName("Group").SetString("12")
- filtersValue.Elem().FieldByName("PrivacyMode").SetString(PrivacyModeCFBlocked)
- filtersValue.Elem().FieldByName("Search").SetString("bulk-target")
- filtersField.Set(filtersValue)
- result, err := svc.BulkUpdateAccounts(context.Background(), input)
- require.NoError(t, err)
- require.True(t, repo.listCalled, "expected filter-target bulk update to resolve matching IDs via account list filters")
- require.Equal(t, PlatformOpenAI, repo.lastListFilters.platform)
- require.Equal(t, AccountTypeOAuth, repo.lastListFilters.accountType)
- require.Equal(t, StatusActive, repo.lastListFilters.status)
- require.Equal(t, "bulk-target", repo.lastListFilters.search)
- require.Equal(t, int64(12), repo.lastListFilters.groupID)
- require.Equal(t, PrivacyModeCFBlocked, repo.lastListFilters.privacyMode)
- require.Equal(t, []int64{7, 11}, repo.bulkUpdateIDs)
- require.Equal(t, 2, result.Success)
- require.Equal(t, 0, result.Failed)
- require.Equal(t, []int64{7, 11}, result.SuccessIDs)
- }
- func TestAdminServiceBulkUpdateAccounts_NormalizesOpenAISettings(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{getByIDsAccounts: []*Account{
- {ID: 1, Platform: PlatformOpenAI, Type: AccountTypeAPIKey},
- {ID: 2, Platform: PlatformOpenAI, Type: AccountTypeAPIKey},
- }}
- svc := &adminServiceImpl{accountRepo: repo}
- result, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{1, 2},
- Credentials: map[string]any{
- openAIEndpointCapabilitiesCredentialKey: []any{"chat_completions", "embeddings"},
- },
- Extra: map[string]any{
- openAILongContextBillingEnabledKey: true,
- "openai_responses_mode": "auto",
- },
- })
- require.NoError(t, err)
- require.Equal(t, 2, result.Success)
- require.Zero(t, result.LongContextInheritedCount)
- require.Equal(t, 1, repo.bulkUpdateCalls)
- require.Contains(t, repo.lastBulkUpdate.Credentials, openAIEndpointCapabilitiesCredentialKey)
- require.Nil(t, repo.lastBulkUpdate.Credentials[openAIEndpointCapabilitiesCredentialKey])
- require.Equal(t, true, repo.lastBulkUpdate.Extra[openAILongContextBillingEnabledKey])
- require.Contains(t, repo.lastBulkUpdate.Extra, "openai_responses_mode")
- require.Nil(t, repo.lastBulkUpdate.Extra["openai_responses_mode"])
- }
- func TestAdminServiceBulkUpdateAccounts_AcceptsLongContextAccountTypes(t *testing.T) {
- for _, accountType := range []string{AccountTypeOAuth, AccountTypeSetupToken, AccountTypeAPIKey} {
- t.Run(accountType, func(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{getByIDsAccounts: []*Account{{
- ID: 1, Platform: PlatformOpenAI, Type: accountType,
- }}}
- svc := &adminServiceImpl{accountRepo: repo}
- result, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- Extra: map[string]any{openAILongContextBillingEnabledKey: false},
- })
- require.NoError(t, err)
- require.Equal(t, 1, result.Success)
- require.Equal(t, 1, repo.bulkUpdateCalls)
- })
- }
- }
- func TestAdminServiceBulkUpdateAccounts_EmbeddingsOnlyResetsResponsesMode(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{getByIDsAccounts: []*Account{
- {ID: 1, Platform: PlatformOpenAI, Type: AccountTypeAPIKey},
- }}
- svc := &adminServiceImpl{accountRepo: repo}
- _, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- Credentials: map[string]any{
- openAIEndpointCapabilitiesCredentialKey: []string{"embeddings"},
- },
- })
- require.NoError(t, err)
- require.Equal(t, []string{"embeddings"}, repo.lastBulkUpdate.Credentials[openAIEndpointCapabilitiesCredentialKey])
- require.Contains(t, repo.lastBulkUpdate.Extra, "openai_responses_mode")
- require.Nil(t, repo.lastBulkUpdate.Extra["openai_responses_mode"])
- }
- func TestAdminServiceBulkUpdateAccounts_RejectsInvalidOpenAISettingValuesBeforeWrite(t *testing.T) {
- tests := []struct {
- name string
- credentials map[string]any
- extra map[string]any
- reason string
- }{
- {name: "long context type", extra: map[string]any{openAILongContextBillingEnabledKey: "true"}, reason: "OPENAI_LONG_CONTEXT_BILLING_INVALID"},
- {name: "empty capabilities", credentials: map[string]any{openAIEndpointCapabilitiesCredentialKey: []any{}}, reason: "OPENAI_ENDPOINT_CAPABILITIES_INVALID"},
- {name: "unknown capability", credentials: map[string]any{openAIEndpointCapabilitiesCredentialKey: []any{"responses"}}, reason: "OPENAI_ENDPOINT_CAPABILITIES_INVALID"},
- {name: "capabilities type", credentials: map[string]any{openAIEndpointCapabilitiesCredentialKey: "chat_completions"}, reason: "OPENAI_ENDPOINT_CAPABILITIES_INVALID"},
- {name: "responses mode", extra: map[string]any{"openai_responses_mode": "sometimes"}, reason: "OPENAI_RESPONSES_MODE_INVALID"},
- {name: "responses type", extra: map[string]any{"openai_responses_mode": true}, reason: "OPENAI_RESPONSES_MODE_INVALID"},
- {
- name: "embeddings conflict",
- credentials: map[string]any{openAIEndpointCapabilitiesCredentialKey: []any{"embeddings"}},
- extra: map[string]any{"openai_responses_mode": "force_responses"},
- reason: "OPENAI_RESPONSES_MODE_INVALID",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{}
- svc := &adminServiceImpl{accountRepo: repo}
- result, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- Credentials: tt.credentials,
- Extra: tt.extra,
- })
- require.Nil(t, result)
- requireApplicationErrorReason(t, err, tt.reason)
- require.Zero(t, repo.bulkUpdateCalls)
- })
- }
- }
- func TestAdminServiceBulkUpdateAccounts_RejectsInvalidOpenAITargetsBeforeWrite(t *testing.T) {
- tests := []struct {
- name string
- accounts []*Account
- input *BulkUpdateAccountsInput
- }{
- {
- name: "missing account",
- accounts: []*Account{{ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth}},
- input: &BulkUpdateAccountsInput{
- AccountIDs: []int64{1, 2},
- Extra: map[string]any{openAILongContextBillingEnabledKey: true},
- },
- },
- {
- name: "mixed platform long context",
- accounts: []*Account{{ID: 1, Platform: PlatformAnthropic, Type: AccountTypeOAuth}},
- input: &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- Extra: map[string]any{openAILongContextBillingEnabledKey: true},
- },
- },
- {
- name: "oauth endpoint capabilities",
- accounts: []*Account{{ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth}},
- input: &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- Credentials: map[string]any{openAIEndpointCapabilitiesCredentialKey: nil},
- },
- },
- {
- name: "unsupported OpenAI long context account type",
- accounts: []*Account{{ID: 1, Platform: PlatformOpenAI, Type: AccountTypeServiceAccount}},
- input: &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- Extra: map[string]any{openAILongContextBillingEnabledKey: true},
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{getByIDsAccounts: tt.accounts}
- svc := &adminServiceImpl{accountRepo: repo}
- result, err := svc.BulkUpdateAccounts(context.Background(), tt.input)
- require.Nil(t, result)
- requireApplicationErrorReason(t, err, "OPENAI_BULK_TARGET_INVALID")
- require.Zero(t, repo.bulkUpdateCalls)
- })
- }
- }
- func TestAdminServiceBulkUpdateAccounts_ForcedResponsesRequiresChatCapability(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{getByIDsAccounts: []*Account{{
- ID: 1,
- Platform: PlatformOpenAI,
- Type: AccountTypeAPIKey,
- Credentials: map[string]any{
- openAIEndpointCapabilitiesCredentialKey: []any{"embeddings"},
- },
- }}}
- svc := &adminServiceImpl{accountRepo: repo}
- result, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- Extra: map[string]any{"openai_responses_mode": "force_chat_completions"},
- })
- require.Nil(t, result)
- requireApplicationErrorReason(t, err, "OPENAI_BULK_TARGET_INVALID")
- require.Zero(t, repo.bulkUpdateCalls)
- }
- func TestAdminServiceBulkUpdateAccounts_ForcedResponsesAcceptsChatCapabilityUpdate(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{getByIDsAccounts: []*Account{{
- ID: 1,
- Platform: PlatformOpenAI,
- Type: AccountTypeAPIKey,
- Credentials: map[string]any{
- openAIEndpointCapabilitiesCredentialKey: []any{"embeddings"},
- },
- }}}
- svc := &adminServiceImpl{accountRepo: repo}
- _, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- Credentials: map[string]any{
- openAIEndpointCapabilitiesCredentialKey: []any{"chat_completions"},
- },
- Extra: map[string]any{"openai_responses_mode": "force_responses"},
- })
- require.NoError(t, err)
- require.Equal(t, 1, repo.bulkUpdateCalls)
- }
- func TestAdminServiceBulkUpdateAccounts_ReportsLongContextShadowInheritance(t *testing.T) {
- parentID := int64(1)
- repo := &accountRepoStubForBulkUpdate{getByIDsAccounts: []*Account{
- {ID: parentID, Platform: PlatformOpenAI, Type: AccountTypeOAuth},
- {ID: 2, Platform: PlatformOpenAI, Type: AccountTypeOAuth, ParentAccountID: &parentID},
- }}
- svc := &adminServiceImpl{accountRepo: repo}
- result, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{parentID, 2},
- Extra: map[string]any{openAILongContextBillingEnabledKey: true},
- })
- require.NoError(t, err)
- require.Equal(t, 1, result.LongContextInheritedCount)
- require.Equal(t, 1, repo.bulkUpdateCalls)
- }
- func TestAdminServiceBulkUpdateAccounts_RequiresParentForShadowOnlyLongContextUpdate(t *testing.T) {
- parentID := int64(10)
- repo := &accountRepoStubForBulkUpdate{getByIDsAccounts: []*Account{
- {ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth, ParentAccountID: &parentID},
- {ID: 2, Platform: PlatformOpenAI, Type: AccountTypeOAuth, ParentAccountID: &parentID},
- }}
- svc := &adminServiceImpl{accountRepo: repo}
- result, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{1, 2},
- Extra: map[string]any{openAILongContextBillingEnabledKey: true},
- })
- require.Nil(t, result)
- requireApplicationErrorReason(t, err, "OPENAI_LONG_CONTEXT_PARENT_REQUIRED")
- require.Zero(t, repo.bulkUpdateCalls)
- }
- func TestAdminServiceBulkUpdateAccounts_ShadowLongContextAllowsOtherUpdates(t *testing.T) {
- parentID := int64(10)
- repo := &accountRepoStubForBulkUpdate{getByIDsAccounts: []*Account{{
- ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth, ParentAccountID: &parentID,
- }}}
- svc := &adminServiceImpl{accountRepo: repo}
- status := StatusDisabled
- result, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- AccountIDs: []int64{1},
- Status: status,
- Extra: map[string]any{openAILongContextBillingEnabledKey: false},
- })
- require.NoError(t, err)
- require.Equal(t, 1, result.LongContextInheritedCount)
- require.Equal(t, 1, repo.bulkUpdateCalls)
- require.NotNil(t, repo.lastBulkUpdate.Status)
- require.Equal(t, status, *repo.lastBulkUpdate.Status)
- }
- func TestAdminServiceBulkUpdateAccounts_ValidatesFilterResolvedOpenAITargets(t *testing.T) {
- repo := &accountRepoStubForBulkUpdate{
- listData: []Account{{ID: 7}},
- listResult: &pagination.PaginationResult{Total: 1},
- getByIDsAccounts: []*Account{{ID: 7, Platform: PlatformAnthropic, Type: AccountTypeOAuth}},
- }
- svc := &adminServiceImpl{accountRepo: repo}
- result, err := svc.BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
- Filters: &BulkUpdateAccountFilters{Platform: PlatformOpenAI},
- Extra: map[string]any{openAILongContextBillingEnabledKey: true},
- })
- require.Nil(t, result)
- requireApplicationErrorReason(t, err, "OPENAI_BULK_TARGET_INVALID")
- require.Equal(t, []int64{7}, repo.getByIDsIDs)
- require.Zero(t, repo.bulkUpdateCalls)
- }
|