vault-link/frontend/deterministic-tests/src/tests/offline-rename-both-clients-same-source.test.ts

84 lines
2.8 KiB
TypeScript

import type { ClientState, TestDefinition } from "../test-definition";
import { assert } from "../utils/assert";
/**
* BUG/EDGE CASE: Both clients rename the same file to different targets.
*
* Client 0 renames X→Y, Client 1 renames X→Z. Both happen offline.
* When they reconnect:
*
* - Client 0's rename (X→Y) goes through first → server has doc at Y
* - Client 1's rename (X→Z): Client 1 still has the old metadata
* pointing to X.md. But the server moved it to Y.md.
*
* The conflict: Client 1 will try to update with relativePath=Z.md
* and parentVersionId pointing to the old state. The server sees the
* path changed and processes it as a rename from Y→Z.
*
* Expected: The file ends up at one path (last rename wins), and both
* clients converge. Content should be preserved.
*/
function verifyFinalState(state: ClientState): void {
// X should not exist (renamed by both)
assert(
!state.files.has("X.md"),
`X.md should not exist, files: ${Array.from(state.files.keys()).join(", ")}`
);
// Exactly one file should exist (either Y.md or Z.md)
assert(
state.files.size === 1,
`Expected 1 file, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}`
);
// Content should be preserved
const content = Array.from(state.files.values())[0];
assert(
content === "original content",
`Expected "original content", got: "${content}"`
);
}
export const offlineRenameBothClientsSameSourceTest: TestDefinition = {
name: "Both Clients Rename Same File to Different Targets (Offline)",
description:
"Client 0 renames X→Y, Client 1 renames X→Z, both offline. " +
"On reconnect, the conflicting renames should resolve and " +
"both clients should converge to the same final path.",
clients: 2,
steps: [
// Setup: create X.md
{
type: "create",
client: 0,
path: "X.md",
content: "original content"
},
{ type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 },
{ type: "sync" },
{ type: "barrier" },
// Both go offline
{ type: "disable-sync", client: 0 },
{ type: "disable-sync", client: 1 },
// Client 0: rename X→Y
{ type: "rename", client: 0, oldPath: "X.md", newPath: "Y.md" },
// Client 1: rename X→Z
{ type: "rename", client: 1, oldPath: "X.md", newPath: "Z.md" },
// Client 0 reconnects first
{ type: "enable-sync", client: 0 },
{ type: "sync", client: 0 },
// Client 1 reconnects
{ type: "enable-sync", client: 1 },
{ type: "sync" },
{ type: "barrier" },
// Both clients should converge
{ type: "assert-consistent", verify: verifyFinalState }
]
};