gendesign/.forgejo/workflows/ci-tradein.yml
bot-backend 79a130736f
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Successful in 47s
CI / frontend-tests (pull_request) Successful in 1m39s
CI Trade-In / frontend-checks (pull_request) Successful in 1m41s
CI / openapi-codegen-check (pull_request) Successful in 2m21s
CI Trade-In / backend-tests (pull_request) Successful in 3m32s
CI / backend-tests (pull_request) Successful in 15m25s
chore(ci): деплой на изменение ролей, ruff-гейт для МЕРЫ, удаление мёртвого workflow
- deploy.yml: добавлен auth/** в path-триггеры. app.core.auth кэширует
  roles.yaml на весь lifetime процесса (@lru_cache) — без триггера правка
  ролей молча не применялась до случайного чужого деплоя.
- ci-tradein.yml: добавлен блокирующий шаг `ruff check` — гейт для tradein
  backend его не гонял вообще. Заодно почищены 3 срабатывания RUF059
  (unused unpacked vars) в test_estimator_pure_units.py:381 — версия ruff,
  зафиксированная в uv.lock (0.15.20), новее той, на которой аудит проверял
  "чистое дерево" (0.7.4, как в .pre-commit-config.yaml); лок не трогаем —
  правим 3 места по established convention (`_` уже используется в 3 других
  тестах того же файла).
- Удалён мёртвый .github/workflows/ci.yml (Forgejo его не исполняет; GitHub
  mirror отстал на 500+ коммитов). Блок postgis-service перенесён как
  закомментированный образец в .forgejo/workflows/ci.yml (тестов против
  живой БД пока нет ни одного).
- README.md: убрана ложная привязка mypy к живому CI-гейту (его там нет,
  только ruff+pytest+coverage); убран stale-блок про .github/workflows/
  (deploy-obsidian.yml там больше не лежит).
2026-08-06 20:23:51 +03:00

222 lines
13 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 Trade-In
# Forgejo Actions pre-merge gate for the SUBPROJECT tradein-mvp/.
# WHY THIS FILE EXISTS (#2208): основной .forgejo/workflows/ci.yml гейтит
# ТОЛЬКО backend/** + frontend/** главного стека — tradein-PR проходили на
# пусто-зелёных чеках (paths-filter no-op), а pytest tradein жил лишь в
# post-merge deploy-tradein.yml. Итог: сломанный tradein-код мержился в main
# и обнаруживался только на деплое. Этот workflow добавляет РЕАЛЬНЫЙ pre-merge
# gate: tradein-backend pytest + tradein-frontend type-check/lint ДО мержа.
on:
# ТОЛЬКО pull_request — НЕТ push-триггера на feature-ветки (CI-шторм #1709,
# см. подробное обоснование в ci.yml). Кратко: раньше push+pull_request на один
# SHA давали разный github.ref → разные concurrency-группы → 2× прогон на
# дефицитных раннерах. В bot-пайплайне каждый коммит идёт через PR, так что
# pull_request гейтит его полностью; push-прогон был чистым дублем.
pull_request:
branches: [main]
concurrency:
# github.ref стабилен на весь PR (refs/pull/<N>/merge) → новый push в ветку PR
# отменяет предыдущий незавершённый прогон ЭТОГО PR вместо накопления.
group: ci-tradein-${{ github.ref }}
cancel-in-progress: true
jobs:
# Paths-filter: гейт бежит ТОЛЬКО когда поменялся tradein-код.
# PR не трогающий tradein-mvp/ → оба job'а no-op'ятся → дёшево.
changes:
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
browser: ${{ steps.filter.outputs.browser }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
backend:
- 'tradein-mvp/backend/**'
- 'tradein-mvp/packages/**'
# workspace root: lock-only bump (uv lock --upgrade) или правка
# [tool.uv.workspace] меняют реальные зависимости → гейт обязан бежать.
- 'tradein-mvp/uv.lock'
- 'tradein-mvp/pyproject.toml'
# auth/roles.yaml — общий RBAC-конфиг обоих стеков, лежит В КОРНЕ
# репы и монтируется в tradein-backend (/app/auth/roles.yaml).
# tests/test_rbac.py читает именно его, поэтому правка ролей обязана
# гонять и этот гейт. Без строки правка roles.yaml не запускала НИ
# ОДИН сьют (та же дыра закрыта симметрично в ci.yml) — так на main
# уехал красный test_get_role_known_users (2026-07-30 → PR #2587).
- 'auth/**'
- '.forgejo/workflows/ci-tradein.yml'
frontend:
- 'tradein-mvp/frontend/**'
- '.forgejo/workflows/ci-tradein.yml'
browser:
# Сайдкар — сервис ВНЕ uv-воркспейса (tradein-mvp/pyproject.toml
# members = backend + packages/*), со своим Dockerfile и без pyproject,
# поэтому и фильтр отдельный: backend-гейт его тестов не видел вовсе.
- 'tradein-mvp/browser/**'
- '.forgejo/workflows/ci-tradein.yml'
backend-tests:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.backend == 'true'
defaults:
run:
working-directory: ./tradein-mvp/backend
env:
# psycopg v3 требует parseable URL на импорте; реального коннекта нет —
# DB-тесты мокаются (mirror deploy-tradein.yml test-job).
DATABASE_URL: postgresql+psycopg://test:test@localhost:5432/test
steps:
- uses: actions/checkout@v4
- 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: Cache uv packages
# Кросс-прогонный кэш скачанных/собранных wheel'ов (~/.cache/uv).
# continue-on-error чтобы сбой cache-бэкенда раннера НИКОГДА не ронял gate.
# Ключ по workspace-локу tradein-mvp/uv.lock (tracked с воркспейса #2137).
uses: actions/cache@v4
continue-on-error: true
with:
path: ~/.cache/uv
key: uv-tradein-${{ runner.os }}-${{ hashFiles('tradein-mvp/uv.lock') }}
restore-keys: |
uv-tradein-${{ runner.os }}-
- name: Sync deps (incl. dev group — pytest, ruff)
# Workspace-лок tradein-mvp/uv.lock TRACKED (с воркспейса #2137; gitignored
# только старый backend/uv.lock) → --frozen детерминирован и зеркалит
# Dockerfile (uv sync --frozen --no-dev там). uv находит workspace root
# вверх от cwd.
run: uv sync --frozen
- name: Lint (ruff check)
# Правила выбраны в tradein-mvp/backend/pyproject.toml ([tool.ruff.lint]
# select = E F I B UP N RUF), но до этого шага их никто не гонял в CI —
# "дерево чистое" было непроверенным утверждением, а не гарантией.
# Версия ruff — та же, что в tradein-mvp/uv.lock (--frozen из шага выше),
# т.е. ровно то, что видит `uv sync --frozen` в Dockerfile.
# Blocking: любое нарушение → job RED (не декоративно).
run: uv run ruff check .
- name: Run pytest (tradein-mvp/backend)
# БЕЗ deselect'ов — сьют гоняется целиком (#2722).
#
# Здесь два года жил `--deselect tests/test_search_api.py::test_search_cache_hit`
# с объяснением «падает ТОЛЬКО в whole-suite ordering, в изоляции проходит —
# global-state leak из другого модуля». Объяснение было неверным в обеих
# половинах: тест падал и в изоляции тоже (401 vs 200), потому что он —
# единственный HTTP-тест в своём файле — ходил в /api/v1/search БЕЗ заголовка
# X-Authenticated-User, а RBAC-гард отвечает на такое 401 (ровно то, что
# фиксирует tests/test_estimate_idor.py). Причина была в тесте, а не в порядке;
# заголовок добавлен, deselect снят, полный прогон зелёный.
#
# Не добавлять сюда новые deselect'ы: молча выключенный тест — это тот же
# класс дефекта, что каталог вне пайплайна (#2722). Тест либо чинится, либо
# помечается xfail с причиной В КОДЕ, где её видно рядом с самим тестом.
#
# NB: в deploy-tradein.yml (post-merge test-job) свой экземпляр этого
# deselect'а — он остаётся до #2680, который правит тот файл. Расхождение
# безвредно: pre-merge гейт тест гоняет, post-merge просто пропустит зелёный.
run: uv run pytest -q
# Тесты браузерного сайдкара (#2722). До этого job'а они не бежали НИГДЕ:
# ci-tradein гейтил только backend/frontend, deploy-tradein — тоже, а каталог
# вне uv-воркспейса, так что и `uv run pytest` из backend их не собирал. Итог:
# 4 теста лежали красными на main (с 2026-06-20 и 2026-07-02), файл при этом
# правился, и никто не узнал. Починка — PR #2724, этот job закрывает причину.
#
# Почему НЕ переиспользуем backend-job:
# 1. сайдкар не член воркспейса → `uv sync --frozen` его не ставит;
# 2. aiohttp (единственная не-stdlib зависимость сьюта) нет в tradein-mvp/uv.lock;
# 3. разный scope paths-filter: правка browser/ не должна гонять backend-сьют.
browser-tests:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.browser == 'true'
# Сьют идёт ~15с. Лимит — страховка от зависшего теста: у сайдкара нет своего
# pyproject, а значит и pytest-timeout'а backend'а (timeout=120). Дешевле
# взять нативный job-таймаут, чем тащить плагин ради одного каталога.
timeout-minutes: 10
defaults:
run:
working-directory: ./tradein-mvp/browser
steps:
- uses: actions/checkout@v4
- name: Set up Python
# 3.12 — как в browser/Dockerfile (FROM python:3.12-slim).
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install test deps
# ВЕСЬ список: pytest + aiohttp. Ни playwright, ни camoufox, ни закачки
# Firefox — camoufox импортируется ЛЕНИВО внутри _launch_browser
# (server.py, `from camoufox.async_api import AsyncCamoufox`), а сами тесты
# мокают _ensure_browser/_do_fetch и грузят server.py по пути через importlib.
# pytest-asyncio тоже НЕ нужен: ни одного `async def test_` — каждый тест сам
# крутит asyncio.run(). Проверено локально на venv ровно из этих двух пакетов.
#
# aiohttp без пина — ровно как в browser/Dockerfile (`pip install ... aiohttp`),
# то есть гейт видит ту же версию, что уедет в образ. Пин здесь означал бы
# проверку версии, которой в проде нет.
run: pip install pytest aiohttp
- name: Run pytest (tradein-mvp/browser)
# Каталог без pyproject/pytest.ini → дефолтная конфигурация, ничего
# не deselect'ится. Ожидание: 108 passed, 0 failed, 0 skipped.
run: pytest -q
frontend-checks:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.frontend == 'true'
defaults:
run:
working-directory: ./tradein-mvp/frontend
steps:
- uses: actions/checkout@v4
- name: Set up Node
# Node 20 — major из tradein-mvp/frontend/Dockerfile (node:20-alpine).
# npm-кэш setup-node НЕ настраиваем: в tradein-mvp/frontend нет
# package-lock.json (Dockerfile ставит через npm install), а cache=npm
# без lockfile падает. Кэш wheels/node тут не критичен для type-check/lint.
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install deps (npm install, no lockfile)
# ТОЧНЫЕ флаги из tradein-mvp/frontend/Dockerfile (deps stage):
# --legacy-peer-deps — Tailwind/React 19 peer-dep mismatches;
# --no-audit --no-fund — тише и быстрее в CI. `install` (не `ci`):
# в tradein-mvp/frontend НЕТ package-lock.json (есть pnpm-lock.yaml, но
# Dockerfile ставит именно npm install) → `npm ci` упал бы.
run: npm install --legacy-peer-deps --no-audit --no-fund
- name: Type-check (tsc --noEmit)
# Blocking: любая TS-ошибка → job RED.
run: npm run type-check
- name: Lint (next lint)
# Blocking: любая ESLint-ошибка → job RED.
run: npm run lint
- name: Mera-public isolation guard (#2631)
# Blocking: статический import-graph публичного лэндинга не должен
# достигать закрытого контура (useMe/lib/api/sessionId/isPathAllowed/
# GuardedRoute вне next/dynamic). Инвариант этапа 1 #2545.
run: npm run check:mera-public-isolation