@alvinveroy/codecompass
Version:
AI-powered MCP server for codebase navigation and LLM prompt optimization
205 lines (173 loc) • 9.56 kB
text/typescript
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);
});
});
});