Files
brain-of-reese/tests/conftest.py
T
ducoterra 12fa69b7ea
Build and Push Containers / build-and-push-app (push) Successful in 3m34s
Build and Push Containers / build-and-push-db (push) Successful in 23s
phase: 152_sync_progress_image_pass
All criteria verified. The CLI path is hook-less (only `app/api/sync.py` passes the hook), and the byte-identity is pinned by `test_on_image_progress_none_run_is_byte_identical`.

## Final Report — Phase 152 final verification pass

- **Verified (no new code needed):** `SyncStatus.images_done/images_total` + reset/clear rules in `app/api/sync.py`; `on_image_progress` threaded through the importer's three PDF vision spans with the continuous per-file counter; the `_image_hook` closure; `sources.js` `· image n/m` label part (box + title + aria).
- **C1** status climbs 1→N + UI shows `· image n/m`: ✅ unit 3/15→15/15, integration 1/15→7/15, E2E 1/4→4/4 (title/aria ride the full string)
- **C2** never frozen: ✅ E2E asserts ≥3 distinct positions, starting at 1, non-decreasing, ≤ total, reaching it
- **C3** cleared at overview/summaries + all terminals; per-file reset via `_hook`; no stale position for image-less files: ✅ unit pins + E2E settle (image part gone in box/title/aria, endpoint 0/0)
- **C4** `/api/sync/status` additive-only: ✅ full-dict pin passes (all pre-existing keys unchanged + the two new 0/0 keys)
- **C5** hook-less caller byte-identical: ✅ CLI passes no hook; `test_on_image_progress_none_run_is_byte_identical` green
- **C6** `uv run pytest`: **3526 passed, exit 0** (95s); dedicated E2E in isolation: **2 passed** (95s); `uv run ruff check . && uv run pyright`: **clean**; coverage: **99% on `app/` (>90% gate)**
- Regression: all five neighbouring E2E suites green in isolation (progress_box 8, chunk_progress 2, label_mobile 4, pdf_support 6, image_documents 4)
- **C7** atomic commit: left to the harness (no git index/history touched per rules)
- **Docs:** README.md — sync-label paragraph already updated this phase, accurately; all `.env.example` vars documented; routes/commands verified — **no changes needed**. AGENTS.md — all references (PLAN, user_stories, test_smoke.py, .env.example, gpg config, .gitignore) verified — **no changes needed**
- **Defects found in prior work:** none (one prior env-leak defect was already fixed inside this phase's task 04, conftest)
- No live-infrastructure touched; no ntfy used. **Next pending phase:** none — `todo/` held only this phase
2026-10-03 01:52:41 -04:00

141 lines
6.7 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 152 (task 04, defect fix): the same leak class for the display
# name — an operator's local ``.env`` may legitimately carry
# ``BOR_APP_NAME`` (the dev environment runs a renamed build), and the
# default-metadata pins (``/api/config`` ``app_name``, the page titles
# + header brand) must see the code default.
os.environ["BOR_APP_NAME"] = _Settings.model_fields["app_name"].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()