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

173 lines
5.5 KiB
TypeScript

import assert from "node:assert/strict";
import type { StoredDatabase } from "sync-client";
export function assertSameFiles(
clients: readonly Map<string, Uint8Array>[]
): void {
assert(
clients.length >= 2,
"Consistency requires at least two distinct clients"
);
assert.equal(
new Set(clients).size,
clients.length,
"Cannot compare a client to itself"
);
const sorted = (files: Map<string, Uint8Array>) =>
[...files].sort(([a], [b]) => a.localeCompare(b));
for (let i = 1; i < clients.length; i++)
assert.deepEqual(
sorted(clients[i]),
sorted(clients[0]),
`Client ${i} differs from client 0 (paths or bytes)`
);
}
export function assertManifest(entries: Record<string, string>): void {
const paths = Object.values(entries);
const folded = paths.map((p) =>
p.normalize("NFC").toUpperCase().normalize("NFC")
);
const directorySpellings = new Map<string, string>();
for (const path of paths) {
assert.equal(
path,
path.normalize("NFC"),
`Noncanonical Unicode path: ${path}`
);
const parts = path.split("/");
parts.forEach((part, i) => {
assert(
Buffer.byteLength(part, "utf8") <= 255,
`Oversized component: ${part}`
);
assert(
!/[<>:"\\|?*\p{Cc}]|[. ]$/u.test(part),
`Nonportable component: ${part}`
);
assert(
!/^(CON|PRN|AUX|NUL|CONIN\$|CONOUT\$|COM[1-9¹²³]|LPT[1-9¹²³])$/u.test(
part.split(".")[0].trimEnd().toUpperCase()
),
`Reserved component: ${part}`
);
if (i < parts.length - 1) {
const prefix = parts.slice(0, i + 1).join("/");
const key = prefix.toUpperCase().normalize("NFC");
const previous = directorySpellings.get(key);
assert(
previous === undefined || previous === prefix,
`Aliased directory spellings: ${prefix} and ${previous}`
);
directorySpellings.set(key, prefix);
}
});
}
assert.equal(
new Set(folded).size,
paths.length,
"Manifest contains aliased/duplicate paths"
);
for (const path of folded) {
assert(
path && !path.startsWith("/") && !path.includes("\\"),
`Invalid path ${path}`
);
assert(
path.split("/").every((p) => p && p !== "." && p !== ".."),
`Invalid component ${path}`
);
assert(
path.split("/")[0] !== ".VAULT-LINK-SYNC",
"Internal path in manifest"
);
assert(
!folded.some(
(other) => other !== path && other.startsWith(`${path}/`)
),
`File/ancestor conflict: ${path}`
);
}
}
/** Independent per-ID policy oracle, intentionally not importing merge code. */
export function pathDecision(
base: string | undefined,
local: string | undefined,
remote: string | undefined
): string | undefined {
if (local === base) return remote;
if (remote === base) return local;
return remote;
}
export function assertQuiescent(
state: Partial<StoredDatabase>,
entries: Record<string, string>,
head: number
): void {
assert.equal(
state.pending,
undefined,
"Pending CAS request at convergence"
);
assert.equal(
state.eventReplay,
undefined,
"Unfinished event replay at convergence"
);
assert.equal(
state.application,
undefined,
"Unfinished filesystem journal at convergence"
);
assert.deepEqual(
state.local,
entries,
"Local UUID/path map differs from server"
);
assert.deepEqual(
state.fileManifest?.entries,
entries,
"Client manifest differs from server"
);
assert.equal(
state.lastSeenUpdateId,
head,
"Client event watermark differs from server"
);
assertManifest(entries);
}
/** Removal exemptions name exact markers and the explicit destructive action.
* Deletion mode never disables preservation for unrelated documents. */
export class ContentLedger {
private readonly markers = new Map<string, string | undefined>();
public add(marker: string): void {
assert(!this.markers.has(marker), "Duplicate marker");
this.markers.set(marker, undefined);
}
public removedBy(content: string, action: string): void {
assert(action.length > 0);
for (const marker of this.markers.keys())
if (content.includes(marker)) this.markers.set(marker, action);
}
public assertPreserved(files: Map<string, Uint8Array>): void {
const texts = [...files.values()].map((bytes) =>
Buffer.from(bytes).toString("utf8")
);
for (const [marker, deletion] of this.markers) {
const count = texts.reduce(
(n, text) => n + text.split(marker).length - 1,
0
);
assert(count <= 1, `Marker ${marker} duplicated ${count} times`);
if (!deletion)
assert.equal(
count,
1,
`Marker ${marker} lost without an explicit delete/overwrite`
);
}
}
}