mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-08-14 07:35:46 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd01e12b90 | ||
|
|
c4359fe4f6 | ||
|
|
a4194a8978 | ||
|
|
1e9ab39adc | ||
|
|
3973290485 | ||
|
|
510db277cd |
Generated
+12
-12
@@ -23,7 +23,7 @@
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@vrtmrz/browser-ui-kit": "0.1.0",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.12",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.13",
|
||||
"@vrtmrz/obsidian-plugin-kit": "0.1.3",
|
||||
"@vrtmrz/ui-interactions": "0.1.2",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
@@ -32,7 +32,7 @@
|
||||
"markdown-it": "^14.2.0",
|
||||
"minimatch": "^10.2.5",
|
||||
"obsidian": "^1.13.1",
|
||||
"octagonal-wheels": "^0.1.52",
|
||||
"octagonal-wheels": "^0.1.53",
|
||||
"qrcode-generator": "^1.4.4",
|
||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||
},
|
||||
@@ -4775,9 +4775,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vrtmrz/livesync-commonlib": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.12.tgz",
|
||||
"integrity": "sha512-SYqUsMcz9241qEUqmnIwVKOPXJB4/FXjXQh8nHPmX004KkV5PUnAm4ClhNwlQzvYfpTI5IHYKej+JIndiQYXmw==",
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.13.tgz",
|
||||
"integrity": "sha512-yWzmq+2sGX39gIB37igrjvctJKD5N3jlWT+ATavm81aOjEp6VUOIT2FzUpOq/3BEM8eE83uzu3N6/rXEpksEiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.808.0",
|
||||
@@ -4793,7 +4793,7 @@
|
||||
"idb": "^8.0.3",
|
||||
"markdown-it": "^14.2.0",
|
||||
"minimatch": "^10.2.5",
|
||||
"octagonal-wheels": "^0.1.51",
|
||||
"octagonal-wheels": "^0.1.53",
|
||||
"pouchdb-adapter-http": "^9.0.0",
|
||||
"pouchdb-adapter-idb": "^9.0.0",
|
||||
"pouchdb-adapter-indexeddb": "^9.0.0",
|
||||
@@ -11532,9 +11532,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/octagonal-wheels": {
|
||||
"version": "0.1.52",
|
||||
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.52.tgz",
|
||||
"integrity": "sha512-9WJN2UveNh90Op1S07cIso1WyNrQbO/unibDLfUGnpomIcU4g6F+p8reZHW0Ed8sGzKF5qGnQp991Y9MB1TwNg==",
|
||||
"version": "0.1.53",
|
||||
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.53.tgz",
|
||||
"integrity": "sha512-4NJsb96Sk6rJXhrTyjAY5GRIoWMFJsFp56b5ba9fV8/87ys2HCjMo0hior4F8k4ma72pLTJT7i6DvuihHxSsMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"idb": "^8.0.3"
|
||||
@@ -15928,7 +15928,7 @@
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"minimatch": "^10.2.5",
|
||||
"octagonal-wheels": "^0.1.52",
|
||||
"octagonal-wheels": "^0.1.53",
|
||||
"pouchdb-adapter-http": "^9.0.0",
|
||||
"pouchdb-adapter-leveldb": "^9.0.0",
|
||||
"pouchdb-core": "^9.0.0",
|
||||
@@ -15951,7 +15951,7 @@
|
||||
"name": "livesync-webapp",
|
||||
"version": "1.0.13-webapp",
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.52"
|
||||
"octagonal-wheels": "^0.1.53"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||
@@ -15963,7 +15963,7 @@
|
||||
"src/apps/webpeer": {
|
||||
"version": "1.0.13-webpeer",
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.52"
|
||||
"octagonal-wheels": "^0.1.53"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||
|
||||
+2
-2
@@ -177,7 +177,7 @@
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@vrtmrz/browser-ui-kit": "0.1.0",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.12",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.13",
|
||||
"@vrtmrz/obsidian-plugin-kit": "0.1.3",
|
||||
"@vrtmrz/ui-interactions": "0.1.2",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
@@ -186,7 +186,7 @@
|
||||
"markdown-it": "^14.2.0",
|
||||
"minimatch": "^10.2.5",
|
||||
"obsidian": "^1.13.1",
|
||||
"octagonal-wheels": "^0.1.52",
|
||||
"octagonal-wheels": "^0.1.53",
|
||||
"qrcode-generator": "^1.4.4",
|
||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||
},
|
||||
|
||||
@@ -58,7 +58,11 @@ async function verifyRemoteState(
|
||||
standardIo.writeStderr(`[Verification] Failed to connect to remote CouchDB: ${dbRet}\n`);
|
||||
return false;
|
||||
}
|
||||
milestone = await dbRet.db.get(MILESTONE_DOCID);
|
||||
try {
|
||||
milestone = await dbRet.db.get(MILESTONE_DOCID);
|
||||
} finally {
|
||||
await dbRet.db.close();
|
||||
}
|
||||
} else if (settings.remoteType === REMOTE_MINIO) {
|
||||
milestone = await (replicator as LiveSyncJournalReplicator).client.downloadJson("_00000000-milestone.json");
|
||||
}
|
||||
|
||||
@@ -708,6 +708,18 @@ describe("runCommand abnormal cases", () => {
|
||||
describe("mark-resolved and unlock-remote commands", () => {
|
||||
it("mark-resolved without args runs on active database", async () => {
|
||||
const core = createCoreMock();
|
||||
const remoteDatabase = {
|
||||
close: vi.fn(async () => undefined),
|
||||
get: vi.fn(async () => ({
|
||||
locked: false,
|
||||
accepted_nodes: ["test-node-id"],
|
||||
})),
|
||||
};
|
||||
core.services.replicator.getActiveReplicator.mockReturnValueOnce({
|
||||
nodeid: "test-node-id",
|
||||
initializeDatabaseForReplication: vi.fn(async () => undefined),
|
||||
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: remoteDatabase })),
|
||||
});
|
||||
const result = await runCommand(makeOptions("mark-resolved", []), {
|
||||
...context,
|
||||
core,
|
||||
@@ -715,6 +727,7 @@ describe("runCommand abnormal cases", () => {
|
||||
expect(result).toBe(true);
|
||||
expect(core.services.replication.markResolved).toHaveBeenCalledTimes(1);
|
||||
expect(core.services.control.applySettings).not.toHaveBeenCalled();
|
||||
expect(remoteDatabase.close).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("mark-resolved with remote-id temporarily activates it and runs markResolved", async () => {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"minimatch": "^10.2.5",
|
||||
"octagonal-wheels": "^0.1.52",
|
||||
"octagonal-wheels": "^0.1.53",
|
||||
"pouchdb-adapter-http": "^9.0.0",
|
||||
"pouchdb-adapter-leveldb": "^9.0.0",
|
||||
"pouchdb-core": "^9.0.0",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"test:browser": "deno test -A --no-check --frozen --config ../../../test/browser-apps/deno.json --lock ../../../test/browser-apps/deno.lock ../../../test/browser-apps/webapp/browser-smoke.test.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.52"
|
||||
"octagonal-wheels": "^0.1.53"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"test:browser": "deno test -A --no-check --frozen --config ../../../test/browser-apps/deno.json --lock ../../../test/browser-apps/deno.lock ../../../test/browser-apps/webpeer/browser-smoke.test.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.52"
|
||||
"octagonal-wheels": "^0.1.53"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-plugin-svelte": "^3.19.0",
|
||||
|
||||
@@ -95,18 +95,19 @@ function requiredEnvironment(name: "hostname" | "username" | "password"): string
|
||||
describe("LocalDatabaseMaintenance Garbage Collection V3 with CouchDB", () => {
|
||||
it("propagates collection safely, completes compaction, and permits content-addressed chunk recreation", async () => {
|
||||
const databaseName = `livesync-gcv3-${crypto.randomUUID()}`;
|
||||
const local = new PouchDB<FixtureContent>(`${databaseName}-source`, { adapter: "memory" });
|
||||
const replica = new PouchDB<FixtureContent>(`${databaseName}-replica`, { adapter: "memory" });
|
||||
const remote = new PouchDB<FixtureContent>(
|
||||
`${requiredEnvironment("hostname").replace(/\/+$/u, "")}/${databaseName}`,
|
||||
{
|
||||
const createRemote = () =>
|
||||
new PouchDB<FixtureContent>(`${requiredEnvironment("hostname").replace(/\/+$/u, "")}/${databaseName}`, {
|
||||
adapter: "http",
|
||||
auth: {
|
||||
username: requiredEnvironment("username"),
|
||||
password: requiredEnvironment("password"),
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
const local = new PouchDB<FixtureContent>(`${databaseName}-source`, { adapter: "memory" });
|
||||
const replica = new PouchDB<FixtureContent>(`${databaseName}-replica`, { adapter: "memory" });
|
||||
const remote = createRemote();
|
||||
const maintenanceRemote = createRemote();
|
||||
const closeMaintenanceRemote = vi.spyOn(maintenanceRemote, "close");
|
||||
|
||||
try {
|
||||
await remote.info();
|
||||
@@ -178,7 +179,7 @@ describe("LocalDatabaseMaintenance Garbage Collection V3 with CouchDB", () => {
|
||||
},
|
||||
})
|
||||
),
|
||||
connectRemoteCouchDBWithSetting: vi.fn(() => Promise.resolve({ db: remote })),
|
||||
connectRemoteCouchDBWithSetting: vi.fn(() => Promise.resolve({ db: maintenanceRemote })),
|
||||
};
|
||||
const notice = vi.fn();
|
||||
const maintenance = Object.create(LocalDatabaseMaintenance.prototype) as LocalDatabaseMaintenance;
|
||||
@@ -201,6 +202,7 @@ describe("LocalDatabaseMaintenance Garbage Collection V3 with CouchDB", () => {
|
||||
|
||||
await maintenance.gcv3();
|
||||
|
||||
expect(closeMaintenanceRemote).toHaveBeenCalledOnce();
|
||||
expect(replicationModes).toEqual(["sync", "pushOnly"]);
|
||||
expect(clearHash).toHaveBeenCalledOnce();
|
||||
expect(notice).toHaveBeenCalledWith("Compaction on remote database completed successfully.", "gc-compact");
|
||||
@@ -251,6 +253,9 @@ describe("LocalDatabaseMaintenance Garbage Collection V3 with CouchDB", () => {
|
||||
data: "recreated",
|
||||
});
|
||||
} finally {
|
||||
if (closeMaintenanceRemote.mock.calls.length === 0) {
|
||||
await maintenanceRemote.close();
|
||||
}
|
||||
await Promise.all([local.destroy(), replica.destroy(), remote.destroy()]);
|
||||
}
|
||||
}, 30_000);
|
||||
|
||||
@@ -747,29 +747,33 @@ Success: ${successCount}, Errored: ${errored}`;
|
||||
this._notice(`Failed to connect to remote for compaction. ${remote}`, "gc-compact");
|
||||
return;
|
||||
}
|
||||
const compactResult = await remote.db.compact({
|
||||
interval: 1000,
|
||||
});
|
||||
// Probably no need to wait, but just in case.
|
||||
let timeout = 2 * 60 * 1000; // 2 minutes
|
||||
for (;;) {
|
||||
const status = await remote.db.info();
|
||||
if ("compact_running" in status && status?.compact_running) {
|
||||
this._notice("Compaction in progress on remote database...", "gc-compact");
|
||||
await delay(2000);
|
||||
timeout -= 2000;
|
||||
if (timeout <= 0) {
|
||||
this._notice("Compaction on remote database timed out.", "gc-compact");
|
||||
return;
|
||||
try {
|
||||
const compactResult = await remote.db.compact({
|
||||
interval: 1000,
|
||||
});
|
||||
// Probably no need to wait, but just in case.
|
||||
let timeout = 2 * 60 * 1000; // 2 minutes
|
||||
for (;;) {
|
||||
const status = await remote.db.info();
|
||||
if ("compact_running" in status && status?.compact_running) {
|
||||
this._notice("Compaction in progress on remote database...", "gc-compact");
|
||||
await delay(2000);
|
||||
timeout -= 2000;
|
||||
if (timeout <= 0) {
|
||||
this._notice("Compaction on remote database timed out.", "gc-compact");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (compactResult && "ok" in compactResult) {
|
||||
this._notice("Compaction on remote database completed successfully.", "gc-compact");
|
||||
} else {
|
||||
this._notice("Compaction on remote database failed.", "gc-compact");
|
||||
if (compactResult && "ok" in compactResult) {
|
||||
this._notice("Compaction on remote database completed successfully.", "gc-compact");
|
||||
} else {
|
||||
this._notice("Compaction on remote database failed.", "gc-compact");
|
||||
}
|
||||
} finally {
|
||||
await remote.db.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -222,6 +222,7 @@ describe("LocalDatabaseMaintenance Garbage Collection V3", () => {
|
||||
const remoteDatabase = {
|
||||
compact: vi.fn(async () => ({ ok: true })),
|
||||
info: vi.fn(async () => ({ compact_running: true })),
|
||||
close: vi.fn(async () => undefined),
|
||||
};
|
||||
Object.assign(maintenance, {
|
||||
core: {
|
||||
@@ -243,6 +244,7 @@ describe("LocalDatabaseMaintenance Garbage Collection V3", () => {
|
||||
"Compaction on remote database completed successfully.",
|
||||
"gc-compact"
|
||||
);
|
||||
expect(remoteDatabase.close).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -187,27 +187,31 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
||||
return false;
|
||||
}
|
||||
|
||||
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
||||
this.localDatabase.clearCaches();
|
||||
// Perform the synchronisation once.
|
||||
const replicated = await this.services.replicator.runFiniteReplicationActivity(
|
||||
() => this.core.replicator.openReplication(this.settings, false, showMessage, true),
|
||||
{ label: "replication" }
|
||||
);
|
||||
if (replicated) {
|
||||
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
|
||||
try {
|
||||
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
||||
this.localDatabase.clearCaches();
|
||||
await this.services.replicator.getActiveReplicator()?.markRemoteResolved(this.settings);
|
||||
Logger(
|
||||
"The local database has been cleaned up.",
|
||||
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
|
||||
);
|
||||
} else {
|
||||
Logger(
|
||||
"Replication has been cancelled. Please try it again.",
|
||||
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
|
||||
// Perform the synchronisation once.
|
||||
const replicated = await this.services.replicator.runFiniteReplicationActivity(
|
||||
() => this.core.replicator.openReplication(this.settings, false, showMessage, true),
|
||||
{ label: "replication" }
|
||||
);
|
||||
if (replicated) {
|
||||
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
|
||||
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
||||
this.localDatabase.clearCaches();
|
||||
await this.services.replicator.getActiveReplicator()?.markRemoteResolved(this.settings);
|
||||
Logger(
|
||||
"The local database has been cleaned up.",
|
||||
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
|
||||
);
|
||||
} else {
|
||||
Logger(
|
||||
"Replication has been cancelled. Please try it again.",
|
||||
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await remoteDB.db.close();
|
||||
}
|
||||
},
|
||||
{ label: "database-cleanup" }
|
||||
|
||||
@@ -128,8 +128,11 @@ describe("compatibility: cleaned-remote reconciliation for IndexedDB clients", (
|
||||
});
|
||||
const runFiniteReplicationActivity = vi.fn(async (task: () => unknown) => await task());
|
||||
const openReplication = vi.fn(async () => true);
|
||||
const remoteDatabase = {
|
||||
close: vi.fn(async () => undefined),
|
||||
};
|
||||
const activeReplicator = Object.assign(new LiveSyncCouchDBReplicator({} as any), {
|
||||
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: {} })),
|
||||
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: remoteDatabase })),
|
||||
markRemoteResolved: vi.fn(async () => undefined),
|
||||
});
|
||||
const services = {
|
||||
@@ -177,5 +180,9 @@ describe("compatibility: cleaned-remote reconciliation for IndexedDB clients", (
|
||||
expect(openReplication).toHaveBeenCalledOnce();
|
||||
expect(openReplication.mock.invocationCallOrder[0]).toBeLessThan(activityFinished.mock.invocationCallOrder[0]);
|
||||
expect(chunkMocks.balanceChunkPurgedDBs).toHaveBeenCalledOnce();
|
||||
expect(remoteDatabase.close).toHaveBeenCalledOnce();
|
||||
expect(remoteDatabase.close.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
activityFinished.mock.invocationCallOrder[0]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -547,7 +547,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
if (typeof db === "string") {
|
||||
Logger($msg("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", { db }), LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
} else {
|
||||
}
|
||||
try {
|
||||
if (await checkSyncInfo(db.db)) {
|
||||
// Logger($msg("obsidianLiveSyncSettingTab.logDatabaseConnected"), LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
@@ -555,6 +556,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
Logger($msg("obsidianLiveSyncSettingTab.logPassphraseNotCompatible"), LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
await db.db.close();
|
||||
}
|
||||
};
|
||||
isPassphraseValid = async () => {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_SETTINGS, REMOTE_COUCHDB } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
|
||||
const negotiationMocks = vi.hoisted(() => ({
|
||||
checkSyncInfo: vi.fn(async () => true),
|
||||
}));
|
||||
|
||||
vi.mock("@/deps.ts", () => ({
|
||||
App: class {},
|
||||
Component: class {},
|
||||
PluginSettingTab: class {},
|
||||
}));
|
||||
vi.mock("@/main.ts", () => ({ default: class {} }));
|
||||
vi.mock("@/common/events.ts", () => ({
|
||||
EVENT_REQUEST_RELOAD_SETTING_TAB: "request-reload-setting-tab",
|
||||
eventHub: { onEvent: vi.fn() },
|
||||
}));
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/pouchdb/negotiation", () => negotiationMocks);
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator", () => ({
|
||||
LiveSyncCouchDBReplicator: class {},
|
||||
}));
|
||||
vi.mock("./LiveSyncSetting.ts", () => ({ LiveSyncSetting: class {} }));
|
||||
vi.mock("./SettingPane.ts", () => ({
|
||||
enableOnly: vi.fn(() => vi.fn()),
|
||||
setLevelClass: vi.fn(),
|
||||
setStyle: vi.fn(),
|
||||
visibleOnly: vi.fn(() => vi.fn()),
|
||||
}));
|
||||
vi.mock("./PaneChangeLog.ts", () => ({ paneChangeLog: vi.fn() }));
|
||||
vi.mock("./PaneSetup.ts", () => ({ paneSetup: vi.fn() }));
|
||||
vi.mock("./PaneGeneral.ts", () => ({ paneGeneral: vi.fn() }));
|
||||
vi.mock("./PaneRemoteConfig.ts", () => ({ paneRemoteConfig: vi.fn() }));
|
||||
vi.mock("./PaneSelector.ts", () => ({ paneSelector: vi.fn() }));
|
||||
vi.mock("./PaneSyncSettings.ts", () => ({ paneSyncSettings: vi.fn() }));
|
||||
vi.mock("./PaneCustomisationSync.ts", () => ({ paneCustomisationSync: vi.fn() }));
|
||||
vi.mock("./PaneHatch.ts", () => ({ paneHatch: vi.fn() }));
|
||||
vi.mock("./PaneAdvanced.ts", () => ({ paneAdvanced: vi.fn() }));
|
||||
vi.mock("./PanePowerUsers.ts", () => ({ panePowerUsers: vi.fn() }));
|
||||
vi.mock("./PanePatches.ts", () => ({ panePatches: vi.fn() }));
|
||||
vi.mock("./PaneMaintenance.ts", () => ({ paneMaintenance: vi.fn() }));
|
||||
|
||||
import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
|
||||
|
||||
describe("ObsidianLiveSyncSettingTab passphrase verification", () => {
|
||||
it("closes the finite remote connection after checking synchronisation information", async () => {
|
||||
const remoteDatabase = {
|
||||
close: vi.fn(async () => undefined),
|
||||
};
|
||||
const replicator = Object.assign(new LiveSyncCouchDBReplicator({} as never), {
|
||||
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: remoteDatabase })),
|
||||
});
|
||||
const plugin = {
|
||||
app: {},
|
||||
core: {
|
||||
services: {
|
||||
API: { isMobile: vi.fn(() => false) },
|
||||
replicator: { getNewReplicator: vi.fn(() => replicator) },
|
||||
},
|
||||
},
|
||||
};
|
||||
const tab = new ObsidianLiveSyncSettingTab({} as never, plugin as never);
|
||||
Object.assign(tab, {
|
||||
_editingSettings: {
|
||||
...DEFAULT_SETTINGS,
|
||||
remoteType: REMOTE_COUCHDB,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(tab.checkWorkingPassphrase()).resolves.toBe(true);
|
||||
|
||||
expect(negotiationMocks.checkSyncInfo).toHaveBeenCalledWith(remoteDatabase);
|
||||
expect(remoteDatabase.close).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ export type CouchDBConnectionProbeResult = { ok: true } | { ok: false; reason: s
|
||||
type CouchDBConnectionResult =
|
||||
| string
|
||||
| {
|
||||
db: unknown;
|
||||
db: { close(): Promise<void> };
|
||||
info: unknown;
|
||||
};
|
||||
|
||||
@@ -50,7 +50,11 @@ export async function probeCouchDBConnection(
|
||||
if (typeof result === "string") {
|
||||
return { ok: false, reason: result };
|
||||
}
|
||||
return { ok: true };
|
||||
try {
|
||||
return { ok: true };
|
||||
} finally {
|
||||
await result.db.close();
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidCouchDBServerURL(value: string): boolean {
|
||||
|
||||
@@ -14,8 +14,9 @@ describe("CouchDB setup connection policy", () => {
|
||||
] as const)(
|
||||
"%s can %s without changing the Commonlib connection contract",
|
||||
async (createIfMissing, _description) => {
|
||||
const close = vi.fn(async () => undefined);
|
||||
const connectRemoteCouchDBWithSetting = vi.fn(async () => ({
|
||||
db: {},
|
||||
db: { close },
|
||||
info: { db_name: "notes" },
|
||||
}));
|
||||
const replicator = {
|
||||
@@ -27,6 +28,7 @@ describe("CouchDB setup connection policy", () => {
|
||||
await expect(probeCouchDBConnection(replicator, settings, createIfMissing)).resolves.toEqual({ ok: true });
|
||||
expect(connectRemoteCouchDBWithSetting).toHaveBeenCalledWith(settings, false, createIfMissing, false);
|
||||
expect(replicator.tryConnectRemote).not.toHaveBeenCalled();
|
||||
expect(close).toHaveBeenCalledOnce();
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ Earlier releases remain available in the 0.25 release history and the legacy rel
|
||||
|
||||
- **Inspect conflicts and file/database differences** now reports local Metadata whose stored document ID does not match the ID derived from its recorded path. Ordinary scans leave unresolved entries and their corresponding Vault paths unchanged, while allowing consistently addressed Metadata for the same logical path to proceed normally.
|
||||
- When one live, unconflicted entry has an unambiguous target, its wrench menu can repair that one local Metadata document after separate confirmation. The target is written and verified before the mismatched source ID is removed; ambiguous or otherwise unsafe entries remain read-only.
|
||||
|
||||
#### Fixed
|
||||
- Fast Fetch now writes deletion tombstones to the local database without attempting to decrypt them. A tombstone has no encrypted payload, and decryption previously aborted the whole fetch at the first deleted document. New devices could not complete their initial sync on vaults that contain old deletions (Commonlib PR #108).
|
||||
- Thank you to @KennethLloyd for the contribution!
|
||||
|
||||
## 1.0.12
|
||||
|
||||
|
||||
Reference in New Issue
Block a user