.
This commit is contained in:
parent
9e81343ab1
commit
3160e850ca
27 changed files with 288 additions and 103 deletions
|
|
@ -3,9 +3,15 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = {
|
||||
description:
|
||||
"Client 0 sends three rapid updates. After syncing, both clients " +
|
||||
"disconnect and reconnect twice. Content should remain correct " +
|
||||
"after each reconnect.",
|
||||
"Probes that the watermark advances correctly through coalesced " +
|
||||
"remote updates. Client 0 sends three rapid updates, all observed " +
|
||||
"by Client 1 (their vault_update_ids may coalesce in the engine's " +
|
||||
"MinCovered). Then Client 1 disconnects and Client 0 issues one " +
|
||||
"MORE update while Client 1 is offline. On Client 1's reconnect, " +
|
||||
"catch-up uses last_seen_vault_update_id — if the coalesced " +
|
||||
"updates wrongly advanced the watermark past Client 0's offline " +
|
||||
"update's id, that update is silently lost. The final assert " +
|
||||
"pins Client 1 receiving the post-reconnect update via catch-up.",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{ type: "create", client: 0, path: "doc.md", content: "original" },
|
||||
|
|
@ -13,40 +19,40 @@ export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "barrier" },
|
||||
|
||||
// Three rapid updates — coalesce in engine queues; both clients
|
||||
// converge to "final update".
|
||||
{ type: "update", client: 0, path: "doc.md", content: "update 1" },
|
||||
{ type: "update", client: 0, path: "doc.md", content: "update 2" },
|
||||
{ type: "update", client: 0, path: "doc.md", content: "final update" },
|
||||
|
||||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContent("doc.md", "final update");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
// Client 1 goes offline.
|
||||
{ type: "disable-sync", client: 1 },
|
||||
{ type: "enable-sync", client: 0 },
|
||||
|
||||
// Client 0 issues a follow-up edit while c1 is offline. This
|
||||
// event has a vault_update_id strictly greater than the
|
||||
// coalesced sequence; if c1's watermark is correct, catch-up
|
||||
// will return it. If the watermark wrongly advanced past it
|
||||
// during the coalesce (too-new), catch-up returns nothing and
|
||||
// c1 silently misses this edit.
|
||||
{
|
||||
type: "update",
|
||||
client: 0,
|
||||
path: "doc.md",
|
||||
content: "post-reconnect edit"
|
||||
},
|
||||
{ type: "sync", client: 0 },
|
||||
|
||||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "barrier" },
|
||||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContent("doc.md", "final update");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
{ type: "disable-sync", client: 1 },
|
||||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContent("doc.md", "final update");
|
||||
s.assertFileCount(1).assertContent(
|
||||
"doc.md",
|
||||
"post-reconnect edit"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,13 @@ export const createRenameResponseSkipsFileTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertAnyFileContains("the-content");
|
||||
// The rename must land at renamed.md on both clients.
|
||||
// assertAnyFileContains alone would have passed even if
|
||||
// the rename were dropped server-side and both clients
|
||||
// converged on doc.md with "the-content".
|
||||
s.assertFileCount(1)
|
||||
.assertContent("renamed.md", "the-content")
|
||||
.assertFileNotExists("doc.md");
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ export const deleteByOtherClientThenRecreateTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("A.md", "recreated by client 0");
|
||||
s.assertFileCount(1).assertContent(
|
||||
"A.md",
|
||||
"recreated by client 0"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -35,7 +35,14 @@ export const deleteRecreateConcurrentUpdateTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileExists("A.md").assertContains("A.md", "recreated");
|
||||
// The description commits to "client 0's recreated content
|
||||
// preserved" — so pin to a single file at A.md with the
|
||||
// recreated content. A deconflicted "updated by client 1"
|
||||
// file would slip past assertContains/assertFileExists.
|
||||
s.assertFileCount(1).assertContent(
|
||||
"A.md",
|
||||
"recreated by client 0"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,29 +3,45 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const idempotencyAfterServerPauseTest: TestDefinition = {
|
||||
description:
|
||||
"Client 0 creates a file, then the server is paused mid-response. " +
|
||||
"After the server resumes, both clients must converge to a single copy of the file with no duplicates.",
|
||||
"The server commits Client 0's create but Client 0 never sees the " +
|
||||
"response — simulating a connection drop after server-side commit. " +
|
||||
"drop-next-create-response intercepts the response in the client's " +
|
||||
"fetch wrapper after the server has already processed the POST, " +
|
||||
"raising SyncResetError. The client's offline-scan retry must be " +
|
||||
"idempotent: server-side dedup of the retried create + the " +
|
||||
"already-committed doc must NOT produce a duplicate file. " +
|
||||
"(Earlier version did `create -> pause -> resume`, where the " +
|
||||
"create could complete cleanly before the pause and the " +
|
||||
"idempotency path was never exercised.)",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "barrier" },
|
||||
|
||||
// Arm the interceptor BEFORE the create so the very first POST
|
||||
// /documents from c0 has its response dropped after server commit.
|
||||
{ type: "drop-next-create-response", client: 0 },
|
||||
|
||||
{
|
||||
type: "create",
|
||||
client: 0,
|
||||
path: "doc.md",
|
||||
content: "important data"
|
||||
},
|
||||
{ type: "pause-server" },
|
||||
|
||||
{ type: "resume-server" },
|
||||
// Block until the server has committed and the client has been
|
||||
// notified the response was dropped — deterministic happens-before
|
||||
// for "server has the doc, client thinks the create failed".
|
||||
{ type: "wait-for-dropped-create-response", client: 0 },
|
||||
|
||||
{ type: "barrier" },
|
||||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
// No duplicate doc despite the client's retry: server-side
|
||||
// path-collision merge or idempotency must collapse them.
|
||||
s.assertFileCount(1).assertContent("doc.md", "important data");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,13 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const interruptedDeleteRetryTest: TestDefinition = {
|
||||
description:
|
||||
"Client 0 deletes a file, then the server is paused. " +
|
||||
"After the server resumes, both clients should have zero files.",
|
||||
"Client 0's delete HTTP is interrupted (server paused) and must " +
|
||||
"retry on resume. Pause is established BEFORE the delete is " +
|
||||
"issued so the DELETE is deterministically in-flight against a " +
|
||||
"frozen server — the earlier ordering (delete then pause) raced " +
|
||||
"the request: under fast scheduling the DELETE could commit " +
|
||||
"before SIGSTOP and the test reduced to a trivial " +
|
||||
"create-then-delete with no interruption at all.",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{ type: "create", client: 0, path: "doc.md", content: "to be deleted" },
|
||||
|
|
@ -12,11 +17,10 @@ export const interruptedDeleteRetryTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "barrier" },
|
||||
|
||||
{ type: "delete", client: 0, path: "doc.md" },
|
||||
|
||||
{ type: "pause-server" },
|
||||
|
||||
{ type: "delete", client: 0, path: "doc.md" },
|
||||
{ type: "resume-server" },
|
||||
|
||||
{ type: "barrier" },
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,18 +3,35 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const localEditLostDuringCreateMergeTest: TestDefinition = {
|
||||
description:
|
||||
"Both clients create doc.md with different content while offline. " +
|
||||
"Client 0 also edits the file before syncing. After both connect, " +
|
||||
"the merged result should contain content from both clients.",
|
||||
"Client 0's create is in-flight (server paused) when Client 0 " +
|
||||
"writes a follow-up local edit. The wire-loop will receive the " +
|
||||
"create response, then must apply the queued local update against " +
|
||||
"the now-resolved doc id rather than discarding it as a stale " +
|
||||
"pending-create. Client 1's pre-existing same-path doc forces a " +
|
||||
"server-side merge, exercising the path where the local edit must " +
|
||||
"be re-attached to the merged doc id via replacePendingDocumentId.",
|
||||
clients: 2,
|
||||
steps: [
|
||||
// Client 1 creates and syncs doc.md first — this is the doc the
|
||||
// server will merge Client 0's later create into.
|
||||
{ type: "create", client: 1, path: "doc.md", content: "from-client-1" },
|
||||
{
|
||||
type: "create",
|
||||
client: 0,
|
||||
path: "doc.md",
|
||||
content: "from-client-0"
|
||||
},
|
||||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "sync", client: 1 },
|
||||
|
||||
// Client 0 starts offline so the create that follows queues into
|
||||
// the engine rather than firing immediately.
|
||||
{ type: "create", client: 0, path: "doc.md", content: "from-client-0" },
|
||||
|
||||
// Pause the server so c0's POST /documents will hang once it goes.
|
||||
{ type: "pause-server" },
|
||||
{ type: "enable-sync", client: 0 },
|
||||
|
||||
// While the create's HTTP is in-flight against the paused server,
|
||||
// c0's local edit lands. This is the queue-coalesce scenario the
|
||||
// engine must survive: the LocalUpdate carries a Promise<DocId>
|
||||
// chained off the pending create, and replacePendingDocumentId
|
||||
// must rewire it once the server's merge response resolves to
|
||||
// Client 1's existing doc id.
|
||||
{
|
||||
type: "update",
|
||||
client: 0,
|
||||
|
|
@ -22,17 +39,19 @@ export const localEditLostDuringCreateMergeTest: TestDefinition = {
|
|||
content: "local-edit-during-create"
|
||||
},
|
||||
|
||||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "sync", client: 1 },
|
||||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "resume-server" },
|
||||
{ type: "barrier" },
|
||||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
// The local edit must NOT be lost in the create-merge
|
||||
// collapse. assertContent (not assertContains) pins the
|
||||
// exact post-merge state — we accept either pure local
|
||||
// content (replace-wins) or a true merge containing both
|
||||
// contributions, but the local edit must be present.
|
||||
s.assertFileCount(1).assertContains(
|
||||
"doc.md",
|
||||
"from-client-1",
|
||||
"local-edit-during-create"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ export const mcDeleteThenOfflineRenameTest: TestDefinition = {
|
|||
s.assertContent("C.md", "unrelated").assertFileNotExists(
|
||||
"A.md"
|
||||
);
|
||||
// The offline-renamed file must survive — either as B.md
|
||||
// (rename preserved) or as a deconflict carrying "original".
|
||||
// A regression that silently drops both leaves only C.md,
|
||||
// which we explicitly forbid here.
|
||||
s.assertAnyFileContains("original");
|
||||
s.ifFileExists("B.md", (inner) =>
|
||||
inner.assertContent("B.md", "original")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ export const mcMultiDeleteOfflineRenameTest: TestDefinition = {
|
|||
.assertFileExists("file-5.md")
|
||||
.assertFileNotExists("file-2.md")
|
||||
.assertFileNotExists("file-4.md");
|
||||
// The offline rename of a remotely-deleted file must
|
||||
// preserve "content-2" somewhere — either at renamed.md
|
||||
// or as a deconflict.
|
||||
s.assertAnyFileContains("content-2");
|
||||
s.ifFileExists("renamed.md", (inner) =>
|
||||
inner.assertContent("renamed.md", "content-2")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const multiFileOperationsTest: TestDefinition = {
|
||||
description:
|
||||
"Client 0 deletes A.md while client 1 is offline. Client 1 updates B.md and renames A.md to D.md offline. " +
|
||||
"After client 1 reconnects, both clients must converge with B.md updated and C.md intact.",
|
||||
"Client 0 deletes A.md while client 1 is offline. Client 1 updates B.md " +
|
||||
"and renames its stale A.md to D.md offline. After client 1 reconnects, " +
|
||||
"B.md must hold client 1's update, C.md must be unchanged, A.md must be " +
|
||||
"gone, and the offline-renamed file must be preserved at D.md (post as " +
|
||||
"a new doc since A.md was deleted server-side).",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{ type: "create", client: 0, path: "A.md", content: "content-a" },
|
||||
|
|
@ -33,12 +36,14 @@ export const multiFileOperationsTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContains("B.md", "updated")
|
||||
.assertFileExists("C.md")
|
||||
// Pin B.md/C.md/D.md exactly: a regression that loses
|
||||
// client 1's offline rename (no D.md) or that drops the
|
||||
// B.md update would otherwise pass the loose checks.
|
||||
s.assertFileCount(3)
|
||||
.assertContent("B.md", "updated by client 1")
|
||||
.assertContent("C.md", "content-c")
|
||||
.assertContent("D.md", "content-a")
|
||||
.assertFileNotExists("A.md");
|
||||
s.ifFileExists("D.md", (inner) =>
|
||||
inner.assertContent("D.md", "content-a")
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -44,9 +44,22 @@ export const offlineConcurrentRenamesTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
// Two concurrent offline renames of the same source:
|
||||
// exactly one file must remain (count 1), it must hold
|
||||
// "shared-content", and that file must be one of the two
|
||||
// rename targets — neither rename may silently land at
|
||||
// some unrelated path.
|
||||
s.assertFileNotExists("A.md")
|
||||
.assertFileCount(1)
|
||||
.assertAnyFileContains("shared-content");
|
||||
if (
|
||||
!s.files.has("B.md") &&
|
||||
!s.files.has("C.md")
|
||||
) {
|
||||
throw new Error(
|
||||
`Expected the surviving file to be B.md or C.md. Files: [${Array.from(s.files.keys()).join(", ")}]`
|
||||
);
|
||||
}
|
||||
s.ifFileExists("B.md", (inner) =>
|
||||
inner.assertContent("B.md", "shared-content")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,9 +29,13 @@ export const offlineDeleteRemoteRenameTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md").assertFileNotExists(
|
||||
"A_renamed.md"
|
||||
);
|
||||
// Delete+rename: client 0's offline delete must propagate.
|
||||
// Final state is no files; require it explicitly so a
|
||||
// regression producing some divergent identical filename
|
||||
// on both clients can't slip past the per-name checks.
|
||||
s.assertFileNotExists("A.md")
|
||||
.assertFileNotExists("A_renamed.md")
|
||||
.assertFileCount(0);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const offlineEditThenMoveSameContentTest: TestDefinition = {
|
||||
description:
|
||||
"A file is renamed and edited to match a deleted file's content. Both clients must converge despite the ambiguity.",
|
||||
"Single-client offline sequence on Client 0: delete A.md, rename " +
|
||||
"B.md to C.md, then update C.md so its content equals the deleted " +
|
||||
"A.md's content. The ambiguity is for the engine: the resulting " +
|
||||
"C.md content matches a doc that was just deleted, but it must " +
|
||||
"still be tracked as the renamed-from-B doc, not resurrected as A. " +
|
||||
"Both clients must converge to a single C.md with 'content A'.",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const offlineRenameRemoteCreateOldPathTest: TestDefinition = {
|
||||
description:
|
||||
"Offline-rename vs. concurrent remote-update of the same doc. " +
|
||||
"Client 0 renames X.md to Y.md while offline. Client 1 updates X.md " +
|
||||
"(same document). When Client 0 reconnects, the rename and update " +
|
||||
"should merge. Y.md should exist with Client 1's content.",
|
||||
"(same document) and syncs. When Client 0 reconnects, the rename " +
|
||||
"and update must merge: Y.md must hold Client 1's updated content. " +
|
||||
"(Filename is legacy — there is no remote create at the old path; " +
|
||||
"this is the rename-vs-update mirror of offline-edit-remote-rename.)",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{ type: "create", client: 0, path: "X.md", content: "original" },
|
||||
|
|
@ -41,7 +44,9 @@ export const offlineRenameRemoteCreateOldPathTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContains(
|
||||
// Pin exact content + path: rename and update must
|
||||
// collapse onto Y.md with Client 1's update.
|
||||
s.assertFileCount(1).assertContent(
|
||||
"Y.md",
|
||||
"updated-by-client-1"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -65,10 +65,12 @@ export const offlineUpdateBothThenDeleteOneTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent(
|
||||
"A.md",
|
||||
"A updated by client 0"
|
||||
).assertFileNotExists("B.md");
|
||||
// Delete must win for B.md: pin file count to 1 so a
|
||||
// deconflict carrying client 1's update of B.md cannot
|
||||
// silently sneak in.
|
||||
s.assertFileCount(1)
|
||||
.assertContent("A.md", "A updated by client 0")
|
||||
.assertFileNotExists("B.md");
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export const onlineDeleteRecreateRapidCycleTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("A.md", "round 3");
|
||||
s.assertFileCount(1).assertContent("A.md", "round 3");
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const queueResetLosesCoalescedLocalEditTest: TestDefinition = {
|
||||
description:
|
||||
"Client 0 goes offline, both clients edit doc.md concurrently, " +
|
||||
"then client 0 reconnects. Both edits must be preserved.",
|
||||
"Client 0's local update is queued in the wire loop while the " +
|
||||
"server is paused (so the POST hangs), then disable-sync forces a " +
|
||||
"SyncReset that clears the wire-loop queue. On re-enable, the " +
|
||||
"engine MUST rediscover the disk content via offline scan and " +
|
||||
"merge it with the meantime remote update — otherwise the " +
|
||||
"queue-reset has silently lost a coalesced local edit. (Earlier " +
|
||||
"version of this test ran the local update while sync was already " +
|
||||
"disabled, so there was no queue to reset.)",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{ type: "create", client: 0, path: "doc.md", content: "original" },
|
||||
|
|
@ -12,13 +18,20 @@ export const queueResetLosesCoalescedLocalEditTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "barrier" },
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
|
||||
{ type: "update", client: 1, path: "doc.md", content: "alpha bravo" },
|
||||
{ type: "sync", client: 1 },
|
||||
|
||||
// Pause the server so c0's wire loop can enqueue but cannot drain.
|
||||
{ type: "pause-server" },
|
||||
// c0's update is queued in the wire loop; the POST will hang.
|
||||
{ type: "update", client: 0, path: "doc.md", content: "charlie delta" },
|
||||
// disable-sync triggers a SyncReset — the in-flight POST aborts
|
||||
// with SyncResetError and the wire-loop queue is cleared.
|
||||
{ type: "disable-sync", client: 0 },
|
||||
{ type: "resume-server" },
|
||||
|
||||
// Re-enable. Offline scan must see disk content "charlie delta"
|
||||
// and merge it with the server's "alpha bravo".
|
||||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "barrier" },
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const renameCreateConflictTest: TestDefinition = {
|
||||
description:
|
||||
"Client 0 creates A.md and syncs. Client 1 renames A.md to B.md and syncs. Client 0 (offline) creates B.md with the same content. After reconnecting, both clients should converge with only B.md.",
|
||||
"Client 0 creates A.md and syncs. Client 1 renames A.md to B.md and " +
|
||||
"syncs. Client 0 (offline) creates B.md with the same content. After " +
|
||||
"reconnecting, both clients converge to two files: B.md (from the " +
|
||||
"rename, content 'hi') and a deconflicted B (1).md (the offline " +
|
||||
"create, content 'hi') — content dedup is content-hash + parent " +
|
||||
"version based, not body-only, so even identical content from " +
|
||||
"different doc lineages produces a distinct file.",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{ type: "enable-sync", client: 0 },
|
||||
|
|
|
|||
|
|
@ -33,10 +33,14 @@ export const renameToPendingPathFallbackTest: TestDefinition = {
|
|||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("B.md").assertContains(
|
||||
"A.md",
|
||||
"tracked B content"
|
||||
);
|
||||
// The rename clobbers the unsynced A.md on disk, so the
|
||||
// expected post-converge state is exactly one file at A.md
|
||||
// with the renamed-from-B content. A regression that
|
||||
// produced a deconflicted A (1).md carrying the lost
|
||||
// "pending A content" would slip past assertContains.
|
||||
s.assertFileNotExists("B.md")
|
||||
.assertFileCount(1)
|
||||
.assertContent("A.md", "tracked B content");
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@ import type { TestDefinition } from "../test-definition";
|
|||
|
||||
export const resetClearsRecentlyDeletedResurrectionTest: TestDefinition = {
|
||||
description:
|
||||
"Client 0 deletes a file. Client 1 toggles sync off and on " +
|
||||
"(simulating reconnect). The deleted file should NOT reappear " +
|
||||
"on Client 1 after the sync reset.",
|
||||
"Client 0 deletes a file. Client 1 calls `reset` (clears tracked " +
|
||||
"state — recently-deleted set, watermark, doc records — but keeps " +
|
||||
"disk files). After the reset and re-handshake, the deleted file " +
|
||||
"should NOT reappear via offline-scan resurrection. This is the " +
|
||||
"stronger probe than disable/enable-sync, which keeps the " +
|
||||
"recently-deleted suppression set intact.",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{
|
||||
|
|
@ -28,8 +31,7 @@ export const resetClearsRecentlyDeletedResurrectionTest: TestDefinition = {
|
|||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 1 },
|
||||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "reset", client: 1 },
|
||||
|
||||
{ type: "barrier" },
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue