Make remote and log resource ownership explicit

This commit is contained in:
vorotamoroz committed 2026-10-29 14:56:06 +00:00
1 parent db95f420b9
commit 28bc6e87a1
17 files changed
+987 -71

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createOwnedResource } from "octagonal-wheels/resource";
import { LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
import { bindableFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/HandlerUtils";
const unusedStartupDependencies: readonly [string, readonly string[]][] = [
["@/serviceFeatures/useP2PSettingsPreparation", ["useP2PSettingsPreparation"]],
["@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions", ["setGetLanguage"]],
["./features/LiveSyncCommands.ts", ["LiveSyncCommands"]],
["./features/HiddenFileSync/CmdHiddenFileSync.ts", ["HiddenFileSync"]],
["./features/ConfigSync/CmdConfigSync.ts", ["ConfigSync"]],
["./modules/features/ModuleLog.ts", ["ModuleLog"]],
["./modules/essentialObsidian/ModuleObsidianEvents.ts", ["ModuleObsidianEvents"]],
["./modules/features/ModuleObsidianSettingTab.ts", ["ModuleObsidianSettingDialogue"]],
["./modules/features/ModuleObsidianDocumentHistory.ts", ["ModuleObsidianDocumentHistory"]],
["./modules/features/ModuleGlobalHistory.ts", ["ModuleObsidianGlobalHistory"]],
["./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts", ["LocalDatabaseMaintenance"]],
["./modules/services/ObsidianServiceHub.ts", ["ObsidianServiceHub"]],
["@vrtmrz/livesync-commonlib/compat/serviceModules/Rebuilder", ["ServiceRebuilder"]],
["@/serviceModules/DatabaseFileAccess.ts", ["ServiceDatabaseFileAccess"]],
["@/serviceModules/ServiceFileAccessImpl.ts", ["ServiceFileAccessObsidian"]],
["@vrtmrz/livesync-commonlib/compat/managers/StorageProcessingManager", ["StorageAccessManager"]],
["./serviceModules/FileHandler.ts", ["ServiceFileHandler"]],
["./serviceModules/FileAccessObsidian.ts", ["FileAccessObsidian"]],
["./managers/StorageEventManagerObsidian.ts", ["StorageEventManagerObsidian"]],
["@vrtmrz/livesync-commonlib/compat/mock_and_interop/wrapper", ["setNoticeClass"]],
["./LiveSyncBaseCore.ts", ["LiveSyncBaseCore"]],
["./modules/features/ModuleObsidianSettingAsMarkdown.ts", ["ModuleObsidianSettingsAsMarkdown"]],
["./modules/features/SetupManager.ts", ["SetupManager"]],
["./serviceFeatures/onLayoutReady/enablei18n.ts", ["enableI18nFeature"]],
["@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner", ["useOfflineScanner"]],
["@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig", ["useRemoteConfiguration"]],
["@vrtmrz/livesync-commonlib/compat/serviceFeatures/checkRemoteSize", ["useCheckRemoteSize"]],
["./serviceFeatures/redFlag.ts", ["useRedFlagFeatures"]],
["./serviceFeatures/setupObsidian/setupProtocol.ts", ["useSetupProtocolFeature"]],
["@/serviceFeatures/setupObsidian/qrCode", ["useSetupQRCodeFeature"]],
["@/serviceFeatures/setupObsidian/setupUri", ["useSetupURIFeature"]],
[
"./serviceFeatures/setupObsidian/setupManagerHandlers.ts",
["showOnboardingInvitation", "useSetupManagerHandlersFeature"],
],
["@vrtmrz/livesync-commonlib/p2p", ["useP2PReplicatorCommands", "useP2PReplicatorFeature"]],
["./serviceFeatures/useP2PReplicatorUI.ts", ["useP2PReplicatorUI"]],
["./serviceFeatures/useReviewHarness.ts", ["useReviewHarness"]],
["./features/P2PSync/P2PReplicator/P2PReplicationUI.ts", ["createOpenReplicationUI", "createOpenRebuildUI"]],
["./serviceFeatures/compatibilityReview.ts", ["useCompatibilityReview"]],
["./serviceFeatures/compatibilityReviewObsidian.ts", ["createObsidianCompatibilityReviewUi"]],
["./serviceModules/FileReflectionProvenance.ts", ["createFileReflectionProvenance"]],
["./serviceFeatures/interactiveConflictResolution", ["useInteractiveConflictResolutionFeature"]],
["./modules/features/InteractiveConflictResolving/ConflictResolveModal.ts", ["ConflictResolveModal"]],
["./serviceFeatures/obsidianReplicationRibbon.ts", ["useObsidianReplicationRibbonFeature"]],
["./serviceFeatures/startupLifecycle", ["useStartupLifecycleFeature"]],
["./serviceFeatures/useJoinedDevices", ["useJoinedDevicesFeature"]],
["./serviceFeatures/useDatabaseDiagnostics", ["useDatabaseDiagnosticsFeature"]],
];
beforeEach(() => {
vi.resetModules();
vi.doMock("./deps", () => ({ Plugin: class {}, Notice: class {}, getLanguage: () => "en" }));
for (const [specifier, names] of unusedStartupDependencies) {
vi.doMock(specifier, () => Object.fromEntries(names.map((name) => [name, vi.fn()])));
}
});
describe("Obsidian plug-in unload logging", () => {
it.each(["complete", "fail"] as const)(
"retains its log connection through shutdown and retires it afterwards: %s",
async (outcome) => {
const { default: LiveSyncPlugin } = await import("./main.ts");
const events: string[] = [];
const sink = vi.fn((message: unknown) => events.push(String(message)));
const addLog = bindableFunction<(message: unknown, level?: number, key?: string) => void>("unload log");
const unregister = addLog.setHandler(sink);
const dispose = vi.fn(() => {
unregister();
events.push("disposed");
});
const logResource = createOwnedResource(sink, dispose);
let finish!: () => void;
let fail!: (reason: Error) => void;
const shutdown = new Promise<void>((resolve, reject) => {
finish = resolve;
fail = reject;
});
const onUnload = vi.fn(() => shutdown);
const core = {
getModule: vi.fn(() => ({ logResource })),
services: { API: { addLog }, control: { onUnload } },
};
expect(LiveSyncPlugin.prototype.onunload.call({ core } as never)).toBeUndefined();
expect(onUnload).toHaveBeenCalledTimes(1);
expect(dispose).not.toHaveBeenCalled();
addLog("during shutdown");
const failure = new Error("database close failed");
if (outcome === "fail") {
fail(failure);
} else {
finish();
}
await vi.waitFor(() => expect(dispose).toHaveBeenCalledTimes(1));
if (outcome === "fail") {
const message = "Failed to unload Self-hosted LiveSync: " + String(failure);
expect(sink).toHaveBeenCalledWith(message, LOG_LEVEL_URGENT, "shutdown");
expect(events).toEqual(["during shutdown", message, "disposed"]);
} else {
expect(events).toEqual(["during shutdown", "disposed"]);
}
expect(() => addLog("after shutdown")).toThrow("not assigned");
}
);
});