@teamamw/test-kit
v0.1.0
Published
AMW shared test harness — env-safety guards, test-tenant auth, synthetic fixtures, secret-scan, a toggleable CI gate, and Vitest/Stryker presets. Dev-time only; never ships to runtime.
Readme
@teamamw/test-kit
Shared test harness for the AMW Suite. Dev-time only — never imported by runtime code. One place to define the testing standard so all 14 apps don't drift into 14 different setups.
It does not contain the tests themselves (those live in each app's tests/,
next to the code they test). It provides the harness: env-safety guards,
test-tenant auth, synthetic fixtures, a secret scanner, the on/off dial, and
Vitest/Stryker presets.
Why it's separate from autofix
The kit is a devDependency apps import to write/run tests. autofix is a
deployed runtime service (the monitor + readiness board). Different artifacts,
loose seam: CI runs the kit → emits results → autofix ingests them for the
board. Changing the kit never redeploys autofix, and vice versa.
Install (per app)
npm i -D @teamamw/test-kit vitest @stryker-mutator/core @stryker-mutator/vitest-runner// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { amwVitestConfig } from '@teamamw/test-kit'
export default defineConfig(amwVitestConfig())amwVitestConfig() wires @teamamw/test-kit/setup, which runs assertTestEnv()
before any test — so a suite pointed at production aborts immediately.
The shape we're testing toward (round-2 research)
Testing Trophy, not the classic pyramid — for TS/API-centric services:
- Static base: TS strict + ESLint (free bug-catching, no test code).
- Core: integration tests at the API boundary — auth, tenant-scope, payments, golden flows. Highest ROI for these apps.
- Targeted unit tests only for complex pure logic (parsers, money math), ideally property-based.
- Thin e2e for critical journeys; synthetic probes (in autofix) watch prod.
- Mutation score (Stryker) is the real gate — coverage % is gameable (AI tests have ~⅓ wrong assertions). A test only counts if it FAILS when the code is broken.
Security / leak rules (non-negotiable)
- No secrets in test code. Mock external services; CI reads masked secrets.
amw-secret-scanblocks commits with hardcoded keys. assertTestEnv()aborts ifNODE_ENV=production,DATABASE_URLlooks like prod, or live payment/email/SMS keys are present.- Test-tenant only.
mintTestToken()refuses the AMW production tenant and defaults to least-privilegeviewer. SetTESTKIT_TENANT_ID(Mike Tester / AUDIT) +TESTKIT_JWT_SECRET. - Synthetic fixtures only —
info+testkit-…@amworldgroup.comemails, reserved555-01xxphones. Never seed from a prod DB.
import { testFetch, fakeContact, assertTestEnv } from '@teamamw/test-kit'
const api = testFetch(process.env.TEST_BASE_URL!) // injects a fresh test token
const res = await api('/api/contacts', { method: 'POST', body: JSON.stringify(fakeContact()) })The on/off dial — TESTKIT_MODE
A strictness dial, not a casual switch (a freely-disableable gate gets disabled and coverage rots). Set per app via a repo/Action variable:
| mode | behaviour |
|---|---|
| off | skip the suite (adoption not started) |
| report | run, report, never block — the default, safe to roll out |
| soft | run, block only on NEW failures vs baseline (advisory) |
| hard | run, block on ANY failure (the destination, once earned) |
The readiness board surfaces each app's mode, so "off" is a visible, auditable choice. Roll out report-only, dial up app-by-app.
CI
Copy ci/testkit.yml → .github/workflows/testkit.yml. Secret-scan blocks
always; tests are advisory until TESTKIT_MODE=hard; mutation runs nightly and
reports to the board.
Adoption order
- Install +
vitest.config.ts+tests/setup.ts(report-only). - Add the CI workflow +
TESTKIT_JWT_SECRET/TESTKIT_TENANT_IDsecrets. - Write integration tests for the risk surfaces (auth, tenant-scope, payments).
- Add Stryker (
configs/stryker.template.json); chase mutation score, not coverage. - Dial
TESTKIT_MODEup once the app has earned it.
