mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-27 19:43:20 +00:00
657 lines
26 KiB
TypeScript
657 lines
26 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
type DocumentID,
|
|
LOG_LEVEL_NOTICE,
|
|
type FilePath,
|
|
type FilePathWithPrefix,
|
|
type MetaEntry,
|
|
type UXFileInfo,
|
|
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
|
|
|
vi.mock("@/deps.ts", () => ({}));
|
|
vi.mock("@/features/HiddenFileCommon/JsonResolveModal.ts", () => ({
|
|
JsonResolveModal: class JsonResolveModal {},
|
|
}));
|
|
vi.mock("@/features/LiveSyncCommands.ts", () => ({
|
|
LiveSyncCommands: class LiveSyncCommands {
|
|
plugin!: { app: unknown };
|
|
core!: { services: unknown; settings: unknown };
|
|
get app() {
|
|
return this.plugin.app;
|
|
}
|
|
get services() {
|
|
return this.core.services;
|
|
}
|
|
get settings() {
|
|
return this.core.settings;
|
|
}
|
|
},
|
|
}));
|
|
vi.mock("./configureHiddenFileSyncMode.ts", () => ({
|
|
configureHiddenFileSyncMode: vi.fn(),
|
|
}));
|
|
|
|
import { HiddenFileSync } from "./CmdHiddenFileSync.ts";
|
|
import { configureHiddenFileSyncMode } from "./configureHiddenFileSyncMode.ts";
|
|
|
|
function createHiddenRevisionOperation() {
|
|
const path = ".obsidian/plugins/example/data.json" as FilePath;
|
|
const file = {
|
|
path,
|
|
name: "data.json",
|
|
isInternal: true,
|
|
body: new Blob(["{\"value\":\"vault\"}"]),
|
|
stat: {
|
|
ctime: 1,
|
|
mtime: 2,
|
|
size: 17,
|
|
type: "file",
|
|
},
|
|
} as UXFileInfo;
|
|
const selected = {
|
|
_id: "i:example" as DocumentID,
|
|
_rev: "2-selected",
|
|
path: `i:${path}` as FilePathWithPrefix,
|
|
ctime: 1,
|
|
mtime: 2,
|
|
size: 17,
|
|
type: "plain",
|
|
datatype: "plain",
|
|
children: [],
|
|
eden: {},
|
|
deleted: false,
|
|
} as MetaEntry;
|
|
const winner = {
|
|
...selected,
|
|
_rev: "3-winner",
|
|
} as MetaEntry;
|
|
const databaseFileAccess = {
|
|
fetchEntryMeta: vi.fn(
|
|
async (_path: unknown, revision?: string) =>
|
|
revision === selected._rev ? selected : winner
|
|
),
|
|
getConflictedRevs: vi.fn(async () => [selected._rev]),
|
|
fetchEntryFromMeta: vi.fn(async () => ({ ...selected, data: "{\"value\":\"database\"}" })),
|
|
storeWithBaseRevision: vi.fn(async () => "3-vault-child"),
|
|
};
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: {
|
|
services: {
|
|
vault: {
|
|
isIgnoredByIgnoreFile: vi.fn(async () => false),
|
|
},
|
|
},
|
|
databaseFileAccess,
|
|
},
|
|
loadFileWithInfo: vi.fn(async () => file),
|
|
updateLastProcessed: vi.fn(),
|
|
_log: vi.fn(),
|
|
});
|
|
return {
|
|
hiddenFileSync,
|
|
path,
|
|
file,
|
|
selected,
|
|
winner,
|
|
databaseFileAccess,
|
|
};
|
|
}
|
|
|
|
describe("HiddenFileSync configuration-change notices", () => {
|
|
it.each(["pushForce", "pullForce", "safe"] as const)(
|
|
"refuses %s initialisation before processing files while Hidden File Sync is disabled",
|
|
async (direction) => {
|
|
const progress = { log: vi.fn(), once: vi.fn(), done: vi.fn() };
|
|
const path = ".obsidian/example.json" as FilePath;
|
|
const operations = {
|
|
rebuildFromStorage: vi.fn(async () => [path]),
|
|
rebuildFromDatabase: vi.fn(async () => []),
|
|
rebuildMerging: vi.fn(async () => [path]),
|
|
adoptCurrentStorageFilesAsProcessed: vi.fn(async () => undefined),
|
|
adoptCurrentDatabaseFilesAsProcessed: vi.fn(async () => undefined),
|
|
scanAllStorageChanges: vi.fn(async () => undefined),
|
|
scanAllDatabaseChanges: vi.fn(async () => undefined),
|
|
};
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
const settings = { syncInternalFiles: false };
|
|
const createProgress = vi.fn(() => progress);
|
|
const log = vi.fn();
|
|
Object.assign(hiddenFileSync, {
|
|
core: { settings },
|
|
_progress: createProgress,
|
|
_log: log,
|
|
...operations,
|
|
});
|
|
|
|
const result = await hiddenFileSync.initialiseInternalFileSync(direction, true, [path]);
|
|
|
|
for (const operation of Object.values(operations)) {
|
|
expect(operation).not.toHaveBeenCalled();
|
|
}
|
|
expect(result).toBe(false);
|
|
expect(settings.syncInternalFiles).toBe(false);
|
|
expect(createProgress).not.toHaveBeenCalled();
|
|
expect(log).toHaveBeenCalledWith(expect.stringContaining("Hidden File Sync is disabled"), LOG_LEVEL_NOTICE);
|
|
expect(progress.done).not.toHaveBeenCalled();
|
|
}
|
|
);
|
|
|
|
it("finishes an existing preparation Notice as not started when initialisation is disabled", async () => {
|
|
const progress = { log: vi.fn(), once: vi.fn(), done: vi.fn() };
|
|
const createProgress = vi.fn();
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: { settings: { syncInternalFiles: false } },
|
|
_progress: createProgress,
|
|
_log: vi.fn(),
|
|
});
|
|
|
|
const result = await hiddenFileSync.initialiseInternalFileSync("safe", true, false, progress);
|
|
|
|
expect(result).toBe(false);
|
|
expect(createProgress).not.toHaveBeenCalled();
|
|
expect(progress.done).toHaveBeenCalledExactlyOnceWith("Hidden File Sync initialisation was not started.");
|
|
});
|
|
|
|
it("does not apply a stored deletion when initialisation is requested while disabled", async () => {
|
|
const path = ".obsidian/example.json" as FilePath;
|
|
const deleted: MetaEntry = {
|
|
_id: "i:example" as DocumentID,
|
|
_rev: "2-deleted",
|
|
path: `i:${path}` as FilePathWithPrefix,
|
|
type: "plain",
|
|
ctime: 1,
|
|
mtime: 10,
|
|
size: 0,
|
|
deleted: true,
|
|
children: [],
|
|
eden: {},
|
|
};
|
|
const progress = { log: vi.fn(), once: vi.fn(), done: vi.fn() };
|
|
const deleteFile = vi.fn(async () => "OK" as const);
|
|
const updateDeletion = vi.fn();
|
|
const getFiles = vi.fn(async () => [deleted]);
|
|
const processedStorage = new Map([[path, "storage-processed"]]);
|
|
const processedDatabase = new Map([[path, "database-processed"]]);
|
|
const resetProcessed = vi.fn(() => processedDatabase.clear());
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: {
|
|
settings: { syncInternalFiles: false },
|
|
services: { vault: { isIgnoredByIgnoreFile: vi.fn(async () => false) } },
|
|
},
|
|
_progress: vi.fn(() => progress),
|
|
_log: vi.fn(),
|
|
_verbose: vi.fn(),
|
|
_fileInfoLastProcessed: processedStorage,
|
|
_databaseInfoLastProcessed: processedDatabase,
|
|
getAllDatabaseFiles: getFiles,
|
|
getPath: (doc: MetaEntry) => doc.path,
|
|
resetLastProcessedDatabase: resetProcessed,
|
|
serializedForEvent: async (_path: unknown, operation: () => Promise<unknown>) => await operation(),
|
|
__deleteFile: deleteFile,
|
|
updateLastProcessedDeletion: updateDeletion,
|
|
adoptCurrentStorageFilesAsProcessed: vi.fn(),
|
|
adoptCurrentDatabaseFilesAsProcessed: vi.fn(),
|
|
scanAllStorageChanges: vi.fn(),
|
|
scanAllDatabaseChanges: vi.fn(),
|
|
});
|
|
|
|
await hiddenFileSync.initialiseInternalFileSync("pullForce", true);
|
|
|
|
expect(deleteFile).not.toHaveBeenCalled();
|
|
expect(updateDeletion).not.toHaveBeenCalled();
|
|
expect(getFiles).not.toHaveBeenCalled();
|
|
expect(resetProcessed).not.toHaveBeenCalled();
|
|
expect(processedStorage).toEqual(new Map([[path, "storage-processed"]]));
|
|
expect(processedDatabase).toEqual(new Map([[path, "database-processed"]]));
|
|
});
|
|
|
|
it("shows manual Hidden File Sync commands only when the feature, Advanced mode, and runtime are ready", () => {
|
|
const commands: Array<{
|
|
id: string;
|
|
checkCallback?: (checking: boolean) => boolean | void;
|
|
}> = [];
|
|
const settings = {
|
|
syncInternalFiles: false,
|
|
useAdvancedMode: false,
|
|
};
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: {
|
|
settings,
|
|
services: {
|
|
API: {
|
|
addCommand: vi.fn((command) => commands.push(command)),
|
|
},
|
|
},
|
|
},
|
|
_isMainReady: vi.fn(() => true),
|
|
_isMainSuspended: vi.fn(() => false),
|
|
_isDatabaseReady: vi.fn(() => true),
|
|
});
|
|
|
|
hiddenFileSync.onload();
|
|
|
|
const commandIds = [
|
|
"livesync-sync-internal",
|
|
"livesync-scaninternal-storage",
|
|
"livesync-scaninternal-database",
|
|
"livesync-internal-scan-offline-changes",
|
|
];
|
|
for (const commandId of commandIds) {
|
|
const command = commands.find(({ id }) => id === commandId);
|
|
expect(command?.checkCallback?.(true)).toBe(false);
|
|
}
|
|
|
|
settings.useAdvancedMode = true;
|
|
for (const commandId of commandIds) {
|
|
const command = commands.find(({ id }) => id === commandId);
|
|
expect(command?.checkCallback?.(true)).toBe(false);
|
|
expect(command?.checkCallback?.(false)).toBe(false);
|
|
}
|
|
|
|
settings.syncInternalFiles = true;
|
|
settings.useAdvancedMode = true;
|
|
for (const commandId of commandIds) {
|
|
const command = commands.find(({ id }) => id === commandId);
|
|
expect(command?.checkCallback?.(true)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("does not report Hidden File Sync as ready before the main runtime is ready", () => {
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: {
|
|
settings: {
|
|
syncInternalFiles: true,
|
|
},
|
|
},
|
|
_isMainReady: vi.fn(() => false),
|
|
_isMainSuspended: vi.fn(() => false),
|
|
});
|
|
|
|
expect(hiddenFileSync.isReady()).toBe(false);
|
|
});
|
|
|
|
it("groups plug-in reloads and an Obsidian restart into one finished Notice", async () => {
|
|
const noticeGroups = {
|
|
setItem: vi.fn(),
|
|
finish: vi.fn(() => true),
|
|
removeItem: vi.fn(() => true),
|
|
};
|
|
const plugin = {
|
|
app: {
|
|
plugins: {
|
|
manifests: {
|
|
alpha: {
|
|
id: "alpha",
|
|
name: "Alpha",
|
|
dir: ".obsidian/plugins/alpha",
|
|
},
|
|
beta: {
|
|
id: "beta",
|
|
name: "Beta",
|
|
dir: ".obsidian/plugins/beta",
|
|
},
|
|
},
|
|
enabledPlugins: new Set(["alpha", "beta"]),
|
|
unloadPlugin: vi.fn(async () => undefined),
|
|
loadPlugin: vi.fn(async () => undefined),
|
|
},
|
|
},
|
|
};
|
|
const core = {
|
|
confirm: { askInPopup: vi.fn() },
|
|
services: {
|
|
context: { noticeGroups },
|
|
API: { getSystemConfigDir: vi.fn(() => ".obsidian") },
|
|
appLifecycle: {
|
|
isReloadingScheduled: vi.fn(() => false),
|
|
scheduleRestart: vi.fn(),
|
|
},
|
|
},
|
|
};
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
plugin,
|
|
core,
|
|
queuedNotificationFiles: new Set([".obsidian/plugins/alpha", ".obsidian/plugins/beta", ".obsidian"]),
|
|
_log: vi.fn(),
|
|
});
|
|
|
|
hiddenFileSync.notifyConfigChange();
|
|
|
|
expect(noticeGroups.setItem).toHaveBeenNthCalledWith(1, "hidden-file-changes", "plugin:alpha", {
|
|
message: "Files in Alpha were updated.",
|
|
action: expect.objectContaining({ label: "Reload Alpha" }),
|
|
});
|
|
expect(noticeGroups.setItem).toHaveBeenNthCalledWith(2, "hidden-file-changes", "plugin:beta", {
|
|
message: "Files in Beta were updated.",
|
|
action: expect.objectContaining({ label: "Reload Beta" }),
|
|
});
|
|
expect(noticeGroups.setItem).toHaveBeenNthCalledWith(3, "hidden-file-changes", "restart", {
|
|
message: "Other Obsidian settings files were updated.",
|
|
action: expect.objectContaining({ label: "Schedule an Obsidian restart" }),
|
|
});
|
|
expect(noticeGroups.setItem.mock.calls.every(([groupKey]) => groupKey === "hidden-file-changes")).toBe(true);
|
|
expect(noticeGroups.finish).toHaveBeenCalledWith("hidden-file-changes", { durationMs: 20_000 });
|
|
expect(core.confirm.askInPopup).not.toHaveBeenCalled();
|
|
|
|
const reloadAction = (noticeGroups.setItem.mock.calls[0]?.[2] as { action: { onSelect: () => void } }).action
|
|
.onSelect;
|
|
reloadAction();
|
|
await vi.waitFor(() => {
|
|
expect(plugin.app.plugins.unloadPlugin).toHaveBeenCalledWith("alpha");
|
|
expect(plugin.app.plugins.loadPlugin).toHaveBeenCalledWith("alpha");
|
|
expect(noticeGroups.removeItem).toHaveBeenCalledWith("hidden-file-changes", "plugin:alpha");
|
|
});
|
|
|
|
const restartAction = (noticeGroups.setItem.mock.calls[2]?.[2] as { action: { onSelect: () => void } }).action
|
|
.onSelect;
|
|
restartAction();
|
|
expect(core.services.appLifecycle.scheduleRestart).toHaveBeenCalledOnce();
|
|
expect(noticeGroups.removeItem).toHaveBeenCalledWith("hidden-file-changes", "restart");
|
|
});
|
|
|
|
it("keeps subordinate initialisation phases below Notice level so one progress Notice owns the scan", async () => {
|
|
const progress = {
|
|
log: vi.fn(),
|
|
once: vi.fn(),
|
|
done: vi.fn(),
|
|
};
|
|
const rebuildMerging = vi.fn(async () => []);
|
|
const adoptCurrentStorageFilesAsProcessed = vi.fn(async () => undefined);
|
|
const adoptCurrentDatabaseFilesAsProcessed = vi.fn(async () => undefined);
|
|
const scanAllStorageChanges = vi.fn(async () => undefined);
|
|
const scanAllDatabaseChanges = vi.fn(async () => undefined);
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: { settings: { syncInternalFiles: true } },
|
|
_progress: vi.fn(() => progress),
|
|
rebuildMerging,
|
|
adoptCurrentStorageFilesAsProcessed,
|
|
adoptCurrentDatabaseFilesAsProcessed,
|
|
scanAllStorageChanges,
|
|
scanAllDatabaseChanges,
|
|
});
|
|
|
|
const result = await hiddenFileSync.initialiseInternalFileSync("safe", true);
|
|
|
|
expect(result).toBe(true);
|
|
expect(rebuildMerging).toHaveBeenCalledWith(false, false);
|
|
expect(scanAllStorageChanges).toHaveBeenCalledWith(false, true, false);
|
|
expect(scanAllDatabaseChanges).toHaveBeenCalledWith(false, true, false);
|
|
expect(progress.done).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it.each(["pushForce", "pullForce", "safe"] as const)(
|
|
"retains the selected files and completes %s initialisation when enabled",
|
|
async (direction) => {
|
|
const files = [".obsidian/plugins/example/main.js", ".obsidian/plugins/example/data.json"] as FilePath[];
|
|
const entries = files.map((path) => ({ path: `i:${path}` as FilePathWithPrefix }));
|
|
const progress = { log: vi.fn(), once: vi.fn(), done: vi.fn() };
|
|
const rebuildFromStorage = vi.fn(async () => files);
|
|
const rebuildFromDatabase = vi.fn(async () => entries);
|
|
const rebuildMerging = vi.fn(async () => files);
|
|
const adoptStorage = vi.fn();
|
|
const adoptDatabase = vi.fn();
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: { settings: { syncInternalFiles: true } },
|
|
_progress: vi.fn(() => progress),
|
|
getPath: (entry: { path: FilePathWithPrefix }) => entry.path,
|
|
rebuildFromStorage,
|
|
rebuildFromDatabase,
|
|
rebuildMerging,
|
|
adoptCurrentStorageFilesAsProcessed: adoptStorage,
|
|
adoptCurrentDatabaseFilesAsProcessed: adoptDatabase,
|
|
scanAllStorageChanges: vi.fn(),
|
|
scanAllDatabaseChanges: vi.fn(),
|
|
});
|
|
|
|
const result = await hiddenFileSync.initialiseInternalFileSync(direction, true, files);
|
|
|
|
expect(result).toBe(true);
|
|
if (direction === "pushForce") {
|
|
expect(rebuildFromStorage).toHaveBeenCalledExactlyOnceWith(false, files, false);
|
|
expect(rebuildFromDatabase).not.toHaveBeenCalled();
|
|
expect(rebuildMerging).not.toHaveBeenCalled();
|
|
} else if (direction === "pullForce") {
|
|
expect(rebuildFromDatabase).toHaveBeenCalledExactlyOnceWith(false, files, false);
|
|
expect(rebuildFromStorage).not.toHaveBeenCalled();
|
|
expect(rebuildMerging).not.toHaveBeenCalled();
|
|
} else {
|
|
expect(rebuildMerging).toHaveBeenCalledExactlyOnceWith(false, files);
|
|
expect(rebuildFromStorage).not.toHaveBeenCalled();
|
|
expect(rebuildFromDatabase).not.toHaveBeenCalled();
|
|
}
|
|
expect(adoptStorage).toHaveBeenCalledExactlyOnceWith(files);
|
|
expect(adoptDatabase).toHaveBeenCalledExactlyOnceWith(files);
|
|
expect(progress.done).toHaveBeenCalledOnce();
|
|
}
|
|
);
|
|
|
|
it("does not report completion when the initialisation entry point refuses to start", async () => {
|
|
vi.mocked(configureHiddenFileSyncMode).mockImplementation(async (_mode, handlers) => {
|
|
await handlers.enable();
|
|
await handlers.initialise("safe");
|
|
return "enabled";
|
|
});
|
|
const progress = { log: vi.fn(), once: vi.fn(), done: vi.fn() };
|
|
const log = vi.fn();
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: { services: { setting: { applyPartial: vi.fn() } } },
|
|
_progress: vi.fn(() => progress),
|
|
_log: log,
|
|
initialiseInternalFileSync: vi.fn(async () => false),
|
|
});
|
|
|
|
await hiddenFileSync.configureHiddenFileSync("MERGE");
|
|
|
|
expect(log).not.toHaveBeenCalledWith("Hidden File Sync initialisation completed.", expect.any(Number));
|
|
});
|
|
|
|
it("retirement guard: does not restore separate gathering and restart Notices", async () => {
|
|
vi.mocked(configureHiddenFileSyncMode).mockImplementation(async (_mode, handlers) => {
|
|
await handlers.enable();
|
|
await handlers.initialise("safe");
|
|
return "enabled";
|
|
});
|
|
const events: string[] = [];
|
|
const progress = {
|
|
log: vi.fn((message: string) => {
|
|
events.push(`progress:${message}`);
|
|
}),
|
|
once: vi.fn(),
|
|
done: vi.fn(),
|
|
};
|
|
const createProgress = vi.fn(() => progress);
|
|
const applyPartial = vi.fn(async () => {
|
|
events.push("apply-settings");
|
|
});
|
|
const initialiseInternalFileSync = vi.fn(async () => true);
|
|
const log = vi.fn();
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: {
|
|
services: {
|
|
setting: { applyPartial },
|
|
},
|
|
},
|
|
initialiseInternalFileSync,
|
|
_progress: createProgress,
|
|
_log: log,
|
|
});
|
|
|
|
await hiddenFileSync.configureHiddenFileSync("MERGE");
|
|
|
|
expect(createProgress).toHaveBeenCalledWith("[⚙ Initialise]\n", LOG_LEVEL_NOTICE);
|
|
expect(events[0]).toBe("progress:Preparing Hidden File Sync...");
|
|
expect(initialiseInternalFileSync).toHaveBeenCalledWith("safe", true, false, progress);
|
|
expect(log).not.toHaveBeenCalledWith("Gathering files for enabling Hidden File Sync", LOG_LEVEL_NOTICE);
|
|
expect(log).not.toHaveBeenCalledWith("Done! Restarting the app is strongly recommended!", LOG_LEVEL_NOTICE);
|
|
expect(log).toHaveBeenCalledWith("Hidden File Sync initialisation completed.", expect.any(Number));
|
|
});
|
|
|
|
it("closes the preparation Notice when enabling Hidden File Sync fails", async () => {
|
|
vi.mocked(configureHiddenFileSyncMode).mockImplementation(async (_mode, handlers) => {
|
|
await handlers.enable();
|
|
return "enabled";
|
|
});
|
|
const error = new Error("setting persistence failed");
|
|
const progress = {
|
|
log: vi.fn(),
|
|
once: vi.fn(),
|
|
done: vi.fn(),
|
|
};
|
|
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
|
|
Object.assign(hiddenFileSync, {
|
|
core: {
|
|
services: {
|
|
setting: {
|
|
applyPartial: vi.fn(async () => {
|
|
throw error;
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
_progress: vi.fn(() => progress),
|
|
_log: vi.fn(),
|
|
});
|
|
|
|
await expect(hiddenFileSync.configureHiddenFileSync("MERGE")).rejects.toBe(error);
|
|
|
|
expect(progress.done).toHaveBeenCalledWith("Failed");
|
|
});
|
|
});
|
|
|
|
describe("HiddenFileSync exact revision repair operations", () => {
|
|
it("stores the current hidden Vault file as a child of the selected live revision", async () => {
|
|
const {
|
|
hiddenFileSync,
|
|
file,
|
|
selected,
|
|
databaseFileAccess,
|
|
} = createHiddenRevisionOperation();
|
|
|
|
await expect(
|
|
hiddenFileSync.storeInternalFileToDatabaseWithBaseRevision(file, selected._rev!)
|
|
).resolves.toBe(true);
|
|
|
|
expect(databaseFileAccess.storeWithBaseRevision).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
path: file.path,
|
|
body: file.body,
|
|
isInternal: true,
|
|
}),
|
|
selected._rev,
|
|
true
|
|
);
|
|
expect(hiddenFileSync.updateLastProcessed).toHaveBeenCalledWith(
|
|
file.path,
|
|
expect.objectContaining({ _rev: "3-vault-child" }),
|
|
file.stat
|
|
);
|
|
});
|
|
|
|
it("refuses to extend a hidden-file revision which is no longer live", async () => {
|
|
const {
|
|
hiddenFileSync,
|
|
file,
|
|
selected,
|
|
databaseFileAccess,
|
|
} = createHiddenRevisionOperation();
|
|
databaseFileAccess.getConflictedRevs.mockResolvedValue([]);
|
|
|
|
await expect(
|
|
hiddenFileSync.storeInternalFileToDatabaseWithBaseRevision(file, selected._rev!)
|
|
).resolves.toBe(false);
|
|
|
|
expect(databaseFileAccess.storeWithBaseRevision).not.toHaveBeenCalled();
|
|
expect(hiddenFileSync.updateLastProcessed).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not create a hidden-file child when asked only to mark a revision which differs from the Vault", async () => {
|
|
const {
|
|
hiddenFileSync,
|
|
file,
|
|
selected,
|
|
databaseFileAccess,
|
|
} = createHiddenRevisionOperation();
|
|
|
|
await expect(
|
|
hiddenFileSync.storeInternalFileToDatabaseWithBaseRevision(
|
|
file,
|
|
selected._rev!,
|
|
false
|
|
)
|
|
).resolves.toBe(false);
|
|
|
|
expect(databaseFileAccess.storeWithBaseRevision).not.toHaveBeenCalled();
|
|
expect(hiddenFileSync.updateLastProcessed).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("marks a matching hidden-file revision without creating a child", async () => {
|
|
const {
|
|
hiddenFileSync,
|
|
file,
|
|
selected,
|
|
databaseFileAccess,
|
|
} = createHiddenRevisionOperation();
|
|
databaseFileAccess.fetchEntryFromMeta.mockResolvedValue({
|
|
...selected,
|
|
data: "{\"value\":\"vault\"}",
|
|
});
|
|
|
|
await expect(
|
|
hiddenFileSync.storeInternalFileToDatabaseWithBaseRevision(
|
|
file,
|
|
selected._rev!,
|
|
false
|
|
)
|
|
).resolves.toBe(true);
|
|
|
|
expect(databaseFileAccess.storeWithBaseRevision).not.toHaveBeenCalled();
|
|
expect(hiddenFileSync.updateLastProcessed).toHaveBeenCalledWith(
|
|
file.path,
|
|
selected,
|
|
file.stat
|
|
);
|
|
});
|
|
|
|
it("applies the selected live hidden-file revision through the existing extraction path", async () => {
|
|
const {
|
|
hiddenFileSync,
|
|
path,
|
|
selected,
|
|
} = createHiddenRevisionOperation();
|
|
const extract = vi.fn(async () => true);
|
|
hiddenFileSync.extractInternalFileFromDatabase = extract;
|
|
|
|
await expect(
|
|
hiddenFileSync.extractInternalFileRevisionFromDatabase(path, selected._rev!, true)
|
|
).resolves.toBe(true);
|
|
|
|
expect(extract).toHaveBeenCalledWith(path, true, undefined, true, false, true, selected._rev);
|
|
});
|
|
|
|
it("does not apply a hidden-file revision which ceased to be live", async () => {
|
|
const {
|
|
hiddenFileSync,
|
|
path,
|
|
selected,
|
|
databaseFileAccess,
|
|
} = createHiddenRevisionOperation();
|
|
databaseFileAccess.getConflictedRevs.mockResolvedValue([]);
|
|
|
|
await expect(
|
|
hiddenFileSync.extractInternalFileRevisionFromDatabase(path, selected._rev!, true)
|
|
).resolves.toBe(false);
|
|
|
|
expect(databaseFileAccess.fetchEntryFromMeta).not.toHaveBeenCalled();
|
|
});
|
|
});
|