UNPKG

@alvinveroy/codecompass

Version:

AI-powered MCP server for codebase navigation and LLM prompt optimization

205 lines (173 loc) 9.56 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import type { ConfigService as ConfigServiceType } from '../lib/config-service'; // Import the type import * as fs from 'fs'; // Import fs for mocking describe('Config Module', () => { // Save original environment variables const originalEnv = { ...process.env }; beforeEach(() => { // Restore process.env to original state before each test process.env = { ...originalEnv }; // Ensure OLLAMA_HOST and QDRANT_HOST are unset so ConfigService uses its internal defaults // for tests relying on those defaults (e.g., "Default Configuration" tests). // Tests that specifically override these variables will set them after this block. delete process.env.OLLAMA_HOST; delete process.env.QDRANT_HOST; // Explicitly reset global variables that ConfigService might use/set // These globals are set by ConfigService.initializeGlobalState() // Resetting them ensures a clean slate for each test's ConfigService instantiation. const g = globalThis as NodeJS.Global & typeof globalThis & { [key: string]: unknown }; g.CURRENT_LLM_PROVIDER = undefined; // Assign undefined directly g.CURRENT_SUGGESTION_PROVIDER = undefined; // Assign undefined directly g.CURRENT_EMBEDDING_PROVIDER = undefined; // Assign undefined directly g.CURRENT_SUGGESTION_MODEL = undefined; // Assign undefined directly vi.resetModules(); // This is key for re-importing and re-instantiating ConfigService }); afterEach(() => { // Restore original environment variables fully after each test process.env = { ...originalEnv }; vi.restoreAllMocks(); vi.resetModules(); }); describe('Default Configuration', () => { let currentConfigService: ConfigServiceType; // Use the imported type beforeEach(async () => { // Dynamically import configService to ensure a fresh instance for each test // after vi.resetModules() in the outer beforeEach has run, // and after OLLAMA_HOST/QDRANT_HOST env vars have been deleted. const mod = await import('../lib/config-service.js'); currentConfigService = mod.configService; }); it('should have default values for all required configuration', () => { expect(currentConfigService.OLLAMA_HOST).toBeDefined(); expect(currentConfigService.QDRANT_HOST).toBeDefined(); expect(currentConfigService.COLLECTION_NAME).toBeDefined(); expect(currentConfigService.EMBEDDING_MODEL).toBeDefined(); expect(currentConfigService.SUGGESTION_MODEL).toBeDefined(); expect(currentConfigService.MAX_RETRIES).toBeGreaterThan(0); expect(currentConfigService.RETRY_DELAY).toBeGreaterThan(0); expect(currentConfigService.MAX_INPUT_LENGTH).toBeGreaterThan(0); }); it('should have valid URL formats for host configurations', () => { const urlPattern = /^https?:\/\/.+/; expect(currentConfigService.OLLAMA_HOST).toMatch(urlPattern); expect(currentConfigService.QDRANT_HOST).toMatch(urlPattern); }); it('should have reasonable limits for MAX_INPUT_LENGTH', () => { expect(currentConfigService.MAX_INPUT_LENGTH).toBeGreaterThan(100); expect(currentConfigService.MAX_INPUT_LENGTH).toBeLessThan(100000); // Assuming there's some reasonable upper limit }); it('should have reasonable values for retry configuration', () => { expect(currentConfigService.MAX_RETRIES).toBeGreaterThanOrEqual(1); expect(currentConfigService.MAX_RETRIES).toBeLessThanOrEqual(10); // Assuming there's some reasonable upper limit expect(currentConfigService.RETRY_DELAY).toBeGreaterThanOrEqual(100); // At least 100ms expect(currentConfigService.RETRY_DELAY).toBeLessThanOrEqual(30000); // Not more than 30 seconds }); }); describe('Logger Configuration', () => { let currentConfigService: ConfigServiceType; // Use the imported type beforeEach(async () => { // Dynamically import for a fresh instance const mod = await import('../lib/config-service.js'); currentConfigService = mod.configService; }); it('should have a properly configured logger', () => { expect(currentConfigService.logger).toBeDefined(); expect(typeof currentConfigService.logger.info).toBe('function'); expect(typeof currentConfigService.logger.error).toBe('function'); expect(typeof currentConfigService.logger.warn).toBe('function'); expect(typeof currentConfigService.logger.debug).toBe('function'); }); it('should be able to log messages without throwing errors', () => { // Mock console methods to prevent actual logging during tests const originalInfo = console.info; const originalError = console.error; const originalWarn = console.warn; const originalDebug = console.debug; console.info = vi.fn(); console.error = vi.fn(); console.warn = vi.fn(); console.debug = vi.fn(); expect(() => currentConfigService.logger.info('Test info message')).not.toThrow(); expect(() => currentConfigService.logger.error('Test error message')).not.toThrow(); expect(() => currentConfigService.logger.warn('Test warning message')).not.toThrow(); expect(() => currentConfigService.logger.debug('Test debug message')).not.toThrow(); // Restore console methods console.info = originalInfo; console.error = originalError; console.warn = originalWarn; console.debug = originalDebug; }); }); describe('Environment Variable Overrides', () => { beforeEach(() => { // Mock 'fs' specifically for this suite. // This ensures that ConfigService does not load from actual config files during these tests. vi.doMock('fs', async () => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const actualFs = await vi.importActual('fs') as typeof import('fs'); // Keep 'typeof import("fs")' for precision return { ...actualFs, existsSync: vi.fn((pathToCheck: string) => { if (typeof pathToCheck === 'string' && (pathToCheck.endsWith('model-config.json') || pathToCheck.endsWith('deepseek-config.json'))) { return false; } return actualFs.existsSync(pathToCheck); }), readFileSync: vi.fn((pathToCheck: string, options?: fs.WriteFileOptions) => { if (typeof pathToCheck === 'string' && (pathToCheck.endsWith('model-config.json') || pathToCheck.endsWith('deepseek-config.json'))) { const e = new Error(`ENOENT: no such file or directory, open '${pathToCheck}' (mocked)`); const errorWithCode = e as Error & { code?: string | number }; errorWithCode.code = 'ENOENT'; throw e; } return actualFs.readFileSync(pathToCheck, options as fs.WriteFileOptions); }), mkdirSync: actualFs.mkdirSync, }; }); }); afterEach(() => { vi.doUnmock('fs'); // Clean up the 'fs' mock after this suite }); it('should respect OLLAMA_HOST environment variable if set', async () => { const testUrl = 'http://test-ollama-host:11434'; process.env.OLLAMA_HOST = testUrl; vi.resetModules(); // Ensure configService is re-initialized const mod = await import('../lib/config-service.js'); const freshConfigService = mod.configService; // reloadConfigsFromFile is implicitly called by constructor if resetModules works as expected // or call it explicitly if needed after re-import freshConfigService.reloadConfigsFromFile(true); expect(freshConfigService.OLLAMA_HOST).toBe(testUrl); }); it('should respect QDRANT_HOST environment variable if set', async () => { const testUrl = 'http://test-qdrant-host:6333'; process.env.QDRANT_HOST = testUrl; vi.resetModules(); const mod = await import('../lib/config-service.js'); const freshConfigService = mod.configService; freshConfigService.reloadConfigsFromFile(true); expect(freshConfigService.QDRANT_HOST).toBe(testUrl); }); it('should respect custom model configurations if set via environment variables', async () => { const testModel = 'test-model-from-env'; const testProvider = 'ollama'; // Set environment variables *before* any potential module import or reset process.env.EMBEDDING_MODEL = testModel; process.env.SUGGESTION_MODEL = testModel; process.env.SUGGESTION_PROVIDER = testProvider; // Ensure a completely fresh import of config-service after env vars are set vi.resetModules(); const { configService: freshConfigService } = await import('../lib/config-service.js'); // The ConfigService constructor should have picked up these env vars. // reloadConfigsFromFile(true) is called by the constructor. // If we call it again, it re-initializes from env vars then attempts file load. // This should be redundant if vi.resetModules() + import works as expected. // However, to be absolutely sure it re-evaluates with current process.env: freshConfigService.reloadConfigsFromFile(true); expect(freshConfigService.EMBEDDING_MODEL).toBe(testModel); expect(freshConfigService.SUGGESTION_MODEL).toBe(testModel); expect(freshConfigService.SUGGESTION_PROVIDER).toBe(testProvider); }); }); });