US01-01 follow-up: replace donor characterization iteration with YAML ledger + full suite (#46)
This commit was merged in pull request #46.
This commit is contained in:
107
tests/characterization/conftest.py
Normal file
107
tests/characterization/conftest.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""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]
|
||||
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
|
||||
Reference in New Issue
Block a user