Skip to main content
Glama
index.test.ts19.2 kB
/** * Core Tools Tests * * Tests for core MCP tools (introspect, help, site_overview). * * @package WP_Navigator_MCP * @since 2.6.0 */ import { describe, it, expect, beforeAll, vi } from 'vitest'; import { toolRegistry, ToolCategory } from '../../tool-registry/index.js'; import { registerCoreTools } from './index.js'; import type { ToolExecutionContext } from '../../tool-registry/types.js'; // Register tools once before all tests beforeAll(() => { registerCoreTools(); }); // ============================================================================= // 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('Core Tools Registration', () => { it('should register wpnav_introspect tool', () => { const tool = toolRegistry.getTool('wpnav_introspect'); expect(tool).toBeDefined(); expect(tool?.category).toBe(ToolCategory.CORE); }); it('should register wpnav_help tool', () => { const tool = toolRegistry.getTool('wpnav_help'); expect(tool).toBeDefined(); expect(tool?.category).toBe(ToolCategory.CORE); }); it('should register wpnav_get_site_overview tool', () => { const tool = toolRegistry.getTool('wpnav_get_site_overview'); expect(tool).toBeDefined(); expect(tool?.category).toBe(ToolCategory.CORE); }); it('should register wpnav_list_post_types tool', () => { const tool = toolRegistry.getTool('wpnav_list_post_types'); expect(tool).toBeDefined(); expect(tool?.category).toBe(ToolCategory.CORE); }); }); // ============================================================================= // wpnav_introspect Tests // ============================================================================= describe('wpnav_introspect', () => { it('should return introspect data with available_cookbooks', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0', name: 'WP Navigator Pro' }, capabilities: ['read', 'write'], }); } if (url.includes('plugins')) { return Promise.resolve([ { plugin: 'gutenberg/gutenberg.php', version: '17.0.0', status: 'active' }, ]); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); expect(tool).toBeDefined(); const result = await tool!.handler({}, createMockContext(mockWpRequest)); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); const data = JSON.parse(result.content[0].text!); // Should have original introspect data expect(data.plugin).toBeDefined(); expect(data.plugin.version).toBe('1.5.0'); // Should have available_cookbooks array expect(data.available_cookbooks).toBeInstanceOf(Array); expect(data.available_cookbooks.length).toBeGreaterThanOrEqual(2); // gutenberg + elementor }); it('should include slug, description, and detected flag for each cookbook', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([]); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); const gutenberg = data.available_cookbooks.find((c: any) => c.slug === 'gutenberg'); expect(gutenberg).toBeDefined(); expect(gutenberg.slug).toBe('gutenberg'); expect(gutenberg).toHaveProperty('description'); expect(typeof gutenberg.detected).toBe('boolean'); }); it('should set detected=true when plugin is active', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([ { plugin: 'gutenberg/gutenberg.php', version: '17.0.0', status: 'active' }, ]); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); const gutenberg = data.available_cookbooks.find((c: any) => c.slug === 'gutenberg'); expect(gutenberg).toBeDefined(); expect(gutenberg.detected).toBe(true); }); it('should set detected=false when plugin is not active', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([ { plugin: 'gutenberg/gutenberg.php', version: '17.0.0', status: 'active' }, ]); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); // Elementor is not in the active plugins list const elementor = data.available_cookbooks.find((c: any) => c.slug === 'elementor'); expect(elementor).toBeDefined(); expect(elementor.detected).toBe(false); }); it('should handle plugins fetch failure gracefully', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.reject(new Error('Connection failed')); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); // Should not error expect(result.isError).toBeFalsy(); const data = JSON.parse(result.content[0].text!); // Should still have introspect data expect(data.plugin.version).toBe('1.5.0'); // Should have cookbooks, all with detected=false expect(data.available_cookbooks).toBeInstanceOf(Array); const allNotDetected = data.available_cookbooks.every((c: any) => c.detected === false); expect(allNotDetected).toBe(true); }); it('should work with both bundled and project cookbooks', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([]); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); // Should have bundled cookbooks const slugs = data.available_cookbooks.map((c: any) => c.slug); expect(slugs).toContain('gutenberg'); expect(slugs).toContain('elementor'); }); // ========================================================================= // Roles Discovery Tests (task-86.11) // ========================================================================= it('should include roles.available array in response', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([]); } if (url.includes('users/me')) { return Promise.resolve({ id: 1, roles: ['administrator'] }); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); // Should have roles object expect(data.roles).toBeDefined(); expect(data.roles.available).toBeInstanceOf(Array); expect(data.roles.available.length).toBeGreaterThanOrEqual(4); // Should include bundled roles expect(data.roles.available).toContain('content-editor'); expect(data.roles.available).toContain('developer'); expect(data.roles.available).toContain('seo-specialist'); expect(data.roles.available).toContain('site-admin'); }); it('should include roles.recommended string in response', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([]); } if (url.includes('users/me')) { return Promise.resolve({ id: 1, roles: ['editor'] }); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); expect(data.roles.recommended).toBeDefined(); expect(typeof data.roles.recommended).toBe('string'); }); it('should recommend site-admin for administrator users', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([]); } if (url.includes('users/me')) { return Promise.resolve({ id: 1, roles: ['administrator'] }); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); expect(data.roles.recommended).toBe('site-admin'); }); it('should recommend content-editor for editor users', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([]); } if (url.includes('users/me')) { return Promise.resolve({ id: 1, roles: ['editor'] }); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); expect(data.roles.recommended).toBe('content-editor'); }); it('should recommend content-editor for author users', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([]); } if (url.includes('users/me')) { return Promise.resolve({ id: 1, roles: ['author'] }); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); expect(data.roles.recommended).toBe('content-editor'); }); it('should default to content-editor when user fetch fails', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([]); } if (url.includes('users/me')) { return Promise.reject(new Error('Unauthorized')); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); // Should still have roles and default to content-editor expect(data.roles).toBeDefined(); expect(data.roles.recommended).toBe('content-editor'); }); it('should include roles.count for quick reference', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('introspect')) { return Promise.resolve({ plugin: { version: '1.5.0' } }); } if (url.includes('plugins')) { return Promise.resolve([]); } if (url.includes('users/me')) { return Promise.resolve({ id: 1, roles: ['administrator'] }); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_introspect'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); expect(data.roles.count).toBeDefined(); expect(typeof data.roles.count).toBe('number'); expect(data.roles.count).toBe(data.roles.available.length); expect(data.roles.count).toBeGreaterThanOrEqual(4); }); }); // ============================================================================= // wpnav_help Tests // ============================================================================= describe('wpnav_help', () => { it('should return help text with quickstart info', async () => { const tool = toolRegistry.getTool('wpnav_help'); expect(tool).toBeDefined(); const result = await tool!.handler({}, createMockContext()); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); const text = result.content[0].text!; expect(text).toContain('WP Navigator MCP'); expect(text).toContain('Connected'); expect(text).toContain('Quick Start'); }); }); // ============================================================================= // wpnav_list_post_types Tests (task-86.2) // ============================================================================= describe('wpnav_list_post_types', () => { it('should return array of post types from /wp/v2/types', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url === '/wp/v2/types') { return Promise.resolve({ post: { name: 'Posts', slug: 'post', description: 'Standard blog posts', hierarchical: false, rest_base: 'posts', rest_namespace: 'wp/v2', }, page: { name: 'Pages', slug: 'page', description: 'Static pages', hierarchical: true, rest_base: 'pages', rest_namespace: 'wp/v2', }, attachment: { name: 'Media', slug: 'attachment', description: '', hierarchical: false, rest_base: 'media', rest_namespace: 'wp/v2', }, }); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_list_post_types'); expect(tool).toBeDefined(); const result = await tool!.handler({}, createMockContext(mockWpRequest)); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); const data = JSON.parse(result.content[0].text!); // Should be an array expect(Array.isArray(data)).toBe(true); expect(data.length).toBe(3); // Should contain post type const postType = data.find((t: any) => t.slug === 'post'); expect(postType).toBeDefined(); expect(postType.name).toBe('Posts'); expect(postType.hierarchical).toBe(false); expect(postType.rest_base).toBe('posts'); // Should contain page type const pageType = data.find((t: any) => t.slug === 'page'); expect(pageType).toBeDefined(); expect(pageType.name).toBe('Pages'); expect(pageType.hierarchical).toBe(true); }); it('should include custom post types like products', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url === '/wp/v2/types') { return Promise.resolve({ post: { name: 'Posts', hierarchical: false, rest_base: 'posts' }, page: { name: 'Pages', hierarchical: true, rest_base: 'pages' }, product: { name: 'Products', slug: 'product', description: 'WooCommerce products', hierarchical: false, rest_base: 'products', rest_namespace: 'wc/v3', }, }); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_list_post_types'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); // Should contain custom product type const productType = data.find((t: any) => t.slug === 'product'); expect(productType).toBeDefined(); expect(productType.name).toBe('Products'); expect(productType.rest_namespace).toBe('wc/v3'); }); it('should handle missing fields gracefully', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url === '/wp/v2/types') { return Promise.resolve({ post: { name: 'Posts', // Missing: description, hierarchical, rest_base, rest_namespace }, }); } return Promise.resolve({}); }); const tool = toolRegistry.getTool('wpnav_list_post_types'); const result = await tool!.handler({}, createMockContext(mockWpRequest)); const data = JSON.parse(result.content[0].text!); const postType = data.find((t: any) => t.slug === 'post'); expect(postType).toBeDefined(); expect(postType.description).toBeNull(); expect(postType.hierarchical).toBe(false); expect(postType.rest_base).toBe('post'); // Falls back to slug expect(postType.rest_namespace).toBe('wp/v2'); // Default }); });

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