diff --git a/server/src/__tests__/plugin-host-services-config-scope.test.ts b/server/src/__tests__/plugin-host-services-config-scope.test.ts new file mode 100644 index 00000000..28518b4f --- /dev/null +++ b/server/src/__tests__/plugin-host-services-config-scope.test.ts @@ -0,0 +1,76 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + getConfig: vi.fn(), + getConfigExactScope: vi.fn(), +})); + +vi.mock("../services/plugin-registry.js", () => ({ + pluginRegistryService: () => ({ + getConfig: mocks.getConfig, + getConfigExactScope: mocks.getConfigExactScope, + }), +})); + +import { buildHostServices } from "../services/plugin-host-services.js"; + +function createEventBusStub() { + return { + forPlugin() { + return { + emit: vi.fn(), + subscribe: vi.fn(), + clear: vi.fn(), + }; + }, + } as any; +} + +describe("plugin host services config scoping", () => { + beforeEach(() => { + mocks.getConfig.mockReset(); + mocks.getConfigExactScope.mockReset(); + }); + + it("does not fall back to legacy global config for company-scoped reads", async () => { + mocks.getConfigExactScope.mockResolvedValue(null); + mocks.getConfig.mockResolvedValue({ + configJson: { tokenRef: "global-token-ref" }, + }); + + const services = buildHostServices( + {} as never, + "plugin-record-id", + "paperclip.example", + createEventBusStub(), + ); + + await expect( + services.config.get({ companyId: "company-a" }), + ).resolves.toEqual({}); + expect(mocks.getConfigExactScope).toHaveBeenCalledWith("plugin-record-id", "company-a"); + expect(mocks.getConfig).not.toHaveBeenCalled(); + + services.dispose(); + }); + + it("still reads the exact global row for explicit global requests", async () => { + mocks.getConfigExactScope.mockResolvedValue({ + configJson: { tokenRef: "global-token-ref" }, + }); + + const services = buildHostServices( + {} as never, + "plugin-record-id", + "paperclip.example", + createEventBusStub(), + ); + + await expect( + services.config.get({ companyId: null }), + ).resolves.toEqual({ tokenRef: "global-token-ref" }); + expect(mocks.getConfigExactScope).toHaveBeenCalledWith("plugin-record-id", null); + + services.dispose(); + }); +}); diff --git a/server/src/services/plugin-host-services.ts b/server/src/services/plugin-host-services.ts index 4ea90454..e76151cf 100644 --- a/server/src/services/plugin-host-services.ts +++ b/server/src/services/plugin-host-services.ts @@ -1054,7 +1054,10 @@ export function buildHostServices( return { config: { async get(params) { - const configRow = await registry.getConfig(pluginId, params?.companyId ?? null); + const companyId = Object.prototype.hasOwnProperty.call(params ?? {}, "companyId") + ? params.companyId ?? null + : null; + const configRow = await registry.getConfigExactScope(pluginId, companyId); return configRow?.configJson ?? {}; }, },