Files
waitfordb/integration_test.go
T
unkin-agent 2b37986218 waitfordb: initial tool — env-configured wait-for-DB init container
Small Go tool + distroless container used as a K8s initContainer to block an
app until its database (Postgres/MySQL) is reachable. Env-var configured
(WAITFORDB_* + libpq PG* fallback), configurable timeout/interval, redacted
logs, exit codes. Woodpecker CI publishes docker-internal/waitfordb on tag.
2026-08-22 13:31:44 +10:00

95 lines
2.5 KiB
Go

//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
}