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>
106 lines
4.2 KiB
Python
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"}
|