Skip to content

CI

CI #305

Workflow file for this run

---
# 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"