Skip to content

Commit 69ad7f2

Browse files
test(provisioner): real-backend gRPC round-trips for Mongo/Queue/Storage (Wave 4) (#47)
* test(provisioner): real-backend gRPC round-trips for Mongo/Queue/Storage (Wave 4) Closes the remaining cells of the provisioner gRPC × backend matrix (INTEGRATION-COVERAGE-PLAN §2.3 / Wave 4). Postgres + Redis server-layer round-trips already shipped (#45, #46); this adds the real-backend Provision → assert artifact → Deprovision → assert-gone lifecycle for the three remaining backends, all driven through the genuine gRPC handlers (breaker wrapping, tier routing, mapError, response shaping): - Mongo: ProvisionResource creates usr_/db_ on a real MongoDB, GetStorageBytes reads real dbStats (>0 after seeding), DeprovisionResource runs the real dropUser/dropDatabase (truehomie DROP-incident class), second Deprovision is a clean idempotent no-op, and Regrade(mongo) asserts the documented skip path. - Queue (NATS): ProvisionResource passes the real NATS monitor health check and returns nats:// URL + subject prefix, GetStorageBytes(queue)=0 (message-metered), Deprovision is the shared-backend no-op, idempotent. - Storage (MinIO/S3): GetStorageBytes object-walk — empty prefix=0, after a real PutObject=exact byte count, after delete=0. (Storage Provision/Deprovision are API-side; provisioner only meters.) All tests env-gated (skip cleanly under `go test -short`, the deploy gate; run for real when the backend env is present). CI: added NATS service container + MinIO docker-run step + the TEST_NATS_HOST / TEST_MINIO_* / CUSTOMER_MONGO_AUTH_URL env wiring to coverage.yml so they execute (mongo was already provided). Verified locally against real mongo/nats/minio containers: all 8 server round-trip tests PASS; integration-only coverage for internal/server = 99.2% (provisionMongo/provisionQueue/GetStorageBytes/DeprovisionResource/RegradeResource all 100%). No bug found in the destroy/regrade paths. Added INTEGRATION-COVERAGE-EXCLUSIONS.md documenting the ≥80 floor method + the only genuinely-unreachable lines (k8s dedicated-backend boot wiring, cmd/ entrypoints). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(provisioner): start NATS via docker run, not a services container The minimal nats:2 image has no wget/curl/nc, so the service-container --health-cmd ('wget ... :8222/healthz') could never pass — GitHub Actions marked the container unhealthy and aborted the coverage job before any test ran (NATS logged 'Server is ready'). Mirror the MinIO pattern: docker run nats:2 -js -m 8222 + a runner-side curl wait on /healthz. Unblocks #47. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Manas Srivastava <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bb1935b commit 69ad7f2

3 files changed

Lines changed: 635 additions & 0 deletions

File tree

.github/workflows/coverage.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,22 @@ jobs:
101101
--health-timeout 5s
102102
--health-retries 10
103103
104+
# NATS (JetStream + HTTP monitor :8222) for the server-layer queue
105+
# round-trip (internal/server/server_live_roundtrip_mqs_test.go::
106+
# TestServer_Queue_*) is started as a `docker run` step below (see
107+
# "Start NATS for queue round-trip"), NOT a `services:` container: the
108+
# minimal nats:2 image has no wget/curl/nc, so a service-container
109+
# `--health-cmd` can NEVER pass — GitHub Actions then marks the container
110+
# unhealthy and aborts the whole job even though the server logged
111+
# "Server is ready". docker-run + a runner-side curl wait avoids that.
112+
113+
# NOTE: MinIO is NOT a `services:` container — GitHub Actions service
114+
# containers cannot supply the required `server /data` command (services
115+
# accept image/env/ports/options only, not args). MinIO is started as a
116+
# `docker run` step below (see "Start MinIO for storage round-trip"), which
117+
# the server-layer storage test (TestServer_Storage_GetStorageBytes_*)
118+
# reaches at 127.0.0.1:9100.
119+
104120
# TEST_* env vars the pool/backend test helpers read via os.Getenv. The
105121
# exact names were grepped from the test files (os.Getenv("TEST_…") and the
106122
# liveXxx() helper fallbacks) — wrong names = silent skip = lost coverage.
@@ -120,7 +136,19 @@ jobs:
120136
# internal/backend/mongo/local_test.go::liveMongoURI
121137
CUSTOMER_MONGO_URL: mongodb://127.0.0.1:27017
122138
# internal/backend/mongo/coverage_extra_test.go auth-fail branches
139+
# ALSO: internal/server/server_live_roundtrip_mqs_test.go::liveMongoAdminURI
140+
# (the gRPC-layer mongo Provision/StorageBytes/Deprovision round-trip uses
141+
# the authenticated instance — the realistic prod createUser path).
123142
CUSTOMER_MONGO_AUTH_URL: mongodb://root:rootpass@127.0.0.1:27018
143+
# internal/server/server_live_roundtrip_mqs_test.go::liveNATSHost — the
144+
# queue LocalBackend health-checks http://$NATS_HOST:8222/healthz.
145+
TEST_NATS_HOST: 127.0.0.1
146+
# internal/server/server_live_roundtrip_mqs_test.go::liveStorageEndpoint —
147+
# the storage GetStorageBytes round-trip against the MinIO started below.
148+
TEST_MINIO_ENDPOINT: 127.0.0.1:9100
149+
TEST_MINIO_ROOT_USER: minioadmin
150+
TEST_MINIO_ROOT_PASSWORD: minioadmin
151+
TEST_MINIO_BUCKET: itest-bucket
124152

125153
steps:
126154
- uses: actions/checkout@v6
@@ -146,6 +174,46 @@ jobs:
146174
# ≠ verified). A red test must fail the job; codecov upload below is
147175
# still soft-failed via fail_ci_if_error: false so a missing token
148176
# doesn't take CI down.
177+
# MinIO can't run as a `services:` container (no way to pass `server /data`).
178+
# Start it here so TestServer_Storage_GetStorageBytes_LiveRoundTrip has a
179+
# real S3-compatible endpoint at 127.0.0.1:9100. Env (MINIO_ROOT_USER/
180+
# PASSWORD) are static literals — no untrusted workflow input is interpolated.
181+
- name: Start MinIO for storage round-trip
182+
run: |
183+
docker run -d --name itest-minio \
184+
-p 9100:9000 \
185+
-e MINIO_ROOT_USER=minioadmin \
186+
-e MINIO_ROOT_PASSWORD=minioadmin \
187+
minio/minio:latest server /data
188+
# Wait for MinIO to report healthy before the test run.
189+
for i in $(seq 1 30); do
190+
if curl -fsS http://127.0.0.1:9100/minio/health/live >/dev/null 2>&1; then
191+
echo "minio healthy after ${i} tries"; break
192+
fi
193+
sleep 1
194+
done
195+
curl -fsS http://127.0.0.1:9100/minio/health/live >/dev/null
196+
197+
# NATS can't be a `services:` container (minimal nats:2 image has no
198+
# wget/curl for a passing --health-cmd). Start it here with explicit
199+
# -js (JetStream) + -m 8222 (HTTP monitor) and wait on /healthz from the
200+
# runner, which DOES have curl. Reached by the queue round-trip at
201+
# 127.0.0.1:4222 / :8222 (TEST_NATS_HOST=127.0.0.1).
202+
- name: Start NATS for queue round-trip
203+
run: |
204+
docker run -d --name itest-nats \
205+
-p 4222:4222 \
206+
-p 8222:8222 \
207+
nats:2 -js -m 8222
208+
# Wait for the NATS HTTP monitor to report healthy before the tests.
209+
for i in $(seq 1 30); do
210+
if curl -fsS http://127.0.0.1:8222/healthz >/dev/null 2>&1; then
211+
echo "nats healthy after ${i} tries"; break
212+
fi
213+
sleep 1
214+
done
215+
curl -fsS http://127.0.0.1:8222/healthz >/dev/null
216+
149217
- name: Generate coverage
150218
working-directory: provisioner
151219
# No `-short`: the integration tests are gated on TEST_* env-var

INTEGRATION-COVERAGE-EXCLUSIONS.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Provisioner — Integration-Coverage Exclusions
2+
3+
> Companion to the standing integration-coverage program
4+
> (`docs/sessions/2026-06-04/INTEGRATION-COVERAGE-PLAN-2026-06-04.md`, Wave 4).
5+
>
6+
> The provisioner's forum-defined done-bar is a **≥80% line-coverage floor measured
7+
> integration-only** (real backends, not mocks) AND flow-completeness (every gRPC
8+
> Provision / Deprovision / Status / Regrade flow has a real-backend round-trip).
9+
>
10+
> The ≥80 floor is computed **after subtracting the lines listed here** — each is
11+
> genuinely unreachable from an integration test (process bootstrap / k8s
12+
> control-plane wiring / fatal-exit) and would otherwise force a fake just to
13+
> tick a line. Every entry has a one-line justification. Keep this list *small*
14+
> it is exclusions, not waivers; a flow that can be driven against a real backend
15+
> must be, not listed here.
16+
17+
## How integration-only coverage is measured (mechanism C, per the PLAN)
18+
19+
```bash
20+
# from provisioner/, with real backends reachable (CI coverage.yml supplies them):
21+
# Postgres, Redis, Mongo (no-auth + auth), NATS (monitor :8222), MinIO.
22+
export CUSTOMER_MONGO_AUTH_URL=mongodb://root:rootpass@127.0.0.1:27018
23+
export TEST_NATS_HOST=127.0.0.1
24+
export TEST_MINIO_ENDPOINT=127.0.0.1:9100
25+
export TEST_MINIO_ROOT_USER=minioadmin TEST_MINIO_ROOT_PASSWORD=minioadmin TEST_MINIO_BUCKET=itest-bucket
26+
export TEST_POSTGRES_CUSTOMERS_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable
27+
export CUSTOMER_REDIS_URL=redis://127.0.0.1:6379
28+
29+
go test ./internal/server/ -coverpkg=./internal/server/... \
30+
-coverprofile=/tmp/srvcov.out -count=1 -timeout 300s
31+
go tool cover -func=/tmp/srvcov.out | tail -1
32+
```
33+
34+
Measured for `internal/server` (the gRPC handler layer — the package this Wave-4
35+
PR touched), all backends wired: **99.2%** of statements — well above the 80
36+
floor before any subtraction.
37+
38+
## Excluded lines (genuinely unreachable from an integration test)
39+
40+
| Location | Symbol | Why it cannot be integration-covered |
41+
|---|---|---|
42+
| `internal/server/server.go` `New()` — the `cfg.K8sEnabled` block | dedicated-backend init (`postgres/redis/mongo/queue.NewK8sDedicatedBackend`) | Requires a live kube-apiserver + kubeconfig; the dedicated (Pro/Team) k8s backends are exercised by the per-backend `k8s_live_test.go` suites against a real cluster (nightly e2e), not by the in-process gRPC server round-trip. The error-log fallback branches fire only on a malformed kubeconfig at process boot. |
43+
| `internal/server/server.go` `NewWithBackends()``poolMgr != nil` typed-nil normalization | constructor guard | The branch that converts a typed-nil `*pool.Manager` to a nil `PoolClaimer` interface is a boot-time correctness guard; it is covered by the unit constructor test, but the "real pool manager passed" arm needs a live `*pgxpool.Pool` to the provisioner DB and is exercised by `internal/pool/manager_db_test.go`, not the server round-trip. |
44+
| `cmd/` (all) | `main`, signal wiring, `os.Exit` | Process entrypoint / fatal-exit paths — not reachable without forking the binary. Excluded from the measured package set (PLAN §1.4). |
45+
46+
Everything else in the gRPC handler layer — every `ProvisionResource`,
47+
`DeprovisionResource`, `GetStorageBytes`, `RegradeResource` arm across postgres,
48+
redis, mongo, queue, and storage — IS driven by a real-backend round-trip
49+
(`server_live_roundtrip_test.go` + `server_live_roundtrip_mqs_test.go`) and sits
50+
at 100% function coverage.

0 commit comments

Comments
 (0)