| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814 |
- //go:build unit
- package service
- import (
- "context"
- "errors"
- "testing"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
- "github.com/stretchr/testify/require"
- )
- type userRepoStub struct {
- user *User
- usersByID map[int64]*User
- getErr error
- createErr error
- deleteErr error
- exists bool
- existsErr error
- aliasExists bool
- aliasErr error
- guardedCreates int
- nextID int64
- created []*User
- updated []*User
- deletedIDs []int64
- usersByEmail map[string]*User
- getByEmailErr error
- getByEmailMisses int
- domainCounts map[string]int
- domainCountErr error
- domainLimitErr error
- domainLimitedCreates int
- }
- func (s *userRepoStub) CountUsersByEmailDomain(_ context.Context, domain string) (int, error) {
- if s.domainCountErr != nil {
- return 0, s.domainCountErr
- }
- return s.domainCounts[domain], nil
- }
- func (s *userRepoStub) CreateWithEmailAliasGuardAndDomainLimit(ctx context.Context, user *User, domain string) error {
- s.domainLimitedCreates++
- if s.domainLimitErr != nil {
- return s.domainLimitErr
- }
- if s.domainCounts[domain] > 0 {
- return ErrEmailDomainRegistrationLimit
- }
- return s.CreateWithEmailAliasGuard(ctx, user)
- }
- func (s *userRepoStub) Create(ctx context.Context, user *User) error {
- if s.createErr != nil {
- return s.createErr
- }
- if s.nextID != 0 && user.ID == 0 {
- user.ID = s.nextID
- }
- s.created = append(s.created, user)
- if s.usersByEmail == nil {
- s.usersByEmail = make(map[string]*User)
- }
- s.usersByEmail[user.Email] = user
- s.user = user
- return nil
- }
- func (s *userRepoStub) CreateWithEmailAliasGuard(ctx context.Context, user *User) error {
- s.guardedCreates++
- if s.aliasErr != nil {
- return s.aliasErr
- }
- if s.aliasExists {
- return ErrEmailExists
- }
- return s.Create(ctx, user)
- }
- func (s *userRepoStub) GetByID(ctx context.Context, id int64) (*User, error) {
- if s.getErr != nil {
- return nil, s.getErr
- }
- if s.usersByID != nil {
- if user, ok := s.usersByID[id]; ok {
- return user, nil
- }
- return nil, ErrUserNotFound
- }
- if s.user == nil {
- return nil, ErrUserNotFound
- }
- return s.user, nil
- }
- func (s *userRepoStub) GetByEmail(ctx context.Context, email string) (*User, error) {
- if s.getByEmailErr != nil {
- return nil, s.getByEmailErr
- }
- if s.getByEmailMisses > 0 {
- s.getByEmailMisses--
- return nil, ErrUserNotFound
- }
- if s.usersByEmail != nil {
- if user, ok := s.usersByEmail[email]; ok {
- return user, nil
- }
- }
- if s.user != nil && s.user.Email == email {
- return s.user, nil
- }
- return nil, ErrUserNotFound
- }
- func (s *userRepoStub) GetFirstAdmin(ctx context.Context) (*User, error) {
- panic("unexpected GetFirstAdmin call")
- }
- func (s *userRepoStub) Update(ctx context.Context, user *User, fields UserUpdateFields) error {
- s.updated = append(s.updated, user)
- if s.usersByEmail == nil {
- s.usersByEmail = make(map[string]*User)
- }
- s.usersByEmail[user.Email] = user
- s.user = user
- return nil
- }
- func (s *userRepoStub) Delete(ctx context.Context, id int64) error {
- s.deletedIDs = append(s.deletedIDs, id)
- return s.deleteErr
- }
- func (s *userRepoStub) GetUserAvatar(ctx context.Context, userID int64) (*UserAvatar, error) {
- panic("unexpected GetUserAvatar call")
- }
- func (s *userRepoStub) UpsertUserAvatar(ctx context.Context, userID int64, input UpsertUserAvatarInput) (*UserAvatar, error) {
- panic("unexpected UpsertUserAvatar call")
- }
- func (s *userRepoStub) DeleteUserAvatar(ctx context.Context, userID int64) error {
- panic("unexpected DeleteUserAvatar call")
- }
- func (s *userRepoStub) List(ctx context.Context, params pagination.PaginationParams) ([]User, *pagination.PaginationResult, error) {
- panic("unexpected List call")
- }
- func (s *userRepoStub) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters UserListFilters) ([]User, *pagination.PaginationResult, error) {
- panic("unexpected ListWithFilters call")
- }
- func (s *userRepoStub) GetLatestUsedAtByUserIDs(ctx context.Context, userIDs []int64) (map[int64]*time.Time, error) {
- panic("unexpected GetLatestUsedAtByUserIDs call")
- }
- func (s *userRepoStub) GetLatestUsedAtByUserID(ctx context.Context, userID int64) (*time.Time, error) {
- panic("unexpected GetLatestUsedAtByUserID call")
- }
- func (s *userRepoStub) UpdateUserLastActiveAt(ctx context.Context, userID int64, activeAt time.Time) error {
- panic("unexpected UpdateUserLastActiveAt call")
- }
- func (s *userRepoStub) UpdateBalance(ctx context.Context, id int64, amount float64) error {
- panic("unexpected UpdateBalance call")
- }
- func (s *userRepoStub) DeductBalance(ctx context.Context, id int64, amount float64) error {
- panic("unexpected DeductBalance call")
- }
- func (s *userRepoStub) AdjustBalance(ctx context.Context, id int64, delta float64) (BalanceChange, error) {
- panic("unexpected AdjustBalance call")
- }
- func (s *userRepoStub) SetBalance(ctx context.Context, id int64, value float64) (BalanceChange, error) {
- panic("unexpected SetBalance call")
- }
- func (s *userRepoStub) UpdateConcurrency(ctx context.Context, id int64, amount int) error {
- panic("unexpected UpdateConcurrency call")
- }
- func (s *userRepoStub) BatchSetConcurrency(context.Context, []int64, int) (int, error) { return 0, nil }
- func (s *userRepoStub) BatchAddConcurrency(context.Context, []int64, int) (int, error) { return 0, nil }
- func (s *userRepoStub) BatchUpdateLimits(context.Context, []int64, *int, *int) (int, error) {
- return 0, nil
- }
- func (s *userRepoStub) ExistsByEmail(ctx context.Context, email string) (bool, error) {
- if s.existsErr != nil {
- return false, s.existsErr
- }
- return s.exists, nil
- }
- func (s *userRepoStub) ExistsByEmailAlias(ctx context.Context, email string) (bool, error) {
- if s.aliasErr != nil {
- return false, s.aliasErr
- }
- return s.aliasExists, nil
- }
- func (s *userRepoStub) RemoveGroupFromAllowedGroups(ctx context.Context, groupID int64) (int64, error) {
- panic("unexpected RemoveGroupFromAllowedGroups call")
- }
- func (s *userRepoStub) RemoveGroupFromUserAllowedGroups(ctx context.Context, userID int64, groupID int64) error {
- panic("unexpected RemoveGroupFromUserAllowedGroups call")
- }
- func (s *userRepoStub) AddGroupToAllowedGroups(ctx context.Context, userID int64, groupID int64) error {
- panic("unexpected AddGroupToAllowedGroups call")
- }
- func (s *userRepoStub) ListUserAuthIdentities(ctx context.Context, userID int64) ([]UserAuthIdentityRecord, error) {
- panic("unexpected ListUserAuthIdentities call")
- }
- func (s *userRepoStub) UnbindUserAuthProvider(context.Context, int64, string) error {
- panic("unexpected UnbindUserAuthProvider call")
- }
- func (s *userRepoStub) UpdateTotpSecret(ctx context.Context, userID int64, encryptedSecret *string) error {
- panic("unexpected UpdateTotpSecret call")
- }
- func (s *userRepoStub) EnableTotp(ctx context.Context, userID int64) error {
- panic("unexpected EnableTotp call")
- }
- func (s *userRepoStub) DisableTotp(ctx context.Context, userID int64) error {
- panic("unexpected DisableTotp call")
- }
- func (s *userRepoStub) GetByIDIncludeDeleted(ctx context.Context, id int64) (*User, error) {
- return s.GetByID(ctx, id)
- }
- type groupRepoStub struct {
- affectedUserIDs []int64
- deleteErr error
- deleteCalls []int64
- guardedCalls []int64
- }
- func (s *groupRepoStub) Create(ctx context.Context, group *Group) error {
- panic("unexpected Create call")
- }
- func (s *groupRepoStub) GetByID(ctx context.Context, id int64) (*Group, error) {
- panic("unexpected GetByID call")
- }
- func (s *groupRepoStub) GetByIDLite(ctx context.Context, id int64) (*Group, error) {
- panic("unexpected GetByIDLite call")
- }
- func (s *groupRepoStub) Update(ctx context.Context, group *Group) error {
- panic("unexpected Update call")
- }
- func (s *groupRepoStub) Delete(ctx context.Context, id int64) error {
- panic("unexpected Delete call")
- }
- func (s *groupRepoStub) DeleteCascade(ctx context.Context, id int64) ([]int64, error) {
- s.deleteCalls = append(s.deleteCalls, id)
- return s.affectedUserIDs, s.deleteErr
- }
- func (s *groupRepoStub) DeleteCascadeIfEmpty(ctx context.Context, id int64) ([]int64, error) {
- s.guardedCalls = append(s.guardedCalls, id)
- return s.affectedUserIDs, s.deleteErr
- }
- func (s *groupRepoStub) List(ctx context.Context, params pagination.PaginationParams) ([]Group, *pagination.PaginationResult, error) {
- panic("unexpected List call")
- }
- func (s *groupRepoStub) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, status, search string, isExclusive *bool) ([]Group, *pagination.PaginationResult, error) {
- panic("unexpected ListWithFilters call")
- }
- func (s *groupRepoStub) ListActive(ctx context.Context) ([]Group, error) {
- panic("unexpected ListActive call")
- }
- func (s *groupRepoStub) ListActiveByPlatform(ctx context.Context, platform string) ([]Group, error) {
- panic("unexpected ListActiveByPlatform call")
- }
- func (s *groupRepoStub) ExistsByName(ctx context.Context, name string) (bool, error) {
- panic("unexpected ExistsByName call")
- }
- func (s *groupRepoStub) GetAccountCount(ctx context.Context, groupID int64) (int64, int64, error) {
- panic("unexpected GetAccountCount call")
- }
- func (s *groupRepoStub) DeleteAccountGroupsByGroupID(ctx context.Context, groupID int64) (int64, error) {
- panic("unexpected DeleteAccountGroupsByGroupID call")
- }
- func (s *groupRepoStub) BindAccountsToGroup(ctx context.Context, groupID int64, accountIDs []int64) error {
- panic("unexpected BindAccountsToGroup call")
- }
- func (s *groupRepoStub) GetAccountIDsByGroupIDs(ctx context.Context, groupIDs []int64) ([]int64, error) {
- panic("unexpected GetAccountIDsByGroupIDs call")
- }
- func (s *groupRepoStub) UpdateSortOrders(ctx context.Context, updates []GroupSortOrderUpdate) error {
- return nil
- }
- type deleteGroupAPIKeyRepoStub struct {
- apiKeyRepoStubForGroupUpdate
- keys []string
- listErr error
- listGroupIDs []int64
- }
- func (s *deleteGroupAPIKeyRepoStub) ListKeysByGroupID(ctx context.Context, groupID int64) ([]string, error) {
- s.listGroupIDs = append(s.listGroupIDs, groupID)
- if s.listErr != nil {
- return nil, s.listErr
- }
- return s.keys, nil
- }
- type proxyRepoStub struct {
- deleteErr error
- countErr error
- accountCount int64
- deletedIDs []int64
- }
- func (s *proxyRepoStub) Create(ctx context.Context, proxy *Proxy) error {
- panic("unexpected Create call")
- }
- func (s *proxyRepoStub) GetByID(ctx context.Context, id int64) (*Proxy, error) {
- panic("unexpected GetByID call")
- }
- func (s *proxyRepoStub) ListByIDs(ctx context.Context, ids []int64) ([]Proxy, error) {
- panic("unexpected ListByIDs call")
- }
- func (s *proxyRepoStub) Update(ctx context.Context, proxy *Proxy) error {
- panic("unexpected Update call")
- }
- func (s *proxyRepoStub) Delete(ctx context.Context, id int64) error {
- s.deletedIDs = append(s.deletedIDs, id)
- return s.deleteErr
- }
- func (s *proxyRepoStub) List(ctx context.Context, params pagination.PaginationParams) ([]Proxy, *pagination.PaginationResult, error) {
- panic("unexpected List call")
- }
- func (s *proxyRepoStub) ListWithFilters(ctx context.Context, params pagination.PaginationParams, protocol, status, search string) ([]Proxy, *pagination.PaginationResult, error) {
- panic("unexpected ListWithFilters call")
- }
- func (s *proxyRepoStub) ListActive(ctx context.Context) ([]Proxy, error) {
- panic("unexpected ListActive call")
- }
- func (s *proxyRepoStub) ListActiveWithAccountCount(ctx context.Context) ([]ProxyWithAccountCount, error) {
- panic("unexpected ListActiveWithAccountCount call")
- }
- func (s *proxyRepoStub) ListWithFiltersAndAccountCount(ctx context.Context, params pagination.PaginationParams, protocol, status, search string) ([]ProxyWithAccountCount, *pagination.PaginationResult, error) {
- panic("unexpected ListWithFiltersAndAccountCount call")
- }
- func (s *proxyRepoStub) ExistsByHostPortAuth(ctx context.Context, host string, port int, username, password string) (bool, error) {
- panic("unexpected ExistsByHostPortAuth call")
- }
- func (s *proxyRepoStub) CountAccountsByProxyID(ctx context.Context, proxyID int64) (int64, error) {
- if s.countErr != nil {
- return 0, s.countErr
- }
- return s.accountCount, nil
- }
- func (s *proxyRepoStub) ListAccountSummariesByProxyID(ctx context.Context, proxyID int64) ([]ProxyAccountSummary, error) {
- panic("unexpected ListAccountSummariesByProxyID call")
- }
- func (s *proxyRepoStub) SweepExpiredProxies(_ context.Context, _ time.Time) (int64, error) {
- return 0, nil
- }
- func (s *proxyRepoStub) ListAllForFallback(_ context.Context) ([]Proxy, error) {
- return nil, nil
- }
- func (s *proxyRepoStub) CountExpired(_ context.Context) (int64, error) {
- return 0, nil
- }
- func (s *proxyRepoStub) CountExpiringSoon(_ context.Context, _ time.Time) (int64, error) {
- return 0, nil
- }
- type redeemRepoStub struct {
- deleteErrByID map[int64]error
- deletedIDs []int64
- batchUpdateIDs []int64
- batchUpdateFields RedeemCodeBatchUpdateFields
- batchUpdateResult int64
- batchUpdateErr error
- batchUpdateCalled bool
- }
- func (s *redeemRepoStub) Create(ctx context.Context, code *RedeemCode) error {
- panic("unexpected Create call")
- }
- func (s *redeemRepoStub) CreateBatch(ctx context.Context, codes []RedeemCode) error {
- panic("unexpected CreateBatch call")
- }
- func (s *redeemRepoStub) GetByID(ctx context.Context, id int64) (*RedeemCode, error) {
- panic("unexpected GetByID call")
- }
- func (s *redeemRepoStub) GetByCode(ctx context.Context, code string) (*RedeemCode, error) {
- panic("unexpected GetByCode call")
- }
- func (s *redeemRepoStub) Update(ctx context.Context, code *RedeemCode) error {
- panic("unexpected Update call")
- }
- func (s *redeemRepoStub) BatchUpdate(ctx context.Context, ids []int64, fields RedeemCodeBatchUpdateFields) (int64, error) {
- s.batchUpdateCalled = true
- s.batchUpdateIDs = append([]int64(nil), ids...)
- s.batchUpdateFields = fields
- if s.batchUpdateErr != nil {
- return 0, s.batchUpdateErr
- }
- if s.batchUpdateResult != 0 {
- return s.batchUpdateResult, nil
- }
- return int64(len(ids)), nil
- }
- func (s *redeemRepoStub) Delete(ctx context.Context, id int64) error {
- s.deletedIDs = append(s.deletedIDs, id)
- if s.deleteErrByID != nil {
- if err, ok := s.deleteErrByID[id]; ok {
- return err
- }
- }
- return nil
- }
- func (s *redeemRepoStub) Use(ctx context.Context, id, userID int64) error {
- panic("unexpected Use call")
- }
- func (s *redeemRepoStub) List(ctx context.Context, params pagination.PaginationParams) ([]RedeemCode, *pagination.PaginationResult, error) {
- panic("unexpected List call")
- }
- func (s *redeemRepoStub) ListWithFilters(ctx context.Context, params pagination.PaginationParams, codeType, status, search string) ([]RedeemCode, *pagination.PaginationResult, error) {
- panic("unexpected ListWithFilters call")
- }
- func (s *redeemRepoStub) ListByUser(ctx context.Context, userID int64, limit int) ([]RedeemCode, error) {
- panic("unexpected ListByUser call")
- }
- func (s *redeemRepoStub) ListByUserPaginated(ctx context.Context, userID int64, params pagination.PaginationParams, codeType string) ([]RedeemCode, *pagination.PaginationResult, error) {
- panic("unexpected ListByUserPaginated call")
- }
- func (s *redeemRepoStub) SumPositiveBalanceByUser(ctx context.Context, userID int64) (float64, error) {
- panic("unexpected SumPositiveBalanceByUser call")
- }
- type subscriptionInvalidateCall struct {
- userID int64
- groupID int64
- }
- type billingCacheStub struct {
- invalidations chan subscriptionInvalidateCall
- }
- func newBillingCacheStub(buffer int) *billingCacheStub {
- return &billingCacheStub{invalidations: make(chan subscriptionInvalidateCall, buffer)}
- }
- func (s *billingCacheStub) GetUserBalance(ctx context.Context, userID int64) (float64, error) {
- panic("unexpected GetUserBalance call")
- }
- func (s *billingCacheStub) SetUserBalance(ctx context.Context, userID int64, balance float64) error {
- panic("unexpected SetUserBalance call")
- }
- func (s *billingCacheStub) DeductUserBalance(ctx context.Context, userID int64, amount float64) error {
- panic("unexpected DeductUserBalance call")
- }
- func (s *billingCacheStub) InvalidateUserBalance(ctx context.Context, userID int64) error {
- panic("unexpected InvalidateUserBalance call")
- }
- func (s *billingCacheStub) GetSubscriptionCache(ctx context.Context, userID, groupID int64) (*SubscriptionCacheData, error) {
- panic("unexpected GetSubscriptionCache call")
- }
- func (s *billingCacheStub) SetSubscriptionCache(ctx context.Context, userID, groupID int64, data *SubscriptionCacheData) error {
- panic("unexpected SetSubscriptionCache call")
- }
- func (s *billingCacheStub) UpdateSubscriptionUsage(ctx context.Context, userID, groupID int64, cost float64) error {
- panic("unexpected UpdateSubscriptionUsage call")
- }
- func (s *billingCacheStub) InvalidateSubscriptionCache(ctx context.Context, userID, groupID int64) error {
- s.invalidations <- subscriptionInvalidateCall{userID: userID, groupID: groupID}
- return nil
- }
- func (s *billingCacheStub) GetAPIKeyRateLimit(ctx context.Context, keyID int64) (*APIKeyRateLimitCacheData, error) {
- panic("unexpected GetAPIKeyRateLimit call")
- }
- func (s *billingCacheStub) SetAPIKeyRateLimit(ctx context.Context, keyID int64, data *APIKeyRateLimitCacheData) error {
- panic("unexpected SetAPIKeyRateLimit call")
- }
- func (s *billingCacheStub) UpdateAPIKeyRateLimitUsage(ctx context.Context, keyID int64, cost float64) error {
- panic("unexpected UpdateAPIKeyRateLimitUsage call")
- }
- func (s *billingCacheStub) InvalidateAPIKeyRateLimit(ctx context.Context, keyID int64) error {
- panic("unexpected InvalidateAPIKeyRateLimit call")
- }
- func (s *billingCacheStub) GetUserPlatformQuotaCache(ctx context.Context, userID int64, platform string) (*UserPlatformQuotaCacheEntry, bool, error) {
- panic("unexpected GetUserPlatformQuotaCache call")
- }
- func (s *billingCacheStub) SetUserPlatformQuotaCache(ctx context.Context, userID int64, platform string, entry *UserPlatformQuotaCacheEntry, ttl time.Duration) error {
- panic("unexpected SetUserPlatformQuotaCache call")
- }
- func (s *billingCacheStub) DeleteUserPlatformQuotaCache(ctx context.Context, userID int64, platform string) error {
- panic("unexpected DeleteUserPlatformQuotaCache call")
- }
- func (s *billingCacheStub) IncrUserPlatformQuotaUsageCache(ctx context.Context, userID int64, platform string, cost float64, ttl time.Duration, markDirty bool) error {
- panic("unexpected IncrUserPlatformQuotaUsageCache call")
- }
- func (s *billingCacheStub) PopDirtyUserPlatformQuotaKeys(ctx context.Context, n int) ([]UserPlatformQuotaKey, error) {
- panic("unexpected PopDirtyUserPlatformQuotaKeys call")
- }
- func (s *billingCacheStub) ReaddDirtyUserPlatformQuotaKeys(ctx context.Context, keys []UserPlatformQuotaKey) error {
- panic("unexpected ReaddDirtyUserPlatformQuotaKeys call")
- }
- func (s *billingCacheStub) BatchGetUserPlatformQuotaCache(ctx context.Context, keys []UserPlatformQuotaKey) ([]*UserPlatformQuotaCacheEntry, error) {
- panic("unexpected BatchGetUserPlatformQuotaCache call")
- }
- func waitForInvalidations(t *testing.T, ch <-chan subscriptionInvalidateCall, expected int) []subscriptionInvalidateCall {
- t.Helper()
- calls := make([]subscriptionInvalidateCall, 0, expected)
- timeout := time.After(2 * time.Second)
- for len(calls) < expected {
- select {
- case call := <-ch:
- calls = append(calls, call)
- case <-timeout:
- t.Fatalf("timeout waiting for %d invalidations, got %d", expected, len(calls))
- }
- }
- return calls
- }
- func TestAdminService_DeleteUser_Success(t *testing.T) {
- repo := &userRepoStub{user: &User{ID: 7, Role: RoleUser}}
- svc := &adminServiceImpl{userRepo: repo}
- err := svc.DeleteUser(context.Background(), 7)
- require.NoError(t, err)
- require.Equal(t, []int64{7}, repo.deletedIDs)
- }
- func TestAdminService_DeleteUser_DeletesOwnedAPIKeys(t *testing.T) {
- repo := &userRepoStub{user: &User{ID: 7, Role: RoleUser}}
- apiKeyRepo := &apiKeyRepoStub{
- allowListByUserID: true,
- listByUserIDKeys: []APIKey{
- {ID: 11, UserID: 7, Key: "sk-user-1"},
- {ID: 12, UserID: 7, Key: "sk-user-2"},
- },
- }
- invalidator := &authCacheInvalidatorStub{}
- svc := &adminServiceImpl{
- userRepo: repo,
- apiKeyRepo: apiKeyRepo,
- authCacheInvalidator: invalidator,
- }
- err := svc.DeleteUser(context.Background(), 7)
- require.NoError(t, err)
- require.Equal(t, []int64{7}, repo.deletedIDs)
- require.Equal(t, []int64{7}, apiKeyRepo.listByUserIDCalls)
- require.Equal(t, []int64{11, 12}, apiKeyRepo.deletedIDs)
- require.ElementsMatch(t, []string{"sk-user-1", "sk-user-2"}, invalidator.keys)
- require.Equal(t, []int64{7}, invalidator.userIDs)
- }
- func TestAdminService_DeleteUser_NotFound(t *testing.T) {
- repo := &userRepoStub{getErr: ErrUserNotFound}
- svc := &adminServiceImpl{userRepo: repo}
- err := svc.DeleteUser(context.Background(), 404)
- require.ErrorIs(t, err, ErrUserNotFound)
- require.Empty(t, repo.deletedIDs)
- }
- func TestAdminService_DeleteUser_AdminGuard(t *testing.T) {
- repo := &userRepoStub{user: &User{ID: 1, Role: RoleAdmin}}
- svc := &adminServiceImpl{userRepo: repo}
- err := svc.DeleteUser(context.Background(), 1)
- require.Error(t, err)
- require.ErrorContains(t, err, "cannot delete admin user")
- require.Empty(t, repo.deletedIDs)
- }
- func TestAdminService_DeleteUser_DeleteError(t *testing.T) {
- deleteErr := errors.New("delete failed")
- repo := &userRepoStub{
- user: &User{ID: 9, Role: RoleUser},
- deleteErr: deleteErr,
- }
- svc := &adminServiceImpl{userRepo: repo}
- err := svc.DeleteUser(context.Background(), 9)
- require.ErrorIs(t, err, deleteErr)
- require.Equal(t, []int64{9}, repo.deletedIDs)
- }
- func TestAdminService_DeleteGroup_Success_WithCacheInvalidation(t *testing.T) {
- cache := newBillingCacheStub(2)
- repo := &groupRepoStub{affectedUserIDs: []int64{11, 12}}
- svc := &adminServiceImpl{
- groupRepo: repo,
- billingCacheService: &BillingCacheService{cache: cache},
- }
- err := svc.DeleteGroup(context.Background(), 5)
- require.NoError(t, err)
- require.Equal(t, []int64{5}, repo.deleteCalls)
- calls := waitForInvalidations(t, cache.invalidations, 2)
- require.ElementsMatch(t, []subscriptionInvalidateCall{
- {userID: 11, groupID: 5},
- {userID: 12, groupID: 5},
- }, calls)
- }
- func TestAdminService_DeleteGroup_InvalidatesAuthCacheForBoundKeys(t *testing.T) {
- repo := &groupRepoStub{}
- apiKeyRepo := &deleteGroupAPIKeyRepoStub{keys: []string{"k1", "k2"}}
- invalidator := &authCacheInvalidatorStub{}
- svc := &adminServiceImpl{
- groupRepo: repo,
- apiKeyRepo: apiKeyRepo,
- authCacheInvalidator: invalidator,
- }
- err := svc.DeleteGroup(context.Background(), 5)
- require.NoError(t, err)
- require.Equal(t, []int64{5}, repo.deleteCalls)
- require.Equal(t, []int64{5}, apiKeyRepo.listGroupIDs)
- require.Equal(t, []string{"k1", "k2"}, invalidator.keys)
- }
- func TestAdminService_DeleteGroup_NotFound(t *testing.T) {
- repo := &groupRepoStub{deleteErr: ErrGroupNotFound}
- svc := &adminServiceImpl{groupRepo: repo}
- err := svc.DeleteGroup(context.Background(), 99)
- require.ErrorIs(t, err, ErrGroupNotFound)
- }
- func TestAdminService_DeleteGroup_Error(t *testing.T) {
- deleteErr := errors.New("delete failed")
- repo := &groupRepoStub{deleteErr: deleteErr}
- svc := &adminServiceImpl{groupRepo: repo}
- err := svc.DeleteGroup(context.Background(), 42)
- require.ErrorIs(t, err, deleteErr)
- }
- func TestAdminService_DeleteGroupIfEmpty_UsesGuardedCascade(t *testing.T) {
- repo := &groupRepoStub{deleteErr: ErrGroupNotEmpty}
- svc := &adminServiceImpl{groupRepo: repo, emptyGroupDeleteRepo: repo}
- err := svc.DeleteGroupIfEmpty(context.Background(), 42)
- require.ErrorIs(t, err, ErrGroupNotEmpty)
- require.Equal(t, []int64{42}, repo.guardedCalls)
- require.Empty(t, repo.deleteCalls)
- }
- func TestAdminService_DeleteGroupIfEmpty_MissingCapabilityReturnsError(t *testing.T) {
- svc := &adminServiceImpl{groupRepo: &groupRepoStub{}}
- require.ErrorContains(t, svc.DeleteGroupIfEmpty(context.Background(), 42), "guarded group deletion is unavailable")
- }
- func TestAdminService_DeleteProxy_Success(t *testing.T) {
- repo := &proxyRepoStub{}
- svc := &adminServiceImpl{proxyRepo: repo}
- err := svc.DeleteProxy(context.Background(), 7)
- require.NoError(t, err)
- require.Equal(t, []int64{7}, repo.deletedIDs)
- }
- func TestAdminService_DeleteProxy_Idempotent(t *testing.T) {
- repo := &proxyRepoStub{}
- svc := &adminServiceImpl{proxyRepo: repo}
- err := svc.DeleteProxy(context.Background(), 404)
- require.NoError(t, err)
- require.Equal(t, []int64{404}, repo.deletedIDs)
- }
- func TestAdminService_DeleteProxy_InUse(t *testing.T) {
- repo := &proxyRepoStub{accountCount: 2}
- svc := &adminServiceImpl{proxyRepo: repo}
- err := svc.DeleteProxy(context.Background(), 77)
- require.ErrorIs(t, err, ErrProxyInUse)
- require.Empty(t, repo.deletedIDs)
- }
- func TestAdminService_DeleteProxy_Error(t *testing.T) {
- deleteErr := errors.New("delete failed")
- repo := &proxyRepoStub{deleteErr: deleteErr}
- svc := &adminServiceImpl{proxyRepo: repo}
- err := svc.DeleteProxy(context.Background(), 33)
- require.ErrorIs(t, err, deleteErr)
- }
- func TestAdminService_DeleteRedeemCode_Success(t *testing.T) {
- repo := &redeemRepoStub{}
- svc := &adminServiceImpl{redeemCodeRepo: repo}
- err := svc.DeleteRedeemCode(context.Background(), 10)
- require.NoError(t, err)
- require.Equal(t, []int64{10}, repo.deletedIDs)
- }
- func TestAdminService_DeleteRedeemCode_Idempotent(t *testing.T) {
- repo := &redeemRepoStub{}
- svc := &adminServiceImpl{redeemCodeRepo: repo}
- err := svc.DeleteRedeemCode(context.Background(), 999)
- require.NoError(t, err)
- require.Equal(t, []int64{999}, repo.deletedIDs)
- }
- func TestAdminService_DeleteRedeemCode_Error(t *testing.T) {
- deleteErr := errors.New("delete failed")
- repo := &redeemRepoStub{deleteErrByID: map[int64]error{1: deleteErr}}
- svc := &adminServiceImpl{redeemCodeRepo: repo}
- err := svc.DeleteRedeemCode(context.Background(), 1)
- require.ErrorIs(t, err, deleteErr)
- require.Equal(t, []int64{1}, repo.deletedIDs)
- }
- func TestAdminService_BatchDeleteRedeemCodes_Success(t *testing.T) {
- repo := &redeemRepoStub{}
- svc := &adminServiceImpl{redeemCodeRepo: repo}
- deleted, err := svc.BatchDeleteRedeemCodes(context.Background(), []int64{1, 2, 3})
- require.NoError(t, err)
- require.Equal(t, int64(3), deleted)
- require.Equal(t, []int64{1, 2, 3}, repo.deletedIDs)
- }
- func TestAdminService_BatchDeleteRedeemCodes_PartialFailures(t *testing.T) {
- repo := &redeemRepoStub{
- deleteErrByID: map[int64]error{
- 2: errors.New("db error"),
- },
- }
- svc := &adminServiceImpl{redeemCodeRepo: repo}
- deleted, err := svc.BatchDeleteRedeemCodes(context.Background(), []int64{1, 2, 3})
- require.NoError(t, err)
- require.Equal(t, int64(2), deleted)
- require.Equal(t, []int64{1, 2, 3}, repo.deletedIDs)
- }
|