UNPKG

@alvinveroy/codecompass

Version:

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

191 lines (190 loc) 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); // Added Mock const retry_utils_1 = require("../utils/retry-utils"); const text_utils_1 = require("../utils/text-utils"); vitest_1.vi.mock('../lib/config-service', async () => { // Import the original module to get default values *inside the factory* // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- This assertion is necessary for tsc to correctly type the dynamic import const originalModule = await vitest_1.vi.importActual('../lib/config-service'); const originalInstanceFromActual = originalModule.configService; // No longer unsafe access due to improved type of originalModule const mockConfigServiceValues = { MAX_RETRIES: originalInstanceFromActual.MAX_RETRIES, RETRY_DELAY: originalInstanceFromActual.RETRY_DELAY, OLLAMA_HOST: originalInstanceFromActual.OLLAMA_HOST, MAX_FILE_CONTENT_LENGTH_FOR_CAPABILITY: originalInstanceFromActual.MAX_FILE_CONTENT_LENGTH_FOR_CAPABILITY || 10000, MAX_DIR_LISTING_ENTRIES_FOR_CAPABILITY: originalInstanceFromActual.MAX_DIR_LISTING_ENTRIES_FOR_CAPABILITY || 50, logger: { warn: vitest_1.vi.fn(), error: vitest_1.vi.fn(), info: vitest_1.vi.fn(), debug: vitest_1.vi.fn(), }, }; return { // Export the mocked configService and logger configService: mockConfigServiceValues, logger: mockConfigServiceValues.logger, // If there are other exports from config-service that utils.ts might use, // spread originalModule here, but be careful not to overwrite your mocks. // Example: ...originalModule (if other named exports are needed and not configService/logger) }; }); // Import the mocked configService *after* vi.mock. // This `configServiceInstanceFromMockFactory` is the instance that `withRetry` (the SUT) will use, // and it's the one we want to manipulate in our tests. // It will be typed as the original ConfigService by TypeScript's static analysis, // but at runtime, it IS our MockableConfigService. const config_service_1 = require("../lib/config-service"); (0, vitest_1.describe)('Utils Module', () => { // This variable will hold the correctly typed reference to our mocked configService. let testSubjectMockedConfigService; let originalDefaultRetryValues; (0, vitest_1.beforeEach)(async () => { // Step 1: Get the actual module with a more specific type. // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- This assertion is necessary for tsc to correctly type the dynamic import const originalModuleFromActualImport = await vitest_1.vi.importActual('../lib/config-service'); // Step 2: Access the 'configService' property. // The cast to PartialOriginalConfig is still useful if configService could be wider than PartialOriginalConfig. const originalInstance = originalModuleFromActualImport.configService; // Step 3: Use the now correctly-typed 'originalInstance' originalDefaultRetryValues = { MAX_RETRIES: originalInstance.MAX_RETRIES, RETRY_DELAY: originalInstance.RETRY_DELAY, }; // Assign the top-level imported mock to our test-scoped variable. // This is the crucial part: we cast the statically imported `configServiceInstanceFromMockFactory` // (which TS thinks is the original ConfigService) to our `MockableConfigService` type. // This is safe because our vi.mock factory ensures it *is* a MockableConfigService at runtime. testSubjectMockedConfigService = config_service_1.configService; vitest_1.vi.useFakeTimers(); // Reset the properties of the *actual mocked instance* before each test // using the correctly typed testSubjectMockedConfigService. testSubjectMockedConfigService.MAX_RETRIES = originalDefaultRetryValues.MAX_RETRIES; testSubjectMockedConfigService.RETRY_DELAY = originalDefaultRetryValues.RETRY_DELAY; // Ensure logger and its methods exist before trying to clear mocks if (testSubjectMockedConfigService.logger) { testSubjectMockedConfigService.logger.warn?.mockClear(); testSubjectMockedConfigService.logger.error?.mockClear(); testSubjectMockedConfigService.logger.info?.mockClear(); testSubjectMockedConfigService.logger.debug?.mockClear(); } }); (0, vitest_1.afterEach)(() => { vitest_1.vi.restoreAllMocks(); // This will also restore vi.spyOn(global, 'setTimeout') vitest_1.vi.useRealTimers(); }); (0, vitest_1.it)('should return the result if the function succeeds on first try', async () => { const fn = vitest_1.vi.fn().mockResolvedValue('success'); const result = await (0, retry_utils_1.withRetry)(fn); (0, vitest_1.expect)(result).toBe('success'); (0, vitest_1.expect)(fn).toHaveBeenCalledTimes(1); }); (0, vitest_1.it)('should retry the function if it fails and succeed on retry', async () => { const fn = vitest_1.vi.fn() .mockRejectedValueOnce(new Error('fail')) .mockResolvedValueOnce('success'); // Mock setTimeout to execute callback immediately vitest_1.vi.spyOn(global, 'setTimeout').mockImplementation((callback) => { callback(); return 0; }); const result = await (0, retry_utils_1.withRetry)(fn, 2); (0, vitest_1.expect)(result).toBe('success'); (0, vitest_1.expect)(fn).toHaveBeenCalledTimes(2); }); (0, vitest_1.it)('should retry multiple times before succeeding', async () => { const fn = vitest_1.vi.fn() .mockRejectedValueOnce(new Error('fail 1')) .mockRejectedValueOnce(new Error('fail 2')) .mockRejectedValueOnce(new Error('fail 3')) .mockResolvedValueOnce('success'); // Mock setTimeout to execute callback immediately vitest_1.vi.spyOn(global, 'setTimeout').mockImplementation((callback) => { callback(); return 0; }); const result = await (0, retry_utils_1.withRetry)(fn, 4); (0, vitest_1.expect)(result).toBe('success'); (0, vitest_1.expect)(fn).toHaveBeenCalledTimes(4); }); (0, vitest_1.it)('should throw an error if all retries fail', async () => { const error = new Error('persistent failure'); const fn = vitest_1.vi.fn().mockRejectedValue(error); // Mock setTimeout to execute callback immediately vitest_1.vi.spyOn(global, 'setTimeout').mockImplementation((callback) => { callback(); return 0; }); await (0, vitest_1.expect)((0, retry_utils_1.withRetry)(fn, 3)).rejects.toThrow('persistent failure'); (0, vitest_1.expect)(fn).toHaveBeenCalledTimes(3); }); (0, vitest_1.it)('should respect the configured MAX_RETRIES when no retry count is provided', async () => { // Modify the properties of the correctly typed mocked configService instance testSubjectMockedConfigService.MAX_RETRIES = 4; const fn = vitest_1.vi.fn().mockRejectedValue(new Error('fail')); // Mock setTimeout to execute callback immediately vitest_1.vi.spyOn(global, 'setTimeout').mockImplementation((callback) => { callback(); return 0; }); await (0, vitest_1.expect)((0, retry_utils_1.withRetry)(fn)).rejects.toThrow('fail'); (0, vitest_1.expect)(fn).toHaveBeenCalledTimes(4); }); (0, vitest_1.it)('should use the provided retry delay between attempts', async () => { // Modify the properties of the correctly typed mocked configService instance testSubjectMockedConfigService.RETRY_DELAY = 1000; // Spy on setTimeout const setTimeoutSpy = vitest_1.vi.spyOn(global, 'setTimeout').mockImplementation((callback) => { callback(); return 0; }); const fn = vitest_1.vi.fn() .mockRejectedValueOnce(new Error('fail')) .mockResolvedValueOnce('success'); await (0, retry_utils_1.withRetry)(fn, 2); // Now verify setTimeout was called with the correct delay // withRetry uses exponential backoff: delay * Math.pow(2, i) // For the first retry (i=0), delay is 1000 * 2^0 = 1000 (0, vitest_1.expect)(setTimeoutSpy).toHaveBeenCalledWith(vitest_1.expect.any(Function), 1000); // No need to restore, beforeEach will reset mockConfigValues.RETRY_DELAY }); }); (0, vitest_1.describe)('preprocessText', () => { (0, vitest_1.it)('should trim leading and trailing whitespace', () => { (0, vitest_1.expect)((0, text_utils_1.preprocessText)(' hello ')).toBe('hello'); (0, vitest_1.expect)((0, text_utils_1.preprocessText)('\n\nhello\n\n')).toBe('hello'); (0, vitest_1.expect)((0, text_utils_1.preprocessText)('\t\thello\t\t')).toBe('hello'); }); (0, vitest_1.it)('should replace multiple spaces with a single space', () => { (0, vitest_1.expect)((0, text_utils_1.preprocessText)('hello world')).toBe('hello world'); (0, vitest_1.expect)((0, text_utils_1.preprocessText)('hello\t\t\tworld')).toBe('hello world'); }); (0, vitest_1.it)('should preserve newlines but normalize multiple newlines', () => { (0, vitest_1.expect)((0, text_utils_1.preprocessText)('hello\nworld')).toBe('hello\nworld'); (0, vitest_1.expect)((0, text_utils_1.preprocessText)('hello\n\n\nworld')).toBe('hello\nworld'); }); (0, vitest_1.it)('should remove control characters', () => { (0, vitest_1.expect)((0, text_utils_1.preprocessText)('hello\x00world')).toBe('helloworld'); (0, vitest_1.expect)((0, text_utils_1.preprocessText)('hello\x01\x02\x03world')).toBe('helloworld'); (0, vitest_1.expect)((0, text_utils_1.preprocessText)('hello\x1Fworld')).toBe('helloworld'); }); (0, vitest_1.it)('should handle empty strings', () => { (0, vitest_1.expect)((0, text_utils_1.preprocessText)('')).toBe(''); (0, vitest_1.expect)((0, text_utils_1.preprocessText)(' ')).toBe(''); (0, vitest_1.expect)((0, text_utils_1.preprocessText)('\n\n\n')).toBe(''); }); (0, vitest_1.it)('should handle strings with only control characters', () => { (0, vitest_1.expect)((0, text_utils_1.preprocessText)('\x00\x01\x02')).toBe(''); }); (0, vitest_1.it)('should handle complex mixed input', () => { const input = ' Hello\x00\n\n World\t\t\x01With\x02\x03Multiple Spaces '; const expected = 'Hello\nWorld WithMultiple Spaces'; (0, vitest_1.expect)((0, text_utils_1.preprocessText)(input)).toBe(expected); }); (0, vitest_1.it)('should handle non-ASCII characters correctly', () => { (0, vitest_1.expect)((0, text_utils_1.preprocessText)(' Héllö Wörld ')).toBe('Héllö Wörld'); (0, vitest_1.expect)((0, text_utils_1.preprocessText)('你好\n世界')).toBe('你好\n世界'); }); }); // Removed extra closing });