docs(16.1-03): complete unit tests plan

This commit is contained in:
2026-10-09 22:52:32 +02:00
parent 837e40ae6a
commit 62025d22b3
2 changed files with 290 additions and 6 deletions

View File

@@ -1,17 +1,17 @@
---
schema_version: 1
open_count: 4
open_count: 6
waived_count: 0
fixed_count: 6
total_count: 10
last_updated: 2026-10-05T12:07:04.853Z
total_count: 12
last_updated: 2026-10-09T20:51:27.671Z
---
# Broken Windows Ledger
> Cross-phase defect register. With `workflow.windows_enforce` enabled, `/gsd-ship` blocks while `open_count > 0`.
> Waive with `gsd-tools windows waive <id> "<reason>"` (reason required).
> Mark fixed with `gsd-tools windows fixed <id>`.
> Cross-phase defect register. With `workflow.windows_enforce` enabled, `/msd-ship` blocks while `open_count > 0`.
> Waive with `msd-tools windows waive <id> "<reason>"` (reason required).
> Mark fixed with `msd-tools windows fixed <id>`.
| id | phase | kind | file | line | description | status | reason | recorded_at | resolved_at |
|----|-------|------|------|------|-------------|--------|--------|-------------|-------------|
@@ -25,6 +25,8 @@ last_updated: 2026-10-05T12:07:04.853Z
| 8 | 11 | skipped-test | parity/broadcast_goldens_test.go | | fonoteka.go: TestBroadcastGoldens/created and /updated t.Skip as pending; Phase 12 turns the recorded PHP created/updated goldens into assertions (album subtree, literal created_at/updated_at and artist id handling) | open | | 2026-09-30T11:32:37.302Z | |
| 9 | 12.1 | stub | admin/src/components/form/RecordActions.vue | | RecordActions.vue is built and tested but not mounted; plan 12.1-02 mounts it in the preview footer, until then meta.actions is served and no screen shows the buttons | fixed | | 2026-10-04T21:55:30.405Z | 2026-10-05T10:41:16.319Z |
| 10 | 12.1 | unmet-truth | .planning/phases/12.1-user-plugin-admin-screens/deferred-items.md | | fonoteka.go: four application tests pin lists the user plugin's admin work changes (TestAdminMetadataFiltering nav list, TestHiddenNeverMarshals user model count, parity TestMigrateSeedsCanonicalGenres and TestRollbackLastIsolatesFonoteka migration ids); with the two from plan 12.1-02 they keep plan 12.1-03's application verify red until plan 12.1-04 updates them | fixed | | 2026-10-05T11:31:36.781Z | 2026-10-05T12:07:04.853Z |
| 11 | 16.1 | unrun-verify | modules/cabana/query_relation_test.go | | TestListRelationSearchPostgres not run here (no Docker); run go test -count=1 ./modules/cabana on a Docker host before /msd-verify-work | open | | 2026-10-09T20:51:22.934Z | |
| 12 | 16.1 | unrun-verify | .planning/phases/16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f/16.1-VALIDATION.md | | sm-journal-plugin TestPostsAdminResaveKeepsHTML (posts_admin_smoke_test.go) not run here (no Docker); run go -C ../sm-journal-plugin test -count=1 ./... on a Docker host before /msd-verify-work | open | | 2026-10-09T20:51:27.671Z | |
````json
[
@@ -157,6 +159,32 @@ last_updated: 2026-10-05T12:07:04.853Z
"recorded_at": "2026-10-05T11:31:36.781Z",
"resolved_at": "2026-10-05T12:07:04.853Z",
"milestone": "v1.0"
},
{
"id": 11,
"kind": "unrun-verify",
"phase": "16.1",
"file": "modules/cabana/query_relation_test.go",
"line": null,
"description": "TestListRelationSearchPostgres not run here (no Docker); run go test -count=1 ./modules/cabana on a Docker host before /msd-verify-work",
"status": "open",
"reason": "",
"recorded_at": "2026-10-09T20:51:22.934Z",
"resolved_at": null,
"milestone": "v1.0"
},
{
"id": 12,
"kind": "unrun-verify",
"phase": "16.1",
"file": ".planning/phases/16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f/16.1-VALIDATION.md",
"line": null,
"description": "sm-journal-plugin TestPostsAdminResaveKeepsHTML (posts_admin_smoke_test.go) not run here (no Docker); run go -C ../sm-journal-plugin test -count=1 ./... on a Docker host before /msd-verify-work",
"status": "open",
"reason": "",
"recorded_at": "2026-10-09T20:51:27.671Z",
"resolved_at": null,
"milestone": "v1.0"
}
]
````

View File

@@ -0,0 +1,256 @@
---
phase: 16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f
plan: 03
subsystem: testing
tags: [vitest, localStorage, gorm, dryrun, postgres, testcontainers, goldmark, xss]
requires:
- phase: 16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f
provides: "16.1-01 fixes (relation subqueries, default locale first, raw HTML in FormatHTML, list state) and the 16.1-02 release v0.1.16"
provides:
- "vitest coverage of the list to form to list round trip (back, cancel, save-and-close, delete), listState.ts helpers, ListView entry logic and logout clearing"
- "TestListRelationSQL: DryRun SQL matrix of the relation subquery builder for every relation type, the refusals and bound search values"
- "TestEnabledLocalesDefaultFirst: every default-locale ordering case"
- "Strict FormatHTML refuse cases and imported-HTML keep cases in sm-journal-plugin"
- "Postgres tests TestListRelationSearchPostgres and TestPostsAdminResaveKeepsHTML, ready for a Docker host"
- "16.1-VALIDATION.md filled: every automated row green, wave_0_complete and nyquist_compliant true"
affects: [16.2 blog editor, next summercms tag, /msd-verify-work 16.1]
actuals:
tokens: 13669 # chars/4 over the realized test diffs of both repos (summercms.go admin+modules, sm-journal-plugin)
tasks: 3
commits: 3 # summercms.go, measured from the ledger before the SUMMARY commit; sm-journal-plugin adds 1 (03907ee)
plan_head_before: 3b8527b39739a34eaee540d1ec0458966146a91c
plan_head_after: 837e40ae6aac05cf060b5c4fbd8872ca5d96e847
tech-stack:
added: []
patterns:
- "bindRecorder: a GORM logger implementing ParamsFilter records statements with placeholders and bound values, so a DryRun test proves a value is bound, not inlined"
- "withoutSubqueries strips (SELECT ...) groups so a test can tell an outer JOIN from one inside an EXISTS"
- "relModelController + relOneColumn: one generic controller and a one-relation columns.yaml per DryRun case, so the relation under test is always cabana_r0"
key-files:
created:
- admin/tests/state/listState.test.ts
modified:
- admin/tests/form/FormView.test.ts
- admin/tests/list/ListView.test.ts
- admin/tests/state/useAuth.test.ts
- modules/cabana/query_relation_test.go
- modules/cabana/locale_order_test.go
- ../sm-journal-plugin/classes/format_html_test.go
- ../sm-journal-plugin/posts_admin_smoke_test.go
- .planning/phases/16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f/16.1-VALIDATION.md
key-decisions:
- "FormatHTML refuse cases now require an error and an empty result for every unsafe input; the old 'or rendered harmless' escape hatch is gone because html.WithUnsafe leaves the gate as the only defence"
- "nyquist_compliant is true because every automated row is green; the Docker-host suites and UAT checks stay as manual rows, and two unrun-verify entries were added to WINDOWS.md"
patterns-established:
- "Bound-value proof for cabana SQL: openBindDryRunDB in query_relation_test.go"
requirements-completed: [D-01, D-02, D-03]
coverage:
- id: D1
description: "A form's back, cancel, save-and-close and delete land on the list with the stored query in the URL, and every list request after the return carries it"
requirement: D-01
verification:
- kind: unit
ref: "admin/tests/form/FormView.test.ts#returning to the list keeps its query (D-01)"
status: pass
human_judgment: false
- id: D2
description: "listState helpers (prefix key, malformed values removed, empty query stores nothing, throwing storage), ListView entry logic (URL wins, cleared controls, same-list navigation, 422, page clamp) and logout clearing list keys but not the sidebar"
requirement: D-01
verification:
- kind: unit
ref: "admin/tests/state/listState.test.ts"
status: pass
- kind: unit
ref: "admin/tests/list/ListView.test.ts#list state entry logic (D-01)"
status: pass
- kind: unit
ref: "admin/tests/state/useAuth.test.ts#logout (T-10-23) removes every stored list query"
status: pass
human_judgment: false
- id: D3
description: "Relation subquery SQL: belongsToMany through the tagged pivot (custom joinForeignKey/joinReferences, self-reference), hasMany, hasOne, belongsTo, soft delete, 422 refusals, bound LIKE-escaped search values, no outer JOIN next to ListExtendQuery and scope predicates, string_agg and join sorts"
requirement: D-02
verification:
- kind: unit
ref: "modules/cabana/query_relation_test.go#TestListRelationSQL"
status: pass
human_judgment: false
- id: D4
description: "enabledContentLocales ordering: default moved first, already first, absent, empty, DefaultLocale or EnabledLocales error, no writer, three locales keep their order"
requirement: D-03
verification:
- kind: unit
ref: "modules/cabana/locale_order_test.go#TestEnabledLocalesDefaultFirst"
status: pass
human_judgment: false
- id: D5
description: "FormatHTML keeps imported HTML unchanged and refuses every unsafe case with an error"
requirement: D-03
verification:
- kind: unit
ref: "../sm-journal-plugin/classes/format_html_test.go#TestFormatHTMLKeepsRawHTML"
status: pass
- kind: unit
ref: "../sm-journal-plugin/classes/format_html_test.go#TestFormatHTMLRejectsUnsafeHTML"
status: pass
human_judgment: false
- id: D6
description: "Postgres-backed tests: relation search counts a post matching through two tags once, sorts by tag names, finds a hasMany note; an admin re-save of HTML content keeps content_html"
requirement: D-02
verification:
- kind: integration
ref: "modules/cabana/query_relation_test.go#TestListRelationSearchPostgres"
status: unknown
- kind: integration
ref: "../sm-journal-plugin/posts_admin_smoke_test.go#TestPostsAdminResaveKeepsHTML"
status: unknown
human_judgment: true
rationale: "Needs Docker (testcontainers); this machine has no Docker socket permission. Both tests vet clean and fail (not skip) here with 'failed to create Docker provider'. Run on a Docker host before /msd-verify-work."
duration: 12min
completed: 2026-10-09
status: complete
---
# Phase 16.1 Plan 03: Unit tests Summary
**The Phase 16.1 fixes are now covered by tests that run without Docker:**
- vitest pins the list to form to list round trip and the browser-storage list state.
- A DryRun SQL matrix pins the relation subquery builder, including a bound-value check through GORM's ParamsFilter hook.
- Table tests pin the default-locale order.
- FormatHTML tests require an error for every unsafe input and an unchanged result for imported HTML.
Two Postgres tests are written for a Docker host.
## Performance
- **Duration:** 12 min
- **Started:** 2026-10-09T20:38:52Z
- **Completed:** 2026-10-09T20:51:32Z
- **Tasks:** 3 of 3
- **Files modified:** 6 in summercms.go (4 admin test files, 2 cabana test files), 2 in sm-journal-plugin, plus VALIDATION.md and WINDOWS.md
## Accomplishments
- **D-01, form round trip (tracer).** A stored query (`sort=code`, `dir=desc`, `filter[active]=true`) is restored after back, cancel, save-and-close and delete. The router lands on the list with that query, and every list GET after the click carries it. A fifth case checks that nothing stored means an unfiltered list with no params.
- Mutation check: with the restore in `ListView.vue` disabled, the four restore cases fail and the unfiltered case still passes.
- **D-01, listState.ts (23 tests).**
- Keys are the prefix plus the controller id, one key per list.
- Invalid JSON, non-objects, arrays and non-string values read as empty and remove the key. The strict parser drops malformed values.
- An empty query, empty filter values or only page 1 remove the key. A write then read round-trips every field.
- `clearListStates` keeps `summer-admin.sidebar` and unrelated keys.
- Storage whose `getItem`, `setItem`, `removeItem`, `key` or `localStorage` getter throws never throws out of the helpers.
- **D-01, ListView entry.**
- With a stored query, no list request goes out without the stored params.
- Every list param in the URL wins and replaces the whole stored query.
- Clearing search, sort and filter through the UI removes the key, and a remount loads the list unfiltered.
- `router.push` to the bare path of the same filtered list keeps the filters and makes no unfiltered request.
- A 422 removes the key.
- A page past `last_page`, from the URL or from storage, is replaced by `last_page` (requests 5 then 2).
- **D-01, logout.** For a successful call, a 500 and a network failure, logout removes every `summer-admin.list:` key and leaves only `summer-admin.sidebar`.
- **D-02, TestListRelationSQL.** 14 SQL-shape cases, 5 refusals and 2 statement-level checks.
- **Custom pivot keys.** The `cabana_rel_labelings` pivot with `labeled_post`/`label_ref` uses exactly those names, and the SQL has none of the default `rel_*_id` names.
- **Self-reference.** `cabana_rel_people` is aliased `cabana_r0` and correlates to the unaliased main table.
- **Soft delete.** The related model adds `"cabana_r0"."deleted_at" IS NULL`.
- **Refusals.** Polymorphic search and sort, and an unknown select column, each answer 422 with only that parameter in `details`. No page is loaded.
- **Search term.** `O'Neil 100%_X` never appears in the SQL text. Four placeholders are each bound to `%o'neil 100\%\_x%`.
- **ListExtendQuery and scope filter.** `user_id = 7 AND (...) AND id IN (3)` has no JOIN outside the subqueries.
- **Sort.** To-many sort orders by `string_agg` in both directions, followed by the id tiebreak. belongsTo and hasOne sort keep the `LEFT JOIN ... "Author"` / `"Profile"` association join.
- **D-02, TestListRelationSearchPostgres.**
- A post with two tags matching "fungi" gives total 1.
- `sort=tags` gives [2 1] ascending and [1 2] descending.
- `search=spore` finds the hasMany note, and `search=ada` finds the belongsTo author.
- **D-03, TestEnabledLocalesDefaultFirst.** 12 subtests, including a check that the writer's slice is not reordered in place.
- **D-03, FormatHTML.**
- **Keep cases.** 7 imported-style bodies round-trip unchanged after trimming. Markdown keeps a trailing raw `<footer>` and `<style>` verbatim.
- **Refuse cases.** All 19 refuse cases must return an error and an empty result. This adds svg `onload`, mixed-case `on*`, `javascript:` and `vbscript:` hrefs, `object`, `embed`, a script inside an HTML block, raw and markdown `data:` URLs, and upper-case `IFRAME`.
- `check_html_test.go` is unchanged and green.
- **D-03, TestPostsAdminResaveKeepsHTML.** An admin creates and then updates (PUT) a post whose content is two HTML paragraphs. After each write, `content_html` equals the content and has no placeholder.
## Task Commits
summercms.go (master, not pushed; ships with the next tag):
1. **Task 1: Tracer, list state round trip and storage coverage:** `a8bce9c` (test)
2. **Task 2: Relation subquery and locale order coverage:** `837922c` (test)
3. **Task 3 phase gate: VALIDATION.md results:** `837e40a` (docs)
sm-journal-plugin (main, not pushed):
3. **Task 3: FormatHTML keep and refuse coverage, admin re-save keeps HTML:** `03907ee` (test)
## Gate output
- Task 1: `npm --prefix admin test -- tests/state/listState.test.ts tests/state/useAuth.test.ts tests/list/ListView.test.ts tests/form/FormView.test.ts tests/smoke` gave 16 files and 299 tests passed. `npm --prefix admin run typecheck` was clean.
- Task 2: `go vet ./modules/cabana` was clean. Four `--- PASS: Test` lines: `TestEnabledLocalesDefaultFirstSmoke`, `TestEnabledLocalesDefaultFirst`, `TestListRelationSearchSmoke`, `TestListRelationSQL`.
- Task 3, journal: `go -C ../sm-journal-plugin vet ./...` was clean and `./classes/` printed ok. Two `--- PASS: Test` lines for the FormatHTML tests.
- Phase gate: `go vet ./...`, `TestDocsTree`, the typecheck, `npm --prefix admin test` (76 files, 1144 tests), and `check-admin-dist.sh` ("modules/boardwalk/dist matches a fresh build") ended with **`phase-16.1-local-gate-ok`**.
- Quick suite latency: 20 s.
## Docker-host result
**Pending.** `TestListRelationSearchPostgres` and `TestPostsAdminResaveKeepsHTML` fail here with `failed to create Docker provider`. They fail rather than skip, as designed. They are a manual row in VALIDATION.md and two `unrun-verify` entries in `.planning/WINDOWS.md`.
Human check before `/msd-verify-work`: on a Docker host, run `go test -count=1 ./...` in summercms.go and `go -C ../sm-journal-plugin test -count=1 ./...`. This includes `TestListQueryContract`, `TestListRelationSearchPostgres` and `TestPostsAdminResaveKeepsHTML`.
## Files Created/Modified
- `admin/tests/state/listState.test.ts` (new): the storage helper tests.
- `admin/tests/form/FormView.test.ts`: "returning to the list keeps its query (D-01)" with the tracer.
- `admin/tests/list/ListView.test.ts`: "list state entry logic (D-01)".
- `admin/tests/state/useAuth.test.ts`: logout clears list keys and keeps the sidebar.
- `modules/cabana/query_relation_test.go`:
- Matrix fixtures: custom pivot, self-reference, soft delete, polymorphic, hasOne and the scoped model and controller.
- `bindRecorder` and `openBindDryRunDB`, `withoutSubqueries`, `pageStatement`.
- `newFilteredListService`; `newDryRunListService` now delegates to it.
- `TestListRelationSQL` and `TestListRelationSearchPostgres`.
- `modules/cabana/locale_order_test.go`: `TestEnabledLocalesDefaultFirst` and its writer test doubles.
- `../sm-journal-plugin/classes/format_html_test.go`: strict refuse cases and keep cases.
- `../sm-journal-plugin/posts_admin_smoke_test.go`: `TestPostsAdminResaveKeepsHTML`.
- `16.1-VALIDATION.md`: every status filled, Wave 0 checked, recorded runs.
- `.planning/WINDOWS.md`: two unrun-verify entries for the Docker-host tests.
## Decisions Made
- **Strict refuse cases.** The FormatHTML refuse cases now require an error and an empty result. The old check also passed when the unsafe markup was rendered harmless. That is no longer a valid outcome, because with `html.WithUnsafe()` the gate is the only defence.
- **`nyquist_compliant: true`.** Every automated row is green. Docker-host suites and UAT checks remain manual rows. The frontmatter `status` stays `draft`, because only validate-phase sets it to `validated`.
## Deviations from Plan
None. The plan executed as written. Small additions within scope:
- hasOne search and sort cases.
- An `EnabledLocales` error case, an empty-default case and a no-in-place-mutation case.
- `newFilteredListService`, needed for the scope-filter case.
## Issues Encountered
- The `windows append` CLI refuses paths outside the repository. The sm-journal-plugin entry was therefore recorded against `16.1-VALIDATION.md`, and its description names the test file.
- The auto-mode classifier twice gave no verdict on the Task 2 commit. The retry succeeded and nothing changed in between.
## User Setup Required
None. No external service configuration is required.
## Next Phase Readiness
- Phase 16.1 has every plan summarized. Next steps:
- Run the Docker-host suites, which are pending.
- Run `/msd-verify-work 16.1` for the UAT rows (D-01 list state, D-03 form locales, the post-64 footer).
- Commits `a8bce9c`, `837922c` (summercms.go) and `03907ee` (sm-journal-plugin) are unpushed and ship with the next release.
- Phase 16.2 can reuse `openBindDryRunDB`, `newFilteredListService` and the generic `relModelController` for any new list SQL.
---
*Phase: 16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f*
*Completed: 2026-10-09*
## Self-Check: PASSED
Created and modified test files exist; commits a8bce9c, 837922c, 837e40a (summercms.go) and 03907ee (sm-journal-plugin) are present.