@chamvi/test-kit
Test helpers for chamvi: a controllable clock, the store contract suite and the workspace contract suite.
npm install --save-dev @chamvi/test-kit @chamvi/core vitest@chamvi/core and vitest are peer dependencies; @chamvi/sandbox is an optional peer for the workspace contract.
describeStoreContract
function describeStoreContract(
create: (clock: ControllableClock) => IStore | Promise<IStore>,
): void;Registers the Vitest suite every IStore implementation must pass. create is called once per test with a fresh ControllableClock and must return a fresh, empty store that uses that clock for every expiry decision.
// test/unit/store.spec.ts
import { MemoryStore } from '@chamvi/core';
import { describeStoreContract } from '@chamvi/test-kit';
describeStoreContract((clock) => new MemoryStore({ clock }));See Writing a Store for what the suite covers.
describeWorkspaceContract
function describeWorkspaceContract(name: string, factory: WorkspaceContractFactory): void;
type WorkspaceContractFactory = () => Promise<IWorkspace>;Registers the Vitest suite every sandbox provider's workspaces must pass: argv exec and exit codes (127 for a command that cannot start), stdin, cwd and path escapes (../.., /etc, an empty path, a NUL byte), the 64 KiB output caps under a flood, a process-group kill on timeout that takes a backgrounded grandchild with it, abort, UTF-8 files and their size cap, and dispose during an exec. factory returns a fresh, repo-less workspace per test; the suite disposes it. It runs real processes with short real timeouts, not a ControllableClock.
describeWorkspaceContract('local', () => createLocalSandbox().create({ runId: 'r1' }));createGitFixture
function createGitFixture(files: Record<string, string>): Promise<GitFixture>;/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
export type GitFixture = {
/** The repository directory. */
path: string;
/** `file://` URL of `path`, as `normalizeRepo` returns it. */
url: string;
/** The one commit (branch `main`, tag `v1`). */
commit: string;
cleanup(): Promise<void>;
};A temporary repository with one commit of files on branch main, tagged v1, ignoring the machine's git config. Pass url to a workspace manager with allowFileRepos: true.
ControllableClock
class ControllableClock implements IClock {
constructor(start?: Date); // default new Date(0)
now(): Date;
setTimeout(fn: () => void, ms: number): { clear(): void };
advance(ms: number): void;
}Time moves only through advance(ms), which fires every timer due within the window in order, including timers scheduled by fired callbacks. The keepAlive timer option has no meaning without a real event loop and is ignored.
const clock = new ControllableClock(new Date('2026-01-01T00:00:00Z'));
const store = new MemoryStore({ clock });
await store.acquire('job', 'replica-a', 1_000); // true
await store.acquire('job', 'replica-b', 1_000); // false, held by replica-a
clock.advance(1_000); // a lease expires at exactly T + ttlMs
await store.acquire('job', 'replica-b', 1_000); // truePass the same clock to the store and to the engine (new Engine({ ..., clock })) to test claim leases, run timeouts and approval expiry without real timers.