613
613parts an APC associate store
DRIVER + SYNC TEST SUITES · v0.7.0
Vitest · 43 new tests

prove the trucks ship
before the trucks ship.

Two new test suites covering the driver app, dispatch endpoints, and offline sync queue. 24 endpoint tests against wrangler dev verify auth, R2 side-effects, and admin/driver role gates. 19 unit tests in jsdom + fake-indexeddb verify the offline sync queue persists and drains correctly. Plus the original 22 bot tests still passing — total 65 tests, run in ~6 seconds.

New worker tests
24
New unit tests
19
Total tests
65
Worker run
~5 sec
Unit run
~1 sec
Cost
$0
How to run

two suites. two terminals. zero ceremony.

Worker tests need wrangler dev running so they can hit real endpoints + R2. Unit tests don't need anything external — they run entirely in jsdom with fake-indexeddb.

worker tests · against wrangler dev
$ cd worker $ npx wrangler secret put JWT_SECRET # enter any test secret · same for both terminals $ npm run dev & [wrangler:inf] Ready on http://127.0.0.1:8787 $ npm test [setup] ✓ wrangler dev is up [setup] Seeding R2 (8 files)... [setup] ✓ deliveries/2026-05-04/belleville.json ✓ er_search · 9 tests passed ✓ er_fulfillment · 6 tests passed ✓ cron pipeline · 3 tests passed ✓ driver login · 5 tests passed ✓ driver list · 1 test passed ✓ route today · 4 tests passed ✓ delivery complete · 4 tests passed ✓ delivery exception · 3 tests passed ✓ location ping · 3 tests passed ✓ dispatch status · 4 tests passed ✓ health + routing · 4 tests passed Test Files 2 passed (2) Tests 46 passed (46) Duration 5.12s
driver-app unit tests · jsdom
$ cd driver-app $ npm install # installs vitest + jsdom + fake-indexeddb $ npm test RUN v1.4.0 /home/.../driver-app ✓ test/storage.test.ts (11 tests) ✓ storage · auth (4) ✓ storage · route (2) ✓ storage · delivery queue (4) ✓ storage · endOfDayReset (1) ✓ test/sync.test.ts (8 tests) ✓ sync · empty queue (1) ✓ sync · happy path (2) ✓ sync · failure handling (3) ✓ sync · queue ordering & idempotency (2) Test Files 2 passed (2) Tests 19 passed (19) Duration 0.94s
Worker · driver + dispatch endpoints

24 tests across 6 endpoints.

Tests use a driver-auth.ts helper that logs in as a known dev driver and caches the JWT. R2 side-effects are verified by reading delivery-events/{date}/{order_id}.json directly via wrangler CLI after each POST.

driver login

5 tests POST /api/v1/driver/login
01

valid PIN returns 200 + JWT + driver info

Verifies JWT format (3 base64 segments), driver fields (id, name, branch, role), expires_in: 86400.

02

driver-role login succeeds with correct PIN

Confirms driver_a gets role: "driver" back, not admin.

03

wrong PIN returns 401

Constant-time PIN comparison rejects without leaking timing info.

04

unknown driver_id returns 401

Same status as wrong PIN — doesn't leak whether a driver exists.

05

missing fields returns 400

Body missing pin field gets a real 400, not a generic 500.

route today

4 tests GET /api/v1/driver/route/today
06

without auth returns 401

Bearer token required.

07

valid driver token returns route for driver's branch

Belleville driver gets the 3-stop seeded route. Verifies stop schema (order_id, customer, order_summary).

08

kingston driver gets empty route (we only seeded belleville)

Empty stops: [] + friendly message field. No 404 — graceful empty state.

09

admin sees their belleville route too

Admins logged into a branch see that branch's route — same flow as drivers, no special handling needed.

delivery complete

4 tests POST /api/v1/driver/delivery/:id/complete
10

without auth returns 401

11

minimal body succeeds and writes event to R2

Verifies delivery-events/{today}/{order_id}.json is written with correct shape via direct R2 read.

12

with photo_base64 writes photo to R2 and links it in event

Tiny 1×1 JPEG base64 → confirms delivery-photos/{today}/{order_id}.jpg exists, event has photo_url: r2://....

13

with signature + notes stores both in event

SVG signature path round-trips verbatim. Notes preserved.

delivery exception

3 tests POST /api/v1/driver/delivery/:id/exception
14

with valid reason writes exception event

Verifies status='exception', reason, attempted_at, notes all stored.

15

without auth returns 401

16

preserves all 5 exception reasons verbatim

Loops through no_one_home, wrong_address, refused, damaged, other — each round-trips correctly through R2.

location ping

3 tests POST /api/v1/driver/location
17

without auth returns 401

18

valid coords write to R2 with correct branch

Branch comes from JWT, not request body. Driver_a's pings tag as belleville.

19

subsequent ping overwrites previous (most-recent-wins)

Two pings with different coords — second wins. Dispatch dashboard always sees the freshest position.

dispatch status

4 tests GET /api/v1/dispatch/status
20

without auth returns 401

21

driver-role token returns 403 (admin only)

Role gate enforced — drivers can't see the dispatch view, even with a valid JWT.

22

admin-role token returns 200 + branches array

Returns both branches (belleville + kingston) with totals + exceptions inbox.

23

aggregates delivery events from R2 into stop status

Tests run in order — earlier delivery completion tests created events. This test verifies the dispatch endpoint reads them correctly: TEST-001 should now show status: 'delivered'.

24

(driver list test grouped here for completeness)

GET /api/v1/driver/list returns drivers with role field, no auth required.

Driver app · sync queue + storage

19 unit tests in jsdom.

No wrangler dev needed — these run in pure Node.js with fake-indexeddb polyfilling IndexedDB and vi.spyOn(global, 'fetch') mocking the network. Verifies the queue persists, drains, retries, and never re-syncs.

storage · IndexedDB

11 tests src/lib/storage.ts
25

auth round-trips through IDB

26

getAuth returns null when no auth stored

27

getAuth auto-deletes expired tokens

Sets auth with expires_at in the past — getAuth() returns null AND removes it from IDB.

28

clearAuth removes the record

29

route round-trips through IDB

30

clearRoute removes the record

31

delivery record round-trips through IDB

32

listDeliveries returns all stored deliveries

Stores 3 records (delivered, exception, synced delivered) — verifies all 3 come back.

33

listUnsyncedDeliveries filters correctly

Only returns delivered/exception with synced: false — pending records and synced records are excluded.

34

updating a delivery overwrites the previous record

Same order_id key → second write replaces first. Critical for marking records synced after upload.

35

endOfDayReset clears route + deliveries but preserves auth

Driver can log out cleanly without losing their token mid-rotation. Specifically tests partial clear semantics.

sync · queue drainer

8 tests src/lib/sync.ts
36

empty queue returns zeros

No deliveries pending → { synced: 0, failed: 0, total: 0 } — fetch never called.

37

uploads delivered record + marks synced

Verifies fetch URL contains /{order_id}/complete, body includes photo/signature/notes, Authorization header has Bearer JWT, and record is marked synced: true + last_sync_attempt: number.

38

uploads exception record to /exception with reason

Different endpoint, different payload shape, but same drain logic.

39

keeps records in queue when network throws

Mock fetch rejects → result has failed=1, synced=0. Record stays in IDB with last_sync_attempt bumped.

40

keeps records in queue on 5xx

Server error treated same as network error — record stays queued for next retry.

41

partial success: some upload, some fail

Two records, mock implements per-call success/failure → result.synced=1, result.failed=1. Verifies records are independently tracked.

42

does not re-sync records already marked synced=true

Idempotency. listUnsyncedDeliveries() filters them out → fetch only called for new records.

43

skips pending records (status="pending")

Pending records aren't ready to sync — they get skipped even if synced=false.

Test infrastructure

three new helpers. one route template.

Driver tests need a few new pieces: a route template for today, an auth helper that logs in once and caches the JWT, and real PIN hashes for the dev drivers (so login actually works in tests).

worker/test/helpers/driver-auth.ts
login + token cache · 80 lines
// Cached per-process so tests don't re-login for every assertion
const tokenCache = new Map<string, string>();

export const KNOWN_DRIVERS = {
  himmat:   { id: 'himmat',   pin: '1234', branch: 'belleville', role: 'admin'  },
  preet:    { id: 'preet',    pin: '2345', branch: 'belleville', role: 'admin'  },
  driver_a: { id: 'driver_a', pin: '3456', branch: 'belleville', role: 'driver' },
  driver_b: { id: 'driver_b', pin: '4567', branch: 'kingston',   role: 'driver' },
  driver_c: { id: 'driver_c', pin: '5678', branch: 'kingston',   role: 'driver' },
};

export async function getDriverToken(driverId): Promise<string> {
  const cached = tokenCache.get(driverId);
  if (cached) return cached;

  const driver = KNOWN_DRIVERS[driverId];
  const r = await fetch(`${BASE_URL}/api/v1/driver/login`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ driver_id: driver.id, pin: driver.pin }),
  });

  const body = await r.json();
  tokenCache.set(driverId, body.token);
  return body.token;
}

export async function authedJSON<T>(driverId, path, init = {}): Promise<T> {
  const r = await authedFetch(driverId, path, init);
  if (!r.ok) throw new Error(`Authed request failed: ${r.status} ${path}`);
  return await r.json() as T;
}
driver-app/test/setup.ts
jsdom polyfill · 7 lines
// Polyfills IndexedDB so idb-keyval works in jsdom.
// Each test file should clear IDB between tests via beforeEach() in the
// individual specs.

import 'fake-indexeddb/auto';
worker/test/data/route-template-belleville.json
3 stops · uploaded with today's date by globalSetup
{
  "branch": "belleville",
  "stops": [
    {
      "order_id": "TEST-001",
      "sequence": 1,
      "customer": {
        "name": "Marcus T.",
        "phone": "613-555-1234",
        "address": "23 Main St, Belleville, ON K8N 4Z2",
        "lat": 44.1628,
        "lng": -77.3833
      },
      "order_summary": {
        "items": [{ "qty": 1, "name": "ACDelco PF64 Engine Oil Filter", "sku": "ACD-PF64" }],
        "total_paid": 14.99,
        "fulfillment": "drop_paid"
      },
      "delivery_window": "9:00-17:00"
    }
    /* +2 more stops */
  ]
}
Changeset

files added or changed.

worker/ ├── src/lib/drivers.ts # EDIT · real SHA-256 PIN hashes for dev drivers └── test/ ├── driver.test.ts NEW# 24 endpoint tests · 413 lines ├── helpers/ │ ├── driver-auth.ts NEW# Login + token cache helper · 80 lines │ └── global-setup.ts # EDIT · seeds today's route at startup └── data/ └── route-template-belleville.json NEW# 3-stop template, date assigned at runtime driver-app/ ├── package.json # EDIT · added vitest, jsdom, fake-indexeddb scripts ├── vitest.config.ts NEW# jsdom env · setupFiles └── test/ ├── setup.ts NEW# Imports fake-indexeddb/auto polyfill ├── storage.test.ts NEW# 11 tests · IndexedDB persistence └── sync.test.ts NEW# 8 tests · queue drainer w/ mocked fetch
5 new test files 1 new helper 2 edits ~900 lines added 2 new deps · jsdom + fake-indexeddb