Files
shankar0123 90bfa5d320 test: triage 37 skipped-test sites — closure comments pinning rationale (Q-1)
Closes Q-1 (cat-s3-58ce7e9840be) — 37 t.Skip / testing.Short() sites
across 9 test files audited. Per-site verdict matrix:

  - cmd/agent/verify_test.go (1 site): defensive guard against unreachable
    httptest.NewTLSServer code path. Document-skip with closure comment.

  - deploy/test/qa_test.go (11 sites): file already gated by `//go:build qa`
    tag. The 11 t.Skip("Requires X — manual test") markers are runtime
    second-line guards for operators who run -tags qa against a stack
    missing the required external service. File-level header comment
    block added explaining the manual-test convention.

  - deploy/test/healthcheck_test.go (5 sites): 3 docker-availability +
    1 testing.Short + 1 hard-skip for not-yet-wired runtime probe
    (image-spec contract above already covers the audit-flagged
    regression). All correctly gated; file-level header comment block
    added explaining each.

  - deploy/test/integration_test.go (5 sites): in-flight-state guards
    (poll-with-skip after 90s polling for agent-online, inter-test
    Phase04→Phase07 ordering, scheduler-tick race for discovered certs,
    inter-test issuer fallthrough, defensive PEM-empty assertion).
    Each site now has a closure comment explaining why skip is the
    right choice rather than fail (upstream phase already surfaces the
    real failure; skipping prevents masking root cause behind cascading
    noise).

  - internal/repository/postgres/{testutil,seed,repo}_test.go (5 sites):
    testing.Short() gates for testcontainers-backed live PostgreSQL
    integration tests. All correctly gated; closure comments added
    naming the run command.

  - internal/connector/notifier/email/email_test.go (2 sites):
    anti-fixture assertions (test asserts SMTP dial fails; if a captive
    portal black-holes the call to success, skip rather than false-pass).
    Closure comments added explaining the fixture assumption.

  - internal/connector/target/iis/iis_test.go (2 sites): platform-gated
    skip for powershell.exe absence on non-Windows hosts. Mirrors the
    production iis_connector.go LookPath guard. Closure comments added.

Total: 17 closure comments anchor the 37 skip sites (some sites share a
single block-level comment). All skips remain in place; the change is
purely documentation. The audit recommendation was "audit each skip and
decide" — for these 37, the decision is uniformly **document-skip**:
the gating is correct, the t.Skip messages name the missing precondition,
and the closure comments now pin the rationale for future readers.

See coverage-gap-audit-2026-04-24-v5/unified-audit.md
cat-s3-58ce7e9840be for closure rationale.
2026-04-25 18:44:36 +00:00

205 lines
6.1 KiB
Go

// Package postgres_test contains integration tests for PostgreSQL repository
// implementations using testcontainers-go. Tests spin up a real PostgreSQL 16
// container and use schema-per-test isolation for parallel safety.
package postgres_test
import (
"context"
"database/sql"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
_ "github.com/lib/pq"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
// testDB holds a shared database connection for a test suite.
// Each test gets its own schema (via search_path) for isolation.
type testDB struct {
db *sql.DB
container testcontainers.Container
}
// setupTestDB starts a PostgreSQL container and runs all migrations.
// Call this once per test file via TestMain or a sync.Once.
func setupTestDB(t *testing.T) *testDB {
t.Helper()
// Q-1 closure (cat-s3-58ce7e9840be): live PostgreSQL needed via
// testcontainers-go (postgres:16-alpine). Run with:
// go test -count=1 ./internal/repository/postgres/... (omit -short)
// The short-mode gate keeps it off the default `go test ./... -short`
// fast loop where docker-in-docker may not be available.
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
ctx := context.Background()
req := testcontainers.ContainerRequest{
Image: "postgres:16-alpine",
ExposedPorts: []string{"5432/tcp"},
Env: map[string]string{
"POSTGRES_DB": "certctl_test",
"POSTGRES_USER": "certctl",
"POSTGRES_PASSWORD": "certctl",
},
WaitingFor: wait.ForLog("database system is ready to accept connections").WithOccurrence(2),
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatalf("failed to start postgres container: %v", err)
}
host, err := container.Host(ctx)
if err != nil {
t.Fatalf("failed to get container host: %v", err)
}
port, err := container.MappedPort(ctx, "5432")
if err != nil {
t.Fatalf("failed to get mapped port: %v", err)
}
connStr := fmt.Sprintf("postgres://certctl:certctl@%s:%s/certctl_test?sslmode=disable", host, port.Port())
db, err := sql.Open("postgres", connStr)
if err != nil {
t.Fatalf("failed to open database: %v", err)
}
// Limit to 1 connection so SET search_path persists across all queries.
db.SetMaxOpenConns(1)
if err := db.Ping(); err != nil {
t.Fatalf("failed to ping database: %v", err)
}
// Run migrations
migrationsPath := findMigrationsDir()
if err := runMigrations(db, migrationsPath); err != nil {
t.Fatalf("failed to run migrations: %v", err)
}
return &testDB{db: db, container: container}
}
// teardown stops the container and closes the connection.
func (tdb *testDB) teardown(t *testing.T) {
t.Helper()
if tdb.db != nil {
tdb.db.Close()
}
if tdb.container != nil {
tdb.container.Terminate(context.Background())
}
}
// freshSchema creates a new PostgreSQL schema for test isolation
// and returns a *sql.DB with search_path set to that schema.
// Each test gets a unique schema so tests don't interfere with each other.
func (tdb *testDB) freshSchema(t *testing.T) *sql.DB {
t.Helper()
// Create a unique schema name from the test name
schemaName := sanitizeSchemaName(t.Name())
ctx := context.Background()
// Create schema
_, err := tdb.db.ExecContext(ctx, fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", schemaName))
if err != nil {
t.Fatalf("failed to create schema %s: %v", schemaName, err)
}
// Set search_path for this connection to use the new schema
_, err = tdb.db.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s, public", schemaName))
if err != nil {
t.Fatalf("failed to set search_path: %v", err)
}
// Run migrations in the new schema
migrationsPath := findMigrationsDir()
if err := runMigrationsWithSearchPath(tdb.db, migrationsPath, schemaName); err != nil {
t.Fatalf("failed to run migrations in schema %s: %v", schemaName, err)
}
// Register cleanup
t.Cleanup(func() {
tdb.db.ExecContext(context.Background(), fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName))
})
return tdb.db
}
// sanitizeSchemaName converts a test name to a valid PostgreSQL schema name.
func sanitizeSchemaName(name string) string {
name = strings.ToLower(name)
name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, " ", "_")
name = strings.ReplaceAll(name, "-", "_")
name = strings.ReplaceAll(name, ".", "_")
// Truncate to 63 chars (PG limit)
if len(name) > 60 {
name = name[:60]
}
return "test_" + name
}
// findMigrationsDir walks up from the test file to find the migrations/ directory.
func findMigrationsDir() string {
_, filename, _, _ := runtime.Caller(0)
dir := filepath.Dir(filename)
// Walk up to find the project root (where migrations/ lives)
for i := 0; i < 10; i++ {
candidate := filepath.Join(dir, "migrations")
if _, err := os.Stat(candidate); err == nil {
return candidate
}
dir = filepath.Dir(dir)
}
// Fallback: try relative from working directory
return "../../../../migrations"
}
// runMigrations reads and executes all .up.sql migration files.
func runMigrations(db *sql.DB, migrationsPath string) error {
files, err := os.ReadDir(migrationsPath)
if err != nil {
return fmt.Errorf("failed to read migrations directory %s: %w", migrationsPath, err)
}
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".up.sql") {
content, err := os.ReadFile(filepath.Join(migrationsPath, file.Name()))
if err != nil {
return fmt.Errorf("failed to read migration %s: %w", file.Name(), err)
}
if _, err := db.Exec(string(content)); err != nil {
return fmt.Errorf("failed to execute migration %s: %w", file.Name(), err)
}
}
}
return nil
}
// runMigrationsWithSearchPath runs migrations within a specific schema.
func runMigrationsWithSearchPath(db *sql.DB, migrationsPath string, schema string) error {
// Set search_path before running migrations
if _, err := db.Exec(fmt.Sprintf("SET search_path TO %s, public", schema)); err != nil {
return fmt.Errorf("failed to set search_path: %w", err)
}
return runMigrations(db, migrationsPath)
}