Files
brain-of-reese/tests/conftest.py
T
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

135 lines
6.3 KiB
Python
Raw 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.
"""Shared fixtures for unit + integration tests."""
from __future__ import annotations
import json
import os
from collections.abc import Iterator
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
# In-process integration/E2E tests drive the app with *mock* embeddings
# (bag-of-words, cosine ~0.1–0.8), not the live aipi model — so the honesty
# gate is calibrated to the mock's distribution, mirroring tests/e2e/
# conftest.py. Must be set before ``app.main`` (below) caches settings.
# The production default stays 0.62 (app/config.py, A8 revised).
os.environ.setdefault("BOR_RELEVANCE_THRESHOLD", "0.30")
# A8 revised 2026-09-14: lexical_support_floor must be <= relevance_threshold.
os.environ.setdefault("BOR_LEXICAL_SUPPORT_FLOOR", "0.15")
# Phase 113: the source usefulness bar (citation slot) is calibrated to
# the mock's compressed distribution, like the threshold above — the
# weakest GROUNDED fixture cosine in the in-process suites (the
# kafkabridge question's static-dns.json, ~0.055) must stay citable so
# the pre-phase citation pins hold, while the bar is ON so endpoint
# tests exercise the tier (tests that pin bar-specific behavior set a
# higher floor explicitly, the BOR_LEXICAL_SUPPORT_FLOOR pattern).
# Production default stays 0.35 (app/config.py, LOCKED A2).
os.environ.setdefault("BOR_SOURCE_USEFULNESS_FLOOR", "0.02")
# Phase 16: single-admin auth is fail-loud — create_app() refuses to boot
# without both vars, and app.main (imported below) builds the app at
# import time. Set known test values first, same pattern as the threshold.
ADMIN_PASSWORD = "test-admin-password"
SESSION_SECRET = "test-session-secret-0123456789abcdef0123456789abcdef"
os.environ.setdefault("BOR_ADMIN_PASSWORD", ADMIN_PASSWORD)
os.environ.setdefault("BOR_SESSION_SECRET", SESSION_SECRET)
# Phase 61 (defect fix): the app under test must see the code DEFAULTS,
# not an operator's local (gitignored) ``.env`` — ``Settings`` loads
# ``env_file=".env"`` from the repo root, and a machine-specific corpus
# (e.g. ``BOR_SUGGESTIONS``, ``BOR_DOCS_REPO``) leaked into the tests
# broke the default-metadata pins. pydantic-settings ranks process env
# vars ABOVE the ``.env`` file, so force the defaults explicitly here,
# before ``app.main`` (below) caches settings. The suggestions default
# is derived from the class field so this can never drift from
# ``app/config.py``; docs-push stays inert (empty repo).
from app.config import Settings as _Settings # noqa: E402
os.environ["BOR_DOCS_REPO"] = ""
os.environ["BOR_SUGGESTIONS"] = json.dumps(_Settings.model_fields["suggestions"].default)
# Phase 62: the same leak class for the new UI customization settings —
# an operator's local ``.env`` may legitimately carry
# ``BOR_INPUT_PLACEHOLDER`` / ``BOR_FOOTER_TEXT``, and the
# default-metadata pins must see the code defaults (derived from
# the class fields, same pattern as the suggestions line above).
# (Phase 91, task 03: the retired CSS-file theme env var no longer
# exists — nothing to pin.)
os.environ["BOR_INPUT_PLACEHOLDER"] = _Settings.model_fields["input_placeholder"].default
os.environ["BOR_FOOTER_TEXT"] = _Settings.model_fields["footer_text"].default
# Phase 106 (task 07): the same leak class for the recency-boost
# settings — an operator's local ``.env`` may legitimately carry
# ``BOR_RECENCY_BOOST`` / ``BOR_RECENCY_HALF_LIFE_DAYS`` re-tuned live,
# and the fine-line integration battery pins the CODE DEFAULTS
# (derived from the class fields, same pattern as the lines above).
os.environ["BOR_RECENCY_BOOST"] = str(_Settings.model_fields["recency_boost"].default)
os.environ["BOR_RECENCY_HALF_LIFE_DAYS"] = str(
_Settings.model_fields["recency_half_life_days"].default
)
# Phase 122 (task 01): the same leak class for the image-document
# toggle — an operator's local ``.env`` may legitimately carry
# ``BOR_IMAGES``/``BOR_IMAGE_EXTENSIONS``/``BOR_IMAGE_DIR``, and the
# "off by default" pins (the ``/api/config`` ``images`` flag, the
# walk's images-off behavior) must see the code defaults.
os.environ["BOR_IMAGES"] = str(_Settings.model_fields["images"].default)
os.environ["BOR_IMAGE_EXTENSIONS"] = str(_Settings.model_fields["image_extensions"].default)
os.environ["BOR_IMAGE_DIR"] = str(_Settings.model_fields["image_dir"].default)
# Phase 124 (task 04): the same leak class for the answer-image toggle
# — an operator's local ``.env`` may legitimately carry
# ``BOR_ANSWER_IMAGES`` (the dev environment runs the feature on), and
# the byte-identical-off pins (the ``show_image`` offer absent, the
# ``answer_image`` done-frame key absent) must see the code default.
os.environ["BOR_ANSWER_IMAGES"] = str(_Settings.model_fields["answer_images"].default)
# Phase 140: the same leak class for the pictorial-PDF toggle +
# threshold — an operator's shell may carry BOR_PDF_PICTORIAL /
# BOR_PDF_PICTORIAL_MIN_CHARS (the owner runs the feature on in
# production), and the task-01 default pins (LOCKED A3 default-on,
# LOCKED A1 default-100) and the task-03 pass tests must see the code
# defaults.
os.environ["BOR_PDF_PICTORIAL"] = str(_Settings.model_fields["pdf_pictorial"].default)
os.environ["BOR_PDF_PICTORIAL_MIN_CHARS"] = str(
_Settings.model_fields["pdf_pictorial_min_chars"].default
)
from app.db import SessionLocal, db_available # noqa: E402
from app.main import app as fastapi_app # noqa: E402
@pytest.fixture()
def client() -> TestClient:
return TestClient(fastapi_app)
@pytest.fixture()
def admin_client(client: TestClient) -> TestClient:
"""A client signed in as the single admin (phase 16).
TestClient keeps its cookie jar across requests, so one login covers
every subsequent request of the test. Use it for the admin-only
surface (``GET /api/docs``, ``/api/steering``).
"""
r = client.post("/api/login", json={"password": ADMIN_PASSWORD})
assert r.status_code == 204, f"admin login failed: {r.status_code} {r.text}"
return client
@pytest.fixture()
def db() -> Iterator[Session]:
"""Real Postgres session (``podman compose up -d db``).
Skips with clear instructions when the database is not running, so the
suite degrades gracefully on a machine without the stack started.
"""
if not db_available():
pytest.skip("Postgres not reachable — run `podman compose up -d db` first")
session = SessionLocal()
try:
yield session
finally:
session.close()