UNPKG

sfcc-dev-mcp

Version:

MCP server for Salesforce B2C Commerce Cloud development assistance including logs, debugging, and development tools

304 lines (228 loc) 10.8 kB
import { Logger } from '../src/utils/logger'; import { existsSync, readFileSync, rmSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; describe('Logger', () => { let logger: Logger; let testLogDir: string; beforeEach(() => { // Create a unique test log directory for each test testLogDir = join(tmpdir(), `sfcc-mcp-logs-test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`); // Clean up if directory somehow exists if (existsSync(testLogDir)) { rmSync(testLogDir, { recursive: true, force: true }); } }); afterEach(() => { // Clean up test log directory if (existsSync(testLogDir)) { rmSync(testLogDir, { recursive: true, force: true }); } }); describe('constructor', () => { it('should create logger and create log directory', () => { logger = new Logger('TEST', true, false, testLogDir); expect(existsSync(testLogDir)).toBe(true); expect(logger.getLogDirectory()).toBe(testLogDir); }); it('should create logger with custom context and write logs correctly', () => { logger = new Logger('CUSTOM-CONTEXT', true, false, testLogDir); logger.log('test message'); const logFile = join(testLogDir, 'sfcc-mcp-info.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[CUSTOM-CONTEXT\] test message\n$/); }); it('should create logger with timestamp disabled', () => { logger = new Logger('TEST', false, false, testLogDir); logger.log('test message'); const logFile = join(testLogDir, 'sfcc-mcp-info.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toBe('[TEST] test message\n'); }); it('should create logger with debug enabled', () => { logger = new Logger('TEST', true, true, testLogDir); logger.debug('debug message'); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toMatch( /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] \[DEBUG\] debug message\n$/, ); }); }); describe('logging methods', () => { beforeEach(() => { logger = new Logger('TEST', true, false, testLogDir); }); it('should write info messages to info log file', () => { logger.info('info message'); const logFile = join(testLogDir, 'sfcc-mcp-info.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] info message\n$/); }); it('should write log messages to info log file', () => { logger.log('log message'); const logFile = join(testLogDir, 'sfcc-mcp-info.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] log message\n$/); }); it('should write warning messages to warn log file', () => { logger.warn('warning message'); const logFile = join(testLogDir, 'sfcc-mcp-warn.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] warning message\n$/); }); it('should write error messages to error log file', () => { logger.error('error message'); const logFile = join(testLogDir, 'sfcc-mcp-error.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] error message\n$/); }); it('should write debug messages to debug log file when debug is enabled', () => { logger = new Logger('TEST', true, true, testLogDir); logger.debug('debug message'); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toMatch( /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] \[DEBUG\] debug message\n$/, ); }); it('should not write debug messages when debug is disabled', () => { logger = new Logger('TEST', true, false, testLogDir); logger.debug('debug message'); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(false); }); it('should handle additional arguments', () => { const testObject = { key: 'value', number: 42 }; logger.info('message with args', 'string arg', testObject); const logFile = join(testLogDir, 'sfcc-mcp-info.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toContain('message with args'); expect(logContent).toContain('string arg'); expect(logContent).toContain('"key": "value"'); expect(logContent).toContain('"number": 42'); }); }); describe('debug logging methods', () => { beforeEach(() => { logger = new Logger('TEST', true, true, testLogDir); }); it('should log method entry', () => { logger.methodEntry('testMethod', { param1: 'value1' }); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toContain('[DEBUG] Entering method: testMethod with params:'); expect(logContent).toContain('"param1":"value1"'); }); it('should log method entry without params', () => { logger.methodEntry('testMethod'); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toContain('[DEBUG] Entering method: testMethod'); expect(logContent).not.toContain('with params:'); }); it('should log method exit', () => { logger.methodExit('testMethod', { result: 'success' }); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toContain('[DEBUG] Exiting method: testMethod with result:'); expect(logContent).toContain('"result":"success"'); }); it('should log method exit without result', () => { logger.methodExit('testMethod'); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toContain('[DEBUG] Exiting method: testMethod'); expect(logContent).not.toContain('with result:'); }); it('should log timing information', () => { const startTime = Date.now() - 100; // Simulate 100ms operation logger.timing('testOperation', startTime); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toMatch(/\[DEBUG\] Performance: testOperation took \d+ms/); }); }); describe('utility methods', () => { beforeEach(() => { logger = new Logger('TEST', true, false, testLogDir); }); it('should create child logger with combined context', () => { const childLogger = logger.createChildLogger('CHILD'); childLogger.info('child message'); const logFile = join(testLogDir, 'sfcc-mcp-info.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toContain('[TEST:CHILD] child message'); }); it('should enable debug logging dynamically', () => { logger.setDebugEnabled(true); logger.debug('now debug is enabled'); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(true); const logContent = readFileSync(logFile, 'utf8'); expect(logContent).toContain('[DEBUG] now debug is enabled'); }); it('should disable debug logging dynamically', () => { logger = new Logger('TEST', true, true, testLogDir); logger.setDebugEnabled(false); logger.debug('this should not appear'); const logFile = join(testLogDir, 'sfcc-mcp-debug.log'); expect(existsSync(logFile)).toBe(false); }); it('should return log directory path', () => { const logDirectory = logger.getLogDirectory(); expect(logDirectory).toBe(testLogDir); expect(existsSync(logDirectory)).toBe(true); }); }); describe('error handling', () => { it('should handle file write errors gracefully', () => { // Mock appendFileSync to throw an error // eslint-disable-next-line @typescript-eslint/no-require-imports const fsMock = jest.spyOn(require('fs'), 'appendFileSync'); fsMock.mockImplementation(() => { throw new Error('File write error'); }); // Mock stderr.write to capture fallback behavior const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true); logger = new Logger('TEST', true, false, testLogDir); logger.error('test error message'); // Should have attempted to write to stderr as fallback expect(stderrSpy).toHaveBeenCalledWith('[LOGGER ERROR] Could not write to log file: Error: File write error\n'); expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('test error message')); stderrSpy.mockRestore(); fsMock.mockRestore(); }); it('should not fallback to stderr for non-error log levels', () => { // Mock appendFileSync to throw an error // eslint-disable-next-line @typescript-eslint/no-require-imports const fsMock = jest.spyOn(require('fs'), 'appendFileSync'); fsMock.mockImplementation(() => { throw new Error('File write error'); }); // Mock stderr.write to capture fallback behavior const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true); logger = new Logger('TEST', true, false, testLogDir); logger.info('test info message'); // Should not have written to stderr for non-error levels expect(stderrSpy).not.toHaveBeenCalled(); stderrSpy.mockRestore(); fsMock.mockRestore(); }); }); });