Skip to main content
Glama
index.test.ts10 kB
/** * Cookbook Tools Tests * * Tests for cookbook MCP tools. * * @package WP_Navigator_MCP * @since 2.1.0 */ import { describe, it, expect, beforeAll, vi } from 'vitest'; import { toolRegistry, ToolCategory } from '../../tool-registry/index.js'; import { registerCookbookTools } from './index.js'; import type { ToolExecutionContext } from '../../tool-registry/types.js'; // Register tools once before all tests beforeAll(() => { // Clear registry and register fresh registerCookbookTools(); }); // ============================================================================= // Mock Context // ============================================================================= const createMockContext = (wpRequestMock?: any): ToolExecutionContext => ({ wpRequest: wpRequestMock || vi.fn(), config: { baseUrl: 'https://test.local', restApi: 'https://test.local/wp-json', wpnavBase: 'https://test.local/wp-json/wpnav/v1', wpnavIntrospect: 'https://test.local/wp-json/wpnav/v1/introspect', toggles: { enableWrites: false, toolTimeoutMs: 60000, }, }, logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), }, clampText: (text: string) => text, }); // ============================================================================= // Tool Registration Tests // ============================================================================= describe('Cookbook Tools Registration', () => { it('should register wpnav_list_cookbooks tool', () => { const tool = toolRegistry.getTool('wpnav_list_cookbooks'); expect(tool).toBeDefined(); expect(tool?.category).toBe(ToolCategory.COOKBOOK); }); it('should register wpnav_load_cookbook tool', () => { const tool = toolRegistry.getTool('wpnav_load_cookbook'); expect(tool).toBeDefined(); expect(tool?.category).toBe(ToolCategory.COOKBOOK); }); it('should register wpnav_match_cookbooks tool', () => { const tool = toolRegistry.getTool('wpnav_match_cookbooks'); expect(tool).toBeDefined(); expect(tool?.category).toBe(ToolCategory.COOKBOOK); }); }); // ============================================================================= // wpnav_list_cookbooks Tests // ============================================================================= describe('wpnav_list_cookbooks', () => { it('should return list of bundled cookbooks', async () => { const tool = toolRegistry.getTool('wpnav_list_cookbooks'); expect(tool).toBeDefined(); const result = await tool!.handler({}, createMockContext()); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); const data = JSON.parse(result.content[0].text!); expect(data.total).toBeGreaterThanOrEqual(2); // gutenberg + elementor expect(data.cookbooks).toBeInstanceOf(Array); expect(data.sources).toHaveProperty('bundled'); expect(data.sources).toHaveProperty('project'); // Check for bundled cookbooks const slugs = data.cookbooks.map((c: any) => c.slug); expect(slugs).toContain('gutenberg'); expect(slugs).toContain('elementor'); }); it('should include cookbook metadata', async () => { const tool = toolRegistry.getTool('wpnav_list_cookbooks'); const result = await tool!.handler({}, createMockContext()); const data = JSON.parse(result.content[0].text!); const gutenberg = data.cookbooks.find((c: any) => c.slug === 'gutenberg'); expect(gutenberg).toBeDefined(); expect(gutenberg.source).toBe('bundled'); expect(gutenberg.has_skill_body).toBe(true); expect(gutenberg.allowed_tools_count).toBeGreaterThan(0); }); it('should include description and requires_wpnav_pro fields', async () => { const tool = toolRegistry.getTool('wpnav_list_cookbooks'); const result = await tool!.handler({}, createMockContext()); const data = JSON.parse(result.content[0].text!); // Gutenberg should have description but not requires_wpnav_pro const gutenberg = data.cookbooks.find((c: any) => c.slug === 'gutenberg'); expect(gutenberg).toBeDefined(); expect(gutenberg.description).toBeTruthy(); expect(gutenberg.description).toContain('Block Editor'); expect(gutenberg.requires_wpnav_pro).toBeNull(); // Elementor has requires-wpnav-pro in its frontmatter const elementor = data.cookbooks.find((c: any) => c.slug === 'elementor'); expect(elementor).toBeDefined(); expect(elementor.description).toBeTruthy(); // Elementor requires wpnav-pro according to the SKILL.md file expect(elementor.requires_wpnav_pro).toBeDefined(); }); }); // ============================================================================= // wpnav_load_cookbook Tests // ============================================================================= describe('wpnav_load_cookbook', () => { it('should return full cookbook content for valid slug', async () => { const tool = toolRegistry.getTool('wpnav_load_cookbook'); expect(tool).toBeDefined(); const result = await tool!.handler({ slug: 'gutenberg' }, createMockContext()); expect(result.isError).toBeFalsy(); expect(result.content).toHaveLength(1); const data = JSON.parse(result.content[0].text!); expect(data.slug).toBe('gutenberg'); expect(data.name).toBeDefined(); expect(data.skill_body).toBeTruthy(); expect(data.allowed_tools).toBeInstanceOf(Array); expect(data.allowed_tools.length).toBeGreaterThan(0); }); it('should return error for invalid slug', async () => { const tool = toolRegistry.getTool('wpnav_load_cookbook'); const result = await tool!.handler({ slug: 'nonexistent-plugin' }, createMockContext()); expect(result.isError).toBe(true); const data = JSON.parse(result.content[0].text!); expect(data.error).toContain('Cookbook not found'); expect(data.hint).toBeDefined(); }); it('should return error for missing slug parameter', async () => { const tool = toolRegistry.getTool('wpnav_load_cookbook'); const result = await tool!.handler({}, createMockContext()); expect(result.isError).toBe(true); const data = JSON.parse(result.content[0].text!); expect(data.error).toContain('Missing required parameter'); }); it('should return elementor cookbook', async () => { const tool = toolRegistry.getTool('wpnav_load_cookbook'); const result = await tool!.handler({ slug: 'elementor' }, createMockContext()); expect(result.isError).toBeFalsy(); const data = JSON.parse(result.content[0].text!); expect(data.slug).toBe('elementor'); expect(data.plugin.min_version).toBeDefined(); expect(data.skill_body).toBeTruthy(); }); }); // ============================================================================= // wpnav_match_cookbooks Tests // ============================================================================= describe('wpnav_match_cookbooks', () => { it('should match provided plugins to cookbooks', async () => { const tool = toolRegistry.getTool('wpnav_match_cookbooks'); expect(tool).toBeDefined(); const plugins = [ { slug: 'gutenberg', version: '17.0.0' }, { slug: 'elementor', version: '3.21.0' }, { slug: 'woocommerce', version: '8.0.0' }, // No cookbook for this ]; const result = await tool!.handler({ plugins }, createMockContext()); expect(result.isError).toBeFalsy(); const data = JSON.parse(result.content[0].text!); expect(data.total_plugins).toBe(3); expect(data.matched_cookbooks).toBe(2); expect(data.matches).toHaveLength(2); expect(data.unmatched_plugins).toContain('woocommerce'); // Check matched cookbook details const gutenbergMatch = data.matches.find((m: any) => m.plugin.slug === 'gutenberg'); expect(gutenbergMatch).toBeDefined(); expect(gutenbergMatch.compatible).toBe(true); expect(gutenbergMatch.has_skill_body).toBe(true); expect(gutenbergMatch.allowed_tools).toBeInstanceOf(Array); }); it('should mark incompatible versions', async () => { const tool = toolRegistry.getTool('wpnav_match_cookbooks'); const plugins = [ { slug: 'elementor', version: '2.0.0' }, // Below min version ]; const result = await tool!.handler({ plugins }, createMockContext()); const data = JSON.parse(result.content[0].text!); expect(data.matches).toHaveLength(1); expect(data.matches[0].compatible).toBe(false); expect(data.matches[0].reason).toBeTruthy(); }); it('should fetch plugins from WordPress when not provided', async () => { const mockWpRequest = vi.fn().mockResolvedValue([ { plugin: 'gutenberg/gutenberg.php', version: '17.0.0', status: 'active', name: 'Gutenberg', }, ]); const tool = toolRegistry.getTool('wpnav_match_cookbooks'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); expect(mockWpRequest).toHaveBeenCalledWith('/wp/v2/plugins?status=active'); const data = JSON.parse(result.content[0].text!); expect(data.total_plugins).toBe(1); }); it('should handle WordPress API errors gracefully', async () => { const mockWpRequest = vi.fn().mockRejectedValue(new Error('Connection failed')); const tool = toolRegistry.getTool('wpnav_match_cookbooks'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); expect(result.isError).toBe(true); const data = JSON.parse(result.content[0].text!); expect(data.error).toContain('Failed to fetch plugins'); expect(data.hint).toBeDefined(); }); it('should return available cookbooks info', async () => { const tool = toolRegistry.getTool('wpnav_match_cookbooks'); const result = await tool!.handler({ plugins: [] }, createMockContext()); const data = JSON.parse(result.content[0].text!); expect(data.available_cookbooks).toBeDefined(); expect(data.available_cookbooks.bundled).toContain('gutenberg'); expect(data.available_cookbooks.bundled).toContain('elementor'); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/littlebearapps/wp-navigator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server