name: CI on: push: branches: [ "**" ] pull_request: branches: [ "**" ] permissions: contents: read security-events: write jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Cache pip packages uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('backend/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Install system dependencies (Linux) if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y libmagic1 libmagic-dev file - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install -r backend/requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu - name: Install test extras run: | pip install pytest-timeout pytest-xdist coverage[toml] - name: Lint with flake8 run: | pip install flake8 flake8 backend/ --max-line-length=120 --exclude=backend/tests/,backend/__pycache__ || true continue-on-error: true - name: Type check with mypy run: | pip install mypy mypy backend/ --config-file=mypy.ini || true continue-on-error: true - name: Security audit with pip-audit run: | pip install pip-audit pip-audit || true continue-on-error: true - name: Set PYTHONPATH run: echo "PYTHONPATH=${{ github.workspace }}" >> $GITHUB_ENV - name: Run fast tests with coverage run: | pytest backend/tests/ -v -m "not slow" --tb=short \ --cov=backend --cov-report=xml --cov-report=term-missing \ --timeout=60 - name: Upload coverage report uses: actions/upload-artifact@v4 if: always() with: name: coverage-report path: coverage.xml - name: Coverage summary run: | python -c " import xml.etree.ElementTree as ET tree = ET.parse('coverage.xml') root = tree.getroot() rate = float(root.attrib.get('line-rate', 0)) * 100 print(f'Overall coverage: {rate:.1f}%') if rate < 60: print('WARNING: Coverage below 60%') " if: always() - name: Run slow tests (ML models) if: success() run: | pytest backend/tests/ -v -m "slow" --tb=short --timeout=300 continue-on-error: true