Files
brain-of-reese/tests/unit/test_pictorial_gate.py
ducoterra 9633eb7653
Build and Push Containers / build-and-push-app (push) Successful in 2m26s
Build and Push Containers / build-and-push-db (push) Successful in 12s
phase: 140_pictorial_pdf_pages
Everything is verified green. Final report:

## Phase 140 final verification pass — report

- Verified all 4 tasks already complete (code in working tree, task files in `complete/`): `pictorial_pages` gate + 2 settings (`BOR_PDF_PICTORIAL`, `BOR_PDF_PICTORIAL_MIN_CHARS` + validator), `PICTORIAL_PAGE_MODE`/`describe_pdf_page` seam + `render_pdf_pages` page selector + mock branch, `_pictorial_pdf_pass` in `_index_pdf_file` with log-only counters, dedicated E2E. No code changes needed.
- Test results (exact commands):
  - `uv run pytest --cov=app --cov-report=term-missing` → **3320 passed, 11 skipped, 0 failed; TOTAL 99%** (>90% gate)
  - `uv run pytest tests/e2e/test_pictorial_pdf.py -v --no-cov` → **3 passed in isolation** (Playwright, mock-only)
  - Pre-existing suites unedited: `test_pdf_support.py` 6 passed; `test_garbage_pdf.py` 2 passed + 1 skipped (owner fixture); `test_pdf_image_context.py` 4 passed
  - `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors
- Completion criteria:
  1. IKEA tar import repro — pinned by 2 integration tests; **skipped loudly here** (`test_sources/ikea_instructions.tar` absent on this machine — verified repo-wide; same house convention as completed phase-133 HNB tests; runs real on the owner's machine). Equivalent logic fully covered by unit pins on synthetic IKEA-class (vector-drawn, zero-raster) PDFs.
  2. Chat grounded on described page — **green** (E2E test b: phrase-only-in-illustration question, PDF in `done.sources`, answer echoes the block)
  3. Clean-text no-op (zero vision calls) + garbage precedence pin — **green** (`test_zero_candidates_is_a_noop` unit; `test_garbage_pdf_with_pictorial_on_takes_the_fallback_not_the_pass` integration, verified alone)
  4. `BOR_PDF_PICTORIAL=0` byte-identical — **green** (unit pin on synthetic PDF; IKEA variant runs on owner's machine)
  5. Full gate + E2E isolation + pre-existing suites — **green** (above)
  6. ruff + pyright — **clean**
  7. Commit/phase-move — delegated to harness per run rules (left in working tree: 16 modified + 8 new files, uncommitted)
- Deviations: none beyond the expected owner-fixture skips (11, all pre-existing fixture-missing skips, including 2 IKEA).
- Next pending phase: **none** — `todo/` holds only 140; 139 is already complete.
2026-09-30 19:16:35 -04:00

197 lines
8.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Unit tests: the pictorial PDF gate (phase 140, task 01, LOCKED A1/A3).
Covers the two new settings (``pdf_pictorial`` — the house kill-switch,
default ON — and ``pdf_pictorial_min_chars`` — the sparseness bar with
its fail-loud validator) and the pure, deterministic sparse-page gate
:func:`app.rag.pdf.pictorial_pages`: the ``<``/``=``/``>`` boundary,
whitespace-only pages, the empty-dict no-op, ascending order, and a
large dict. The IKEA docstring example is pinned against the named
threshold.
"""
from __future__ import annotations
from typing import Any
import pytest
from pydantic import ValidationError
from app.config import Settings
from app.rag.pdf import pictorial_pages
def _settings(**kwargs: Any) -> Settings:
"""Build Settings without reading a .env file (deterministic tests)."""
kwargs.setdefault("_env_file", None)
return Settings(**kwargs) # pyright: ignore[reportCallIssue] (kwarg exists at runtime)
# ---------------------------------------------------------------------------
# The settings — defaults + env overrides
# ---------------------------------------------------------------------------
def test_pdf_pictorial_defaults_on_and_env_override(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""LOCKED A3: the pass is ON by default (the owner-confirmed
2026-09-30 default); ``BOR_PDF_PICTORIAL`` is the house kill-switch
(``0``/``false`` disables the pass entirely — the ``BOR_IMAGES``
naming pattern)."""
monkeypatch.delenv("BOR_PDF_PICTORIAL", raising=False)
assert _settings().pdf_pictorial is True
monkeypatch.setenv("BOR_PDF_PICTORIAL", "0")
assert _settings().pdf_pictorial is False
monkeypatch.setenv("BOR_PDF_PICTORIAL", "false")
assert _settings().pdf_pictorial is False
monkeypatch.setenv("BOR_PDF_PICTORIAL", "1")
assert _settings().pdf_pictorial is True
def test_pdf_pictorial_min_chars_defaults_to_100_and_env_override(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""LOCKED A1: the default sparseness bar is 100 stripped chars
(the IKEA fixture's sparse pages run 4–73, its text-heavy pages
3,091+ — 100 sits between the classes); ``BOR_PDF_PICTORIAL_MIN_CHARS``
overrides it."""
monkeypatch.delenv("BOR_PDF_PICTORIAL_MIN_CHARS", raising=False)
assert _settings().pdf_pictorial_min_chars == 100
monkeypatch.setenv("BOR_PDF_PICTORIAL_MIN_CHARS", "50")
assert _settings().pdf_pictorial_min_chars == 50
monkeypatch.setenv("BOR_PDF_PICTORIAL_MIN_CHARS", "0")
assert _settings().pdf_pictorial_min_chars == 0
# ---------------------------------------------------------------------------
# The validators — the fail-loud pattern
# ---------------------------------------------------------------------------
def test_pdf_pictorial_min_chars_zero_is_legal() -> None:
"""PLAN A21: ``0`` is the every-page-is-a-candidate mode (the LLM
calls are free) — it must NOT raise."""
assert _settings(pdf_pictorial_min_chars=0).pdf_pictorial_min_chars == 0
def test_pdf_pictorial_min_chars_default_is_legal() -> None:
"""The default (100) is trivially legal — the constructor must
accept it (guards a validator regression that would block startup)."""
assert _settings().pdf_pictorial_min_chars == 100
def test_pdf_pictorial_min_chars_negative_fails_loud_naming_the_field() -> None:
"""A negative threshold is a typo — the validator fails startup
loudly (the ``agent_max_rounds`` pattern), naming the field."""
with pytest.raises(ValidationError, match="pdf_pictorial_min_chars"):
_settings(pdf_pictorial_min_chars=-1)
# ---------------------------------------------------------------------------
# pictorial_pages — the pure, deterministic gate
# ---------------------------------------------------------------------------
def test_pictorial_pages_strict_less_than_boundary() -> None:
"""The ASSUMPTION's boundary, pinned from all three sides at
``min_chars=5``: 4 stripped chars IS a candidate (``<``), EXACTLY 5
is NOT (``=`` — a page with exactly the bar's stripped chars is not
sparse), and 6 is NOT (``>``)."""
page_texts = {
1: "abcd", # 4 < 5 → candidate
2: "abcde", # 5 == 5 → NOT a candidate (the pinned boundary)
3: "abcdef", # 6 > 5 → NOT a candidate
}
assert pictorial_pages(page_texts, min_chars=5) == [1]
def test_pictorial_pages_min_chars_zero_marks_every_page() -> None:
"""PLAN A21's special case: ``min_chars=0`` is the "describe
everything" mode — EVERY page in the map is a candidate (the strict
``<`` test cannot express this: no length is below 0, so 0 is not a
stricter bar); an empty map still yields ``[]``."""
assert pictorial_pages({1: "a", 2: "x" * 7000}, min_chars=0) == [1, 2]
# A whitespace-only entry (defensive — the extractor's map never
# holds one) is still a candidate: the 0 branch is ALL KEYS.
assert pictorial_pages({1: " "}, min_chars=0) == [1]
assert pictorial_pages({}, min_chars=0) == []
def test_pictorial_pages_whitespace_only_page_is_a_candidate() -> None:
"""A whitespace-only page (strips to nothing — 0 < any positive bar)
is a candidate: the gate counts STRIPPED chars, so padding cannot
mask sparseness. (A truly text-LESS page has no key in the map at
all — the importer's pass owns that case.)"""
assert pictorial_pages({3: " \n\t \r\n "}, min_chars=100) == [3]
def test_pictorial_pages_empty_dict_yields_empty_list() -> None:
"""An empty map (no readable pages at all) yields ``[]`` — the
clean-text PDF's zero-candidates no-op (no render, no vision
call)."""
assert pictorial_pages({}, min_chars=100) == []
def test_pictorial_pages_result_is_ascending_even_when_input_isnt() -> None:
"""The result is ASCENDING (the render/describe order) regardless of
the dict's insertion order — the unsorted candidate set
{9, 2, 50, 5} comes back as [2, 5, 9, 50], and the one non-candidate
(the 210-char page 12) stays out."""
page_texts = {
9: "tail marker",
2: "head marker",
12: "not sparse " + "x" * 200, # 12+? — long enough, excluded
50: "Illustration 50",
5: "mid marker",
}
assert pictorial_pages(page_texts, min_chars=100) == [2, 5, 9, 50]
def test_pictorial_pages_large_dict() -> None:
"""A 500-page map with every 10th page sparse (an IKEA-class shape
at scale): exactly the sparse pages come back, ascending, and the
450 text-heavy pages are excluded — the pure function handles a
whole book in one call (no cap, LOCKED A3/A21)."""
page_texts: dict[int, str] = {}
expected: list[int] = []
for page in range(1, 501):
if page % 10 == 0:
page_texts[page] = "Illustration " + str(page) # 11-13 chars
expected.append(page)
else:
page_texts[page] = "w" * 3000 # text-heavy, never a candidate
assert len(page_texts) == 500 and len(expected) == 50
assert pictorial_pages(page_texts, min_chars=100) == expected
def test_pictorial_pages_ikea_example_from_the_docstring() -> None:
"""The docstring's IKEA example, pinned against the named threshold
(LOCKED A1, default 100): the title page ("BESTÅ", 5 chars) and the
"Illustration N" marker pages ARE candidates; the 7 KB of
multilingual warnings is NOT. A title page is a candidate —
describing it is useful (the logo + product name) and never
harmful."""
page_texts = {
1: "BESTÅ", # 5 chars → candidate
2: "w" * 7000, # 7 KB warnings → NOT
6: "Illustration 6", # 14 chars → candidate
}
assert pictorial_pages(page_texts, min_chars=100) == [1, 6]
def test_pictorial_pages_is_pure_and_does_not_mutate_input() -> None:
"""Pure + deterministic: repeated calls agree, and the input map
survives untouched (the gate reads, never rewrites)."""
page_texts = {3: "marker", 1: "x" * 500, 2: " "}
original = dict(page_texts)
first = pictorial_pages(page_texts, min_chars=100)
assert pictorial_pages(page_texts, min_chars=100) == first
assert page_texts == original
def test_pictorial_pages_uses_stripped_length_defensively() -> None:
"""Defensive strip: an UNSTRIPPED map entry (a caller that did not
pre-strip) is judged on its stripped length — padding on either
side cannot push a sparse page past the bar."""
assert pictorial_pages({1: " " + "y" * 98 + " "}, min_chars=100) == [1]
assert pictorial_pages({1: " " + "y" * 100 + " "}, min_chars=100) == []