TypeScript SDK Testing
Test budget denials, outages, run stops, leases, receipts, and control-plane contracts offline with the Solwyn TypeScript SDK.
@solwyn/sdk/testing provides an in-process FakeControlPlane that speaks the same strict wire
models as the production control plane. It opens no socket and never needs a Solwyn credential.
The fake replaces only the Solwyn control-plane boundary. Provider calls are separate: use a mock provider when an allowed call can reach provider dispatch, or use the exported denial-only sentinel when the test must prove that a hard denial happens before dispatch.
Test a hard denial
import { BudgetExceededError } from "@solwyn/sdk";
import {
denialOnlyOpenAIClient,
FakeControlPlane,
} from "@solwyn/sdk/testing";
import { expect, test } from "vitest";
test("a hard denial never reaches the provider", async () => {
const plane = new FakeControlPlane();
plane.denyNext();
const client = plane.wrap(denialOnlyOpenAIClient(), {
leaseEnabled: false,
model: "gpt-4o",
});
try {
await expect(client.chat.completions.create()).rejects.toBeInstanceOf(
BudgetExceededError,
);
} finally {
await client.close();
}
expect(plane.checks).toHaveLength(1);
expect(plane.denialReceipts).toHaveLength(1);
expect(plane.unmatchedRequests).toEqual([]);
});denialOnlyOpenAIClient() throws if provider dispatch is reached. Do not use it for an allowed or
fail-open call; supply your own provider-shaped mock for those tests.
Inspect recorded traffic
Each plane exposes the parsed requests it accepted:
| Property | Recorded request |
|---|---|
checks | Budget checks |
confirms | Reservation or lease settlements |
leaseGrants | Lease grant requests |
leaseRenewals | Lease renewals |
leaseSurrenders | Lease surrenders |
ingested | Accepted metadata events |
denialReceipts | Ingested events with a denial source |
aggregateReplays | Replayed aggregate denial receipts |
untrackedReports | Untracked-surface advisories |
breakerReports | Provider-breaker reports |
unmatchedRequests | Method/path pairs the fake does not implement |
stoppedRuns returns the run IDs currently stopped with stopRun() and their reasons.
The fake validates request and response shapes, reservation identity, lease generations, duplicate
settlement, and ingest behavior. It enforces the server's 1,000-event metadata batch limit, the
100-report untracked batch limit, the 256 KiB untracked body limit, unique untracked keys within a
batch, and project matching for breaker reports, and returns indexed rejected and duplicates
arrays. It does not compute prices or decrement a running dollar budget. Constructor values such
as budgetLimit, currentUsage, and remainingBudget are fixed state that you provide.
| Constructor option | Default | Meaning |
|---|---|---|
mode | "hard_deny" | Project budget mode returned by checks. |
budgetLimit, currentUsage, remainingBudget | 100, 0, limit minus usage | Fixed budget state. |
postureMode | Project mode | Effective scoped or run lease enforcement, independent of the project mode. Only a hard posture can produce finalGrant. |
runUsage | {} | Explicit synthetic spend per run ID for stopped-run verdicts. |
priceHints | { [provider]: 1 } | Price hints on opted-in text checks; null returns an empty map. |
leaseEligible, grantedTokens, headroomShareTokens, finalGrant | true, 200000, granted tokens, false | Lease grant shape. |
leaseLengthS, refreshIntervalS | 120, 15 | Lease lifetime and refresh interval in seconds; the refresh must be shorter. |
failoverTuningAllowed, projectId | true, "proj_fake" | Failover-tuning directive and project identity. |
plane.wrap(client, options) builds a Solwyn wrapper wired to the fake. It rejects apiKey,
apiUrl, and fetch options with TypeError, neutralizes any SOLWYN_* environment values, and
defaults budgetCheckCacheTtl to 0.
Call resetRecording() to clear recorded arrays without replacing the plane's scripted state.
Create a fresh FakeControlPlane for each test when you need complete isolation.
Script scenarios
Windowed scenario methods such as outage() and slow() return a ScenarioWindow with end()
and Symbol.dispose. Most accept a requests count and, where relevant, an endpoint path. Omit
the count to keep the scenario active until it is ended. denyNext(), denyRun(), and stopRun()
update scripted state and return void.
| Method | Effect |
|---|---|
denyNext(n?, { period?, scope? }) | Queue a denial for checks or leases. tag applies only to the check scope. |
denyRun(runId) | Deny a particular run with the agent_run period. |
stopRun(runId, { reason? }) | Return a run-control stop for an exact run ID. The only accepted reason is "manual_kill", the default. |
outage({ requests?, path? }) | Make matching control-plane requests fail like an unreachable fetch. |
slow(seconds, { requests?, path? }) | Delay matching requests and honor abort signals. Without a path, only settlement confirmations are delayed. |
readOnly({ requests?, path? }) | Return the read-only-key response for matching writes. |
refuseChecks({ status?, requests?, retryAfter? }) | Return a scripted 422, 429, or 503 from budget checks. |
refuseLeases({ status?, code?, requests? }) | Return lease-unavailable or holder-cap responses. |
rejectIngest(...) | Reject selected event indexes, a count, or a malformed response; select exactly one mode. |
misrouteStops({ requests? }) | Return a stop directive for the wrong run to exercise drift handling. |
Use clearDenials(), clearStop(runId), expireReservations(), and expireLeases() to move the
fake into a recovery or expiry state.
const plane = new FakeControlPlane();
const outage = plane.outage({
requests: 1,
path: "/api/v1/budgets/check",
});
try {
// This call observes the configured failOpen behavior.
await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello" }],
});
} finally {
outage.end();
}Because the example can proceed to provider dispatch, client must wrap a caller-owned mock or a
provider client intended for that test.
Test an operator stop
Run-scoped scenarios need an active Node run context:
import { RunStoppedError } from "@solwyn/sdk";
import { currentRun, run } from "@solwyn/sdk/node";
import {
denialOnlyOpenAIClient,
FakeControlPlane,
} from "@solwyn/sdk/testing";
import { expect, test } from "vitest";
test("an operator stop blocks the exact run", async () => {
const plane = new FakeControlPlane();
const client = plane.wrap(denialOnlyOpenAIClient(), {
leaseEnabled: false,
model: "gpt-4o",
});
try {
await run("operator-stop", async () => {
const runId = currentRun()?.agentRunId;
if (runId === undefined) throw new Error("missing run context");
plane.stopRun(runId);
await expect(client.chat.completions.create()).rejects.toBeInstanceOf(
RunStoppedError,
);
});
} finally {
await client.close();
}
});The testing entry itself is edge-safe. Run-context APIs—run(), createRun(), currentRun(),
currentRunContext(), and RunHandle—require the Node entry.
Reserved magic models
The fake reserves seven model names for concise end-to-end scenarios:
| Model | Behavior |
|---|---|
solwyn-test/deny | Monthly denial under hard_deny; an allowed overrun under alert_only. |
solwyn-test/deny-alert | Allowed overrun forced to alert_only, with a reservation and negative remaining budget; provider dispatch continues. |
solwyn-test/deny-tag | Tag-scoped denial; lease-ineligible. |
solwyn-test/deny-stopped | Hard run_stopped denial with remaining budget 0, plus a manual_kill stop directive when the client opts in; requires a run. |
solwyn-test/runaway | First call per run is allowed, later calls deny the run; requires a run. |
solwyn-test/kill | First call per run is allowed, later calls stop the run; requires a run. |
solwyn-test/lease-ineligible | Allowed, but explicitly ineligible for a lease. |
Unknown names under solwyn-test/ fail fast. The three run-scoped models fail fast when no active
run() or RunHandle.activate() context exists.
Contract probes
The testing entry exports reusable probes for a caller-selected fetch, base URL, and API key:
import {
assertCheckContract,
assertConfirmContract,
assertLeaseContract,
assertReceiptIngestContract,
assertRunControlContract,
} from "@solwyn/sdk/testing";assertCheckContract()checks monthly, stopped-run, tag, agent-run, and allow verdicts.assertConfirmContract()checks reservation settlement and replay behavior.assertLeaseContract()checks grant, renewal, lease settlement, refusal, and surrender shapes.assertRunControlContract()checks opted-in, legacy, stopped, and unstopped responses. It also requires astoppedRunIdfor a run you have already stopped on the target.assertReceiptIngestContract()checks a server denial receipt and an aggregate replay.
The probes assert a prepared contract; they do not create server state. In a fake test, script the
required state on FakeControlPlane first. In a live contract test, provision isolated server state
yourself and deliberately pass a live fetch and credentials. Probes preserve any gateway path
prefix in apiUrl. Calling a live endpoint is never an
implicit part of the offline test path.
Test-runner integration
The package does not auto-register a Vitest plugin and has no runtime dependency on Vitest. Keep
fixtures in your own suite and opt in explicitly. A reliable teardown should close the client and
assert that plane.unmatchedRequests is empty, even when the test fails.