219 lines
6.4 KiB
Go
219 lines
6.4 KiB
Go
package deploy
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"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 TestDeploymentEntrypointsExistInSource(t *testing.T) {
|
|
dockerfile, err := os.ReadFile("docker/Dockerfile")
|
|
if err != nil {
|
|
t.Fatalf("read docker/Dockerfile: %v", err)
|
|
}
|
|
entrypoints := regexp.MustCompile(`\./cmd/(proxy-[a-z0-9-]+)`).FindAllStringSubmatch(string(dockerfile), -1)
|
|
if len(entrypoints) == 0 {
|
|
t.Fatal("docker/Dockerfile has no proxy command build target")
|
|
}
|
|
for _, entrypoint := range entrypoints {
|
|
assertCommandDirectory(t, entrypoint[1])
|
|
}
|
|
|
|
document := loadComposeDocument(t)
|
|
for name, service := range document.Services {
|
|
if len(service.Command) == 0 || !strings.HasPrefix(service.Command[0], "proxy-") {
|
|
continue
|
|
}
|
|
if _, err := os.Stat(filepath.Join("..", "cmd", service.Command[0])); err != nil {
|
|
t.Errorf("compose service %s command %q has no source entrypoint: %v", name, service.Command[0], err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestKubernetesBaseExcludesUnimplementedProcessManifests(t *testing.T) {
|
|
payload, err := os.ReadFile("kubernetes/base/kustomization.yaml")
|
|
if err != nil {
|
|
t.Fatalf("read kustomization.yaml: %v", err)
|
|
}
|
|
if strings.Contains(string(payload), "checker.yaml") {
|
|
t.Fatal("kubernetes base includes checker.yaml before proxy-checker exists")
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func assertCommandDirectory(t *testing.T, command string) {
|
|
t.Helper()
|
|
info, err := os.Stat(filepath.Join("..", "cmd", command))
|
|
if err != nil || !info.IsDir() {
|
|
t.Errorf("build command %q has no source directory", command)
|
|
}
|
|
}
|