From 4e5af2b45fb47072d040af809e123c3c72669509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 15 May 2026 02:53:13 -0400 Subject: [PATCH] fix(settings): guard disabled model reads --- src/main/ipc-core-read-handlers.ts | 7 +- tests/unit/ipc-core-read-handlers.test.ts | 100 ++++++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 tests/unit/ipc-core-read-handlers.test.ts diff --git a/src/main/ipc-core-read-handlers.ts b/src/main/ipc-core-read-handlers.ts index a7d07090..098c38a9 100644 --- a/src/main/ipc-core-read-handlers.ts +++ b/src/main/ipc-core-read-handlers.ts @@ -199,9 +199,9 @@ export function registerCoreReadHandlers({ }); registerHandle(IPC_CHANNELS.getConnectorDisabledModels, (_event, payload) => { + let parsed: z.infer; try { - const parsed = connectorModelPoolQuerySchema.parse(payload); - return ok(db.core.settings.getDisabledModels(parsed.companyId, parsed.connectorId)); + parsed = connectorModelPoolQuerySchema.parse(payload); } catch (err) { logAppError(logger, err, { kind: "user_input", @@ -210,6 +210,9 @@ export function registerCoreReadHandlers({ }, "warn"); return ok([]); } + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + return ok(db.core.settings.getDisabledModels(parsed.companyId, parsed.connectorId)); }); registerHandle(IPC_CHANNELS.getCompanyMetrics, (_event, payload) => { diff --git a/tests/unit/ipc-core-read-handlers.test.ts b/tests/unit/ipc-core-read-handlers.test.ts new file mode 100644 index 00000000..32f61e7b --- /dev/null +++ b/tests/unit/ipc-core-read-handlers.test.ts @@ -0,0 +1,100 @@ +import type { IpcMainInvokeEvent } from "electron"; +import { describe, expect, it, vi } from "vitest"; +import type { DesktopResult } from "@shared/contracts"; +import { IPC_CHANNELS } from "@shared/contracts"; +import type { AppDatabase } from "../../src/main/database"; +import { registerCoreReadHandlers } from "../../src/main/ipc-core-read-handlers"; +import type { RegisterCoreHandlersDependencies } from "../../src/main/ipc-core-common"; + +vi.mock("electron", () => ({ + dialog: { + showMessageBox: vi.fn(), + showOpenDialog: vi.fn(), + }, + nativeTheme: { themeSource: "system" }, + shell: { + openExternal: vi.fn(), + openPath: vi.fn(), + }, +})); + +type IpcHandler = ( + event: IpcMainInvokeEvent, + ...args: unknown[] +) => Promise> | DesktopResult; + +const companyId = "11111111-1111-4111-8111-111111111111"; + +function createReadHandlerHarness(ensureCompanyExists: RegisterCoreHandlersDependencies["ensureCompanyExists"]) { + const handlers = new Map(); + const getDisabledModels = vi.fn(() => [{ id: "claude-sonnet-4-5", label: "Claude Sonnet 4.5" }]); + const db = { + core: { + settings: { + getCurrentCompanyId: vi.fn(() => companyId), + getDisabledModels, + }, + }, + projection: { + snapshot: { + listRendererSnapshot: vi.fn(() => ({})), + }, + }, + } as unknown as AppDatabase; + + registerCoreReadHandlers({ + registerHandle: (channel, handler) => { + handlers.set(channel, handler); + }, + db, + ensureCompanyExists, + publishDomainChanged: vi.fn(), + checkForUpdates: vi.fn(async () => true), + resetAfterRestore: vi.fn(), + listConnectorModels: vi.fn(async () => []), + logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, + }); + + const handler = handlers.get(IPC_CHANNELS.getConnectorDisabledModels); + if (!handler) { + throw new Error("getConnectorDisabledModels handler was not registered."); + } + return { handler, getDisabledModels }; +} + +describe("ipc-core-read-handlers connector model pool reads", () => { + it("reads disabled models after confirming the company exists", async () => { + const ensureCompanyExists = vi.fn(() => null); + const { handler, getDisabledModels } = createReadHandlerHarness(ensureCompanyExists); + + const result = await handler(null as unknown as IpcMainInvokeEvent, { + companyId, + connectorId: "anthropic", + }); + + expect(result).toEqual({ + ok: true, + data: [{ id: "claude-sonnet-4-5", label: "Claude Sonnet 4.5" }], + }); + expect(ensureCompanyExists).toHaveBeenCalledWith(companyId); + expect(getDisabledModels).toHaveBeenCalledWith(companyId, "anthropic"); + }); + + it("returns the company boundary error before reading disabled models", async () => { + const companyError = { + ok: false, + error: { code: "COMPANY_NOT_FOUND", message: "Company not found." }, + } satisfies DesktopResult; + const ensureCompanyExists = vi.fn(() => companyError); + const { handler, getDisabledModels } = createReadHandlerHarness(ensureCompanyExists); + + const result = await handler(null as unknown as IpcMainInvokeEvent, { + companyId, + connectorId: "anthropic", + }); + + expect(result).toEqual(companyError); + expect(ensureCompanyExists).toHaveBeenCalledWith(companyId); + expect(getDisabledModels).not.toHaveBeenCalled(); + }); +});