Skip to main content
Glama
logger.test.ts13.3 kB
/** * @vitest-environment node */ import { vi, MockedFunction } from 'vitest'; // import * as fs from 'fs'; - not used directly, only through jest mocks // Mock the fs module before importing Logger vi.mock('fs', () => ({ appendFileSync: vi.fn(), writeFileSync: vi.fn(), existsSync: vi.fn(() => true), mkdirSync: vi.fn(), })); // Now import the mocked fs and Logger module const { appendFileSync, existsSync } = await import('fs'); const loggerModule = await import('../utils/logger.js'); const { Logger, createLogger, defaultLogger } = loggerModule; // Type the mocks with proper types const mockAppendFileSync = appendFileSync as MockedFunction<typeof appendFileSync>; const mockExistsSync = existsSync as MockedFunction<typeof existsSync>; describe('Logger', () => { // Environment backup let originalEnv: Record<string, string | undefined>; beforeEach(() => { // Backup environment originalEnv = { ...process.env }; // Set up common test environment process.env.LOG_FILE = '/tmp/test.log'; // Clear all mocks before each test vi.clearAllMocks(); // Reset the module-level logFileInitialized variable by reimporting the module vi.resetModules(); }); afterEach(() => { // Restore environment process.env = originalEnv; }); describe('constructor', () => { it('should create a logger with a context', () => { const logger = new Logger('TestContext'); expect(logger).toBeInstanceOf(Logger); }); it('should create a logger without a context', () => { const logger = new Logger(); expect(logger).toBeInstanceOf(Logger); }); }); describe('debug method', () => { beforeEach(() => { // Set LOG_LEVEL for each test in this describe block process.env.LOG_LEVEL = 'DEBUG'; }); it('should log debug messages when LOG_LEVEL is DEBUG', () => { const logger = new Logger('TestContext'); logger.debug('Test debug message'); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain('DEBUG'); expect(logMessage).toContain('[TestContext]'); expect(logMessage).toContain('Test debug message'); }); it('should include additional data when provided', () => { const logger = new Logger('TestContext'); const testData = { key: 'value' }; logger.debug('Test debug message', testData); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain(JSON.stringify(testData, null, 2)); }); it('should use empty string when no data is provided', () => { const logger = new Logger('TestContext'); logger.debug('Test debug message'); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).not.toContain('undefined'); }); it('should not log when LOG_FILE is not set', () => { delete process.env.LOG_FILE; const logger = new Logger('TestContext'); logger.debug('Test debug message'); expect(mockAppendFileSync).not.toHaveBeenCalled(); }); }); describe('info method', () => { it('should log info messages when LOG_LEVEL is DEBUG', () => { process.env.LOG_LEVEL = 'DEBUG'; const logger = new Logger('TestContext'); logger.info('Test info message'); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain('INFO'); expect(logMessage).toContain('[TestContext]'); expect(logMessage).toContain('Test info message'); }); it('should log info messages when LOG_LEVEL is INFO', () => { process.env.LOG_LEVEL = 'INFO'; const logger = new Logger('TestContext'); logger.info('Test info message'); expect(mockAppendFileSync).toHaveBeenCalled(); }); it('should not log info messages when LOG_LEVEL is WARN', () => { process.env.LOG_LEVEL = 'WARN'; const logger = new Logger('TestContext'); logger.info('Test info message'); expect(mockAppendFileSync).not.toHaveBeenCalled(); }); it('should include additional data when provided', () => { process.env.LOG_LEVEL = 'INFO'; const logger = new Logger('TestContext'); const testData = { key: 'value' }; logger.info('Test info message', testData); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain(JSON.stringify(testData, null, 2)); }); }); describe('warn method', () => { it('should log warn messages when LOG_LEVEL is INFO', () => { process.env.LOG_LEVEL = 'INFO'; const logger = new Logger('TestContext'); logger.warn('Test warn message'); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain('WARN'); expect(logMessage).toContain('[TestContext]'); expect(logMessage).toContain('Test warn message'); }); it('should not log warn messages when LOG_LEVEL is ERROR', () => { process.env.LOG_LEVEL = 'ERROR'; const logger = new Logger('TestContext'); logger.warn('Test warn message'); expect(mockAppendFileSync).not.toHaveBeenCalled(); }); it('should include additional data when provided', () => { process.env.LOG_LEVEL = 'WARN'; const logger = new Logger('TestContext'); const testData = { key: 'value' }; logger.warn('Test warn message', testData); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain(JSON.stringify(testData, null, 2)); }); }); describe('error method', () => { it('should log error messages when LOG_LEVEL is WARN', () => { process.env.LOG_LEVEL = 'WARN'; const logger = new Logger('TestContext'); logger.error('Test error message'); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain('ERROR'); expect(logMessage).toContain('[TestContext]'); expect(logMessage).toContain('Test error message'); }); it('should format Error objects correctly', () => { process.env.LOG_LEVEL = 'ERROR'; const logger = new Logger('TestContext'); const testError = new Error('Test error'); logger.error('Error occurred', testError); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain('Error: Test error'); }); it('should stringify objects correctly', () => { process.env.LOG_LEVEL = 'ERROR'; const logger = new Logger('TestContext'); const testData = { foo: 'bar' }; logger.error('Error with data', testData); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain(JSON.stringify(testData, null, 2)); }); it('should use empty string when no error is provided', () => { process.env.LOG_LEVEL = 'ERROR'; const logger = new Logger('TestContext'); logger.error('Test error message'); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).not.toContain('undefined'); }); }); describe('default exports', () => { it('should export a default logger with DeepSourceMCP context', () => { process.env.LOG_LEVEL = 'DEBUG'; defaultLogger.debug('Test message'); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain('[DeepSourceMCP]'); }); it('should provide a createLogger helper function', () => { process.env.LOG_LEVEL = 'DEBUG'; const customLogger = createLogger('CustomContext'); customLogger.debug('Test message'); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain('[CustomContext]'); }); }); describe('log file initialization', () => { it('should write to log file when configured with LOG_FILE', () => { process.env.LOG_FILE = '/tmp/test.log'; process.env.LOG_LEVEL = 'DEBUG'; // Mock file system operations mockExistsSync.mockReturnValue(true); // File and directory already exist mockAppendFileSync.mockImplementation(() => undefined); const logger = new Logger('TestContext'); logger.debug('Test message'); // Should write to the log file expect(mockAppendFileSync).toHaveBeenCalledWith('/tmp/test.log', expect.any(String)); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain('DEBUG'); expect(logMessage).toContain('Test message'); }); it('should handle write errors gracefully', () => { process.env.LOG_FILE = '/tmp/test.log'; process.env.LOG_LEVEL = 'DEBUG'; // Mock file system operations to throw error mockExistsSync.mockReturnValue(true); mockAppendFileSync.mockImplementation(() => { throw new Error('Permission denied'); }); const logger = new Logger('TestContext'); // Should not throw even when file operations fail expect(() => logger.debug('Test message')).not.toThrow(); }); it('should handle file initialization errors gracefully', async () => { process.env.LOG_FILE = '/tmp/test.log'; process.env.LOG_LEVEL = 'DEBUG'; // Clear the require cache for logger to reset module state vi.resetModules(); // Mock the fs module with a mkdirSync that throws an error const mockMkdirSync = vi.fn(() => { throw new Error('Permission denied'); }); const mockWriteFileSync = vi.fn(); const mockAppendFileSync = vi.fn(); const mockExistsSync = vi.fn(() => false); vi.doMock('fs', () => ({ appendFileSync: mockAppendFileSync, writeFileSync: mockWriteFileSync, existsSync: mockExistsSync, mkdirSync: mockMkdirSync, })); // Re-import the modules after mocking const loggerModule = await import('../utils/logger.js'); const { Logger } = loggerModule; const logger = new Logger('TestContext'); // Should not throw when log file initialization fails expect(() => logger.debug('Test message')).not.toThrow(); // mkdirSync should have been called during initialization expect(mockMkdirSync).toHaveBeenCalled(); }); it('should create directory when it does not exist', async () => { process.env.LOG_FILE = '/tmp/test.log'; process.env.LOG_LEVEL = 'DEBUG'; // Clear the require cache for logger to reset module state vi.resetModules(); // Mock the fs module to simulate a directory that doesn't exist const mockMkdirSync = vi.fn(); const mockWriteFileSync = vi.fn(); const mockAppendFileSync = vi.fn(); const mockExistsSync = vi.fn(() => false); vi.doMock('fs', () => ({ appendFileSync: mockAppendFileSync, writeFileSync: mockWriteFileSync, existsSync: mockExistsSync, mkdirSync: mockMkdirSync, })); // Re-import the modules after mocking const loggerModule = await import('../utils/logger.js'); const { Logger } = loggerModule; const logger = new Logger('TestContext'); // Trigger initialization logger.debug('Test message'); // mkdirSync should have been called to create the directory expect(mockMkdirSync).toHaveBeenCalledWith('/tmp', { recursive: true }); // writeFileSync should have been called to create the log file expect(mockWriteFileSync).toHaveBeenCalledWith('/tmp/test.log', ''); }); }); describe('error string fallback', () => { it('should use String() fallback when JSON.stringify throws', () => { process.env.LOG_LEVEL = 'ERROR'; const logger = new Logger('TestContext'); // Create a circular reference object that will fail JSON.stringify const circular: Record<string, unknown> = {}; circular.ref = circular; // Mock JSON.stringify to throw const originalStringify = JSON.stringify; JSON.stringify = vi.fn().mockImplementation(() => { throw new Error('Circular reference'); }); logger.error('Error with circular ref', circular); expect(mockAppendFileSync).toHaveBeenCalled(); const logMessage = String(mockAppendFileSync.mock.calls[0]?.[1]); expect(logMessage).toContain('[object Object]'); // This is what String() would produce // Restore JSON.stringify JSON.stringify = originalStringify; }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sapientpants/deepsource-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server