Skip to content

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.py
test_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.