//go:build integration package redisactivity import ( "context" "fmt" "os" "sync" "sync/atomic" "testing" "time" "github.com/redis/go-redis/v9" "proxy-pool/internal/platform/credentials" ) var testNamespaceSequence atomic.Uint64 type redisTestFixture struct { Adapter *Adapter Client *redis.Client Credentials *credentials.MemoryStore Namespace string Cleanup func() } func TestRedisFixtureUsesIsolatedNamespace(t *testing.T) { fixture := newRedisTestFixture(t) key := fixture.Adapter.keys.operation("fixture-probe") if err := fixture.Client.Set(t.Context(), key, "ok", time.Minute).Err(); err != nil { t.Fatalf("write isolated fixture key: %v", err) } if value, err := fixture.Client.Get(t.Context(), key).Result(); err != nil || value != "ok" { t.Fatalf("read isolated fixture key = (%q, %v)", value, err) } } func newRedisTestFixture(t *testing.T) redisTestFixture { return newRedisFixtureWithCredentialCapacity(t, 10_000) } func newRedisFixtureWithCredentialCapacity(t *testing.T, credentialCapacity int) redisTestFixture { t.Helper() redisURL := os.Getenv("PROXY_POOL_TEST_REDIS_URL") if redisURL == "" { t.Skip("PROXY_POOL_TEST_REDIS_URL is not set") } redisOptions, err := redis.ParseURL(redisURL) if err != nil { t.Fatalf("parse PROXY_POOL_TEST_REDIS_URL: %v", err) } client := redis.NewClient(redisOptions) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := client.Ping(ctx).Err(); err != nil { _ = client.Close() t.Fatalf("ping test Redis: %v", err) } credentialStore, err := credentials.NewMemoryStore(credentialCapacity) if err != nil { _ = client.Close() t.Fatalf("NewMemoryStore(): %v", err) } namespace := fmt.Sprintf("it-%d-%d-%d", os.Getpid(), time.Now().UnixNano(), testNamespaceSequence.Add(1)) adapter, err := New(client, Options{ Namespace: namespace, Credentials: credentialStore, OperationTTL: time.Minute, MaxCandidateScan: 2_048, CleanupLimit: 128, }) if err != nil { _ = client.Close() t.Fatalf("New(): %v", err) } var cleanupOnce sync.Once cleanup := func() { cleanupOnce.Do(func() { cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 5*time.Second) defer cleanupCancel() if err := deleteRedisNamespace(cleanupCtx, client, redisKeyPrefix+namespace+":*"); err != nil { t.Errorf("clean Redis test namespace: %v", err) } _ = client.Close() }) } t.Cleanup(cleanup) return redisTestFixture{ Adapter: adapter, Client: client, Credentials: credentialStore, Namespace: namespace, Cleanup: cleanup, } } func deleteRedisNamespace(ctx context.Context, client *redis.Client, pattern string) error { var cursor uint64 for { keys, next, err := client.Scan(ctx, cursor, pattern, 128).Result() if err != nil { return err } if len(keys) > 0 { if err := client.Unlink(ctx, keys...).Err(); err != nil { return err } } cursor = next if cursor == 0 { return nil } } }