vault-link/frontend/test-support/hardening.test.ts
2026-09-27 13:58:54 +01:00

140 lines
4.9 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { NetworkFaults } from "./network";
import { AssertableState } from "../deterministic-tests/src/utils/assertable-state";
import { TESTS } from "../deterministic-tests/src/test-registry";
import type { ExpectedDocument } from "../deterministic-tests/src/test-definition";
const id = "00000000-0000-4000-8000-000000000001";
function state(
files: Record<string, string>,
identities = Object.fromEntries(
Object.keys(files).map((path, i) => [
i ? id.replace(/1$/, String(i + 1)) : id,
path
])
)
) {
const map = new Map(Object.entries(files));
return new AssertableState({
files: map,
clientFiles: [map, new Map(map)],
canonical: identities,
manifests: [identities, identities],
bytes: new Map(
[...map].map(([path, text]) => [
path,
new TextEncoder().encode(text)
])
)
});
}
test("scripted verifiers reject the concrete shared-loss and duplication counterexamples", () => {
const cases = [
[
"multi-file-operations",
{ "B.md": "updated", "C.md": "CORRUPTED", "unexpected.md": "EXTRA" }
],
[
"concurrent-edit-exact-same-position",
{ "doc.md": "slow slow fast brown fox CORRUPTED" }
]
] as const;
for (const [name, files] of cases) {
const step = TESTS[name]!.steps.filter(
(s) => s.type === "assert-consistent"
).at(-1)!;
assert.throws(() => step.verify!(state(files)));
}
const partial = state({ "a.md": "A", "b.md": "B" });
partial.assertContent("a.md", "A");
assert.throws(() => partial.assertComplete(), /complete file set/);
});
test("complete document oracles kill lost-byte, duplicate-file and identity-reuse mutations", () => {
const expected: ExpectedDocument[] = [
{ key: "a", path: "a.md", content: "exact bytes 🦊\r\n" }
];
const identities = new Map<string, string>();
state({ "a.md": "exact bytes 🦊\r\n" }).assertDocuments(
expected,
identities
);
const corruptions: Record<string, string>[] = [
{ "a.md": "exact bytes 🦊\n" },
{ "a.md": "exact bytes 🦊\r\nexact bytes 🦊\r\n" },
{ "a.md": "exact bytes 🦊\r\n", "extra.md": "EXTRA" }
];
for (const corrupted of corruptions)
assert.throws(() =>
state(corrupted).assertDocuments(expected, identities)
);
assert.throws(
() =>
state(
{ "a.md": "exact bytes 🦊\r\n" },
{ [id.replace(/1$/, "2")]: "a.md" }
).assertDocuments(expected, identities),
/identity changed/
);
assert.throws(
() =>
state({ "a.md": "exact bytes 🦊\r\n" }).assertDocuments(
[{ ...expected[0], key: "recreated" }],
identities
),
/reused/
);
});
test("request checkpoints hold the exact boundary and reject unexercised response predicates", async () => {
const network = new NetworkFaults();
let sent = 0,
completed = false;
const input = "http://localhost/vaults/test/documents/document";
const init = {
method: "PUT",
body: JSON.stringify({ requestId: "one", parentVersionId: 1 })
};
const fetcher = network.wrap(async () => {
sent++;
return Response.json({ type: "StaleBase" });
});
network.hold({ kind: "content", point: "before" });
assert.throws(() => network.resumeRequest(), /before it is reached/);
const request = fetcher(input, init).then(() => {
completed = true;
});
await network.waitForHeldRequest();
assert.equal(sent, 0);
assert.equal(completed, false);
assert.throws(() => network.assertConsumed(), /never released/);
network.resumeRequest();
await request;
network.assertConsumed();
assert.equal(network.requests[0].responseType, "StaleBase");
network.hold({ kind: "content", point: "after", responseType: "Accepted" });
assert.equal(
network.requestsSinceCheckpoint().length,
0,
"An earlier response must not satisfy a new race assertion"
);
await fetcher(input, init);
assert.throws(() => network.assertConsumed(), /never reached/);
network.releaseRequests();
});
test("scripted races have no sleep steps and the stale-content regression asserts its response", () => {
for (const [name, scenario] of Object.entries(TESTS))
assert(
!scenario!.steps.some((s) => s.type === "sleep"),
`${name} still relies on timing`
);
assert(
TESTS["merging-update-response-survives-user-rename"]!.steps.some(
(s) =>
s.type === "assert-response" && s.responseType === "StaleBase"
)
);
});