name: Quality Gates
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
# =============================================================================
# TIER 1: Fast Checks (< 2 minutes)
# =============================================================================
lint-and-format:
name: "Lint & Format Check"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install ruff mypy types-python-dateutil types-requests pydantic
pip install -e .
- name: Python Lint (ruff)
run: ruff check src/ tests/ --output-format=github
- name: Python Format Check
run: ruff format --check src/ tests/
- name: Type Check (mypy)
run: mypy src/boring/ --config-file=pyproject.toml --no-error-summary || echo "Type check completed with warnings"
# =============================================================================
# TIER 2: 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: '3.11'
- name: Install security tools
run: |
pip install bandit safety pip-audit tomli
- name: Bandit Security Scan
run: bandit -r src/ -ll -ii --format json --output bandit-report.json
- name: Dependency Vulnerability Check
run: |
# Extract dependencies from pyproject.toml
python -c "import tomli; deps = tomli.load(open('pyproject.toml', 'rb'))['project']['dependencies']; print('\n'.join(deps))" > /tmp/deps.txt
# Audit dependencies only (skip local package)
pip-audit -r /tmp/deps.txt --desc || echo "Vulnerability check completed with warnings"
continue-on-error: true
- name: Upload Security Reports
if: always()
uses: actions/upload-artifact@v4
with:
name: security-reports
path: bandit-report.json
# =============================================================================
# TIER 3: Unit Tests with Coverage (Blocking)
# =============================================================================
test:
name: "Test Suite"
runs-on: ubuntu-latest
needs: [lint-and-format]
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
pip install radon interrogate
- name: Check Code Complexity
run: |
radon cc src/boring/ -a -nb --total-average
radon mi src/boring/ -nb
- name: Check Docstring Coverage
run: |
interrogate -vv --fail-under=60 src/boring/ --ignore-init-module --ignore-magic || echo "Docstring coverage warning"
- name: Run Unit Tests with Coverage
run: |
pytest tests/unit/ \
--cov=src/boring \
--cov-report=xml \
--cov-report=term-missing \
--cov-report=html \
--cov-fail-under=50 \
-v --tb=short
- name: Upload Coverage Reports
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
fail_ci_if_error: false
continue-on-error: true
- name: Upload HTML Coverage Report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: htmlcov/
# =============================================================================
# TIER 4: Integration Tests
# =============================================================================
integration:
name: "Integration Tests"
runs-on: ubuntu-latest
needs: [test]
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -e ".[dev]"
- name: Run Integration Tests
run: pytest tests/integration/ -v --tb=short || echo "Integration tests completed with warnings"
# =============================================================================
# QUALITY GATE SUMMARY
# =============================================================================
quality-gate:
name: "Quality Gate Status"
runs-on: ubuntu-latest
needs: [lint-and-format, security, test, integration]
if: always()
steps:
- name: Check Quality Gate
run: |
echo "## Quality Gate Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.lint-and-format.result }}" == "success" ]; then
echo "✅ Lint & Format: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Lint & Format: FAILED" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ needs.security.result }}" == "success" ]; then
echo "✅ Security Scan: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Security Scan: REVIEW NEEDED" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ needs.test.result }}" == "success" ]; then
echo "✅ Test Suite: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Test Suite: FAILED" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Coverage Threshold**: 50%" >> $GITHUB_STEP_SUMMARY
- name: Fail if critical checks failed
if: needs.lint-and-format.result == 'failure' || needs.test.result == 'failure'
run: exit 1