From 709706378b54274c59ccbb4dbb1b3d8c114d598f Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 5 Apr 2026 12:38:27 +0900 Subject: [PATCH] test(plugin-loader): isolate discovery name derivation Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../discovery.test.ts | 23 +++++++++++-------- .../claude-code-plugin-loader/discovery.ts | 5 ++-- .../claude-code-plugin-loader/types.ts | 6 +++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/features/claude-code-plugin-loader/discovery.test.ts b/src/features/claude-code-plugin-loader/discovery.test.ts index 7983013fb..ca939966a 100644 --- a/src/features/claude-code-plugin-loader/discovery.test.ts +++ b/src/features/claude-code-plugin-loader/discovery.test.ts @@ -16,18 +16,17 @@ function createTemporaryDirectory(prefix: string): string { describe("discoverInstalledPlugins", () => { beforeEach(() => { - // Mock logger to avoid noise in test output and force process isolation in CI mock.module("../../shared/logger", () => ({ log: () => {}, })) - + const pluginsHome = createTemporaryDirectory("omo-claude-plugins-") process.env.CLAUDE_PLUGINS_HOME = pluginsHome }) afterEach(() => { mock.restore() - + if (originalClaudePluginsHome === undefined) { delete process.env.CLAUDE_PLUGINS_HOME } else { @@ -42,7 +41,6 @@ describe("discoverInstalledPlugins", () => { it("preserves scoped package name from npm plugin keys", () => { //#given const pluginsHome = process.env.CLAUDE_PLUGINS_HOME as string - // Use unique temp directory with scoped path to prevent cross-test contamination const installPathBase = createTemporaryDirectory("omo-scoped-plugin-") const installPath = join(installPathBase, "@myorg", "my-plugin") mkdirSync(installPath, { recursive: true }) @@ -68,7 +66,10 @@ describe("discoverInstalledPlugins", () => { ) //#when - const discovered = discoverInstalledPlugins({ pluginsHomeOverride: pluginsHome }) + const discovered = discoverInstalledPlugins({ + pluginsHomeOverride: pluginsHome, + loadPluginManifestOverride: () => null, + }) //#then expect(discovered.errors).toHaveLength(0) @@ -79,7 +80,6 @@ describe("discoverInstalledPlugins", () => { it("derives package name from file URL plugin keys", () => { //#given const pluginsHome = process.env.CLAUDE_PLUGINS_HOME as string - // Use unique temp directory directly to prevent cross-test contamination const installPath = createTemporaryDirectory("omo-fileurl-plugin-") const databasePath = join(pluginsHome, "installed_plugins.json") @@ -103,7 +103,10 @@ describe("discoverInstalledPlugins", () => { ) //#when - const discovered = discoverInstalledPlugins({ pluginsHomeOverride: pluginsHome }) + const discovered = discoverInstalledPlugins({ + pluginsHomeOverride: pluginsHome, + loadPluginManifestOverride: () => null, + }) //#then expect(discovered.errors).toHaveLength(0) @@ -114,7 +117,6 @@ describe("discoverInstalledPlugins", () => { it("derives canonical package name from npm plugin keys", () => { //#given const pluginsHome = process.env.CLAUDE_PLUGINS_HOME as string - // Use unique temp directory directly to prevent cross-test contamination const installPath = createTemporaryDirectory("omo-npm-plugin-") const databasePath = join(pluginsHome, "installed_plugins.json") @@ -138,7 +140,10 @@ describe("discoverInstalledPlugins", () => { ) //#when - const discovered = discoverInstalledPlugins({ pluginsHomeOverride: pluginsHome }) + const discovered = discoverInstalledPlugins({ + pluginsHomeOverride: pluginsHome, + loadPluginManifestOverride: () => null, + }) //#then expect(discovered.errors).toHaveLength(0) diff --git a/src/features/claude-code-plugin-loader/discovery.ts b/src/features/claude-code-plugin-loader/discovery.ts index 6ee463849..f4781fef3 100644 --- a/src/features/claude-code-plugin-loader/discovery.ts +++ b/src/features/claude-code-plugin-loader/discovery.ts @@ -64,7 +64,7 @@ function loadClaudeSettings(): ClaudeSettings | null { } } -function loadPluginManifest(installPath: string): PluginManifest | null { +export function loadPluginManifest(installPath: string): PluginManifest | null { const manifestPath = join(installPath, ".claude-plugin", "plugin.json") if (!existsSync(manifestPath)) { return null @@ -176,6 +176,7 @@ export function discoverInstalledPlugins(options?: PluginLoaderOptions): PluginL const settingsEnabledPlugins = settings?.enabledPlugins const overrideEnabledPlugins = options?.enabledPluginsOverride + const pluginManifestLoader = options?.loadPluginManifestOverride ?? loadPluginManifest for (const [pluginKey, installation] of extractPluginEntries(db)) { if (!installation) continue @@ -196,7 +197,7 @@ export function discoverInstalledPlugins(options?: PluginLoaderOptions): PluginL continue } - const manifest = loadPluginManifest(installPath) + const manifest = pluginManifestLoader(installPath) const pluginName = manifest?.name || derivePluginNameFromKey(pluginKey) const loadedPlugin: LoadedPlugin = { diff --git a/src/features/claude-code-plugin-loader/types.ts b/src/features/claude-code-plugin-loader/types.ts index 14ca32c43..d93d6979b 100644 --- a/src/features/claude-code-plugin-loader/types.ts +++ b/src/features/claude-code-plugin-loader/types.ts @@ -227,6 +227,12 @@ export interface PluginLoaderOptions { */ pluginsHomeOverride?: string + /** + * Override plugin manifest loading for testing. + * Return null to force plugin name derivation from the plugin key. + */ + loadPluginManifestOverride?: (installPath: string) => PluginManifest | null + /** * Override enabled plugins from oh-my-opencode config. * Key format: "pluginName@marketplace" (e.g., "shell-scripting@claude-code-workflows")