feat: add external observation review queue

This commit is contained in:
ik
2026-09-03 18:47:31 +07:00
parent 53d700126a
commit c4f4deb24b
10 changed files with 429 additions and 13 deletions
+63 -2
View File
@@ -4,13 +4,14 @@ from datetime import datetime, timedelta, timezone
from uuid import UUID
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool
from app.database import Base, get_session
from app.community_importer import stage_observations
from app.main import app
from app.models import Bait, BaitKind, CatchReport, Fish, ImportStatus, ModerationEvent, ModerationStatus, OfficialRecordImport, SourceType, Spot, Waterbody
from app.models import Bait, BaitKind, CatchReport, ExternalEntityAlias, ExternalObservation, Fish, ImportStatus, ModerationEvent, ModerationStatus, OfficialRecordImport, SourceType, Spot, Waterbody
engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool)
@@ -89,6 +90,66 @@ def test_admin_requires_token() -> None:
assert client.get("/api/v1/admin/catch-reports").status_code == 401
assert client.get("/api/v1/admin/imports").status_code == 401
assert client.post("/api/v1/admin/imports/official-records").status_code == 401
assert client.get("/api/v1/admin/external-observations").status_code == 401
def test_external_observation_requires_mapping_and_complete_data_before_publication() -> None:
with Session(engine) as db:
stage_observations(db, [{
"source_system": "rf4db", "source_external_id": "review-complete",
"source_url": "https://rf4db.com/catches/review-complete", "fish": "Pike external",
"fish_external_id": "fish-1", "waterbody": "Lake external",
"waterbody_external_id": "lake-1", "x": 31, "y": 41, "weight_g": 6200,
"bait": "Тестовая приманка", "player_name": "External Player",
}])
observation_id = db.scalar(select(ExternalObservation.id).where(
ExternalObservation.source_external_id == "review-complete"
))
headers = {"Authorization": "Bearer change-me-in-production"}
premature = client.post(f"/api/v1/admin/external-observations/{observation_id}/publish", headers=headers)
assert premature.status_code == 409
mapped = client.patch(
f"/api/v1/admin/external-observations/{observation_id}/mapping", headers=headers,
json={"fish_slug": "pike", "waterbody_slug": "test-lake", "note": "verified fixture"},
)
assert mapped.status_code == 200
assert mapped.json()["status"] == "ready"
published = client.post(f"/api/v1/admin/external-observations/{observation_id}/publish", headers=headers)
assert published.status_code == 200
assert published.json()["status"] == "published"
repeated = client.post(f"/api/v1/admin/external-observations/{observation_id}/publish", headers=headers)
assert repeated.json()["catch_report_id"] == published.json()["catch_report_id"]
with Session(engine) as db:
observation = db.get(ExternalObservation, observation_id)
report = db.get(CatchReport, observation.catch_report_id)
aliases = list(db.scalars(select(ExternalEntityAlias).where(ExternalEntityAlias.source_system == "rf4db")))
assert report.moderation_status == ModerationStatus.approved
assert report.raw_payload["provenance"]["source_external_id"] == "review-complete"
assert {alias.entity_type for alias in aliases} == {"fish", "waterbody"}
def test_incomplete_external_observation_stays_out_of_public_data() -> None:
with Session(engine) as db:
stage_observations(db, [{
"source_system": "rf4db", "source_external_id": "review-incomplete",
"source_url": "https://rf4db.com/catches/review-incomplete", "fish": "Pike external",
"waterbody": "Lake external", "x": 32, "y": 42,
}])
observation_id = db.scalar(select(ExternalObservation.id).where(
ExternalObservation.source_external_id == "review-incomplete"
))
headers = {"Authorization": "Bearer change-me-in-production"}
mapped = client.patch(
f"/api/v1/admin/external-observations/{observation_id}/mapping", headers=headers,
json={"fish_slug": "pike", "waterbody_slug": "test-lake"},
)
assert mapped.json()["status"] == "mapped"
assert client.post(f"/api/v1/admin/external-observations/{observation_id}/publish", headers=headers).status_code == 409
rejected = client.patch(
f"/api/v1/admin/external-observations/{observation_id}/reject", headers=headers,
json={"reason": "weight is absent"},
)
assert rejected.json()["status"] == "rejected"
def test_admin_can_start_and_list_official_import(monkeypatch) -> None: