# AEGIS CYBER — Test Suite

Wired into `npm test`. Two tiers, so the cheap checks run anywhere and the
DB-backed regression gate runs against a throwaway Postgres.

## `npm run test:unit` — zero setup
Pure-logic suites (`node:test`, no DB, no app modules):
`wave11-lib.test.mjs`, `wave12-extras.test.mjs` — HMAC signing/verification,
composite-score formula, seeder idempotency, briefing-pack ZIP integrity,
preflight shape. Runs on plain Node with no install or database.

## `npm run test:integration` — needs a throwaway Postgres + env
- **`tenant-isolation-probes.test.ts`** — the client-data-isolation **regression
  gate**. Seeds two tenants and asserts every probed storage method filters by
  `tenantId` (tenant-A reads return zero tenant-B rows; cross-tenant writes are
  rejected; UBO national-IDs are seeded the *real* way — AES-GCM ciphertext +
  tenant-keyed HMAC blind index, never plaintext). If a storage method ever drops
  its tenant filter, this fails.
- **`ai-prompt-injection-offline.test.ts`** — AI prompt-injection regression suite
  (mock provider, no DB writes — but it loads the server/DB modules, so it needs
  the env below).

These import the real `server/*` modules, whose boot guards require a database
URL and the platform secrets. **Always point at a throwaway DB — never a real
one.** Every value below is a disposable test value, not a secret.

### One-time setup (throwaway Postgres on port 5433)
```bash
docker run -d --name aegis-test-pg -p 5433:5432 \
  -e POSTGRES_USER=aegis -e POSTGRES_PASSWORD=testpw -e POSTGRES_DB=aegis_test \
  postgres:16-alpine

docker exec aegis-test-pg psql -U aegis -d aegis_test -c \
  "CREATE ROLE aegis_app LOGIN PASSWORD 'apppw'; CREATE ROLE aegis_rls_bypass BYPASSRLS;"

DATABASE_URL='postgres://aegis:testpw@localhost:5433/aegis_test' npx drizzle-kit push --force

docker exec aegis-test-pg psql -U aegis -d aegis_test -c \
  "GRANT USAGE ON SCHEMA public TO aegis_app;
   GRANT ALL ON ALL TABLES IN SCHEMA public TO aegis_app;
   GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO aegis_app;"
```

### Run (all values are throwaway test values)
```bash
export DATABASE_URL='postgres://aegis:testpw@localhost:5433/aegis_test'
export AEGIS_APP_DB_PASSWORD='apppw'
export PII_AES_MASTER_KEY=$(printf '0%.0s' {1..64})      # any 64-hex-char value
export PII_HMAC_MASTER_KEY=$PII_AES_MASTER_KEY
export ENCRYPTION_KEY=$PII_AES_MASTER_KEY
export MACHINE_KEY_SALT=$PII_AES_MASTER_KEY
export EXAMINER_TOKEN_SALT=$PII_AES_MASTER_KEY
export SESSION_SECRET='throwaway-session-secret-0123456789'
export REGULATOR_SEED_PASSWORD='throwaway-pw-0123456789abcd'

npm run test:integration     # or `npm test` for unit + integration
```

### Teardown
```bash
docker rm -f aegis-test-pg
```

## Runtime authz harness — `bash scripts/run-authz-harness.sh`
The unit/integration suites prove the isolation guards *exist and behave* at the module level. The
**runtime authz harness** (`scripts/runtime-authz-harness.ts`, one-command runner above) proves they
**fire end-to-end**: it stands up a throwaway Postgres, boots the **real Express app**
(`registerRoutes` — real session store, `tenantMiddleware`, the real `requireRole`/`requirePlatformRole`/
`assertTenantOwnership` guards and handlers), logs in over real HTTP as a **tenant-scoped admin** (the
admin-trap actor: global role non-admin + a tenant-A primary grant), and fires that authenticated
session's real requests at **another tenant's** resources — asserting each is rejected (403 / filtered),
one sink per guard mechanism, plus an own-tenant positive control (proves no over-lock).

This upgrades the IDOR/admin-trap isolation claim from *"source-proven the guard exists"* to
*"runtime-proven the guard fires for a real cross-tenant request."* **Honest scope:** it verifies the
enumerated isolation class — it is NOT a full pen-test / red-team (no injection, infra, session-fixation,
or business-logic coverage; that broader bar is a separate, typically externally-commissioned exercise).
Run on demand (it boots the full app + schedulers, so it's a standalone script, not part of `npm test`).

## Not run by `npm test`
- **`ai-prompt-injection-live.test.ts`** — gated behind `AI_PROMPT_INJECTION_LIVE=true`;
  needs a real AI provider credential and makes paid network calls. Run manually.

## CI note
`test:unit` runs with no setup. `test:integration` expects a provisioned Postgres
service plus the env above (all throwaway in CI). The isolation suite is the gate
that keeps tenant isolation from silently regressing.
