mor
This commit is contained in:
parent
ff1f474324
commit
e151208e15
1 changed files with 2933 additions and 784 deletions
140
frontend/test-support/hardening.test.ts
Normal file
140
frontend/test-support/hardening.test.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
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"
|
||||
)
|
||||
);
|
||||
});
|
||||
Loading…
Reference in a new issue