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

407 lines
15 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { fixture, bytes, head } from "./sync-fixture";
import { toStoredSnapshot } from "../sync-client/src/sync-operations/content";
for (const origin of ["bootstrap", "history recovery"])
test(`cancelling a replacement retains the ${origin} merge context`, async () => {
const f = await fixture(
{ "a.md": "BASE\n" },
{ getDocumentVersionContent: async () => bytes("REMOTE\nBASE\n") }
);
const recoveryBase = await toStoredSnapshot({
content: bytes("BASE\n")
});
if (origin === "bootstrap")
f.database.state.documents.a.bootstrap = true;
else f.database.state.documents.a.recoveryBase = recoveryBase;
await f.database.save();
await f.settings.setSetting("ignorePatterns", ["private.md"]);
let injected = false;
f.disk.boundary = async (label) => {
if (
injected ||
!label.startsWith("before:mkdir:.vault-link-sync/transactions/")
)
return;
injected = true;
await f.disk.userRename("a.md", "private.md");
await f.disk.userWrite("private.md", bytes("BASE\nLOCAL\n"));
await f.syncer.syncLocallyUpdatedFile({
oldPath: "a.md",
relativePath: "private.md"
});
};
await f.internals.incorporateContent(head("a", 2, "REMOTE\nBASE\n"));
assert(injected);
const deferred = f.database.state.documents.a;
assert.equal(deferred.base, undefined);
if (origin === "bootstrap") assert.equal(deferred.bootstrap, true);
else assert.deepEqual(deferred.recoveryBase, recoveryBase);
f.disk.boundary = () => {};
await f.settings.setSetting("ignorePatterns", []);
await f.internals.incorporateFileManifest(
f.database.state.fileManifest
);
const content = Buffer.from(
f.disk.userFiles().get("private.md")!
).toString();
assert.equal(content.match(/REMOTE/g)?.length, 1);
assert.equal(content.match(/LOCAL/g)?.length, 1);
assert.equal(f.database.state.documents.a.base?.vaultUpdateId, 2);
assert.equal(f.database.state.documents.a.recoveryBase, undefined);
});
for (const timing of ["before", "durable"])
test(`a save that exceeds the size limit ${timing} staging keeps its exact bytes`, async () => {
const f = await fixture({ "a.bin": "PUBLIC" });
await f.settings.setSetting("maxFileSizeMB", 100 / (1024 * 1024));
const large = "PRIVATE".repeat(100);
let injected = false;
f.disk.boundary = async (label) => {
if (
injected ||
!label.startsWith(
`${timing}:rename:a.bin->.vault-link-sync/transactions/`
)
)
return;
injected = true;
await f.disk.userWrite("a.bin", bytes(large));
await f.syncer.syncLocallyUpdatedFile({ relativePath: "a.bin" });
};
await f.files.apply(structuredClone(f.database.state), {
a: {
expected: await toStoredSnapshot({ content: bytes("PUBLIC") }),
replacement: await toStoredSnapshot({
content: bytes("REMOTE")
})
}
});
assert(injected);
assert.equal(
Buffer.from(f.disk.userFiles().get("a.bin")!).toString(),
large
);
assert.equal(f.database.state.excluded?.a, "a.bin");
assert.deepEqual(f.database.state.local, { a: "a.bin" });
});
test("an excluded move after installation survives an uncertain journal acknowledgement", async () => {
const f = await fixture({ "a.md": "PUBLIC" });
await f.settings.setSetting("ignorePatterns", ["private.md"]);
let moved = false,
crashed = false;
f.disk.boundary = async (label) => {
if (
moved ||
!label.startsWith("durable:rename:") ||
!label.endsWith(".output->a.md")
)
return;
moved = true;
await f.disk.userRename("a.md", "private.md");
await f.disk.userWrite("private.md", bytes("SECRET"));
await f.syncer.syncLocallyUpdatedFile({
oldPath: "a.md",
relativePath: "private.md"
});
};
f.persistence.boundary = (label) => {
if (!moved || crashed || label !== "durable:save") return;
crashed = true;
throw new Error("lost journal acknowledgement");
};
await assert.rejects(
f.files.apply(structuredClone(f.database.state), {
a: {
expected: await toStoredSnapshot({ content: bytes("PUBLIC") }),
replacement: await toStoredSnapshot({
content: bytes("PUBLIC REMOTE")
})
}
}),
/lost journal acknowledgement/
);
assert(moved && crashed);
f.disk.boundary = () => {};
f.persistence.boundary = () => {};
await f.database.recoverPersistence();
await f.files.recover();
assert.deepEqual(f.database.state.local, { a: "private.md" });
assert.equal(
Buffer.from(f.disk.userFiles().get("private.md")!).toString(),
"SECRET"
);
});
test("moving a tracked file into an ignored path during a journal leaves it protected", async () => {
const f = await fixture({ "a.md": "PUBLIC", "b.md": "OTHER" });
await f.settings.setSetting("ignorePatterns", ["private.md"]);
const next = structuredClone(f.database.state);
next.local.b = "remote-b.md";
let injected = false;
f.disk.boundary = async (label) => {
if (
injected ||
!label.startsWith("before:mkdir:.vault-link-sync/transactions/")
)
return;
injected = true;
await f.disk.userRename("a.md", "private.md");
await f.disk.userWrite("private.md", bytes("SECRET"));
await f.syncer.syncLocallyUpdatedFile({
oldPath: "a.md",
relativePath: "private.md"
});
};
await f.files.apply(next);
assert(injected);
assert.equal(
Buffer.from(f.disk.userFiles().get("private.md") ?? []).toString(),
"SECRET"
);
assert.equal(f.database.state.local.a, "private.md");
assert.equal(f.database.state.excluded?.a, "a.md");
});
test("a directory move protects each ignored descendant during a journal", async () => {
const f = await fixture({ "folder/a.md": "PUBLIC", "b.md": "OTHER" });
await f.settings.setSetting("ignorePatterns", ["private/**"]);
const next = structuredClone(f.database.state);
next.local.b = "remote-b.md";
let injected = false;
f.disk.boundary = async (label) => {
if (
injected ||
!label.startsWith("before:mkdir:.vault-link-sync/transactions/")
)
return;
injected = true;
await f.disk.userRename("folder/a.md", "private/a.md");
await f.disk.userWrite("private/a.md", bytes("SECRET"));
await f.syncer.syncLocallyUpdatedFile({
oldPath: "folder",
relativePath: "private"
});
};
await f.files.apply(next);
assert(injected);
assert.equal(
Buffer.from(f.disk.userFiles().get("private/a.md") ?? []).toString(),
"SECRET"
);
assert.equal(f.database.state.local["folder/a"], "private/a.md");
assert.equal(f.database.state.excluded?.["folder/a"], "folder/a.md");
});
test("a later editor save replaces earlier unsynced text after a merge was prepared", async () => {
const f = await fixture({ "a.md": "base" });
const next = structuredClone(f.database.state);
let saves = 0;
f.disk.boundary = async (label) => {
if (saves === 0 && label.startsWith("before:rename:a.md->")) {
saves++;
await f.disk.userWrite("a.md", bytes("base ONE"));
await f.syncer.syncLocallyUpdatedFile({ relativePath: "a.md" });
} else if (
saves === 1 &&
label.startsWith("before:write:") &&
label.endsWith(".output")
) {
saves++;
await f.disk.userWrite("a.md", bytes("base TWO"));
await f.syncer.syncLocallyUpdatedFile({ relativePath: "a.md" });
}
};
await f.files.apply(next, {
a: {
expected: await toStoredSnapshot({ content: bytes("base") }),
replacement: await toStoredSnapshot({
content: bytes("base REMOTE")
})
}
});
assert.equal(saves, 2);
assert.equal(
Buffer.from(f.disk.userFiles().get("a.md")!).toString(),
"base TWO REMOTE"
);
assert.deepEqual(f.database.state.local, { a: "a.md" });
});
test("a content save during remote application remains on the same document", async () => {
const f = await fixture({ "a.md": "base" });
f.database.state.documents.a.base = {
...head("a", 1, "base"),
hash: f.database.state.documents.a.observedHash!
};
const next = structuredClone(f.database.state);
next.documents.a.base = {
...head("a", 2, "base REMOTE"),
hash: (await toStoredSnapshot({ content: bytes("base REMOTE") })).hash
};
let injected = false;
f.disk.boundary = async (label) => {
if (
injected ||
!label.startsWith(
"durable:rename:a.md->.vault-link-sync/transactions/"
)
)
return;
injected = true;
await f.disk.userWrite("a.md", bytes("base LOCAL"));
await f.syncer.syncLocallyUpdatedFile({ relativePath: "a.md" });
};
await f.files.apply(next, {
a: {
expected: await toStoredSnapshot({ content: bytes("base") }),
replacement: await toStoredSnapshot({
content: bytes("base REMOTE")
})
}
});
assert.equal(f.disk.userFiles().size, 1);
const result = Buffer.from(
(await f.disk.readSnapshot("a.md"))!.content
).toString();
assert(result.includes("LOCAL") && result.includes("REMOTE"));
});
async function setup() {
const f = await fixture({ "a.md": "ORIGINAL", "b.md": "OTHER" });
f.database.state.documents.a.base = {
...head("a", 1, "ORIGINAL"),
hash: f.database.state.documents.a.observedHash!
};
const next = structuredClone(f.database.state);
next.local = { a: "remote.md", b: "remote-b.md" };
next.fileManifest = { fileManifestId: 3, entries: { ...next.local } };
const expected = await toStoredSnapshot({ content: bytes("ORIGINAL") });
const replacement = await toStoredSnapshot({
content: bytes("ORIGINAL REMOTE")
});
next.documents.a.base = {
...head("a", 2, "ORIGINAL REMOTE"),
hash: replacement.hash
};
return { f, next, writes: { a: { expected, replacement } } };
}
test("editor changes preserve exact bytes and identities at every filesystem read, mutation and persistence boundary", async (t) => {
const { f, next, writes } = await setup();
const labels: string[] = [];
const trace = (l: string) => {
labels.push(l);
};
f.disk.boundary = trace;
f.persistence.boundary = trace;
await f.files.apply(next, writes);
const failures = [];
let exercised = 0;
for (const kind of ["move", "move-recreate", "delete-recreate", "save"])
for (let target = 0; target < labels.length; target++) {
const { f, next, writes } = await setup();
let count = 0,
injected = false,
from: string | undefined;
const boundary = async (l: string) => {
if (injected) return;
if (count++ !== target) return;
const files = f.disk.userFiles();
from = files.has("a.md")
? "a.md"
: files.has("remote.md")
? "remote.md"
: undefined;
if (kind === "save" && !from) from = "a.md";
if (!from) return;
injected = true;
exercised++;
if (kind === "save") {
await f.disk.userWrite(
from,
bytes(
Buffer.from(
files.get(from) ?? bytes("ORIGINAL")
).toString() + " LOCAL"
)
);
await f.syncer.syncLocallyUpdatedFile({
relativePath: from
});
} else if (kind === "delete-recreate") {
await f.disk.userDelete(from);
await f.syncer.syncLocallyDeletedFile(from);
} else {
await f.disk.userRename(from, "mine.md");
await f.syncer.syncLocallyUpdatedFile({
oldPath: from,
relativePath: "mine.md"
});
}
if (kind !== "move" && kind !== "save") {
await f.disk.userWrite(from, bytes("REPLACEMENT"));
await f.syncer.syncLocallyCreatedFile(from);
}
};
f.disk.boundary = boundary;
f.persistence.boundary = boundary;
let error;
try {
await f.files.apply(next, writes);
} catch (e) {
error = String(e);
}
f.disk.boundary = () => {};
f.persistence.boundary = () => {};
try {
await f.database.recoverPersistence();
await f.files.recover();
await f.internals.scan();
} catch (e) {
error += ";retry=" + String(e);
}
if (!injected) continue;
const fs = Object.fromEntries(
[...f.disk.userFiles()].map(([p, b]) => [
p,
Buffer.from(b).toString()
])
);
const bad =
kind === "save"
? ![
"ORIGINAL LOCAL REMOTE",
"ORIGINAL REMOTE LOCAL"
].includes(fs[f.database.state.local.a]) ||
Object.keys(f.database.state.local).length !== 2
: kind === "delete-recreate"
? f.database.state.local.a !== undefined
: f.database.state.local.a !== "mine.md" ||
fs["mine.md"] !== "ORIGINAL REMOTE";
if (
bad ||
(kind !== "move" &&
kind !== "save" &&
fs[from!] !== "REPLACEMENT")
)
failures.push({
kind,
target,
label: labels[target],
error,
ids: f.database.state.local,
files: fs
});
}
assert(
exercised > 50,
"The sweep must actually exercise the injected actions"
);
t.diagnostic(
`${labels.length} boundaries; ${exercised} editor interleavings`
);
assert.deepEqual(failures, []);
});