mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-23 01:23:22 +00:00
Cover content evidence after conflict-base compaction
This commit is contained in:
1 parent
f47db4f256
commit
bf8e899bf7
2 files changed
+44
-5
No files matched your search
@@ -13,6 +13,7 @@ import {
|
||||
type ServiceDatabaseFileAccessDependencies,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/serviceModules/ServiceDatabaseFileAccessBase";
|
||||
import type { ServiceFileHandlerDependencies } from "@vrtmrz/livesync-commonlib/compat/serviceModules/ServiceFileHandlerBase";
|
||||
import type { FileReflectionProvenanceRecord } from "@vrtmrz/livesync-commonlib/compat/interfaces/FileReflectionProvenance";
|
||||
import { ServiceFileHandler } from "./FileHandler";
|
||||
import {
|
||||
createConflictResolutionOperations,
|
||||
@@ -38,7 +39,7 @@ function makeFile(body: string, mtime = oldTime): UXFileInfo {
|
||||
|
||||
async function makeDevice(name: string) {
|
||||
const db = new PouchDB<EntryDoc>(name, { adapter: "memory" });
|
||||
const reflection = new Map<FilePathWithPrefix, { revision: string; observedStorageMtime?: number }>();
|
||||
const reflection = new Map<FilePathWithPrefix, FileReflectionProvenanceRecord>();
|
||||
let storage = makeFile(old);
|
||||
const settings = { ...DEFAULT_SETTINGS, useOnlyLocalChunk: true, writeDocumentsIfConflicted: false };
|
||||
const setting = { currentSettings: () => settings };
|
||||
@@ -94,7 +95,7 @@ async function makeDevice(name: string) {
|
||||
conflict,
|
||||
fileReflectionProvenance: {
|
||||
get: (value: FilePathWithPrefix) => Promise.resolve(reflection.get(value)),
|
||||
set: (value: FilePathWithPrefix, record: { revision: string }) => {
|
||||
set: (value: FilePathWithPrefix, record: FileReflectionProvenanceRecord) => {
|
||||
reflection.set(value, record);
|
||||
return Promise.resolve();
|
||||
},
|
||||
@@ -287,7 +288,32 @@ describe("file provenance across multiple devices and real CouchDB", () => {
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
it.each(["missing record", "compacted base"] as const)(
|
||||
it("reflects the selected content after compacting an unchanged losing branch", async () => {
|
||||
const f = await conflictedDevices(4);
|
||||
const { keep, content } = await resolveAndReplicate(f);
|
||||
const index = f.revisions.findIndex((rev, i) => i > 0 && rev !== keep);
|
||||
const device = f.devices[index];
|
||||
const record = device.reflection.get(path);
|
||||
expect(record?.revision).toBe(f.revisions[index]);
|
||||
expect(record?.contentHash).toMatch(/^sha256:[0-9a-f]{64}$/u);
|
||||
expect(record?.contentSize).toBe(device.getStorage().body.size);
|
||||
await device.db.compact();
|
||||
await expect(device.db.get(path, { rev: f.revisions[index] })).rejects.toMatchObject({ status: 404 });
|
||||
|
||||
const before = (await device.db.info()).update_seq;
|
||||
await expect(device.handler.storeFileToDB(path)).resolves.toBe(true);
|
||||
expect((await device.db.info()).update_seq).toBe(before);
|
||||
expect(await device.getStorage().body.text()).toBe(content);
|
||||
expect(device.reflection.get(path)?.revision).toBe(keep);
|
||||
await device.db.replicate.to(f.remote);
|
||||
for (const peer of f.devices) {
|
||||
await peer.db.replicate.from(f.remote);
|
||||
expect((await leaves(peer.db)).map((doc) => doc._rev)).toEqual([keep]);
|
||||
expect(await revisionContent(peer, keep)).toBe(content);
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
it.each(["missing record", "compacted legacy base", "compacted hashed base with local edits"] as const)(
|
||||
"preserves uncertain storage as one independent conflict with four devices: %s",
|
||||
async (reason) => {
|
||||
const f = await conflictedDevices(4);
|
||||
@@ -301,6 +327,17 @@ describe("file provenance across multiple devices and real CouchDB", () => {
|
||||
} else {
|
||||
await device.db.compact();
|
||||
await expect(device.db.get(path, { rev: f.revisions[index] })).rejects.toMatchObject({ status: 404 });
|
||||
if (reason === "compacted legacy base") {
|
||||
// Revision-only records still require protection when their recorded body is unavailable.
|
||||
const { revision, observedStorageMtime } = device.reflection.get(path)!;
|
||||
device.reflection.set(path, { revision, observedStorageMtime });
|
||||
} else {
|
||||
expect(device.reflection.get(path)?.contentHash).toMatch(/^sha256:/u);
|
||||
const original = device.getStorage();
|
||||
const edited = (await original.body.text()).replace("Edited", "EDITED");
|
||||
device.setStorage(makeFile(edited, original.stat.mtime));
|
||||
expect(device.getStorage().body.size).toBe(original.body.size);
|
||||
}
|
||||
}
|
||||
const uncertainContent = await device.getStorage().body.text();
|
||||
await expect(device.handler.storeFileToDB(path)).resolves.toBe(true);
|
||||
|
||||
Reference in new issue
Block a user