Files
paliad/Makefile
mAi c901293c9c
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
feat(cicd): Slice A — pre-deploy gate + role-split migration smoke
Adds .gitea/workflows/test.yaml that gates every push on `go build`,
`bun run build`, `go vet`, the migration coordination check, and the
role-split end-to-end migration smoke. On push to main + green, calls
Dokploy's compose.deploy API and polls /health/ready until 200.

t-paliad-282 / m/paliad#114. Design: docs/design-cicd-pre-deploy-gate-2026-05-25.md
(inventor shift on mai/cronus/inventor-ci-cd-pre).

Catches all three of today's outage classes:

  brunel (~13:20) slot collision     -> TestMigrations_NoDuplicateSlot
  hermes (~16:05) dropped-col refs   -> TestBootSmoke
  mig 129 (~14:56) 42501 ownership   -> TestMigrations_EndToEndAsAppRole

Snapshot approach. internal/db/testdata/prod-snapshot.sql is a pg_dump
of youpc-supabase paliad schema + applied_migrations rows. CI restores
this into a fresh `supabase/postgres:15.8.1.060` (same image, same role
topology as prod) and runs ApplyMigrations as the `postgres` role
(which is NOT a superuser on supabase/postgres, matching prod). Existing
migrations are skipped (already in applied_migrations); only NEW migs
from the PR run end-to-end. This sidesteps the fresh-DB idempotence
debt in some historical migrations (mig 037 missing pg_trgm, mig 051
inner COMMIT) — those are tracked separately and don't block the gate.

Sub-changes:

- internal/handlers/handlers.go — new /health/ready endpoint distinct
  from /healthz. /healthz stays liveness (process alive, no DB); /ready
  is readiness (DB pool pings within 2 s). Returns 503 when svc or pool
  is nil (DB-less deploys are intentionally not-ready). svc.Pool added
  to handlers.Services, wired in cmd/server/main.go.

- internal/db/migrate_test.go — TestMigrations_NoDuplicateSlot (pure
  unit, catches brunel) and TestMigrations_EndToEndAsAppRole (snapshot-
  gated, catches the 42501 class).

- cmd/server/main_smoke_test.go — TestBootSmoke now also asserts
  /health/ready returns 503 with a nil svc. New TestHealthReady_Live
  asserts 200 against a live pool.

- internal/db/migrations/024_rename_department_columns.up.sql and
  027_rename_to_partner_units.up.sql — ALTER INDEX / ALTER POLICY
  exception handlers now catch undefined_object OR undefined_table OR
  duplicate_object. Old handler only caught undefined_object; Postgres
  raises undefined_table when source object never existed, and
  duplicate_object when destination already exists. The expanded
  handlers make these migrations truly idempotent across all plausible
  starting states.

- Makefile — verify-mig-app, test-frontend, refresh-snapshot targets.
  refresh-snapshot pg_dumps youpc-supabase prod (needs PALIAD_PROD_DATABASE_URL),
  strips pg16 \restrict commands for pg15 restore compat, and filters
  applied_migrations rows to this branch's max on-disk version.

- internal/db/testdata/README.md — explains the snapshot's purpose,
  refresh procedure, and how to verify locally.

- docs/cicd-runner-setup-2026-05-25.md — one-time admin steps for
  registering a Gitea Actions runner on mriver and wiring DOKPLOY_TOKEN
  as a repo secret. Documents soft-launch plan per m's Q11.4 (keep
  Dokploy's autoDeploy=true webhook alive for one week, disable after
  the workflow has gated 5 successful deploys).

Build clean. Full go test ./internal/... ./cmd/... green without
TEST_DATABASE_URL. With TEST_DATABASE_URL + TEST_APP_DATABASE_URL set
to a supabase/postgres scratch + snapshot restored:
TestMigrations_NoDuplicateSlot, TestMigrations_EndToEndAsAppRole,
TestBootSmoke, TestHealthReady_Live all pass. Live-DB service tests in
internal/services/* fail under supabase/postgres 15.8 with a 42P08
parameter-binding error (unrelated to Slice A — tracked as a follow-up).
2026-05-25 17:42:06 +02:00

144 lines
6.9 KiB
Makefile

# Paliad — developer entrypoints.
#
# Targets here are the gate tier from the test-strategy design
# (docs/design-paliad-test-strategy-2026-05-19.md). Slice 1 lands:
#
# make verify-migrations — dry-run every pending migration (BEGIN..ROLLBACK)
# plus the full boot smoke (apply + tracker
# advances + /healthz returns 200).
# make verify-mig — alias for verify-migrations.
# make test — short test pass: go test ./internal/... -short
# plus the cmd/server package. Includes the
# live-DB tests when TEST_DATABASE_URL is set,
# skips them otherwise.
# make test-go — go test ./... -race (full Go suite).
#
# Future slices will extend this with:
# make test-frontend — bun test (Slice 3 / Slice 6)
# make e2e — Playwright golden-path suite (Slice 4)
#
# All targets are idempotent. None of them write to the filesystem outside
# the test runner's working dirs. None of them touch internal/db/migrations/
# files.
.PHONY: help verify-migrations verify-mig verify-mig-app test test-go test-frontend refresh-snapshot
help:
@echo "Paliad — developer targets"
@echo ""
@echo " verify-migrations Dry-run pending migrations + boot smoke (needs TEST_DATABASE_URL)"
@echo " verify-mig Alias for verify-migrations"
@echo " verify-mig-app End-to-end migration smoke as non-superuser role"
@echo " (needs TEST_APP_DATABASE_URL — t-paliad-282 / m/paliad#114)"
@echo " test Short test pass — covers gate tier"
@echo " test-go Full Go suite with race detector"
@echo " test-frontend Frontend bun:test suite"
@echo ""
@echo "Set TEST_DATABASE_URL to enable live-DB tests. Example:"
@echo " export TEST_DATABASE_URL=postgres://paliad:...@localhost:11833/paliad_test"
@echo ""
@echo "Set TEST_APP_DATABASE_URL to enable the role-split smoke. Example:"
@echo " export TEST_APP_DATABASE_URL=postgres://paliad_app:...@localhost:5432/paliad_scratch"
# Gate target — the test that would have caught mig 098 / mig 099 before
# deploy. Combines:
# - TestMigrations_DryRun (internal/db): per-migration BEGIN..ROLLBACK
# - TestBootSmoke (cmd/server): apply-end-to-end + tracker advances
# + /healthz 200
#
# Requires TEST_DATABASE_URL. Without it, both tests skip and the target
# is effectively a no-op — guard against that explicitly so CI doesn't
# silently green a missing env var.
verify-migrations:
@if [ -z "$$TEST_DATABASE_URL" ]; then \
echo "ERROR: TEST_DATABASE_URL is not set."; \
echo " The migration gate cannot run without a scratch DB."; \
echo " Set TEST_DATABASE_URL to a Postgres URL the test can"; \
echo " open transactions against, e.g."; \
echo " export TEST_DATABASE_URL=postgres://paliad:PW@localhost:11833/paliad_test"; \
exit 2; \
fi
@echo "==> migration dry-run (per-mig BEGIN..ROLLBACK)"
go test -count=1 -run TestMigrations_DryRun ./internal/db/
@echo "==> boot smoke (apply + tracker + /healthz)"
go test -count=1 -run TestBootSmoke ./cmd/server/
verify-mig: verify-migrations
# Gate-tier test pass. -short skips the slow live-DB tests when the
# author opts out via `if testing.Short() { t.Skip(...) }`; today most of
# paliad's live-DB tests gate on TEST_DATABASE_URL instead, so -short is
# forward-compatible rather than load-bearing.
test:
go test -short ./internal/... ./cmd/...
# Full Go suite with race detection. Slower but catches concurrent-map
# regressions that -short would skip; intended for the merge-to-main gate
# (full suite, not per-PR).
test-go:
go test -race ./...
# Frontend bun:test suite. Runs the 4 existing pure-TS tests today; will
# grow as mendel's Slice 3 (frontend test infill) lands.
test-frontend:
cd frontend && bun test
# Role-split end-to-end migration smoke — the catch for the mig 129 42501
# ownership class (m/paliad#114). Runs ApplyMigrations as a non-superuser
# role against TEST_APP_DATABASE_URL. Fails the build if any migration
# assumes more privilege than the deploy role has.
#
# Developer setup (local):
# psql -c "CREATE ROLE paliad_app LOGIN PASSWORD 'ci' NOSUPERUSER;"
# psql -c "CREATE DATABASE paliad_scratch OWNER paliad_app;"
# export TEST_APP_DATABASE_URL=postgres://paliad_app:ci@localhost:5432/paliad_scratch
verify-mig-app:
@if [ -z "$$TEST_APP_DATABASE_URL" ]; then \
echo "ERROR: TEST_APP_DATABASE_URL is not set."; \
echo " The role-split migration smoke cannot run without a non-superuser scratch DB."; \
echo " See Makefile comments above this target for setup."; \
exit 2; \
fi
go test -count=1 -run TestMigrations_EndToEndAsAppRole ./internal/db/
# Refresh the prod schema snapshot used by CI's migration smoke
# (t-paliad-282 / m/paliad#114). Connects to youpc-supabase prod, dumps
# the paliad schema + applied_migrations rows, strips rows beyond the
# current branch's max on-disk version, and writes
# internal/db/testdata/prod-snapshot.sql.
#
# When to refresh:
# - After merging a PR that added a new migration to main.
# - When CI's migration smoke starts spuriously failing because the
# snapshot's applied set diverges from on-disk by more than this
# branch's worth of new migs.
#
# Requires PALIAD_PROD_DATABASE_URL env var (a Postgres URL with
# pg_dump rights on youpc-supabase). Example:
# export PALIAD_PROD_DATABASE_URL='postgres://postgres:PW@100.99.98.201:11833/postgres'
refresh-snapshot:
@if [ -z "$$PALIAD_PROD_DATABASE_URL" ]; then \
echo "ERROR: PALIAD_PROD_DATABASE_URL is not set."; \
echo " Refresh requires read access to youpc-supabase prod."; \
exit 2; \
fi
@echo "==> dumping paliad schema (no owner, no privs)..."
@pg_dump --schema-only --schema=paliad --no-owner --no-privileges \
--no-publications --no-subscriptions \
"$$PALIAD_PROD_DATABASE_URL" > internal/db/testdata/prod-snapshot.sql.tmp
@echo "==> appending applied_migrations rows..."
@pg_dump --data-only --table=paliad.applied_migrations \
--no-owner --no-privileges \
"$$PALIAD_PROD_DATABASE_URL" >> internal/db/testdata/prod-snapshot.sql.tmp
@echo "==> stripping pg16 \\restrict / \\unrestrict commands for pg15 compat..."
@sed -i.bak '/^\\restrict /d; /^\\unrestrict /d' internal/db/testdata/prod-snapshot.sql.tmp
@rm -f internal/db/testdata/prod-snapshot.sql.tmp.bak
@echo "==> stripping applied_migrations rows beyond branch's max on-disk version..."
@MAX_VER=$$(ls internal/db/migrations/*.up.sql | xargs -I{} basename {} | sed 's/_.*//' | sort -n | tail -1); \
awk -v max=$$MAX_VER ' \
/^[0-9]+\t/ { split($$0, a, "\t"); if (a[1]+0 > max) next; } \
{ print } \
' internal/db/testdata/prod-snapshot.sql.tmp > internal/db/testdata/prod-snapshot.sql
@rm internal/db/testdata/prod-snapshot.sql.tmp
@wc -l internal/db/testdata/prod-snapshot.sql