diff --git a/apps/web/src/pages/media.astro b/apps/web/src/pages/media.astro index 26e4e52..a0298d8 100644 --- a/apps/web/src/pages/media.astro +++ b/apps/web/src/pages/media.astro @@ -9,6 +9,11 @@ const requested = Astro.url.searchParams.get("type") ?? "fish"; const type = allowed.has(requested) ? requested : "fish"; let assets: MediaAsset[] = [], unavailable = false; try { assets = await api(`/api/v1/media/catalog?entity_type=${type}`); } catch { unavailable = true; } +if (unavailable) { + Astro.response.status = 503; + Astro.response.headers.set("Retry-After", "60"); + Astro.response.headers.set("Cache-Control", "no-store"); +} const labels: Record = {fish:"Рыбы",waterbody:"Водоёмы",tackle:"Снасти и приманки",reference:"Справочные материалы"}; --- diff --git a/apps/web/tests/degradation.spec.ts b/apps/web/tests/degradation.spec.ts new file mode 100644 index 0000000..75b6984 --- /dev/null +++ b/apps/web/tests/degradation.spec.ts @@ -0,0 +1,21 @@ +import { expect, test } from "@playwright/test"; + +for (const path of [ + "/waterbodies", + "/waterbodies/vyunok", + "/waterbodies/vyunok/pike", + "/fish", + "/media?type=waterbody", + "/tackle", + "/tackle/analytics", + "/tackle/items/00000000-0000-0000-0000-000000000000", +]) { + test(`${path} exposes a cache-safe unavailable state`, async ({ page }) => { + const response = await page.goto(path); + expect(response?.status(), `${path} status`).toBe(503); + expect(response?.headers()["retry-after"], `${path} retry header`).toBe("60"); + expect(response?.headers()["cache-control"], `${path} cache header`).toBe("no-store"); + await expect(page.getByRole("alert")).toContainText(/временно недоступ|недоступен/i); + await expect(page.locator("body")).not.toContainText(/Traceback|localhost:8000|API \d+/i); + }); +} diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 467b3b1..ae03d49 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -56,7 +56,7 @@ - [ ] **W05 · Crosswalk источников.** Добавлен offline-конструктор консервативных предложений: нормализуются только точные имена/алиасы, неоднозначные и unmatched строки не получают canonical key; отсутствие ID выдаётся лишь диагностикой и не считается удалением. RF4MAP/RF4-STAT directory parsers теперь преобразуются в crosswalk identities без переноса метрик или автоматической привязки. Осталось подать полный набор реальных RF4DB/RF4MAP/RF4 Posts identities и вручную подтвердить результаты, включая три ранее отмеченных отсутствующих RF4MAP объекта. - [ ] **W06 · Координаты и точность.** В `ExternalObservation`, staging, provenance опубликованного улова и публичных activity/spot-ответах добавлены `coordinate_raw`, `coordinate_precision = exact | approximate | area | missing` и список источников; RF4DB/RF4-STAT/RF4MAP/RF4 Posts parsers теперь протягивают исходную строку, включая строки без доступных числовых координат. Исправлена spot detail: точность (`точные`/`приблизительные`/`район`/`не указаны`) теперь видна рядом с координатами и покрыта smoke regression. In-app Chromium и focused smoke подтвердили `/spots/vyunok-321x654` на desktop/mobile без overflow; browser regression для отсутствующей точки проверяет недоступное состояние без ложных координат, а API regression параметризован по всем четырём precision-состояниям. Осталась полная browser QA матрица для всех precision/error-состояний. - [ ] **W07 · Публичный API и страницы.** API и detail-страница теперь выводят подтверждённые detail-факты водоёма: описание, уровень, количество видов, алиасы, число ссылок на точки и отдельный счётчик изображений-кандидатов; источники и непроверенные media не смешиваются. Public media API теперь передаёт reviewed `media_role`, waterbody detail принимает только `waterbody_cover`/`waterbody_map`/`waterbody_depth_map`, а UI подписывает карту/заставку/скриншот текстом. Осталось завершить browser QA для реальных role-вариантов и не показывать abstract fingerprint как внешнее изображение. -- [ ] **W08 · Приёмка и эксплуатация.** Fixture-based parser tests, offline catalog/media audit и повторный idempotent import подтверждены: `created=0 updated=19`, в PostgreSQL ровно `19` RF4DB waterbodies плюс `2` legacy-записи, media audit сообщает `issues=[]` и `orphaned_files=[]`. In-app Chromium и focused smoke покрывают desktop/mobile основные страницы; 20.09 targeted waterbody E2E подтвердил detail с пустой активностью, not-found waterbody и not-found waterbody/fish pair, а spot regression — unavailable state без ложной координаты; полный web E2E прошёл `27 passed, 1 skipped`, visual-matrix также прошёл. Остаются полная browser QA error/empty matrix и закрытие внешних detail-данных. Сетевые тесты не выполнять; регулярный импорт оставить opt-in и под общим cooldown/backoff. +- [ ] **W08 · Приёмка и эксплуатация.** Fixture-based parser tests, offline catalog/media audit и повторный idempotent import подтверждены: `created=0 updated=19`, в PostgreSQL ровно `19` RF4DB waterbodies плюс `2` legacy-записи, media audit сообщает `issues=[]` и `orphaned_files=[]`. In-app Chromium и focused smoke покрывают desktop/mobile основные страницы; 20.09 targeted waterbody E2E подтвердил detail с пустой активностью, not-found waterbody и not-found waterbody/fish pair, а spot regression — unavailable state без ложной координаты; каталоги, media, analytics и tackle detail теперь имеют regression для `503` + `Retry-After` + `no-store` без утечки внутренних ошибок. Полный web E2E и visual-matrix остаются обязательным финальным прогоном; внешние detail-данные закрываются отдельно. Сетевые тесты не выполнять; регулярный импорт оставить opt-in и под общим cooldown/backoff. ### Каталог снастей, наживок и прочей оснастки