-
Notifications
You must be signed in to change notification settings - Fork 1
testing
- tests/test_repodocs.py
- tests/test_fix_publish.py
- tests/test_fix_translate_cite.py
- tests/test_fix_generate.py
- tests/test_fix_plan.py
- tests/test_fix_render_links.py
- tests/test_fix_scan_util.py
- tests/test_fix_citation_repair.py
- pyproject.toml
The test suite lives under tests/ and is run with pytest (declared as the test optional dependency group, pytest>=8, in pyproject.toml). It has two distinct shapes: one large end-to-end module built around a single selftest() function ported from a former in-module self-check, and a set of smaller, narrowly targeted test_fix_*.py modules that each pin down one cubic-review regression against a specific source file.
Sources: pyproject.toml:L21-L22, tests/test_repodocs.py:L1-L1
test_repodocs.py imports nearly every public symbol from the repodocs package (plan, render, _util, backend, citations, cli, generate, gitlinks, publish, scan, translate) and defines a single long function, selftest(), that exercises them all in sequence using plain assert statements rather than individual pytest test functions. The only pytest entry point in the file is test_repodocs(), a one-line wrapper that calls selftest().
Sources: tests/test_repodocs.py:L12-L26, tests/test_repodocs.py:L589-L590
selftest() uses two small local helpers, _raises(fn, *a) and _raises_exit(fn, *a), to assert that a callable raises ValueError or SystemExit (with a nonzero code) respectively, since bare assert cannot express "and it raised."
Sources: tests/test_repodocs.py:L28-L41
The body of selftest() walks through the pipeline roughly in the order a repo would be processed, each block wrapped in its own tempfile.TemporaryDirectory():
| Area | What is asserted |
|---|---|
Planning (plan_pages, scan, scan_inventory) |
mandatory slugs (overview, installation, architecture, development, changelog) appear; overview is first; small files are excluded from component pages |
validate_pages |
bad slugs, non-dict entries, and duplicate slugs are dropped; missing files fall back to README.md
|
parse_pages |
JSON fenced in ```json ``` blocks parses; non-JSON input raises |
CITATION_RE |
matches real citation links, ignores plain markdown links |
compute_file_hash |
known bytes hash to a known sha256 |
generate_decision |
pure skip/regenerate decision table (new/unchanged/changed/added/removed) |
llm_plan idempotency |
unchanged inventory reuses cached plan.json; changed inventory retries the planner; a fallback plan is never cached with .plan.hash
|
graph_digest |
absent/corrupt graphify-out/graph.json yields ""; a real graph yields a compact digest naming import counts |
decide_page |
a shared hcache memoizes file hashes across pages, even after a file mutates |
build_html |
embeds page titles and markdown, excludes index.md, and exits via SystemExit on an empty output dir |
vendor_assets failure |
a network OSError during asset vendoring produces a friendly die() exit, not a raw traceback |
gitlinks helpers |
_github_slug, rewrite_citation_links (including HTML/attribute escaping of injected markup), citations_safe
|
group_pages |
deterministic nav grouping into Overview/Features/Reference/Development |
translate_prompt |
embeds the source markdown verbatim plus the named target language |
lang_labels |
falls back to English for unknown language codes/out-dir names |
jobs_count |
REPODOCS_JOBS env var parsing/clamping (default 4, range 1..16) |
| backend selection |
backend_name, effective_model, backend_contract behavior across omp/claude/codex
|
publish staging |
stage_publish/stage_wiki file layout, staged_secret_findings, publish_branch_safe, symlinked-source rejection |
publish_wiki_push |
end-to-end against a local bare git remote: clone-missing, no-op, normal push, and push-failure paths |
setup_install |
fresh/identical/differs-kept/force-overwrite install states |
| security regressions |
safe_repo_file traversal/absolute/NUL/symlink rejection; validate_pages traversal drop; scan self-ingestion exclusion; citation_error dishonest-citation detection; citation_problems; XSS/DOMPurify wiring; THIRD_PARTY_NOTICES completeness |
Sources: tests/test_repodocs.py:L44-L586
flowchart TD
A[selftest] --> B[plan_pages / validate_pages / scan]
A --> C[generate_decision / decide_page / compute_file_hash]
A --> D[build_html / vendor_assets failure]
A --> E[gitlinks: citation link rewrite + safety]
A --> F[backend selection + effective_model]
A --> G[publish: stage_publish / stage_wiki / publish_wiki_push]
A --> H[security regressions: safe_repo_file, scan self-ingestion, citation_error]
I[test_repodocs] --> A
Sources: tests/test_repodocs.py:L44-L590
Alongside selftest, eight test_fix_*.py modules use ordinary pytest test functions (with fixtures like tmp_path, monkeypatch, capsys) to pin individual bugs found during cubic code review, each documented with a docstring naming the PR and the review comment it addresses.
Covers repodocs.publish: publish_branch_safe normalizes a refs/heads/ prefix and is case-insensitive against main/master; PUBLISH_SECRET_PATTERNS catches an ENCRYPTED PRIVATE KEY block; publish_push can be called twice against the same branch without failing on a leftover local orphan ref, and cleans up its repodocs-publish-* branches; _reject_symlink_dest and publish_wiki_push refuse a symlinked file or symlinked parent directory inside a wiki clone before copying; clone failures are classified correctly (WikiPublishError for an auth failure, WikiNotInitialized for a missing local path); cmd_publish_wiki reaches its documented Home fallback for an index-only out/ dir, and rejects a symlinked overview.md before the citation scanner ever reads through it; _publishable_subdir_mds skips a translated subdirectory that has no rendered wiki.html yet.
Sources: tests/test_fix_publish.py:L1-L28, tests/test_fix_publish.py:L50-L94, tests/test_fix_publish.py:L99-L240
Covers repodocs.translate and repodocs.citations: localize_headings skips a ## Relevant source files heading text that appears inside a fenced code block while still translating the real H2 outside it; translate_plan_file rejects a non-list plan.json (falling back to a verbatim copy without calling the LLM); cmd_translate rejects path-like language codes (.., ., ../x, a/b, /etc) via SystemExit; propagates a plan-translation failure as a nonzero return code while still writing the fallback copy; and blocks (exit code 1) a translation that drops the Sources: citation line while allowing one that keeps it. On the citations side, requires_evidence still exempts a ## Relevant source files bullet list but flags an ordinary body bullet list; citation_problems flags an all-bullets page with no citation, rejects a citation-shaped label with no linked href, ignores a citation hidden in an HTML comment or code fence, and accepts a real rendered citation.
Sources: tests/test_fix_translate_cite.py:L1-L121, tests/test_fix_translate_cite.py:L124-L191
Covers repodocs.generate: _safe_page rejects a traversal slug (../evil) and prints "unsafe slug" to stderr; cmd_generate refuses to write outside out even when an unsafe slug slips past plan.py's own validation; decide_page drops an escaping candidate file (via symlink or ../ path) before hashing it; page_prompt omits the graphify-out/graph.json hint when that file is a symlink escaping the repo, but includes it for a real in-repo graph; decide_page falls back to "generate" on a corrupt .hashes.json record instead of crashing; index.md lists the full plan even when cmd_generate is invoked with --pages/only restricting which pages are (re)generated; _gitignore_notice does not raise when out == repo (an empty relative path).
Sources: tests/test_fix_generate.py:L1-L146
Covers repodocs.plan: graph_digest returns "" for invalid UTF-8 in graph.json instead of raising; validate_pages drops a null files field into [] instead of crashing; load_plan validates a cached plan.json, dropping a traversal slug; load_plan recovers from a corrupt (non-JSON) cache by falling back to the heuristic plan (overview first); llm_plan falls back to the heuristic plan when the LLM's plan omits a mandatory slug (e.g. installation for a repo with a README), and does not cache that fallback via .plan.hash; plan_pages allocates collision-safe component-* slugs for two directories whose slugified names collide (Foo Bar and foo-bar), producing component-foo-bar and component-foo-bar-2.
Sources: tests/test_fix_plan.py:L1-L93
Covers repodocs.render, repodocs.gitlinks, and repodocs.backend: render_html HTML-escapes a malicious breadcrumb so it cannot break out of <title> or the inline <script> block (verified against the exact REPO = ... JSON literal with </ escaped), and HTML-attribute-escapes a malicious ghroot href; CDN_ASSETS entries are pinned to exact semver (no floating major version) and every CDN <script>/<link> carries a matching sha384- SRI hash from SRI, while vendored (local) assets never carry that SRI value; rewrite_citation_links escapes a malicious base URL so it cannot inject a live onmouseover attribute; _github_slug rejects HTML metacharacters in the repo name and accepts dotted/underscored/dashed names; citations_safe treats an untracked source file outside the output dir as dirty, allows untracked entries under the output dir, and defaults to dirty when no out_rel is given; the codex backend's run_llm prints a read-boundary warning ("read-only", "does NOT restrict reads", "untrusted repositories") exactly once per process.
Sources: tests/test_fix_render_links.py:L1-L133
Covers repodocs.scan and repodocs._util: a dangling symlink and (where os.mkfifo exists) a FIFO are excluded from src_files/line_counts; passing out=repo/src/generated to scan prunes only that subtree, leaving sibling directories intact; a README.md or package.json that is a symlink escaping the repo is dropped (has_readme false, manifest excluded); a CI workflow file that is an escaping symlink is dropped while a real sibling workflow is kept, and the resulting ci list is alphabetically sorted; safe_repo_file returns None on a self-referencing symlink loop (ELOOP) rather than raising.
Sources: tests/test_fix_scan_util.py:L1-L112
Covers repair_citations in repodocs.citations: a citation that overshoots end-of-file by exactly one line (the model's "+1" off-by-one) is clamped to the file's real last line, after which citation_error reports it as valid; a citation that is already valid, one whose file does not exist, and one whose start line itself is past EOF are all left untouched by repair_citations (the last two are unrepairable and are left for citation_error/citation_problems to flag as invalid).
Sources: tests/test_fix_citation_repair.py:L1-L35
pyproject.toml declares pytest>=8 under [project.optional-dependencies] test, and configures ruff (not pytest) with a py310 target and a lint rule selection of E4, E7, E9, F — line length is intentionally not enforced. There is no [tool.pytest.ini_options] section in pyproject.toml, so pytest runs with its defaults (test discovery over the tests/ directory's test_*.py files).
Sources: pyproject.toml:L21-L22, pyproject.toml:L43-L50
- Home
- Installation & Setup
- Architecture
- CLI Reference
- LLM Backend Selection
- Repository Scanning
- Wiki Page Planning
- Page Generation
- Source Citations
- Translation
- HTML Rendering
- Diagram Rendering
- Diagram Poster Tool
- Git Remote Link Resolution
- Publishing
- GitHub Wiki Integration
- Shared Utilities
- Environment Configuration
- Security & Trust Boundaries
- Limitations & Non-goals
- Testing
- Development
- Contributing
- Upgrading
- Changelog