import { test } from "node:test";
import assert from "node:assert/strict";
import { EngineClient, EngineError, CONTRACT_VERSION } from "./engine-client.js";
import { makeStubWorker } from "./test-support/stub-worker.js";
const TABLE = {
TableName: "Widgets",
KeySchema: [
{ AttributeName: "pk", KeyType: "HASH" },
{ AttributeName: "sk", KeyType: "RANGE" },
],
AttributeDefinitions: [
{ AttributeName: "pk", AttributeType: "S" },
{ AttributeName: "sk", AttributeType: "S" },
],
BillingMode: "PAY_PER_REQUEST",
};
const put = (client, pk, sk) =>
client.execute("PutItem", { TableName: TABLE.TableName, Item: { pk: { S: pk }, sk: { S: sk } } });
const scanCount = async (client) =>
(await client.execute("Scan", { TableName: TABLE.TableName })).Count;
function clientWith(opts = {}) {
let worker;
const client = new EngineClient({
createWorker: () => {
worker = makeStubWorker(opts.stub);
return worker;
},
...opts.client,
});
return { client, getWorker: () => worker };
}
test("boots, reports the contract, capabilities, and persistence mode", async () => {
const { client } = clientWith();
const descriptor = await client.ready();
assert.equal(descriptor.contractVersion, 1);
assert.equal(client.persistenceMode, "opfs");
assert.equal(client.persistent, true);
assert.ok(client.supports("Query"));
assert.equal(client.supports("UpdateTimeToLive"), false);
client.terminate();
});
test("CreateTable then PutItem then Query surfaces the items", async () => {
const { client } = clientWith();
await client.execute("CreateTable", TABLE);
await put(client, "u#1", "a");
await put(client, "u#1", "b");
const res = await client.execute("Query", {
TableName: TABLE.TableName,
KeyConditionExpression: "pk = :pk",
ExpressionAttributeValues: { ":pk": { S: "u#1" } },
});
assert.equal(res.Count, 2);
client.terminate();
});
test("state survives a simulated reload against the same store", async () => {
const store = new Map();
const first = new EngineClient({ createWorker: () => makeStubWorker({ store }) });
await first.execute("CreateTable", TABLE);
await put(first, "u#1", "a");
first.terminate();
const second = new EngineClient({ createWorker: () => makeStubWorker({ store }) });
await second.ready();
assert.equal(await scanCount(second), 1);
second.terminate();
});
test("a request issued before boot completes is queued and resolves once ready", async () => {
const { client } = clientWith({ stub: { openDelay: 25 } });
const created = client.execute("CreateTable", TABLE); assert.equal(client.contractVersion, null); await created;
assert.equal(client.contractVersion, 1);
assert.equal(await scanCount(client), 0);
client.terminate();
});
test("fallback (memory) mode reports non-persistent and still serves operations", async () => {
const { client } = clientWith({ stub: { persistenceMode: "memory" } });
await client.ready();
assert.equal(client.persistenceMode, "memory");
assert.equal(client.persistent, false);
await client.execute("CreateTable", TABLE);
await put(client, "u#1", "a");
assert.equal(await scanCount(client), 1);
client.terminate();
});
test("an engine error envelope surfaces as a typed EngineError", async () => {
const { client } = clientWith();
await client.execute("CreateTable", TABLE);
await assert.rejects(
() => client.execute("CreateTable", TABLE), (e) => {
assert.ok(e instanceof EngineError);
assert.match(e.type, /ResourceInUseException/);
return true;
},
);
client.terminate();
});
test("a contract-version mismatch surfaces a clear boot error", async () => {
const { client } = clientWith({ stub: { contractVersion: 999 } });
await assert.rejects(
() => client.ready(),
(e) => {
assert.ok(e instanceof EngineError);
assert.match(e.type, /ContractMismatch/);
assert.match(e.message, /contract mismatch/);
assert.match(e.message, /999/);
return true;
},
);
client.terminate();
});
test("a worker crash rejects boot rather than hanging forever", async () => {
const { client, getWorker } = clientWith({ stub: { openDelay: 50 } });
getWorker().__fireError("wasm trap");
await assert.rejects(() => client.ready(), (e) => e instanceof EngineError && /worker error|wasm trap/.test(e.message));
client.terminate();
});
test("a worker crash AFTER boot rejects the next call instead of hanging", async () => {
const { client, getWorker } = clientWith();
await client.ready(); getWorker().__fireError("wasm trap after boot");
await assert.rejects(
() => client.execute("Scan", { TableName: TABLE.TableName }),
(e) => e instanceof EngineError && /worker error|wasm trap/.test(e.message),
);
client.terminate();
});
test("a worker messageerror rejects in-flight calls instead of hanging", async () => {
const { client, getWorker } = clientWith({ stub: { openDelay: 50 } });
getWorker().__fireMessageError(); await assert.rejects(
() => client.ready(),
(e) => e instanceof EngineError && /messageerror/.test(e.message),
);
client.terminate();
});
test("execute after terminate rejects fast instead of posting to a dead worker", async () => {
const { client } = clientWith();
await client.ready();
client.terminate();
await assert.rejects(
() => client.execute("Scan", { TableName: "X" }),
(e) => e instanceof EngineError && /terminated/.test(e.message),
);
});
test("a body-less op defaults its request to {} rather than sending undefined", async () => {
let captured;
const recordingWorker = () => {
const listeners = new Set();
const reply = (id, result) => {
queueMicrotask(() => {
for (const l of listeners) l({ data: { id, ok: true, result } });
});
};
return {
addEventListener(type, h) {
if (type === "message") listeners.add(h);
},
removeEventListener(type, h) {
listeners.delete(h);
},
postMessage(msg) {
if (msg.op === "open") {
reply(msg.id, JSON.stringify({ contractVersion: 1, capabilities: ["ListTables"], persistenceMode: "memory" }));
} else if (msg.op === "execute") {
captured = msg.payload;
reply(msg.id, JSON.stringify({ TableNames: [] }));
}
},
terminate() {
listeners.clear();
},
};
};
const client = new EngineClient({ createWorker: recordingWorker });
const res = await client.execute("ListTables");
assert.deepEqual(captured.request, {}, "body-less request must default to {}");
assert.deepEqual(res, { TableNames: [] });
client.terminate();
});
test("the public surface declared in engine-client.d.ts exists at runtime", () => {
assert.equal(typeof CONTRACT_VERSION, "number");
for (const method of ["ready", "execute", "supports", "terminate"]) {
assert.equal(typeof EngineClient.prototype[method], "function", `EngineClient.${method}`);
}
assert.ok(
Object.getOwnPropertyDescriptor(EngineClient.prototype, "persistent")?.get,
"EngineClient.persistent getter",
);
const err = new EngineError(JSON.stringify({ __type: "X", message: "m" }));
assert.ok(err instanceof Error);
assert.equal(err.type, "X");
assert.equal(typeof err.envelope, "string");
});