feat: harden production data and backups
This commit is contained in:
@@ -187,10 +187,13 @@ def test_admin_can_start_and_list_official_import(monkeypatch) -> None:
|
||||
def test_pending_report_accepts_one_validated_screenshot(monkeypatch) -> None:
|
||||
created = client.post("/api/v1/catch-reports", json={"fish_slug": "pike", "waterbody_slug": "test-lake", "x": 91, "y": 92, "weight_g": 4200}).json()
|
||||
monkeypatch.setattr("app.main.upload_screenshot", lambda raw, **metadata: "reports/test.jpg" if raw == b"image-bytes" and metadata == {"filename": "catch.jpg", "content_type": "image/jpeg"} else "unexpected")
|
||||
response = client.post(f"/api/v1/catch-reports/{created['id']}/screenshot", files={"screenshot": ("catch.jpg", b"image-bytes", "image/jpeg")})
|
||||
upload_url = f"/api/v1/catch-reports/{created['id']}/screenshot"
|
||||
assert client.post(upload_url, files={"screenshot": ("catch.jpg", b"image-bytes", "image/jpeg")}).status_code == 401
|
||||
assert client.post(upload_url, headers={"X-Upload-Token": "wrong"}, files={"screenshot": ("catch.jpg", b"image-bytes", "image/jpeg")}).status_code == 401
|
||||
response = client.post(upload_url, headers={"X-Upload-Token": created["screenshot_upload_token"]}, files={"screenshot": ("catch.jpg", b"image-bytes", "image/jpeg")})
|
||||
assert response.status_code == 204
|
||||
duplicate = client.post(f"/api/v1/catch-reports/{created['id']}/screenshot", files={"screenshot": ("catch.jpg", b"image-bytes", "image/jpeg")})
|
||||
assert duplicate.status_code == 409
|
||||
reused = client.post(upload_url, headers={"X-Upload-Token": created["screenshot_upload_token"]}, files={"screenshot": ("catch.jpg", b"image-bytes", "image/jpeg")})
|
||||
assert reused.status_code == 401
|
||||
|
||||
|
||||
def test_admin_delete_anonymizes_report_removes_screenshot_and_keeps_audit(monkeypatch) -> None:
|
||||
|
||||
@@ -13,6 +13,7 @@ def production_settings(**changes) -> Settings:
|
||||
"s3_secret_key": "s" * 32,
|
||||
"s3_public_endpoint_url": "https://files.rf4spotter.ru",
|
||||
"cors_origins": ["https://rf4spotter.ru"],
|
||||
"seed_demo_data": False,
|
||||
}
|
||||
return Settings(**(values | changes))
|
||||
|
||||
@@ -30,6 +31,7 @@ def test_production_settings_accept_real_domains_and_secrets() -> None:
|
||||
("s3_secret_key", "rf4-local-secret"),
|
||||
("cors_origins", ["http://rf4spotter.ru"]),
|
||||
("s3_public_endpoint_url", "http://files.rf4spotter.ru"),
|
||||
("seed_demo_data", True),
|
||||
])
|
||||
def test_production_settings_reject_insecure_values(field: str, value: object) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
from sqlalchemy import create_engine, func, select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app.database import Base
|
||||
from app.models import CatchReport, Fish, Spot, Waterbody
|
||||
from app import seed as seed_module
|
||||
|
||||
|
||||
def test_seed_repairs_partial_database_and_is_idempotent(monkeypatch) -> None:
|
||||
engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool)
|
||||
Base.metadata.create_all(engine)
|
||||
sessions = sessionmaker(bind=engine, expire_on_commit=False)
|
||||
with Session(engine) as db:
|
||||
db.add(Fish(slug="pike", name_ru="Щука", trophy_weight_g=10_000))
|
||||
db.commit()
|
||||
|
||||
monkeypatch.setattr(seed_module, "SessionLocal", sessions)
|
||||
monkeypatch.setattr(seed_module.settings, "seed_demo_data", False)
|
||||
seed_module.seed()
|
||||
seed_module.seed()
|
||||
|
||||
with Session(engine) as db:
|
||||
assert db.scalar(select(func.count()).select_from(Fish)) == 2
|
||||
assert db.scalar(select(func.count()).select_from(Waterbody)) == 2
|
||||
assert db.scalar(select(func.count()).select_from(Spot)) == 2
|
||||
assert db.scalar(select(func.count()).select_from(CatchReport)) == 0
|
||||
Reference in New Issue
Block a user