feat: manage external source lifecycle
This commit is contained in:
@@ -9,6 +9,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.community_importer import CommunityImportError, stage_observations
|
||||
from app.community_review import ExternalReviewError, map_observation, publish_observation, suggest_aliases
|
||||
from app.source_lifecycle import record_source_check
|
||||
from app.database import Base
|
||||
from app.models import CatchReport, DataSource, ExternalEntityAlias, ExternalObservation, Fish, Waterbody
|
||||
from rf4_research.community_sources import parse_rf4db_catches, parse_rf4map_point, parse_rf4posts_spot
|
||||
@@ -128,6 +129,8 @@ def test_changed_published_record_requires_review_and_reuses_report(db: Session)
|
||||
assert report.moderation_status.value == "pending"
|
||||
assert report.weight_g == 5000
|
||||
assert item.weight_g == 6000
|
||||
assert item.moderation_version == 1
|
||||
assert item.reviewed_at is not None
|
||||
stage_observations(db, [record() | {"weight_g": 6000}])
|
||||
assert item.status == "staged"
|
||||
with pytest.raises(ExternalReviewError):
|
||||
@@ -140,6 +143,49 @@ def test_changed_published_record_requires_review_and_reuses_report(db: Session)
|
||||
assert db.scalar(select(func.count()).select_from(CatchReport)) == 1
|
||||
|
||||
|
||||
def test_missing_source_withdraws_published_record_until_manual_review(db: Session) -> None:
|
||||
fish = Fish(slug="pike", name_ru="Щука")
|
||||
water = Waterbody(slug="test-lake", name_ru="Тестовое озеро")
|
||||
db.add_all([fish, water])
|
||||
db.commit()
|
||||
seen = datetime(2026, 9, 13, 8, tzinfo=timezone.utc)
|
||||
checked = datetime(2026, 9, 13, 9, tzinfo=timezone.utc)
|
||||
stage_observations(db, [record() | {"weight_g": 5000}], fetched_at=seen)
|
||||
item = db.scalar(select(ExternalObservation))
|
||||
assert item is not None and item.catch_report is not None
|
||||
|
||||
record_source_check(db, item, "missing", checked_at=checked)
|
||||
|
||||
assert item.status == "withdrawn"
|
||||
assert item.source_check_status == "missing"
|
||||
assert item.source_checked_at.replace(tzinfo=timezone.utc) == checked
|
||||
assert item.catch_report.moderation_status.value == "pending"
|
||||
assert item.moderation_version == 1
|
||||
|
||||
stage_observations(db, [record() | {"weight_g": 5000}], fetched_at=checked)
|
||||
assert item.status == "staged"
|
||||
assert item.source_check_status == "available"
|
||||
assert item.catch_report.moderation_status.value == "pending"
|
||||
assert "reappeared" in (item.review_note or "")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", ["temporary_error", "blocked"])
|
||||
def test_non_authoritative_source_failures_do_not_withdraw(db: Session, status: str) -> None:
|
||||
fish = Fish(slug="pike", name_ru="Щука")
|
||||
water = Waterbody(slug="test-lake", name_ru="Тестовое озеро")
|
||||
db.add_all([fish, water])
|
||||
db.commit()
|
||||
stage_observations(db, [record() | {"weight_g": 5000}])
|
||||
item = db.scalar(select(ExternalObservation))
|
||||
assert item is not None and item.catch_report is not None
|
||||
|
||||
record_source_check(db, item, status) # type: ignore[arg-type]
|
||||
|
||||
assert item.status == "published"
|
||||
assert item.catch_report.moderation_status.value == "approved"
|
||||
assert item.source_check_status == status
|
||||
|
||||
|
||||
def test_auto_publication_requires_enabled_source(db: Session) -> None:
|
||||
source = DataSource(key="rf4db", name="RF4DB", base_url="https://rf4db.com", default_confidence=70, enabled=False)
|
||||
fish = Fish(slug="pike", name_ru="Щука")
|
||||
|
||||
Reference in New Issue
Block a user