Testing architecture¶
The pplx_export test suite: fully offline, checked-in fixtures, snapshot-locked rendering.
The section keeps its original numbering from the architecture overview;
for task-oriented testing how-tos see Testing and
Fixtures.
13. Test system¶
Location tests/ (outside the package); run uv run pytest tests (fully offline —
fixtures are checked in, zero network; the case count keeps growing with regression additions,
per actual run output).
| File | Coverage | Essentials |
|---|---|---|
conftest.py |
render_fixture / rendered fixtures |
copies the fixtures' raw JSON into a tmp directory and re-renders offline reusing commands.rerender_cmd.rerender (conftest.py:56-68) — the same code as the production path |
test_render_snapshots.py |
render snapshot regression | five-mode full-thread fixtures (search/deep-research/computer/council/study) + five trimmed defect scenarios (computer answer fallback / subagent fallback / USER_RESPONSE Q&A pair / stub-turn association / nested workflow collapse); re-rendered output compared byte-for-byte against goldens; plus a dict-repr residue signature check |
test_units.py |
core unit tests | BatchState (incl. trailing-zero normalization _norm_ts, expired terminal state, corrupt backup), Throttle (backoff formula/cap/reset), plan_incremental (four states/early stop/full/force), AssetDownloader._final_name (double-extension guard), normalize_math_delims (fence/inline-code protection, mixed residue), TestDetectMode (search_mode detection), TestSafeFolder (directory-name cleanup), TestFetchMissingBlocks (study scope + multi-account grouping), TestExternalReviewFixes (external review regressions: tables not truncated / cookie permissions / domain matching, etc.) |
test_interruptions.py |
interruption semantics | classify_wf_status five classes, attribution waterfall ③ appendix, render annotation, interruption registration; scenario fixtures: scenario_limit_interrupted (limit interruption + 38-step unconsumed payload into the appendix), scenario_canceled |
test_stub_workflows.py |
stub-turn association | match_stub_workflows semantics (stub definition, 10s window, used_cand single consumption, anchored exclusion), render_wf_item nested-rendering recursion/dedup guard (depth ≤2 + id set against self-reference, render.py:171) |
test_fix_n01…n11_*.py(9 files) |
second-round external review regressions (N-01..N-12) |
inline-asset same-name multi-version write conflicts (N-01) / spaces backlink relative levels (N-02) / citation dedup shared implementation + turns/ stale cleanup (N-03, N-12) / cookie JSON-list domain suffix matching (N-04) / WebBridge error classification, no pointless sleep on the last retry, etc. (N-05/06/09) / usage-backfill mid-run write accounting (N-07) / Throttle.backoff overflow guard (N-08) / table header escaping (N-10) / batch total accounting (N-11); each file's header docstring recaps the corresponding findings |
test_fix_v301 / v305_*.py(2 files) |
third-round review regressions (V3-01 / V3-05) |
text_payload body rendering of nested WORKFLOW_ITEM_SOURCES (V3-01); closing export-path test blind spots — export-only artifacts such as thread.json interruptions writing (V3-05) |
test_fix_v4*.py |
fourth-round review regressions (V4-01..V4-06) |
added finding by finding as fixes landed (e.g. V4-03 handle-asset write idempotency); each file's header docstring recaps the finding |
test_fix_v5_review.py(+ additions to existing files) |
fifth-round review regressions (V5-01..V5-10) |
lastUpdated contract (V5-01: export uses the index updatedAt / ts_us_to_iso_full / is_unchanged restored) / find_thread_dirs and spaces backlink tightening (V5-03/04) / --mode five-mode filtering (V5-05) / cookie atomic write (V5-06) / spaces offline exemption (V5-08) / cron snippet out_root and quoting (V5-10); the regression for the directory-migration conflict semantics changing to always-keep-keeper was rewritten inside test_fix_v401_thread_dir_migration.py |
test_relations.py |
relations graph (§15) | same_prompt normalization (case/trailing punctuation/NFKC/no edges for empty queries, in-cluster chaining not cliques, scheduled-task rerun vs manual resend evidence, query_source taken from raw) / references (citation URLs, answer-text links, bare uuids, unarchived and self-links ignored, dedup single-direction, subagent citations included in the scan surface) / subagent_of (edges built from conversation-level sub_agents, archived subagent threads recorded in evidence, real-fixture end-to-end) |
test_search_mode_backfill.py |
search-mode-backfill (§16) | search_mode specificity priority / original values preserved / unknown values ignored / non-dict tolerance; local raw zero-network, expired terminal skip, online fallback, idempotent resume, --limit; refresh merge of enrichment keys, first run without an old index, corrupt old index warns but still writes; batch --mode filter treats index search_mode as authoritative, heuristic fallback unchanged |
test_sync_deleted.py |
sync-deleted (§17) | candidate determination (disappearance from the union of all account indexes — cross-account export_via guards against false positives, missing indexes safely skipped, non-ok states produce no candidates); deleted as a terminal state alongside expired (is_terminal, same tail-trimming in plan_incremental) |
test_answer_variants.pytest_answer_variant_logging.py |
answer_variants detection chain (§18) | collect narrowed criteria (hit / non-control standalone hit / control excluded / negatives), rerender registration idempotency, no key on healthy threads; ANSWER_VARIANT_DETECTED single-line fields, silent on empty hits, jsonl dedup and first_seen, rerender warns on change only |
Fixture provenance (full mapping table in tests/fixtures/README.md): raw data of
representative threads copied from web_archive/ (raw_entries/raw_blocks/thread.json); the
goldens of full-thread fixtures are the archive's current artifacts; trimmed fixtures are cut by
entry-uuid subsets, preserving attribution relations.
Regression strategy = any render-layer change that alters artifact bytes turns the tests red,
interlocked with re-render's idempotency promise.