| name: Pull Request Checks |
|
|
| on: |
| pull_request: |
| branches: [ main, develop ] |
| push: |
| branches: [ develop ] |
|
|
| env: |
| AWS_REGION: us-west-2 |
| S3_BUCKET: fredmlv1 |
| LAMBDA_FUNCTION: fred-ml-processor |
| PYTHON_VERSION: '3.9' |
|
|
| jobs: |
| |
| quality: |
| name: π Code Quality |
| runs-on: ubuntu-latest |
| |
| steps: |
| - name: Checkout code |
| uses: actions/checkout@v4 |
| |
| - name: Set up Python ${{ env.PYTHON_VERSION }} |
| uses: actions/setup-python@v4 |
| with: |
| python-version: ${{ env.PYTHON_VERSION }} |
| |
| - name: Cache pip dependencies |
| uses: actions/cache@v3 |
| with: |
| path: ~/.cache/pip |
| key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} |
| restore-keys: | |
| ${{ runner.os }}-pip- |
| |
| - name: Install dependencies |
| run: | |
| python -m pip install --upgrade pip |
| pip install -r requirements.txt |
| pip install black flake8 mypy isort |
| |
| - name: Check code formatting |
| run: | |
| echo "π¨ Checking code formatting..." |
| black --check --diff . |
| |
| - name: Run linting |
| run: | |
| echo "π Running linting..." |
| flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics |
| flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics |
| |
| - name: Check import sorting |
| run: | |
| echo "π¦ Checking import sorting..." |
| isort --check-only --diff . |
| |
| - name: Run type checking |
| run: | |
| echo "π Running type checking..." |
| mypy lambda/ frontend/ src/ --ignore-missing-imports |
| |
| |
| unit-tests: |
| name: π§ͺ Unit Tests |
| runs-on: ubuntu-latest |
| |
| steps: |
| - name: Checkout code |
| uses: actions/checkout@v4 |
| |
| - name: Set up Python ${{ env.PYTHON_VERSION }} |
| uses: actions/setup-python@v4 |
| with: |
| python-version: ${{ env.PYTHON_VERSION }} |
| |
| - name: Install dependencies |
| run: | |
| python -m pip install --upgrade pip |
| pip install -r requirements.txt |
| pip install pytest pytest-cov |
| |
| - name: Run unit tests |
| run: | |
| echo "π§ͺ Running unit tests..." |
| pytest tests/unit/ -v --cov=lambda --cov=frontend --cov-report=xml --cov-report=term-missing |
| |
| - name: Upload coverage to Codecov |
| uses: codecov/codecov-action@v3 |
| with: |
| file: ./coverage.xml |
| flags: unittests |
| name: codecov-umbrella |
| fail_ci_if_error: false |
|
|
| |
| security: |
| name: π Security Scan |
| runs-on: ubuntu-latest |
| |
| steps: |
| - name: Checkout code |
| uses: actions/checkout@v4 |
| |
| - name: Run Bandit security scan |
| run: | |
| echo "π Running security scan..." |
| pip install bandit |
| bandit -r lambda/ frontend/ src/ -f json -o bandit-report.json || true |
| |
| - name: Upload security report |
| uses: actions/upload-artifact@v3 |
| if: always() |
| with: |
| name: security-report |
| path: bandit-report.json |
|
|
| |
| dependencies: |
| name: π¦ Dependency Check |
| runs-on: ubuntu-latest |
| |
| steps: |
| - name: Checkout code |
| uses: actions/checkout@v4 |
| |
| - name: Set up Python ${{ env.PYTHON_VERSION }} |
| uses: actions/setup-python@v4 |
| with: |
| python-version: ${{ env.PYTHON_VERSION }} |
| |
| - name: Check for outdated dependencies |
| run: | |
| echo "π¦ Checking for outdated dependencies..." |
| pip install pip-check-updates |
| pcu --version || echo "pip-check-updates not available" |
| |
| - name: Check for security vulnerabilities |
| run: | |
| echo "π Checking for security vulnerabilities..." |
| pip install safety |
| safety check || true |
| |
| |
| docs: |
| name: π Documentation Check |
| runs-on: ubuntu-latest |
| |
| steps: |
| - name: Checkout code |
| uses: actions/checkout@v4 |
| |
| - name: Check README |
| run: | |
| echo "π Checking documentation..." |
| if [ ! -f "README.md" ]; then |
| echo "β README.md is missing" |
| exit 1 |
| fi |
| |
| |
| required_sections=("## ποΈ Architecture" "## π Features" "## π οΈ Setup") |
| for section in "${required_sections[@]}"; do |
| if ! grep -q "$section" README.md; then |
| echo "β Missing required section: $section" |
| exit 1 |
| fi |
| done |
| |
| echo "β
Documentation check passed" |
| |
| - name: Check deployment docs |
| run: | |
| if [ ! -f "docs/deployment/streamlit-cloud.md" ]; then |
| echo "β Streamlit Cloud deployment guide is missing" |
| exit 1 |
| fi |
| echo "β
Deployment documentation exists" |
| |
| |
| build-test: |
| name: ποΈ Build Test |
| runs-on: ubuntu-latest |
| |
| steps: |
| - name: Checkout code |
| uses: actions/checkout@v4 |
| |
| - name: Set up Python ${{ env.PYTHON_VERSION }} |
| uses: actions/setup-python@v4 |
| with: |
| python-version: ${{ env.PYTHON_VERSION }} |
| |
| - name: Install dependencies |
| run: | |
| python -m pip install --upgrade pip |
| pip install -r requirements.txt |
| |
| - name: Test Lambda package creation |
| run: | |
| echo "π¦ Testing Lambda package creation..." |
| cd lambda |
| pip install -r requirements.txt -t . |
| zip -r ../lambda-test-package.zip . |
| cd .. |
| |
| if [ -f "lambda-test-package.zip" ]; then |
| echo "β
Lambda package created successfully" |
| ls -la lambda-test-package.zip |
| else |
| echo "β Lambda package creation failed" |
| exit 1 |
| fi |
| |
| - name: Test Streamlit app import |
| run: | |
| echo "π¨ Testing Streamlit app imports..." |
| python -c "import sys; sys.path.append('frontend'); from app import load_config, init_aws_clients; print('β
Streamlit app imports successfully')" |
| |
| |
| comment: |
| name: π¬ Comment Results |
| runs-on: ubuntu-latest |
| needs: [quality, unit-tests, security, dependencies, docs, build-test] |
| if: github.event_name == 'pull_request' |
| |
| steps: |
| - name: Checkout code |
| uses: actions/checkout@v4 |
| |
| - name: Download test results |
| uses: actions/download-artifact@v3 |
| with: |
| name: test-results |
| |
| - name: Create PR comment |
| uses: actions/github-script@v7 |
| with: |
| script: | |
| const fs = require('fs'); |
| |
| let comment = '## π§ͺ Pull Request Test Results\n\n'; |
| |
| // Check job results |
| const jobs = ['quality', 'unit-tests', 'security', 'dependencies', 'docs', 'build-test']; |
| let passed = 0; |
| let total = jobs.length; |
| |
| for (const job of jobs) { |
| const result = context.payload.workflow_run?.conclusion || 'unknown'; |
| const status = result === 'success' ? 'β
' : 'β'; |
| comment += `${status} **${job}**: ${result}\n`; |
| if (result === 'success') passed++; |
| } |
| |
| comment += `\n**Summary**: ${passed}/${total} checks passed\n\n`; |
| |
| if (passed === total) { |
| comment += 'π All checks passed! This PR is ready for review.\n'; |
| } else { |
| comment += 'β οΈ Some checks failed. Please review and fix the issues.\n'; |
| } |
| |
| // Add test coverage if available |
| try { |
| const coverage = fs.readFileSync('coverage.xml', 'utf8'); |
| const coverageMatch = coverage.match(/<coverage.*?line-rate="([^"]+)"/); |
| if (coverageMatch) { |
| const coveragePercent = Math.round(parseFloat(coverageMatch[1]) * 100); |
| comment += `\nπ **Test Coverage**: ${coveragePercent}%\n`; |
| } |
| } catch (e) { |
| // Coverage file not available |
| } |
| |
| github.rest.issues.createComment({ |
| issue_number: context.issue.number, |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| body: comment |
| }); |