gendesign/.forgejo/workflows/ci.yml
Light1YT eb251ba7e7
All checks were successful
CI / changes (push) Successful in 13s
CI / changes (pull_request) Successful in 12s
CI / frontend-tests (push) Successful in 53s
CI / openapi-codegen-check (push) Successful in 1m58s
CI / frontend-tests (pull_request) Successful in 47s
CI / openapi-codegen-check (pull_request) Successful in 1m50s
CI / backend-tests (push) Successful in 8m54s
CI / backend-tests (pull_request) Successful in 8m45s
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 2m50s
Deploy / build-frontend (push) Successful in 3m11s
Deploy / build-worker (push) Successful in 4m38s
Deploy / deploy (push) Successful in 2m1s
ci(infra): coverage-gate (#68) + OpenAPI codegen-assert (#69) + uptime monitoring (#75)
#68: pytest-cov + [tool.coverage] fail_under=65 (baseline 71%), backend-tests --cov + xml.
#69: openapi-codegen-check job (dump app.openapi() → openapi-typescript → git diff).
  Job использует node_modules-pinned openapi-typescript/prettier (НЕ npx --yes latest —
  иначе version-mismatch ложный diff). + regenerated api-types.ts из СВЕЖЕГО main openapi
  (8288 строк, включая #73 /freshness — синхронен).
#75: Uptime Kuma isolated stack + status.gendsgn.ru Caddy + external Telegram watchdog.
#77: no change — build cache достаточен.

Closes #68
Closes #69
Closes #75
Closes #77
2026-06-13 23:28:06 +05:00

274 lines
14 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

name: CI
# Forgejo Actions pytest gate for the MAIN backend (backend/).
# WHY THIS FILE EXISTS: Forgejo runs ONLY .forgejo/workflows/* — the
# .github/workflows/ci.yml pytest gate does NOT execute on git.gendsgn.ru
# (proven: Forgejo Actions runs показывают только deploy/build jobs). Без этого
# backend-изменения мержились + деплоились БЕЗ автотестов → live-баг #994
# (district 500) уехал в прод необнаруженным. Этот workflow добавляет реальный
# gate: backend-сьют GREEN (1687 passed / 0 failed) после CI-rehab 1+2.
#
# Lane = MOCK-ONLY (day 1): нет postgres service-контейнера — сьют мокает БД,
# единственный real-Postgres тест (tests/sql/ mv_layout) self-skip'ается через
# connectivity-probe. PDF-тесты (WeasyPrint) РЕАЛЬНО ИДУТ здесь (libpango
# установлен ниже), тогда как на macOS-dev они runtime-skip'аются.
# FUTURE: добавить `postgis/postgis:16-3.4` service + гонять mv_layout — см.
# .github/workflows/ci.yml как образец service-блока.
on:
pull_request:
branches: [main]
push:
branches:
# Mirror the bot-PR / feature-branch flow в .claude/rules/git-pr.md:
# PR получает gate, прямые пуши в feature-ветки — тоже.
- "feat/**"
- "fix/**"
- "refactor/**"
- "chore/**"
- "docs/**"
- "perf/**"
- "test/**"
- "ci/**"
- "hotfix/**"
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Paths-filter: gate бежит ТОЛЬКО когда поменялся backend-код или SQL,
# которые этот backend читает (mirror deploy.yml changes-job). На чисто
# frontend/docs PR job no-op'ится → дёшево.
changes:
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
backend:
- 'backend/**'
- 'data/sql/**'
- '.forgejo/workflows/ci.yml'
frontend:
- 'frontend/**'
- '.forgejo/workflows/ci.yml'
backend-tests:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.backend == 'true'
defaults:
run:
working-directory: backend
env:
# TESTING=1 активирует RBAC-bypass (app/main.py rbac_guard пропускает
# запросы при settings.testing=True) — иначе 401 на всём /api/v1.
TESTING: "1"
# Stub DSN: psycopg v3 требует parseable URL на импорте; реального коннекта
# нет — DB-тесты мокаются, real-DB тест (tests/sql/) self-skip'ается через
# connectivity-probe к этому хосту (5432 недоступен → skip).
DATABASE_URL: postgresql+psycopg://test:test@localhost:5432/test
REDIS_URL: redis://localhost:6379/0
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
# Официальный standalone-инсталлер. НЕ astral-sh/setup-uv — он ломается
# на Forgejo-runner с PEP 668 externally-managed-environment (#666 CI).
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install system deps for geo + WeasyPrint
# libpq/gdal/proj/geos — geo-стек (geopandas/shapely/pyproj).
# libcairo2/libpango* — нативные либы WeasyPrint: с ними PDF-тесты
# (tests/test_layout_tz_pdf.py) РЕАЛЬНО ИДУТ на CI (на macOS-dev они
# module-skip'аются probe'ом native-libs). Mirror .github ci.yml:55-57.
run: |
sudo apt-get update
sudo apt-get install -y libpq-dev libgdal-dev libproj-dev libgeos-dev \
libcairo2 libpango-1.0-0 libpangoft2-1.0-0
- name: Install Python deps (incl. dev group — ruff, pytest)
# backend/uv.lock закоммичен → --frozen (детерминированно, fail при
# дрейфе lock vs pyproject). dev-группа ставится по умолчанию (ruff/pytest).
run: uv sync --frozen
- name: Lint (ruff check)
# Дешёвый fail-fast. NB: `ruff format --check` НАМЕРЕННО НЕ в gate —
# на момент создания 17 файлов repo-wide дали бы day-1 red. Отдельный
# format-pass = future enhancement. `ruff check .` сейчас green.
run: uv run ruff check .
- name: Test (pytest + coverage gate)
# --ignore=tests/smoke: prod-smoke бьёт по live https://gendsgn.ru
# (помечены @pytest.mark.prod_smoke; pyproject addopts уже их deselect'ит,
# но --ignore — belt-and-suspenders на случай сбора фикстур).
# tests/integration self-skip'ается через requires_test_db (skipif на
# TEST_DATABASE_URL, который тут не задан) → НЕ игнорим, оно чисто skip'ается.
# tests/sql/ mv_layout self-skip'ается через Postgres-connectivity probe
# (5432 недоступен в этом mock-lane) → SKIP. Это intended.
# PDF-тесты ИДУТ (libpango выше). Target: 0 failed, skips OK.
#
# Coverage-gate (#68): --cov=app меряет покрытие пакета app/.
# --cov-fail-under=65 → job RED если покрытие упало ниже baseline
# (измерено 2026-06: mock-lane сьют ~71%, см. [tool.coverage] в pyproject;
# 65 = floor с запасом, не flaky). На CI PDF-тесты РЕАЛЬНО идут (libpango),
# поэтому реальное CI-покрытие ≥ локально-измеренного floor.
# coverage.xml — артефакт для будущего Codecov/Coveralls upload (#68 badge).
# term-missing → видно непокрытые строки прямо в job-логе.
run: |
uv run pytest -q --ignore=tests/smoke \
--cov=app \
--cov-report=term-missing:skip-covered \
--cov-report=xml:coverage.xml \
--cov-fail-under=65
- name: Coverage summary → job output
# Дешёвый human-readable итог. Бежит даже если gate упал (if: always) —
# чтобы было видно НАСКОЛЬКО просело покрытие, а не только "fail".
# GITHUB_STEP_SUMMARY поддержан не во всех версиях Forgejo act_runner →
# если переменная пустая/файла нет, печатаем в обычный лог (fallback).
if: always()
run: |
[ -f coverage.xml ] || { echo "coverage.xml отсутствует — пропускаю summary"; exit 0; }
report="$(uv run coverage report --skip-covered --sort=cover | tail -40)"
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
{ echo '```'; echo "$report"; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
else
echo "$report"
fi
frontend-tests:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.frontend == 'true'
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- name: Set up Node
# Node 20 — совпадает с major из frontend/Dockerfile (node:20-alpine).
# cache=npm + cache-dependency-path на lockfile → переиспользуем ~/.npm
# между прогонами (mirror Dockerfile's `--mount=type=cache,target=/root/.npm`).
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install deps (npm ci, frozen lockfile)
# ТОЧНЫЕ флаги из frontend/Dockerfile (deps stage):
# --legacy-peer-deps — Tailwind 4 alpha + React 19 peer-dep mismatches;
# --no-audit --no-fund — тише и быстрее в CI. `ci` (не `install`) =
# детерминированно из package-lock.json, fail при дрейфе lock vs package.json.
run: npm ci --legacy-peer-deps --no-audit --no-fund
- name: Test (vitest)
# `npm run test` = `vitest run` (single-shot, не watch). Только тесты —
# `next build` НАМЕРЕННО не здесь (тяжёлый, verified в deploy.yml build).
run: npm run test
# OpenAPI → TS codegen drift-gate (#69). Защищает фронт от молчаливого
# рассинхрона типов: если backend изменил OpenAPI-схему, а
# frontend/src/lib/api-types.ts не перегенерён (`npm run codegen`) — job RED.
#
# Бежит когда поменялся backend ИЛИ frontend (backend-change может застейлить
# типы даже без правок во frontend/). Отдельный job (не внутри frontend-tests),
# чтобы vitest и codegen-gate скейлились независимо.
#
# WHY no running server: `npm run codegen` бьёт по http://localhost:8000/openapi.json,
# но схема = app.openapi() — её можно сдампить из python БЕЗ uvicorn/DB
# (app.main импортируется под TESTING=1 со stub-DSN, как в backend-tests).
# Это убирает flaky port-wait. openapi-typescript v7 принимает локальный файл.
#
# WHY prettier: committed api-types.ts форматируется prettier'ом через
# pre-commit hook (mirrors-prettier, no config → defaults, 2-space). Raw
# openapi-typescript отдаёт 4-space → diff-шум. Прогоняем prettier (defaults)
# на regen, чтобы сравнивать ТОЛЬКО контент, не форматирование.
openapi-codegen-check:
runs-on: ubuntu-latest
needs: changes
if: |
needs.changes.outputs.backend == 'true' ||
needs.changes.outputs.frontend == 'true'
env:
# Те же stub-переменные, что backend-tests: psycopg требует parseable URL
# на импорте; реального коннекта нет (схему дампим, не обслуживаем запросы).
TESTING: "1"
DATABASE_URL: postgresql+psycopg://test:test@localhost:5432/test
REDIS_URL: redis://localhost:6379/0
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install system deps for geo + WeasyPrint
# app.main транзитивно тянет geo/PDF-модули. На macOS-dev импорт схемы
# проходит и без этих либ, но на ubuntu ставим как backend-tests
# (belt-and-suspenders, чтобы import app.main точно не падал).
run: |
sudo apt-get update
sudo apt-get install -y libpq-dev libgdal-dev libproj-dev libgeos-dev \
libcairo2 libpango-1.0-0 libpangoft2-1.0-0
- name: Install backend deps (uv sync --frozen)
working-directory: backend
run: uv sync --frozen
- name: Install frontend deps (npm ci)
working-directory: frontend
run: npm ci --legacy-peer-deps --no-audit --no-fund
- name: Dump OpenAPI schema from app (no server)
working-directory: backend
run: uv run python -c "import json; from app.main import app; print(json.dumps(app.openapi()))" > /tmp/openapi.json
- name: Regenerate api-types.ts + format (prettier defaults)
working-directory: frontend
# 1) openapi-typescript из дампнутого файла (эквивалент `npm run codegen`,
# который читает ту же схему по URL). 2) prettier (defaults, как
# pre-commit hook) → формат совпадает с committed-файлом.
run: |
npx openapi-typescript /tmp/openapi.json -o src/lib/api-types.ts
npx prettier --write src/lib/api-types.ts
- name: Assert api-types.ts is up-to-date
working-directory: frontend
# git diff --exit-code: 0 если файл не изменился (типы актуальны),
# 1 если regen дал другой результат (типы устарели → fail с подсказкой).
run: |
if ! git diff --exit-code -- src/lib/api-types.ts; then
echo "::error::frontend/src/lib/api-types.ts устарел относительно backend OpenAPI." \
"Запусти: cd frontend && npm run codegen (с backend на :8000), затем закоммить." \
"Pre-commit prettier отформатирует автоматически."
exit 1
fi
echo "✓ api-types.ts актуален относительно backend OpenAPI-схемы."