sync
CI / backend-and-migrations (push) Canceled after 0s
CI / astro-build (push) Canceled after 0s
CI / compose-e2e (push) Canceled after 0s

This commit is contained in:
ik
2026-09-08 09:31:41 +07:00
parent 8b2d7e2e1c
commit ddb6909c4d
21 changed files with 255 additions and 58 deletions
+31
View File
@@ -57,6 +57,37 @@ def test_invalid_period_is_rejected() -> None:
assert client.get("/api/v1/activity?sort=unknown").status_code == 422
def test_timeline_includes_more_than_catch_page_and_sitemap_includes_old_spots() -> None:
with Session(engine) as db:
fish = db.scalar(select(Fish).where(Fish.slug == "pike"))
water = db.scalar(select(Waterbody).where(Waterbody.slug == "test-lake"))
spot = Spot(waterbody=water, x=901, y=902)
db.add(spot)
db.flush()
spot_id = spot.id
reports = [CatchReport(fish=fish, waterbody=water, spot=spot, weight_g=1000,
reported_at=datetime.now(timezone.utc) - timedelta(hours=1 if i < 60 else 100),
source_type=SourceType.manual_import, source_confidence=70,
moderation_status=ModerationStatus.approved) for i in range(61)]
db.add_all(reports)
db.commit()
try:
result = client.get(f"/api/v1/spots/{spot_id}/timeline")
assert result.status_code == 200
assert sum(row["count"] for row in result.json()) == 60
assert len(client.get(f"/api/v1/spots/{spot_id}/catches").json()) == 50
for report in reports:
report.reported_at = datetime.now(timezone.utc) - timedelta(days=10)
db.commit()
assert "/spots/test-lake-901x902" in client.get("/api/v1/public-spot-pages").json()
finally:
for report in reports:
db.delete(report)
db.flush()
db.delete(spot)
db.commit()
def test_list_pagination_and_filter_validation() -> None:
assert client.get("/api/v1/fishes?limit=0").status_code == 422
assert client.get("/api/v1/fishes?limit=1&offset=0").status_code == 200
+28 -1
View File
@@ -8,7 +8,7 @@ from sqlalchemy import create_engine, func, select
from sqlalchemy.orm import Session
from app.community_importer import CommunityImportError, stage_observations
from app.community_review import ExternalReviewError, map_observation, suggest_aliases
from app.community_review import ExternalReviewError, map_observation, publish_observation, suggest_aliases
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
@@ -111,6 +111,33 @@ def test_complete_observation_with_reviewed_aliases_is_published(db: Session) ->
assert db.scalar(select(func.count()).select_from(CatchReport)) == 1
def test_changed_published_record_requires_review_and_reuses_report(db: Session) -> 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))
map_observation(db, item, fish, water)
report = publish_observation(db, item)
report_id = report.id
stage_observations(db, [record() | {"weight_g": 6000}])
assert item.status == "staged"
assert report.moderation_status.value == "pending"
assert report.weight_g == 5000
assert item.weight_g == 6000
stage_observations(db, [record() | {"weight_g": 6000}])
assert item.status == "staged"
with pytest.raises(ExternalReviewError):
publish_observation(db, item)
map_observation(db, item, fish, water)
updated = publish_observation(db, item)
assert updated.id == report_id
assert updated.weight_g == 6000
assert updated.moderation_status.value == "approved"
assert db.scalar(select(func.count()).select_from(CatchReport)) == 1
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="Щука")
+12
View File
@@ -9,3 +9,15 @@ def test_cache_copies_values_and_invalidates() -> None:
assert cache.get(("activity",), 20) == [{"score": 10}]
cache.invalidate()
assert cache.get(("activity",), 20) is None
def test_cache_bounds_memory_and_rejects_result_started_before_invalidation() -> None:
cache = PublicResponseCache(max_entries=2)
generation = cache.generation()
cache.set((1,), [1])
cache.set((2,), [2])
cache.set((3,), [3])
assert cache.get((1,), 20) is None
cache.invalidate()
cache.set((4,), [4], generation=generation)
assert cache.get((4,), 20) is None