vault-link/frontend/test-support/harness.test.ts
2026-09-21 21:29:52 +01:00

316 lines
11 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { MemoryDisk, MemoryPersistence } from "./storage";
import { NetworkFaults, classifyRequest } from "./network";
import {
ContentLedger,
assertManifest,
assertSameFiles,
pathDecision
} from "./oracles";
import { Random } from "./random";
import { TestRunner } from "../deterministic-tests/src/test-runner";
import type { ServerControl } from "../deterministic-tests/src/server-control";
import { Logger } from "sync-client";
import { ManagedWebSocketFactory } from "../deterministic-tests/src/managed-websocket";
const bytes = (text: string) => new TextEncoder().encode(text);
test("consistency oracle rejects self comparison, divergent paths and invalid UTF-8 bytes", () => {
const first = new Map([["a.bin", new Uint8Array([0xff])]]);
assert.throws(() => assertSameFiles([first, first]), /itself/);
assert.throws(() =>
assertSameFiles([first, new Map([["a.bin", new Uint8Array([0xfe])]])])
);
assert.throws(() => assertSameFiles([first, new Map()]));
assertSameFiles([first, structuredClone(first)]);
});
test("ledger catches shared loss, duplicated bytes, and unrelated loss in delete workloads", () => {
const ledger = new ContentLedger();
ledger.add("first-marker");
ledger.add("second-marker");
ledger.removedBy("first-marker", "explicit delete of doc A");
assert.throws(
() => ledger.assertPreserved(new Map()),
/second-marker.*lost/
);
assert.throws(
() =>
ledger.assertPreserved(
new Map([["a", bytes("second-marker second-marker")]])
),
/duplicated/
);
ledger.assertPreserved(new Map([["a", bytes("second-marker")]]));
});
test("memory adapter enforces v4 operations and snapshot ownership", async () => {
const disk = new MemoryDisk();
await disk.userWrite("folder/a", bytes("A"));
const snap = (await disk.readSnapshot("folder/a"))!;
snap.content[0] = 0;
assert.equal(
Buffer.from((await disk.readSnapshot("folder/a"))!.content).toString(),
"A"
);
await assert.rejects(
disk.write("folder/a", { content: bytes("B") }),
/exists/
);
await disk.userWrite("folder/b", bytes("B"));
await assert.rejects(disk.rename("folder/a", "folder/b"), /exists/);
await assert.rejects(disk.delete("folder/a"), /regular file/);
await assert.rejects(disk.delete("folder"), /regular file/);
await assert.rejects(disk.readSnapshot("../outside"), /Unsafe/);
await assert.rejects(disk.stat("folder/a/child"), /ancestor/);
await assert.rejects(disk.readSnapshot("folder"), /directory/);
});
for (const phase of ["before", "visible", "durable"] as const) {
for (const powerLoss of [false, true]) {
test(`${powerLoss ? "power loss" : "process death"} at ${phase} rename boundary`, async () => {
const disk = new MemoryDisk();
await disk.userWrite("a", bytes("A"));
const session = disk.session();
let fired = false;
disk.boundary = (label) => {
if (label === `${phase}:rename:a->b`) {
fired = true;
disk.crash(powerLoss);
throw new Error("crash");
}
};
await assert.rejects(session.rename("a", "b"), /crash/);
assert(fired);
disk.boundary = () => {};
const moved =
phase === "durable" || (phase === "visible" && !powerLoss);
assert.deepEqual([...disk.userFiles().keys()], [moved ? "b" : "a"]);
assert.throws(
() => session.write("stale", { content: bytes("bad") }),
/crashed/
);
});
}
}
test("persistence has deep ownership and both old/new uncertain save outcomes", async () => {
for (const phase of ["before", "durable"]) {
const store = new MemoryPersistence({ settings: { vaultName: "old" } });
const loaded = await store.load();
loaded.settings!.vaultName = "mutated";
assert.equal(store.snapshot().settings!.vaultName, "old");
store.boundary = (label) => {
if (label === `${phase}:save`) throw new Error("save failed");
};
await assert.rejects(
store.save({ settings: { vaultName: "new" } }),
/save failed/
);
assert.equal(
store.snapshot().settings!.vaultName,
phase === "before" ? "old" : "new"
);
}
});
test("CAS hooks match v4 create, update and manifest and preserve retry payload", async () => {
for (const kind of ["create", "content", "manifest"] as const) {
const net = new NetworkFaults();
const url = `http://test/vaults/test/${kind === "manifest" ? "file-manifest" : "documents/uuid"}`;
const body = {
requestId: "same",
parentVersionId: kind === "create" ? null : 3
};
const init = { method: "PUT", body: JSON.stringify(body) };
assert.equal(classifyRequest(url, init)?.kind, kind);
let committed = 0;
const wrapped = net.wrap(async () => {
committed++;
return new Response('{"type":"Accepted"}');
});
net.arm(kind);
await assert.rejects(wrapped(url, init), /Injected after-commit/);
await net.wait();
assert.throws(() => net.assertConsumed(), /never retried/);
assert.equal(committed, 1);
await wrapped(url, init);
net.assertConsumed();
await assert.rejects(
wrapped(url, {
...init,
body: JSON.stringify({ ...body, changed: true })
}),
/Retry changed/
);
}
assert.equal(
classifyRequest("http://test/documents", { method: "POST" }),
undefined
);
});
test("unconsumed and before-send faults cannot silently pass", async () => {
const net = new NetworkFaults();
net.arm("manifest", "before");
assert.throws(() => net.assertConsumed(), /never fired/);
let called = false;
await assert.rejects(
net.wrap(async () => {
called = true;
return new Response();
})("http://test/file-manifest", {
method: "PUT",
body: '{"requestId":"r"}'
})
);
assert(!called);
assert.throws(() => net.assertConsumed(), /never retried/);
await net.wrap(async () => new Response())("http://test/file-manifest", {
method: "PUT",
body: '{"requestId":"r"}'
});
net.assertConsumed();
});
test("HTTP observation checkpoint blocks catchup, resumes, and respects abort", async () => {
const network = new NetworkFaults();
const wrapped = network.wrap(async () => new Response("{}"));
network.pauseObservation();
let delivered = false;
const request = wrapped("http://test/events-since?after=0").then(() => {
delivered = true;
});
await network.waitForObservation();
assert(!delivered, "Catchup bypassed the observation checkpoint");
network.resumeObservation();
await request;
network.assertConsumed();
network.pauseObservation();
const controller = new AbortController();
const interrupted = wrapped("http://test/vault-snapshot", {
signal: controller.signal
});
const rejected = assert.rejects(interrupted, /abort/i);
await network.waitForObservation();
controller.abort();
await rejected;
network.resumeObservation();
network.assertConsumed();
});
test("independent path decision truth table covers create, delete and rename", () => {
for (const base of [undefined, "a", "b"])
for (const local of [undefined, "a", "b"])
for (const remote of [undefined, "a", "b"]) {
const result = pathDecision(base, local, remote);
assert.equal(result, remote !== base ? remote : local);
}
assertManifest({ a: "folder/é.md", b: "other.bin" });
for (const entries of [
{ a: "A", b: "a" },
{ a: "é", b: "e\u0301" },
{ a: "x", b: "x/y" }
])
assert.throws(() => assertManifest(entries));
});
test("seeded actions are repeatable, including seed zero", () => {
for (const seed of [0, 1, 12345, 0xffffffff]) {
const first = new Random(seed);
const second = new Random(seed);
assert.deepEqual(
Array.from({ length: 100 }, () => first.int(100)),
Array.from({ length: 100 }, () => second.int(100))
);
}
});
test("runner cleanup errors fail an otherwise successful test", async () => {
const server = {
isRunning: () => true,
resume: () => {}
} as unknown as ServerControl;
const runner = new TestRunner(
server,
new Logger(),
"unused",
"http://unused"
);
let cleaned = 0;
Object.assign(runner, {
initializeAgents: async () => {},
agents: [
{
clientId: 0,
database: () => ({ evidence: "before cleanup" }),
disk: { image: () => ({ bytes: [0, 255] }) },
network: { requests: [{ body: { requestId: "original" } }] },
cleanup: async () => {
cleaned++;
throw new Error("late background error");
}
}
]
});
const result = await runner.runTest("cleanup-probe", {
clients: 2,
steps: []
});
assert.equal(result.success, false);
assert.match(result.error!, /late background error/);
assert.equal(cleaned, 1, "Failed cleanup was run twice");
assert.deepEqual(result.diagnostics, [
{
client: 0,
database: { evidence: "before cleanup" },
disk: { bytes: [0, 255] },
requests: [{ body: { requestId: "original" } }]
}
]);
});
test("disposed harness rejects late websocket reconnects", async () => {
const factory = new ManagedWebSocketFactory();
const Socket = factory.constructorFn;
await factory.finish();
assert.throws(() => new Socket("ws://unused"), /after harness cleanup/);
});
test("runner preserves primary failure and cleanup diagnostics", async () => {
const server = {
isRunning: () => true,
resume: () => {}
} as unknown as ServerControl;
const runner = new TestRunner(
server,
new Logger(),
"unused",
"http://unused"
);
Object.assign(runner, {
initializeAgents: async () => {},
executeStep: async () => {
throw new Error("primary assertion failed");
},
agents: [
{
clientId: 0,
database: () => ({}),
disk: { image: () => ({}) },
network: { requests: [] },
cleanup: async () => {
throw new Error("cleanup failed too");
}
}
]
});
const result = await runner.runTest("cleanup-probe", {
clients: 2,
steps: [{ type: "barrier" }]
});
assert.equal(result.success, false);
assert.match(result.error!, /primary assertion failed/);
assert.match(result.error!, /cleanup failed too/);
});