test: parse_duplicate_categories could not fail for the merge it names

Found by an external Grok Bot adversarial review (improvise-jkxr), verified by
injection rather than taken on report.

The test parses two `## Category: Type` sections -- `- A` then `- B` -- and
asserted only `!item_names.is_empty()`. push_category concatenates on a
duplicate section name, and a merge keeping EITHER half alone still leaves the
list non-empty, so only a total wipe could ever have failed it.

Fault injected: `existing.items.extend(items)` -> `existing.items = items`, so
a second section replaces the first. The old assertion PASSED; the new one
fails with left ["B"], right ["A", "B"].

Pins order as well as membership -- insertion order is load-bearing (gotcha 18)
-- matching the standard its neighbour already holds groups to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
Edward Langley
2026-09-21 13:49:58 -07:00
parent d358da46a1
commit 2b086b205a
2 changed files with 23 additions and 1 deletions
+22
View File
@@ -777,3 +777,25 @@ fixture had a single `Month`, so it demonstrated the row half and left the
column half asserted by nothing -- the same defect one axis over. `Feb` is now
the empty column, the precondition is a pair on each layout ((2, 1) drawn
against (3, 2) unpruned), and the CSV header is asserted to carry `Jan` alone.
### persistence::parse::parser_edge_cases::parse_duplicate_categories
Found by an external Grok Bot adversarial review (improvise-jkxr), and the
finding was verified by injection rather than taken on report.
The test parses two `## Category: Type` sections — `- A` then `- B` — and
asserted only `!item_names.is_empty()`. That cannot fail for the defect the
test's name and placement imply. `ParsedFile::push_category` concatenates on a
duplicate section name (`invariant(duplicate_category_sections_merge_in_pass_one)`),
and a merge that kept *either* half alone still leaves the list non-empty.
**Fault injected:** `existing.items.extend(items)` → `existing.items = items`,
so a second section *replaces* the first instead of merging. The old assertion
**passed**. The strengthened assertion fails with `left: ["B"], right: ["A", "B"]`.
Only a total wipe of both sections could ever have failed the original.
The assertion is now `assert_eq!(item_names, ["A", "B"])`, which pins order as
well as membership — insertion order is load-bearing here (gotcha 18), and the
neighbouring `two_sections_naming_one_category_round_trip_to_a_stable_group_order`
already holds groups to that standard. This test was the item-list half of the
same invariant, held to a weaker one.
+1 -1
View File
@@ -1098,7 +1098,7 @@ mod parser_edge_cases {
let m = parse_md(text).unwrap();
let cat = m.model.category("Type").unwrap();
let item_names: Vec<&str> = cat.items.values().map(|i| i.name.as_str()).collect();
assert!(!item_names.is_empty());
assert_eq!(item_names, ["A", "B"], "the merge dropped a section's items");
}
#[test]