Skip to content

Commit 1ff5c1d

Browse files
committed
Stabilize DB-backed test lanes
1 parent 973ecf8 commit 1ff5c1d

16 files changed

Lines changed: 1467 additions & 212 deletions

.github/workflows/ci.yml

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,29 @@ on:
1919
description: "Run Go race detector tests"
2020
type: boolean
2121
default: false
22+
run_db_tests:
23+
description: "Run DB-backed test lanes with a Postgres service"
24+
type: boolean
25+
default: false
26+
db_test_lane:
27+
description: "DB lane to run when run_db_tests is true"
28+
type: choice
29+
default: stable
30+
options:
31+
- stable
32+
- routine
33+
- ui
34+
- energy
35+
- readiness
36+
- storage
2237

2338
env:
2439
REGISTRY: ghcr.io
2540
IMAGE_NAME: ${{ github.repository }}
2641

42+
permissions:
43+
contents: read
44+
2745
jobs:
2846
build:
2947
name: Build, Vet & Test
@@ -42,18 +60,70 @@ jobs:
4260
- name: Vet
4361
run: CGO_ENABLED=0 go vet ./...
4462

45-
# Unit tests. All current test files are pure (no DATABASE_URL,
46-
# no network) — verified by grepping *_test.go for DATABASE_URL,
47-
# pgxpool, net.Dial → zero matches. If a future test grows
48-
# external deps, gate it behind a build tag rather than wiring
49-
# services into this job; the build job's purpose is catching
50-
# logic regressions in seconds, not standing up a stack.
63+
# Unit tests. DB-backed integration tests are gated by
64+
# HEALTH_DB_TESTS=1 and skip in this job even if libpq environment
65+
# variables happen to exist. The separate DB Tests job runs the
66+
# stable DB lanes with an isolated Postgres service on PR/push.
5167
#
5268
# CGO_ENABLED stays at 0 (matches the build above). Use the
5369
# manual Race Detector job when reviewing concurrency-sensitive
5470
# changes; it runs with CGO=1 because Go's -race requires CGO.
5571
- name: Test
56-
run: CGO_ENABLED=0 go test ./...
72+
run: make test-unit
73+
74+
db-tests:
75+
name: DB Tests
76+
runs-on: ubuntu-latest
77+
if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_db_tests }}
78+
services:
79+
postgres:
80+
image: postgres:16
81+
env:
82+
POSTGRES_DB: health_test
83+
POSTGRES_USER: postgres
84+
POSTGRES_PASSWORD: postgres
85+
ports:
86+
- 5432:5432
87+
options: >-
88+
--health-cmd "pg_isready -U postgres -d health_test"
89+
--health-interval 5s
90+
--health-timeout 5s
91+
--health-retries 12
92+
env:
93+
HEALTH_DB_TESTS: "1"
94+
PGHOST: localhost
95+
PGPORT: "5432"
96+
PGDATABASE: health_test
97+
PGUSER: postgres
98+
PGPASSWORD: postgres
99+
GOCACHE: /tmp/health-go-build-cache
100+
steps:
101+
- uses: actions/checkout@v5
102+
103+
- uses: actions/setup-go@v6
104+
with:
105+
go-version-file: go.mod
106+
cache: true
107+
108+
- name: Run routine DB lane
109+
if: ${{ github.event_name != 'workflow_dispatch' || inputs.db_test_lane == 'stable' || inputs.db_test_lane == 'routine' }}
110+
run: make test-db
111+
112+
- name: Run full UI DB lane
113+
if: ${{ github.event_name != 'workflow_dispatch' || inputs.db_test_lane == 'stable' || inputs.db_test_lane == 'ui' }}
114+
run: make test-db-ui
115+
116+
- name: Run full Energy DB lane
117+
if: ${{ github.event_name != 'workflow_dispatch' || inputs.db_test_lane == 'stable' || inputs.db_test_lane == 'energy' }}
118+
run: make test-db-energy
119+
120+
- name: Run Readiness DB lane
121+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.db_test_lane == 'readiness' }}
122+
run: make test-db-readiness
123+
124+
- name: Run full Storage DB lane
125+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.db_test_lane == 'storage' }}
126+
run: make test-db-storage
57127

58128
race:
59129
name: Race Detector
@@ -78,7 +148,7 @@ jobs:
78148
name: Docker Build & Push (amd64)
79149
needs: build
80150
runs-on: ubuntu-latest
81-
if: ${{ github.ref == 'refs/heads/main' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.build_amd64 && !inputs.run_race)) }}
151+
if: ${{ github.ref == 'refs/heads/main' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.build_amd64 && !inputs.run_race && !inputs.run_db_tests)) }}
82152
permissions:
83153
contents: read
84154
packages: write
@@ -119,7 +189,7 @@ jobs:
119189
name: Docker Build & Push (arm64)
120190
needs: build
121191
runs-on: ubuntu-latest
122-
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && inputs.build_arm64 && !inputs.run_race }}
192+
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && inputs.build_arm64 && !inputs.run_race && !inputs.run_db_tests }}
123193
permissions:
124194
contents: read
125195
packages: write

CONTRIBUTING.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,52 @@ Use Go 1.24+ and PostgreSQL.
1717

1818
```bash
1919
go mod tidy
20-
go test ./...
20+
make test-unit
2121
go vet ./...
2222
```
2323

2424
Dependencies are managed through normal Go module resolution. Review dependency changes in `go.mod` and `go.sum`; the repository does not commit a `vendor/` tree.
2525

26-
DB-backed integration tests skip when no Postgres connection is configured. To run them, provide libpq environment variables or `READINESS_TEST_DSN`. Tests create throwaway schemas and drop them during cleanup.
26+
DB-backed integration tests are explicit. They skip unless `HEALTH_DB_TESTS=1` is set, even when `PGHOST` or other libpq variables are present in your shell. To run them, provide libpq environment variables or `READINESS_TEST_DSN`, then use:
27+
28+
```bash
29+
HEALTH_DB_TESTS=1 make test-db-storage
30+
HEALTH_DB_TESTS=1 make test-db-ui
31+
HEALTH_DB_TESTS=1 make test-db-ui-fast
32+
HEALTH_DB_TESTS=1 make test-db-energy
33+
HEALTH_DB_TESTS=1 make test-db-energy-smoke
34+
HEALTH_DB_TESTS=1 make test-db-readiness
35+
HEALTH_DB_TESTS=1 make test-db
36+
```
37+
38+
`test-db` is the routine DB contract aggregate: fast UI DB smoke, EnergyBank DB smoke, and targeted Readiness DB checks. It intentionally does not run the full UI package, full EnergyBank verdict-band suite, or full `./internal/storage` package. Use the full domain lanes when a change touches that area.
39+
40+
Lane commands:
41+
42+
```bash
43+
make test-db # routine DB contract: ui-fast + energy-smoke + readiness
44+
make test-db-ui-fast # representative UI/admin DB handler checks
45+
make test-db-energy-smoke # one EnergyBank verdict-band DB contract check
46+
make test-db-readiness # readiness schema/calibration DB checks
47+
make test-db-ui # full internal/ui DB package sweep
48+
make test-db-energy # full EnergyBank verdict-band DB group
49+
make test-db-storage # full internal/storage DB package sweep
50+
```
51+
52+
When `HEALTH_DB_TESTS=1` is set, missing or unreachable Postgres is a test failure, not a skip. Tests create throwaway schemas with test prefixes and drop them during cleanup.
53+
Readiness monitoring and broad readiness writer families are intentionally outside the routine `test-db-readiness` lane until their fixtures are narrowed or runtime is measured separately. Full UI, full Energy, and full Storage lanes are manual/domain sweeps, not the normal edit-loop.
2754

2855
See `docs/TEST_COVERAGE.md` for the current focused coverage roadmap and fixture rules.
2956

3057
### CI and race detector policy
3158

3259
The default required-looking CI job is `Build, Vet & Test`. Keep that job name stable unless repository branch protection is updated at the same time.
3360

34-
Default PR and push CI uses `CGO_ENABLED=0` for build, vet, and tests, matching the production binary. Race detector tests are available as a manual GitHub Actions run: start the `CI` workflow with `run_race=true`. Race-only dispatches do not publish Docker images, even when the image build inputs are left at their defaults. Use this before merging changes that touch tenant management, scheduler goroutines, async AI generation, recompute coordination, Telegram webhook dispatch, or notification loops.
61+
Default PR and push CI uses `CGO_ENABLED=0` for build, vet, and unit tests, matching the production binary, and also runs the stable DB lanes against an isolated Postgres service container: routine `make test-db`, full UI DB, and full Energy DB. Full Storage DB is still manual because the current full storage package exceeds the 120s lane timeout in chronic-load writer tests.
62+
63+
Manual DB runs are available through the `CI` workflow: set `run_db_tests=true` and choose `stable`, `routine`, `ui`, `energy`, `readiness`, or `storage`.
64+
65+
Race detector tests are also manual: start the `CI` workflow with `run_race=true`. Race-only and DB-only dispatches do not publish Docker images, even when the image build inputs are left at their defaults. Use the race detector before merging changes that touch tenant management, scheduler goroutines, async AI generation, recompute coordination, Telegram webhook dispatch, or notification loops.
3566

3667
For local checks on a machine with a CGO toolchain:
3768

Makefile

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: dev build backfill backfill-force energy-backfill energy-backfill-dry import docker-up docker-down test
1+
.PHONY: dev build backfill backfill-force energy-backfill energy-backfill-dry import docker-up docker-down test test-unit test-db-storage test-db-ui test-db-ui-fast test-db-energy test-db-energy-smoke test-db-readiness test-db smoke-health-post
22

33
ADDR ?= :8080
44

@@ -40,7 +40,33 @@ docker-up:
4040
docker-down:
4141
docker compose down
4242

43+
test-unit:
44+
HEALTH_DB_TESTS= CGO_ENABLED=0 go test ./...
45+
46+
test-db-storage:
47+
HEALTH_DB_TESTS=1 go test ./internal/storage -count=1 -timeout=120s
48+
49+
test-db-ui:
50+
HEALTH_DB_TESTS=1 go test ./internal/ui -count=1 -timeout=120s
51+
52+
test-db-ui-fast:
53+
HEALTH_DB_TESTS=1 go test ./internal/ui -run "^(TestAdminCheckinCoverage_JSONShape|TestOnboardingWizard_RejectsSchemaAll|TestFragmentAdminReadinessMonitoring_Renders)$$" -count=1 -timeout=60s
54+
55+
test-db-energy:
56+
HEALTH_DB_TESTS=1 go test ./internal/storage -run "TestComputeUserVerdictBands" -count=1 -timeout=90s
57+
58+
test-db-energy-smoke:
59+
HEALTH_DB_TESTS=1 go test ./internal/storage -run "^TestComputeUserVerdictBands_UsesCompatibleFormulaWarmup$$" -count=1 -timeout=30s
60+
61+
test-db-readiness:
62+
HEALTH_DB_TESTS=1 go test ./internal/storage -run '^(TestSaveNaiveBaseline_NilValueWithValidReasonPersists|TestVerifyReadinessRedesignSchema_DetectsReasonColumnDrift|TestRecomputeChipCalibrations_Integration_(RejectsCrossEpochLabels|PercentileP80|InsufficientData))$$' -count=1 -timeout=90s
63+
64+
test-db: test-db-ui-fast test-db-energy-smoke test-db-readiness
65+
4366
test:
67+
$(MAKE) smoke-health-post
68+
69+
smoke-health-post:
4470
curl -s -X POST http://localhost$(ADDR)/health \
4571
-H "Content-Type: application/json" \
4672
-H "automation-name: Test" \

docs/TEST_COVERAGE.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,24 @@ This roadmap tracks high-value Health Dashboard test coverage without turning th
44

55
## Constraints
66

7-
- Default CI must stay pure and fast under `go test ./...`.
7+
- Default CI must stay pure and fast under `make test-unit` / `go test ./...`.
88
- Fixtures must be synthetic or anonymized. Do not commit personal health exports, screenshots, logs, or raw metric dumps.
9-
- DB-backed tests should skip cleanly unless a Postgres connection is configured.
9+
- DB-backed tests should skip cleanly unless `HEALTH_DB_TESTS=1` is set. When that flag is set, missing or unreachable Postgres is a failure.
1010
- Coverage thresholds are intentionally deferred until the active product contracts have a useful baseline.
1111

12+
## Test Lanes
13+
14+
| Lane | Command | Contract |
15+
|---|---|---|
16+
| Unit/default | `make test-unit` | Fast pure suite. DB tests skip even if libpq env vars are present. |
17+
| Routine DB | `HEALTH_DB_TESTS=1 make test-db` | Fast sequential DB contract aggregate: UI smoke, Energy smoke, and targeted Readiness DB checks. This is the normal DB edit-loop. |
18+
| UI DB smoke | `HEALTH_DB_TESTS=1 make test-db-ui-fast` | Representative UI/admin DB handler checks. |
19+
| Energy DB smoke | `HEALTH_DB_TESTS=1 make test-db-energy-smoke` | One EnergyBank verdict-band DB contract check. |
20+
| Readiness DB | `HEALTH_DB_TESTS=1 make test-db-readiness` | Runs an explicit readiness storage regex covering baseline persistence/schema drift and chip calibration recompute. Monitoring and broad readiness-adjacent writer families stay out until measured or migrated. |
21+
| UI DB full | `HEALTH_DB_TESTS=1 make test-db-ui` | Full `./internal/ui` DB package sweep. Manual/domain verification; currently around 41s on the remote Postgres path. |
22+
| Energy DB full | `HEALTH_DB_TESTS=1 make test-db-energy` | Full EnergyBank verdict-band DB group: `go test ./internal/storage -run "TestComputeUserVerdictBands" -count=1 -timeout=90s`. Domain verification; currently around 22s after fixture narrowing and batched seed inserts. |
23+
| Full Storage DB | `HEALTH_DB_TESTS=1 make test-db-storage` | Full `./internal/storage` DB package run. Manual/pre-merge only; not part of `make test-db`. |
24+
1225
## Current Coverage Map
1326

1427
| Area | Current state | Next useful coverage |
@@ -18,7 +31,7 @@ This roadmap tracks high-value Health Dashboard test coverage without turning th
1831
| UI and admin APIs | Contract tests cover admin pages, auth/session behavior, webhook dispatch, dashboard sections, and tenant scope. | Pin response-shape changes before frontend code starts depending on them. |
1932
| Storage writers | Readiness redesign, EnergyBank, sleep gates, freshness, calibration, and tenant helpers have focused tests, with DB-backed tests gated. | Prefer bug-driven storage contract tests over broad repository-level sweeps. |
2033
| Notifications | Morning/evening report, freshness banners, smart retry, Telegram webhook, and proactive framework tests cover key behavior. | Add regression tests when notification timing or skip conditions change. |
21-
| CI policy | Default CI runs build, vet, and pure tests; race detector is manual and documented. | Keep race detector separate from default PR checks unless the runtime cost becomes acceptable. |
34+
| CI policy | Default CI runs build, vet, pure tests, routine DB, full UI DB, and full Energy DB against an isolated Postgres service. Full Storage DB and race detector remain manual. | Move full storage into default CI only after the chronic/acute writer families fit a bounded timeout. |
2235

2336
## Delivered First Batch
2437

0 commit comments

Comments
 (0)