Skip to content

feat(api): populate claim_url on every 201 anon provision response (DOG-21) #173

feat(api): populate claim_url on every 201 anon provision response (DOG-21)

feat(api): populate claim_url on every 201 anon provision response (DOG-21) #173

Workflow file for this run

name: coverage
on:
pull_request:
branches: [master, main]
push:
branches: [master, main]
permissions:
contents: read
jobs:
coverage:
runs-on: ubuntu-latest
timeout-minutes: 15
# Service containers mirror ci.yml's build-and-test job. Without these
# `go test ./...` failed with `dial tcp [::1]:5432: connect: connection
# refused` and coverage measured only the handful of pure-unit packages
# that don't touch a DB. See CLAUDE.md rule 23 (the local gate must
# equal CI) — coverage.yml must run the same hermetic suite ci.yml does.
services:
postgres:
# pgvector/pgvector:pg16 is the stock postgres:16 image with the
# `vector` extension preinstalled. The /vector/new handler's local
# provider runs `CREATE EXTENSION vector` inside the freshly-created
# customer DB; on a plain postgres:16 image that errors and every
# vector-handler test SKIPs (vector.go measured ~44% under CI). Using
# the pgvector image lets the full /vector/new provisioning path run
# so vector.go contributes real coverage. It is a drop-in superset of
# postgres:16 — every other test behaves identically.
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: instant_dev_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
mongo:
image: mongo:6
ports:
- 27017:27017
options: >-
--health-cmd "mongosh --quiet --eval 'db.adminCommand({ ping: 1 }).ok' | grep -q 1"
--health-interval 10s
--health-timeout 5s
--health-retries 5
nats:
# The /queue/new handler's local provider verifies NATS is reachable
# via the monitoring /healthz endpoint (port 8222) BEFORE returning a
# connection URL (synchronous-provisioning principle). Without a NATS
# service that 8222 health check fails → every /queue/new test 503s and
# the queue handler's provision + per-tenant-credential arms never run.
# `-js -m 8222` starts JetStream and binds the HTTP monitoring port the
# provider probes. No healthcheck option here because the stock nats
# image has no curl/wget/nc; the provider's own health probe is the
# readiness gate, and the run step waits on it implicitly via the test
# retries. (worker/provisioner added nats the same way.)
image: nats:latest
ports:
- 4222:4222
- 8222:8222
options: --name cov-ci-nats
# NATS server args. GHA passes service "options" to `docker create`;
# the image entrypoint args go via the `image` command — but GHA
# service containers don't accept a command override, so JetStream +
# monitoring are enabled through the env the nats image honours.
env:
# nats:latest reads these to enable JetStream + the 8222 monitor
# without a command override (which GHA service containers disallow).
NATS_EXTRA_ARGS: "-js -m 8222"
env:
TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/instant_dev_test?sslmode=disable
TEST_REDIS_URL: redis://localhost:6379/15
# db-provider admin target — internal/providers/db/local.go CREATEs a
# customer database per /db/new and connects here. Without this DB,
# every postgres-provisioning test 503'd. Mirrors ci.yml.
TEST_POSTGRES_CUSTOMERS_URL: postgres://postgres:postgres@localhost:5432/instant_customers?sslmode=disable
# Mongo provider tests skip cleanly when unset; wire it so the
# nosql package contributes to coverage too.
TEST_MONGO_URI: mongodb://localhost:27017
steps:
- uses: actions/checkout@v6
with:
path: api
# Full history so diff-cover can resolve origin/<base_ref> for the
# patch-coverage gate below (shallow clones lack the base commit).
fetch-depth: 0
- name: Checkout proto sibling (for go.mod replace ../proto)
uses: actions/checkout@v6
with:
repository: ${{ vars.PROTO_REPO || format('{0}/proto', github.repository_owner) }}
token: ${{ secrets.REPO_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}
path: proto
- name: Checkout common sibling (for go.mod replace ../common)
uses: actions/checkout@v6
with:
repository: ${{ vars.COMMON_REPO || format('{0}/common', github.repository_owner) }}
token: ${{ secrets.REPO_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}
path: common
- uses: actions/setup-go@v6
with:
go-version-file: api/go.mod
- name: Apply DB migrations to the test database
# Mirrors ci.yml — applies the REAL migration files (not the
# hand-maintained testhelpers.runMigrations mirror) and creates
# the instant_customers DB the db-provider admin target points at.
env:
PGPASSWORD: postgres
working-directory: api
run: |
for f in $(ls internal/db/migrations/*.sql | sort); do
echo "→ applying $(basename "$f")"
psql -h localhost -U postgres -d instant_dev_test -f "$f" >/dev/null
done
echo "all migrations applied to instant_dev_test"
psql -h localhost -U postgres -d postgres -c "CREATE DATABASE instant_customers" >/dev/null
echo "created instant_customers (db-provider admin target)"
- name: Generate coverage
working-directory: api
# `-p 1` matches ci.yml — every package shares the single
# instant_dev_test DB + redis/15; default parallelism corrupts
# shared state mid-test. `-short` matches deploy.yml's hermetic
# suite (e2e-tagged tests are excluded from ./... anyway).
# continue-on-error so a single flaky test doesn't drop the
# entire coverage artifact — codecov still ingests cov.out.
continue-on-error: true
run: go test ./... -short -count=1 -p 1 -coverprofile=coverage.out -covermode=atomic
- uses: codecov/codecov-action@v6
with:
files: api/coverage.out
flags: api
fail_ci_if_error: false
# ------------------------------------------------------------------
# Org patch-coverage mandate: every changed line in a PR diff must be
# covered by a test (100%), and the project floor stays >=95%.
# Tool: diff-cover (https://github.com/Bachmann1234/diff-cover) reads a
# Cobertura report + the git diff vs the base branch. The "Generate
# coverage" step above is continue-on-error, so it still produces
# coverage.out even if a flaky test trips — the gate reads that file.
# ------------------------------------------------------------------
- uses: actions/setup-python@v6
if: github.event_name == 'pull_request'
with:
python-version: '3.12'
- name: Install diff-cover + cobertura converter
if: github.event_name == 'pull_request'
run: |
pip install diff-cover
go install github.com/boumenot/gocover-cobertura@latest
- name: Convert coverage to Cobertura
if: github.event_name == 'pull_request'
working-directory: api
run: $(go env GOPATH)/bin/gocover-cobertura < coverage.out > coverage.xml
- name: Patch coverage gate (100% of changed lines)
if: github.event_name == 'pull_request'
working-directory: api
run: |
git fetch origin "${{ github.base_ref }}" --depth=1 || true
diff-cover coverage.xml \
--compare-branch="origin/${{ github.base_ref }}" \
--fail-under=100
- name: Project coverage floor (>=95% production code)
if: github.event_name == 'pull_request'
working-directory: api
# The >=95% floor is measured over PRODUCTION code only. We drop
# genuinely-non-shippable packages from the coverage profile before
# computing the total — this is correct measurement, NOT a waiver.
# No internal/<domain> production package is ever excluded here.
#
# Excluded (and why):
# internal/testhelpers — test-DB/setup harness, imported only by
# tests; never runs in prod (sits ~5%).
# cmd/smoke-buildinfo — diagnostic/smoke binary, not shipped logic.
# cmd/* — pure diagnostic/smoke binaries.
# e2e/ — black-box E2E suite (//go:build e2e).
# proto/gen, *_pb.go — generated protobuf code.
# Build-tag-gated files (//go:build e2e|integration|chaos|loadtest)
# are not compiled into the `-short` run, so they never appear in
# coverage.out — the path filter below is belt-and-suspenders.
run: |
# Keep the `mode:` header line; drop excluded package paths.
grep -vE '(/internal/testhelpers/|/cmd/|/e2e/|/proto/gen/|_pb\.go:)' \
coverage.out > coverage.prod.out
total=$(go tool cover -func=coverage.prod.out | tail -1 | awk '{print $3}' | tr -d '%')
echo "Total project coverage: ${total}%"
awk -v t="$total" 'BEGIN { exit (t+0 >= 95) ? 0 : 1 }' \
|| { echo "::error::Production coverage ${total}% is below the 95% floor"; exit 1; }