| name: Backend CI |
|
|
| on: |
| push: |
| branches: [main] |
| pull_request: |
| branches: [main] |
|
|
| jobs: |
| lint: |
| name: Lint & Format Check |
| runs-on: ubuntu-latest |
| steps: |
| - uses: actions/checkout@v5 |
|
|
| - uses: actions/setup-python@v6 |
| with: |
| python-version: "3.12" |
|
|
| - name: Install ruff |
| run: pip install ruff |
|
|
| - name: Ruff lint |
| run: ruff check . |
|
|
| - name: Ruff format check |
| run: ruff format --check . |
|
|
| type-check: |
| name: Import & Syntax Validation |
| runs-on: ubuntu-latest |
| steps: |
| - uses: actions/checkout@v5 |
|
|
| - uses: actions/setup-python@v6 |
| with: |
| python-version: "3.12" |
| cache: pip |
|
|
| - name: Install dependencies |
| run: pip install -r requirements.txt |
|
|
| - name: Validate Python syntax (compile all) |
| run: python -m compileall app/ main.py -q |
|
|
| - name: Verify imports resolve |
| run: python -c "from app.core.config import get_settings; from app.models.db import Base; print('All imports OK')" |
|
|
| tests: |
| name: Pytest |
| runs-on: ubuntu-latest |
| steps: |
| - uses: actions/checkout@v5 |
|
|
| - uses: actions/setup-python@v6 |
| with: |
| python-version: "3.12" |
| cache: pip |
|
|
| - name: Install dependencies |
| run: | |
| pip install -r requirements.txt |
| pip install -r requirements-dev.txt |
| |
| - name: Run pytest with coverage |
| env: |
| DATABASE_URL: "sqlite:///:memory:" |
| JWT_SECRET: "${{ github.run_id }}-${{ github.run_attempt }}-ci-ephemeral" |
| ENVIRONMENT: test |
| LLM_API_KEY: test-key |
| CORS_ORIGINS: '["http://testserver"]' |
| run: pytest tests/ --cov=app --cov-report=xml --cov-report=term |
|
|
| - name: Upload coverage artifact |
| uses: actions/upload-artifact@v4 |
| if: always() |
| with: |
| name: backend-coverage |
| path: coverage.xml |
| retention-days: 14 |
|
|
| migration-check: |
| name: Database Migration Check |
| runs-on: ubuntu-latest |
| steps: |
| - uses: actions/checkout@v5 |
|
|
| - uses: actions/setup-python@v6 |
| with: |
| python-version: "3.12" |
| cache: pip |
|
|
| - name: Install dependencies |
| run: pip install -r requirements.txt |
|
|
| - name: Verify Alembic config |
| run: python -c "from alembic.config import Config; c = Config('alembic.ini'); print('Alembic config OK')" |
|
|
| dependency-audit: |
| name: Dependency Vulnerability Scan |
| runs-on: ubuntu-latest |
| |
| |
| |
| continue-on-error: true |
| steps: |
| - uses: actions/checkout@v5 |
|
|
| - uses: actions/setup-python@v6 |
| with: |
| python-version: "3.12" |
| cache: pip |
|
|
| - name: Install pip-audit |
| run: pip install pip-audit |
|
|
| - name: Audit backend requirements |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| run: | |
| pip-audit --requirement requirements.txt --desc \ |
| --ignore-vuln CVE-2026-1839 \ |
| --ignore-vuln CVE-2025-2953 \ |
| --ignore-vuln CVE-2025-3730 |
| |
| docker-build: |
| name: Docker Build |
| runs-on: ubuntu-latest |
| needs: [lint, type-check, tests] |
| services: |
| postgres: |
| image: pgvector/pgvector:pg16 |
| env: |
| POSTGRES_PASSWORD: postgres |
| POSTGRES_DB: depscreen |
| ports: |
| - 5432:5432 |
| options: >- |
| --health-cmd pg_isready |
| --health-interval 10s |
| --health-timeout 5s |
| --health-retries 5 |
| steps: |
| - uses: actions/checkout@v5 |
|
|
| - name: Build Docker image |
| run: docker build -t depscreen-backend . |
|
|
| - name: Verify container starts |
| run: | |
| docker run -d --name test-container \ |
| --network host \ |
| -e DATABASE_URL=postgresql://postgres:postgres@localhost:5432/depscreen \ |
| -e LLM_API_KEY=test \ |
| -e LLM_BASE_URL=https://test.example.com \ |
| -e LLM_MODEL=test \ |
| -e JWT_SECRET=test-secret \ |
| -e ENVIRONMENT=testing \ |
| -p 8000:8000 \ |
| depscreen-backend |
| echo "Waiting for container to start..." |
| for i in $(seq 1 60); do |
| if curl -sf http://localhost:8000/health/live > /dev/null 2>&1; then |
| echo "Health check passed after ${i}s" |
| break |
| fi |
| sleep 2 |
| done |
| curl -f http://localhost:8000/health/live || (docker logs test-container && exit 1) |
| docker stop test-container |
| |