mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 03:55:51 +00:00
@@ -39,7 +39,7 @@ import { useExternalSqlFileChanges } from "@/composables/useExternalSqlFileChang
|
||||
import { useWebDavAutoUpload } from "@/composables/useWebDavAutoUpload";
|
||||
import { useScheduledDatabaseBackups } from "@/composables/useScheduledDatabaseBackups";
|
||||
import { shouldDrawDesktopWindowFrame } from "@/composables/useWindowControls";
|
||||
import { createOpenTabsRestorationBarrier, initializeDesktopOpenTabs, type OpenTabsRestorationBarrier } from "@/lib/app/openTabsStartup";
|
||||
import { createOpenTabsRestorationBarrier, initializeDesktopOpenTabs, initializeOpenTabs, type OpenTabsRestorationBarrier } from "@/lib/app/openTabsStartup";
|
||||
import { useSaveSqlFolderSelection } from "@/composables/useSaveSqlFolderSelection";
|
||||
import "@/i18n";
|
||||
import { translateBackendError } from "@/i18n/backend-errors";
|
||||
@@ -2376,7 +2376,6 @@ async function initApp() {
|
||||
console.log(`[STARTUP] queryStore.initOpenTabs: ${(performance.now() - t0).toFixed(0)}ms`);
|
||||
};
|
||||
|
||||
if (!desktopOpenTabsRestorationBarrier) await settingsStore.initAiConfigs();
|
||||
try {
|
||||
if (desktopOpenTabsRestorationBarrier) {
|
||||
await initializeDesktopOpenTabs({
|
||||
@@ -2386,7 +2385,11 @@ async function initApp() {
|
||||
onOptionalStateError: (error) => console.error("[STARTUP] settingsStore.initAiConfigs failed", error),
|
||||
});
|
||||
} else {
|
||||
await restoreOpenTabs();
|
||||
await initializeOpenTabs({
|
||||
initializeOptionalState: () => settingsStore.initAiConfigs(),
|
||||
restoreOpenTabs,
|
||||
onOptionalStateError: (error) => console.error("[STARTUP] settingsStore.initAiConfigs failed", error),
|
||||
});
|
||||
}
|
||||
await settingsStore.initDesktopSettings().catch(() => {});
|
||||
|
||||
|
||||
@@ -20,21 +20,28 @@ export function createOpenTabsRestorationBarrier(): OpenTabsRestorationBarrier {
|
||||
};
|
||||
}
|
||||
|
||||
interface InitializeDesktopOpenTabsOptions {
|
||||
barrier: OpenTabsRestorationBarrier;
|
||||
interface InitializeOpenTabsOptions {
|
||||
initializeOptionalState: () => Promise<void>;
|
||||
restoreOpenTabs: () => Promise<void>;
|
||||
onOptionalStateError: (error: unknown) => void;
|
||||
}
|
||||
|
||||
export async function initializeDesktopOpenTabs({ barrier, initializeOptionalState, restoreOpenTabs, onOptionalStateError }: InitializeDesktopOpenTabsOptions): Promise<void> {
|
||||
export async function initializeOpenTabs({ initializeOptionalState, restoreOpenTabs, onOptionalStateError }: InitializeOpenTabsOptions): Promise<void> {
|
||||
try {
|
||||
try {
|
||||
await initializeOptionalState();
|
||||
} catch (error) {
|
||||
onOptionalStateError(error);
|
||||
}
|
||||
await restoreOpenTabs();
|
||||
await initializeOptionalState();
|
||||
} catch (error) {
|
||||
onOptionalStateError(error);
|
||||
}
|
||||
await restoreOpenTabs();
|
||||
}
|
||||
|
||||
interface InitializeDesktopOpenTabsOptions extends InitializeOpenTabsOptions {
|
||||
barrier: OpenTabsRestorationBarrier;
|
||||
}
|
||||
|
||||
export async function initializeDesktopOpenTabs({ barrier, ...options }: InitializeDesktopOpenTabsOptions): Promise<void> {
|
||||
try {
|
||||
await initializeOpenTabs(options);
|
||||
} finally {
|
||||
barrier.settle();
|
||||
}
|
||||
|
||||
+68
-1
@@ -1,10 +1,77 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { test } from "vitest";
|
||||
import { createOpenTabsRestorationBarrier, initializeDesktopOpenTabs } from "../../apps/desktop/src/lib/app/openTabsStartup.ts";
|
||||
import { createOpenTabsRestorationBarrier, initializeDesktopOpenTabs, initializeOpenTabs } from "../../apps/desktop/src/lib/app/openTabsStartup.ts";
|
||||
|
||||
const appSource = readFileSync("apps/desktop/src/App.vue", "utf8");
|
||||
|
||||
test("web startup reports optional initialization failure and continues restoring persisted state", async () => {
|
||||
const events: string[] = [];
|
||||
|
||||
await initializeOpenTabs({
|
||||
initializeOptionalState: async () => {
|
||||
events.push("initialize-ai-configs");
|
||||
throw new Error("AI config storage unavailable");
|
||||
},
|
||||
restoreOpenTabs: async () => {
|
||||
events.push("initialize-editor-settings");
|
||||
events.push("initialize-connections");
|
||||
events.push("restore-tabs");
|
||||
},
|
||||
onOptionalStateError: () => events.push("report-ai-config-error"),
|
||||
});
|
||||
|
||||
assert.deepEqual(events, ["initialize-ai-configs", "report-ai-config-error", "initialize-editor-settings", "initialize-connections", "restore-tabs"]);
|
||||
});
|
||||
|
||||
test("shared startup initializes optional state once and propagates required restoration failure", async () => {
|
||||
let optionalInitializationCount = 0;
|
||||
|
||||
await assert.rejects(
|
||||
initializeOpenTabs({
|
||||
initializeOptionalState: async () => {
|
||||
optionalInitializationCount += 1;
|
||||
},
|
||||
restoreOpenTabs: async () => {
|
||||
throw new Error("persisted connections unavailable");
|
||||
},
|
||||
onOptionalStateError: () => assert.fail("successful optional initialization must not be reported"),
|
||||
}),
|
||||
/persisted connections unavailable/,
|
||||
);
|
||||
|
||||
assert.equal(optionalInitializationCount, 1);
|
||||
});
|
||||
|
||||
test("web startup uses the guarded initializer behind the existing authentication gate", () => {
|
||||
const initAppStart = appSource.indexOf("async function initApp()");
|
||||
const initAppEnd = appSource.indexOf("function restoreActiveConnectionContext", initAppStart);
|
||||
assert.ok(initAppStart >= 0 && initAppEnd > initAppStart);
|
||||
|
||||
const initAppSource = appSource.slice(initAppStart, initAppEnd);
|
||||
const webInitialization = initAppSource.indexOf("await initializeOpenTabs({");
|
||||
const optionalInitialization = initAppSource.indexOf("initializeOptionalState: () => settingsStore.initAiConfigs()", webInitialization);
|
||||
const requiredRestoration = initAppSource.indexOf("restoreOpenTabs,", optionalInitialization);
|
||||
const savedSqlInitialization = initAppSource.indexOf("savedSqlStore.initFromStorage()", requiredRestoration);
|
||||
assert.ok(webInitialization >= 0);
|
||||
assert.ok(optionalInitialization > webInitialization);
|
||||
assert.ok(requiredRestoration > optionalInitialization);
|
||||
assert.ok(savedSqlInitialization > requiredRestoration);
|
||||
assert.equal(initAppSource.includes("if (!desktopOpenTabsRestorationBarrier) await settingsStore.initAiConfigs()"), false);
|
||||
|
||||
const mountedStart = appSource.indexOf("onMounted(async () =>");
|
||||
const mountedEnd = appSource.indexOf("onUnmounted(", mountedStart);
|
||||
assert.ok(mountedStart >= 0 && mountedEnd > mountedStart);
|
||||
|
||||
const mountedSource = appSource.slice(mountedStart, mountedEnd);
|
||||
const authCheck = mountedSource.indexOf('fetch(apiUrl("/api/auth/check"))');
|
||||
const loginRedirect = mountedSource.indexOf('history.replaceState(null, "", webPath("/login"))', authCheck);
|
||||
const authenticatedInitialization = mountedSource.indexOf("if (!setupRequired.value && (!needsAuth.value || authenticated.value)) void initApp()", loginRedirect);
|
||||
assert.ok(authCheck >= 0);
|
||||
assert.ok(loginRedirect > authCheck);
|
||||
assert.ok(authenticatedInitialization > loginRedirect);
|
||||
});
|
||||
|
||||
test("desktop SQL file opening survives unrelated initialization failure and follows restored tabs", async () => {
|
||||
const events: string[] = [];
|
||||
const barrier = createOpenTabsRestorationBarrier();
|
||||
|
||||
Reference in New Issue
Block a user