Skip to main content
Glama
CompanySearchStrategy.test.tsβ€’10.2 kB
/** * Unit tests for CompanySearchStrategy * Issue #598: Add strategy-specific unit tests */ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; // Mock searchObject before importing CompanySearchStrategy vi.mock('../../../src/api/operations/search.js', () => ({ searchObject: vi.fn(), })); import { CompanySearchStrategy } from '../../../src/services/search-strategies/CompanySearchStrategy.js'; import { SearchType, MatchType, SortType, UniversalResourceType, } from '../../../src/handlers/tool-configs/universal/types.js'; import { AttioRecord } from '../../../src/types/attio.js'; import { StrategyDependencies } from '../../../src/services/search-strategies/interfaces.js'; import { FilterValidationError } from '../../../src/errors/api-errors.js'; import { searchObject } from '../../../src/api/operations/search.js'; describe('CompanySearchStrategy', () => { let strategy: CompanySearchStrategy; let mockDependencies: StrategyDependencies; let mockAdvancedSearchFunction: ReturnType<typeof vi.fn>; let mockMergeFilters: ReturnType<typeof vi.fn>; let mockCreateDateFilter: ReturnType<typeof vi.fn>; let mockCompanyRecord: AttioRecord; beforeEach(() => { mockAdvancedSearchFunction = vi.fn(); mockMergeFilters = vi.fn(); mockCreateDateFilter = vi.fn(); mockDependencies = { advancedSearchFunction: mockAdvancedSearchFunction, mergeFilters: mockMergeFilters, createDateFilter: mockCreateDateFilter, }; mockCompanyRecord = { id: { value: 'company-123' }, name: 'Test Company', domain: 'test.com', industry: 'Technology', } as AttioRecord; strategy = new CompanySearchStrategy(mockDependencies); // Default mock implementations mockMergeFilters.mockImplementation((existing, dateFilter) => ({ ...existing, ...dateFilter, })); mockCreateDateFilter.mockReturnValue(null); }); afterEach(() => { vi.clearAllMocks(); }); describe('interface compliance', () => { it('should return correct resource type', () => { expect(strategy.getResourceType()).toBe(UniversalResourceType.COMPANIES); }); it('should support advanced filtering', () => { expect(strategy.supportsAdvancedFiltering()).toBe(true); }); it('should support query search', () => { expect(strategy.supportsQuerySearch()).toBe(true); }); }); describe('basic search', () => { it('should perform basic search without filters', async () => { mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ limit: 10, offset: 0, }); expect(results).toEqual([mockCompanyRecord]); expect(mockAdvancedSearchFunction).toHaveBeenCalledWith( expect.objectContaining({ filters: [] }), 10, 0 ); }); it('should handle search with query', async () => { // Basic queries (no search_type) now route through searchObject() vi.mocked(searchObject).mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ query: 'test company', limit: 10, offset: 0, }); expect(results).toEqual([mockCompanyRecord]); expect(searchObject).toHaveBeenCalled(); }); it('should handle missing advanced search function', async () => { const strategyWithoutFunction = new CompanySearchStrategy({}); await expect(strategyWithoutFunction.search({})).rejects.toThrow( 'Companies search function not available' ); }); }); describe('advanced filtering', () => { it('should pass through filters when no timeframe params', async () => { const filters = { name: 'Test' }; mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ filters, search_type: SearchType.ADVANCED, }); expect(results).toEqual([mockCompanyRecord]); expect(mockAdvancedSearchFunction).toHaveBeenCalledWith( filters, undefined, undefined ); }); it('should merge date filters with existing filters', async () => { const filters = { name: 'Test' }; const dateFilter = { created_at: { gte: '2023-01-01' } }; const timeframeParams = { timeframe_attribute: 'created_at', start_date: '2023-01-01', }; mockCreateDateFilter.mockReturnValue(dateFilter); mockMergeFilters.mockReturnValue({ ...filters, ...dateFilter }); mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ filters, timeframeParams, search_type: SearchType.ADVANCED, }); expect(results).toEqual([mockCompanyRecord]); expect(mockCreateDateFilter).toHaveBeenCalledWith(timeframeParams); expect(mockMergeFilters).toHaveBeenCalledWith(filters, dateFilter); }); it('should validate filters and throw FilterValidationError', async () => { const invalidFilters = { invalid_field: 'value' }; const validationError = new FilterValidationError('Invalid filter field'); mockAdvancedSearchFunction.mockRejectedValue(validationError); await expect( strategy.search({ filters: invalidFilters, search_type: SearchType.ADVANCED, }) ).rejects.toThrow(FilterValidationError); }); it('should handle missing filter utility functions', async () => { const strategyWithoutUtils = new CompanySearchStrategy({ advancedSearchFunction: mockAdvancedSearchFunction, }); mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategyWithoutUtils.search({ filters: { name: 'Test' }, timeframeParams: { start_date: '2023-01-01' }, search_type: SearchType.ADVANCED, }); expect(results).toEqual([mockCompanyRecord]); expect(mockAdvancedSearchFunction).toHaveBeenCalledWith( { name: 'Test' }, undefined, undefined ); }); }); describe('content search', () => { it('should delegate content search to advanced search function', async () => { mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ query: 'technology', search_type: SearchType.CONTENT, fields: ['name', 'industry'], match_type: MatchType.PARTIAL, }); expect(results).toEqual([mockCompanyRecord]); expect(mockAdvancedSearchFunction).toHaveBeenCalled(); }); it('should handle exact match type', async () => { mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ query: 'Test Company', search_type: SearchType.CONTENT, match_type: MatchType.EXACT, }); expect(results).toEqual([mockCompanyRecord]); }); }); describe('pagination', () => { it('should handle pagination parameters', async () => { mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ limit: 5, offset: 10, }); expect(results).toEqual([mockCompanyRecord]); expect(mockAdvancedSearchFunction).toHaveBeenCalledWith( expect.objectContaining({ filters: [] }), 5, 10 ); }); it('should use default pagination when not specified', async () => { mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({}); expect(results).toEqual([mockCompanyRecord]); expect(mockAdvancedSearchFunction).toHaveBeenCalledWith( expect.objectContaining({ filters: [] }), undefined, undefined ); }); }); describe('error handling', () => { it('should gracefully handle API errors when listing without filters', async () => { const apiError = new Error('API Connection Failed'); mockAdvancedSearchFunction.mockRejectedValue(apiError); const results = await strategy.search({}); expect(results).toEqual([]); }); it('should handle null return from advanced search function', async () => { const strategyWithNullFunction = new CompanySearchStrategy({ advancedSearchFunction: null, }); await expect(strategyWithNullFunction.search({})).rejects.toThrow( 'Companies search function not available' ); }); }); describe('search type combinations', () => { it('should handle BASIC search type', async () => { mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ search_type: SearchType.BASIC, }); expect(results).toEqual([mockCompanyRecord]); }); it('should handle ADVANCED search type with complex filters', async () => { const complexFilters = { name: { contains: 'Test' }, industry: { in: ['Technology', 'Software'] }, employee_count: { gte: 100 }, }; mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ filters: complexFilters, search_type: SearchType.ADVANCED, }); expect(results).toEqual([mockCompanyRecord]); // mergeFilters is not invoked without timeframe; ensure advancedSearchFunction received filters expect(mockAdvancedSearchFunction).toHaveBeenCalledWith( complexFilters, undefined, undefined ); }); it('should handle CONTENT search type with field specification', async () => { mockAdvancedSearchFunction.mockResolvedValue([mockCompanyRecord]); const results = await strategy.search({ query: 'technology company', fields: ['name', 'description', 'industry'], search_type: SearchType.CONTENT, match_type: MatchType.PARTIAL, sort: SortType.RELEVANCE, }); expect(results).toEqual([mockCompanyRecord]); }); }); });

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/kesslerio/attio-mcp-server'

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