name: CI – Lint, Test, Build on: push: branches: [main, develop] pull_request: branches: [main, develop] env: PYTHON_VERSION: "3.12" ENCRYPTION_KEY: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" SECRET_KEY: "ci-test-secret-key-for-github-actions-only-not-for-production" DATABASE_URL: "postgresql+asyncpg://pecflow:pecflow_ci@localhost:5432/pecflow_test" DATABASE_URL_SYNC: "postgresql://pecflow:pecflow_ci@localhost:5432/pecflow_test" REDIS_URL: "redis://localhost:6379/0" jobs: # ── Lint Backend ───────────────────────────────────────────────────────────── lint-backend: name: Lint Backend (ruff + mypy) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: pip - name: Install dependencies working-directory: backend run: | pip install --upgrade pip pip install -e ".[dev]" - name: Run ruff (lint) working-directory: backend run: ruff check app tests --output-format=github - name: Run ruff (format check) working-directory: backend run: ruff format --check app tests - name: Run mypy (type check) working-directory: backend env: ENCRYPTION_KEY: ${{ env.ENCRYPTION_KEY }} SECRET_KEY: ${{ env.SECRET_KEY }} DATABASE_URL: ${{ env.DATABASE_URL }} DATABASE_URL_SYNC: ${{ env.DATABASE_URL_SYNC }} run: mypy app --ignore-missing-imports --no-strict-optional # ── Test Backend ───────────────────────────────────────────────────────────── test-backend: name: Test Backend (pytest) runs-on: ubuntu-latest services: postgres: image: postgres:16-alpine env: POSTGRES_DB: pecflow_test POSTGRES_USER: pecflow POSTGRES_PASSWORD: pecflow_ci ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10 redis: image: redis:7-alpine ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 3s --health-retries 10 steps: - uses: actions/checkout@v4 - name: Setup Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: pip - name: Install dependencies working-directory: backend run: | pip install --upgrade pip pip install -e ".[dev]" pip install aiosqlite # per test integration con SQLite - name: Run unit tests working-directory: backend env: ENCRYPTION_KEY: ${{ env.ENCRYPTION_KEY }} SECRET_KEY: ${{ env.SECRET_KEY }} DATABASE_URL: ${{ env.DATABASE_URL }} DATABASE_URL_SYNC: ${{ env.DATABASE_URL_SYNC }} run: pytest tests/unit -v --tb=short - name: Run integration tests working-directory: backend env: ENCRYPTION_KEY: ${{ env.ENCRYPTION_KEY }} SECRET_KEY: ${{ env.SECRET_KEY }} DATABASE_URL: sqlite+aiosqlite:///./test_ci.db DATABASE_URL_SYNC: sqlite:///./test_ci.db run: pytest tests/integration -v --tb=short - name: Run all tests with coverage working-directory: backend env: ENCRYPTION_KEY: ${{ env.ENCRYPTION_KEY }} SECRET_KEY: ${{ env.SECRET_KEY }} DATABASE_URL: sqlite+aiosqlite:///./test_ci_cov.db DATABASE_URL_SYNC: sqlite:///./test_ci_cov.db run: | pytest tests/ \ --cov=app \ --cov-report=xml \ --cov-report=term-missing \ -v --tb=short continue-on-error: true # non blocca la CI se coverage < target - name: Upload coverage to GitHub uses: codecov/codecov-action@v4 with: file: backend/coverage.xml flags: backend continue-on-error: true # ── Build Docker ───────────────────────────────────────────────────────────── build-docker: name: Build Docker Image runs-on: ubuntu-latest needs: [lint-backend, test-backend] steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build backend image uses: docker/build-push-action@v5 with: context: ./backend file: ./backend/Dockerfile push: false tags: pecflow-backend:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max # ── Security Scan ───────────────────────────────────────────────────────────── security: name: Security Scan runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install pip-audit run: pip install pip-audit - name: Audit Python dependencies working-directory: backend run: pip-audit -r <(pip install -e ".[dev]" --dry-run 2>/dev/null || echo "") || true continue-on-error: true - name: Scan for secrets (Gitleaks) uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} continue-on-error: true