Test fixtures¶
tests/fixtures/ holds the deterministic input data for the test suite: synthetic raw API captures of representative threads (raw_entries.json / raw_blocks.json / thread.json), plus their rendered products committed as golden snapshots (conversation.md + turns/). Any rendering-layer regression breaks the byte-for-byte comparison against the goldens.
What a fixture is¶
Each fixture is one directory with raw captures plus a golden/ tree:
| Path | Role |
|---|---|
raw_entries.json |
raw API capture: the thread's entries |
raw_blocks.json |
raw API capture: workflow blocks (absent where the mode has none, e.g. search_demo, study_demo) |
thread.json |
archived thread metadata |
golden/conversation.md + golden/turns/turn_*.md |
committed rendered products, compared byte-for-byte |
All fixtures are synthetic and deterministic:
- Account identities — usernames / display names / emails / user_ids are shared placeholders (
alice/bob,Alice Example/Bob Example,alice@example.com/bob@example.com,00000000-0000-4000-8000-0000000000aa/00000000-0000-4000-8000-0000000000bb); the BOT space is00000000-0000-4000-8000-0000000000b0/bot-EXAMPLE;read_write_tokenis the fixed placeholder00000000-0000-4000-8000-0000000000ff. - Identifier mapping — thread / entry / asset UUIDs are uuid5-derived (fixed namespace + original value) synthetic values tagged with the
5cbeef00prefix, consistent repo-wide: the same input always maps to the same output, preserving cross-references.toolu_run ids likewise become fixed-length synthetic ids tagged with5crub0. - User text — queries / titles / space names / Q&A payloads / file-handle paths carry generic synthetic text; CloudFront/S3 signed URLs have their signature query strings stripped.
The following generic placeholder strings are test-semantic markers and are kept intentionally: 已推送, All done, Add analysis tool shell wrapper, 这是子代理的最终结论段落, Answer skipped.
Naming¶
Fixture directories carry descriptive names only — they identify the mode or the defect scenario, never a thread identifier:
- Full-thread fixtures:
<mode>_demo(e.g.search_demo,council_demo). - Trimmed defect scenarios:
scenario_<defect-scenario>.
Inventory¶
Full-thread snapshots (golden = rendered products, byte-for-byte)¶
| fixture | coverage |
|---|---|
search_demo |
search, single turn; R code fence / inline code |
deep_research_demo |
deep-research; raw contains \(, golden contains 26× $$ — end-to-end math-delimiter conversion |
computer_demo |
computer, multi-turn (7 turns) workflow rendering |
council_demo |
council model-committee rendering (~1.0 MB raw capture) |
study_demo |
study (generic study question) |
Trimmed scenario fixtures (single-entry trims; golden = corresponding turn files, turns renumbered)¶
Specific defect scenarios trimmed into a single entry (thread_metadata preserved, background_entries cleared — verified that none of the trimmed entries' workflow blocks carry sub-agent step dependencies, so trimming does not change the render path).
| fixture | coverage |
|---|---|
scenario_computer_answer_fallback |
computer answer fallback: when the plain FINAL path is guard-skipped (workflow_snapshots only), the answer is recovered from the schematized workflow_block's variant=answer TEXT (parsers.wf_block_answer) |
scenario_subagent_fallback |
sub-agent fallback: with no background match, render the sub-agent headline + the step's own items (sub-agent summary is not lost) |
scenario_user_response |
WORKFLOW_ITEM_USER_RESPONSE Q&A rendering (4 questions / 4 answers); turn with an empty query |
scenario_subagent_stub |
subagent_result stub turn: 10-second time-window association of unanchored background sub-agents |
scenario_workflow_item_nested |
nested WORKFLOW_ITEM_WORKFLOW collapsed-block rendering |
Interruption / cancellation scenarios (multi-entry + background_entries trims, relations intact)¶
Interruption semantics depend on the entry ↔ background attribution relation, so background_entries cannot be cleared; the trim is a uuid subset (at least one payload each for the anchored / stub-window / appendix classes).
| fixture | coverage |
|---|---|
scenario_limit_interrupted |
spending-limit interruption (locked_reason=spending_limit_exceeded + AWAITING): ⏸ annotations on the workflow header / sub-agent headlines / stub-turn summaries; attribution waterfall level ③ — the unconsumed 38-step payload lands in the conversation.md appendix; anchored / stub-window payloads stay out of the appendix and are never double-rendered |
scenario_canceled |
WORKFLOW_CANCELED: ⛔ annotation on the workflow header |
Maintaining fixtures¶
tests/scrub_fixtures.py is the deterministic maintenance tool (tests/scrub_fixtures.py:529): positional text replacement, identifier remapping, and golden snapshot regeneration. It is idempotent and safe to re-run.
uv run python tests/scrub_fixtures.py # apply replacements + regenerate goldens
uv run python tests/scrub_fixtures.py --check # gate only, no writes
- Golden regeneration (
tests/scrub_fixtures.py:500-524) — each fixture's raw JSON is re-rendered in a temporary directory through the same offlinererenderpath used in production and by the test suite;golden/is refreshed byte-consistently, and a turn-count mismatch aborts with an error. - Replacement sources live outside the repo — account identity values are read at runtime from the user-level config (
~/.config/pplx-export/config.tomlviapplx_export.config; see Configuration); content-class replacement pairs come from a local override mappingtests/scrub_pairs.local.json— never committed; the committed template istests/scrub_pairs.example.json(pairs:[old, new]entries applied in order;gate: zero-tolerance substrings). - Deterministic remapping — remaining UUIDs →
5cbeef00-tagged uuid5 values,toolu_ids →5crub0-tagged fixed-length ids,read_write_token→ the fixed placeholder; values already carrying a marker prefix are skipped (idempotent). Scope: positional pairs apply only inside their fixture directory; the local content pairs and the identity / token / UUID / toolu_ mappings apply globally. --checkgate mode — scans every file undertests/fixtures/for the union of all replacement sources, gate patterns, and local-absolute-path / signed-URL residue, and exits non-zero on any hit without writing anything. On a re-run, a mapping whose new value is already present counts as applied.
Run it after changing a fixture's raw JSON or the renderer, and run --check before committing fixture changes. Inside the test suite, tests/conftest.py::render_fixture uses the same offline re-render path, so snapshot tests always diff fresh renderer output against the committed goldens.
See also¶
- Testing — how the suite consumes these fixtures
- Test system architecture — the regression strategy behind the goldens
- Offline operations — the re-render pipeline that regenerates them