Files
kandev/apps/web/lib/plugins/registry.integration-settings.test.ts
T

165 lines
5.9 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { pluginRegistry } from "./registry";
const PRIMARY_PLUGIN_ID = "plugin-a";
const SECONDARY_PLUGIN_ID = "plugin-b";
const SOURCE_CONTROL_PROVIDER_ID = "source-control";
const SECOND_SOURCE_CONTROL_PROVIDER_ID = "source-control-secondary";
const WORKSPACE_ID = "workspace-1";
const OTHER_WORKSPACE_ID = "workspace-2";
const registration = {
id: SOURCE_CONTROL_PROVIDER_ID,
label: "Source Control",
description: "Configure source control.",
Component: () => null,
};
function setIntegrationEnabled(
pluginId: string,
integrationId: string,
workspaceId: string,
enabled: boolean,
): void {
(
pluginRegistry.setIntegrationEnabled.bind(pluginRegistry) as unknown as (
pluginId: string,
integrationId: string,
workspaceId: string,
enabled: boolean,
) => void
)(pluginId, integrationId, workspaceId, enabled);
}
function isIntegrationEnabled(integrationId: string, workspaceId: string): boolean {
return (
pluginRegistry.isIntegrationEnabled.bind(pluginRegistry) as unknown as (
integrationId: string,
workspaceId: string,
) => boolean
)(integrationId, workspaceId);
}
describe("pluginRegistry — integration settings", () => {
afterEach(() => {
pluginRegistry.unregisterPlugin(PRIMARY_PLUGIN_ID);
pluginRegistry.unregisterPlugin(SECONDARY_PLUGIN_ID);
});
it("exposes native integration settings registration to scoped plugins", () => {
const scoped = pluginRegistry.forPlugin(PRIMARY_PLUGIN_ID);
expect("registerIntegrationSettings" in scoped).toBe(true);
});
it("keeps a contribution with its active plugin owner", () => {
function Settings() {
return null;
}
pluginRegistry.forPlugin(PRIMARY_PLUGIN_ID).registerIntegrationSettings({
...registration,
icon: "cloud",
Component: Settings,
});
expect(pluginRegistry.getIntegrationSetting(SOURCE_CONTROL_PROVIDER_ID)).toEqual({
pluginId: PRIMARY_PLUGIN_ID,
...registration,
icon: "cloud",
Component: Settings,
});
});
it("rejects duplicate active ownership without replacing the owner", () => {
pluginRegistry.forPlugin(PRIMARY_PLUGIN_ID).registerIntegrationSettings(registration);
expect(() =>
pluginRegistry.forPlugin(SECONDARY_PLUGIN_ID).registerIntegrationSettings(registration),
).toThrow(
`integration settings "${SOURCE_CONTROL_PROVIDER_ID}" is already owned by "${PRIMARY_PLUGIN_ID}"`,
);
expect(pluginRegistry.getIntegrationSetting(SOURCE_CONTROL_PROVIDER_ID)?.pluginId).toBe(
PRIMARY_PLUGIN_ID,
);
});
it("rejects core integration IDs and path-unsafe IDs", () => {
const scoped = pluginRegistry.forPlugin(PRIMARY_PLUGIN_ID);
expect(() => scoped.registerIntegrationSettings({ ...registration, id: "github" })).toThrow(
'integration settings id "github" is reserved by the host',
);
expect(() =>
scoped.registerIntegrationSettings({ ...registration, id: "unsafe/path" }),
).toThrow('integration settings id "unsafe/path" must be a URL-safe slug');
});
it("revokes on unload and lets a successor claim the id", () => {
pluginRegistry.forPlugin(PRIMARY_PLUGIN_ID).registerIntegrationSettings(registration);
pluginRegistry.unregisterPlugin(PRIMARY_PLUGIN_ID);
pluginRegistry.forPlugin(SECONDARY_PLUGIN_ID).registerIntegrationSettings(registration);
expect(pluginRegistry.getIntegrationSetting(SOURCE_CONTROL_PROVIDER_ID)?.pluginId).toBe(
SECONDARY_PLUGIN_ID,
);
});
it("keeps enabled state independent for each registration and workspace", () => {
pluginRegistry.forPlugin(PRIMARY_PLUGIN_ID).registerIntegrationSettings(registration);
pluginRegistry.forPlugin(PRIMARY_PLUGIN_ID).registerIntegrationSettings({
...registration,
id: SECOND_SOURCE_CONTROL_PROVIDER_ID,
});
setIntegrationEnabled(PRIMARY_PLUGIN_ID, SOURCE_CONTROL_PROVIDER_ID, WORKSPACE_ID, true);
setIntegrationEnabled(
PRIMARY_PLUGIN_ID,
SECOND_SOURCE_CONTROL_PROVIDER_ID,
OTHER_WORKSPACE_ID,
true,
);
expect(isIntegrationEnabled(SOURCE_CONTROL_PROVIDER_ID, WORKSPACE_ID)).toBe(true);
expect(isIntegrationEnabled(SOURCE_CONTROL_PROVIDER_ID, OTHER_WORKSPACE_ID)).toBe(false);
expect(isIntegrationEnabled(SECOND_SOURCE_CONTROL_PROVIDER_ID, WORKSPACE_ID)).toBe(false);
expect(isIntegrationEnabled(SECOND_SOURCE_CONTROL_PROVIDER_ID, OTHER_WORKSPACE_ID)).toBe(true);
});
it("rejects state updates for an integration owned by another plugin", () => {
pluginRegistry.forPlugin(PRIMARY_PLUGIN_ID).registerIntegrationSettings(registration);
pluginRegistry.forPlugin(SECONDARY_PLUGIN_ID).registerIntegrationSettings({
...registration,
id: SECOND_SOURCE_CONTROL_PROVIDER_ID,
});
const listener = vi.fn();
const unsubscribe = pluginRegistry.subscribe(listener);
setIntegrationEnabled(PRIMARY_PLUGIN_ID, SECOND_SOURCE_CONTROL_PROVIDER_ID, WORKSPACE_ID, true);
expect(listener).not.toHaveBeenCalled();
expect(isIntegrationEnabled(SECOND_SOURCE_CONTROL_PROVIDER_ID, WORKSPACE_ID)).toBe(false);
unsubscribe();
});
it("suppresses unchanged notifications and clears state on unload", () => {
pluginRegistry.forPlugin(PRIMARY_PLUGIN_ID).registerIntegrationSettings(registration);
const listener = vi.fn();
const unsubscribe = pluginRegistry.subscribe(listener);
setIntegrationEnabled(PRIMARY_PLUGIN_ID, SOURCE_CONTROL_PROVIDER_ID, WORKSPACE_ID, true);
setIntegrationEnabled(PRIMARY_PLUGIN_ID, SOURCE_CONTROL_PROVIDER_ID, WORKSPACE_ID, true);
expect(listener).toHaveBeenCalledTimes(1);
expect(isIntegrationEnabled(SOURCE_CONTROL_PROVIDER_ID, WORKSPACE_ID)).toBe(true);
pluginRegistry.unregisterPlugin(PRIMARY_PLUGIN_ID);
expect(isIntegrationEnabled(SOURCE_CONTROL_PROVIDER_ID, WORKSPACE_ID)).toBe(false);
expect(listener).toHaveBeenCalledTimes(2);
unsubscribe();
});
});