diff --git a/.planning/phases/02-api-parity-harness-bootstrap/02-04-SUMMARY.md b/.planning/phases/02-api-parity-harness-bootstrap/02-04-SUMMARY.md new file mode 100644 index 0000000..9743f92 --- /dev/null +++ b/.planning/phases/02-api-parity-harness-bootstrap/02-04-SUMMARY.md @@ -0,0 +1,156 @@ +--- +phase: 02-api-parity-harness-bootstrap +plan: 04 +subsystem: testing +tags: [parity, testcontainers, postgres, httptest, seed-hook, coverage] + +requires: + - phase: 02-api-parity-harness-bootstrap + provides: Isolated PHP on 127.0.0.1:8423 with a parity-named SQLite DB + - phase: 02-api-parity-harness-bootstrap + provides: Recorded API seed plus 154 live PHP route fixtures + - phase: 02-api-parity-harness-bootstrap + provides: tide record/replay/proxy, capture rules, named vars, manifest --next-batch, summer parity:* +provides: + - App-owned testcontainers Postgres lifecycle for go test ./parity + - Synthetic SQL-backed httptest handler with POST/GET /synthetic/items + - newTarget and seedHooks seam for Phase 3 genres without claiming PHP routes + - TestParityCorpus: 154 recorded/pending selectable subtests, zero Go passes +affects: [02-05, 03] + +tech-stack: + added: + - github.com/testcontainers/testcontainers-go v0.44.0 (app tests only) + - github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0 + - github.com/jackc/pgx/v5 v5.10.0 (stdlib driver) + patterns: + - One Postgres container per go test ./parity suite; t.Cleanup/Terminate; -short skips, missing Docker fails + - YAML names a seed hook; only registered Go functions run parameterized SQL + - Pending PHP routes are recorded and selectable but never sent to the synthetic handler + +key-files: + created: + - ../fonoteka.go/parity/parity_test.go + - ../fonoteka.go/parity/synthetic_test.go + - ../fonoteka.go/parity/testdata/synthetic-seed.yaml + - ../fonoteka.go/parity/testdata/synthetic-read.yaml + modified: + - ../fonoteka.go/go.mod + - ../fonoteka.go/go.sum + - ../fonoteka.go/parity/README.md + +key-decisions: + - "newTarget and seedHooks live in the app test package so Phase 3 can swap the synthetic handler for the real app and add a temporary genres SQL hook until POST genres is ported" + - "Pending never equals passing: TestParityCorpus reports recorded 154/154 passing 0 pending 154 and does not replay PHP fixtures against the synthetic handler" + - "Unavailable Docker fails TestMain; testing.Short skips the container so the fast loop stays fast" + +patterns-established: + - "Pattern: invokeSeedHook rejects unknown names; YAML never carries SQL text" + - "Pattern: corpus subtest names sanitize route ids (slash/space to _) so -run selects one flow" + - "Pattern: a deliberately ported PHP fixture against the synthetic handler must fail with a path diff (status 200 vs 404)" + +requirements-completed: [QA-02, QA-03] + +duration: 9min +completed: 2026-09-17 +--- + +# Phase 2 Plan 4: Honest Go replay with testcontainers Postgres Summary + +**App `go test ./parity` starts testcontainers Postgres, replays a SQL-backed synthetic write/read through tide, and lists all 154 PHP routes as recorded/pending with zero Go passes** + +## Performance + +- **Duration:** 9 min +- **Started:** 2026-09-17T11:49:42Z +- **Completed:** 2026-09-17T11:58:36Z +- **Tasks:** 2 +- **Files modified:** 7 (app repo) plus this SUMMARY + +## Accomplishments + +- Testcontainers Postgres (postgres:16-alpine) plus `pgx` `database/sql` backs a tiny `synthetic_items` table for the integration suite. +- `TestParitySynthetic` replays `testdata/synthetic-seed.yaml` (POST) and `synthetic-read.yaml` (GET + `synthetic-item` SQL hook); unknown hooks fail; a mutated row fails at `$[0].name`. +- `newTarget` / `seedHooks` is the Phase 3 seam: swap in the real app handler, mark GET genres `ported`, keep a temporary `genres` hook until POST genres lands. +- `TestParityCorpus` validates all 154 manifest fixtures as selectable pending subtests and never counts them as a Go pass. A cloned ported locale fixture against the synthetic handler fails with `status` 200 vs 404. + +## Task Commits + +Each task was committed atomically (app repo `fonoteka.go`): + +1. **Task 1: Replay a synthetic write/read flow against Postgres** - `e0ab93c` (feat) +2. **Task 2: Expose the full corpus as honest selectable subtests** - `ecec761` (feat) + +**Plan metadata:** this SUMMARY commit + +## Files Created/Modified + +- `../fonoteka.go/go.mod` / `go.sum` — test-only testcontainers, postgres module, pgx v5.10.0; local replace to summercms +- `../fonoteka.go/parity/parity_test.go` — `newTarget`, `seedHooks`, `TestParityCorpus` +- `../fonoteka.go/parity/synthetic_test.go` — Postgres lifecycle, synthetic handler, `TestParitySynthetic` +- `../fonoteka.go/parity/testdata/synthetic-seed.yaml` — HTTP write fixture with captured id +- `../fonoteka.go/parity/testdata/synthetic-read.yaml` — list fixture with `seed_hook: synthetic-item` +- `../fonoteka.go/parity/README.md` — in-process `go test ./parity` commands and Phase 3 hook notes + +## Decisions Made + +- Keep testcontainers/pgx out of framework production code (QA-03). +- Fail the package (not skip) when Docker cannot start Postgres; `-short` is the only green skip. +- Coverage totals come from the manifest so `-run TestParityCorpus/` does not require sibling subtests to have incremented counters. +- Phase 2 does not register a `genres` hook yet — only documents the name and the POST route that will replace it. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] slopcheck SLOP/SUS on local replace and transitives** +- **Found during:** Task 1 (dependency scan after `go get`) +- **Issue:** slopcheck marked `git.golem15.com/golem15/summercms` as SLOP (unpublished replace) and several testcontainers transitives as SLOP/SUS. Direct requires are the RESEARCH.md VERIFIED set (testcontainers postgres module, pgx/stdlib). +- **Fix:** Kept Go module resolution versions; did not add undeclared direct imports. Transitives stay indirect. +- **Files modified:** `../fonoteka.go/go.mod`, `../fonoteka.go/go.sum` +- **Verification:** `slopcheck scan` after tidy; `git diff -- go.mod go.sum` inspected before commit +- **Committed in:** `e0ab93c` + +**2. [Rule 1 - Bug] parent coverage asserts broke `-run` of one corpus flow** +- **Found during:** Task 2 (`go test -run TestParityCorpus/GET__...`) +- **Issue:** Parent `TestParityCorpus` asserted 154 pending after the loop, so selecting one subtest failed with `pending 0`. +- **Fix:** Moved asserts into `t.Run("coverage")` and compute totals from the manifest, not from filtered subtest side effects. Sanitized names use three underscores for `/_fonoteka` paths. +- **Files modified:** `../fonoteka.go/parity/parity_test.go`, `../fonoteka.go/parity/README.md` +- **Verification:** `-run TestParityCorpus/GET___fonoteka_api_v1_me_locale_jwt_locale` passes; coverage prints `recorded 154/154 passing 0 failing 0 unrecorded 0 pending 154` +- **Committed in:** `ecec761` + +--- + +**Total deviations:** 2 auto-fixed (1 dependency-scan, 1 -run isolation) +**Impact on plan:** Required for honest CI selection and a legitimate local module replace. No PHP/Nuxt/MCP source changes. No framework production deps. + +## Issues Encountered + +- TDD RED was observed without a RED commit (`TDD_MODE=false`): stub `NotFoundHandler` failed capture at `$.id` (`create-item`), then the SQL-backed handler made the suite green in the Task 1 commit. +- slopcheck age heuristics fire on well-known transitives (`Azure/go-ansiterm`, `magiconair/properties`, `power-devops/perfstat`); treat RESEARCH.md Package Legitimacy Audit as the gate for direct requires. + +## User Setup Required + +None - no external service configuration required. Local Docker is required for `go test ./parity` without `-short`. + +## Next Phase Readiness + +Ready for `02-05` (contract, security and integration tests). Phase 3 can replace `newTarget` with the real app handler, mark GET genres `ported`, and register a temporary `genres` seed hook until POST genres is ported. + +## Verification + +- Task 1: `go test ./parity -run TestParitySynthetic` starts Postgres and passes write/read, unknown hook, `$[0].name` mismatch +- Task 2: `recorded 154/154 passing 0 failing 0 unrecorded 0 pending 154`; `-run` selects one flow; ported-mismatch reports status 200 vs 404 +- Plan: `go vet ./...` and `go test ./...` exit 0 in both `summercms.go` and `fonoteka.go` +- Docker unavailable: `TestMain` exits 1 (no skip) + +## Self-Check: PASSED + +- [x] Key files exist on disk +- [x] `git -C ../fonoteka.go log --grep=02-04` returns Task 1 and Task 2 commits +- [x] Task acceptance criteria re-run and pass +- [x] Plan verification commands pass + +--- +*Phase: 02-api-parity-harness-bootstrap* +*Completed: 2026-09-17*