Conformance test kit
Run the shared rail contract cases with tollstile/testing and understand what they verify.
railConformance() from tollstile/testing returns test cases for a rail and its fake provider. Writing a rail? Start with Build a rail. It is part of the tollstile package, has no test-runner dependency, and does not contact a real provider on its own.
Run the cases
The rail's test suite supplies createHarness. Register every returned case with your runner and report its skip reason:
import { describe, it } from "vitest";
import { railConformance } from "tollstile/testing";
import { createHarness } from "./rail-harness";
describe("rail contract", () => {
for (const test of railConformance(createHarness)) {
if (test.skip !== undefined) {
it.skip(`${test.name}: ${test.skip}`, () => test.run());
} else {
it(test.name, () => test.run());
}
}
});./rail-harness is your test implementation, not a Tollstile export. See the repository's test rail harness and x402 harness.
RailHarness
createHarness: () => RailHarness is synchronous. The kit creates a probe to inspect capabilities and hooks, then a fresh harness per case. Give each harness isolated provider state.
| Field or method | Contract |
|---|---|
rail | The rail under test |
clock | Shared by rail and fake provider; now(): Date and advance(ms): void |
price? | Fixed price supported by the fake, default "$1" |
pay({ denial, offer, url }) | Returns Promise<Request> carrying a valid payment; every invocation must create a fresh proof |
settlements() | Number of economic effects performed, synchronously or as a promise |
loseNextSettleResponse?() | Next settlement performs its effect, then throws PROVIDER_TIMEOUT |
failNextSettle?() | Next settlement performs no effect, then throws PROVIDER_TIMEOUT or PROVIDER_UNAVAILABLE |
tamper?(request) | Returns a copy with a proof the rail must reject |
reconcileAfterMs? | Time to advance before lookup/reconciliation; default one hour |
The kit runs a GET /conformance gate with a memory ledger. ConformanceCase contains name, optional skip, and asynchronous run(). Failed assertions reject the case.
What is checked
| Case | What it verifies | Conditional coverage |
|---|---|---|
| Usable contract | Name, lookup, flow/refund declarations; proof absent without payment | Always |
| Challenge → pay → receipt | Integer offer amount, settlement and completed fulfillment, one provider effect, receipt | Always |
| Single-use replay | Replay denied without another effect | Single-use rails only |
| Tampered proof | Denial and no settlement | Requires tamper |
| Failed handler | Release, upfront refund, or recorded unrefundable paid-at-verification outcome | According to flow/capabilities |
| Repeated settlement | No second effect; recorded reference or rejection | Always |
| Lost settlement response | Reconciliation resolves ledger against provider evidence | Requires loseNextSettleResponse |
| Failure before effect | No false settled record after reconciliation | Requires failNextSettle |
| Redaction | Stored redacted data, idempotent redaction, lookup still works | Single-use rail implementing redact |
A skipped case is unverified coverage, not a successful test. The kit does not replace rail-specific tests for every malformed field, amount/asset/recipient mismatch, provider outage, refund ambiguity, concurrency, or protocol vector. It also does not prove live provider finality or availability.
Other testing exports
| Export | Use |
|---|---|
fakeClock(start?) / FakeClock | Controllable clock; default starts at 2026-01-01T00:00:00.000Z |
httpContext(request, options?) | Context from a Web Request; options: resource, principal, requestId. Reads the HTTP idempotency header. |
mcpContext(tool, meta, options?) | Context with no HTTP carrier; options: arguments, principal, clientCapabilities, requestId. Reads the MCP idempotency key. |
RailHarness, ConformanceCase | Harness and case types |
Use testRail() from tollstile for failure simulation. Its effect counters let tests assert no duplicate settlement or refund.
Ledger and adapter conformance
The shared ledger suites are repository test helpers, not exports of tollstile/testing: Postgres suite and SQLite suite. They exercise reservations, state transitions, accounting, claims, and persistence. Driver examples still need validation on the deployment database.
Adapters are tested through their public behavior: challenge/payment round trip, failure release, rejected settlement withholding output, unknown settlement serving output without a receipt, and retry-key forwarding. SPEC.md defines the obligations; it does not imply that every planned suite is a public API. There is no exported runRailConformance() or runPolicyConformance() in the current package.
Before publishing a rail, run its full test suite and complete the provider-specific checks listed on its documentation page. See Roadmap.