From 0f075469c8d4a401583006341856b79b2ec6223d Mon Sep 17 00:00:00 2001 From: IK Date: Sun, 6 Sep 2026 14:34:05 +0700 Subject: [PATCH] test: verify clean production bootstrap --- README.md | 2 +- apps/web/Dockerfile | 2 ++ apps/web/package.json | 3 +- apps/web/tests/production-bootstrap.spec.ts | 23 +++++++++++++++ compose.production.yaml | 5 +++- deploy/README.md | 9 +++++- deploy/compose.bootstrap.yaml | 8 ++++++ deploy/test-production-bootstrap.sh | 32 +++++++++++++++++++++ docs/ROADMAP.md | 22 +++++++------- 9 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 apps/web/tests/production-bootstrap.spec.ts create mode 100644 deploy/compose.bootstrap.yaml create mode 100755 deploy/test-production-bootstrap.sh diff --git a/README.md b/README.md index 7468008..61cd5e1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ RF4 Spotter — неофициальный сервис свежих точек Подробный план и актуальные чекбоксы находятся в [`docs/ROADMAP.md`](docs/ROADMAP.md). Результаты проверки интерфейса и пять приоритетных UX-пакетов описаны в [`docs/UI_UX_AUDIT.md`](docs/UI_UX_AUDIT.md). -Production-контур для домена `rf4spotter.ru`, TLS, секреты, backup/restore и команды первого запуска описаны в [`deploy/README.md`](deploy/README.md). Он использует отдельный `compose.production.yaml`; локальный `compose.yaml` остаётся средой разработки. Production seed добавляет только справочники — демонстрационные уловы отключены. +Production-контур для домена `rf4spotter.ru`, TLS, секреты, backup/restore и команды первого запуска описаны в [`deploy/README.md`](deploy/README.md). Он использует отдельный `compose.production.yaml`; локальный `compose.yaml` остаётся средой разработки. Production seed добавляет только справочники — демонстрационные уловы отключены. Изолированные проверки `deploy/test-production-bootstrap.sh` и `deploy/test-backup-restore.sh` подтверждают старт с пустых volumes и восстановление данных. Gitea Actions workflow `.gitea/workflows/ci.yml` на каждый push и pull request проверяет Python, миграции на чистой PostgreSQL, Astro build и полный Compose/Playwright-сценарий. При падении E2E сохраняются логи контейнеров и Playwright-артефакты. diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 952fc8c..397ae45 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -1,5 +1,7 @@ FROM node:22-alpine AS build WORKDIR /app +ARG PUBLIC_API_URL=http://localhost:8000 +ENV PUBLIC_API_URL=$PUBLIC_API_URL COPY package*.json ./ RUN npm install COPY . . diff --git a/apps/web/package.json b/apps/web/package.json index 0138d14..6c06d59 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -9,7 +9,8 @@ "start": "node ./dist/server/entry.mjs", "check": "astro check", "test:unit": "node --test tests/unit/*.test.ts", - "test:e2e": "playwright test" + "test:e2e": "playwright test", + "test:bootstrap": "playwright test tests/production-bootstrap.spec.ts" }, "dependencies": { "@astrojs/check": "^0.9.10", diff --git a/apps/web/tests/production-bootstrap.spec.ts b/apps/web/tests/production-bootstrap.spec.ts new file mode 100644 index 0000000..b94d440 --- /dev/null +++ b/apps/web/tests/production-bootstrap.spec.ts @@ -0,0 +1,23 @@ +import { expect, test } from "@playwright/test"; + +test("production bootstrap supports submission and moderation", async ({ page, request }) => { + const player = `Bootstrap Player ${Date.now()}`; + const x = 7000 + Date.now() % 1000; + const y = x + 1; + + await page.goto("/"); + await expect(page.getByRole("heading", { name: "Выбирай место, пока клюёт." })).toBeVisible(); + await page.goto("/report"); + await page.getByLabel("Координата X *").fill(String(x)); + await page.getByLabel("Координата Y *").fill(String(y)); + await page.getByLabel("Вес, граммы *").fill("4321"); + await page.getByLabel("Ник игрока").fill(player); + await page.getByRole("button", { name: "Отправить на проверку" }).click(); + await expect(page.getByText("Улов отправлен на модерацию. Спасибо!")).toBeVisible(); + + const response = await request.get(`${process.env.BOOTSTRAP_API_URL}/api/v1/admin/catch-reports?status=pending`, { + headers: { Authorization: `Bearer ${process.env.BOOTSTRAP_ADMIN_TOKEN}` }, + }); + expect(response.ok()).toBeTruthy(); + expect(await response.text()).toContain(player); +}); diff --git a/compose.production.yaml b/compose.production.yaml index b936270..291dfd3 100644 --- a/compose.production.yaml +++ b/compose.production.yaml @@ -105,7 +105,10 @@ services: limits: {cpus: "1.00", memory: 1G} web: - build: ./apps/web + build: + context: ./apps/web + args: + PUBLIC_API_URL: https://${SITE_DOMAIN:?Set SITE_DOMAIN} restart: unless-stopped environment: PUBLIC_API_URL: https://${SITE_DOMAIN:?Set SITE_DOMAIN} diff --git a/deploy/README.md b/deploy/README.md index 91ae8ac..f136aea 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -32,6 +32,14 @@ curl -fsS https://rf4spotter.ru/health curl -fsS https://rf4spotter.ru/ready ``` +До запуска на сервере можно воспроизвести полный production bootstrap на пустых изолированных volumes. Скрипт собирает образы, применяет миграции, проверяет отсутствие демо-уловов, readiness и отправку заявки: + +```bash +./deploy/test-production-bootstrap.sh +``` + +По умолчанию временно используются только loopback-порты `14321` и `18000`; PostgreSQL и MinIO наружу не публикуются. Контур и volumes удаляются после проверки. Drill успешно пройден 6 сентября 2026 года. + API-контейнер перед стартом применяет Alembic-миграции и запускает идемпотентный seed. В production он добавляет только минимальные справочники и точки; демонстрационные уловы жёстко отключены настройкой `SEED_DEMO_DATA=false`. Проверка TLS и маршрутизации: @@ -90,7 +98,6 @@ Drill успешно пройден 6 сентября 2026 года. На це ## 7. Что ещё блокирует приглашение альфа-пользователей -- проверка полного bootstrap на пустых volumes; - политика хранения и удаления пользовательских данных; - базовый мониторинг `/ready`, диска и срока TLS-сертификата. diff --git a/deploy/compose.bootstrap.yaml b/deploy/compose.bootstrap.yaml new file mode 100644 index 0000000..fb1f2ee --- /dev/null +++ b/deploy/compose.bootstrap.yaml @@ -0,0 +1,8 @@ +services: + api: + ports: + - "127.0.0.1:${BOOTSTRAP_API_PORT:-18000}:8000" + web: + networks: [backend, edge] + ports: + - "127.0.0.1:${BOOTSTRAP_WEB_PORT:-14321}:4321" diff --git a/deploy/test-production-bootstrap.sh b/deploy/test-production-bootstrap.sh new file mode 100755 index 0000000..58bb6c3 --- /dev/null +++ b/deploy/test-production-bootstrap.sh @@ -0,0 +1,32 @@ +#!/bin/sh +set -eu + +repo=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd) +cd "$repo" +project="rf4-bootstrap-$$" +export COMPOSE_PROJECT_NAME="$project" +export BOOTSTRAP_API_PORT=${BOOTSTRAP_API_PORT:-18000} +export BOOTSTRAP_WEB_PORT=${BOOTSTRAP_WEB_PORT:-14321} +export POSTGRES_PASSWORD=bootstrap-postgres-password +export DATABASE_URL=postgresql+psycopg://rf4:bootstrap-postgres-password@db:5432/rf4_spotter +compose="docker compose --env-file .env.production.example -f compose.production.yaml -f deploy/compose.bootstrap.yaml" + +cleanup() { + status=$? + if [ "$status" -ne 0 ]; then + $compose ps >&2 || true + $compose logs --no-color api web db minio >&2 || true + fi + $compose down --volumes --remove-orphans >/dev/null 2>&1 || true +} +trap cleanup EXIT INT TERM + +$compose up --build -d --wait db minio api web +curl -fsS "http://127.0.0.1:$BOOTSTRAP_API_PORT/ready" >/dev/null +curl -fsS "http://127.0.0.1:$BOOTSTRAP_WEB_PORT/" >/dev/null +test "$($compose exec -T db psql -At -U rf4 -d rf4_spotter -c 'select version_num from alembic_version')" = "0010" +test "$($compose exec -T db psql -At -U rf4 -d rf4_spotter -c 'select count(*) from fish')" = "2" +test "$($compose exec -T db psql -At -U rf4 -d rf4_spotter -c 'select count(*) from waterbody')" = "2" +test "$($compose exec -T db psql -At -U rf4 -d rf4_spotter -c 'select count(*) from catch_report')" = "0" +WEB_URL="http://127.0.0.1:$BOOTSTRAP_WEB_PORT" BOOTSTRAP_API_URL="http://127.0.0.1:$BOOTSTRAP_API_PORT" BOOTSTRAP_ADMIN_TOKEN=replace-with-at-least-32-random-characters npm --prefix apps/web run test:bootstrap +echo "Production bootstrap passed from empty volumes" diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index af38982..04c7def 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -2,7 +2,7 @@ Этот файл — рабочий источник правды по развитию проекта. После завершения задачи её чекбокс меняется с `[ ]` на `[x]`, рядом добавляется ссылка на коммит или короткое подтверждение проверки. Новые задачи добавляются в соответствующий этап, а не хранятся только в переписке. -Последняя сверка плана со спецификацией, кодом и UI/UX-аудитом: 5 сентября 2026 года. +Последняя сверка плана со спецификацией, кодом и UI/UX-аудитом: 6 сентября 2026 года. Обозначения: @@ -59,7 +59,7 @@ - [x] Добавить health/readiness-проверки PostgreSQL, MinIO, API и импорта; отразить их в Compose (`/health` без зависимостей, `/ready` с компонентами и режимом обязательного импорта). - [x] Добавить структурированные JSON-логи без пользовательских секретов и персональных технических данных (whitelist полей, redaction, request ID; Uvicorn access-log отключён). - [x] Добавить Gitea Actions CI: backend tests, Astro check/build, E2E и применение всех миграций на чистой PostgreSQL; сохранять логи Compose и Playwright-артефакты при падении (`.gitea/workflows/ci.yml`). -- [ ] Добавить отдельный тест полного bootstrap: пустые volumes → миграции → seed → readiness → основной E2E. +- [x] Добавить отдельный тест полного bootstrap: пустые production volumes → миграции `0010` → seed без демо-уловов → readiness → браузерная отправка и проверка moderation API (`deploy/test-production-bootstrap.sh`, 6 сентября 2026). - [x] Сделать seed устойчивым к частично заполненной БД: справочники досеиваются независимо, демо-уловы идемпотентны и принудительно отключены в production; повторный/частичный запуск покрыт конфигурационными и интеграционными проверками. - [ ] Проверить списочные API по требованию раздела 12: пагинация, предсказуемая сортировка и валидация фильтров для справочников, импортов, модерации и внешнего staging. - [ ] Проверить необходимые индексы PostgreSQL и планы запросов для activity, модерации, дедупликации и очистки rate limit; зафиксировать допустимый бюджет запросов пилота. @@ -79,7 +79,7 @@ - [ ] Добавить smoke-проверку административной очереди внешних источников на desktop/mobile без публикации реальных записей. - [ ] Обновить README: архитектура, все переменные окружения, импорт, модерация, backup/restore, эксплуатация логов и известные ограничения. - [ ] Выбрать лицензию кода и политику использования данных. -- [ ] Завершить production-профиль для `rf4spotter.ru`: Compose, Caddy/TLS, закрытые внутренние сервисы, CORS, resource limits, fail-fast секреты, backup/restore и runbook добавлены; остаются полный bootstrap и проверка на целевом сервере. +- [ ] Завершить production-профиль для `rf4spotter.ru`: Compose, Caddy/TLS, закрытые внутренние сервисы, CORS, resource limits, fail-fast секреты, backup/restore, runbook и полный bootstrap готовы; остаётся проверка DNS/TLS на целевом сервере. - [ ] Заменить демонстрационные секреты и определить целевое размещение перед внешней публикацией. ## Источники данных и согласование @@ -115,17 +115,15 @@ ## Ближайший рабочий пакет -Технические health/readiness и безопасные логи готовы. Следующие пункты выполняются строго по одному: +Технический production-контур, health/readiness, backup/restore и безопасные логи готовы. Следующие пункты выполняются строго по одному: 1. защита официального импорта от конкурентных запусков; -2. полный bootstrap-тест и исправление seed для частично заполненной БД; -3. security-аудит admin/CORS/headers/secrets и повторной загрузки скриншота; -4. backup/restore PostgreSQL и MinIO с реальной проверкой восстановления; -5. пагинация/сортировка списочных API и индексы PostgreSQL; -6. UI/UX-пакет A: единая шкала активности и числительные; -7. UI/UX-пакеты B–D: мобильная главная, форма и рекорды; -8. accessibility/admin safety и Lighthouse; -9. production-профиль и финальное обновление README. +2. пагинация/сортировка списочных API и индексы PostgreSQL; +3. политика хранения и автоматическая очистка персональных/staging данных; +4. UI/UX-пакеты B–D: мобильная главная, форма и рекорды; +5. accessibility/admin safety и Lighthouse; +6. мониторинг, DNS/TLS и проверка production-профиля на целевом сервере; +7. финальное обновление README, лицензия кода и политика данных. После каждого пункта необходимо: