Skip to content

QA: no structure-preservation regression suite for mutating commands (seed-fixture + raw-diff invariants); covers the #838/#839 class #840

Description

@sebsnyk

Problem

There is no regression test that asserts structure preservation across the mutating-command surface. The existing live harness (scripts/live-tests/{docs,sheets,slides}.sh, gated by GOG_LIVE=1) exercises that commands run, but it does not seed one rich fixture and assert that each edit leaves untargeted structure intact. As a result, structure-loss bugs (e.g. #838 markdown block replacement dropping bullets/heading; #839 insert-image --at deleting the anchor) are found reactively in real use, not by CI.

A focused QA pass over the current surface (v0.28.1-dev, 21ca030d) found the surface is mostly sound, but the two known data-loss bugs are real, and nothing guards against regressions or the next instance of the class.

Proposal: a structure-preservation regression module

Add scripts/live-tests/structure.sh (sourced and run by live-test.sh, skip key structure) that:

  1. Seeds one rich fixture per surface, then snapshots … raw --json as BEFORE.
    • Docs: H1 + H2 headings; a paragraph with bold / italic / link runs; a bulleted list (3 items); a numbered list (3 items); a 2x2 table; an inline image; a Person chip.
    • Slides: a slide with a title shape (multi-word, one styled sub-range + one linked sub-range) and a body shape with 3 bulleted paragraphs.
    • Sheets: a 3x3 range with a bold + background-coloured header row.
  2. Runs each mutating op against one target, then re-reads raw --json as AFTER.
  3. Asserts the invariants below via the existing Python-assert idiom (paragraph bullet.listId, paragraphStyle.namedStyleType, run textStyle.{bold,italic,underline,link}, Person chip, inline image object, table cell text + cell style, sheet cell value + textFormat/backgroundColor), AND asserts adjacent/untargeted content + document order are unchanged (diff the BEFORE/AFTER element list outside the target range).

Each op gets a run_required/PASS-FAIL line. Known-bug ops (#838 block-replacement, #839 insert-image --at) seed run_optional (or xfail) entries so the suite documents them and flips to PASS when fixed.

Coverage matrix (command x invariant) — from the live QA pass

Legend: PASS = structure preserved; FAIL = data loss; — = n/a.

Command bullet/listId heading namedStyle run textStyle Person chip inline image table cells/style adjacent/order Result
docs find-replace (plain) PASS PASS PASS PASS PASS (native ReplaceAllText)
docs find-replace --format markdown, inline repl PASS PASS PASS PASS PASS (guarded by inlineReplacement)
docs find-replace --format markdown, block/multi-para repl FAIL FAIL n/a PASS FAIL → #838
docs sed PASS PASS PASS PASS PASS
docs format (bold/link/code) PASS PASS PASS PASS PASS
docs format --heading-level on a list item keeps bullet sets heading PASS PASS borderline (heading+bullet coexist)
docs insert / update --at PASS PASS PASS PASS PASS (inserts at match start)
docs cell-update --format markdown PASS (cell) PASS (siblings intact) PASS PASS
docs insert-table / table-row insert PASS (existing cells survive) PASS PASS
docs insert-image --at inserts image deletes anchor text FAIL → #839
docs insert-person --at inserts chip deletes anchor (documented) by-design
slides style-text / link / bullets (range) PASS PASS (neighbours intact) PASS PASS
slides insert-text PASS PASS PASS
slides replace-text (scoped) run-collapse on exact-match run* PASS (other runs/links intact) caveat*
sheets update / find-replace header textFormat intact PASS PASS
sheets delete-dimension (--force) header format intact preserves tables PASS PASS
sheets merge / unmerge PASS (A1 intact) PASS

* slides replace-text uses the native Slides ReplaceAllText API; when the match exactly covers a styled sub-run the replacement inherits the surrounding run style (Google API behaviour, not gog deleting structure). Worth a doc note, not a gog data-loss defect.

Root-cause map

Why this is the right safety net

The harness slots into the existing GOG_LIVE integration structure (one new sourced module + one skip key), reuses the raw --json + Python-assert idiom already used in docs.sh, and turns "we discover data loss in production" into "CI fails the moment a mutating op drops a structural property." It is the smallest addition that covers the whole class rather than one instance.

A skeleton module is in a draft PR (linked below).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions