account_service_delete_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. //go:build unit
  2. // 账号服务删除方法的单元测试
  3. // 测试 AccountService.Delete 方法在各种场景下的行为
  4. package service
  5. import (
  6. "context"
  7. "errors"
  8. "testing"
  9. "time"
  10. "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
  11. "github.com/stretchr/testify/require"
  12. )
  13. // accountRepoStub 是 AccountRepository 接口的测试桩实现。
  14. // 用于隔离测试 AccountService.Delete 方法,避免依赖真实数据库。
  15. //
  16. // 设计说明:
  17. // - exists: 模拟 ExistsByID 返回的存在性结果
  18. // - existsErr: 模拟 ExistsByID 返回的错误
  19. // - deleteErr: 模拟 Delete 返回的错误
  20. // - deletedIDs: 记录被调用删除的账号 ID,用于断言验证
  21. type accountRepoStub struct {
  22. exists bool // ExistsByID 的返回值
  23. existsErr error // ExistsByID 的错误返回值
  24. deleteErr error // Delete 的错误返回值
  25. deletedIDs []int64 // 记录已删除的账号 ID 列表
  26. }
  27. // 以下方法在本测试中不应被调用,使用 panic 确保测试失败时能快速定位问题
  28. func (s *accountRepoStub) Create(ctx context.Context, account *Account) error {
  29. panic("unexpected Create call")
  30. }
  31. func (s *accountRepoStub) GetByID(ctx context.Context, id int64) (*Account, error) {
  32. panic("unexpected GetByID call")
  33. }
  34. func (s *accountRepoStub) GetByIDs(ctx context.Context, ids []int64) ([]*Account, error) {
  35. panic("unexpected GetByIDs call")
  36. }
  37. // ExistsByID 返回预设的存在性检查结果。
  38. // 这是 Delete 方法调用的第一个仓储方法,用于验证账号是否存在。
  39. func (s *accountRepoStub) ExistsByID(ctx context.Context, id int64) (bool, error) {
  40. return s.exists, s.existsErr
  41. }
  42. func (s *accountRepoStub) GetByCRSAccountID(ctx context.Context, crsAccountID string) (*Account, error) {
  43. panic("unexpected GetByCRSAccountID call")
  44. }
  45. func (s *accountRepoStub) FindByExtraField(ctx context.Context, key string, value any) ([]Account, error) {
  46. panic("unexpected FindByExtraField call")
  47. }
  48. func (s *accountRepoStub) ListCRSAccountIDs(ctx context.Context) (map[string]int64, error) {
  49. panic("unexpected ListCRSAccountIDs call")
  50. }
  51. func (s *accountRepoStub) Update(ctx context.Context, account *Account) error {
  52. panic("unexpected Update call")
  53. }
  54. // Delete 记录被删除的账号 ID 并返回预设的错误。
  55. // 通过 deletedIDs 可以验证删除操作是否被正确调用。
  56. func (s *accountRepoStub) Delete(ctx context.Context, id int64) error {
  57. s.deletedIDs = append(s.deletedIDs, id)
  58. return s.deleteErr
  59. }
  60. // 以下是接口要求实现但本测试不关心的方法
  61. func (s *accountRepoStub) List(ctx context.Context, params pagination.PaginationParams) ([]Account, *pagination.PaginationResult, error) {
  62. panic("unexpected List call")
  63. }
  64. func (s *accountRepoStub) ListAllWithFilters(context.Context, string, string, string, string, int64, string) ([]Account, error) {
  65. return nil, nil
  66. }
  67. func (s *accountRepoStub) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, *pagination.PaginationResult, error) {
  68. panic("unexpected ListWithFilters call")
  69. }
  70. func (s *accountRepoStub) ListByGroup(ctx context.Context, groupID int64) ([]Account, error) {
  71. panic("unexpected ListByGroup call")
  72. }
  73. func (s *accountRepoStub) ListActive(ctx context.Context) ([]Account, error) {
  74. panic("unexpected ListActive call")
  75. }
  76. func (s *accountRepoStub) ListByPlatform(ctx context.Context, platform string) ([]Account, error) {
  77. panic("unexpected ListByPlatform call")
  78. }
  79. func (s *accountRepoStub) UpdateLastUsed(ctx context.Context, id int64) error {
  80. panic("unexpected UpdateLastUsed call")
  81. }
  82. func (s *accountRepoStub) BatchUpdateLastUsed(ctx context.Context, updates map[int64]time.Time) error {
  83. panic("unexpected BatchUpdateLastUsed call")
  84. }
  85. func (s *accountRepoStub) SetError(ctx context.Context, id int64, errorMsg string) error {
  86. panic("unexpected SetError call")
  87. }
  88. func (s *accountRepoStub) ClearError(ctx context.Context, id int64) error {
  89. panic("unexpected ClearError call")
  90. }
  91. func (s *accountRepoStub) SetSchedulable(ctx context.Context, id int64, schedulable bool) error {
  92. panic("unexpected SetSchedulable call")
  93. }
  94. func (s *accountRepoStub) AutoPauseExpiredAccounts(ctx context.Context, now time.Time) (int64, error) {
  95. panic("unexpected AutoPauseExpiredAccounts call")
  96. }
  97. func (s *accountRepoStub) BindGroups(ctx context.Context, accountID int64, groupIDs []int64) error {
  98. panic("unexpected BindGroups call")
  99. }
  100. func (s *accountRepoStub) ListSchedulable(ctx context.Context) ([]Account, error) {
  101. panic("unexpected ListSchedulable call")
  102. }
  103. func (s *accountRepoStub) ListSchedulableByGroupID(ctx context.Context, groupID int64) ([]Account, error) {
  104. panic("unexpected ListSchedulableByGroupID call")
  105. }
  106. func (s *accountRepoStub) ListSchedulableByPlatform(ctx context.Context, platform string) ([]Account, error) {
  107. panic("unexpected ListSchedulableByPlatform call")
  108. }
  109. func (s *accountRepoStub) ListSchedulableByGroupIDAndPlatform(ctx context.Context, groupID int64, platform string) ([]Account, error) {
  110. panic("unexpected ListSchedulableByGroupIDAndPlatform call")
  111. }
  112. func (s *accountRepoStub) ListSchedulableByPlatforms(ctx context.Context, platforms []string) ([]Account, error) {
  113. panic("unexpected ListSchedulableByPlatforms call")
  114. }
  115. func (s *accountRepoStub) ListSchedulableByGroupIDAndPlatforms(ctx context.Context, groupID int64, platforms []string) ([]Account, error) {
  116. panic("unexpected ListSchedulableByGroupIDAndPlatforms call")
  117. }
  118. func (s *accountRepoStub) ListSchedulableUngroupedByPlatform(ctx context.Context, platform string) ([]Account, error) {
  119. panic("unexpected ListSchedulableUngroupedByPlatform call")
  120. }
  121. func (s *accountRepoStub) ListSchedulableUngroupedByPlatforms(ctx context.Context, platforms []string) ([]Account, error) {
  122. panic("unexpected ListSchedulableUngroupedByPlatforms call")
  123. }
  124. func (s *accountRepoStub) ListModelAvailabilityCandidates(ctx context.Context, groupID *int64, platforms []string, includeGrouped bool) ([]Account, error) {
  125. panic("unexpected ListModelAvailabilityCandidates call")
  126. }
  127. func (s *accountRepoStub) SetRateLimited(ctx context.Context, id int64, resetAt time.Time) error {
  128. panic("unexpected SetRateLimited call")
  129. }
  130. func (s *accountRepoStub) SetModelRateLimit(ctx context.Context, id int64, scope string, resetAt time.Time, reason ...string) error {
  131. panic("unexpected SetModelRateLimit call")
  132. }
  133. func (s *accountRepoStub) SetOverloaded(ctx context.Context, id int64, until time.Time) error {
  134. panic("unexpected SetOverloaded call")
  135. }
  136. func (s *accountRepoStub) SetTempUnschedulable(ctx context.Context, id int64, until time.Time, reason string) error {
  137. panic("unexpected SetTempUnschedulable call")
  138. }
  139. func (s *accountRepoStub) ClearTempUnschedulable(ctx context.Context, id int64) error {
  140. panic("unexpected ClearTempUnschedulable call")
  141. }
  142. func (s *accountRepoStub) ClearRateLimit(ctx context.Context, id int64) error {
  143. panic("unexpected ClearRateLimit call")
  144. }
  145. func (s *accountRepoStub) ClearAntigravityQuotaScopes(ctx context.Context, id int64) error {
  146. panic("unexpected ClearAntigravityQuotaScopes call")
  147. }
  148. func (s *accountRepoStub) ClearModelRateLimits(ctx context.Context, id int64) error {
  149. panic("unexpected ClearModelRateLimits call")
  150. }
  151. func (s *accountRepoStub) UpdateSessionWindow(ctx context.Context, id int64, start, end *time.Time, status string) error {
  152. panic("unexpected UpdateSessionWindow call")
  153. }
  154. func (s *accountRepoStub) UpdateSessionWindowEnd(ctx context.Context, id int64, end time.Time) error {
  155. panic("unexpected UpdateSessionWindowEnd call")
  156. }
  157. func (s *accountRepoStub) UpdateExtra(ctx context.Context, id int64, updates map[string]any) error {
  158. panic("unexpected UpdateExtra call")
  159. }
  160. func (s *accountRepoStub) BulkUpdate(ctx context.Context, ids []int64, updates AccountBulkUpdate) (int64, error) {
  161. panic("unexpected BulkUpdate call")
  162. }
  163. func (s *accountRepoStub) IncrementQuotaUsed(ctx context.Context, id int64, amount float64) error {
  164. return nil
  165. }
  166. func (s *accountRepoStub) ResetQuotaUsedAndClearRateLimitCooldown(ctx context.Context, id int64) error {
  167. return nil
  168. }
  169. func (s *accountRepoStub) RevertProxyFallback(ctx context.Context, accountID int64) error {
  170. panic("unexpected RevertProxyFallback call")
  171. }
  172. func (s *accountRepoStub) ListShadowsByParent(ctx context.Context, parentID int64) ([]*Account, error) {
  173. return nil, nil
  174. }
  175. // TestAccountService_Delete_NotFound 测试删除不存在的账号时返回正确的错误。
  176. // 预期行为:
  177. // - ExistsByID 返回 false(账号不存在)
  178. // - 返回 ErrAccountNotFound 错误
  179. // - Delete 方法不被调用(deletedIDs 为空)
  180. func TestAccountService_Delete_NotFound(t *testing.T) {
  181. repo := &accountRepoStub{exists: false}
  182. svc := &AccountService{accountRepo: repo}
  183. err := svc.Delete(context.Background(), 55)
  184. require.ErrorIs(t, err, ErrAccountNotFound)
  185. require.Empty(t, repo.deletedIDs) // 验证删除操作未被调用
  186. }
  187. // TestAccountService_Delete_CheckError 测试存在性检查失败时的错误处理。
  188. // 预期行为:
  189. // - ExistsByID 返回数据库错误
  190. // - 返回包含 "check account" 的错误信息
  191. // - Delete 方法不被调用
  192. func TestAccountService_Delete_CheckError(t *testing.T) {
  193. repo := &accountRepoStub{existsErr: errors.New("db down")}
  194. svc := &AccountService{accountRepo: repo}
  195. err := svc.Delete(context.Background(), 55)
  196. require.Error(t, err)
  197. require.ErrorContains(t, err, "check account") // 验证错误信息包含上下文
  198. require.Empty(t, repo.deletedIDs)
  199. }
  200. // TestAccountService_Delete_DeleteError 测试删除操作失败时的错误处理。
  201. // 预期行为:
  202. // - ExistsByID 返回 true(账号存在)
  203. // - Delete 被调用但返回错误
  204. // - 返回包含 "delete account" 的错误信息
  205. // - deletedIDs 记录了尝试删除的 ID
  206. func TestAccountService_Delete_DeleteError(t *testing.T) {
  207. repo := &accountRepoStub{
  208. exists: true,
  209. deleteErr: errors.New("delete failed"),
  210. }
  211. svc := &AccountService{accountRepo: repo}
  212. err := svc.Delete(context.Background(), 55)
  213. require.Error(t, err)
  214. require.ErrorContains(t, err, "delete account")
  215. require.Equal(t, []int64{55}, repo.deletedIDs) // 验证删除操作被调用
  216. }
  217. // TestAccountService_Delete_Success 测试删除操作成功的场景。
  218. // 预期行为:
  219. // - ExistsByID 返回 true(账号存在)
  220. // - Delete 成功执行
  221. // - 返回 nil 错误
  222. // - deletedIDs 记录了被删除的 ID
  223. func TestAccountService_Delete_Success(t *testing.T) {
  224. repo := &accountRepoStub{exists: true}
  225. svc := &AccountService{accountRepo: repo}
  226. err := svc.Delete(context.Background(), 55)
  227. require.NoError(t, err)
  228. require.Equal(t, []int64{55}, repo.deletedIDs) // 验证正确的 ID 被删除
  229. }