Files
2026-04-26 20:34:39 +02:00

259 lines
6.2 KiB
Go

package redisconn_test
import (
"context"
"strings"
"testing"
"time"
"galaxy/redisconn"
"github.com/alicebob/miniredis/v2"
"go.opentelemetry.io/otel/metric/noop"
tracenoop "go.opentelemetry.io/otel/trace/noop"
)
func TestDefaultConfigReturnsExpectedTuning(t *testing.T) {
t.Parallel()
cfg := redisconn.DefaultConfig()
if cfg.OperationTimeout != redisconn.DefaultOperationTimeout {
t.Fatalf("operation timeout = %v, want %v", cfg.OperationTimeout, redisconn.DefaultOperationTimeout)
}
if cfg.DB != redisconn.DefaultDB {
t.Fatalf("db = %d, want %d", cfg.DB, redisconn.DefaultDB)
}
}
func TestConfigValidateRejectsInvalidValues(t *testing.T) {
t.Parallel()
tests := []struct {
name string
mutate func(*redisconn.Config)
wantSub string
}{
{
name: "missing master",
mutate: func(c *redisconn.Config) {
c.MasterAddr = ""
},
wantSub: "master addr",
},
{
name: "missing password",
mutate: func(c *redisconn.Config) {
c.Password = ""
},
wantSub: "password",
},
{
name: "blank replica entry",
mutate: func(c *redisconn.Config) {
c.ReplicaAddrs = []string{" "}
},
wantSub: "replica addr",
},
{
name: "negative db",
mutate: func(c *redisconn.Config) {
c.DB = -1
},
wantSub: "db must not be negative",
},
{
name: "non-positive timeout",
mutate: func(c *redisconn.Config) {
c.OperationTimeout = 0
},
wantSub: "operation timeout",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
cfg := redisconn.DefaultConfig()
cfg.MasterAddr = "127.0.0.1:6379"
cfg.Password = "secret"
tt.mutate(&cfg)
err := cfg.Validate()
if err == nil {
t.Fatalf("expected validate error, got nil")
}
if !strings.Contains(err.Error(), tt.wantSub) {
t.Fatalf("error %q does not contain %q", err, tt.wantSub)
}
})
}
}
func TestLoadFromEnvHappyPath(t *testing.T) {
const prefix = "TESTSVC"
t.Setenv(prefix+"_REDIS_MASTER_ADDR", "127.0.0.1:6379")
t.Setenv(prefix+"_REDIS_REPLICA_ADDRS", "127.0.0.1:6380, 127.0.0.1:6381 ,")
t.Setenv(prefix+"_REDIS_PASSWORD", "secret")
t.Setenv(prefix+"_REDIS_DB", "3")
t.Setenv(prefix+"_REDIS_OPERATION_TIMEOUT", "500ms")
cfg, err := redisconn.LoadFromEnv(prefix)
if err != nil {
t.Fatalf("load from env: %v", err)
}
if cfg.MasterAddr != "127.0.0.1:6379" {
t.Fatalf("master addr = %q", cfg.MasterAddr)
}
if cfg.Password != "secret" {
t.Fatalf("password = %q", cfg.Password)
}
if got, want := cfg.DB, 3; got != want {
t.Fatalf("db = %d, want %d", got, want)
}
if got, want := cfg.OperationTimeout, 500*time.Millisecond; got != want {
t.Fatalf("operation timeout = %v, want %v", got, want)
}
if got, want := len(cfg.ReplicaAddrs), 2; got != want {
t.Fatalf("replica count = %d, want %d", got, want)
}
}
func TestLoadFromEnvRejectsDeprecatedTLSEnabled(t *testing.T) {
const prefix = "TESTSVC"
t.Setenv(prefix+"_REDIS_MASTER_ADDR", "127.0.0.1:6379")
t.Setenv(prefix+"_REDIS_PASSWORD", "secret")
t.Setenv(prefix+"_REDIS_TLS_ENABLED", "true")
_, err := redisconn.LoadFromEnv(prefix)
if err == nil {
t.Fatal("expected error when TLS_ENABLED is set")
}
if !strings.Contains(err.Error(), "TLS_ENABLED") {
t.Fatalf("error %q should name TLS_ENABLED", err)
}
if !strings.Contains(err.Error(), "ARCHITECTURE.md") {
t.Fatalf("error %q should reference ARCHITECTURE.md", err)
}
}
func TestLoadFromEnvRejectsDeprecatedUsername(t *testing.T) {
const prefix = "TESTSVC"
t.Setenv(prefix+"_REDIS_MASTER_ADDR", "127.0.0.1:6379")
t.Setenv(prefix+"_REDIS_PASSWORD", "secret")
t.Setenv(prefix+"_REDIS_USERNAME", "anything")
_, err := redisconn.LoadFromEnv(prefix)
if err == nil {
t.Fatal("expected error when USERNAME is set")
}
if !strings.Contains(err.Error(), "USERNAME") {
t.Fatalf("error %q should name USERNAME", err)
}
}
func TestLoadFromEnvRequiresPassword(t *testing.T) {
const prefix = "TESTSVC"
t.Setenv(prefix+"_REDIS_MASTER_ADDR", "127.0.0.1:6379")
t.Setenv(prefix+"_REDIS_PASSWORD", "")
if _, err := redisconn.LoadFromEnv(prefix); err == nil {
t.Fatal("expected error when password is empty")
}
}
func TestNewMasterClientPingsMiniredis(t *testing.T) {
t.Parallel()
server := miniredis.RunT(t)
server.RequireAuth("secret")
cfg := redisconn.DefaultConfig()
cfg.MasterAddr = server.Addr()
cfg.Password = "secret"
if err := cfg.Validate(); err != nil {
t.Fatalf("validate: %v", err)
}
client := redisconn.NewMasterClient(cfg)
t.Cleanup(func() {
_ = client.Close()
})
if err := redisconn.Ping(context.Background(), client, cfg.OperationTimeout); err != nil {
t.Fatalf("ping miniredis: %v", err)
}
}
func TestNewReplicaClientsReturnsExpectedLength(t *testing.T) {
t.Parallel()
server1 := miniredis.RunT(t)
server2 := miniredis.RunT(t)
cfg := redisconn.DefaultConfig()
cfg.MasterAddr = "ignored:6379"
cfg.Password = "secret"
cfg.ReplicaAddrs = []string{server1.Addr(), server2.Addr()}
clients := redisconn.NewReplicaClients(cfg)
t.Cleanup(func() {
for _, client := range clients {
_ = client.Close()
}
})
if got, want := len(clients), 2; got != want {
t.Fatalf("client count = %d, want %d", got, want)
}
}
func TestNewReplicaClientsReturnsNilWhenUnconfigured(t *testing.T) {
t.Parallel()
cfg := redisconn.DefaultConfig()
cfg.MasterAddr = "ignored:6379"
cfg.Password = "secret"
if clients := redisconn.NewReplicaClients(cfg); clients != nil {
t.Fatalf("clients = %v, want nil", clients)
}
}
func TestInstrumentAcceptsNoopProviders(t *testing.T) {
t.Parallel()
server := miniredis.RunT(t)
server.RequireAuth("secret")
cfg := redisconn.DefaultConfig()
cfg.MasterAddr = server.Addr()
cfg.Password = "secret"
client := redisconn.NewMasterClient(cfg)
t.Cleanup(func() {
_ = client.Close()
})
err := redisconn.Instrument(
client,
redisconn.WithTracerProvider(tracenoop.NewTracerProvider()),
redisconn.WithMeterProvider(noop.NewMeterProvider()),
)
if err != nil {
t.Fatalf("instrument: %v", err)
}
if err := redisconn.Ping(context.Background(), client, cfg.OperationTimeout); err != nil {
t.Fatalf("ping after instrument: %v", err)
}
}
func TestInstrumentRejectsNilClient(t *testing.T) {
t.Parallel()
if err := redisconn.Instrument(nil); err == nil {
t.Fatal("expected error for nil client")
}
}