SOLWYN

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:

PropertyRecorded request
checksBudget checks
confirmsReservation or lease settlements
leaseGrantsLease grant requests
leaseRenewalsLease renewals
leaseSurrendersLease surrenders
ingestedAccepted metadata events
denialReceiptsIngested events with a denial source
aggregateReplaysReplayed aggregate denial receipts
untrackedReportsUntracked-surface advisories
breakerReportsProvider-breaker reports
unmatchedRequestsMethod/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 optionDefaultMeaning
mode"hard_deny"Project budget mode returned by checks.
budgetLimit, currentUsage, remainingBudget100, 0, limit minus usageFixed budget state.
postureModeProject modeEffective 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, finalGranttrue, 200000, granted tokens, falseLease grant shape.
leaseLengthS, refreshIntervalS120, 15Lease lifetime and refresh interval in seconds; the refresh must be shorter.
failoverTuningAllowed, projectIdtrue, "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.

MethodEffect
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:

ModelBehavior
solwyn-test/denyMonthly denial under hard_deny; an allowed overrun under alert_only.
solwyn-test/deny-alertAllowed overrun forced to alert_only, with a reservation and negative remaining budget; provider dispatch continues.
solwyn-test/deny-tagTag-scoped denial; lease-ineligible.
solwyn-test/deny-stoppedHard run_stopped denial with remaining budget 0, plus a manual_kill stop directive when the client opts in; requires a run.
solwyn-test/runawayFirst call per run is allowed, later calls deny the run; requires a run.
solwyn-test/killFirst call per run is allowed, later calls stop the run; requires a run.
solwyn-test/lease-ineligibleAllowed, 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 a stoppedRunId for 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.

On this page