npm packages are not installable yet: [email protected] can be published after 2026-09-16 10:30 UTC. Details
Tollstile

Conformance test kit

Run the shared rail contract cases with tollstile/testing and understand what they verify.

Public Beta · early access

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 methodContract
railThe rail under test
clockShared 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

CaseWhat it verifiesConditional coverage
Usable contractName, lookup, flow/refund declarations; proof absent without paymentAlways
Challenge → pay → receiptInteger offer amount, settlement and completed fulfillment, one provider effect, receiptAlways
Single-use replayReplay denied without another effectSingle-use rails only
Tampered proofDenial and no settlementRequires tamper
Failed handlerRelease, upfront refund, or recorded unrefundable paid-at-verification outcomeAccording to flow/capabilities
Repeated settlementNo second effect; recorded reference or rejectionAlways
Lost settlement responseReconciliation resolves ledger against provider evidenceRequires loseNextSettleResponse
Failure before effectNo false settled record after reconciliationRequires failNextSettle
RedactionStored redacted data, idempotent redaction, lookup still worksSingle-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

ExportUse
fakeClock(start?) / FakeClockControllable 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, ConformanceCaseHarness 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.

On this page