From 62025d22b3bffed3d8646627d699e489dafc7d3b Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 9 Oct 2026 22:52:32 +0200 Subject: [PATCH] docs(16.1-03): complete unit tests plan --- .planning/WINDOWS.md | 40 ++- .../16.1-03-SUMMARY.md | 256 ++++++++++++++++++ 2 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 .planning/phases/16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f/16.1-03-SUMMARY.md diff --git a/.planning/WINDOWS.md b/.planning/WINDOWS.md index 6385287..493ceea 100644 --- a/.planning/WINDOWS.md +++ b/.planning/WINDOWS.md @@ -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 ""` (reason required). -> Mark fixed with `gsd-tools windows fixed `. +> Cross-phase defect register. With `workflow.windows_enforce` enabled, `/msd-ship` blocks while `open_count > 0`. +> Waive with `msd-tools windows waive ""` (reason required). +> Mark fixed with `msd-tools windows fixed `. | 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" } ] ```` diff --git a/.planning/phases/16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f/16.1-03-SUMMARY.md b/.planning/phases/16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f/16.1-03-SUMMARY.md new file mode 100644 index 0000000..2631375 --- /dev/null +++ b/.planning/phases/16.1-journal-admin-urgent-fixes-relation-search-list-state-post-f/16.1-03-SUMMARY.md @@ -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 `