@oatscenter/trellis-testkit
v0.100.0-rc.40
Published
Node integration test host using ordinary Trellis runtime binaries and public SDK clients.
Readme
@oatscenter/trellis-testkit
Node helpers that own a real Trellis/NATS runtime for service-repository tests.
Use ordinary node --test; there is no runner, matrix, or case registration.
Generated Participants
List the fixture source and dependencies in trellis.toml, author participants
in contract.trellis, run trellis install, and import their generated
participant exports. Never construct test contracts, subjects, or authorization
evidence by hand.
import { TrellisTestRuntime } from "@oatscenter/trellis-testkit";
import { TrellisService } from "@oatscenter/trellis/service";
import { apps, participants } from "test-trellis";
await using runtime = await TrellisTestRuntime.start();
const identity = await runtime.registerService({
name: "provider",
contract: participants.provider.participant,
});
const service = await TrellisService.connect({
trellisUrl: runtime.trellisUrl,
participant: participants.provider.participant,
name: "provider",
seed: identity.seed,
}).orThrow();
const client = await runtime.connectClient({
name: "caller",
contract: apps.caller.request,
});Each registerService call returns a new instance key. Without deployment,
a service uses the runtime's default deployment unless a different participant
already runs there; then it gets a deployment of its own. Registering the same
participant again adds an instance (a replica) to its deployment.
Register generated handlers before starting the service. Exercise generated
caller methods and inspect their Result values with ordinary assertions. Stop
the service in finally, await its run task, and let await using stop the
test runtime. Node native OAuth defaults to process-local custody; applications
that need persistent login provide their own explicit custody adapter.
For events, subscribe through a generated surface before publishing, then await the actual typed payload. For operations and jobs, await their public terminal result. No helper should replace a real boundary with a fabricated result.
Ownership and Waiting
Ordinary users sharing one app
const alice = await runtime.createUser({ name: "Alice" });
const bob = await runtime.createUser({ name: "Bob" });
for (const user of [alice, bob]) {
await runtime.grantUserCapabilities({
user,
capabilityIds: apps.caller.request.requiredCapabilities,
});
}
const a1 = await runtime.connectClient({
name: "Alice 1",
contract: apps.caller.request,
user: alice,
});
const a2 = await runtime.connectClient({
name: "Alice 2",
contract: apps.caller.request,
user: alice,
});
const b1 = await runtime.connectClient({
name: "Bob 1",
contract: apps.caller.request,
user: bob,
});The app registration is reused; every login has independent OAuth/DPoP custody.
Alice's sessions retain one principal, distinct from Bob's. Runtime-owned user
handles expose name, username, and principalId, never passwords. Explicit
user login uses real consent and never grants or restores capabilities. This
also applies to registerClient and authorizeNativeUserCode with user.
Omitting user retains the seeded-admin convenience; service seeds are unchanged.
Runtime lifecycle
The package downloads the latest production CLI and server release (the newest
stable release, or the newest prerelease before the first stable one), verified
against the checksum published with it; TRELLIS_TEST_VERSION selects another
release, and TRELLIS_TEST_SERVER_BIN selects
local binaries. It reports the trellis-server version it tests against.
Automatic acquisition currently supports
Linux x86-64 only; other native targets are not staged for release. Each runtime is
built the way an operator builds one: trellis-server nats new makes the
broker's operator and config, the testkit writes config.toml, and the first
start creates the keys and NATS accounts. trellis-server --dev runs the real
broker. There is no nsc dependency. TRELLIS_TEST_CACHE_DIR shares only
verified native binaries; each runtime has private configuration, credentials,
SQLite data, broker state, process files, and logs.
start() seeds an administrator by default. Use firstAdmin: "browser-flow"
only when exercising first-admin browser setup. To use producer-built binaries
without downloading, pass trellis: { source: { kind: "path", server } };
both files must match the package version. There is no fallback for invalid
paths.
Startup accepts /readyz only when its processId identifies the spawned
native host, so another runtime at the selected port cannot satisfy readiness.
restart() restarts the entire production host and its managed broker while
retaining data and identity. stop() waits for child cleanup before removing
the sandbox. The host receives SIGTERM first; if it exceeds the shutdown bound,
the testkit force-closes its isolated Unix process group, including the managed
broker. Descendants are also reclaimed if the host crashes. await using shares
that cleanup path. The package does not attach tests to a shared NATS URL or
assign infrastructure by test name.
Real broker adapter tests can use connectNats() for a privileged isolated
connection. It does not own the broker and closes on stop or restart; acquire a
new connection after restart. Ordinary service tests use generated clients and
registerService/connectClient instead.
With interruptibleNativeProxy, nativeProxyUrl() returns the advertised
endpoint for raw clients exercising nativeTransportGate(). Runtime internals
and connectNats() stay on the direct broker endpoint. The advertised override
is a server launch option, not a change to the generated config file.
runtime.waitFor provides bounded polling for public transitions. Prefer an
observable completion signal when available. tempSqlitePath and
sqliteMemoryUrl support deterministic real SQLite tests without a control
plane.
Repository Live Suite
See integration setup for generation, binary
builds, and TRELLIS_TEST_SERVER_BIN environment
variables. Run all or a native filter:
npm run test:integration
npm run test:integration -- --test-name-pattern='generated runtime workflows' ts/integration/runtime_test.ts