when: event: [push, pull_request] branch: [dev, main, master] services: - name: postgres image: postgres:17-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: blog_test steps: - name: deps image: python:3.13 volumes: - /tmp/uv-cache:/root/.cache/uv environment: UV_CACHE_DIR: /root/.cache/uv UV_LINK_MODE: copy UV_PYTHON: "3.13" commands: - pip install uv - uv sync --group lints --group tests --group types --group dev - name: lint image: python:3.13 volumes: - /tmp/uv-cache:/root/.cache/uv environment: UV_CACHE_DIR: /root/.cache/uv UV_LINK_MODE: copy UV_PYTHON: "3.13" depends_on: [deps] commands: - pip install uv - uv run --no-sync ruff check . - uv run --no-sync ruff format --check . - uv run --no-sync isort --check-only . - name: type image: python:3.13 volumes: - /tmp/uv-cache:/root/.cache/uv environment: UV_CACHE_DIR: /root/.cache/uv UV_LINK_MODE: copy UV_PYTHON: "3.13" depends_on: [deps] commands: - pip install uv - uv run --no-sync mypy . - name: test-unit image: python:3.13 volumes: - /tmp/uv-cache:/root/.cache/uv environment: UV_CACHE_DIR: /root/.cache/uv UV_LINK_MODE: copy UV_PYTHON: "3.13" COVERAGE_FILE: .coverage.unit depends_on: [deps] commands: - pip install uv - uv run --no-sync pytest tests/unit/ -o "addopts=--cov=app --cov-report=term-missing --cov-fail-under=0" - name: test-integration image: python:3.13 volumes: - /tmp/uv-cache:/root/.cache/uv environment: UV_CACHE_DIR: /root/.cache/uv UV_LINK_MODE: copy UV_PYTHON: "3.13" DB_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/blog_test SKIP_INIT_DB: "1" COVERAGE_FILE: .coverage.integration depends_on: [deps] commands: - pip install uv - uv run --no-sync pytest tests/integration/ -v -o "addopts=--cov=app --cov-report=term-missing --cov-fail-under=0" - name: test-e2e image: python:3.13 volumes: - /tmp/uv-cache:/root/.cache/uv environment: UV_CACHE_DIR: /root/.cache/uv UV_LINK_MODE: copy UV_PYTHON: "3.13" DB_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/blog_test SKIP_INIT_DB: "1" depends_on: [test-integration] commands: - pip install uv - uv run --no-sync alembic upgrade head - apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 - uv run --no-sync playwright install chromium - uv run --no-sync blog & - sleep 5 - uv run --no-sync pytest tests/e2e/ -v --no-cov - name: coverage image: python:3.13 volumes: - /tmp/uv-cache:/root/.cache/uv environment: UV_CACHE_DIR: /root/.cache/uv UV_LINK_MODE: copy UV_PYTHON: "3.13" depends_on: [test-unit, test-integration, test-e2e] commands: - pip install uv - uv run --no-sync coverage combine .coverage.unit .coverage.integration - uv run --no-sync coverage report --fail-under=70 --include=app/* - uv run --no-sync coverage html - name: pr-comment image: python:3.13 volumes: - /tmp/uv-cache:/root/.cache/uv environment: UV_CACHE_DIR: /root/.cache/uv UV_LINK_MODE: copy UV_PYTHON: "3.13" GITEA_API_TOKEN: from_secret: gitea_api_token depends_on: [coverage, lint, type] when: event: [pull_request] commands: - pip install uv - | SHA7=$(printf '%.7s' "${CI_COMMIT_SHA:-unknown}") COMMIT_URL="${CI_FORGE_URL}/${CI_REPO_OWNER}/${CI_REPO_NAME}/commit/${CI_COMMIT_SHA}" SOURCE="${CI_COMMIT_SOURCE:-${CI_COMMIT_SOURCE_BRANCH:-?}}" TARGET="${CI_COMMIT_TARGET:-${CI_COMMIT_TARGET_BRANCH:-?}}" PIPELINE_URL="${CI_PIPELINE_URL:-}" COVER=$(uv run --no-sync coverage report --include='app/*' | tail -1 | awk '{print $NF}') if [ -z "$GITEA_API_TOKEN" ]; then echo "pr-comment: GITEA_API_TOKEN not set, skipping" exit 0 fi FMT='{"body": "## CI Summary\n\n**Commit:** [`%s`](%s)\n**Branch:** `%s` → `%s`\n**Pipeline:** [View](%s)\n\n### Checks\n\n| Check | Status |\n|-------|--------|\n| Lint (ruff + isort) | ✅ |\n| Type check (mypy) | ✅ |\n| Unit tests | ✅ |\n| Integration tests | ✅ |\n| E2E tests | ✅ |\n| Coverage | **%s** |\n\n---\n*Reported by Woodpecker CI*"}' BODY=$(printf "$FMT" "$SHA7" "$COMMIT_URL" "$SOURCE" "$TARGET" "$PIPELINE_URL" "$COVER") curl -s -X POST "${CI_FORGE_URL}/api/v1/repos/${CI_REPO_OWNER}/${CI_REPO_NAME}/issues/${CI_COMMIT_PULL_REQUEST}/comments" -H "Authorization: token $${GITEA_API_TOKEN}" -H "Content-Type: application/json" --data-binary "$BODY"