Skip to main content
Glama
index.test.ts8.39 kB
/** * Content Tools Tests * * Tests for content MCP tools (wpnav_search). * * @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 { registerContentTools } from './index.js'; import type { ToolExecutionContext } from '../../tool-registry/types.js'; // Register tools once before all tests beforeAll(() => { registerContentTools(); }); // ============================================================================= // 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, }); // ============================================================================= // wpnav_search Tests (task-86.3) // ============================================================================= describe('wpnav_search', () => { it('should register wpnav_search tool', () => { const tool = toolRegistry.getTool('wpnav_search'); expect(tool).toBeDefined(); expect(tool?.category).toBe(ToolCategory.CONTENT); }); it('should search posts, pages, and media by default', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('/wp/v2/posts?search=')) { return Promise.resolve([ { id: 1, title: { rendered: 'Test Post' }, link: 'https://test.local/post/1', modified: '2024-01-01', }, { id: 2, title: { rendered: 'Another Test' }, link: 'https://test.local/post/2', modified: '2024-01-02', }, ]); } if (url.includes('/wp/v2/pages?search=')) { return Promise.resolve([ { id: 10, title: { rendered: 'Test Page' }, link: 'https://test.local/page/10', modified: '2024-01-01', }, ]); } if (url.includes('/wp/v2/media?search=')) { return Promise.resolve([]); } return Promise.resolve([]); }); const tool = toolRegistry.getTool('wpnav_search'); expect(tool).toBeDefined(); const result = await tool!.handler({ query: 'test' }, createMockContext(mockWpRequest)); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); const data = JSON.parse(result.content[0].text!); expect(data.query).toBe('test'); expect(data.total_results).toBe(3); // 2 posts + 1 page + 0 media expect(data.results.posts.count).toBe(2); expect(data.results.pages.count).toBe(1); expect(data.results.media.count).toBe(0); }); it('should search only specified types', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('/wp/v2/users?search=')) { return Promise.resolve([{ id: 1, name: 'Admin', slug: 'admin' }]); } return Promise.resolve([]); }); const tool = toolRegistry.getTool('wpnav_search'); const result = await tool!.handler( { query: 'admin', types: ['users'] }, createMockContext(mockWpRequest) ); const data = JSON.parse(result.content[0].text!); expect(data.total_results).toBe(1); expect(data.results.users).toBeDefined(); expect(data.results.users.count).toBe(1); expect(data.results.users.items[0].name).toBe('Admin'); // Should not have posts, pages, media expect(data.results.posts).toBeUndefined(); expect(data.results.pages).toBeUndefined(); expect(data.results.media).toBeUndefined(); }); it('should respect per_page limit', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { // Verify per_page is in the URL expect(url).toContain('per_page=10'); return Promise.resolve([]); }); const tool = toolRegistry.getTool('wpnav_search'); await tool!.handler({ query: 'test', per_page: 10 }, createMockContext(mockWpRequest)); expect(mockWpRequest).toHaveBeenCalled(); }); it('should cap per_page at 20', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { // Should cap at 20 even if 100 requested expect(url).toContain('per_page=20'); return Promise.resolve([]); }); const tool = toolRegistry.getTool('wpnav_search'); await tool!.handler({ query: 'test', per_page: 100 }, createMockContext(mockWpRequest)); expect(mockWpRequest).toHaveBeenCalled(); }); it('should handle API errors gracefully', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('/wp/v2/posts')) { return Promise.reject(new Error('Connection failed')); } if (url.includes('/wp/v2/pages')) { return Promise.resolve([ { id: 10, title: { rendered: 'Working Page' }, link: 'https://test.local/page/10', modified: '2024-01-01', }, ]); } return Promise.resolve([]); }); const tool = toolRegistry.getTool('wpnav_search'); const result = await tool!.handler({ query: 'test' }, createMockContext(mockWpRequest)); // Should not error, should return empty for failed endpoint expect(result.isError).toBeFalsy(); const data = JSON.parse(result.content[0].text!); expect(data.results.posts.count).toBe(0); // Failed endpoint returns empty expect(data.results.pages.count).toBe(1); // Working endpoint returns results }); it('should require query parameter', async () => { const tool = toolRegistry.getTool('wpnav_search'); await expect(tool!.handler({}, createMockContext())).rejects.toThrow( 'Missing required fields: query' ); }); it('should encode query parameter for URL safety', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { // Should have encoded the space expect(url).toContain('search=hello%20world'); return Promise.resolve([]); }); const tool = toolRegistry.getTool('wpnav_search'); await tool!.handler({ query: 'hello world' }, createMockContext(mockWpRequest)); expect(mockWpRequest).toHaveBeenCalled(); }); it('should extract correct fields for content vs users', async () => { const mockWpRequest = vi.fn().mockImplementation((url: string) => { if (url.includes('/wp/v2/posts')) { return Promise.resolve([ { id: 1, title: { rendered: 'Post Title' }, link: 'https://test.local/post/1', modified: '2024-01-01', content: { rendered: '<p>Should not be included</p>' }, }, ]); } if (url.includes('/wp/v2/users')) { return Promise.resolve([ { id: 1, name: 'User Name', slug: 'user-name', email: 'user@test.local', // Should not be included }, ]); } return Promise.resolve([]); }); const tool = toolRegistry.getTool('wpnav_search'); const result = await tool!.handler( { query: 'test', types: ['posts', 'users'] }, createMockContext(mockWpRequest) ); const data = JSON.parse(result.content[0].text!); // Posts should have content fields const post = data.results.posts.items[0]; expect(post.id).toBe(1); expect(post['title.rendered']).toBe('Post Title'); expect(post.link).toBe('https://test.local/post/1'); expect(post['content.rendered']).toBeUndefined(); // Not extracted // Users should have user fields const user = data.results.users.items[0]; expect(user.id).toBe(1); expect(user.name).toBe('User Name'); expect(user.slug).toBe('user-name'); expect(user.email).toBeUndefined(); // Not extracted }); });

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