Files
photoanalyzer/tests/characterization/conftest.py

113 lines
3.9 KiB
Python

"""Shared fixtures for donor characterization tests (US01-01).
Builds a small deterministic synthetic photo library in a temp dir — never the
real library, never _IGNORE/ contents. Every image has a stable logical fixture
ID (FX_*) so captured outputs stay keyed to identity, not to paths.
"""
import shutil
import sqlite3
import sys
from pathlib import Path
import numpy as np
import pytest
from PIL import Image
REPO = Path(__file__).resolve().parents[2]
# The donors are frozen in the read-only archive (US07-01). Only this suite — and
# the parity test that compares against them — puts that directory on sys.path;
# production never does, which tests/unit/test_legacy_archive.py enforces.
ARCHIVED_SOURCES = REPO / "legacy_cli_archive" / "src"
sys.path.insert(0, str(ARCHIVED_SOURCES))
sys.path.insert(0, str(REPO))
EXIFTOOL = shutil.which("exiftool")
# Stable logical fixture IDs → deterministic generation recipe (seed, size).
# Structured low-frequency content (blocks + gradient) so phash survives resize.
FIXTURES = {
"fx-blocks-01": {"seed": 1, "size": (800, 600)},
"fx-blocks-02": {"seed": 2, "size": (800, 600)},
"fx-blocks-03": {"seed": 3, "size": (640, 480)},
"fx-blocks-04": {"seed": 4, "size": (800, 600)},
}
def make_image_array(fixture_id: str) -> np.ndarray:
spec = FIXTURES[fixture_id]
w, h = spec["size"]
rng = np.random.default_rng(spec["seed"])
base = np.zeros((h, w, 3), dtype=np.uint8)
for _ in range(6):
x0 = int(rng.integers(0, w - 150))
y0 = int(rng.integers(0, h - 150))
col = rng.integers(0, 256, 3)
base[y0:y0 + 150, x0:x0 + 150] = col
grad = np.linspace(0, 120, w, dtype=np.uint8)
base[:, :, 0] = np.clip(base[:, :, 0].astype(int) + grad[None, :], 0, 255)
return base
def write_fixture(fixture_id: str, path: Path, quality: int = 95):
path.parent.mkdir(parents=True, exist_ok=True)
Image.fromarray(make_image_array(fixture_id)).save(path, quality=quality)
return path
@pytest.fixture
def img(tmp_path):
"""One fx-blocks-01 JPEG in a temp dir."""
return write_fixture("fx-blocks-01", tmp_path / "fx-blocks-01.jpg")
@pytest.fixture
def library(tmp_path):
"""Temp library:
root/fx-blocks-01.jpg
root/album_a/fx-blocks-02.jpg
root/album_a/fx-blocks-02.PNG (uppercase ext, distinct file)
root/album_b/fx-blocks-03.jpg
root/_IGNORE/secret.jpg (must never be discovered)
root/album_b/.@__thumb/thumb.jpg (must never be discovered)
root/notes.txt (unsupported)
"""
root = tmp_path / "lib"
write_fixture("fx-blocks-01", root / "fx-blocks-01.jpg")
write_fixture("fx-blocks-02", root / "album_a" / "fx-blocks-02.jpg")
img = Image.fromarray(make_image_array("fx-blocks-02"))
(root / "album_a").mkdir(parents=True, exist_ok=True)
img.save(root / "album_a" / "fx-blocks-02.PNG")
write_fixture("fx-blocks-03", root / "album_b" / "fx-blocks-03.jpg")
write_fixture("fx-blocks-04", root / "_IGNORE" / "secret.jpg")
write_fixture("fx-blocks-04", root / "album_b" / ".@__thumb" / "thumb.jpg")
(root / "notes.txt").write_text("not an image")
return root
@pytest.fixture
def db(tmp_path):
import photo_analyzer as pa
conn = pa.get_db(str(tmp_path / "test.db"))
yield conn
conn.close()
def seed_analyzed(conn: sqlite3.Connection, path: str, **overrides):
"""Insert a row and mark it analyzed with a deterministic result."""
import photo_analyzer as pa
result = {
"description": "A red block pattern.",
"tags": ["blocks", "test"],
"people_count": 0,
"setting": "indoor",
"time_of_day": "unknown",
"season": "unknown",
"mood": "calm",
"location_hint": None,
"approx_year": None,
}
result.update(overrides)
pa.upsert_pending(conn, path)
pa.mark_analyzed(conn, path, result, raw="{}")
return result