package redispkg import ( "context" "os" "testing" "time" ) func TestDisabledIsSafe(t *testing.T) { r := New("") ctx := context.Background() if _, ok := r.Get(ctx, "x"); ok { t.Fatal("disabled Get must miss") } r.Set(ctx, "x", "y", time.Second) // 不 panic if n := r.IncrWindow(ctx, "k", time.Second); n != 1 { t.Fatal("disabled IncrWindow must allow") } un, ok := r.Lock(ctx, "lk", time.Second) if !ok { t.Fatal("disabled Lock must always acquire") } un() // ScanLock disabled mode. un2, ok2 := r.ScanLock(ctx, "slk", time.Second) if !ok2 { t.Fatal("disabled ScanLock must always acquire") } un2() } func TestDeadRedisLockFailsOpen(t *testing.T) { r := New("redis://127.0.0.1:16399") // 死端口 un, ok := r.Lock(context.Background(), "lk", time.Second) if !ok { t.Fatal("Lock on redis error must fail open (spec §9)") } un() } func TestDeadRedisScanLockFailsOpen(t *testing.T) { r := New("redis://127.0.0.1:16399") un, ok := r.ScanLock(context.Background(), "slk", time.Second) if !ok { t.Fatal("ScanLock on redis error must fail open") } un() } func newLiveRedis(t *testing.T) *R { t.Helper() url := os.Getenv("REDIS_URL") if url == "" { t.Skip("REDIS_URL not set") } return New(url) } func TestIncrWindowSetsTTL(t *testing.T) { r := newLiveRedis(t) ctx := context.Background() key := "test:incrwindow:ttl:" + t.Name() r.c.Del(ctx, key) n := r.IncrWindow(ctx, key, 5*time.Second) if n != 1 { t.Fatalf("first call = %d, want 1", n) } ttl, err := r.c.TTL(ctx, key).Result() if err != nil { t.Fatal(err) } if ttl <= 0 { t.Fatalf("TTL should be positive after first increment, got %v", ttl) } // Second call: n=2, TTL should still be positive. n = r.IncrWindow(ctx, key, 5*time.Second) if n != 2 { t.Fatalf("second call = %d, want 2", n) } r.c.Del(ctx, key) } func TestScanLockRenewal(t *testing.T) { r := newLiveRedis(t) ctx := context.Background() key := "test:scanlock:" + t.Name() r.c.Del(ctx, key) unlock, ok := r.ScanLock(ctx, key, 2*time.Second) if !ok { t.Fatal("should acquire") } // Wait 3 seconds — without renewal, the lock would expire at 2s. time.Sleep(3 * time.Second) // A second attempt should fail (lock still held by first, renewed). _, ok2 := r.ScanLock(ctx, key, 2*time.Second) if ok2 { t.Fatal("second acquire should fail — lock should have been renewed") } unlock() r.c.Del(ctx, key) } func TestScanLockMutualExclusion(t *testing.T) { r := newLiveRedis(t) ctx := context.Background() key := "test:scanlock:mutex:" + t.Name() r.c.Del(ctx, key) unlock1, ok1 := r.ScanLock(ctx, key, 10*time.Second) if !ok1 { t.Fatal("first should acquire") } _, ok2 := r.ScanLock(ctx, key, 10*time.Second) if ok2 { t.Fatal("second should not acquire while first holds") } unlock1() // After unlock, a new acquire should succeed. time.Sleep(50 * time.Millisecond) unlock3, ok3 := r.ScanLock(ctx, key, 10*time.Second) if !ok3 { t.Fatal("should acquire after unlock") } unlock3() r.c.Del(ctx, key) }