UNPKG

@alvinveroy/codecompass

Version:

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

542 lines (528 loc) 28.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const server_1 = require("../lib/server"); // Import startServer // Import actual modules to be mocked const http_1 = __importDefault(require("http")); const axios_1 = __importDefault(require("axios")); // Import axios // Define stable mock for McpServer.connect const mcpConnectStableMock = vitest_1.vi.fn(); // Mock dependencies vitest_1.vi.mock('@modelcontextprotocol/sdk/server/mcp.js', async (importOriginal) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const actual = await importOriginal(); return { ...actual, McpServer: vitest_1.vi.fn().mockImplementation(() => ({ connect: mcpConnectStableMock, // Use stable mock tool: vitest_1.vi.fn(), resource: vitest_1.vi.fn(), prompt: vitest_1.vi.fn(), // Added prompt mock })), ResourceTemplate: vitest_1.vi.fn().mockImplementation((uriTemplate, _options) => { // Basic mock for ResourceTemplate constructor return { uriTemplate }; }) }; }); // Corrected mock path for configService and logger vitest_1.vi.mock('../lib/config-service', async (importOriginal) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const actual = await importOriginal(); return { ...actual, // Spread actual to keep non-mocked parts if any, or specific exports configService: { // Provide all properties and methods accessed by server.ts // Basic defaults, specific tests can override via vi.spyOn or direct mock value changes HTTP_PORT: 3001, OLLAMA_HOST: 'http://127.0.0.1:11434', QDRANT_HOST: 'http://127.0.0.1:6333', COLLECTION_NAME: 'test-collection', SUGGESTION_MODEL: 'test-model', SUGGESTION_PROVIDER: 'ollama', EMBEDDING_MODEL: 'nomic-embed-text:v1.5', EMBEDDING_PROVIDER: 'ollama', DEEPSEEK_API_KEY: '', OPENAI_API_KEY: '', GEMINI_API_KEY: '', CLAUDE_API_KEY: '', VERSION: 'test-version', // Add if server.ts uses configService.VERSION reloadConfigsFromFile: vitest_1.vi.fn(), // Add any other properties/methods from ConfigService that server.ts uses // For example, if it uses specific model names for summarization/refinement: SUMMARIZATION_MODEL: 'test-summary-model', REFINEMENT_MODEL: 'test-refinement-model', // Add any other config values used in startServer MAX_SNIPPET_LENGTH: 500, }, logger: { info: vitest_1.vi.fn(), warn: vitest_1.vi.fn(), error: vitest_1.vi.fn(), debug: vitest_1.vi.fn(), // Add debug if used add: vitest_1.vi.fn(), // If logger.add is called } }; }); vitest_1.vi.mock('../lib/ollama', () => ({ checkOllama: vitest_1.vi.fn().mockResolvedValue(true), checkOllamaModel: vitest_1.vi.fn().mockResolvedValue(true), // generateEmbedding: vi.fn().mockResolvedValue([0.1, 0.2, 0.3]), // Not directly used by startServer // generateSuggestion: vi.fn().mockResolvedValue('Test suggestion'), // Not directly used by startServer // summarizeSnippet: vi.fn().mockResolvedValue('Test summary') // Not directly used by startServer })); vitest_1.vi.mock('../lib/qdrant', () => ({ initializeQdrant: vitest_1.vi.fn().mockResolvedValue({ search: vitest_1.vi.fn().mockResolvedValue([]), getCollections: vitest_1.vi.fn().mockResolvedValue({ collections: [] }) }) })); vitest_1.vi.mock('../lib/repository', async (importOriginal) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const actual = await importOriginal(); return { ...actual, // Keep actual exports like IndexingStatusReport type validateGitRepository: vitest_1.vi.fn().mockResolvedValue(true), indexRepository: vitest_1.vi.fn().mockResolvedValue(undefined), getRepositoryDiff: vitest_1.vi.fn().mockResolvedValue('+ test\n- test2'), getGlobalIndexingStatus: vitest_1.vi.fn().mockReturnValue({ status: 'idle', message: 'Mocked idle status', overallProgress: 0, lastUpdatedAt: new Date().toISOString(), }), }; }); vitest_1.vi.mock('isomorphic-git', async (importOriginal) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const actual = await importOriginal(); return { ...actual, // Keep actual exports default: { ...(actual.default || {}), // Spread existing default export properties if any listFiles: vitest_1.vi.fn().mockResolvedValue(['file1.ts', 'file2.ts']), // Add other isomorphic-git functions if server.ts uses them directly }, // If named exports from isomorphic-git are used, mock them here too // e.g., resolveRef: vi.fn(), }; }); // --- START: vi.mock for 'http' and related definitions --- // Define shared mock function instances for the http server methods const mockHttpServerListenFn = vitest_1.vi.fn((_port, listeningListenerOrHostname, _backlog, listeningListener) => { if (typeof listeningListenerOrHostname === 'function') { listeningListenerOrHostname(); // Call the listener if it's the second argument } else if (typeof listeningListener === 'function') { listeningListener(); // Call the listener if it's the fourth argument } return mockHttpServerInstance; // Return the mock server instance }); const mockHttpServerOnFn = vitest_1.vi.fn(); const mockHttpServerCloseFn = vitest_1.vi.fn(); const mockHttpServerAddressFn = vitest_1.vi.fn(); const mockHttpServerSetTimeoutFn = vitest_1.vi.fn(); // Define the mock http server instance that createServer will return const mockHttpServerInstance = { listen: mockHttpServerListenFn, on: mockHttpServerOnFn, close: mockHttpServerCloseFn, address: mockHttpServerAddressFn, setTimeout: mockHttpServerSetTimeoutFn, }; // Cast to satisfy http.Server type vitest_1.vi.mock('http', async (importOriginal) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const actualHttpModule = await importOriginal(); // Ensure httpModule is imported as type const mockHttpMethods = { createServer: vitest_1.vi.fn(() => mockHttpServerInstance), Server: vitest_1.vi.fn(() => mockHttpServerInstance), // Mock constructor IncomingMessage: actualHttpModule.IncomingMessage, // Preserve actual types if needed ServerResponse: actualHttpModule.ServerResponse, // Preserve actual types if needed // Add any other http members that server.ts might use directly from the http import }; return { ...mockHttpMethods, // Makes methods available for `import * as http from 'http'` default: mockHttpMethods, // This is what `import http from 'http'` will resolve to }; }); // Mock for axios vitest_1.vi.mock('axios'); // Mock for process.exit let mockProcessExit; // Mock for console.info const mockConsoleInfo = vitest_1.vi.spyOn(console, 'info').mockImplementation(vitest_1.vi.fn()); // Mock for ../lib/version vitest_1.vi.mock('../lib/version', () => ({ VERSION: 'test-version-from-mock' })); // Mock for ../lib/llm-provider vitest_1.vi.mock('../lib/llm-provider', async (importOriginal) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const actual = await importOriginal(); return { ...actual, getLLMProvider: vitest_1.vi.fn().mockResolvedValue({ checkConnection: vitest_1.vi.fn().mockResolvedValue(true), generateText: vitest_1.vi.fn().mockResolvedValue('mock llm text'), }), switchSuggestionModel: vitest_1.vi.fn().mockResolvedValue(true), }; }); vitest_1.vi.mock('fs/promises', async (importOriginal) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const actual = await importOriginal(); return { ...actual, readFile: vitest_1.vi.fn().mockResolvedValue('mock file content'), }; }); (0, vitest_1.describe)('Server Tool Response Formatting', () => { (0, vitest_1.beforeEach)(() => { vitest_1.vi.clearAllMocks(); }); (0, vitest_1.describe)('normalizeToolParams', () => { (0, vitest_1.it)('should handle string input as query', () => { const result = (0, server_1.normalizeToolParams)('test query'); (0, vitest_1.expect)(result).toEqual({ query: 'test query' }); }); (0, vitest_1.it)('should handle object input with query property', () => { const result = (0, server_1.normalizeToolParams)({ query: 'test query' }); (0, vitest_1.expect)(result).toEqual({ query: 'test query' }); }); (0, vitest_1.it)('should handle object input without query property', () => { const input = { other: 'value' }; const result = (0, server_1.normalizeToolParams)(input); (0, vitest_1.expect)(result).toEqual(input); // Expect the object to be returned as-is }); (0, vitest_1.it)('should handle primitive values', () => { const result = (0, server_1.normalizeToolParams)(123); (0, vitest_1.expect)(result).toEqual({ query: '123' }); }); (0, vitest_1.it)('should handle null or undefined input', () => { (0, vitest_1.expect)((0, server_1.normalizeToolParams)(null)).toEqual({ query: "" }); (0, vitest_1.expect)((0, server_1.normalizeToolParams)(undefined)).toEqual({ query: "" }); }); (0, vitest_1.it)('should handle stringified JSON object', () => { const input = { key: "value", num: 1 }; const result = (0, server_1.normalizeToolParams)(JSON.stringify(input)); (0, vitest_1.expect)(result).toEqual(input); }); }); (0, vitest_1.describe)('Tool Response Formatting', () => { // ... existing tests for tool response formatting ... // These tests are structural and don't involve server startup logic // so they can remain as they are. (0, vitest_1.it)('should verify search_code tool returns markdown formatted response', () => { // This is a structural test to ensure the response format is correct // The actual implementation would be tested with integration tests const response = ` # Search Results for: "test query" ## test/file.ts - Last Modified: 2025-05-07T00:00:00Z - Relevance: 0.95 ### Code Snippet \`\`\` Test content \`\`\` ### Summary Test summary `; // Verify the response contains markdown formatting elements (0, vitest_1.expect)(response).toContain('# Search Results'); (0, vitest_1.expect)(response).toContain('## test/file.ts'); (0, vitest_1.expect)(response).toContain('### Code Snippet'); (0, vitest_1.expect)(response).toContain('```'); (0, vitest_1.expect)(response).toContain('### Summary'); }); (0, vitest_1.it)('should verify generate_suggestion tool returns markdown formatted response', () => { const response = ` # Code Suggestion for: "test query" ## Suggestion Test suggestion ## Context Used ### test/file.ts - Last modified: 2025-05-07T00:00:00Z - Relevance: 0.95 \`\`\` Test content \`\`\` ## Recent Changes \`\`\` + test - test2 \`\`\` `; // Verify the response contains markdown formatting elements (0, vitest_1.expect)(response).toContain('# Code Suggestion'); (0, vitest_1.expect)(response).toContain('## Suggestion'); (0, vitest_1.expect)(response).toContain('## Context Used'); (0, vitest_1.expect)(response).toContain('### test/file.ts'); (0, vitest_1.expect)(response).toContain('```'); (0, vitest_1.expect)(response).toContain('## Recent Changes'); }); (0, vitest_1.it)('should verify get_repository_context tool returns markdown formatted response', () => { const response = ` # Repository Context Summary ## Summary Test suggestion ## Relevant Files ### test/file.ts - Last modified: 2025-05-07T00:00:00Z - Relevance: 0.95 \`\`\` Test content \`\`\` ## Recent Changes \`\`\` + test - test2 \`\`\` `; // Verify the response contains markdown formatting elements (0, vitest_1.expect)(response).toContain('# Repository Context Summary'); (0, vitest_1.expect)(response).toContain('## Summary'); (0, vitest_1.expect)(response).toContain('## Relevant Files'); (0, vitest_1.expect)(response).toContain('### test/file.ts'); (0, vitest_1.expect)(response).toContain('```'); (0, vitest_1.expect)(response).toContain('## Recent Changes'); }); }); }); (0, vitest_1.describe)('Server Startup and Port Handling', () => { // Use the new mock-aware types let mcs; // mcs for mockedConfigService let ml; // ml for mockedLogger let mockedMcpServerConnect; // Typed the mock instance let originalNodeEnv; (0, vitest_1.beforeEach)(async () => { originalNodeEnv = process.env.NODE_ENV; // Store original NODE_ENV process.env.NODE_ENV = 'test'; // Set for tests vitest_1.vi.clearAllMocks(); // Initialize mockProcessExit here mockProcessExit = vitest_1.vi.spyOn(process, 'exit').mockImplementation(vitest_1.vi.fn()); mockHttpServerCloseFn.mockReset(); // Get the mocked configService and logger from the vi.mock factory // This ensures we are interacting with the same mocked objects that the SUT uses. const mockedConfigModule = await import('../lib/config-service.js'); // Cast to 'unknown' first, then to the mock type mcs = mockedConfigModule.configService; // Keep as unknown for complex mock/real hybrid ml = mockedConfigModule.logger; // Keep as unknown for complex mock/real hybrid // Clear mocks using the typed instances // mcs and ml are already assigned from the first import. // No need to re-import or re-assign. // Clear mocks using the typed instances ml.info?.mockClear(); ml.warn?.mockClear(); ml.error?.mockClear(); ml.debug?.mockClear(); mcs.reloadConfigsFromFile?.mockClear(); // Assign and clear the stable McpServer.connect mock mcpConnectStableMock.mockClear(); mockedMcpServerConnect = mcpConnectStableMock; // Default mock for axios.get // eslint-disable-next-line @typescript-eslint/unbound-method vitest_1.vi.mocked(axios_1.default.get).mockResolvedValue({ status: 200, data: {} }); }); (0, vitest_1.afterEach)(() => { process.env.NODE_ENV = originalNodeEnv; // Restore original NODE_ENV // Restore any global mocks if necessary, though vi.clearAllMocks() handles most if (mockProcessExit) mockProcessExit.mockClear(); // mockProcessExit is defined in beforeEach mockConsoleInfo.mockClear(); }); (0, vitest_1.it)('should start the server and listen on the configured port if free', async () => { await (0, server_1.startServer)('/fake/repo'); (0, vitest_1.expect)(mcs.reloadConfigsFromFile).toHaveBeenCalled(); (0, vitest_1.expect)(http_1.default.createServer).toHaveBeenCalled(); (0, vitest_1.expect)(mockHttpServerListenFn).toHaveBeenCalledWith(mcs.HTTP_PORT, vitest_1.expect.any(Function)); // Changed mockedConfigService to mcs (0, vitest_1.expect)(ml.info).toHaveBeenCalledWith(vitest_1.expect.stringContaining(`CodeCompass HTTP server listening on port ${mcs.HTTP_PORT} for status and notifications.`)); // Changed mockedLogger to ml and mockedConfigService to mcs // Removed: expect(mockedMcpServerConnect).toHaveBeenCalled(); // This assertion is incorrect for this test, as McpServer.connect is only called // upon an actual MCP client initialization request to the /mcp endpoint, // not during general HTTP server startup. (0, vitest_1.expect)(mockProcessExit).not.toHaveBeenCalled(); }); // Add the new 'it' block here, starting around line 395 of your provided file content (0, vitest_1.it)('should handle EADDRINUSE, detect existing CodeCompass server, log status, and exit with 0', async () => { // Define the mock status for an existing server const existingServerPingVersion = 'existing-ping-version'; // Version obtained from ping const mockExistingServerStatus = { // No version property here, as IndexingStatusReport does not define it status: 'idle', message: 'Existing server idle', overallProgress: 100, lastUpdatedAt: new Date().toISOString(), }; mockHttpServerListenFn.mockImplementation((_portOrOptions, _hostnameOrListener, _backlogOrListener, _listeningListener) => { const errorArgs = mockHttpServerOnFn.mock.calls.find(call => call[0] === 'error'); if (errorArgs && typeof errorArgs[1] === 'function') { const errorHandler = errorArgs[1]; const error = new Error('listen EADDRINUSE: address already in use'); error.code = 'EADDRINUSE'; errorHandler(error); } return mockHttpServerInstance; }); // Mock axios.get specifically for this test // eslint-disable-next-line @typescript-eslint/unbound-method vitest_1.vi.mocked(axios_1.default.get).mockImplementation((url) => { if (url.endsWith('/api/ping')) { return Promise.resolve({ status: 200, data: { service: "CodeCompass", status: "ok", version: existingServerPingVersion } }); } if (url.endsWith('/api/indexing-status')) { return Promise.resolve({ status: 200, data: mockExistingServerStatus }); } return Promise.resolve({ status: 404, data: {} }); // Default for other calls }); // Expect ServerStartupError with specific message and code await (0, vitest_1.expect)((0, server_1.startServer)('/fake/repo')).rejects.toThrow( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument vitest_1.expect.objectContaining({ name: "ServerStartupError", message: `Port ${mcs.HTTP_PORT} in use by another CodeCompass instance.`, exitCode: 0 })); (0, vitest_1.expect)(ml.warn).toHaveBeenCalledWith(`HTTP Port ${mcs.HTTP_PORT} is already in use. Attempting to ping...`); // eslint-disable-next-line @typescript-eslint/unbound-method (0, vitest_1.expect)(axios_1.default.get).toHaveBeenCalledWith(`http://localhost:${mcs.HTTP_PORT}/api/ping`, { timeout: 500 }); // eslint-disable-next-line @typescript-eslint/unbound-method (0, vitest_1.expect)(axios_1.default.get).toHaveBeenCalledWith(`http://localhost:${mcs.HTTP_PORT}/api/indexing-status`, { timeout: 1000 }); (0, vitest_1.expect)(mockConsoleInfo).toHaveBeenCalledWith(vitest_1.expect.stringContaining(`--- Status of existing CodeCompass instance on port ${mcs.HTTP_PORT} ---`)); (0, vitest_1.expect)(mockConsoleInfo).toHaveBeenCalledWith(vitest_1.expect.stringContaining(`Version: ${existingServerPingVersion}`)); // Use version from ping (0, vitest_1.expect)(mockConsoleInfo).toHaveBeenCalledWith(vitest_1.expect.stringContaining(`Status: ${mockExistingServerStatus.status}`)); (0, vitest_1.expect)(mockConsoleInfo).toHaveBeenCalledWith(vitest_1.expect.stringContaining(`Progress: ${mockExistingServerStatus.overallProgress}%`)); (0, vitest_1.expect)(ml.info).toHaveBeenCalledWith("Current instance will exit as another CodeCompass server is already running."); // mockProcessExit is not directly called by startServer's main catch in test mode anymore (0, vitest_1.expect)(mockedMcpServerConnect).not.toHaveBeenCalled(); }); (0, vitest_1.it)('should handle EADDRINUSE, detect a non-CodeCompass server, log error, and exit with 1', async () => { mockHttpServerListenFn.mockImplementation((_portOrOptions, _hostnameOrListener, _backlogOrListener, _listeningListener) => { const errorArgs = mockHttpServerOnFn.mock.calls.find(call => call[0] === 'error'); if (errorArgs && typeof errorArgs[1] === 'function') { const errorHandler = errorArgs[1]; const error = new Error('listen EADDRINUSE: address already in use'); error.code = 'EADDRINUSE'; errorHandler(error); } return mockHttpServerInstance; }); // Mock axios.get for /api/ping // eslint-disable-next-line @typescript-eslint/unbound-method vitest_1.vi.mocked(axios_1.default.get).mockImplementation((url) => { if (url.endsWith('/api/ping')) { // Ping returns non-CodeCompass response or error return Promise.resolve({ status: 200, data: { service: "OtherService" } }); } return Promise.resolve({ status: 404, data: {} }); }); await (0, vitest_1.expect)((0, server_1.startServer)('/fake/repo')).rejects.toThrow( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument vitest_1.expect.objectContaining({ name: "ServerStartupError", message: `Port ${mcs.HTTP_PORT} in use by non-CodeCompass server.`, exitCode: 1 })); // Verify the specific error log calls in order (0, vitest_1.expect)(ml.error).toHaveBeenNthCalledWith(1, vitest_1.expect.stringContaining(`Port ${mcs.HTTP_PORT} is in use by non-CodeCompass server. Response: {"service":"OtherService"}`)); (0, vitest_1.expect)(ml.error).toHaveBeenNthCalledWith(2, vitest_1.expect.stringContaining('Please free the port or configure a different one')); (0, vitest_1.expect)(ml.error).toHaveBeenNthCalledWith(3, "Failed to start CodeCompass", vitest_1.expect.objectContaining({ message: `Port ${mcs.HTTP_PORT} in use by non-CodeCompass server.` })); (0, vitest_1.expect)(mockedMcpServerConnect).not.toHaveBeenCalled(); }); (0, vitest_1.it)('should handle EADDRINUSE, ping fails (e.g. ECONNREFUSED), log error, and exit with 1', async () => { mockHttpServerListenFn.mockImplementation((_portOrOptions, _hostnameOrListener, _backlogOrListener, _listeningListener) => { const errorArgs = mockHttpServerOnFn.mock.calls.find(call => call[0] === 'error'); if (errorArgs && typeof errorArgs[1] === 'function') { const errorHandler = errorArgs[1]; const error = new Error('listen EADDRINUSE'); error.code = 'EADDRINUSE'; errorHandler(error); // Simulate EADDRINUSE } return mockHttpServerInstance; }); const pingError = new Error('Connection refused'); pingError.code = 'ECONNREFUSED'; // eslint-disable-next-line @typescript-eslint/unbound-method vitest_1.vi.mocked(axios_1.default.get).mockImplementation((url) => { if (url.endsWith('/api/ping')) { return Promise.reject(pingError); } return Promise.resolve({ status: 404, data: {} }); }); await (0, vitest_1.expect)((0, server_1.startServer)('/fake/repo')).rejects.toThrow( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument vitest_1.expect.objectContaining({ name: "ServerStartupError", message: `Port ${mcs.HTTP_PORT} in use or ping failed.`, exitCode: 1 })); (0, vitest_1.expect)(ml.error).toHaveBeenCalledWith(vitest_1.expect.stringContaining(`Port ${mcs.HTTP_PORT} is in use by an unknown service or the existing CodeCompass server is unresponsive to pings.`)); (0, vitest_1.expect)(ml.error).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Ping error details: Error: Connection refused')); (0, vitest_1.expect)(ml.error).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Please free the port or configure a different one')); // Add this new expectation for the log from the main catch block (0, vitest_1.expect)(ml.error).toHaveBeenCalledWith("Failed to start CodeCompass", vitest_1.expect.objectContaining({ message: `Port ${mcs.HTTP_PORT} in use or ping failed.` })); (0, vitest_1.expect)(mockedMcpServerConnect).not.toHaveBeenCalled(); // MCP server should not connect }); (0, vitest_1.it)('should handle EADDRINUSE, ping OK, but /api/indexing-status fails, log error, and exit with 1', async () => { mockHttpServerListenFn.mockImplementation(() => { const errorArgs = mockHttpServerOnFn.mock.calls.find(call => call[0] === 'error'); if (errorArgs && typeof errorArgs[1] === 'function') { const errorHandler = errorArgs[1]; const error = new Error('listen EADDRINUSE'); error.code = 'EADDRINUSE'; errorHandler(error); // Simulate EADDRINUSE } return mockHttpServerInstance; }); // Mock axios.get for /api/ping // eslint-disable-next-line @typescript-eslint/unbound-method vitest_1.vi.mocked(axios_1.default.get).mockImplementation((url) => { if (url.endsWith('/api/ping')) { return Promise.resolve({ status: 200, data: { service: "CodeCompass", status: "ok", version: "test-version" } }); // Ping success } if (url.endsWith('/api/indexing-status')) { return Promise.reject(new Error('Failed to fetch status')); // Status fetch fails } // This was incorrect, should be the response for axios.get return Promise.resolve({ status: 404, data: {} }); }); await (0, vitest_1.expect)((0, server_1.startServer)('/fake/repo')).rejects.toThrow( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument vitest_1.expect.objectContaining({ name: "ServerStartupError", // The message might vary slightly based on the exact point of failure in the EADDRINUSE logic for status fetch // For example, if it's the catch block after statusError: message: `Port ${mcs.HTTP_PORT} in use, status fetch error.`, exitCode: 1 })); (0, vitest_1.expect)(ml.error).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Error fetching status from existing CodeCompass server')); (0, vitest_1.expect)(mockedMcpServerConnect).not.toHaveBeenCalled(); }); (0, vitest_1.it)('should handle non-EADDRINUSE errors on HTTP server and exit with 1', async () => { const otherError = new Error('Some other server error'); otherError.code = 'EACCES'; // Example of another error code mockHttpServerListenFn.mockImplementation(() => { // Simulate listen failure by invoking the 'error' handler const errorArgs = mockHttpServerOnFn.mock.calls.find(call => call[0] === 'error'); if (errorArgs && typeof errorArgs[1] === 'function') { const errorHandler = errorArgs[1]; errorHandler(otherError); } return mockHttpServerInstance; }); // We need to ensure listen is called to trigger the 'on' setup await (0, vitest_1.expect)((0, server_1.startServer)('/fake/repo')).rejects.toThrow( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument vitest_1.expect.objectContaining({ name: "ServerStartupError", message: `HTTP server error: ${otherError.message}`, exitCode: 1 })); // Check that the 'on' handler was attached (0, vitest_1.expect)(mockHttpServerOnFn).toHaveBeenCalledWith('error', vitest_1.expect.any(Function)); // Check for the specific error log for non-EADDRINUSE (0, vitest_1.expect)(ml.error).toHaveBeenCalledWith(`Failed to start HTTP server on port ${mcs.HTTP_PORT}: ${otherError.message}`); (0, vitest_1.expect)(mockedMcpServerConnect).not.toHaveBeenCalled(); }); });