Files
galaxy-game/rtmanager/integration/harness/redis.go
T
2026-04-28 20:39:18 +02:00

103 lines
2.9 KiB
Go

package harness
import (
"context"
"sync"
"testing"
"github.com/redis/go-redis/v9"
testcontainers "github.com/testcontainers/testcontainers-go"
rediscontainer "github.com/testcontainers/testcontainers-go/modules/redis"
)
const redisImage = "redis:7"
// RedisEnv carries the per-package Redis fixture. The container is
// started lazily on the first EnsureRedis call and torn down by
// ShutdownRedis at TestMain exit. Both stream consumers and the
// per-game lease store hit this real Redis (miniredis would suffice
// for streams alone, but the lease semantics and eviction-by-TTL we
// rely on in `health_test` are easier to verify against a real
// daemon).
type RedisEnv struct {
container *rediscontainer.RedisContainer
addr string
}
// Addr returns the externally reachable host:port of the Redis
// container. Both the runtime under test and the harness-owned client
// connect through the same endpoint.
func (env *RedisEnv) Addr() string { return env.addr }
// NewClient opens a fresh `*redis.Client` against the harness Redis.
// Tests close their client through `t.Cleanup`; the harness keeps no
// shared client to avoid cross-test connection-pool surprises.
func (env *RedisEnv) NewClient(t testing.TB) *redis.Client {
t.Helper()
client := redis.NewClient(&redis.Options{Addr: env.addr})
t.Cleanup(func() { _ = client.Close() })
return client
}
var (
redisOnce sync.Once
redisEnv *RedisEnv
redisErr error
)
// EnsureRedis starts the per-package Redis container on first
// invocation and returns it. When Docker is unavailable the helper
// calls `t.Skip` so the suite stays green on hosts without a daemon.
func EnsureRedis(t testing.TB) *RedisEnv {
t.Helper()
redisOnce.Do(func() {
redisEnv, redisErr = startRedis()
})
if redisErr != nil {
t.Skipf("rtmanager integration: redis container start failed (Docker unavailable?): %v", redisErr)
}
return redisEnv
}
// FlushRedis drops every key on the harness Redis. Tests call it from
// their setup so streams, offset records, and leases from previous
// scenarios do not leak.
func FlushRedis(t testing.TB) {
t.Helper()
env := EnsureRedis(t)
client := redis.NewClient(&redis.Options{Addr: env.addr})
defer func() { _ = client.Close() }()
if _, err := client.FlushAll(context.Background()).Result(); err != nil {
t.Fatalf("flush rtmanager redis: %v", err)
}
}
// ShutdownRedis terminates the shared container. `TestMain` invokes it
// after `m.Run`.
func ShutdownRedis() {
if redisEnv == nil {
return
}
if redisEnv.container != nil {
_ = testcontainers.TerminateContainer(redisEnv.container)
}
redisEnv = nil
}
func startRedis() (*RedisEnv, error) {
ctx := context.Background()
container, err := rediscontainer.Run(ctx, redisImage)
if err != nil {
return nil, err
}
addr, err := container.Endpoint(ctx, "")
if err != nil {
_ = testcontainers.TerminateContainer(container)
return nil, err
}
return &RedisEnv{
container: container,
addr: addr,
}, nil
}