From 13730189f20613718c52a146a9ee0edef0fa1e91 Mon Sep 17 00:00:00 2001 From: Jamie McGregor Nelson Date: Sun, 15 Mar 2026 10:26:10 -0400 Subject: [PATCH] test(claude-import): use portable fixtures for marketplace discovery tests --- .../gsd/tests/marketplace-discovery.test.ts | 51 ++++++++-------- src/tests/marketplace-discovery.test.ts | 60 +++++++++---------- 2 files changed, 52 insertions(+), 59 deletions(-) diff --git a/src/resources/extensions/gsd/tests/marketplace-discovery.test.ts b/src/resources/extensions/gsd/tests/marketplace-discovery.test.ts index f6bd91982..62fe8581c 100644 --- a/src/resources/extensions/gsd/tests/marketplace-discovery.test.ts +++ b/src/resources/extensions/gsd/tests/marketplace-discovery.test.ts @@ -16,23 +16,20 @@ import { discoverMarketplace, resolvePluginRoot } from '../marketplace-discovery'; +import { getMarketplaceFixtures } from './marketplace-test-fixtures.js'; -// Resolve paths relative to the gsd-2 project root so CI can skip cleanly when repos are absent -const GSD2_ROOT = path.resolve(import.meta.dirname, '../../../../..'); -const CLAUDE_SKILLS_PATH = path.resolve(GSD2_ROOT, '../claude_skills'); -const CLAUDE_PLUGINS_OFFICIAL_PATH = path.resolve(GSD2_ROOT, '../claude-plugins-official'); +const fixtureSetup = getMarketplaceFixtures(import.meta.dirname); +const fixtures = fixtureSetup.fixtures; +const CLAUDE_SKILLS_PATH = fixtures?.claudeSkillsPath; +const CLAUDE_PLUGINS_OFFICIAL_PATH = fixtures?.claudePluginsOfficialPath; -function marketplacesAvailable(): boolean { - return fs.existsSync(CLAUDE_SKILLS_PATH) && fs.existsSync(CLAUDE_PLUGINS_OFFICIAL_PATH); -} - -const skipReason = !marketplacesAvailable() - ? `Marketplace repos not found: ${CLAUDE_SKILLS_PATH}, ${CLAUDE_PLUGINS_OFFICIAL_PATH}` +const skipReason = !fixtureSetup.available + ? fixtureSetup.skipReason ?? 'Marketplace repos not found' : undefined; describe('parseMarketplaceJson', { skip: skipReason }, () => { it('should parse jamie-style marketplace.json', () => { - const result = parseMarketplaceJson(CLAUDE_SKILLS_PATH); + const result = parseMarketplaceJson(CLAUDE_SKILLS_PATH!); assert.strictEqual(result.success, true); if (result.success) { assert.strictEqual(result.manifest.name, 'jamie-bitflight-skills'); @@ -41,7 +38,7 @@ describe('parseMarketplaceJson', { skip: skipReason }, () => { }); it('should parse official-style marketplace.json', () => { - const result = parseMarketplaceJson(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = parseMarketplaceJson(CLAUDE_PLUGINS_OFFICIAL_PATH!); assert.strictEqual(result.success, true); if (result.success) { assert.strictEqual(result.manifest.name, 'claude-plugins-official'); @@ -74,29 +71,29 @@ describe('parseMarketplaceJson', { skip: skipReason }, () => { describe('resolvePluginRoot', () => { it('should resolve relative paths correctly', () => { - const result = resolvePluginRoot(CLAUDE_SKILLS_PATH, './plugins/python3-development'); - assert.strictEqual(result, path.join(CLAUDE_SKILLS_PATH, 'plugins/python3-development')); + const result = resolvePluginRoot(CLAUDE_SKILLS_PATH!, './plugins/python3-development'); + assert.strictEqual(result, path.join(CLAUDE_SKILLS_PATH!, 'plugins/python3-development')); }); it('should handle paths without ./ prefix', () => { - const result = resolvePluginRoot(CLAUDE_SKILLS_PATH, 'plugins/python3-development'); - assert.strictEqual(result, path.join(CLAUDE_SKILLS_PATH, 'plugins/python3-development')); + const result = resolvePluginRoot(CLAUDE_SKILLS_PATH!, 'plugins/python3-development'); + assert.strictEqual(result, path.join(CLAUDE_SKILLS_PATH!, 'plugins/python3-development')); }); it('should return null for external sources', () => { - const result = resolvePluginRoot(CLAUDE_SKILLS_PATH, 'https://github.com/example/plugin'); + const result = resolvePluginRoot(CLAUDE_SKILLS_PATH!, 'https://github.com/example/plugin'); assert.strictEqual(result, null); }); it('should return null for git sources', () => { - const result = resolvePluginRoot(CLAUDE_SKILLS_PATH, { source: 'github', repo: 'example/plugin' }); + const result = resolvePluginRoot(CLAUDE_SKILLS_PATH!, { source: 'github', repo: 'example/plugin' }); assert.strictEqual(result, null); }); }); describe('inspectPlugin', () => { it('should inspect a plugin with plugin.json', () => { - const pluginDir = path.join(CLAUDE_SKILLS_PATH, 'plugins/python3-development'); + const pluginDir = path.join(CLAUDE_SKILLS_PATH!, 'plugins/python3-development'); const result = inspectPlugin(pluginDir); assert.strictEqual(result.status, 'ok'); @@ -119,7 +116,7 @@ describe('inspectPlugin', () => { describe('discoverMarketplace', () => { it('should discover all plugins in jamie-style marketplace', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); assert.strictEqual(result.status, 'ok'); assert.strictEqual(result.pluginFormat, 'jamie-style'); @@ -132,7 +129,7 @@ describe('discoverMarketplace', () => { }); it('should discover all plugins in official-style marketplace', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); assert.strictEqual(result.status, 'ok'); assert.strictEqual(result.pluginFormat, 'official-style'); @@ -150,7 +147,7 @@ describe('discoverMarketplace', () => { }); it('should inventory skills, agents, commands correctly', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); const pythonPlugin = result.plugins.find(p => p.name === 'python3-development'); assert.ok(pythonPlugin !== undefined); @@ -162,7 +159,7 @@ describe('discoverMarketplace', () => { }); it('should discover MCP servers from plugin.json', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); const pythonPlugin = result.plugins.find(p => p.name === 'python3-development'); assert.ok(pythonPlugin !== undefined); @@ -172,7 +169,7 @@ describe('discoverMarketplace', () => { }); it('should discover LSP servers from marketplace.json', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); const tsPlugin = result.plugins.find(p => p.name === 'typescript-lsp'); assert.ok(tsPlugin !== undefined); @@ -182,7 +179,7 @@ describe('discoverMarketplace', () => { }); it('should detect external plugins correctly', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); const externalPlugin = result.plugins.find(p => p.name === 'atlassian'); assert.ok(externalPlugin !== undefined); @@ -195,8 +192,8 @@ describe('discoverMarketplace', () => { describe('smoke test', () => { it('should be able to run discovery from both marketplace repos', () => { - const jamieResult = discoverMarketplace(CLAUDE_SKILLS_PATH); - const officialResult = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const jamieResult = discoverMarketplace(CLAUDE_SKILLS_PATH!); + const officialResult = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); assert.strictEqual(jamieResult.status, 'ok'); assert.strictEqual(officialResult.status, 'ok'); diff --git a/src/tests/marketplace-discovery.test.ts b/src/tests/marketplace-discovery.test.ts index 52b403957..9bb981f09 100644 --- a/src/tests/marketplace-discovery.test.ts +++ b/src/tests/marketplace-discovery.test.ts @@ -20,25 +20,21 @@ import { discoverMarketplace, resolvePluginRoot } from '../resources/extensions/gsd/marketplace-discovery.js'; +import { getMarketplaceFixtures } from '../resources/extensions/gsd/tests/marketplace-test-fixtures.js'; -// Resolve paths to the external marketplace repos -// Tests run from src/tests/, so we need to go up to gsd-2, then into ../claude_skills -const REPOS_BASE = path.resolve(import.meta.dirname, '../../..'); -const CLAUDE_SKILLS_PATH = path.join(REPOS_BASE, 'claude_skills'); -const CLAUDE_PLUGINS_OFFICIAL_PATH = path.join(REPOS_BASE, 'claude-plugins-official'); +const fixtureSetup = getMarketplaceFixtures(import.meta.dirname); +const fixtures = fixtureSetup.fixtures; +const CLAUDE_SKILLS_PATH = fixtures?.claudeSkillsPath; +const CLAUDE_PLUGINS_OFFICIAL_PATH = fixtures?.claudePluginsOfficialPath; -function marketplacesAvailable(): boolean { - return fs.existsSync(CLAUDE_SKILLS_PATH) && fs.existsSync(CLAUDE_PLUGINS_OFFICIAL_PATH); -} - -const skipReason = !marketplacesAvailable() - ? `Marketplace repos not found: ${CLAUDE_SKILLS_PATH}, ${CLAUDE_PLUGINS_OFFICIAL_PATH}` +const skipReason = !fixtureSetup.available + ? fixtureSetup.skipReason ?? 'Marketplace repos not found' : undefined; describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { describe('claude_skills marketplace (jamie-style)', () => { it('should discover at least 15 plugins', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); assert.strictEqual(result.status, 'ok', `Expected ok status, got error: ${result.error}`); assert.ok(result.plugins.length >= 15, @@ -46,13 +42,13 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should detect jamie-style format', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); assert.strictEqual(result.pluginFormat, 'jamie-style'); }); it('should verify python3-development has skills and agents', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); const pythonPlugin = result.plugins.find(p => p.name === 'python3-development'); assert.ok(pythonPlugin, 'python3-development plugin should exist'); @@ -73,7 +69,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should verify all resolved paths exist on disk', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); // Filter plugins with resolved paths (local plugins, not external) const localPlugins = result.plugins.filter(p => p.resolvedPath !== null); @@ -87,7 +83,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should preserve canonical names for known plugins', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); const knownPluginNames = [ 'python3-development', 'bash-development', @@ -106,7 +102,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should have consistent summary counts', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); assert.strictEqual(result.summary.total, result.plugins.length, 'Total count should match plugins array length'); @@ -121,7 +117,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { describe('claude-plugins-official marketplace (official-style)', () => { it('should discover at least 10 plugins', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); assert.strictEqual(result.status, 'ok', `Expected ok status, got error: ${result.error}`); assert.ok(result.plugins.length >= 10, @@ -129,13 +125,13 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should detect official-style format', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); assert.strictEqual(result.pluginFormat, 'official-style'); }); it('should extract LSP servers from inline marketplace metadata', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); // TypeScript LSP plugin should have lspServers from marketplace.json const tsPlugin = result.plugins.find(p => p.name === 'typescript-lsp'); @@ -152,7 +148,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should have description from inline metadata', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); const tsPlugin = result.plugins.find(p => p.name === 'typescript-lsp'); assert.ok(tsPlugin, 'typescript-lsp plugin should exist'); @@ -162,7 +158,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should handle external plugins (URL sources) correctly', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); // Find plugins with URL sources (external) const externalPlugins = result.plugins.filter(p => p.resolvedPath === null); @@ -178,7 +174,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should preserve canonical names for known official plugins', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); const knownPluginNames = [ 'typescript-lsp', 'pyright-lsp', @@ -197,7 +193,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should extract multiple LSP server types', () => { - const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const result = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); // Check that multiple LSP plugins have their servers extracted const lspPlugins = [ @@ -313,7 +309,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { describe('Component inventory accuracy', () => { it('should accurately count skills in python3-development', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); const pythonPlugin = result.plugins.find(p => p.name === 'python3-development'); assert.ok(pythonPlugin, 'python3-development should exist'); @@ -334,7 +330,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should discover MCP servers from plugin.json', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); const pythonPlugin = result.plugins.find(p => p.name === 'python3-development'); assert.ok(pythonPlugin, 'python3-development should exist'); @@ -343,7 +339,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should include commands in inventory when present', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); const pythonPlugin = result.plugins.find(p => p.name === 'python3-development'); assert.ok(pythonPlugin, 'python3-development should exist'); @@ -352,7 +348,7 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should detect hooks when present', () => { - const result = discoverMarketplace(CLAUDE_SKILLS_PATH); + const result = discoverMarketplace(CLAUDE_SKILLS_PATH!); // Find any plugin with hooks const pluginWithHooks = result.plugins.find(p => @@ -367,8 +363,8 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { describe('Cross-marketplace consistency', () => { it('should return consistent type structure for both marketplaces', () => { - const jamie = discoverMarketplace(CLAUDE_SKILLS_PATH); - const official = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const jamie = discoverMarketplace(CLAUDE_SKILLS_PATH!); + const official = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); // Both should have the same top-level structure const requiredKeys = ['status', 'marketplacePath', 'marketplaceName', @@ -388,8 +384,8 @@ describe('Marketplace Discovery Contract Tests', { skip: skipReason }, () => { }); it('should return consistent plugin structure', () => { - const jamie = discoverMarketplace(CLAUDE_SKILLS_PATH); - const official = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH); + const jamie = discoverMarketplace(CLAUDE_SKILLS_PATH!); + const official = discoverMarketplace(CLAUDE_PLUGINS_OFFICIAL_PATH!); const jamiePlugin = jamie.plugins[0]; const officialPlugin = official.plugins[0];