gendesign/.forgejo/workflows/ci.yml
bot-backend 7f732ff24d
Some checks failed
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Successful in 59s
CI / openapi-codegen-check (pull_request) Failing after 1m42s
CI / backend-tests (pull_request) Failing after 8m50s
ci: drop push-on-feature-branch trigger to kill duplicate CI runs (#1709)
Каждый коммит в ветку с открытым PR триггерил ДВА прогона ci.yml на один
SHA: push-событие (github.ref=refs/heads/<branch>) и pull_request-событие
(github.ref=refs/pull/<N>/merge). Разный github.ref → разные concurrency-
группы → прогоны не отменяли друг друга → 2x нагрузка на и так дефицитные
2 раннера (CI-шторм 2026-06-17, деплой tradein #1699 простоял 17 мин).

В bot-пайплайне каждый коммит идёт через PR, поэтому pull_request гейтит
его полностью; push-прогон был чистым дублем. Оставляю только pull_request:
теперь github.ref стабилен на весь PR и concurrency cancel-in-progress
корректно отменяет superseded-прогоны при новом пуше.

Refs #1709
2026-06-17 22:09:01 +03:00

273 lines
15 KiB
YAML
Raw Permalink 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 — НЕТ push-триггера на feature-ветки (CI-шторм #1709).
# WHY: раньше был и push: [feat/**,fix/**,...]. Каждый коммит в ветку с открытым
# PR триггерил ДВА прогона на ОДИН SHA: push-событие (github.ref=refs/heads/<branch>)
# и pull_request-событие (github.ref=refs/pull/<N>/merge). Разный github.ref →
# разные concurrency-группы (см. ниже) → прогоны НЕ отменяют друг друга → 2× job
# при и так дефицитных раннерах. В bot-пайплайне каждый коммит идёт через PR, так
# что pull_request гейтит его полностью; push-прогон был чистым дублем.
# Trade-off: push в feature-ветку БЕЗ открытого PR не получит CI до открытия PR
# (бот открывает PR сразу после первого push) — приемлемо.
pull_request:
branches: [main]
concurrency:
# Теперь, когда остался только pull_request, github.ref стабилен на весь PR
# (refs/pull/<N>/merge) → новый push в ветку PR отменяет предыдущий незавершённый
# прогон ЭТОГО PR (cancel-in-progress) вместо накопления параллельных.
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-схемы."