613parts ops · CI spec

Test Workflow

Five parallel jobs run on every PR. Unit tests run in 3 seconds with no setup. The full integration suite spawns wrangler dev and uses a real Google Maps key from CI secrets — or falls back to a deterministic stub for fork PRs that can't access secrets. Results stream into a single sticky PR comment.

Jobs

Fast · ~3s
worker-unit-tests
Worker unit tests
13 tests · no setup
Library-level vitest suite. Mocks the geocoder. Verifies CSV parsing, JSON parsing, alias-map field resolution, and CSV ↔ JSON parity in isolation. Always runs, including on fork PRs.
Live · ~75s
worker-tests
Worker integration
45+ tests · wrangler dev
Spawns wrangler dev on port 8787, seeds R2 via miniflare, hits HTTP endpoints. Bot search, fulfillment, driver/dispatch endpoints, invoice import (12 tests). Uses real Google Maps key if available, stub otherwise.
Fast · ~30s
driver-app-tests
Driver app
19 tests · jsdom
IndexedDB persistence (auth, route, delivery queue, end-of-day reset) and sync queue drainer (happy path, network failure, partial success, idempotency).
Fast · ~35s
dispatch-app-tests
Dispatch app
46 tests · jsdom + Leaflet mocks
Map component (truck/stop pins, click handlers, bounds), ExceptionInbox (cards, polling, auth), DashboardScreen (auth guard, stats, drawer, 401/403).
Aggregator · ~5s
comment-pr
PR comment
runs after all 4 finish
Downloads each job's test-results.json artifact, parses pass/fail counts, and posts (or updates) a sticky comment via marocchino/sticky-pull-request-comment@v2.

One-time setup

1
Add the Google Maps API key as a repo secret. In GitHub: Settings → Secrets and variables → Actions → New repository secret. Name: GOOGLE_MAPS_API_KEY. Value: your Google Cloud Geocoding API key.
Free tier covers 40,000 calls/month — well above CI's expected ~50 calls per run.
2
(Optional) Restrict the API key. In Google Cloud Console, restrict the key to the Geocoding API only and set IP allowlists if you want belt-and-braces. Not strictly needed since CI doesn't log the key, but it's good hygiene.
3
Push to a branch and open a PR. The workflow triggers automatically. First run takes ~90 seconds; the sticky comment appears under your PR once comment-pr finishes.
Fork PR fallback GitHub blocks fork PRs from accessing repo secrets — that's a security feature, not a bug. When GOOGLE_MAPS_API_KEY is empty, the workflow flips to GEOCODE_STUB=true and runs a deterministic hash-based geocoder. Same parsing/routing/branch-grouping logic is validated either way; only the live Google API call differs.

Sample PR comment

github-actions[bot]
commented · just now

✅ All checks passed

Worker (unit) · 13/13 tests passed
Worker (e2e) · 57/57 tests passed
Driver app · 19/19 tests passed
Dispatch app · 46/46 tests passed

When checks fail, the same comment is updated in place — never appended — so your PR thread stays clean. Click "Run details" to dive into a specific failure.

What ran where

TriggerBranchBehavior
pull_requestany → main/developAll 5 jobs run. Comment posts to PR.
pushmain / develop4 test jobs run. No comment (no PR target).
workflow_dispatchanyManual trigger from Actions tab. Same as push.

The .dev.vars step

This is the bit that wires the secret. The integration job composes a fresh .dev.vars on every run, choosing geocoding mode based on whether the secret is set:

# From .github/workflows/test.yml · worker-tests job
- name: Create test .dev.vars
  env:
    GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
  run: |
    {
      echo "JWT_SECRET=ci-test-secret-not-for-production-use"
      echo "CRON_TRIGGER_TOKEN=ci-test-cron-token"
      echo "STRIPE_SECRET=sk_test_ci_placeholder"
      if [ -n "$GOOGLE_MAPS_API_KEY" ]; then
        echo "GOOGLE_MAPS_API_KEY=$GOOGLE_MAPS_API_KEY"
      else
        echo "GEOCODE_STUB=true"
      fi
    } > .dev.vars

.dev.vars is in .gitignore — secrets never land in the repo. The file is created fresh each CI run and discarded when the runner shuts down.

Caching

Each job uses actions/setup-node@v4 with cache: 'npm'. First run on a branch hits npm registry (~20s); subsequent runs restore from cache (~3s). Worker job caches worker/package-lock.json; the app jobs cache their own.