name: Run Tests & Linting on: [push, pull_request] permissions: contents: read jobs: # ── Tests ────────────────────────────────────────────────────────────── test: name: Tests runs-on: ubuntu-latest services: redis: image: redis:7 ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 rabbitmq: image: rabbitmq:3-management ports: - 5672:5672 - 15672:15672 options: >- --health-cmd "rabbitmq-diagnostics -q ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout Code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install Dependencies run: | python -m pip install --upgrade pip pip install -r requirements-dev.txt - name: Run Tests # Exclude E2E tests (-m "not e2e") as they require Docker-in-Docker (testcontainers) # which isn't well-supported in GitHub Actions without additional DinD configuration. # E2E tests can be run locally with: pytest -m e2e run: pytest tests/ -v --cov=app --cov-report=xml --cov-report=term --junitxml=junit.xml -o junit_family=legacy -m "not e2e" - name: Upload coverage reports to Codecov if: ${{ !cancelled() }} uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.xml fail_ci_if_error: false - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} - name: Upload test artifacts if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: test-results path: | junit.xml coverage.xml # ── Flake8 ───────────────────────────────────────────────────────────── flake8: name: Flake8 runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install Dependencies run: | python -m pip install --upgrade pip pip install flake8 - name: Run Flake8 run: flake8 app/ --max-line-length=120 --extend-ignore=E203,W503 # ── Black ────────────────────────────────────────────────────────────── black: name: Black runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install Dependencies run: | python -m pip install --upgrade pip pip install black - name: Run Black run: black --check app/ --line-length=120 # ── Mypy ─────────────────────────────────────────────────────────────── mypy: name: Mypy runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install Dependencies run: | python -m pip install --upgrade pip pip install -r requirements-dev.txt - name: Run Mypy run: mypy app/ # ── Pylint ───────────────────────────────────────────────────────────── pylint: name: Pylint runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install Dependencies run: | python -m pip install --upgrade pip pip install -r requirements-dev.txt - name: Run Pylint run: pylint app/ # ── Bandit ───────────────────────────────────────────────────────────── bandit: name: Bandit runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install Dependencies run: | python -m pip install --upgrade pip pip install bandit - name: Run Bandit - Full Report if: ${{ !cancelled() }} run: bandit -r app/ -f json -o bandit-report.json || true - name: Run Bandit - Fail on High/Medium run: bandit -r app/ -ll - name: Upload Bandit Report if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: bandit-report path: bandit-report.json