The API workspace uses Vitest 4. The suite combines pure unit tests, property tests, NestJS/Supertest integration tests, provider conformance and official-SDK compatibility checks. The Nuxt applications currently have no standalone browser test suite; they are protected by strict typecheck, production builds and the whole-stack smoke.
#Commands
Run the complete workspace suite:
npm test
The root delegates to each workspace with a test script. At present only
@payment-emulator/api defines one. Scenario JSON is compiled automatically by
the API's pretest script.
Run one API file or use watch mode:
npm test --workspace @payment-emulator/api -- test/money.spec.ts
npm run test:watch --workspace @payment-emulator/api
Run the broader verification gates:
npm run docs:check
npm run typecheck
npm run lint
npm run build
npm run smoke
No coverage command or coverage threshold is configured. Do not claim a coverage percentage from the installed coverage package alone.
#Test layers
| Layer | Examples | What it proves |
|---|---|---|
| Pure/unit | money.spec.ts, scenario-matcher.spec.ts, credentials.spec.ts |
Deterministic transformations without infrastructure |
| Property | ledger-invariants.spec.ts |
Fees and arbitrary posting sets preserve accounting invariants |
| Static conformance | conformance.spec.ts |
Every plugin declares total states/errors, valid routes and verifiable signatures |
| Integration | test/integration/*.spec.ts |
Real Nest app, HTTP, PostgreSQL migrations, transactions, roles and concurrency |
| Official SDK | stripe-sdk.spec.ts, mercadopago-sdk.spec.ts, integration/paypal.spec.ts |
Provider libraries accept selected wire behaviour/signatures |
| Contract | test/integration/webhook-queue.spec.ts |
One suite run against every configured webhook transport, so the drivers stay alternatives rather than lookalikes |
| Whole-stack smoke | scripts/stack-smoke.mjs |
HTTP flows through the API and both Nitro apps, with ledger checks; does not independently prove worker delivery |
#Integration database
Integration files use describe.skipIf(!hasDatabase). Without DATABASE_URL,
npm test still runs the pure and static suites and reports database tests as
skipped. To run everything locally, expose a disposable PostgreSQL URL:
$env:DATABASE_URL = 'postgresql://payment:payment@localhost:54329/payment_emulator_test'
npm test
A driver is skipped when the thing it needs is not configured, the same rule
hasDatabase applies: MONGO_URL for mongo, REDIS_URL for bullmq. The
postgres transport runs whenever the database does, which is the point of it.
The harness in apps/api/test/integration/harness.ts builds the real AppModule,
calls the same configureApp() as production and applies migrations before the
tests. Integration files are serialized by vitest.config.ts because they share
one database and would otherwise race to initialize migration metadata.
Use a test database, not a developer database containing data worth keeping.
Tests create and remove application state through real endpoints and migrations —
though not all of them clean up: several specs leave applications behind, and the
harness's own Harness ARS/Harness USD people are never removed. Pointing the
suite at a database whose console you are about to look at will fill its
listings.
#Conformance suites
apps/api/test/conformance.spec.ts iterates every registered plugin for static
declarations. apps/api/test/integration/conformance.spec.ts runs the same
behavioural guarantees for Mercado Pago and Stripe:
- replay for the same idempotency key and body;
- provider-shaped conflict for a different body;
- one resource under concurrent identical requests;
- one refund under concurrent retries;
- authorization separated from capture;
- balanced accounting and partial/full refund behaviour;
- deterministic scenario faults and state projection.
Add a provider to these parameterized cases. A separate bespoke happy-path test does not replace conformance.
#Official SDK tests
The installed test SDKs are currently stripe@22.6.0 and
mercadopago@3.6.0 (resolved from the lockfile).
- Stripe can target a custom host/port, so
stripe-sdk.spec.tsdrives the live Nest test server through the unmodified SDK. Telemetry is disabled to preserve the no-provider-network guarantee. - Mercado Pago's SDK cannot target the emulator for requests, but its stateless
webhook validator can verify emulator signatures. That limitation is explicit
in
docs/en/fidelity/mercadopago.md.
#Writing a useful test
- Choose the narrowest layer that can fail for the rule.
- Reuse
startEmulator(),operator(),anonymous()andfundingSource()for integration setup. - For concurrency rules, actually overlap several calls; two requests can miss each other by timing and make a broken lock look correct.
- Assert provider-shaped output on provider routes and emulator vocabulary on control/checkout routes.
- For authorization, include the wrong role and a different tenant, not only a missing token.
- For ledger writes, assert debit equals credit and inspect the economically relevant account balances.
#CI
.github/workflows/ci.yml has two jobs:
checks: installs from lockfile, then typechecks, lints, tests, builds and runs the pure smoke against PostgreSQL 17.stack: builds the Compose stack, waits for health checks, executesscripts/stack-smoke.mjs, prints logs and removes its volumes.
The stack smoke covers console login, application registration through Nitro, anonymous checkout payment, balance-payment refusal without a session, balanced ledger settlement and a Stripe form-encoded/manual-capture/refund flow.
The stack smoke also exercises PayPal OAuth, approval and merchant capture. A successful smoke does not verify every optional webhook transport or prove that a worker delivered an event; use the queue integration tests for those paths.