Skip to main content
Glama
search.test.ts14.5 kB
/** * Unit tests for the search handler */ import { describe, it, expect, beforeEach } from 'vitest'; import { handleSearch } from '../../src/handlers/search.js'; import { McpError } from '../../src/types/core.js'; import { mockApiClient, mockLogger, resetAllMocks, createMockRequest, getLoggerFunctions, sampleCard, sampleDashboard } from '../setup.js'; describe('handleSearch', () => { beforeEach(() => { resetAllMocks(); }); describe('Parameter validation', () => { it('should throw error when no search parameters are provided', async () => { const request = createMockRequest('search', {}); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'Missing query, ids, or database_id parameter in search request', { requestId: 'test-request-id' } ); }); it('should throw error when both query and ids are provided', async () => { const request = createMockRequest('search', { query: 'test', ids: [1] }); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'Cannot use both query and ids parameters simultaneously', { requestId: 'test-request-id' } ); }); it('should throw error when ids is used with multiple models', async () => { const request = createMockRequest('search', { ids: [1], models: ['card', 'dashboard'] }); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'ids parameter can only be used with a single model type', { requestId: 'test-request-id' } ); }); it('should throw error when ids is used with table model', async () => { const request = createMockRequest('search', { ids: [1], models: ['table'] }); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'ids parameter cannot be used with table model', { requestId: 'test-request-id' } ); }); it('should throw error when database model is mixed with others', async () => { const request = createMockRequest('search', { query: 'test', models: ['database', 'card'] }); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'database model cannot be mixed with other models', { requestId: 'test-request-id' } ); }); it('should throw error when database_id is used with database model', async () => { const request = createMockRequest('search', { query: 'test', models: ['database'], database_id: 1 }); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'database_id parameter cannot be used when searching solely for databases', { requestId: 'test-request-id' } ); }); it('should throw error when invalid model types are specified', async () => { const request = createMockRequest('search', { query: 'test', models: ['invalid-model'] }); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'Invalid models[0] parameter: invalid-model', expect.objectContaining({ requestId: 'test-request-id', validValues: expect.any(Array) }) ); }); it('should throw error when database_id is invalid', async () => { const request = createMockRequest('search', { database_id: 'invalid' }); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); }); it('should throw error when search_native_query is used without cards model', async () => { const request = createMockRequest('search', { query: 'test', models: ['dashboard'], search_native_query: true }); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'search_native_query parameter can only be used when searching cards exclusively', { requestId: 'test-request-id' } ); }); }); describe('Search functionality', () => { it('should successfully search with query parameter', async () => { const searchResults = [sampleCard, sampleDashboard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test' }); const result = await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockApiClient.request).toHaveBeenCalledWith( expect.stringContaining('/api/search?q=test') ); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); expect(result.content[0].text).toContain('Test Card'); expect(result.content[0].text).toContain('Test Dashboard'); }); it('should successfully search with ids parameter', async () => { const searchResults = [sampleCard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { ids: [1], models: ['card'] }); const result = await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockApiClient.request).toHaveBeenCalledWith( expect.stringContaining('/api/search?models=card&ids=1') ); expect(result.content[0].text).toContain('Test Card'); }); it('should successfully search with database_id parameter', async () => { const searchResults = [sampleCard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { database_id: 1 }); const result = await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockApiClient.request).toHaveBeenCalledWith( expect.stringContaining('/api/search?models=card&models=dashboard&table_db_id=1') ); expect(result.content[0].text).toContain('Test Card'); }); it('should handle empty search results', async () => { const searchResults: any[] = []; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'nonexistent' }); const result = await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); const responseData = JSON.parse(result.content[0].text); expect(responseData.search_metrics.total_results).toBeDefined(); expect(responseData.results).toEqual([]); }); it('should handle API errors', async () => { const apiError = new Error('API Error'); mockApiClient.request.mockRejectedValue(apiError); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test' }); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(); }); }); describe('Search parameters', () => { it('should use custom max_results parameter', async () => { const searchResults = [sampleCard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test', max_results: 10 }); await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockApiClient.request).toHaveBeenCalledWith( expect.stringContaining('/api/search?q=test') ); }); it('should throw error when max_results is 0', async () => { const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test', max_results: 0 }); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'Invalid max_results parameter - must be a positive number', expect.objectContaining({ requestId: 'test-request-id' }) ); }); it('should throw error when max_results exceeds 50', async () => { const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test', max_results: 100 }); await expect( handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError) ).rejects.toThrow(McpError); expect(mockLogger.logWarn).toHaveBeenCalledWith( 'max_results exceeds maximum allowed value', expect.objectContaining({ requestId: 'test-request-id', maxResults: 100 }) ); }); it('should use search_native_query parameter', async () => { const searchResults = [sampleCard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test', models: ['card'], search_native_query: true }); await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockApiClient.request).toHaveBeenCalledWith( expect.stringContaining('search_native_query=true') ); }); it('should use include_dashboard_questions parameter', async () => { const searchResults = [sampleDashboard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test', models: ['dashboard'], include_dashboard_questions: true }); await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockApiClient.request).toHaveBeenCalledWith( expect.stringContaining('include_dashboard_questions=true') ); }); it('should use archived parameter', async () => { const searchResults = [sampleCard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test', archived: true }); await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockApiClient.request).toHaveBeenCalledWith( expect.stringContaining('archived=true') ); }); it('should use verified parameter', async () => { const searchResults = [sampleCard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test', verified: true }); await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockApiClient.request).toHaveBeenCalledWith( expect.stringContaining('verified=true') ); }); }); describe('Logging', () => { it('should log debug information', async () => { const searchResults = [sampleCard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test' }); await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockLogger.logDebug).toHaveBeenCalledWith( 'Search with query: "test", models: card, dashboard' ); }); it('should log success information', async () => { const searchResults = [sampleCard]; mockApiClient.request.mockResolvedValue(searchResults); const [logDebug, logInfo, logWarn, logError] = getLoggerFunctions(); const request = createMockRequest('search', { query: 'test' }); await handleSearch(request, 'test-request-id', mockApiClient as any, logDebug, logInfo, logWarn, logError); expect(mockLogger.logInfo).toHaveBeenCalledWith( expect.stringContaining('Search found 1 items') ); }); }); });

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/jerichosequitin/Metabase'

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