Skip to content

@chamvi/test-kit ​

Test helpers for chamvi: a controllable clock, the store contract suite and the workspace contract suite.

bash
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 ​

ts
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.

ts
// 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 ​

ts
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.

ts
describeWorkspaceContract('local', () => createLocalSandbox().create({ runId: 'r1' }));

createGitFixture ​

ts
function createGitFixture(files: Record<string, string>): Promise<GitFixture>;
ts
/*
 * 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 ​

ts
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.

ts
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); // true

Pass 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.