subscription_revoke_cache_test.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "testing"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/config"
  8. "github.com/stretchr/testify/require"
  9. )
  10. type revokeCacheUserSubRepoStub struct {
  11. userSubRepoNoop
  12. sub *UserSubscription
  13. deleted bool
  14. getActiveCalls int
  15. }
  16. func (r *revokeCacheUserSubRepoStub) GetByID(_ context.Context, id int64) (*UserSubscription, error) {
  17. if r.sub == nil || r.sub.ID != id || r.deleted {
  18. return nil, ErrSubscriptionNotFound
  19. }
  20. cp := *r.sub
  21. return &cp, nil
  22. }
  23. func (r *revokeCacheUserSubRepoStub) Delete(_ context.Context, id int64) error {
  24. if r.sub == nil || r.sub.ID != id || r.deleted {
  25. return ErrSubscriptionNotFound
  26. }
  27. r.deleted = true
  28. return nil
  29. }
  30. func (r *revokeCacheUserSubRepoStub) GetActiveByUserIDAndGroupID(_ context.Context, userID, groupID int64) (*UserSubscription, error) {
  31. r.getActiveCalls++
  32. if r.deleted || r.sub == nil || r.sub.UserID != userID || r.sub.GroupID != groupID {
  33. return nil, ErrSubscriptionNotFound
  34. }
  35. cp := *r.sub
  36. return &cp, nil
  37. }
  38. func TestRevokeSubscription_InvalidatesL1CacheSynchronously(t *testing.T) {
  39. repo := &revokeCacheUserSubRepoStub{
  40. sub: &UserSubscription{
  41. ID: 1,
  42. UserID: 10,
  43. GroupID: 20,
  44. Status: SubscriptionStatusActive,
  45. ExpiresAt: time.Now().Add(time.Hour),
  46. },
  47. }
  48. svc := NewSubscriptionService(groupRepoNoop{}, repo, nil, nil, &config.Config{
  49. SubscriptionCache: config.SubscriptionCacheConfig{
  50. L1Size: 16,
  51. L1TTLSeconds: 60,
  52. },
  53. })
  54. t.Cleanup(svc.Stop)
  55. _, err := svc.GetActiveSubscription(context.Background(), 10, 20)
  56. require.NoError(t, err)
  57. svc.subCacheL1.Wait()
  58. require.Equal(t, 1, repo.getActiveCalls)
  59. err = svc.RevokeSubscription(context.Background(), 1)
  60. require.NoError(t, err)
  61. _, err = svc.GetActiveSubscription(context.Background(), 10, 20)
  62. require.ErrorIs(t, err, ErrSubscriptionNotFound)
  63. require.Equal(t, 2, repo.getActiveCalls, "撤销后应回源确认订阅已不存在,不能命中旧 L1")
  64. }
  65. type restoreUserSubRepoStub struct {
  66. userSubRepoNoop
  67. sub *UserSubscription
  68. existsActive bool
  69. restoreCalls int
  70. restoredStatus string
  71. }
  72. func (r *restoreUserSubRepoStub) GetByIDIncludeDeleted(_ context.Context, id int64) (*UserSubscription, error) {
  73. if r.sub == nil || r.sub.ID != id {
  74. return nil, ErrSubscriptionNotFound
  75. }
  76. cp := *r.sub
  77. return &cp, nil
  78. }
  79. func (r *restoreUserSubRepoStub) ExistsActiveByUserIDAndGroupID(context.Context, int64, int64) (bool, error) {
  80. return r.existsActive, nil
  81. }
  82. func (r *restoreUserSubRepoStub) Restore(_ context.Context, id int64, restoredStatus string) (*UserSubscription, error) {
  83. if r.sub == nil || r.sub.ID != id {
  84. return nil, ErrSubscriptionNotFound
  85. }
  86. r.restoreCalls++
  87. r.restoredStatus = restoredStatus
  88. cp := *r.sub
  89. cp.Status = restoredStatus
  90. cp.DeletedAt = nil
  91. r.sub = &cp
  92. return &cp, nil
  93. }
  94. func TestRestoreSubscription_ExpiredActiveRestoresAsExpired(t *testing.T) {
  95. deletedAt := time.Now().Add(-time.Hour)
  96. repo := &restoreUserSubRepoStub{
  97. sub: &UserSubscription{
  98. ID: 1,
  99. UserID: 10,
  100. GroupID: 20,
  101. Status: SubscriptionStatusActive,
  102. ExpiresAt: time.Now().Add(-time.Minute),
  103. DeletedAt: &deletedAt,
  104. },
  105. }
  106. svc := NewSubscriptionService(groupRepoNoop{}, repo, nil, nil, nil)
  107. t.Cleanup(svc.Stop)
  108. restored, err := svc.RestoreSubscription(context.Background(), 1)
  109. require.NoError(t, err)
  110. require.Equal(t, 1, repo.restoreCalls)
  111. require.Equal(t, SubscriptionStatusExpired, repo.restoredStatus)
  112. require.Equal(t, SubscriptionStatusExpired, restored.Status)
  113. require.Nil(t, restored.DeletedAt)
  114. }
  115. func TestRestoreSubscription_NotRevokedReturnsConflict(t *testing.T) {
  116. repo := &restoreUserSubRepoStub{
  117. sub: &UserSubscription{
  118. ID: 1,
  119. UserID: 10,
  120. GroupID: 20,
  121. Status: SubscriptionStatusActive,
  122. ExpiresAt: time.Now().Add(time.Hour),
  123. },
  124. }
  125. svc := NewSubscriptionService(groupRepoNoop{}, repo, nil, nil, nil)
  126. t.Cleanup(svc.Stop)
  127. _, err := svc.RestoreSubscription(context.Background(), 1)
  128. require.ErrorIs(t, err, ErrSubscriptionNotRevoked)
  129. require.Zero(t, repo.restoreCalls)
  130. }
  131. func TestRestoreSubscription_LiveSubscriptionConflict(t *testing.T) {
  132. deletedAt := time.Now().Add(-time.Hour)
  133. repo := &restoreUserSubRepoStub{
  134. existsActive: true,
  135. sub: &UserSubscription{
  136. ID: 1,
  137. UserID: 10,
  138. GroupID: 20,
  139. Status: SubscriptionStatusExpired,
  140. ExpiresAt: time.Now().Add(-time.Hour),
  141. DeletedAt: &deletedAt,
  142. },
  143. }
  144. svc := NewSubscriptionService(groupRepoNoop{}, repo, nil, nil, nil)
  145. t.Cleanup(svc.Stop)
  146. _, err := svc.RestoreSubscription(context.Background(), 1)
  147. require.ErrorIs(t, err, ErrSubscriptionRestoreConflict)
  148. require.Zero(t, repo.restoreCalls)
  149. }