Files
photoanalyzer/tests/characterization/test_webapp_query.py
domverse c0a816ac80 US01-01: replace donor characterization iteration with YAML ledger + full suite
Merge the wrong-folder work into the repo: donor_ledger.yaml (44 rows, 12
required areas) and the characterization suite under tests/characterization/
(61 tests incl. ledger lint). Drop the superseded first iteration
(donor_ledger/ledger.json, test_donors/test_ledger/traceability, fixtures).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 16:06:03 +02:00

106 lines
4.2 KiB
Python

"""Characterize webapp read layer (donor: query._fts_query, _where, search,
facets, stats, photo, all_paths) against a seeded temp DB."""
from pathlib import Path
import pytest
import photo_analyzer as pa
from webapp import query
from conftest import seed_analyzed
def test_fts_query_sanitization_goldens():
assert query._fts_query("beach sun") == '"beach"* "sun"*'
assert query._fts_query('AND OR "quoted"') == '"AND"* "OR"* "quoted"*'
assert query._fts_query("!!! ???") is None, "pure punctuation can't crash MATCH"
assert query._fts_query("") is None
assert query._fts_query("Straße") == '"Straße"*', "unicode words survive"
def test_where_clause_goldens():
clauses, params = query._where({
"setting": "indoor", "people": "3+", "year_min": "2010",
"has_location": "1"})
assert clauses == [
"p.setting = ?", "p.people_count >= 3", "p.approx_year >= ?",
"p.location_hint IS NOT NULL AND TRIM(p.location_hint) != '' "
"AND LOWER(p.location_hint) != 'null'"]
assert params == ["indoor", 2010]
assert query._where({}) == ([], [])
@pytest.fixture
def seeded(db):
seed_analyzed(db, "/lib/album_a/beach.jpg",
description="A sunny beach with palm trees.",
tags=["beach", "palm"], setting="outdoor", people_count=2,
approx_year=2015, location_hint="Rome, Italy")
seed_analyzed(db, "/lib/album_a/city.jpg",
description="A night city street.", tags=["city"],
setting="outdoor", time_of_day="night", people_count=0,
approx_year=2011)
seed_analyzed(db, "/lib/album_b/dinner.jpg",
description="Family dinner at home.", tags=["family", "dinner"],
setting="indoor", people_count=4)
pa.mark_exif_written(db, "/lib/album_b/dinner.jpg")
pa.upsert_pending(db, "/lib/album_b/err.jpg")
pa.mark_error(db, "/lib/album_b/err.jpg", "timeout")
return db
def test_search_fts_prefix_match(seeded):
out = query.search(seeded, q="beach")
assert out["total"] == 1
assert out["rows"][0]["path"] == "/lib/album_a/beach.jpg"
assert out["rows"][0]["tags"] == ["beach", "palm"], "tags decoded from JSON"
assert query.search(seeded, q="bea")["total"] == 1, "prefix term"
def test_search_filters_and_browse(seeded):
assert query.search(seeded)["total"] == 4, "empty query = browse all"
assert query.search(seeded, filters={"setting": "indoor"})["total"] == 1
assert query.search(seeded, filters={"people": "3+"})["total"] == 1
assert query.search(seeded, filters={"year_min": 2012})["total"] == 1
assert query.search(seeded, filters={"has_location": "1"})["total"] == 1
assert query.search(seeded, q="city", filters={"setting": "indoor"})["total"] == 0, \
"search ANDs with filters"
def test_search_paging(seeded):
page = query.search(seeded, limit=2, offset=2)
assert page["total"] == 4
assert len(page["rows"]) == 2
assert page["offset"] == 2
def test_photo_omits_raw_response(seeded):
d = query.photo(seeded, "/lib/album_a/beach.jpg")
assert d["description"] == "A sunny beach with palm trees."
assert "raw_response" not in d
assert query.photo(seeded, "/nope.jpg") is None
def test_facets_and_albums(seeded):
f = query.facets(seeded, Path("/lib"))
assert (f["year_min"], f["year_max"]) == (2011, 2015)
assert {a["album"]: a["count"] for a in f["albums"]} == {
"album_a": 2, "album_b": 2}
assert {v["value"]: v["count"] for v in f["setting"]} == {
"outdoor": 2, "indoor": 1}
def test_stats_album_progress_and_errors(seeded):
s = query.stats(seeded, Path("/lib"))
assert s["total"] == 4
assert s["status"] == {"analyzed": 2, "exif_written": 1, "error": 1}
albums = {a["album"]: (a["done"], a["total"]) for a in s["albums"]}
assert albums == {"album_a": (2, 2), "album_b": (1, 2)}, \
"done counts analyzed + exif_written"
assert s["errors"] == [{"path": "/lib/album_b/err.jpg", "error": "timeout"}]
def test_all_paths_is_the_image_endpoint_allowlist(seeded):
assert query.all_paths(seeded) == {
"/lib/album_a/beach.jpg", "/lib/album_a/city.jpg",
"/lib/album_b/dinner.jpg", "/lib/album_b/err.jpg"}