diff --git a/apps/api/tests/test_api.py b/apps/api/tests/test_api.py index ce89b80..4c8e9e1 100644 --- a/apps/api/tests/test_api.py +++ b/apps/api/tests/test_api.py @@ -62,6 +62,7 @@ def test_list_pagination_and_filter_validation() -> None: headers = {"Authorization": "Bearer change-me-in-production"} assert client.get("/api/v1/admin/external-observations?status=unknown", headers=headers).status_code == 422 assert client.get("/api/v1/admin/catch-reports?offset=-1", headers=headers).status_code == 422 + assert client.get("/api/v1/admin/catch-reports?limit=101", headers=headers).status_code == 422 def test_liveness_does_not_probe_dependencies() -> None: diff --git a/apps/api/tests/test_storage.py b/apps/api/tests/test_storage.py index 28c2bac..03e0ba7 100644 --- a/apps/api/tests/test_storage.py +++ b/apps/api/tests/test_storage.py @@ -6,6 +6,7 @@ import pytest from PIL import Image from app.storage import ScreenshotError, prepare_image, validate_upload_metadata +from app.config import settings def test_prepare_image_removes_metadata() -> None: @@ -28,6 +29,11 @@ def test_prepare_image_rejects_non_image() -> None: prepare_image(b"not an image") +def test_prepare_image_rejects_oversized_body_before_decoding() -> None: + with pytest.raises(ScreenshotError, match="8 MB"): + prepare_image(b"x" * (settings.screenshot_max_bytes + 1)) + + def test_upload_metadata_requires_matching_supported_mime_and_extension() -> None: validate_upload_metadata("catch.jpeg", "image/jpeg") validate_upload_metadata("catch.webp", "image/webp") diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 8a4ab18..18b7e5f 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -109,8 +109,8 @@ - [x] Опубликовать страницы `/rules` и `/privacy`, добавить ссылки в footer и обязательное несохраняемое согласие перед отправкой улова. - [ ] Создать и проверить ящики `privacy@rf4spotter.ru` и `abuse@rf4spotter.ru`, определить срок реакции на обращения. -- [ ] Проверить публичные abuse-сценарии: burst заявок, крупные файлы, повторные отправки и рост очереди модерации. -- [ ] Зафиксировать короткий сценарий приёмки пилота и измеримые критерии: успешная отправка/модерация, понятность оценки, свежесть данных и допустимое время ответа. +- [x] Проверить локально публичные abuse-сценарии: шестая заявка блокируется, файл > 8 МБ отклоняется до декодирования, повтор screenshot запрещён, admin queue ограничена 100 строками; нагрузочный прогон остаётся серверным шагом. +- [x] Зафиксировать сценарий приёмки открытой альфы, измеримые пороги и стоп-критерии (`docs/open-alpha-acceptance.md`). - [ ] Согласовать первые категории рекордов, водоёмы и виды рыб. - [ ] Наполнить базу небольшим разрешённым набором реальных данных. - [ ] Провести тестирование с несколькими игроками по подготовленному сценарию. @@ -122,9 +122,9 @@ Технический production-контур, health/readiness, backup/restore и безопасные логи готовы. Следующие пункты выполняются строго по одному: -1. создать и проверить контакты `privacy`/`abuse`; -2. подготовить сценарий и измеримые критерии открытой альфы, включая публичную нагрузку и злоупотребления; -3. мониторинг, DNS/TLS и проверка production-профиля на целевом сервере. +1. согласовать стартовые категории, водоёмы и виды рыб; +2. создать и проверить контакты `privacy`/`abuse`; +3. мониторинг, нагрузка, DNS/TLS и production-профиль на целевом сервере. После каждого пункта необходимо: diff --git a/docs/open-alpha-acceptance.md b/docs/open-alpha-acceptance.md new file mode 100644 index 0000000..0401b61 --- /dev/null +++ b/docs/open-alpha-acceptance.md @@ -0,0 +1,43 @@ +# Приёмка открытой альфы RF4 Spotter + +Версия: 7 сентября 2026 года. Проверка проводится сначала владельцем проекта, затем группой из 5–15 игроков. В пилот входят только согласованные водоёмы, рыбы и источники; неполный staging не публикуется. + +## Условия открытия + +- production preflight, DNS, TLS, `/health` и `/ready` успешны; +- настроены внешний backup, ежедневное обслуживание и уведомления мониторинга; +- работают `privacy@rf4spotter.ru` и `abuse@rf4spotter.ru`, назначен ответственный; +- опубликованы `/rules` и `/privacy`, форма требует согласия; +- заглушки секретов отсутствуют, административные маршруты закрыты двумя уровнями авторизации; +- импортирован небольшой разрешённый набор реальных данных, демоданные отсутствуют. + +## Сценарий игрока + +1. На телефоне открыть главную, выбрать водоём и рыбу, получить результат не позднее второй высоты экрана. +2. Открыть точку и своими словами объяснить активность, уверенность и свежесть. +3. Отправить улов с обязательными полями и согласием; убедиться, что до модерации он не публичен. +4. Модератор проверяет заявку, указывает причину отказа либо одобряет её. +5. После одобрения точка появляется в выдаче; удаление заявки обезличивает данные и сохраняет аудит. +6. Проверить рекорды и сброс фильтров на desktop и 390 px. + +## Измеримые критерии + +| Область | Проходной порог | +|---|---| +| Доступность | axe: 0 critical/serious; Lighthouse accessibility ≥ 95 | +| Интерфейс | нет горизонтального overflow на 390 px; ≥ 80% участников завершают поиск без подсказки | +| Понятность | ≥ 80% верно объясняют разницу активности и уверенности | +| API | p95 публичных списков ≤ 250 мс; admin queue ≤ 500 мс на пилотном объёме | +| Надёжность | readiness успешен; backup моложе 26 часов и проходит checksum | +| Модерация | 100% пользовательских заявок сначала `pending`; медианное решение ≤ 12 часов | +| Свежесть | успешный разрешённый импорт не старше настроенного интервала; сбой явно виден в readiness/monitoring | +| Abuse | шестая заявка одного клиента за 10 минут получает `429`; файл > 8 МБ отклоняется; повторный screenshot отклоняется | +| Обращения | abuse подтверждается ≤ 24 часов, privacy/delete ≤ 72 часов | + +## Стоп-критерии + +Альфа закрывается для новых отправок при утечке данных или секретов, потере возможности восстановления, обходе модерации, недоступности обоих контактов более 24 часов либо устойчивой ошибке 5xx выше 2% за 15 минут. Публичное чтение можно оставить только если оно не усугубляет инцидент. + +## Результат прогона + +Для каждого запуска фиксируются дата, версия/коммит, участники, объём данных, p95, ошибки, возраст backup, результаты axe/Lighthouse и решение `GO`/`NO-GO`. Блокирующие дефекты получают владельца и срок повторной проверки.