package deploy import ( "os" "slices" "strings" "testing" "go.yaml.in/yaml/v4" ) type composeDocument struct { Services map[string]composeService `yaml:"services"` Volumes map[string]any `yaml:"volumes"` } type composeService struct { Image string `yaml:"image"` Command []string `yaml:"command"` Ports []string `yaml:"ports"` Volumes []string `yaml:"volumes"` Tmpfs []string `yaml:"tmpfs"` DependsOn any `yaml:"depends_on"` } func TestPostgresIntegrationFixtureIsIsolatedAndEphemeral(t *testing.T) { document := loadComposeFile(t, "docker-compose.test.yml") postgres, ok := document.Services["postgres"] if !ok { t.Fatal("docker-compose.test.yml has no postgres service") } if postgres.Image != "postgres:18-alpine" { t.Fatalf("postgres image = %q, want postgres:18-alpine", postgres.Image) } if !slices.Equal(postgres.Ports, []string{"127.0.0.1:15432:5432"}) { t.Fatalf("postgres ports = %v, want loopback test port", postgres.Ports) } if !slices.Contains(postgres.Tmpfs, "/var/lib/postgresql") || len(postgres.Volumes) != 0 { t.Fatalf("postgres tmpfs = %v, volumes = %v; want ephemeral PG18 data root", postgres.Tmpfs, postgres.Volumes) } } func TestRedisFixtureScriptUsesDedicatedComposeProject(t *testing.T) { payload, err := os.ReadFile("../scripts/test-redis.ps1") if err != nil { t.Fatalf("read test-redis.ps1: %v", err) } script := string(payload) for _, required := range []string{ `-p $composeProject`, `up -d --wait --wait-timeout 60 redis`, `down --volumes --remove-orphans`, } { if !strings.Contains(script, required) { t.Errorf("test-redis.ps1 missing %q", required) } } } func TestPostgresFixtureScriptUsesDedicatedComposeProject(t *testing.T) { payload, err := os.ReadFile("../scripts/test-postgres.ps1") if err != nil { t.Fatalf("read test-postgres.ps1: %v", err) } script := string(payload) for _, required := range []string{ `-p $composeProject`, `up -d --wait --wait-timeout 60 postgres`, `PROXY_POOL_TEST_POSTGRES_URL`, `go test -count=1 -tags=integration -timeout 60s ./internal/adapters/postgresadmin/...`, `down --volumes --remove-orphans`, } { if !strings.Contains(script, required) { t.Errorf("test-postgres.ps1 missing %q", required) } } } func TestControllerFixtureScriptStartsBothStoresInDedicatedProject(t *testing.T) { payload, err := os.ReadFile("../scripts/test-controller.ps1") if err != nil { t.Fatalf("read test-controller.ps1: %v", err) } script := string(payload) for _, required := range []string{ `-p $composeProject`, `up -d --wait --wait-timeout 60 postgres redis`, `PROXY_POOL_TEST_POSTGRES_URL`, `PROXY_POOL_TEST_REDIS_URL`, `go test -count=1 -tags=integration -timeout 60s ./internal/controller/bootstrap`, `down --volumes --remove-orphans`, } { if !strings.Contains(script, required) { t.Errorf("test-controller.ps1 missing %q", required) } } } func TestLocalRedisIsExplicitlyEphemeral(t *testing.T) { document := loadComposeDocument(t) redis, ok := document.Services["redis"] if !ok { t.Fatal("docker-compose.yml has no redis service") } if value, ok := commandFlag(redis.Command, "--appendonly"); !ok || value != "no" { t.Fatalf("redis --appendonly = %q, %t; want no", value, ok) } if value, ok := commandFlag(redis.Command, "--save"); !ok || value != "" { t.Fatalf("redis --save = %q, %t; want empty schedule", value, ok) } if len(redis.Volumes) != 0 { t.Fatalf("redis volumes = %v; want no persistent mount", redis.Volumes) } if _, exists := document.Volumes["redis-data"]; exists { t.Fatal("docker-compose.yml still declares redis-data") } } func TestLocalGatewaysDoNotDependOnControlPlaneStorage(t *testing.T) { document := loadComposeDocument(t) for _, name := range []string{"gateway-a", "gateway-b"} { gateway, ok := document.Services[name] if !ok { t.Fatalf("docker-compose.yml has no %s service", name) } for _, storage := range []string{"postgres", "redis"} { if composeDependsOn(gateway.DependsOn, storage) { t.Errorf("%s depends on %s; gateway startup must be storage-independent", name, storage) } } } } func loadComposeDocument(t *testing.T) composeDocument { t.Helper() return loadComposeFile(t, "docker-compose.yml") } func loadComposeFile(t *testing.T, name string) composeDocument { t.Helper() payload, err := os.ReadFile(name) if err != nil { t.Fatalf("read %s: %v", name, err) } var document composeDocument if err := yaml.Unmarshal(payload, &document); err != nil { t.Fatalf("parse %s: %v", name, err) } return document } func composeDependsOn(value any, service string) bool { switch dependencies := value.(type) { case map[string]any: _, exists := dependencies[service] return exists case []any: for _, dependency := range dependencies { if dependency == service { return true } } } return false } func commandFlag(command []string, name string) (string, bool) { for index := 0; index+1 < len(command); index++ { if command[index] == name { return command[index+1], true } } return "", false }