admin_service_email_identity_sync_test.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "fmt"
  6. "testing"
  7. "time"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
  9. "github.com/stretchr/testify/require"
  10. )
  11. type ensureEmailCall struct {
  12. userID int64
  13. email string
  14. }
  15. type replaceEmailCall struct {
  16. userID int64
  17. oldEmail string
  18. newEmail string
  19. }
  20. type emailSyncRepoStub struct {
  21. user *User
  22. nextID int64
  23. updateCalls int
  24. created []*User
  25. updated []*User
  26. ensureCalls []ensureEmailCall
  27. replaceCalls []replaceEmailCall
  28. ensureErr error
  29. replaceErr error
  30. }
  31. func (s *emailSyncRepoStub) CreateWithEmailAliasGuard(ctx context.Context, user *User) error {
  32. return s.Create(ctx, user)
  33. }
  34. func (s *emailSyncRepoStub) Create(_ context.Context, user *User) error {
  35. if s.nextID != 0 && user.ID == 0 {
  36. user.ID = s.nextID
  37. }
  38. s.created = append(s.created, user)
  39. s.user = user
  40. return nil
  41. }
  42. func (s *emailSyncRepoStub) GetByID(_ context.Context, _ int64) (*User, error) {
  43. if s.user == nil {
  44. return nil, ErrUserNotFound
  45. }
  46. cloned := *s.user
  47. return &cloned, nil
  48. }
  49. func (s *emailSyncRepoStub) GetByEmail(_ context.Context, _ string) (*User, error) {
  50. return nil, ErrUserNotFound
  51. }
  52. func (s *emailSyncRepoStub) GetFirstAdmin(context.Context) (*User, error) {
  53. return nil, fmt.Errorf("unexpected GetFirstAdmin call")
  54. }
  55. func (s *emailSyncRepoStub) Update(_ context.Context, user *User, _ UserUpdateFields) error {
  56. s.updateCalls++
  57. s.updated = append(s.updated, user)
  58. s.user = user
  59. return nil
  60. }
  61. func (s *emailSyncRepoStub) Delete(context.Context, int64) error { return nil }
  62. func (s *emailSyncRepoStub) GetUserAvatar(context.Context, int64) (*UserAvatar, error) {
  63. return nil, fmt.Errorf("unexpected GetUserAvatar call")
  64. }
  65. func (s *emailSyncRepoStub) UpsertUserAvatar(context.Context, int64, UpsertUserAvatarInput) (*UserAvatar, error) {
  66. return nil, fmt.Errorf("unexpected UpsertUserAvatar call")
  67. }
  68. func (s *emailSyncRepoStub) DeleteUserAvatar(context.Context, int64) error {
  69. return fmt.Errorf("unexpected DeleteUserAvatar call")
  70. }
  71. func (s *emailSyncRepoStub) List(context.Context, pagination.PaginationParams) ([]User, *pagination.PaginationResult, error) {
  72. return nil, nil, fmt.Errorf("unexpected List call")
  73. }
  74. func (s *emailSyncRepoStub) ListWithFilters(context.Context, pagination.PaginationParams, UserListFilters) ([]User, *pagination.PaginationResult, error) {
  75. return nil, nil, fmt.Errorf("unexpected ListWithFilters call")
  76. }
  77. func (s *emailSyncRepoStub) GetLatestUsedAtByUserIDs(context.Context, []int64) (map[int64]*time.Time, error) {
  78. return map[int64]*time.Time{}, nil
  79. }
  80. func (s *emailSyncRepoStub) GetLatestUsedAtByUserID(context.Context, int64) (*time.Time, error) {
  81. return nil, nil
  82. }
  83. func (s *emailSyncRepoStub) UpdateUserLastActiveAt(context.Context, int64, time.Time) error {
  84. return nil
  85. }
  86. func (s *emailSyncRepoStub) UpdateBalance(context.Context, int64, float64) error { return nil }
  87. func (s *emailSyncRepoStub) DeductBalance(context.Context, int64, float64) error { return nil }
  88. func (s *emailSyncRepoStub) UpdateConcurrency(context.Context, int64, int) error { return nil }
  89. func (s *emailSyncRepoStub) ExistsByEmail(context.Context, string) (bool, error) { return false, nil }
  90. func (s *emailSyncRepoStub) ExistsByEmailAlias(context.Context, string) (bool, error) {
  91. return false, nil
  92. }
  93. func (s *emailSyncRepoStub) AdjustBalance(ctx context.Context, id int64, delta float64) (BalanceChange, error) {
  94. panic("unexpected AdjustBalance call")
  95. }
  96. func (s *emailSyncRepoStub) SetBalance(ctx context.Context, id int64, value float64) (BalanceChange, error) {
  97. panic("unexpected SetBalance call")
  98. }
  99. func (s *emailSyncRepoStub) RemoveGroupFromAllowedGroups(context.Context, int64) (int64, error) {
  100. return 0, nil
  101. }
  102. func (s *emailSyncRepoStub) BatchSetConcurrency(context.Context, []int64, int) (int, error) {
  103. return 0, nil
  104. }
  105. func (s *emailSyncRepoStub) BatchAddConcurrency(context.Context, []int64, int) (int, error) {
  106. return 0, nil
  107. }
  108. func (s *emailSyncRepoStub) BatchUpdateLimits(context.Context, []int64, *int, *int) (int, error) {
  109. return 0, nil
  110. }
  111. func (s *emailSyncRepoStub) AddGroupToAllowedGroups(context.Context, int64, int64) error { return nil }
  112. func (s *emailSyncRepoStub) RemoveGroupFromUserAllowedGroups(context.Context, int64, int64) error {
  113. return nil
  114. }
  115. func (s *emailSyncRepoStub) ListUserAuthIdentities(context.Context, int64) ([]UserAuthIdentityRecord, error) {
  116. return nil, nil
  117. }
  118. func (s *emailSyncRepoStub) UnbindUserAuthProvider(context.Context, int64, string) error { return nil }
  119. func (s *emailSyncRepoStub) UpdateTotpSecret(context.Context, int64, *string) error { return nil }
  120. func (s *emailSyncRepoStub) EnableTotp(context.Context, int64) error { return nil }
  121. func (s *emailSyncRepoStub) DisableTotp(context.Context, int64) error { return nil }
  122. func (s *emailSyncRepoStub) GetByIDIncludeDeleted(ctx context.Context, id int64) (*User, error) {
  123. return s.GetByID(ctx, id)
  124. }
  125. func (s *emailSyncRepoStub) EnsureEmailAuthIdentity(_ context.Context, userID int64, email string) error {
  126. s.ensureCalls = append(s.ensureCalls, ensureEmailCall{userID: userID, email: email})
  127. return s.ensureErr
  128. }
  129. func (s *emailSyncRepoStub) ReplaceEmailAuthIdentity(_ context.Context, userID int64, oldEmail, newEmail string) error {
  130. s.replaceCalls = append(s.replaceCalls, replaceEmailCall{
  131. userID: userID,
  132. oldEmail: oldEmail,
  133. newEmail: newEmail,
  134. })
  135. return s.replaceErr
  136. }
  137. func TestAdminService_CreateUser_DoesNotReturnPartialSuccessFromEmailIdentityResync(t *testing.T) {
  138. repo := &emailSyncRepoStub{
  139. nextID: 55,
  140. ensureErr: fmt.Errorf("unexpected email resync"),
  141. }
  142. svc := &adminServiceImpl{userRepo: repo}
  143. user, err := svc.CreateUser(context.Background(), &CreateUserInput{
  144. Email: "admin-created@example.com",
  145. Password: "strong-pass",
  146. })
  147. require.NoError(t, err)
  148. require.NotNil(t, user)
  149. require.Equal(t, int64(55), user.ID)
  150. require.Empty(t, repo.ensureCalls)
  151. require.Empty(t, repo.replaceCalls)
  152. }
  153. func TestAdminService_UpdateUser_DoesNotReturnPartialSuccessFromEmailIdentityResync(t *testing.T) {
  154. repo := &emailSyncRepoStub{
  155. user: &User{
  156. ID: 91,
  157. Email: "before@example.com",
  158. Role: RoleUser,
  159. Status: StatusActive,
  160. Concurrency: 3,
  161. },
  162. replaceErr: fmt.Errorf("unexpected email resync"),
  163. }
  164. svc := &adminServiceImpl{userRepo: repo}
  165. updated, err := svc.UpdateUser(context.Background(), 91, &UpdateUserInput{
  166. Email: "after@example.com",
  167. })
  168. require.NoError(t, err)
  169. require.NotNil(t, updated)
  170. require.Equal(t, "after@example.com", updated.Email)
  171. require.Empty(t, repo.replaceCalls)
  172. require.Empty(t, repo.ensureCalls)
  173. }