Skip to main content
Glama
embeddingGenerator.test.ts29 kB
/** * @fileOverview: Comprehensive tests for LocalEmbeddingGenerator functionality * @module: LocalEmbeddingGenerator Tests * @context: Testing embedding generation with provider fallback and error handling */ import { LocalEmbeddingGenerator, GenerationOptions, GenerationProgress, } from '../embeddingGenerator'; import { LocalEmbeddingStorage } from '../embeddingStorage'; import { LocalEmbeddingProvider, getDefaultLocalProvider } from '../localEmbeddingProvider'; import { apiClient } from '../../client/apiClient'; import { openaiService } from '../../core/openaiService'; import { logger } from '../../utils/logger'; // Mock dependencies jest.mock('../embeddingStorage', () => ({ LocalEmbeddingStorage: jest.fn().mockImplementation(() => ({ initializeDatabase: jest.fn().mockResolvedValue(undefined), ensureDimensionCompatibility: jest.fn().mockResolvedValue(undefined), storeEmbedding: jest.fn().mockResolvedValue(undefined), storeFileMetadata: jest.fn().mockResolvedValue(undefined), getEmbeddingMetadata: jest.fn().mockReturnValue({ embeddingFormat: 'float32', embeddingDimensions: 1024, embeddingProvider: 'voyageai', }), getProjectEmbeddings: jest.fn().mockResolvedValue([]), clearProjectEmbeddings: jest.fn().mockResolvedValue(undefined), close: jest.fn().mockResolvedValue(undefined), })), })); // Mock the static generateContentHash method const mockStorageModule = jest.requireMock('../embeddingStorage'); mockStorageModule.LocalEmbeddingStorage.generateContentHash = jest .fn() .mockReturnValue('mock-hash'); jest.mock('../localEmbeddingProvider'); jest.mock('../../client/apiClient'); jest.mock('../../utils/logger'); jest.mock('fs'); jest.mock('../treeSitterProcessor'); jest.mock('globby'); // Mock getDefaultLocalProvider function jest.mock('../localEmbeddingProvider', () => ({ LocalEmbeddingProvider: jest.fn(), getDefaultLocalProvider: jest.fn(), })); // Setup LocalEmbeddingProvider mock const mockLocalProvider = { generateEmbeddings: jest.fn().mockResolvedValue([ { embedding: [0.1, 0.2, 0.3], model: 'all-MiniLM-L6-v2', dimensions: 384, }, ]), generateQueryEmbedding: jest.fn().mockResolvedValue([0.1, 0.2, 0.3]), getModelInfo: jest.fn().mockReturnValue({ name: 'all-MiniLM-L6-v2', dimensions: 384, provider: 'transformers.js', }), dispose: jest.fn().mockResolvedValue(undefined), }; (LocalEmbeddingProvider as jest.Mock).mockImplementation(() => mockLocalProvider); // Mock the openaiService module jest.mock('../../core/openaiService', () => ({ openaiService: { isReady: jest.fn(), getClient: jest.fn(), }, })); const mockOpenAIService = require('../../core/openaiService').openaiService; describe('LocalEmbeddingGenerator', () => { let generator: LocalEmbeddingGenerator; let mockStorage: jest.Mocked<LocalEmbeddingStorage>; let mockApiClient: jest.Mocked<typeof apiClient>; let mockLogger: jest.Mocked<typeof logger>; beforeEach(() => { // Reset all mocks jest.clearAllMocks(); // Import the mocked modules const { LocalEmbeddingStorage } = require('../embeddingStorage'); // Setup LocalEmbeddingStorage mock mockStorage = new LocalEmbeddingStorage() as jest.Mocked<LocalEmbeddingStorage>; LocalEmbeddingStorage.isEnabled = jest.fn().mockReturnValue(true); // Add the deleteEmbeddingsByFile method to the mock mockStorage.deleteEmbeddingsByFile = jest.fn().mockResolvedValue(5); // Setup apiClient mock with successful response mockApiClient = { post: jest.fn().mockResolvedValue({ data: { embeddings: [[0.1, 0.2, 0.3]] }, }), generateEmbeddings: jest.fn().mockResolvedValue({ embeddings: [[0.1, 0.2, 0.3]], model: process.env.VOYAGEAI_MODEL || 'voyageai-model', dimensions: 3, input_type: 'document', encoding_format: 'float32', total_tokens: 10, processing_time_ms: 100, provider: 'voyage', }), } as any; // Setup logger mock mockLogger = { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn(), } as any; // Setup globby mock to return test files const mockProjectPath = '/path/to/test/project'; const mockGlobby = jest .fn() .mockResolvedValue([ require('path').join(mockProjectPath, 'src', 'index.ts'), require('path').join(mockProjectPath, 'src', 'utils.ts'), require('path').join(mockProjectPath, 'package.json'), ]); (require('globby') as any).globby = mockGlobby; // Setup fs mocks for file operations const mockFs = { readFileSync: jest.fn((filePath: string) => { if (filePath.includes('index.ts')) { return 'export function hello() { return "world"; }'; } else if (filePath.includes('utils.ts')) { return 'export const util = "test";'; } else if (filePath.includes('package.json')) { return '{"name": "test", "version": "1.0.0"}'; } return ''; }), statSync: jest.fn(() => ({ mtime: new Date(), size: 100, isFile: () => true, isDirectory: () => false, })), }; (require('fs') as any).readFileSync = mockFs.readFileSync; (require('fs') as any).statSync = mockFs.statSync; // Apply mocks (apiClient as any).post = mockApiClient.post; (apiClient as any).generateEmbeddings = mockApiClient.generateEmbeddings; (logger as any).info = mockLogger.info; (logger as any).warn = mockLogger.warn; (logger as any).error = mockLogger.error; (logger as any).debug = mockLogger.debug; // Mock environment variables process.env.USE_LOCAL_EMBEDDINGS = 'true'; process.env.LOCAL_EMBEDDING_MODEL = 'all-MiniLM-L6-v2'; // Required for local provider selection process.env.AMBIANCE_API_KEY = process.env.AMBIANCE_API_KEY || 'test-key'; // Setup getDefaultLocalProvider mock to return our mock provider (getDefaultLocalProvider as jest.Mock).mockReturnValue(mockLocalProvider); // Create generator with mock storage to avoid constructor issues generator = new LocalEmbeddingGenerator(mockStorage); }); afterEach(() => { // Clean up environment variables delete process.env.USE_LOCAL_EMBEDDINGS; delete process.env.LOCAL_EMBEDDING_MODEL; delete process.env.AMBIANCE_API_KEY; delete process.env.OPENAI_API_KEY; }); describe('Constructor', () => { test('should initialize with provided storage', () => { const customStorage = {} as LocalEmbeddingStorage; const gen = new LocalEmbeddingGenerator(customStorage); expect(gen).toBeInstanceOf(LocalEmbeddingGenerator); }); test('should create default storage when none provided', () => { const gen = new LocalEmbeddingGenerator(); expect(gen).toBeInstanceOf(LocalEmbeddingGenerator); expect(LocalEmbeddingStorage).toHaveBeenCalled(); }); test('should initialize with Ambiance API key from environment', () => { process.env.AMBIANCE_API_KEY = process.env.AMBIANCE_API_KEY || 'test-ambiance-key'; const gen = new LocalEmbeddingGenerator(); // The key should be set internally - we can't test private properties directly expect(gen).toBeDefined(); }); test('should handle missing API keys gracefully', () => { delete process.env.AMBIANCE_API_KEY; delete process.env.OPENAI_API_KEY; const gen = new LocalEmbeddingGenerator(); expect(gen).toBeDefined(); }); }); describe('generateProjectEmbeddings', () => { const mockProjectId = 'test-project-123'; const mockProjectPath = '/path/to/test/project'; beforeEach(() => { // Additional setup for generation tests if needed }); test('should throw error when local embeddings not enabled', async () => { (LocalEmbeddingStorage.isEnabled as jest.Mock).mockReturnValue(false); await expect( generator.generateProjectEmbeddings(mockProjectId, mockProjectPath) ).rejects.toThrow('Local embeddings not enabled'); }); test('should initialize database on generation start', async () => { mockApiClient.post.mockResolvedValue({ data: { embeddings: [[0.1, 0.2, 0.3]] }, }); await generator.generateProjectEmbeddings(mockProjectId, mockProjectPath); expect(mockStorage.initializeDatabase).toHaveBeenCalled(); }); test('should process files and return progress', async () => { mockApiClient.post.mockResolvedValue({ data: { embeddings: [[0.1, 0.2, 0.3]] }, }); const result = await generator.generateProjectEmbeddings(mockProjectId, mockProjectPath); expect(result).toHaveProperty('totalFiles'); expect(result).toHaveProperty('processedFiles'); expect(result).toHaveProperty('totalChunks'); expect(result).toHaveProperty('processedChunks'); expect(result).toHaveProperty('embeddings'); expect(result).toHaveProperty('errors'); expect(Array.isArray(result.errors)).toBe(true); }); test('should handle force regeneration option', async () => { mockApiClient.post.mockResolvedValue({ data: { embeddings: [[0.1, 0.2, 0.3]] }, }); const options: GenerationOptions = { force: true }; await generator.generateProjectEmbeddings(mockProjectId, mockProjectPath, options); // Should proceed with generation regardless of existing embeddings expect(mockStorage.initializeDatabase).toHaveBeenCalled(); }); test('should respect batch size configuration', async () => { mockApiClient.post.mockResolvedValue({ data: { embeddings: [[0.1, 0.2, 0.3]] }, }); const options: GenerationOptions = { batchSize: 16 }; await generator.generateProjectEmbeddings(mockProjectId, mockProjectPath, options); expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); }); test('should handle rate limiting', async () => { mockApiClient.post.mockResolvedValue({ data: { embeddings: [[0.1, 0.2, 0.3]] }, }); const options: GenerationOptions = { rateLimit: 100 }; const startTime = Date.now(); await generator.generateProjectEmbeddings(mockProjectId, mockProjectPath, options); const endTime = Date.now(); // Should take at least the rate limit time expect(endTime - startTime).toBeGreaterThanOrEqual(0); // Basic timing test }); test('should handle file patterns filter', async () => { mockApiClient.post.mockResolvedValue({ data: { embeddings: [[0.1, 0.2, 0.3]] }, }); const options: GenerationOptions = { filePatterns: ['*.ts'] }; await generator.generateProjectEmbeddings(mockProjectId, mockProjectPath, options); expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); }); test('should handle parallel mode', async () => { mockApiClient.post.mockResolvedValue({ data: { embeddings: [[0.1, 0.2, 0.3]] }, }); const options: GenerationOptions = { parallelMode: true, maxConcurrency: 5 }; await generator.generateProjectEmbeddings(mockProjectId, mockProjectPath, options); expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); }); test('should handle API errors gracefully', async () => { // Clear any previous provider failures // @ts-ignore - accessing private static for testing LocalEmbeddingGenerator.providerFailures.clear(); // Set up to use OpenAI provider and disable local embeddings process.env.USE_OPENAI_EMBEDDINGS = 'true'; process.env.OPENAI_API_KEY = 'test-key'; process.env.USE_LOCAL_EMBEDDINGS = 'false'; // Disable local fallback mockOpenAIService.isReady.mockReturnValue(true); mockOpenAIService.getClient.mockReturnValue({}); // Mock OpenAI client as available // Create generator with OpenAI enabled const openaiGenerator = new LocalEmbeddingGenerator(mockStorage); // Mock the OpenAI client to throw an error const mockOpenAIClient = { embeddings: { create: jest.fn().mockRejectedValue(new Error('API Error')), }, }; mockOpenAIService.getClient.mockReturnValue(mockOpenAIClient); const result = await openaiGenerator.generateProjectEmbeddings( mockProjectId, mockProjectPath ); // Method catches errors and adds them to progress.errors instead of throwing expect(result.errors.length).toBeGreaterThan(0); expect(result.errors[0]).toContain('API Error'); expect(mockLogger.error).toHaveBeenCalled(); // Clean up delete process.env.USE_OPENAI_EMBEDDINGS; delete process.env.OPENAI_API_KEY; delete process.env.USE_LOCAL_EMBEDDINGS; mockOpenAIService.isReady.mockReset(); mockOpenAIService.getClient.mockReset(); }); test('should handle chunking options', async () => { mockApiClient.generateEmbeddings.mockResolvedValue({ embeddings: [[0.1, 0.2, 0.3]], model: process.env.VOYAGEAI_MODEL || 'voyageai-model', dimensions: 3, input_type: 'document', encoding_format: 'float32', total_tokens: 10, processing_time_ms: 100, provider: 'voyage', }); const options: GenerationOptions = { maxChunkSize: 1000, overlapSize: 100, preferSymbolBoundaries: true, includeContext: true, }; await generator.generateProjectEmbeddings(mockProjectId, mockProjectPath, options); expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); }); }); describe('generateQueryEmbedding', () => { test('should generate embedding for query text', async () => { mockApiClient.generateEmbeddings.mockResolvedValue({ embeddings: [[0.1, 0.2, 0.3, 0.4, 0.5]], model: process.env.VOYAGEAI_MODEL || 'voyageai-model', dimensions: 5, input_type: 'document', encoding_format: 'float32', total_tokens: 10, processing_time_ms: 100, provider: 'voyage', }); const result = await generator.generateQueryEmbedding('test query'); expect(Array.isArray(result)).toBe(true); expect(result.length).toBeGreaterThan(0); expect(typeof result[0]).toBe('number'); }); test('should handle empty query', async () => { // Set up to use OpenAI provider process.env.USE_OPENAI_EMBEDDINGS = 'true'; process.env.OPENAI_API_KEY = 'test-key'; mockApiClient.generateEmbeddings.mockResolvedValue({ embeddings: [[]], model: 'text-embedding-3-small', dimensions: 1536, input_type: 'document', encoding_format: 'float32', total_tokens: 10, processing_time_ms: 100, provider: 'openai', }); const result = await generator.generateQueryEmbedding(''); expect(Array.isArray(result)).toBe(true); // Clean up delete process.env.USE_OPENAI_EMBEDDINGS; delete process.env.OPENAI_API_KEY; }); test('should handle API errors in query embedding', async () => { // Clear any previous provider failures // @ts-ignore - accessing private static for testing LocalEmbeddingGenerator.providerFailures.clear(); // Set up to use OpenAI provider and disable local embeddings process.env.USE_OPENAI_EMBEDDINGS = 'true'; process.env.OPENAI_API_KEY = 'test-key'; process.env.USE_LOCAL_EMBEDDINGS = 'false'; // Disable local fallback mockOpenAIService.isReady.mockReturnValue(true); mockOpenAIService.getClient.mockReturnValue({}); // Mock OpenAI client as available // Create generator with OpenAI enabled and local disabled const openaiGenerator = new LocalEmbeddingGenerator(mockStorage); // Mock the OpenAI client to throw an error const mockOpenAIClient = { embeddings: { create: jest.fn().mockRejectedValue(new Error('API Error')), }, }; mockOpenAIService.getClient.mockReturnValue(mockOpenAIClient); await expect(openaiGenerator.generateQueryEmbedding('test')).rejects.toThrow( 'Embedding generation failed with all providers' ); // Clean up delete process.env.USE_OPENAI_EMBEDDINGS; delete process.env.OPENAI_API_KEY; delete process.env.USE_LOCAL_EMBEDDINGS; mockOpenAIService.isReady.mockReset(); mockOpenAIService.getClient.mockReset(); }); test('should use project-specific embedding generation when projectId provided', async () => { mockApiClient.generateEmbeddings.mockResolvedValue({ embeddings: [[0.1, 0.2, 0.3]], model: process.env.VOYAGEAI_MODEL || 'voyageai-model', dimensions: 3, input_type: 'document', encoding_format: 'float32', total_tokens: 10, processing_time_ms: 100, provider: 'voyage', }); await generator.generateQueryEmbedding('test', 'project-123'); expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); }); }); describe('dispose', () => { test('should close storage connection', async () => { await generator.dispose(); expect(mockStorage.close).toHaveBeenCalled(); }); test('should handle dispose errors gracefully', async () => { mockStorage.close.mockRejectedValue(new Error('Close error')); await expect(generator.dispose()).resolves.toBeUndefined(); expect(mockLogger.error).toHaveBeenCalled(); }); }); describe('Provider Selection Logic', () => { test('should prioritize local provider when USE_LOCAL_EMBEDDINGS=true and LOCAL_EMBEDDING_MODEL is set', async () => { // With USE_LOCAL_EMBEDDINGS=true and LOCAL_EMBEDDING_MODEL set (from beforeEach), // the provider selection should prioritize local even with Ambiance API key present const result = await generator.generateQueryEmbedding('test'); // Should not call Ambiance API since local is prioritized expect(mockApiClient.generateEmbeddings).not.toHaveBeenCalled(); // Should call local provider instead expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); }); test('should use local provider by default even when Ambiance API key is available', async () => { // Keep LOCAL_EMBEDDING_MODEL set but add Ambiance API key process.env.AMBIANCE_API_KEY = process.env.AMBIANCE_API_KEY || 'test-key'; mockApiClient.post.mockResolvedValue({ data: { embeddings: [[0.1, 0.2, 0.3]] }, }); await generator.generateQueryEmbedding('test'); // Should not call Ambiance API unless explicitly enabled expect(mockApiClient.generateEmbeddings).not.toHaveBeenCalled(); expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); }); test('should handle local provider errors and fallback to OpenAI when explicitly enabled', async () => { // Clear any previous provider failures // @ts-ignore - accessing private static for testing LocalEmbeddingGenerator.providerFailures.clear(); // Mock local provider to throw error const failingLocalProvider = { generateEmbeddings: jest.fn().mockRejectedValue(new Error('Local provider failed')), generateQueryEmbedding: jest.fn().mockRejectedValue(new Error('Local provider failed')), getModelInfo: jest.fn().mockReturnValue({ name: 'all-MiniLM-L6-v2', dimensions: 384, provider: 'transformers.js', }), dispose: jest.fn().mockResolvedValue(undefined), }; // Enable OpenAI explicitly as fallback process.env.USE_OPENAI_EMBEDDINGS = 'true'; process.env.OPENAI_API_KEY = 'test-key'; mockOpenAIService.isReady.mockReturnValue(true); mockOpenAIService.getClient.mockReturnValue({}); // Mock OpenAI client as available // Create generator with OpenAI enabled const openaiGenerator = new LocalEmbeddingGenerator(mockStorage); // Mock getDefaultLocalProvider to return the failing provider for this generator (getDefaultLocalProvider as jest.Mock).mockReturnValue(failingLocalProvider); // Mock the OpenAI client to return successful results const mockOpenAIClient = { embeddings: { create: jest.fn().mockResolvedValue({ data: [{ embedding: [0.1, 0.2, 0.3] }], }), }, }; mockOpenAIService.getClient.mockReturnValue(mockOpenAIClient); const result = await openaiGenerator.generateQueryEmbedding('test'); expect(failingLocalProvider.generateEmbeddings).toHaveBeenCalled(); expect(mockOpenAIClient.embeddings.create).toHaveBeenCalled(); expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual([0.1, 0.2, 0.3]); // Clean up delete process.env.USE_OPENAI_EMBEDDINGS; delete process.env.OPENAI_API_KEY; mockOpenAIService.isReady.mockReset(); mockOpenAIService.getClient.mockReset(); // Restore original mock (getDefaultLocalProvider as jest.Mock).mockReturnValue(mockLocalProvider); }); test('should handle missing API keys with local provider', async () => { delete process.env.AMBIANCE_API_KEY; delete process.env.OPENAI_API_KEY; // Should still work with local provider const result = await generator.generateQueryEmbedding('test'); expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); expect(mockApiClient.generateEmbeddings).not.toHaveBeenCalled(); }); }); describe('Error Handling', () => { test('should handle database initialization errors', async () => { mockStorage.initializeDatabase.mockRejectedValue(new Error('DB Error')); await expect(generator.generateProjectEmbeddings('test', '/path')).rejects.toThrow( 'DB Error' ); }); test('should handle dimension compatibility errors', async () => { mockStorage.ensureDimensionCompatibility.mockRejectedValue(new Error('Dimension mismatch')); await expect(generator.generateProjectEmbeddings('test', '/path')).rejects.toThrow( 'Dimension mismatch' ); }); test('should handle file system errors', async () => { // Mock globby to throw error (this is what getProjectFiles actually uses) const mockGlobby = jest.fn().mockRejectedValue(new Error('File system error')); (require('globby') as any).globby = mockGlobby; const result = await generator.generateProjectEmbeddings('test', '/invalid/path'); // Method catches errors and adds them to progress.errors instead of throwing expect(result.errors.length).toBeGreaterThan(0); expect(result.errors[0]).toContain('File system error'); // Restore original mock (require('globby') as any).globby = jest .fn() .mockResolvedValue([ require('path').join('/path/to/test/project', 'src', 'index.ts'), require('path').join('/path/to/test/project', 'src', 'utils.ts'), require('path').join('/path/to/test/project', 'package.json'), ]); }); test('should handle invalid project paths', async () => { // Mock globby to throw error for invalid paths const mockGlobby = jest.fn().mockRejectedValue(new Error('Invalid project path')); (require('globby') as any).globby = mockGlobby; const result = await generator.generateProjectEmbeddings('test', '/nonexistent/path'); // Method catches errors and adds them to progress.errors instead of throwing expect(result.errors.length).toBeGreaterThan(0); expect(result.errors[0]).toContain('Invalid project path'); // Restore original mock (require('globby') as any).globby = jest .fn() .mockResolvedValue([ require('path').join('/path/to/test/project', 'src', 'index.ts'), require('path').join('/path/to/test/project', 'src', 'utils.ts'), require('path').join('/path/to/test/project', 'package.json'), ]); }); }); describe('Configuration and Environment', () => { test('should respect environment variable configurations', async () => { process.env.EMBEDDING_BATCH_SIZE = '16'; process.env.EMBEDDING_MAX_CONCURRENCY = '5'; process.env.EMBEDDING_PARALLEL_MODE = 'true'; await generator.generateProjectEmbeddings('test', '/path'); // With local provider prioritized, should call local provider instead of Ambiance API expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); expect(mockApiClient.generateEmbeddings).not.toHaveBeenCalled(); }); test('should handle missing environment configurations gracefully', async () => { // Remove all optional environment variables delete process.env.EMBEDDING_BATCH_SIZE; delete process.env.EMBEDDING_MAX_CONCURRENCY; delete process.env.EMBEDDING_PARALLEL_MODE; await generator.generateProjectEmbeddings('test', '/path'); // Should use default values with local provider expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); expect(mockApiClient.generateEmbeddings).not.toHaveBeenCalled(); }); }); describe('Progress Tracking', () => { test('should provide detailed progress information', async () => { const progress = await generator.generateProjectEmbeddings('test', '/path'); expect(progress).toHaveProperty('totalFiles', expect.any(Number)); expect(progress).toHaveProperty('processedFiles', expect.any(Number)); expect(progress).toHaveProperty('totalChunks', expect.any(Number)); expect(progress).toHaveProperty('processedChunks', expect.any(Number)); expect(progress).toHaveProperty('embeddings', expect.any(Number)); expect(progress).toHaveProperty('errors', expect.any(Array)); // Should use local provider instead of Ambiance API expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); expect(mockApiClient.generateEmbeddings).not.toHaveBeenCalled(); }); test('should track errors in progress', async () => { // Mock local provider to throw error during embedding generation const originalGenerateEmbeddings = mockLocalProvider.generateEmbeddings; mockLocalProvider.generateEmbeddings.mockRejectedValue( new Error('Embedding generation failed') ); const progress = await generator.generateProjectEmbeddings('test', '/path'); expect(progress.errors.length).toBeGreaterThan(0); expect(progress.errors[0]).toContain('Failed to generate embeddings'); // Should use local provider even with errors expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); expect(mockApiClient.generateEmbeddings).not.toHaveBeenCalled(); // Restore original mock mockLocalProvider.generateEmbeddings = originalGenerateEmbeddings; }); }); describe('Edge Cases', () => { test('should handle empty file list', async () => { // Mock globby to return empty array const mockGlobby = jest.fn().mockResolvedValue([]); (require('globby') as any).globby = mockGlobby; const progress = await generator.generateProjectEmbeddings('test', '/empty/path'); expect(progress.totalFiles).toBe(0); expect(progress.processedFiles).toBe(0); // Restore original mock (require('globby') as any).globby = jest .fn() .mockResolvedValue([ require('path').join('/path/to/test/project', 'src', 'index.ts'), require('path').join('/path/to/test/project', 'src', 'utils.ts'), require('path').join('/path/to/test/project', 'package.json'), ]); }); test('should handle files with no content', async () => { jest.doMock('fs', () => ({ ...jest.requireActual('fs'), readFileSync: jest.fn().mockReturnValue(''), })); const progress = await generator.generateProjectEmbeddings('test', '/path'); expect(progress.processedFiles).toBeGreaterThan(0); // Should use local provider instead of Ambiance API expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); expect(mockApiClient.generateEmbeddings).not.toHaveBeenCalled(); }); test('should handle very large files', async () => { const largeContent = 'x'.repeat(100000); // 100KB file jest.doMock('fs', () => ({ ...jest.requireActual('fs'), readFileSync: jest.fn().mockReturnValue(largeContent), })); await generator.generateProjectEmbeddings('test', '/path'); // Should use local provider instead of Ambiance API expect(mockLocalProvider.generateEmbeddings).toHaveBeenCalled(); expect(mockApiClient.generateEmbeddings).not.toHaveBeenCalled(); }); }); });

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/sbarron/AmbianceMCP'

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