Skip to main content
Glama
query-service-sql-like.test.ts13.5 kB
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; import { createMcpServer, createServices } from '../../src/server/index.js'; import * as fs from 'fs/promises'; import * as path from 'path'; import * as crypto from 'crypto'; /** * RED PHASE - REQ-9: SQL LIKE Search Implementation * * E2E tests for SQL LIKE-compatible search in query service: * - Wildcard % (matches any sequence of characters) * - Wildcard _ (matches single character) * - Case-insensitive search * - Plain text search (no wildcards) * - Empty pattern returns all entities * * These tests should FAIL initially, then PASS after GREEN phase implementation. */ // Helper to retry directory removal on Windows async function removeDirectoryWithRetry(dir: string, maxRetries = 3): Promise<void> { for (let i = 0; i < maxRetries; i++) { try { await fs.rm(dir, { recursive: true, force: true }); return; } catch (error: unknown) { if (i === maxRetries - 1) throw error; await new Promise((resolve) => setTimeout(resolve, 100 * (i + 1))); } } } // Helper to parse MCP tool result // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters function parseResult<T>(result: unknown): T { const r = result as { content: { type: string; text: string }[] }; return JSON.parse(r.content[0].text) as T; } describe('E2E: Query Service - SQL LIKE Search (RED Phase)', () => { let client: Client; let storagePath: string; let cleanup: () => Promise<void>; let planId: string; beforeAll(async () => { storagePath = path.join( process.cwd(), '.test-temp', 'query-sql-like-' + String(Date.now()) + '-' + crypto.randomUUID() ); await fs.mkdir(storagePath, { recursive: true }); const services = await createServices(storagePath); const { server } = createMcpServer(services); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); client = new Client( { name: 'query-sql-like-test', version: '1.0.0' }, { capabilities: {} } ); await server.connect(serverTransport); await client.connect(clientTransport); cleanup = async (): Promise<void> => { await client.close(); await server.close(); await removeDirectoryWithRetry(storagePath); }; // Setup: Create test plan const planResult = await client.callTool({ name: 'plan', arguments: { action: 'create', name: 'SQL LIKE Search Test Plan', description: 'Testing SQL LIKE search functionality', }, }); const plan = parseResult<{ planId: string }>(planResult); planId = plan.planId; // Setup: Create test requirements with various titles await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'User Authentication Service', description: 'Implement user authentication', category: 'functional', priority: 'high', source: { type: 'user-request' }, impact: { scope: ['auth'], complexityEstimate: 5, riskLevel: 'medium' }, }, }, }); await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'Database Service Layer', description: 'Create database abstraction', category: 'technical', priority: 'critical', source: { type: 'user-request' }, impact: { scope: ['db'], complexityEstimate: 8, riskLevel: 'high' }, }, }, }); await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'API Gateway', description: 'Setup API gateway', category: 'technical', priority: 'high', source: { type: 'user-request' }, impact: { scope: ['api'], complexityEstimate: 6, riskLevel: 'medium' }, }, }, }); await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'Service Discovery', description: 'Implement service discovery', category: 'technical', priority: 'medium', source: { type: 'user-request' }, impact: { scope: ['infra'], complexityEstimate: 7, riskLevel: 'medium' }, }, }, }); await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'Test Coverage', description: 'Increase test coverage to 80%', category: 'non-functional', priority: 'low', source: { type: 'user-request' }, impact: { scope: ['testing'], complexityEstimate: 3, riskLevel: 'low' }, }, }, }); }); afterAll(async () => { await cleanup(); }); // ============================================================ // RED: Wildcard % - Match any sequence of characters // ============================================================ describe('SQL LIKE: Wildcard % (any characters)', () => { it('RED: should find all entities with "Service" using %Service%', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: '%Service%', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "User Authentication Service", "Database Service Layer", "Service Discovery" expect(result.total).toBeGreaterThanOrEqual(3); expect(result.results.length).toBeGreaterThanOrEqual(3); }); it('RED: should find entities starting with "Database" using Database%', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: 'Database%', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "Database Service Layer" expect(result.total).toBe(1); }); it('RED: should find entities ending with "Gateway" using %Gateway', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: '%Gateway', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "API Gateway" expect(result.total).toBe(1); }); it('RED: should return all entities with % wildcard alone', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: '%', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match all 5 requirements expect(result.total).toBe(5); }); }); // ============================================================ // RED: Wildcard _ - Match single character // ============================================================ describe('SQL LIKE: Wildcard _ (single character)', () => { it('RED: should match single character using _est', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: '_est', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "Test Coverage" (Test contains "Test") expect(result.total).toBeGreaterThanOrEqual(1); }); it('RED: should match pattern with multiple _ wildcards', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: 'S__vice', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "Service" (er = two chars) expect(result.total).toBeGreaterThanOrEqual(1); }); }); // ============================================================ // RED: Case-insensitive search // ============================================================ describe('SQL LIKE: Case-insensitive search', () => { it('RED: should find matches regardless of case - uppercase', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: '%SERVICE%', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "User Authentication Service", "Database Service Layer", "Service Discovery" expect(result.total).toBeGreaterThanOrEqual(3); }); it('RED: should find matches regardless of case - lowercase', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: '%service%', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "User Authentication Service", "Database Service Layer", "Service Discovery" expect(result.total).toBeGreaterThanOrEqual(3); }); it('RED: should find matches with mixed case', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: '%SeRvIcE%', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "User Authentication Service", "Database Service Layer", "Service Discovery" expect(result.total).toBeGreaterThanOrEqual(3); }); }); // ============================================================ // RED: Plain text search (no wildcards) // ============================================================ describe('SQL LIKE: Plain text search', () => { it('RED: should match plain text without wildcards', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: 'API', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "API Gateway" expect(result.total).toBe(1); }); it('RED: should match partial word as substring', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: 'auth', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "User Authentication Service" expect(result.total).toBe(1); }); }); // ============================================================ // RED: Combined wildcards // ============================================================ describe('SQL LIKE: Combined wildcards', () => { it('RED: should combine % and _ wildcards', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: 'S_rv%', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "Service" patterns (Se + rv + anything) expect(result.total).toBeGreaterThanOrEqual(1); }); }); // ============================================================ // RED: Empty pattern // ============================================================ describe('SQL LIKE: Empty pattern', () => { it('RED: should return all entities for empty string', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: '', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match all 5 requirements (empty string matches everything) expect(result.total).toBe(5); }); }); // ============================================================ // RED: Special case - search in descriptions // ============================================================ describe('SQL LIKE: Search in multiple fields', () => { it('RED: should search in description field with wildcards', async () => { const searchResult = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: '%abstraction%', }, }); const result = parseResult<{ results: unknown[]; total: number }>(searchResult); // Should match: "Database Service Layer" (description: "Create database abstraction") expect(result.total).toBe(1); }); }); });

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/cppmyjob/cpp-mcp-planner'

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