"""US09-01: the documentation the application serves, checked without a browser. What is checkable offline is everything that makes the manuals *navigable* rather than merely present: every page reachable, every internal link and anchor landing somewhere real, the vendored renderer being the file that was pinned, and the application actually serving `docs/` in both the working copy and the image. The rendering itself — marked, mermaid, anchors, link rewriting — is proven in a real browser by ``tests/e2e/test_docs_ui.py``. """ from __future__ import annotations import hashlib import json import re from pathlib import Path import pytest from starlette.testclient import TestClient from photo_pipeline.api.app import create_app from photo_pipeline.api.security import DEFAULT_HEADERS from photo_pipeline.config import Config # Every check here belongs to the documentation gate (US09-05). pytestmark = pytest.mark.phase_i REPO = Path(__file__).resolve().parents[2] DOCS = REPO / "docs" VENDOR = REPO / "frontend" / "js" / "vendor" INDEX = DOCS / "index.md" MARKDOWN_LINK = re.compile(r"\[[^\]]*\]\(([^)\s]+)\)") HEADING = re.compile(r"^#{1,6}\s+(.+?)\s*$", re.MULTILINE) FENCE = re.compile(r"^```.*?^```", re.MULTILINE | re.DOTALL) def pages() -> list[Path]: return sorted(DOCS.rglob("*.md")) def body(path: Path) -> str: """A document without its fenced code, so an example link in a shell snippet is not mistaken for a link the reader can follow.""" return FENCE.sub("", path.read_text()) def slug(text: str) -> str: """The heading-anchor rule, mirroring ``frontend/js/docs.js``. Deliberately duplicated: this check has to run without a browser. The browser test is the authority on the real behaviour, and it asserts the same anchors. """ cleaned = re.sub(r"[^\w\s-]", "", text.lower(), flags=re.UNICODE).strip() return re.sub(r"[\s-]+", "-", cleaned).strip("-") or "section" def anchors(path: Path) -> set[str]: found: dict[str, int] = {} for heading in HEADING.findall(body(path)): # Markdown emphasis and inline code are not part of the rendered text. plain = re.sub(r"[*`_]", "", heading) base = slug(plain) found[base] = found.get(base, 0) + 1 return {name if index == 1 else f"{name}-{index}" for name, count in found.items() for index in range(1, count + 1)} def internal_links(path: Path) -> list[str]: return [ href for href in MARKDOWN_LINK.findall(body(path)) if not re.match(r"^[a-z][a-z\d+.-]*:", href, re.IGNORECASE) and not href.startswith("//") ] # ── the documentation tree ─────────────────────────────────────────────────── def test_the_index_exists_and_names_every_page(): """A page nobody links to is a page nobody reads.""" assert INDEX.is_file() listed = { (INDEX.parent / href.split("#")[0]).resolve() for href in internal_links(INDEX) if href.split("#")[0].endswith(".md") } unreachable = [page.name for page in pages() if page != INDEX and page.resolve() not in listed] assert unreachable == [], f"not linked from the index: {unreachable}" def test_every_internal_link_and_anchor_resolves(): broken: list[str] = [] for page in pages(): for href in internal_links(page): target, _, anchor = href.partition("#") destination = page if not target else (page.parent / target).resolve() if not destination.is_file(): broken.append(f"{page.name} → {href} (no such file)") continue if anchor and anchor not in anchors(destination): broken.append(f"{page.name} → {href} (no such heading)") assert broken == [], broken def test_every_page_starts_with_one_title(): for page in pages(): titles = [line for line in page.read_text().splitlines() if line.startswith("# ")] assert len(titles) == 1, f"{page.name} has {len(titles)} top-level titles" # ── the vendored renderer ──────────────────────────────────────────────────── def test_the_vendored_libraries_are_the_files_that_were_pinned(): """A vendored dependency without a recorded checksum is a dependency nobody is reviewing. Replacing one has to be a visible change to this manifest.""" manifest = json.loads((VENDOR / "VERSIONS.json").read_text()) recorded = {entry["file"]: entry for entry in manifest["libraries"]} on_disk = {path.name for path in VENDOR.iterdir() if path.suffix in (".js", ".mjs")} assert on_disk == set(recorded), f"unrecorded vendored files: {on_disk ^ set(recorded)}" for name, entry in recorded.items(): digest = hashlib.sha256((VENDOR / name).read_bytes()).hexdigest() assert digest == entry["sha256"], f"{name} does not match the pinned checksum" assert entry["version"] in entry["url"], f"{name}: the pinned URL and version disagree" assert entry["license"], f"{name}: no license recorded" def test_the_documentation_view_is_wired_into_the_shell(): shell = (REPO / "frontend" / "index.html").read_text() assert 'data-nav="docs"' in shell, "the manuals are unreachable from the navigation" assert '"/docs"' in (REPO / "frontend" / "js" / "app.js").read_text() # ── the policy the view runs under ─────────────────────────────────────────── def test_the_script_boundary_is_unchanged_and_only_style_was_relaxed(): """The measured cost of rendering diagrams in the browser, held to that cost. mermaid needs to style the SVG it builds, which `style-src 'self'` refuses. It does not need to execute generated code, so `script-src` must never acquire the escape hatch that would let injected markup run. """ policy = DEFAULT_HEADERS["content-security-policy"] directives = { part.split(" ")[0]: part.split(" ")[1:] for part in (piece.strip() for piece in policy.split(";")) if part } assert directives["script-src"] == ["'self'"] assert "'unsafe-inline'" in directives["style-src"] for directive in ("default-src", "img-src", "connect-src", "font-src"): assert "'self'" in directives[directive] assert "'unsafe-inline'" not in directives[directive] assert directives["frame-ancestors"] == ["'none'"] # ── serving it ─────────────────────────────────────────────────────────────── def test_the_application_serves_the_markdown_without_a_session(tmp_path): """Whoever cannot get past the access secret is exactly who needs these pages.""" config = Config.from_env({"PHOTO_PIPELINE_DATA_DIR": str(tmp_path / "data")}) (tmp_path / "data").mkdir() with TestClient(create_app(config), raise_server_exceptions=False) as client: response = client.get("/docs/index.md", headers={"cookie": ""}) assert response.status_code == 200 assert "Photo Pipeline documentation" in response.text assert client.get("/docs/overview.md").status_code == 200 assert client.get("/docs/nothing-here.md").status_code == 404 # The mount is a directory, not a path parameter: nothing above it is reachable. assert client.get("/docs/../pyproject.toml").status_code in (307, 404) assert client.get("/docs/%2e%2e/pyproject.toml").status_code == 404 def test_the_image_ships_the_documentation_it_serves(): """An image without `docs/` serves an empty manual — and the build context is deny-by-default, so a new directory is excluded until it is named.""" assert "\n!docs\n" in (REPO / ".dockerignore").read_text() assert "COPY docs ./docs" in (REPO / "Dockerfile").read_text()