.
This commit is contained in:
parent
25f64ec690
commit
ff1f474324
1 changed files with 3271 additions and 2003 deletions
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue