Complete central provider, resource, scheduling, and recovery contracts

This commit is contained in:
vorotamoroz
2026-08-31 15:06:06 +00:00
parent 24228bf7cf
commit 1d2077d3fc
18 changed files with 464 additions and 34 deletions
@@ -960,6 +960,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
/**
* Checks the edited CouchDB passphrase through an owned synchronisation-
* information resource. A missing document may be created by the check.
* Incompatibility and operational failure retain their distinct existing
* result messages.
*/
checkWorkingPassphrase = async (): Promise<boolean> => {
if (this.editingSettings.remoteType == REMOTE_MINIO) return true;
@@ -980,6 +982,15 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
Logger($msg("obsidianLiveSyncSettingTab.logPassphraseNotCompatible"), LOG_LEVEL_NOTICE);
return false;
}
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
Logger(
$msg("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", {
db: reason,
}),
LOG_LEVEL_NOTICE
);
return false;
} finally {
await resource.dispose();
}
@@ -1,10 +1,13 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_SETTINGS, REMOTE_COUCHDB } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { DEFAULT_SETTINGS, LOG_LEVEL_NOTICE, REMOTE_COUCHDB } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { REMOTE_RESOURCE_KINDS } from "@vrtmrz/livesync-commonlib/replication";
const settingsInitialisationMocks = vi.hoisted(() => ({
applySettingsWithInitialisationChoice: vi.fn(),
}));
const loggerMocks = vi.hoisted(() => ({
Logger: vi.fn(),
}));
vi.mock("@/deps.ts", () => ({
App: class {},
@@ -18,6 +21,14 @@ vi.mock("@/deps.ts", () => ({
requireApiVersion: vi.fn(() => false),
}));
vi.mock("@/main.ts", () => ({ default: class {} }));
vi.mock("@vrtmrz/livesync-commonlib/compat/common/logger", async (importOriginal) => {
const actual = await importOriginal<typeof import("@vrtmrz/livesync-commonlib/compat/common/logger")>();
return { ...actual, Logger: loggerMocks.Logger };
});
vi.mock("@/common/translation", async (importOriginal) => {
const actual = await importOriginal<typeof import("@/common/translation")>();
return { ...actual, $msg: vi.fn(actual.$msg) };
});
vi.mock("@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions", () => ({
getLanguage: vi.fn(() => "en"),
compatGlobal: {
@@ -58,9 +69,12 @@ vi.mock("./PanePatches.ts", () => ({ panePatches: vi.fn() }));
vi.mock("./PaneMaintenance.ts", () => ({ paneMaintenance: vi.fn() }));
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
import { $msg } from "@/common/translation";
beforeEach(() => {
settingsInitialisationMocks.applySettingsWithInitialisationChoice.mockReset();
loggerMocks.Logger.mockClear();
vi.mocked($msg).mockClear();
});
describe("ObsidianLiveSyncSettingTab passphrase verification", () => {
@@ -120,6 +134,70 @@ describe("ObsidianLiveSyncSettingTab passphrase verification", () => {
expect(getNewReplicator).not.toHaveBeenCalled();
});
it("reports a CouchDB connection or setup failure with the connection-failure message", async () => {
const failure = new Error("remote unavailable");
const check = vi.fn(async () => {
throw failure;
});
const dispose = vi.fn(async () => undefined);
const createRemoteResource = vi.fn(async () => ({ check, dispose }));
const plugin = {
app: {},
core: {
services: {
replicator: { createRemoteResource },
},
},
};
const tab = new ObsidianLiveSyncSettingTab({} as never, plugin as never);
Object.assign(tab, {
_editingSettings: {
...DEFAULT_SETTINGS,
remoteType: REMOTE_COUCHDB,
},
});
await expect(tab.checkWorkingPassphrase()).resolves.toBe(false);
expect(vi.mocked($msg)).toHaveBeenCalledWith("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", {
db: failure.message,
});
expect(vi.mocked($msg)).not.toHaveBeenCalledWith("obsidianLiveSyncSettingTab.logPassphraseNotCompatible");
expect(loggerMocks.Logger).toHaveBeenCalledWith(expect.any(String), LOG_LEVEL_NOTICE);
expect(dispose).toHaveBeenCalledOnce();
});
it("reports an actual synchronisation-information mismatch with the incompatibility message", async () => {
const check = vi.fn(async () => false);
const dispose = vi.fn(async () => undefined);
const createRemoteResource = vi.fn(async () => ({ check, dispose }));
const plugin = {
app: {},
core: {
services: {
replicator: { createRemoteResource },
},
},
};
const tab = new ObsidianLiveSyncSettingTab({} as never, plugin as never);
Object.assign(tab, {
_editingSettings: {
...DEFAULT_SETTINGS,
remoteType: REMOTE_COUCHDB,
},
});
await expect(tab.checkWorkingPassphrase()).resolves.toBe(false);
expect(vi.mocked($msg)).toHaveBeenCalledWith("obsidianLiveSyncSettingTab.logPassphraseNotCompatible");
expect(vi.mocked($msg)).not.toHaveBeenCalledWith(
"obsidianLiveSyncSettingTab.logCheckPassphraseFailed",
expect.anything()
);
expect(loggerMocks.Logger).toHaveBeenCalledWith(expect.any(String), LOG_LEVEL_NOTICE);
expect(dispose).toHaveBeenCalledOnce();
});
});
describe("ObsidianLiveSyncSettingTab connection testing", () => {