import { describe, expect, it } from "vitest"; import { normalizePluginsConfig, resolveEffectiveEnableState, resolveEnableState, resolveMemorySlotDecision, } from "./config-state.js"; function normalizeVoiceCallEntry(entry: Record) { return normalizePluginsConfig({ entries: { "voice-call": entry, }, }).entries["voice-call"]; } function expectResolvedEnableState( params: Parameters, expected: ReturnType, ) { expect(resolveEnableState(...params)).toEqual(expected); } function expectNormalizedEnableState(params: { id: string; origin: "bundled" | "workspace"; config: Record; manifestEnabledByDefault?: boolean; expected: ReturnType; }) { expectResolvedEnableState( [ params.id, params.origin, normalizePluginsConfig(params.config), params.manifestEnabledByDefault, ], params.expected, ); } describe("normalizePluginsConfig", () => { it.each([ [{}, "memory-core"], [{ slots: { memory: "custom-memory" } }, "custom-memory"], [{ slots: { memory: "none" } }, null], [{ slots: { memory: "None" } }, null], [{ slots: { memory: " custom-memory " } }, "custom-memory"], [{ slots: { memory: "" } }, "memory-core"], [{ slots: { memory: " " } }, "memory-core"], ] as const)("normalizes memory slot for %o", (config, expected) => { expect(normalizePluginsConfig(config).slots.memory).toBe(expected); }); it.each([ { name: "normalizes plugin hook policy flags", entry: { hooks: { allowPromptInjection: false, }, }, expectedHooks: { allowPromptInjection: false, }, }, { name: "drops invalid plugin hook policy values", entry: { hooks: { allowPromptInjection: "nope", } as unknown as { allowPromptInjection: boolean }, }, expectedHooks: undefined, }, ] as const)("$name", ({ entry, expectedHooks }) => { expect(normalizeVoiceCallEntry(entry)?.hooks).toEqual(expectedHooks); }); it.each([ { name: "normalizes plugin subagent override policy settings", subagent: { allowModelOverride: true, allowedModels: [" anthropic/claude-sonnet-4-6 ", "", "openai/gpt-5.4"], }, expected: { allowModelOverride: true, hasAllowedModelsConfig: true, allowedModels: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4"], }, }, { name: "preserves explicit subagent allowlist intent even when all entries are invalid", subagent: { allowModelOverride: true, allowedModels: [42, null, "anthropic"], } as unknown as { allowModelOverride: boolean; allowedModels: string[] }, expected: { allowModelOverride: true, hasAllowedModelsConfig: true, allowedModels: ["anthropic"], }, }, { name: "keeps explicit invalid subagent allowlist config visible to callers", subagent: { allowModelOverride: "nope", allowedModels: [42, null], } as unknown as { allowModelOverride: boolean; allowedModels: string[] }, expected: { hasAllowedModelsConfig: true, }, }, ] as const)("$name", ({ subagent, expected }) => { expect(normalizeVoiceCallEntry({ subagent })?.subagent).toEqual(expected); }); it("normalizes legacy plugin ids to their merged bundled plugin id", () => { const result = normalizePluginsConfig({ allow: ["openai-codex", "google-gemini-cli", "minimax-portal-auth"], deny: ["openai-codex", "google-gemini-cli", "minimax-portal-auth"], entries: { "openai-codex": { enabled: true, }, "google-gemini-cli": { enabled: true, }, "minimax-portal-auth": { enabled: false, }, }, }); expect(result.allow).toEqual(["openai", "google", "minimax"]); expect(result.deny).toEqual(["openai", "google", "minimax"]); expect(result.entries.openai?.enabled).toBe(true); expect(result.entries.google?.enabled).toBe(true); expect(result.entries.minimax?.enabled).toBe(false); }); }); describe("resolveEffectiveEnableState", () => { function resolveBundledTelegramState(config: Parameters[0]) { const normalized = normalizePluginsConfig(config); return resolveEffectiveEnableState({ id: "telegram", origin: "bundled", config: normalized, rootConfig: { channels: { telegram: { enabled: true, }, }, }, }); } it.each([ [{ enabled: true }, { enabled: true }], [ { enabled: true, entries: { telegram: { enabled: false, }, }, }, { enabled: false, reason: "disabled in config" }, ], ] as const)("resolves bundled telegram state for %o", (config, expected) => { expect(resolveBundledTelegramState(config)).toEqual(expected); }); }); describe("resolveEnableState", () => { it.each([ [ "openai", "bundled", normalizePluginsConfig({}), undefined, { enabled: false, reason: "bundled (disabled by default)" }, ], ["openai", "bundled", normalizePluginsConfig({}), true, { enabled: true }], ["google", "bundled", normalizePluginsConfig({}), true, { enabled: true }], ["profile-aware", "bundled", normalizePluginsConfig({}), true, { enabled: true }], ] as const)( "resolves %s enable state for origin=%s manifestEnabledByDefault=%s", (id, origin, config, manifestEnabledByDefault, expected) => { expectResolvedEnableState([id, origin, config, manifestEnabledByDefault], expected); }, ); it.each([ { name: "keeps the selected memory slot plugin enabled even when omitted from plugins.allow", config: { allow: ["telegram"], slots: { memory: "memory-core" }, }, expected: { enabled: true }, }, { name: "keeps explicit disable authoritative for the selected memory slot plugin", config: { allow: ["telegram"], slots: { memory: "memory-core" }, entries: { "memory-core": { enabled: false, }, }, }, expected: { enabled: false, reason: "disabled in config" }, }, ] as const)("$name", ({ config, expected }) => { expectNormalizedEnableState({ id: "memory-core", origin: "bundled", config, expected, }); }); it.each([ [ normalizePluginsConfig({}), { enabled: false, reason: "workspace plugin (disabled by default)", }, ], [ normalizePluginsConfig({ allow: ["workspace-helper"], }), { enabled: true }, ], [ normalizePluginsConfig({ entries: { "workspace-helper": { enabled: true, }, }, }), { enabled: true }, ], ] as const)("resolves workspace-helper enable state for %o", (config, expected) => { expect(resolveEnableState("workspace-helper", "workspace", config)).toEqual(expected); }); it("does not let the default memory slot auto-enable an untrusted workspace plugin", () => { expectNormalizedEnableState({ id: "memory-core", origin: "workspace", config: { slots: { memory: "memory-core" }, }, expected: { enabled: false, reason: "workspace plugin (disabled by default)", }, }); }); }); describe("resolveMemorySlotDecision", () => { it("disables a memory-only plugin when slot points elsewhere", () => { const result = resolveMemorySlotDecision({ id: "old-memory", kind: "memory", slot: "new-memory", selectedId: null, }); expect(result.enabled).toBe(false); }); it("keeps a dual-kind plugin enabled when memory slot points elsewhere", () => { const result = resolveMemorySlotDecision({ id: "dual-plugin", kind: ["memory", "context-engine"], slot: "new-memory", selectedId: null, }); expect(result.enabled).toBe(true); expect(result.selected).toBeUndefined(); }); it("selects a dual-kind plugin when it owns the memory slot", () => { const result = resolveMemorySlotDecision({ id: "dual-plugin", kind: ["memory", "context-engine"], slot: "dual-plugin", selectedId: null, }); expect(result.enabled).toBe(true); expect(result.selected).toBe(true); }); it("keeps a dual-kind plugin enabled when memory slot is null", () => { const result = resolveMemorySlotDecision({ id: "dual-plugin", kind: ["memory", "context-engine"], slot: null, selectedId: null, }); expect(result.enabled).toBe(true); }); it("disables a memory-only plugin when memory slot is null", () => { const result = resolveMemorySlotDecision({ id: "old-memory", kind: "memory", slot: null, selectedId: null, }); expect(result.enabled).toBe(false); }); });