no power loss
This commit is contained in:
parent
d56084e4bc
commit
2fcdbfd27d
1 changed files with 1374 additions and 3676 deletions
219
frontend/test-support/file-application.test.ts
Normal file
219
frontend/test-support/file-application.test.ts
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { fixture, bytes } from "./sync-fixture";
|
||||
import { toStoredSnapshot } from "../sync-client/src/sync-operations/content";
|
||||
import { LocalChangesDuringReconciliation } from "../sync-client/src/errors/errors";
|
||||
|
||||
for (const equal of [false, true])
|
||||
test(`swaps preserve identities with ${equal ? "equal" : "distinct"} content and save once`, async () => {
|
||||
const f = await fixture({ "a.md": "A", "b.md": equal ? "A" : "B" });
|
||||
let saves = 0;
|
||||
f.persistence.boundary = (label) => {
|
||||
if (label === "before:save") saves++;
|
||||
};
|
||||
const next = structuredClone(f.database.state);
|
||||
next.local = { a: "b.md", b: "a.md" };
|
||||
await f.files.apply(next);
|
||||
assert.deepEqual(f.database.state.local, next.local);
|
||||
assert.deepEqual(
|
||||
f.disk.userFiles(),
|
||||
new Map([
|
||||
["a.md", bytes(equal ? "A" : "B")],
|
||||
["b.md", bytes("A")]
|
||||
])
|
||||
);
|
||||
assert.equal(saves, 1);
|
||||
assert.deepEqual(await f.disk.listFilesRecursively(), ["a.md", "b.md"]);
|
||||
});
|
||||
|
||||
for (const direction of ["file-to-directory", "directory-to-file"])
|
||||
test(direction, async () => {
|
||||
const f = await fixture(
|
||||
direction === "file-to-directory"
|
||||
? { folder: "A", "b.md": "B" }
|
||||
: { "a.md": "A", "folder/b.md": "B" }
|
||||
);
|
||||
const ids = Object.keys(f.database.state.local);
|
||||
const next = structuredClone(f.database.state);
|
||||
next.local =
|
||||
direction === "file-to-directory"
|
||||
? { [ids[0]]: "folder/a.md", [ids[1]]: "b.md" }
|
||||
: { [ids[0]]: "folder", [ids[1]]: "other.md" };
|
||||
const expected = structuredClone(next.local);
|
||||
await f.files.apply(next);
|
||||
assert.deepEqual(f.database.state.local, expected);
|
||||
assert.deepEqual(
|
||||
new Set(f.disk.userFiles().keys()),
|
||||
new Set(Object.values(f.database.state.local))
|
||||
);
|
||||
assert.deepEqual(
|
||||
[...f.disk.userFiles().values()]
|
||||
.map((value) => Buffer.from(value).toString())
|
||||
.sort(),
|
||||
["A", "B"]
|
||||
);
|
||||
});
|
||||
|
||||
test("an unexpected occupant is preserved under a visible conflict name", async () => {
|
||||
const f = await fixture();
|
||||
await f.disk.userWrite("remote.md", bytes("unexpected"));
|
||||
const next = structuredClone(f.database.state);
|
||||
next.local.a = "remote.md";
|
||||
await f.files.apply(next);
|
||||
assert.equal(f.database.state.local.a, "remote.md");
|
||||
assert.equal(f.disk.userFiles().size, 2);
|
||||
assert.deepEqual(
|
||||
[...f.disk.userFiles().values()]
|
||||
.map((value) => Buffer.from(value).toString())
|
||||
.sort(),
|
||||
["local", "unexpected"]
|
||||
);
|
||||
assert.equal(Object.keys(f.database.state.local).length, 2);
|
||||
});
|
||||
|
||||
test("content edited after planning is merged with the incoming snapshot", async () => {
|
||||
const f = await fixture({ "a.md": "base\n" });
|
||||
const expected = (await f.files.snapshot("a.md"))!;
|
||||
await f.disk.userWrite("a.md", bytes("base\nlocal\n"));
|
||||
await f.files.apply(structuredClone(f.database.state), {
|
||||
a: {
|
||||
expected,
|
||||
replacement: await toStoredSnapshot({
|
||||
content: bytes("remote\nbase\n")
|
||||
})
|
||||
}
|
||||
});
|
||||
assert.deepEqual(
|
||||
f.disk.userFiles().get("a.md"),
|
||||
bytes("remote\nbase\nlocal\n")
|
||||
);
|
||||
});
|
||||
|
||||
test("deleting the source after planning does not resurrect it", async () => {
|
||||
const f = await fixture();
|
||||
const next = structuredClone(f.database.state);
|
||||
next.local.a = "remote.md";
|
||||
await f.disk.userDelete("a.md");
|
||||
await f.files.apply(next, {
|
||||
a: { replacement: await toStoredSnapshot({ content: bytes("remote") }) }
|
||||
});
|
||||
assert.deepEqual(f.disk.userFiles(), new Map());
|
||||
assert.deepEqual(f.database.state.local, {});
|
||||
});
|
||||
|
||||
for (const protect of ["ignore", "size"] as const)
|
||||
test(`protect ${protect} at application time`, async () => {
|
||||
const f = await fixture();
|
||||
const next = structuredClone(f.database.state);
|
||||
next.local.a = "remote.md";
|
||||
if (protect === "ignore")
|
||||
await f.settings.setSetting("ignorePatterns", ["a.md"]);
|
||||
else await f.settings.setSetting("maxFileSizeMB", 0);
|
||||
await f.files.apply(next, {
|
||||
a: {
|
||||
replacement: await toStoredSnapshot({
|
||||
content: bytes("remote")
|
||||
})
|
||||
}
|
||||
});
|
||||
assert.deepEqual(
|
||||
f.disk.userFiles(),
|
||||
new Map([["a.md", bytes("local")]])
|
||||
);
|
||||
assert.equal(f.database.state.local.a, "a.md");
|
||||
assert.equal(f.database.state.excluded?.a, "a.md");
|
||||
});
|
||||
|
||||
for (const when of ["planning", "installed"])
|
||||
test(`notified move during ${when} is attributed to the right identity on rescan`, async () => {
|
||||
const f = await fixture({ "a.md": "A", "b.md": "B" });
|
||||
let changed = false;
|
||||
f.disk.boundary = async (label) => {
|
||||
if (
|
||||
changed ||
|
||||
label !==
|
||||
(when === "planning"
|
||||
? "read:stat:a.md"
|
||||
: "visible:rename:a.md->b.md")
|
||||
)
|
||||
return;
|
||||
changed = true;
|
||||
const oldPath = when === "planning" ? "a.md" : "b.md";
|
||||
await f.disk.userRename(oldPath, "editor.md");
|
||||
await f.syncer.syncLocallyUpdatedFile({
|
||||
oldPath,
|
||||
relativePath: "editor.md"
|
||||
});
|
||||
};
|
||||
const next = structuredClone(f.database.state);
|
||||
next.local = { a: "b.md", b: "a.md" };
|
||||
if (when === "planning")
|
||||
await assert.rejects(
|
||||
f.files.apply(next),
|
||||
LocalChangesDuringReconciliation
|
||||
);
|
||||
else await f.files.apply(next);
|
||||
assert(changed);
|
||||
await f.internals.scan();
|
||||
assert.equal(f.database.state.local.a, "editor.md");
|
||||
assert.deepEqual(
|
||||
f.disk.userFiles().get(f.database.state.local.a),
|
||||
bytes("A")
|
||||
);
|
||||
assert.deepEqual(
|
||||
f.disk.userFiles().get(f.database.state.local.b),
|
||||
bytes("B")
|
||||
);
|
||||
});
|
||||
|
||||
for (const action of ["move", "delete"] as const)
|
||||
test(`notified ${action}/recreate between file operations does not repurpose the replacement file`, async () => {
|
||||
const f = await fixture({ "a.md": "A", "b.md": "B" });
|
||||
await f.settings.setSetting("ignorePatterns", ["private.md"]);
|
||||
let changed = false;
|
||||
f.disk.boundary = async (label) => {
|
||||
if (changed || label !== "visible:rename:a.md->remote-a.md") return;
|
||||
changed = true;
|
||||
if (action === "move") {
|
||||
await f.disk.userRename("b.md", "private.md");
|
||||
await f.syncer.syncLocallyUpdatedFile({
|
||||
oldPath: "b.md",
|
||||
relativePath: "private.md"
|
||||
});
|
||||
} else {
|
||||
await f.disk.userDelete("b.md");
|
||||
await f.syncer.syncLocallyDeletedFile("b.md");
|
||||
}
|
||||
await f.disk.userWrite("b.md", bytes("replacement"));
|
||||
await f.syncer.syncLocallyCreatedFile("b.md");
|
||||
};
|
||||
const next = structuredClone(f.database.state);
|
||||
next.local = { a: "remote-a.md", b: "remote-b.md" };
|
||||
await f.files.apply(next);
|
||||
assert(changed);
|
||||
await f.internals.scan();
|
||||
assert.deepEqual(f.disk.userFiles().get("b.md"), bytes("replacement"));
|
||||
assert.equal(f.disk.userFiles().has("remote-b.md"), false);
|
||||
assert.equal(
|
||||
f.database.state.local.b,
|
||||
action === "move" ? "private.md" : undefined
|
||||
);
|
||||
if (action === "move")
|
||||
assert.deepEqual(f.disk.userFiles().get("private.md"), bytes("B"));
|
||||
});
|
||||
|
||||
test("a file and nested directory exchange their exact paths", async () => {
|
||||
const f = await fixture({ a: "A", "b/deep/child": "B" });
|
||||
const [a, b] = Object.keys(f.database.state.local);
|
||||
const next = structuredClone(f.database.state);
|
||||
next.local = { [a]: "b", [b]: "a/deep/child" };
|
||||
await f.files.apply(next);
|
||||
assert.deepEqual(f.database.state.local, { [a]: "b", [b]: "a/deep/child" });
|
||||
assert.deepEqual(
|
||||
f.disk.userFiles(),
|
||||
new Map([
|
||||
["b", bytes("A")],
|
||||
["a/deep/child", bytes("B")]
|
||||
])
|
||||
);
|
||||
});
|
||||
Loading…
Reference in a new issue