Skip to main content
Glama

SFCC Development MCP Server

by taurgis
log-handler.test.ts•14.2 kB
import { LogToolHandler } from '../src/core/handlers/log-handler.js'; import { HandlerContext } from '../src/core/handlers/base-handler.js'; import { SFCCLogClient } from '../src/clients/log-client.js'; import { Logger } from '../src/utils/logger.js'; // Mock the SFCCLogClient jest.mock('../src/clients/log-client.js'); describe('LogToolHandler', () => { let mockLogger: jest.Mocked<Logger>; let mockLogClient: jest.Mocked<SFCCLogClient>; let context: HandlerContext; let handler: LogToolHandler; beforeEach(() => { mockLogger = { debug: jest.fn(), log: jest.fn(), error: jest.fn(), timing: jest.fn(), methodEntry: jest.fn(), methodExit: jest.fn(), } as any; mockLogClient = { getLatestLogs: jest.fn(), summarizeLogs: jest.fn(), searchLogs: jest.fn(), listLogFiles: jest.fn(), getLogFileContents: jest.fn(), } as any; (SFCCLogClient as jest.MockedClass<typeof SFCCLogClient>).mockImplementation(() => mockLogClient); jest.spyOn(Logger, 'getChildLogger').mockReturnValue(mockLogger); context = { logger: mockLogger, config: { hostname: 'test.demandware.net', username: 'test', password: 'test', clientId: 'test', clientSecret: 'test', }, capabilities: { canAccessLogs: true, canAccessOCAPI: true }, }; handler = new LogToolHandler(context, 'Log'); }); afterEach(() => { jest.restoreAllMocks(); }); describe('canHandle', () => { it('should handle log-related tools', () => { expect(handler.canHandle('get_latest_error')).toBe(true); expect(handler.canHandle('get_latest_warn')).toBe(true); expect(handler.canHandle('get_latest_info')).toBe(true); expect(handler.canHandle('get_latest_debug')).toBe(true); expect(handler.canHandle('summarize_logs')).toBe(true); expect(handler.canHandle('search_logs')).toBe(true); expect(handler.canHandle('list_log_files')).toBe(true); expect(handler.canHandle('get_log_file_contents')).toBe(true); }); it('should not handle non-log tools', () => { expect(handler.canHandle('get_sfcc_class_info')).toBe(false); expect(handler.canHandle('unknown_tool')).toBe(false); }); }); // Helper function to initialize handler for tests that need it const initializeHandler = async () => { await (handler as any).initialize(); }; describe('initialization', () => { it('should initialize log client when capabilities allow', async () => { // Manually trigger initialization await (handler as any).initialize(); expect(SFCCLogClient).toHaveBeenCalledWith(context.config); expect(mockLogger.debug).toHaveBeenCalledWith('Log client initialized'); }); it('should not initialize log client when capabilities do not allow', async () => { const contextWithoutLogs = { ...context, capabilities: { canAccessLogs: false, canAccessOCAPI: false }, }; const handlerWithoutLogs = new LogToolHandler(contextWithoutLogs, 'Log'); // Manually trigger initialization to test the capabilities check await (handlerWithoutLogs as any).initialize(); // Reset the mock counter for this test (SFCCLogClient as jest.MockedClass<typeof SFCCLogClient>).mockClear(); const result = await handlerWithoutLogs.handle('get_latest_error', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Log client not configured - ensure log access is enabled.'); expect(SFCCLogClient).not.toHaveBeenCalled(); }); it('should not initialize without config', async () => { const contextWithoutConfig = { ...context, config: null as any, }; const handlerWithoutConfig = new LogToolHandler(contextWithoutConfig, 'Log'); const result = await handlerWithoutConfig.handle('get_latest_error', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Log client not configured - ensure log access is enabled.'); }); }); describe('disposal', () => { it('should dispose log client properly', async () => { // Initialize first await initializeHandler(); // Dispose await (handler as any).dispose(); expect(mockLogger.debug).toHaveBeenCalledWith('Log client disposed'); }); }); describe('get_latest_* tools', () => { beforeEach(async () => { await initializeHandler(); mockLogClient.getLatestLogs.mockResolvedValue('Test log entry\n2023-01-01T00:00:00Z'); }); it('should handle get_latest_error', async () => { const result = await handler.handle('get_latest_error', { limit: 5, date: '20230101' }, Date.now()); expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('error', 5, '20230101'); expect(result.content[0].text).toContain('Test log entry'); expect(mockLogger.debug).toHaveBeenCalledWith('Fetching latest error logs limit=5 date=20230101'); }); it('should handle get_latest_warn with default parameters', async () => { await handler.handle('get_latest_warn', {}, Date.now()); expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('warn', 10, undefined); expect(mockLogger.debug).toHaveBeenCalledWith('Fetching latest warn logs limit=10 date=today'); }); it('should handle get_latest_info', async () => { await handler.handle('get_latest_info', { limit: 15 }, Date.now()); expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('info', 15, undefined); }); it('should handle get_latest_debug', async () => { await handler.handle('get_latest_debug', { date: '20230101' }, Date.now()); expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('debug', 10, '20230101'); }); }); describe('summarize_logs tool', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle summarize_logs', async () => { const mockSummary = JSON.stringify({ date: '20230101', totalLogs: 100, errorCount: 5, warnCount: 10, infoCount: 85, }); mockLogClient.summarizeLogs.mockResolvedValue(mockSummary); await handler.handle('summarize_logs', { date: '20230101' }, Date.now()); expect(mockLogClient.summarizeLogs).toHaveBeenCalledWith('20230101'); expect(mockLogger.debug).toHaveBeenCalledWith('Summarizing logs for date 20230101'); }); it('should handle summarize_logs with default date', async () => { const mockSummary = JSON.stringify({ date: 'today', totalLogs: 50 }); mockLogClient.summarizeLogs.mockResolvedValue(mockSummary); await handler.handle('summarize_logs', {}, Date.now()); expect(mockLogClient.summarizeLogs).toHaveBeenCalledWith(undefined); expect(mockLogger.debug).toHaveBeenCalledWith('Summarizing logs for date today'); }); }); describe('search_logs tool', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle search_logs with required pattern', async () => { const mockResults = JSON.stringify({ results: [{ message: 'Error occurred', timestamp: '2023-01-01T00:00:00Z' }], total: 1, }); mockLogClient.searchLogs.mockResolvedValue(mockResults); const args = { pattern: 'error', logLevel: 'error', limit: 25, date: '20230101' }; const result = await handler.handle('search_logs', args, Date.now()); expect(mockLogClient.searchLogs).toHaveBeenCalledWith('error', 'error', 25, '20230101'); expect(result.content[0].text).toContain('Error occurred'); expect(mockLogger.debug).toHaveBeenCalledWith('Searching logs pattern="error" level=error limit=25'); }); it('should handle search_logs with default parameters', async () => { const mockResults = JSON.stringify({ results: [], total: 0 }); mockLogClient.searchLogs.mockResolvedValue(mockResults); await handler.handle('search_logs', { pattern: 'test' }, Date.now()); expect(mockLogClient.searchLogs).toHaveBeenCalledWith('test', undefined, 20, undefined); expect(mockLogger.debug).toHaveBeenCalledWith('Searching logs pattern="test" level=all limit=20'); }); it('should throw error when pattern is missing', async () => { const result = await handler.handle('search_logs', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('pattern must be a non-empty string'); }); it('should throw error when pattern is empty', async () => { const result = await handler.handle('search_logs', { pattern: '' }, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('pattern must be a non-empty string'); }); }); describe('get_log_file_contents tool', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle get_log_file_contents with filename', async () => { const mockFileContents = 'Log file contents with some test data\nMore log entries...'; mockLogClient.getLogFileContents.mockResolvedValue(mockFileContents); const result = await handler.handle('get_log_file_contents', { filename: 'error-2023-01-01.log' }, Date.now()); expect(mockLogClient.getLogFileContents).toHaveBeenCalledWith('error-2023-01-01.log', undefined, undefined); expect(result.content[0].text).toContain('Log file contents with some test data'); expect(mockLogger.debug).toHaveBeenCalledWith('Reading log file contents: error-2023-01-01.log (maxBytes=default, tailOnly=false)'); }); it('should handle get_log_file_contents with maxBytes and tailOnly options', async () => { const mockFileContents = 'Tail content of log file...'; mockLogClient.getLogFileContents.mockResolvedValue(mockFileContents); const result = await handler.handle('get_log_file_contents', { filename: 'large-log.log', maxBytes: 1024, tailOnly: true, }, Date.now()); expect(mockLogClient.getLogFileContents).toHaveBeenCalledWith('large-log.log', 1024, true); expect(result.content[0].text).toContain('Tail content of log file'); expect(mockLogger.debug).toHaveBeenCalledWith('Reading log file contents: large-log.log (maxBytes=1024, tailOnly=true)'); }); it('should handle get_log_file_contents with maxBytes and tailOnly=false (full file with size limit)', async () => { const mockFileContents = 'Full file content with size limit applied...'; mockLogClient.getLogFileContents.mockResolvedValue(mockFileContents); const result = await handler.handle('get_log_file_contents', { filename: 'large-log.log', maxBytes: 512, tailOnly: false, }, Date.now()); expect(mockLogClient.getLogFileContents).toHaveBeenCalledWith('large-log.log', 512, false); expect(result.content[0].text).toContain('Full file content with size limit'); expect(mockLogger.debug).toHaveBeenCalledWith('Reading log file contents: large-log.log (maxBytes=512, tailOnly=false)'); }); it('should require filename parameter', async () => { const result = await handler.handle('get_log_file_contents', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('filename must be a non-empty string'); }); it('should handle empty filename', async () => { const result = await handler.handle('get_log_file_contents', { filename: '' }, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('filename must be a non-empty string'); }); }); describe('list_log_files tool', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle list_log_files', async () => { const mockFiles = JSON.stringify([ { name: 'error-2023-01-01.log', size: 1024, modified: '2023-01-01T00:00:00Z' }, { name: 'info-2023-01-01.log', size: 2048, modified: '2023-01-01T00:00:00Z' }, ]); mockLogClient.listLogFiles.mockResolvedValue(mockFiles); const result = await handler.handle('list_log_files', {}, Date.now()); expect(mockLogClient.listLogFiles).toHaveBeenCalled(); expect(result.content[0].text).toContain('error-2023-01-01.log'); expect(result.content[0].text).toContain('info-2023-01-01.log'); expect(mockLogger.debug).toHaveBeenCalledWith('Listing log files'); }); }); describe('error handling', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle client errors gracefully', async () => { mockLogClient.getLatestLogs.mockRejectedValue(new Error('Client connection failed')); const result = await handler.handle('get_latest_error', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Client connection failed'); }); it('should throw error for unsupported tools', async () => { await expect(handler.handle('unsupported_tool', {}, Date.now())) .rejects.toThrow('Unsupported tool'); }); }); describe('timing and logging', () => { beforeEach(async () => { await initializeHandler(); }); it('should log timing information', async () => { mockLogClient.getLatestLogs.mockResolvedValue('empty logs'); const startTime = Date.now(); await handler.handle('get_latest_error', {}, startTime); expect(mockLogger.timing).toHaveBeenCalledWith('get_latest_error', startTime); }); it('should log execution details', async () => { mockLogClient.getLatestLogs.mockResolvedValue('test logs'); await handler.handle('get_latest_error', { limit: 5 }, Date.now()); expect(mockLogger.debug).toHaveBeenCalledWith('Fetching latest error logs limit=5 date=today'); expect(mockLogger.debug).toHaveBeenCalledWith( 'get_latest_error completed successfully', expect.any(Object), ); }); }); });

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/taurgis/sfcc-dev-mcp'

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