@alvinveroy/codecompass
Version:
AI-powered MCP server for codebase navigation and LLM prompt optimization
542 lines (528 loc) • 28.8 kB
JavaScript
;
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();
});
});