CI #305
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| # CI/CD pipeline for Appeal Case Manager | |
| name: CI | |
| on: # yamllint disable-line rule:truthy | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| schedule: | |
| # Nightly full-suite run at 17:00 UTC = 3am AEST / 4am AEDT. | |
| # Catches silent regressions from upstream package drift (Mongo image, | |
| # python-docx, ruff, eslint rule updates, etc.) between commits. | |
| - cron: "0 17 * * *" | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| # Cancel older runs on the same ref as soon as a newer one starts. | |
| # Saves GitHub Actions minutes and surfaces the latest result faster. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| backend-lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Show commit SHA | |
| run: | | |
| echo "::notice title=Commit::${{ github.sha }} on ${{ github.ref_name }} (event: ${{ github.event_name }})" | |
| git log -1 --pretty=format:'SHA: %H%nAuthor: %an%nDate: %ai%nMessage: %s' | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install dependencies | |
| run: | | |
| pip install ruff | |
| pip install -r backend/requirements.txt | |
| - name: Lint backend | |
| run: >- | |
| ruff check backend/ | |
| --exclude="backend/tests/,backend/scripts/" | |
| --output-format=github | |
| - name: Check config syntax | |
| run: | | |
| python -c " | |
| import ast, sys | |
| with open('backend/config.py') as f: | |
| ast.parse(f.read()) | |
| print('config.py: syntax OK') | |
| " | |
| backend-tests: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Show commit SHA | |
| run: | | |
| echo "::notice title=Commit::${{ github.sha }} on ${{ github.ref_name }} (event: ${{ github.event_name }})" | |
| git log -1 --pretty=format:'SHA: %H%nAuthor: %an%nDate: %ai%nMessage: %s' | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install dependencies | |
| run: | | |
| pip install pytest pytest-asyncio httpx | |
| pip install -r backend/requirements.txt | |
| - name: Run unit tests (no server required) | |
| working-directory: backend | |
| env: | |
| MONGO_URL: mongodb://localhost:27017 | |
| DB_NAME: test_ci_db | |
| FRONTEND_URL: http://localhost:3000 | |
| ADMIN_EMAILS: [email protected] | |
| CONTACT_EMAIL: [email protected] | |
| PYTHONPATH: ${{ github.workspace }}/backend | |
| # Pulled from repo Settings → Secrets → TEST_PASSWORD. | |
| # Safe fallback if the secret isn't set (unit tests don't actually authenticate). | |
| TEST_PASSWORD: ${{ secrets.TEST_PASSWORD || 'change-me-local-only' }} | |
| run: | | |
| pytest tests/test_offence_framework_integrity.py \ | |
| tests/test_ground_dedup.py \ | |
| tests/test_legislation_framework.py \ | |
| -v --tb=short -q | |
| backend-integration-tests: | |
| runs-on: ubuntu-latest | |
| # Real MongoDB container — tests tagged @requires_mongo actually run here | |
| # instead of skipping as they do in backend-tests. | |
| services: | |
| mongo: | |
| image: mongo:7 | |
| ports: | |
| - 27017:27017 | |
| options: >- | |
| --health-cmd "mongosh --quiet --eval 'db.adminCommand({ ping: 1 }).ok' | grep -q 1" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Show commit SHA | |
| run: | | |
| echo "::notice title=Commit::${{ github.sha }} on ${{ github.ref_name }} (event: ${{ github.event_name }})" | |
| git log -1 --pretty=format:'SHA: %H%nAuthor: %an%nDate: %ai%nMessage: %s' | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install dependencies | |
| run: | | |
| pip install pytest pytest-asyncio httpx | |
| pip install -r backend/requirements.txt | |
| - name: Wait for MongoDB to be ready | |
| run: | | |
| for i in {1..30}; do | |
| if nc -z localhost 27017; then echo "Mongo is up"; exit 0; fi | |
| echo "Waiting for Mongo ($i/30)..."; sleep 1 | |
| done | |
| echo "Mongo failed to come up"; exit 1 | |
| - name: Run integration tests (requires MongoDB) | |
| working-directory: backend | |
| env: | |
| MONGO_URL: mongodb://localhost:27017 | |
| DB_NAME: test_ci_db | |
| FRONTEND_URL: http://localhost:3000 | |
| ADMIN_EMAILS: [email protected] | |
| CONTACT_EMAIL: [email protected] | |
| PYTHONPATH: ${{ github.workspace }}/backend | |
| # Pulled from repo Settings → Secrets → TEST_PASSWORD. | |
| # Safe fallback if the secret isn't set. | |
| TEST_PASSWORD: ${{ secrets.TEST_PASSWORD || 'change-me-local-only' }} | |
| run: | | |
| pytest tests/test_ground_dedup.py::test_cleanup_function \ | |
| tests/test_ground_dedup.py::test_idempotent_sync \ | |
| -v --tb=short | |
| frontend-build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Show commit SHA | |
| run: | | |
| echo "::notice title=Commit::${{ github.sha }} on ${{ github.ref_name }} (event: ${{ github.event_name }})" | |
| git log -1 --pretty=format:'SHA: %H%nAuthor: %an%nDate: %ai%nMessage: %s' | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: "22" | |
| cache: "yarn" | |
| cache-dependency-path: frontend/yarn.lock | |
| - name: Install dependencies | |
| working-directory: frontend | |
| run: yarn install | |
| - name: Lint frontend | |
| working-directory: frontend | |
| run: >- | |
| npx eslint src/ --ext .js,.jsx | |
| --max-warnings 0 | |
| - name: Build | |
| working-directory: frontend | |
| env: | |
| REACT_APP_BACKEND_URL: https://criminallawappealmanagement.com.au | |
| run: yarn build | |
| security-check: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Show commit SHA | |
| run: | | |
| echo "::notice title=Commit::${{ github.sha }} on ${{ github.ref_name }} (event: ${{ github.event_name }})" | |
| git log -1 --pretty=format:'SHA: %H%nAuthor: %an%nDate: %ai%nMessage: %s' | |
| - name: Check for exposed credentials in production code | |
| run: | | |
| echo "Scanning production code for hardcoded credentials..." | |
| # Production code only — tests/ reads TEST_PASSWORD from env vars with safe placeholder fallbacks. | |
| MATCHES=$(grep -rn \ | |
| -e 'Grubbygrub88' \ | |
| -e 'sk-[a-zA-Z0-9]\{20,\}' \ | |
| -e 'AKIA[0-9A-Z]\{16\}' \ | |
| -e 'AIza[0-9A-Za-z_-]\{35\}' \ | |
| -e 'ghp_[0-9A-Za-z]\{36\}' \ | |
| backend/ frontend/src/ \ | |
| --include="*.py" \ | |
| --include="*.js" \ | |
| --include="*.jsx" \ | |
| --include="*.ts" \ | |
| --include="*.tsx" \ | |
| --exclude-dir=tests \ | |
| --exclude-dir=node_modules \ | |
| --exclude-dir=build \ | |
| || true) | |
| if [ -n "$MATCHES" ]; then | |
| echo "::error::Hardcoded credentials detected in production code" | |
| echo "$MATCHES" | |
| exit 1 | |
| fi | |
| echo "✅ No credentials found in production code" | |
| - name: Check .env files not committed | |
| run: | | |
| if [ -f "backend/.env" ] || [ -f "frontend/.env" ]; then | |
| echo "::error::backend/.env and frontend/.env should not be committed" | |
| exit 1 | |
| fi | |
| echo "✅ No .env files committed" | |
| - name: Check for identity leaks in shipped code | |
| # IDENTITY_LOCK enforcement — blocks the build if any agent re-introduces | |
| # foreign client-side telemetry, preview hostnames, or shared/fallback key | |
| # patterns into shipped source, public HTML, or dev plugins. | |
| run: | | |
| echo "Scanning shipped source for identity leaks..." | |
| LEAKS=$(grep -rEn \ | |
| -e 'posthog\.com' \ | |
| -e 'posthog\.init' \ | |
| -e '"phc_[A-Za-z0-9]{20,}"' \ | |
| -e "'phc_[A-Za-z0-9]{20,}'" \ | |
| -e 'google-analytics\.com' \ | |
| -e 'googletagmanager\.com' \ | |
| -e 'gtag\(' \ | |
| -e 'sentry\.io' \ | |
| -e 'Sentry\.init' \ | |
| -e 'fullstory\.com' \ | |
| -e 'hotjar\.com' \ | |
| -e 'logrocket' \ | |
| -e 'segment\.com/analytics\.js' \ | |
| -e 'vercel\.app' \ | |
| -e 'netlify\.app' \ | |
| -e 'onrender\.com' \ | |
| -e 'up\.railway\.app' \ | |
| -e 'pages\.dev' \ | |
| -e 'LLM_KEY' \ | |
| -e 'UNIVERSAL_KEY' \ | |
| backend/ frontend/src/ frontend/public/ frontend/plugins/ \ | |
| --include="*.py" \ | |
| --include="*.js" \ | |
| --include="*.jsx" \ | |
| --include="*.ts" \ | |
| --include="*.tsx" \ | |
| --include="*.html" \ | |
| --include="*.css" \ | |
| --include="*.json" \ | |
| --include="*.md" \ | |
| --exclude-dir=tests \ | |
| --exclude-dir=node_modules \ | |
| --exclude-dir=build \ | |
| --exclude-dir=dist \ | |
| --exclude-dir=__pycache__ \ | |
| || true) | |
| if [ -n "$LEAKS" ]; then | |
| echo "::error::Identity leak detected — telemetry, preview hostnames, or shared key patterns found in shipped code" | |
| echo "$LEAKS" | |
| exit 1 | |
| fi | |
| echo "✅ No identity leaks found — identity lock intact" |