| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- //go:build integration
- package repository
- import (
- "fmt"
- "testing"
- "time"
- "github.com/stretchr/testify/require"
- "github.com/stretchr/testify/suite"
- )
- type RedeemCacheSuite struct {
- IntegrationRedisSuite
- cache *redeemCache
- }
- func (s *RedeemCacheSuite) SetupTest() {
- s.IntegrationRedisSuite.SetupTest()
- s.cache = NewRedeemCache(s.rdb).(*redeemCache)
- }
- func (s *RedeemCacheSuite) TestGetRedeemAttemptCount_Missing() {
- missingUserID := int64(99999)
- count, err := s.cache.GetRedeemAttemptCount(s.ctx, missingUserID)
- require.NoError(s.T(), err, "expected nil error for missing rate-limit key")
- require.Equal(s.T(), 0, count, "expected zero count for missing key")
- }
- func (s *RedeemCacheSuite) TestIncrementAndGetRedeemAttemptCount() {
- userID := int64(1)
- key := fmt.Sprintf("%s%d", redeemRateLimitKeyPrefix, userID)
- require.NoError(s.T(), s.cache.IncrementRedeemAttemptCount(s.ctx, userID), "IncrementRedeemAttemptCount")
- count, err := s.cache.GetRedeemAttemptCount(s.ctx, userID)
- require.NoError(s.T(), err, "GetRedeemAttemptCount")
- require.Equal(s.T(), 1, count, "count mismatch")
- ttl, err := s.rdb.TTL(s.ctx, key).Result()
- require.NoError(s.T(), err, "TTL")
- s.AssertTTLWithin(ttl, redeemRateLimitWindow-time.Second, redeemRateLimitWindow)
- }
- func (s *RedeemCacheSuite) TestIncrementDoesNotRefreshFixedWindow() {
- userID := int64(3)
- key := redeemRateLimitKey(userID)
- shortTTL := 5 * time.Minute
- require.NoError(s.T(), s.cache.IncrementRedeemAttemptCount(s.ctx, userID))
- require.NoError(s.T(), s.rdb.PExpire(s.ctx, key, shortTTL).Err())
- require.NoError(s.T(), s.cache.IncrementRedeemAttemptCount(s.ctx, userID))
- ttl, err := s.rdb.PTTL(s.ctx, key).Result()
- require.NoError(s.T(), err)
- s.AssertTTLWithin(ttl, shortTTL-time.Second, shortTTL)
- }
- func (s *RedeemCacheSuite) TestIncrementRepairsMissingTTL() {
- userID := int64(4)
- key := redeemRateLimitKey(userID)
- require.NoError(s.T(), s.rdb.Set(s.ctx, key, 5, 0).Err())
- ttl, err := s.rdb.PTTL(s.ctx, key).Result()
- require.NoError(s.T(), err)
- require.Equal(s.T(), time.Duration(-1), ttl)
- require.NoError(s.T(), s.cache.IncrementRedeemAttemptCount(s.ctx, userID))
- count, err := s.cache.GetRedeemAttemptCount(s.ctx, userID)
- require.NoError(s.T(), err)
- require.Equal(s.T(), 6, count)
- ttl, err = s.rdb.PTTL(s.ctx, key).Result()
- require.NoError(s.T(), err)
- s.AssertTTLWithin(ttl, redeemRateLimitWindow-time.Second, redeemRateLimitWindow)
- }
- func (s *RedeemCacheSuite) TestMultipleIncrements() {
- userID := int64(2)
- require.NoError(s.T(), s.cache.IncrementRedeemAttemptCount(s.ctx, userID))
- require.NoError(s.T(), s.cache.IncrementRedeemAttemptCount(s.ctx, userID))
- require.NoError(s.T(), s.cache.IncrementRedeemAttemptCount(s.ctx, userID))
- count, err := s.cache.GetRedeemAttemptCount(s.ctx, userID)
- require.NoError(s.T(), err)
- require.Equal(s.T(), 3, count, "count after 3 increments")
- }
- func (s *RedeemCacheSuite) TestAcquireAndReleaseRedeemLock() {
- ok, err := s.cache.AcquireRedeemLock(s.ctx, "CODE", 10*time.Second)
- require.NoError(s.T(), err, "AcquireRedeemLock")
- require.True(s.T(), ok)
- // Second acquire should fail
- ok, err = s.cache.AcquireRedeemLock(s.ctx, "CODE", 10*time.Second)
- require.NoError(s.T(), err, "AcquireRedeemLock 2")
- require.False(s.T(), ok, "expected lock to be held")
- // Release
- require.NoError(s.T(), s.cache.ReleaseRedeemLock(s.ctx, "CODE"), "ReleaseRedeemLock")
- // Now acquire should succeed
- ok, err = s.cache.AcquireRedeemLock(s.ctx, "CODE", 10*time.Second)
- require.NoError(s.T(), err, "AcquireRedeemLock after release")
- require.True(s.T(), ok)
- }
- func (s *RedeemCacheSuite) TestAcquireRedeemLock_TTL() {
- lockKey := redeemLockKeyPrefix + "CODE2"
- lockTTL := 15 * time.Second
- ok, err := s.cache.AcquireRedeemLock(s.ctx, "CODE2", lockTTL)
- require.NoError(s.T(), err, "AcquireRedeemLock CODE2")
- require.True(s.T(), ok)
- ttl, err := s.rdb.TTL(s.ctx, lockKey).Result()
- require.NoError(s.T(), err, "TTL lock key")
- s.AssertTTLWithin(ttl, 1*time.Second, lockTTL)
- }
- func (s *RedeemCacheSuite) TestReleaseRedeemLock_Idempotent() {
- // Release a lock that doesn't exist should not error
- require.NoError(s.T(), s.cache.ReleaseRedeemLock(s.ctx, "NONEXISTENT"))
- // Acquire, release, release again
- ok, err := s.cache.AcquireRedeemLock(s.ctx, "IDEMPOTENT", 10*time.Second)
- require.NoError(s.T(), err)
- require.True(s.T(), ok)
- require.NoError(s.T(), s.cache.ReleaseRedeemLock(s.ctx, "IDEMPOTENT"))
- require.NoError(s.T(), s.cache.ReleaseRedeemLock(s.ctx, "IDEMPOTENT"), "second release should be idempotent")
- }
- func TestRedeemCacheSuite(t *testing.T) {
- suite.Run(t, new(RedeemCacheSuite))
- }
|