vault-link/frontend/deterministic-tests/src/tests/remote-rename-collides-with-pending-local-create.test.ts
2026-05-04 13:07:18 +01:00

76 lines
3.3 KiB
TypeScript

import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition";
export const remoteRenameCollidesWithPendingLocalCreateTest: TestDefinition = {
// TODO(refactor): the failure mode described below is the
// pre-refactor "deflect-to-conflict-uuid" path that no longer
// exists. Under the new model the wire loop never moves files for
// path placement, so the remote rename can't deflect anywhere; the
// reconciler waits for the slot to free. Convergence assertion is
// still valid (no conflict-uuid stashes, both files present, the
// local create lands at a server-deconflicted sibling).
description:
"Client 0 has doc D tracked at `original.md`. Client 1 owns doc E " +
"and renames it to `target.md` server-side. Before client 0's " +
"drain processes the WS broadcast for E, the user creates a new " +
"local file `target.md` (a different doc, untracked). When the " +
"buffered RemoteChange for E drains, the engine has to reconcile " +
"doc E onto `target.md` even though the slot is held by client " +
"0's pending LocalCreate. Convergence requires both clients end " +
"up with [target.md = E] and the local create lands at a " +
"server-deconflicted sibling (e.g. `target (1).md`).",
clients: 2,
steps: [
{ type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 },
{ type: "create", client: 1, path: "original.md", content: "v1\n" },
{ type: "barrier" },
// Pause client 0's WS so the upcoming remote rename buffers and
// we can stage a colliding local create before the rename
// drains on client 0.
{ type: "pause-websocket", client: 0 },
// Client 1 renames the doc. Server commits, broadcasts to
// client 0 (buffered).
{
type: "rename",
client: 1,
oldPath: "original.md",
newPath: "target.md"
},
{ type: "sync", client: 1 },
// Client 0 still believes the doc is at `original.md`. The user
// creates a NEW file at `target.md` (an unrelated untracked
// doc). Disk on client 0 now has both `original.md` (the
// tracked doc) and `target.md` (the new untracked file).
{ type: "create", client: 0, path: "target.md", content: "extra\n" },
// Resume client 0's WS. The buffered RemoteChange drains.
// The reconciler must converge without ever leaving a
// conflict-uuid stash on disk.
{ type: "resume-websocket", client: 0 },
{ type: "barrier" },
{
type: "assert-consistent",
verify: (state: AssertableState): void => {
state.assertFileCount(2);
for (const path of state.files.keys()) {
if (path.startsWith("conflict-")) {
throw new Error(
`Unexpected conflict-uuid stash on a converged client: ${path}`
);
}
}
state.assertFileExists("target.md");
state.assertContent("target.md", "v1\n");
// The local create gets server-deconflicted to a
// sibling path (e.g. `target (1).md`).
}
}
]
};