feat(api): populate claim_url on every 201 anon provision response (DOG-21) #173
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; } |