//go:build integration // Integration test: runs a real postgres container that accepts connections a // few seconds after start and asserts waitfordb retries then succeeds. Run with: // // go test -tags integration -run TestIntegration -v . // // Requires podman (or docker via WAITFORDB_TEST_RUNTIME). Skipped otherwise. package main import ( "context" "os" "os/exec" "strings" "testing" "time" "git.unkin.net/unkin/waitfordb/internal/config" "git.unkin.net/unkin/waitfordb/internal/driver" "git.unkin.net/unkin/waitfordb/internal/wait" ) func TestIntegrationPostgresComesUpLate(t *testing.T) { runtime := envOr("WAITFORDB_TEST_RUNTIME", "podman") if _, err := exec.LookPath(runtime); err != nil { t.Skipf("%s not available: %v", runtime, err) } image := envOr("WAITFORDB_TEST_PG_IMAGE", "docker.io/library/postgres:16") name := "waitfordb-it-" + time.Now().Format("150405") // Publish on a fixed host port; map to a random-ish high port. hostPort := "55432" args := []string{ "run", "-d", "--rm", "--name", name, "-e", "POSTGRES_PASSWORD=secret", "-e", "POSTGRES_USER=app", "-e", "POSTGRES_DB=appdb", "-p", hostPort + ":5432", image, } if out, err := exec.Command(runtime, args...).CombinedOutput(); err != nil { t.Skipf("cannot start container (%s): %v\n%s", runtime, err, out) } t.Cleanup(func() { _ = exec.Command(runtime, "rm", "-f", name).Run() }) t.Setenv("WAITFORDB_HOST", "127.0.0.1") t.Setenv("WAITFORDB_PORT", hostPort) t.Setenv("WAITFORDB_USER", "app") t.Setenv("WAITFORDB_PASSWORD", "secret") t.Setenv("WAITFORDB_DATABASE", "appdb") t.Setenv("WAITFORDB_SSLMODE", "disable") t.Setenv("WAITFORDB_TIMEOUT", "60s") t.Setenv("WAITFORDB_INTERVAL", "1s") cfg, err := config.Load(os.Getenv) if err != nil { t.Fatalf("config: %v", err) } drv, err := driver.Get(cfg.Driver) if err != nil { t.Fatalf("driver: %v", err) } pinger, err := drv.Open(cfg) if err != nil { t.Fatalf("open: %v", err) } defer pinger.Close() res := wait.Run(context.Background(), wait.Params{Timeout: cfg.Timeout, Interval: cfg.Interval, ConnectTimeout: cfg.ConnectTimeout}, pinger.Ping, func(n int, err error, _, _, _ time.Duration) { t.Logf("attempt %d failed as expected: %v", n, err) }, wait.RealClock{}) if !res.OK { t.Fatalf("expected readiness, got %+v", res) } t.Logf("ready after %s (%d attempts)", res.Elapsed, res.Attempts) if res.Attempts < 1 { t.Errorf("expected at least one attempt") } } func envOr(k, def string) string { if v := strings.TrimSpace(os.Getenv(k)); v != "" { return v } return def }