Skip to main content
Glama
logging-resources.test.ts11.9 kB
import { registerLoggingResources } from '../../src/resources/logging-resources'; import { SimplifierClient } from '../../src/client/simplifier-client'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { SimplifierLogListResponse, SimplifierLogEntryDetails } from '../../src/client/types'; // Mock the resourcesresult wrapper jest.mock('../../src/resources/resourcesresult', () => ({ wrapResourceResult: jest.fn() })); // Mock the SimplifierClient jest.mock('../../src/client/simplifier-client'); describe('Logging Resources', () => { let mockServer: jest.Mocked<McpServer>; let mockClient: jest.Mocked<SimplifierClient>; let mockWrapResourceResult: jest.MockedFunction<any>; beforeEach(() => { // Create mock server with resource method mockServer = { resource: jest.fn(), } as any; // Create mock client mockClient = { listLogEntriesPaginated: jest.fn(), getLogPages: jest.fn(), getLogEntry: jest.fn(), } as any; // Get the mocked wrapResourceResult mockWrapResourceResult = require('../../src/resources/resourcesresult').wrapResourceResult; mockWrapResourceResult.mockClear(); }); const createMockExtra = () => ({ signal: new AbortController().signal, requestId: 'test-request-id', sendNotification: jest.fn(), sendRequest: jest.fn() }); afterEach(() => { jest.clearAllMocks(); }); const mockLogListResponse: SimplifierLogListResponse = { list: [ { id: 'LOG001', entryDate: '2025-01-15T10:30:00Z', level: 3, messageKey: 'System_Error', messageParams: [], hasDetails: true, category: 'System' }, { id: 'LOG002', entryDate: '2025-01-15T09:15:00Z', level: 2, messageKey: 'System_Warning', messageParams: ['param1'], hasDetails: false, category: 'Application' } ] }; const mockLogEntryDetails: SimplifierLogEntryDetails = { id: 'LOG001', entryDate: '2025-01-15T10:30:00Z', level: 3, messageKey: 'System_Error', messageParams: [], hasDetails: true, category: 'System', details: 'java.lang.NullPointerException: Test error\n\tat com.example.Test.method(Test.java:42)', context: [] }; describe('registerLoggingResources', () => { it('should register two logging resources', () => { registerLoggingResources(mockServer, mockClient); expect(mockServer.resource).toHaveBeenCalledTimes(2); // Check that specific resources are registered const calls = mockServer.resource.mock.calls; const resourceNames = calls.map(call => call[0]); expect(resourceNames).toContain('logging-list'); expect(resourceNames).toContain('log-entry-details'); }); describe('logging list handler', () => { let loggingListHandler: any; beforeEach(() => { registerLoggingResources(mockServer, mockClient); loggingListHandler = mockServer.resource.mock.calls[0][3]; // First resource (logging list) }); it('should call wrapResourceResult with correct parameters', async () => { const testUri = new URL('simplifier://logging'); mockWrapResourceResult.mockResolvedValue({ contents: [] }); await loggingListHandler(testUri, {}, createMockExtra()); expect(mockWrapResourceResult).toHaveBeenCalledWith( testUri, expect.any(Function) ); }); it('should return log entries list through wrapper', async () => { const testUri = new URL('simplifier://logging'); mockClient.listLogEntriesPaginated.mockResolvedValue(mockLogListResponse); mockClient.getLogPages.mockResolvedValue({ pages: 1, pagesize: 50 }); mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => { const result = await fn(); return { contents: [{ uri: uri.href, text: JSON.stringify(result, null, 2), mimeType: 'application/json' }] }; }); const result = await loggingListHandler(testUri, {}, createMockExtra()); expect(mockClient.listLogEntriesPaginated).toHaveBeenCalledWith(0, 50, "MCP Resource: logging-list", {}); expect(mockClient.getLogPages).toHaveBeenCalledWith(50, {}); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.logs).toHaveLength(2); expect(resultData.totalCount).toBe(2); expect(resultData.logs[0].uri).toBe('simplifier://logging/entry/LOG001'); expect(resultData.logs[0].id).toBe('LOG001'); expect(resultData.logs[0].levelName).toBe('Error'); }); it('should return note about using tool for filtering', async () => { const testUri = new URL('simplifier://logging'); mockClient.listLogEntriesPaginated.mockResolvedValue(mockLogListResponse); mockClient.getLogPages.mockResolvedValue({ pages: 1, pagesize: 50 }); mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => { const result = await fn(); return { contents: [{ uri: uri.href, text: JSON.stringify(result, null, 2), mimeType: 'application/json' }] }; }); const result = await loggingListHandler(testUri, {}, createMockExtra()); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.note).toContain('logging-list tool'); }); it('should handle API errors through wrapper', async () => { const testUri = new URL('simplifier://logging'); const testError = new Error('API Error'); mockClient.listLogEntriesPaginated.mockRejectedValue(testError); mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => { try { await fn(); return { contents: [] }; } catch (e) { return { contents: [{ uri: uri.href, text: JSON.stringify({ error: `Failed to fetch: ${e}` }), mimeType: 'application/json' }] }; } }); const result = await loggingListHandler(testUri, {}, createMockExtra()); expect(mockClient.listLogEntriesPaginated).toHaveBeenCalled(); expect(result.contents[0].text).toContain('Failed to fetch'); expect(result.contents[0].text).toContain('API Error'); }); }); describe('log entry details handler', () => { let logEntryDetailsHandler: any; beforeEach(() => { registerLoggingResources(mockServer, mockClient); logEntryDetailsHandler = mockServer.resource.mock.calls[1][3]; // Second resource (log entry details) }); it('should return log entry details', async () => { const testUri = new URL('simplifier://logging/entry/LOG001'); mockClient.getLogEntry.mockResolvedValue(mockLogEntryDetails); mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => { const result = await fn(); return { contents: [{ uri: uri.href, text: JSON.stringify(result, null, 2), mimeType: 'application/json' }] }; }); const result = await logEntryDetailsHandler(testUri, {}, createMockExtra()); expect(mockClient.getLogEntry).toHaveBeenCalledWith('LOG001', 'MCP Resource: log-entry-details'); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.id).toBe('LOG001'); expect(resultData.level).toBe(3); expect(resultData.levelName).toBe('Error'); expect(resultData.details).toContain('NullPointerException'); expect(resultData.hasDetails).toBe(true); }); it('should throw error if log entry ID is missing', async () => { const testUri = new URL('simplifier://logging/entry/'); mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => { try { await fn(); return { contents: [] }; } catch (e: any) { return { contents: [{ uri: uri.href, text: JSON.stringify({ error: e.message }), mimeType: 'application/json' }] }; } }); const result = await logEntryDetailsHandler(testUri, {}, createMockExtra()); expect(mockClient.getLogEntry).not.toHaveBeenCalled(); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.error).toBe('Log entry ID is required'); }); }); }); describe('resource registration configuration', () => { it('should register logging-list as a simple resource', () => { registerLoggingResources(mockServer, mockClient); // First call should be simple resource (string URI) expect(mockServer.resource).toHaveBeenNthCalledWith( 1, 'logging-list', 'simplifier://logging', expect.objectContaining({ title: 'Simplifier Log Entries', mimeType: 'application/json' }), expect.any(Function) ); }); it('should register log-entry-details as a template resource', () => { registerLoggingResources(mockServer, mockClient); // Second call should be template resource const secondCall = mockServer.resource.mock.calls[1]; expect(secondCall[0]).toBe('log-entry-details'); expect(secondCall[1]).toHaveProperty('uriTemplate'); expect(secondCall[2]).toHaveProperty('title', 'Log Entry Details'); }); }); describe('level name conversion', () => { it('should convert numeric log levels to names', async () => { const testUri = new URL('simplifier://logging'); const responseWithDifferentLevels: SimplifierLogListResponse = { list: [ { id: '1', entryDate: '2025-01-15T10:00:00Z', level: 0, messageKey: 'debug', messageParams: [], hasDetails: false, category: 'Test' }, { id: '2', entryDate: '2025-01-15T10:00:00Z', level: 1, messageKey: 'info', messageParams: [], hasDetails: false, category: 'Test' }, { id: '3', entryDate: '2025-01-15T10:00:00Z', level: 2, messageKey: 'warning', messageParams: [], hasDetails: false, category: 'Test' }, { id: '4', entryDate: '2025-01-15T10:00:00Z', level: 3, messageKey: 'error', messageParams: [], hasDetails: false, category: 'Test' }, { id: '5', entryDate: '2025-01-15T10:00:00Z', level: 4, messageKey: 'critical', messageParams: [], hasDetails: false, category: 'Test' } ] }; mockClient.listLogEntriesPaginated.mockResolvedValue(responseWithDifferentLevels); mockClient.getLogPages.mockResolvedValue({ pages: 1, pagesize: 50 }); registerLoggingResources(mockServer, mockClient); const loggingListHandler = mockServer.resource.mock.calls[0][3]; mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => { const result = await fn(); return { contents: [{ uri: uri.href, text: JSON.stringify(result, null, 2), mimeType: 'application/json' }] }; }); const result = await loggingListHandler(testUri, {}, createMockExtra()); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.logs[0].levelName).toBe('Debug'); expect(resultData.logs[1].levelName).toBe('Info'); expect(resultData.logs[2].levelName).toBe('Warning'); expect(resultData.logs[3].levelName).toBe('Error'); expect(resultData.logs[4].levelName).toBe('Critical'); }); }); });

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/SimplifierIO/simplifier-mcp'

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