This commit is contained in:
Andras Schmelczer 2026-09-27 11:09:36 +01:00
commit ff1f474324

View file

@ -1,9 +1,10 @@
import { SyncPhase } from "../sync-client/src/sync-operations/sync-scheduler";
import assert from "node:assert/strict";
import { SUPPORTED_API_VERSION } from "../sync-client/src/consts";
import { PermanentSyncError } from "../sync-client/src/errors/errors";
import { test } from "node:test";
import type { Database } from "../sync-client/src/persistence/database";
import { emptyState } from "../sync-client/src/persistence/database";
import { createEmptyDatabase } from "../sync-client/src/persistence/database";
import { SyncClient } from "../sync-client/src/sync-client";
import { hashSnapshot } from "../sync-client/src/snapshot";
import { EventListeners } from "../sync-client/src/utils/data-structures/event-listeners";
@ -32,9 +33,9 @@ test("ignored rename must not produce a duplicate submitted path", async () => {
relativePath: "a.md"
});
await f.settings.setSettings({ ignorePatterns: ["ignored.md"] });
await f.internals.scan();
f.internals.stopped = false;
await f.internals.preparePush();
await f.internals.refreshLocalFiles();
f.internals.scheduler.phase = SyncPhase.Idle;
await f.internals.prepareNextUpload();
const p = f.database.state.pending;
assert(p?.type === "fileManifest");
assert.equal(
@ -51,12 +52,12 @@ test("ignored untracked ancestor must remain in place", async () => {
await f.disk.userWrite("private", bytes("LOCAL PRIVATE"));
await f.settings.setSettings({ ignorePatterns: ["private"] });
(f.database.state.documents.a ??= {}).remote = head("a", 2, "remote");
await f.internals.reconcile({
await f.internals.applyRemoteChanges({
fileManifestId: 3,
entries: { a: "private/note.md" }
});
f.internals.stopped = false;
await f.internals.preparePush();
f.internals.scheduler.phase = SyncPhase.Idle;
await f.internals.prepareNextUpload();
assert.deepEqual(await f.disk.read("private"), {
content: bytes("LOCAL PRIVATE")
});
@ -70,7 +71,7 @@ test("oversized untracked file must remain in place", async () => {
await f.disk.userWrite("a.md", bytes("LOCAL OVERSIZED"));
await f.settings.setSettings({ maxFileSizeMB: 5 / (1024 * 1024) });
(f.database.state.documents.a ??= {}).remote = head("a", 2, "R");
await f.internals.reconcile({
await f.internals.applyRemoteChanges({
fileManifestId: 3,
entries: { a: "a.md" }
});
@ -105,15 +106,15 @@ test("rejected unknown success must use submitted content as merge base", async
};
f.database.state.lastSeenUpdateId = 1;
await f.disk.userWrite("a.md", bytes("base A"));
f.internals.stopped = false;
await f.internals.preparePush();
f.internals.scheduler.phase = SyncPhase.Idle;
await f.internals.prepareNextUpload();
const req = f.database.state.pending!.request.requestId;
await assert.rejects(f.internals.finishPending(), /413/);
await assert.rejects(f.internals.uploads.completePendingUpload(), /413/);
await f.disk.userWrite("a.md", bytes("base"));
reject = false;
await f.syncer.retryRejectedRequests();
await f.internals.finishPending();
await f.internals.incorporateEventBatch({
await f.internals.uploads.completePendingUpload();
await f.internals.applyEventBatch({
headEventId: 3,
endEventId: 3,
events: [
@ -150,7 +151,7 @@ test("bootstrap adopts same-path identity even after multiple notifications", as
fileManifest: { fileManifestId: 2, entries: { remoteA: "a.md" } },
documents: [head("remoteA", 1, "remote a")]
};
await f.internals.scan(initial);
await f.internals.refreshLocalFiles(initial);
assert.equal(f.database.state.local.remoteA, "a.md");
});
@ -176,9 +177,9 @@ test("oversized remote head must not cause identical stale uploads forever", asy
};
(f.database.state.documents.a ??= {}).remote = head("a", 2, remoteText);
await f.settings.setSettings({ maxFileSizeMB: 10 / (1024 * 1024) });
f.internals.stopped = false;
f.internals.scheduler.phase = SyncPhase.Idle;
// Even a small local edit must wait until its required remote base is readable.
assert.equal(await f.internals.preparePush(), false);
assert.equal(await f.internals.prepareNextUpload(), false);
assert.equal(attempts, 0);
assert.equal(f.database.state.pending, undefined);
});
@ -205,12 +206,12 @@ test("local undo of accepted-but-rejected manifest must survive replay", async (
oldPath: "a.md",
relativePath: "b.md"
});
await f.internals.scan();
f.internals.stopped = false;
await f.internals.preparePush();
await f.internals.refreshLocalFiles();
f.internals.scheduler.phase = SyncPhase.Idle;
await f.internals.prepareNextUpload();
const req = f.database.state.pending!.request.requestId;
f.internals.stopped = true;
await assert.rejects(f.internals.finishPending(), /413/);
f.internals.scheduler.phase = SyncPhase.Stopped;
await assert.rejects(f.internals.uploads.completePendingUpload(), /413/);
await f.disk.userRename("b.md", "a.md");
await f.syncer.syncLocallyUpdatedFile({
oldPath: "b.md",
@ -218,8 +219,8 @@ test("local undo of accepted-but-rejected manifest must survive replay", async (
});
reject = false;
await f.syncer.retryRejectedRequests();
await f.internals.finishPending();
await f.internals.incorporateEventBatch({
await f.internals.uploads.completePendingUpload();
await f.internals.applyEventBatch({
headEventId: 2,
endEventId: 2,
events: [
@ -397,7 +398,7 @@ test("oversized files should be filtered before reading complete snapshots", asy
f.disk.boundary = (label) => {
if (label === "read:snapshot:huge.bin") reads++;
};
await f.internals.scan();
await f.internals.refreshLocalFiles();
assert.equal(reads, 0);
});
@ -412,7 +413,7 @@ test("permanent destination failure replans the affected file and finishes the b
const next = structuredClone(f.database.state);
next.local = { a: "unsupported/a.md", b: "good.md" };
next.fileManifest = { fileManifestId: 2, entries: { ...next.local } };
await f.files.apply(next);
await f.files.applyChanges(next);
await f.settings.setSettings({ ignorePatterns: ["unsupported/**"] });
assert.deepEqual(
[...f.disk.userFiles().values()]
@ -508,7 +509,7 @@ for (const interrupted of [false, true]) {
await f.database.save();
const saved = f.persistence.snapshot();
const catchUp = async () =>
f.internals.incorporateEventBatch(
f.internals.applyEventBatch(
await service.getEvents!(f.database.state.lastSeenUpdateId!)
);
if (interrupted) {
@ -539,7 +540,7 @@ test("an ignored occupant arriving after the scan keeps its path and privacy", a
);
await f.settings.setSettings({ ignorePatterns: ["private"] });
(f.database.state.documents.a ??= {}).remote = head("a", 2, "remote");
await f.internals.reconcile({
await f.internals.applyRemoteChanges({
fileManifestId: 3,
entries: { a: "private/note.md" }
});
@ -547,8 +548,8 @@ test("an ignored occupant arriving after the scan keeps its path and privacy", a
(await f.disk.read("private"))?.content,
bytes("SECRET")
);
f.internals.stopped = false;
await f.internals.preparePush();
f.internals.scheduler.phase = SyncPhase.Idle;
await f.internals.prepareNextUpload();
const { pending } = f.database.state;
assert(
pending?.type !== "content" ||
@ -571,7 +572,7 @@ test("content notifications during every snapshot do not starve a complete names
});
}
};
await f.internals.scan();
await f.internals.refreshLocalFiles();
assert.equal(notifications, 2);
assert.deepEqual(f.database.state.local, { a: "a.md", b: "b.md" });
});
@ -604,10 +605,10 @@ for (const boundary of ["before:save", "durable:save"]) {
};
f.database.state.lastSeenUpdateId = 1;
await f.disk.userWrite("a.md", bytes("base A"));
f.internals.stopped = false;
await f.internals.preparePush();
f.internals.scheduler.phase = SyncPhase.Idle;
await f.internals.prepareNextUpload();
const { requestId } = f.database.state.pending!.request;
await assert.rejects(f.internals.finishPending(), /413/);
await assert.rejects(f.internals.uploads.completePendingUpload(), /413/);
await f.disk.userWrite("a.md", bytes("base"));
const first = {
headEventId: 3,
@ -639,15 +640,15 @@ for (const boundary of ["before:save", "durable:save"]) {
if (label === boundary) throw new Error("injected crash");
};
await assert.rejects(
f.internals.finishPending(),
f.internals.uploads.completePendingUpload(),
/injected crash/
);
f.persistence.boundary = () => { };
f.disk.crash(true);
f = await fixture({}, service, f);
assert.equal(f.database.state.lastSeenUpdateId, 1);
await f.internals.finishPending();
await f.internals.incorporateEventBatch(first);
await f.internals.uploads.completePendingUpload();
await f.internals.applyEventBatch(first);
assert.equal(f.database.state.lastSeenUpdateId, 3);
assert.deepEqual(
(await f.disk.read("a.md"))?.content,
@ -753,21 +754,21 @@ for (const retry of [false, true]) {
};
f.database.state.lastSeenUpdateId = 1;
await f.disk.userWrite("a.md", bytes("edited"));
f.internals.stopped = false;
await f.internals.preparePush();
if (retry) await assert.rejects(f.internals.finishPending(), /413/);
f.internals.scheduler.phase = SyncPhase.Idle;
await f.internals.prepareNextUpload();
if (retry) await assert.rejects(f.internals.uploads.completePendingUpload(), /413/);
// Recovery folds queued deletions into the durable namespace before
// an outstanding request is retried.
f.internals.stopped = true;
f.internals.scheduler.phase = SyncPhase.Stopped;
await f.disk.userDelete("a.md");
await f.syncer.syncLocallyDeletedFile("a.md");
await f.internals.scan();
await f.internals.refreshLocalFiles();
assert.deepEqual(f.database.state.local, {});
if (retry) {
reject = false;
await f.syncer.retryRejectedRequests();
}
await f.internals.finishPending();
await f.internals.uploads.completePendingUpload();
assert.equal(f.database.state.pending, undefined);
assert.deepEqual(f.database.state.local, {});
assert.equal(await f.disk.read("a.md"), undefined);