| """Tests for the admin dataset endpoints (spec §2) incl. transactional cascade purge.""" |
| import os |
| from pathlib import Path |
|
|
| from sqlalchemy import select |
|
|
| from app.storage import get_storage |
|
|
| from app.db import SessionLocal |
| from app.models import Case, Dataset, Embedding, KnownDog, Match, Picture, UnknownDog, User |
| from app.models.base import SubjectType |
| from app.services.batch_loader import load_dataset |
| from scripts.make_sample_images import make_image |
| from scripts.prepare_test_data import prepare |
| from tests.conftest import auth |
|
|
|
|
| def _build_input(tmp: Path, layout: dict[str, int], *, identical: bool = False) -> Path: |
| root = tmp / "input" |
| for folder, count in layout.items(): |
| d = root / folder |
| d.mkdir(parents=True) |
| shared = make_image(abs(hash(folder)) % 1000) |
| for i in range(count): |
| (d / f"img{i}.jpg").write_bytes(shared if identical else make_image((abs(hash(folder)) + i) % 1000)) |
| return root |
|
|
|
|
| def _load(folder: Path, csv_name: str, dtype: str, name: str, **kw) -> int: |
| db = SessionLocal() |
| try: |
| res = load_dataset(db, folder=folder, dataset_type=dtype, name=name, |
| description=None, csv_path=folder / csv_name, **kw) |
| return res.dataset_id |
| finally: |
| db.close() |
|
|
|
|
| def test_list_requires_admin(client, owner_token): |
| assert client.get("/admin/datasets", headers=auth(owner_token)).status_code == 403 |
|
|
|
|
| def test_list_and_detail(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2, "dogB": 2}) |
| prepare(root, root, holdout=1, seed=1) |
| dsid = _load(root, "known_dogs.csv", "known", "My Known", mark_lost=True, mark_lost_pct=100) |
|
|
| lst = client.get("/admin/datasets", headers=auth(admin_token)) |
| assert lst.status_code == 200 |
| row = next(d for d in lst.json() if d["id"] == dsid) |
| assert row["name"] == "My Known" |
| assert row["type"] == "known" |
| assert row["dog_count"] == 2 |
| assert row["case_count"] == 2 |
|
|
| detail = client.get(f"/admin/datasets/{dsid}", headers=auth(admin_token)) |
| assert detail.status_code == 200 |
| stats = detail.json()["stats"] |
| assert stats["known_dog_count"] == 2 |
| assert stats["case_count"] == 2 |
| assert stats["picture_count"] == 2 |
|
|
|
|
| def test_image_dataset_lookup(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}) |
| prepare(root, root, holdout=1, seed=1) |
| dsid = _load(root, "known_dogs.csv", "known", "K") |
| db = SessionLocal() |
| pic_id = db.execute(select(Picture.id)).scalars().first() |
| db.close() |
| r = client.get(f"/admin/image/{pic_id}/dataset", headers=auth(admin_token)) |
| assert r.status_code == 200 |
| assert r.json()["dataset_id"] == dsid |
| assert r.json()["dataset_name"] == "K" |
|
|
|
|
| def test_embed_all_idempotent_then_reembeds(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}) |
| prepare(root, root, holdout=1, seed=1) |
| dsid = _load(root, "known_dogs.csv", "known", "K") |
|
|
| |
| r1 = client.post(f"/admin/datasets/{dsid}/embed-all", headers=auth(admin_token)).json() |
| assert r1["embedded"] == 0 |
| assert r1["skipped"] == r1["total_pictures"] >= 1 |
|
|
| |
| db = SessionLocal() |
| db.execute(Embedding.__table__.delete()) |
| db.commit() |
| db.close() |
| r2 = client.post(f"/admin/datasets/{dsid}/embed-all", headers=auth(admin_token)).json() |
| assert r2["embedded"] == r2["total_pictures"] >= 1 |
| assert r2["errors"] == 0 |
|
|
|
|
| def test_embed_all_reports_progress(tmp_path): |
| """The service streams progress events and commits incrementally over the run.""" |
| from app.models import Dataset |
| from app.services import datasets as ds |
|
|
| root = _build_input(tmp_path, {"dogA": 3, "dogB": 3}) |
| prepare(root, root, holdout=1, seed=1) |
| dsid = _load(root, "known_dogs.csv", "known", "K", skip_embeddings=True) |
|
|
| events: list[dict] = [] |
| db = SessionLocal() |
| try: |
| dataset = db.get(Dataset, dsid) |
| result = ds.embed_all(db, dataset, progress=events.append, commit_every=2) |
| finally: |
| db.close() |
|
|
| assert result["embedded"] == result["total_pictures"] >= 4 |
| assert events[0]["processed"] == 0 |
| assert events[-1]["processed"] == result["total_pictures"] |
| assert all(e["total"] == result["total_pictures"] for e in events) |
| assert [e["processed"] for e in events] == sorted(e["processed"] for e in events) |
|
|
|
|
| def test_embed_all_job_runs_in_background(client, admin_token, tmp_path): |
| """POST embed-all-job returns a job id; polling it reaches done with the encode counts.""" |
| import time |
|
|
| root = _build_input(tmp_path, {"dogA": 2}) |
| prepare(root, root, holdout=1, seed=1) |
| dsid = _load(root, "known_dogs.csv", "known", "K", skip_embeddings=True) |
|
|
| start = client.post(f"/admin/datasets/{dsid}/embed-all-job", headers=auth(admin_token)) |
| assert start.status_code == 202 |
| job_id = start.json()["job_id"] |
|
|
| job = None |
| for _ in range(50): |
| job = client.get(f"/admin/jobs/{job_id}", headers=auth(admin_token)).json() |
| if job["status"] in ("done", "error"): |
| break |
| time.sleep(0.1) |
| assert job and job["status"] == "done", job |
| assert job["result"]["embedded"] == job["result"]["total_pictures"] >= 1 |
| assert client.post("/admin/datasets/9999/embed-all-job", headers=auth(admin_token)).status_code == 404 |
|
|
|
|
| def test_match_endpoint_creates_matches(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K", mark_lost=True, mark_lost_pct=100) |
| u_id = _load(root, "found_dogs.csv", "unknown", "U") |
|
|
| r = client.post(f"/admin/datasets/{u_id}/match", headers=auth(admin_token)) |
| assert r.status_code == 200 |
| assert r.json()["matches_created"] >= 1 |
|
|
|
|
| def test_purge_cascade_deletes_everything(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| k_id = _load(root, "known_dogs.csv", "known", "K", mark_lost=True, mark_lost_pct=100) |
| u_id = _load(root, "found_dogs.csv", "unknown", "U") |
| client.post(f"/admin/datasets/{u_id}/match", headers=auth(admin_token)) |
|
|
| |
| db = SessionLocal() |
| assert db.execute(select(Match)).scalars().first() is not None |
| db.close() |
|
|
| |
| res = client.delete(f"/admin/datasets/{u_id}", headers=auth(admin_token)) |
| assert res.status_code == 200 |
| deleted = res.json()["deleted"] |
| assert deleted["unknown_dogs"] == 1 |
| assert deleted["cases"] == 1 |
| assert deleted["matches"] >= 1 |
| assert deleted["datasets"] == 1 |
|
|
| db = SessionLocal() |
| assert db.execute(select(UnknownDog).where(UnknownDog.dataset_id == u_id)).scalars().first() is None |
| assert db.execute(select(Match)).scalars().first() is None |
| assert db.get(Dataset, u_id) is None |
| db.close() |
|
|
| |
| res2 = client.delete(f"/admin/datasets/{k_id}", headers=auth(admin_token)) |
| deleted2 = res2.json()["deleted"] |
| |
| assert deleted2["known_dogs"] == 1 |
| assert deleted2["users"] == 1 |
| assert deleted2["cases"] == 1 |
|
|
| db = SessionLocal() |
| assert db.execute(select(KnownDog).where(KnownDog.dataset_id == k_id)).scalars().first() is None |
| assert db.execute(select(User).where(User.dataset_id == k_id)).scalars().first() is None |
| |
| assert db.execute(select(Picture)).scalars().first() is None |
| db.close() |
|
|
|
|
| def test_admin_all_cases_and_owners(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K", mark_lost=True, mark_lost_pct=100) |
|
|
| cases = client.get("/admin/cases", headers=auth(admin_token)).json() |
| assert cases["total"] >= 1 |
| row = next(c for c in cases["items"] if c["type"] == "lost") |
| assert row["dog"]["kind"] == "known" and row["person"] is not None |
| assert "match_count" in row |
| |
| assert all(c["type"] == "lost" for c in |
| client.get("/admin/cases?kind=lost", headers=auth(admin_token)).json()["items"]) |
|
|
| owners = client.get("/admin/owners", headers=auth(admin_token)).json() |
| assert owners["total"] >= 1 |
| assert owners["items"][0]["dog_count"] >= 1 and owners["items"][0]["dogs"] |
| |
| assert client.get("/admin/cases").status_code in (401, 403) |
|
|
|
|
| def test_admin_set_dog_status(client, admin_token, owner_token): |
| |
| client.post( |
| "/cases/found", |
| data={ |
| "event_zip": "20001", "event_date": "2026-06-03", "finder_email": "f@example.com", |
| "current_location_detail": "Happy Paws Shelter", |
| }, |
| files={"files": ("f.jpg", make_image(5), "image/jpeg")}, |
| ) |
| uid = client.get("/admin/dogs?kind=unknown", headers=auth(admin_token)).json()["items"][0]["id"] |
|
|
| r = client.post( |
| f"/admin/dogs/unknown/{uid}/status", headers=auth(admin_token), json={"status": "reunited"} |
| ) |
| assert r.status_code == 200 and r.json()["profile"]["status"] == "reunited" |
|
|
| |
| assert client.post( |
| f"/admin/dogs/unknown/{uid}/status", headers=auth(admin_token), json={"status": "home"} |
| ).status_code == 400 |
| |
| assert client.post( |
| f"/admin/dogs/banana/{uid}/status", headers=auth(admin_token), json={"status": "reunited"} |
| ).status_code == 400 |
| assert client.post( |
| "/admin/dogs/unknown/999999/status", headers=auth(admin_token), json={"status": "reunited"} |
| ).status_code == 404 |
| |
| assert client.post( |
| f"/admin/dogs/unknown/{uid}/status", headers=auth(owner_token), json={"status": "reunited"} |
| ).status_code == 403 |
|
|
|
|
| def test_admin_delete_dog(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K", mark_lost=True, mark_lost_pct=100) |
| did = client.get("/admin/dogs?kind=known", headers=auth(admin_token)).json()["items"][0]["id"] |
|
|
| r = client.delete(f"/admin/dogs/known/{did}", headers=auth(admin_token)) |
| assert r.status_code == 200 and r.json()["deleted"]["dogs"] == 1 |
| assert client.get(f"/admin/dog/known/{did}", headers=auth(admin_token)).status_code == 404 |
| assert client.delete("/admin/dogs/known/99999", headers=auth(admin_token)).status_code == 404 |
| assert client.delete("/admin/dogs/banana/1", headers=auth(admin_token)).status_code == 400 |
|
|
|
|
| def test_admin_owners_search_and_recent_first(client, admin_token): |
| client.post("/auth/register", json={ |
| "name": "Aaron First", "email": "aaron@example.com", "password": "password123", "zip": "20001"}) |
| client.post("/auth/register", json={ |
| "name": "Zelda Last", "email": "zelda@example.com", "password": "password123", "zip": "20001"}) |
|
|
| names = [o["name"] for o in client.get("/admin/owners", headers=auth(admin_token)).json()["items"]] |
| assert names.index("Zelda Last") < names.index("Aaron First") |
|
|
| hit = client.get("/admin/owners?q=zelda", headers=auth(admin_token)).json() |
| assert hit["total"] == 1 and hit["items"][0]["name"] == "Zelda Last" |
| assert client.get("/admin/owners?q=nobodyxyz", headers=auth(admin_token)).json()["total"] == 0 |
|
|
|
|
| def test_admin_owner_detail(client, admin_token, owner_token): |
| |
| dog = client.post("/dogs", headers=auth(owner_token), json={"name": "Rex"}).json() |
| client.post( |
| f"/dogs/{dog['id']}/photos", headers=auth(owner_token), |
| files={"files": ("d.jpg", make_image(7), "image/jpeg")}, |
| ) |
| client.post( |
| "/cases/lost", headers=auth(owner_token), |
| json={"known_dog_id": dog["id"], "event_zip": "77002", "event_date": "2026-06-01"}, |
| ) |
| uid = client.get("/auth/me", headers=auth(owner_token)).json()["id"] |
|
|
| body = client.get(f"/admin/owners/{uid}", headers=auth(admin_token)).json() |
| assert body["owner"]["id"] == uid and body["owner"]["dog_count"] == 1 |
| assert len(body["dogs"]) == 1 and body["dogs"][0]["name"] == "Rex" |
| assert body["dogs"][0]["picture_count"] == 1 |
| assert len(body["cases"]) == 1 and body["cases"][0]["type"] == "lost" |
|
|
| |
| assert client.get("/admin/owners/999999", headers=auth(admin_token)).status_code == 404 |
| admin_id = client.get("/auth/me", headers=auth(admin_token)).json()["id"] |
| assert client.get(f"/admin/owners/{admin_id}", headers=auth(admin_token)).status_code == 404 |
| |
| assert client.get(f"/admin/owners/{uid}", headers=auth(owner_token)).status_code == 403 |
|
|
|
|
| def test_admin_delete_person(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K", mark_lost=True, mark_lost_pct=100) |
| owner = client.get("/admin/owners", headers=auth(admin_token)).json()["items"][0] |
| uid, dog_count = owner["id"], owner["dog_count"] |
|
|
| r = client.delete(f"/admin/owners/{uid}", headers=auth(admin_token)) |
| assert r.status_code == 200 |
| assert r.json()["deleted"]["users"] == 1 and r.json()["deleted"]["known_dogs"] == dog_count |
| |
| assert all(o["id"] != uid for o in client.get("/admin/owners", headers=auth(admin_token)).json()["items"]) |
| assert client.delete(f"/admin/owners/{uid}", headers=auth(admin_token)).status_code == 404 |
| |
| from app.db import SessionLocal |
| from app.models import User |
| from app.models.base import UserRole |
| from sqlalchemy import select |
| db = SessionLocal() |
| admin_id = db.execute(select(User.id).where(User.role == UserRole.admin)).scalars().first() |
| db.close() |
| assert client.delete(f"/admin/owners/{admin_id}", headers=auth(admin_token)).status_code == 400 |
|
|
|
|
| def test_admin_delete_case(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K", mark_lost=True, mark_lost_pct=100) |
| cid = client.get("/admin/cases?kind=lost", headers=auth(admin_token)).json()["items"][0]["id"] |
|
|
| r = client.delete(f"/admin/cases/{cid}", headers=auth(admin_token)) |
| assert r.status_code == 200 and r.json()["deleted"]["cases"] == 1 |
| remaining = client.get("/admin/cases", headers=auth(admin_token)).json()["items"] |
| assert all(c["id"] != cid for c in remaining) |
| |
| assert client.get("/admin/dogs?kind=known", headers=auth(admin_token)).json()["total"] == 1 |
| assert client.delete("/admin/cases/99999", headers=auth(admin_token)).status_code == 404 |
|
|
|
|
| def test_admin_case_detail(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K", mark_lost=True, mark_lost_pct=100) |
| cid = client.get("/admin/cases?kind=lost", headers=auth(admin_token)).json()["items"][0]["id"] |
|
|
| d = client.get(f"/admin/cases/{cid}", headers=auth(admin_token)).json() |
| assert d["case"]["id"] == cid |
| assert d["dog"]["profile"]["kind"] == "known" |
| assert isinstance(d["dog"]["photos"], list) and isinstance(d["matches"], list) |
| assert client.get("/admin/cases/99999", headers=auth(admin_token)).status_code == 404 |
|
|
|
|
| def test_admin_run_case_match(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K", mark_lost=True, mark_lost_pct=100) |
| _load(root, "found_dogs.csv", "unknown", "U") |
|
|
| lost = client.get("/admin/cases?kind=lost", headers=auth(admin_token)).json()["items"] |
| cid = lost[0]["id"] |
| matches = client.post(f"/admin/cases/{cid}/run-match", headers=auth(admin_token)).json() |
| assert len(matches) >= 1 and matches[0]["candidate"]["type"] == "unknown" |
| assert client.post("/admin/cases/99999/run-match", headers=auth(admin_token)).status_code == 404 |
|
|
|
|
| def test_purge_retains_user_shared_with_another_dataset(): |
| """A user shared across datasets (reused email in test CSVs) still owns a dog in the OTHER |
| dataset. Purging one dataset must NOT fail on the FK / delete that user — it should keep the |
| user (detached: dataset_id nulled) and leave the other dataset intact.""" |
| from app.models.base import DatasetType, UserRole |
| from app.services.datasets import purge_dataset |
|
|
| db = SessionLocal() |
| try: |
| d1 = Dataset(name="A", type=DatasetType.known) |
| d2 = Dataset(name="B", type=DatasetType.known) |
| db.add_all([d1, d2]) |
| db.flush() |
| |
| shared = User(name="o", email="shared@example.com", zip="20001", |
| password_hash="x", role=UserRole.owner, dataset_id=d1.id) |
| db.add(shared) |
| db.flush() |
| dog_a = KnownDog(owner_id=shared.id, name="A0", description="", dataset_id=d1.id) |
| dog_b = KnownDog(owner_id=shared.id, name="B0", description="", dataset_id=d2.id) |
| db.add_all([dog_a, dog_b]) |
| db.commit() |
| a_id, b_id, dog_a_id, dog_b_id, uid = d1.id, d2.id, dog_a.id, dog_b.id, shared.id |
|
|
| counts = purge_dataset(db, d1) |
| db.expire_all() |
|
|
| |
| assert db.get(Dataset, a_id) is None |
| assert db.get(KnownDog, dog_a_id) is None |
| assert counts["users"] == 0 and counts["users_detached"] == 1 |
| |
| survivor = db.get(User, uid) |
| assert survivor is not None and survivor.dataset_id is None |
| assert db.get(Dataset, b_id) is not None |
| assert db.get(KnownDog, dog_b_id) is not None |
| finally: |
| db.close() |
|
|
|
|
| def test_purge_missing_dataset_404(client, admin_token): |
| assert client.delete("/admin/datasets/99999", headers=auth(admin_token)).status_code == 404 |
|
|
|
|
| def test_purge_deletes_media_files(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2, "dogB": 2}) |
| prepare(root, root, holdout=1, seed=1) |
| dsid = _load(root, "known_dogs.csv", "known", "K") |
|
|
| db = SessionLocal() |
| storage = get_storage() |
| paths: set[str] = set() |
| for fp, tp in db.execute(select(Picture.file_path, Picture.thumb_path)).all(): |
| paths.add(storage.abs_path(fp)) |
| if tp: |
| paths.add(storage.abs_path(tp)) |
| db.close() |
| assert paths and all(os.path.exists(p) for p in paths) |
|
|
| res = client.delete(f"/admin/datasets/{dsid}", headers=auth(admin_token)).json() |
| assert res["deleted"]["media_files"] == len(paths) |
| assert all(not os.path.exists(p) for p in paths) |
|
|
|
|
| def test_list_all_dogs_grouped(client, admin_token, owner_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2, "dogB": 2}) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K") |
| _load(root, "found_dogs.csv", "unknown", "U") |
|
|
| |
| assert client.get("/admin/dogs", headers=auth(owner_token)).status_code == 403 |
|
|
| allres = client.get("/admin/dogs", headers=auth(admin_token)).json() |
| assert allres["counts"] == {"known": 2, "unknown": 2} |
| assert allres["total"] == 4 |
| assert {i["kind"] for i in allres["items"]} == {"known", "unknown"} |
| |
| first = allres["items"][0] |
| for field in ("kind", "id", "name", "status", "picture_count", "dataset_name", "thumb_url"): |
| assert field in first |
|
|
| known = client.get("/admin/dogs?kind=known", headers=auth(admin_token)).json() |
| assert known["total"] == 2 |
| assert all(i["kind"] == "known" for i in known["items"]) |
| assert known["items"][0]["dataset_name"] == "K" |
|
|
| unknown = client.get("/admin/dogs?kind=unknown", headers=auth(admin_token)).json() |
| assert unknown["total"] == 2 |
| assert all(i["kind"] == "unknown" for i in unknown["items"]) |
|
|
|
|
| def test_test_match_finds_counterpart(client, admin_token, owner_token, tmp_path): |
| |
| root = _build_input(tmp_path, {"dogA": 2, "dogB": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| k_id = _load(root, "known_dogs.csv", "known", "K") |
| _load(root, "found_dogs.csv", "unknown", "U") |
|
|
| db = SessionLocal() |
| known_dog_id = db.execute( |
| select(KnownDog.id).where(KnownDog.dataset_id == k_id).order_by(KnownDog.id) |
| ).scalars().first() |
| db.close() |
|
|
| |
| assert client.get( |
| f"/admin/test-match?kind=known&dog_id={known_dog_id}", headers=auth(owner_token) |
| ).status_code == 403 |
|
|
| r = client.get(f"/admin/test-match?kind=known&dog_id={known_dog_id}", headers=auth(admin_token)) |
| assert r.status_code == 200, r.text |
| body = r.json() |
| assert body["query"]["kind"] == "known" |
| assert body["query_embedded"] is True |
| assert body["candidate_count"] == 2 |
| assert len(body["results"]) >= 1 |
| top = body["results"][0] |
| assert top["kind"] == "unknown" |
| assert top["score"] > 0.99 |
|
|
|
|
| def test_test_match_unembedded_dog(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2}) |
| prepare(root, root, holdout=1, seed=1) |
| k_id = _load(root, "known_dogs.csv", "known", "K", skip_embeddings=True) |
| db = SessionLocal() |
| dog_id = db.execute(select(KnownDog.id).where(KnownDog.dataset_id == k_id)).scalars().first() |
| db.close() |
| body = client.get(f"/admin/test-match?kind=known&dog_id={dog_id}", headers=auth(admin_token)).json() |
| assert body["query_embedded"] is False |
| assert body["results"] == [] |
|
|
|
|
| def test_test_match_404_and_bad_kind(client, admin_token): |
| assert client.get("/admin/test-match?kind=known&dog_id=99999", headers=auth(admin_token)).status_code == 404 |
| assert client.get("/admin/test-match?kind=banana&dog_id=1", headers=auth(admin_token)).status_code == 400 |
|
|
|
|
| def test_dog_detail_returns_all_photos(client, admin_token, owner_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 5}) |
| prepare(root, root, holdout=None, seed=1) |
| k_id = _load(root, "known_dogs.csv", "known", "K") |
| db = SessionLocal() |
| dog_id = db.execute(select(KnownDog.id).where(KnownDog.dataset_id == k_id)).scalars().first() |
| db.close() |
|
|
| assert client.get(f"/admin/dog/known/{dog_id}", headers=auth(owner_token)).status_code == 403 |
|
|
| r = client.get(f"/admin/dog/known/{dog_id}", headers=auth(admin_token)) |
| assert r.status_code == 200, r.text |
| body = r.json() |
| assert body["profile"]["id"] == dog_id |
| assert len(body["photos"]) == 3 |
| assert all(p["url"] for p in body["photos"]) |
| assert client.get(f"/admin/dog/known/99999", headers=auth(admin_token)).status_code == 404 |
| assert client.get(f"/admin/dog/banana/1", headers=auth(admin_token)).status_code == 400 |
|
|
|
|
| def test_breed_filter(client, admin_token, tmp_path): |
| |
| root = _build_input(tmp_path, {"dogA": 2, "dogB": 2}, identical=True) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K") |
|
|
| |
| breeds = client.get("/admin/breeds", headers=auth(admin_token)).json() |
| assert breeds["breeds"], breeds |
| label = breeds["breeds"][0]["label"] |
| assert label.startswith("mock-breed-") |
|
|
| |
| page = client.get("/admin/dogs?kind=known", headers=auth(admin_token)).json() |
| assert page["items"] |
| assert "predicted_breeds" in page["items"][0] |
|
|
| |
| dog0 = page["items"][0] |
| top_label = dog0["predicted_breeds"][0] |
| filtered = client.get( |
| f"/admin/dogs?kind=known&breed={top_label}&breed_k=1", headers=auth(admin_token) |
| ).json() |
| assert all(top_label in d["predicted_breeds"] for d in filtered["items"]) |
| assert filtered["total"] >= 1 |
|
|
| |
| none = client.get("/admin/dogs?breed=not-a-real-breed&breed_k=10", headers=auth(admin_token)).json() |
| assert none["total"] == 0 |
|
|
|
|
| def test_estimated_breeds_blend_frequency_and_confidence(client): |
| |
| |
| |
| from app.models import BreedPrediction, User |
| from app.models.base import UserRole |
| from app.services.datasets import aggregated_breeds |
|
|
| db = SessionLocal() |
| try: |
| owner = User(name="O", email="breedvote@example.com", zip="20001", role=UserRole.owner) |
| db.add(owner) |
| db.flush() |
| dog = KnownDog(owner_id=owner.id, name="Rex") |
| db.add(dog) |
| db.flush() |
| model = ("test-breed", "v1") |
| per_photo = [ |
| [("beagle", 0.36), ("labrador", 0.34)], |
| [("beagle", 0.35), ("labrador", 0.33)], |
| [("labrador", 0.95), ("beagle", 0.05)], |
| ] |
| |
| for preds in per_photo: |
| pic = Picture( |
| subject_type=SubjectType.known, subject_id=dog.id, |
| file_path="x.jpg", mime_type="image/jpeg", |
| ) |
| db.add(pic) |
| db.flush() |
| for rank, (label, score) in enumerate(preds): |
| db.add(BreedPrediction( |
| picture_id=pic.id, model_name=model[0], model_version=model[1], |
| rank=rank, label=label, score=score, |
| )) |
| db.commit() |
|
|
| result = aggregated_breeds(db, SubjectType.known, dog.id, model, limit=3) |
| assert result[0] == "labrador" |
| assert set(result[:2]) == {"labrador", "beagle"} |
| finally: |
| db.close() |
|
|
|
|
| def test_list_all_dogs_ordering_and_pagination(client, admin_token, tmp_path): |
| root = _build_input(tmp_path, {"dogA": 2, "dogB": 2, "dogC": 2}) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K") |
| _load(root, "found_dogs.csv", "unknown", "U") |
|
|
| |
| newest = client.get("/admin/dogs?kind=all", headers=auth(admin_token)).json() |
| assert [i["kind"] for i in newest["items"]] == ["unknown"] * 3 + ["known"] * 3 |
| assert newest["total"] == 6 |
|
|
| |
| page = client.get("/admin/dogs?kind=all&limit=4&offset=2", headers=auth(admin_token)).json() |
| assert [i["kind"] for i in page["items"]] == ["unknown", "known", "known", "known"] |
|
|
| |
| oldest = client.get("/admin/dogs?kind=all&sort=oldest", headers=auth(admin_token)).json() |
| assert oldest["items"][0]["kind"] == "known" |
| assert [i["id"] for i in oldest["items"]] == [i["id"] for i in reversed(newest["items"])] |
|
|
|
|
| def test_list_all_dogs_added_date_filter(client, admin_token, tmp_path): |
| from datetime import date, timedelta |
|
|
| root = _build_input(tmp_path, {"dogA": 2, "dogB": 2}) |
| prepare(root, root, holdout=1, seed=1) |
| _load(root, "known_dogs.csv", "known", "K") |
|
|
| today = date.today().isoformat() |
| tomorrow = (date.today() + timedelta(days=1)).isoformat() |
| yesterday = (date.today() - timedelta(days=1)).isoformat() |
|
|
| assert client.get(f"/admin/dogs?added_to={today}", headers=auth(admin_token)).json()["total"] >= 2 |
| assert client.get(f"/admin/dogs?added_from={tomorrow}", headers=auth(admin_token)).json()["total"] == 0 |
| assert client.get(f"/admin/dogs?added_to={yesterday}", headers=auth(admin_token)).json()["total"] == 0 |
|
|
|
|
| def test_list_all_dogs_zip_prefix_filter(client, admin_token): |
| |
| for zip_code in ("77002", "20001"): |
| client.post( |
| "/cases/found", |
| data={"event_zip": zip_code, "event_date": "2026-06-03", "finder_email": "f@example.com"}, |
| files={"files": ("f.jpg", make_image(101), "image/jpeg")}, |
| ) |
| page = client.get("/admin/dogs?kind=unknown&zip=770", headers=auth(admin_token)).json() |
| assert page["total"] == 1 |
| assert page["counts"]["unknown"] == 1 |
| assert all(d["zip"].startswith("770") for d in page["items"]) |
|
|