--- 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*