name: CI Pipeline on: push: branches: ["**"] pull_request: branches: ["main"] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Test (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["3.11", "3.12"] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest pytest-cov pytest-asyncio pytest-mock - name: Run tests with coverage run: | pytest --cov=. --cov-report=xml --cov-report=term-missing --cov-report=html -v - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 if: matrix.python-version == '3.12' with: file: ./coverage.xml flags: unittests name: codecov-umbrella fail_ci_if_error: false - name: Archive coverage report uses: actions/upload-artifact@v4 if: matrix.python-version == '3.12' with: name: coverage-report path: htmlcov/ retention-days: 7 lint: name: Lint & Format Check 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.12' cache: 'pip' - name: Install linting tools run: | pip install ruff black isort mypy - name: Run ruff (fast Python linter) run: | ruff check . --output-format=github - name: Check code formatting with black run: | black --check --diff . - name: Check import sorting with isort run: | isort --check-only --diff . - name: Type check with mypy run: | mypy . --ignore-missing-imports --show-error-codes continue-on-error: true # Don't fail CI on type errors initially security: name: Security Scan 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.12' - name: Install safety & bandit run: | pip install safety bandit[toml] - name: Check for known vulnerabilities in dependencies run: | pip install -r requirements.txt safety check --json || true - name: Run bandit security linter run: | bandit -r . -f json -o bandit-report.json || true bandit -r . -f screen - name: Upload security reports uses: actions/upload-artifact@v4 with: name: security-reports path: | bandit-report.json retention-days: 30 build: name: Build & Test Container runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build Docker image uses: docker/build-push-action@v5 with: context: . push: false tags: test-build:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max load: true - name: Test container starts and responds run: | docker run -d --name test-container -p 8000:8000 test-build:${{ github.sha }} sleep 10 # Check if container is still running if [ "$(docker inspect -f '{{.State.Running}}' test-container)" != "true" ]; then echo "Container failed to start" docker logs test-container exit 1 fi # Check health endpoint curl -f http://localhost:8000/health || exit 1 docker stop test-container docker rm test-container summary: name: CI Summary runs-on: ubuntu-latest needs: [test, lint, security, build] if: always() steps: - name: Check all jobs status run: | echo "Test: ${{ needs.test.result }}" echo "Lint: ${{ needs.lint.result }}" echo "Security: ${{ needs.security.result }}" echo "Build: ${{ needs.build.result }}" if [ "${{ needs.test.result }}" != "success" ] || \ [ "${{ needs.lint.result }}" != "success" ] || \ [ "${{ needs.build.result }}" != "success" ]; then echo "❌ CI pipeline failed" exit 1 fi echo "✅ All CI checks passed"