bc-webclient-mcp
Version:
Model Context Protocol (MCP) server for Microsoft Dynamics 365 Business Central via WebUI protocol. Enables AI assistants to interact with BC through the web client protocol, supporting Card, List, and Document pages with full line item support and server
303 lines • 12.6 kB
JavaScript
/**
* Logger Tests
*
* Tests for centralized logging utilities with structured logging support.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { logger, createChildLogger, createToolLogger, createConnectionLogger, LogLevels, } from './logger.js';
describe('logger', () => {
// Store original log level to restore after tests
let originalLevel;
beforeEach(() => {
originalLevel = logger.level;
});
afterEach(() => {
// Restore original log level
logger.level = originalLevel;
});
describe('Global logger', () => {
it('is defined and has standard log methods', () => {
expect(logger).toBeDefined();
expect(typeof logger.info).toBe('function');
expect(typeof logger.error).toBe('function');
expect(typeof logger.warn).toBe('function');
expect(typeof logger.debug).toBe('function');
expect(typeof logger.trace).toBe('function');
expect(typeof logger.fatal).toBe('function');
});
it('has a name property', () => {
// Pino logger has bindings that include the name
expect(logger.bindings()).toHaveProperty('name');
expect(logger.bindings().name).toBe('bc-webclient-mcp');
});
it('has a level property', () => {
expect(typeof logger.level).toBe('string');
expect(['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent']).toContain(logger.level);
});
});
describe('createChildLogger()', () => {
it('creates a child logger with context', () => {
// Arrange
const context = { userId: '123', operation: 'test' };
// Act
const childLogger = createChildLogger(context);
// Assert
expect(childLogger).toBeDefined();
expect(typeof childLogger.info).toBe('function');
expect(childLogger.bindings()).toMatchObject(context);
});
it('child logger inherits parent configuration', () => {
// Arrange
const context = { test: 'value' };
const childLogger = createChildLogger(context);
// Assert
expect(childLogger.level).toBe(logger.level);
expect(childLogger.bindings().name).toBe('bc-webclient-mcp');
});
it('handles empty context', () => {
// Arrange & Act
const childLogger = createChildLogger({});
// Assert
expect(childLogger).toBeDefined();
expect(childLogger.bindings()).toMatchObject({});
});
it('handles complex context with nested objects', () => {
// Arrange
const context = {
metadata: { version: '1.0', tags: ['test', 'unit'] },
count: 42,
};
// Act
const childLogger = createChildLogger(context);
// Assert
expect(childLogger.bindings()).toMatchObject(context);
});
});
describe('createToolLogger()', () => {
it('creates logger with tool name only', () => {
// Arrange & Act
const toolLogger = createToolLogger('GetPageMetadata');
// Assert
expect(toolLogger).toBeDefined();
expect(toolLogger.bindings()).toMatchObject({ tool: 'GetPageMetadata' });
});
it('creates logger with tool name and pageContextId', () => {
// Arrange & Act
const toolLogger = createToolLogger('GetPageMetadata', 'session123:page21');
// Assert
expect(toolLogger.bindings()).toMatchObject({
tool: 'GetPageMetadata',
pageContextId: 'session123:page21',
sessionId: 'session123',
});
});
it('extracts sessionId from pageContextId', () => {
// Arrange & Act
const toolLogger = createToolLogger('SearchPages', 'abc-def-ghi:42');
// Assert
expect(toolLogger.bindings().sessionId).toBe('abc-def-ghi');
expect(toolLogger.bindings().pageContextId).toBe('abc-def-ghi:42');
});
it('handles pageContextId without colon', () => {
// Arrange & Act
const toolLogger = createToolLogger('SearchPages', 'simpleId');
// Assert
expect(toolLogger.bindings().sessionId).toBe('simpleId');
expect(toolLogger.bindings().pageContextId).toBe('simpleId');
});
it('handles empty pageContextId', () => {
// Arrange & Act
const toolLogger = createToolLogger('Tool', '');
// Assert
// Empty string is falsy, so pageContextId should not be added
expect(toolLogger.bindings()).toMatchObject({ tool: 'Tool' });
expect(toolLogger.bindings()).not.toHaveProperty('pageContextId');
});
it('handles undefined pageContextId', () => {
// Arrange & Act
const toolLogger = createToolLogger('Tool', undefined);
// Assert
expect(toolLogger.bindings()).toMatchObject({ tool: 'Tool' });
expect(toolLogger.bindings()).not.toHaveProperty('pageContextId');
});
});
describe('createConnectionLogger()', () => {
it('creates logger with sessionId only', () => {
// Arrange & Act
const connLogger = createConnectionLogger('session-abc-123');
// Assert
expect(connLogger).toBeDefined();
expect(connLogger.bindings()).toMatchObject({ sessionId: 'session-abc-123' });
});
it('creates logger with sessionId and operation', () => {
// Arrange & Act
const connLogger = createConnectionLogger('session-456', 'authenticate');
// Assert
expect(connLogger.bindings()).toMatchObject({
sessionId: 'session-456',
operation: 'authenticate',
});
});
it('handles empty operation', () => {
// Arrange & Act
const connLogger = createConnectionLogger('session-789', '');
// Assert
// Empty string is falsy, so operation should not be added
expect(connLogger.bindings()).toMatchObject({ sessionId: 'session-789' });
expect(connLogger.bindings()).not.toHaveProperty('operation');
});
it('handles undefined operation', () => {
// Arrange & Act
const connLogger = createConnectionLogger('session-xyz', undefined);
// Assert
expect(connLogger.bindings()).toMatchObject({ sessionId: 'session-xyz' });
expect(connLogger.bindings()).not.toHaveProperty('operation');
});
});
describe('LogLevels', () => {
describe('isDebugEnabled()', () => {
it('returns true when level is debug', () => {
// Arrange
LogLevels.setLevel('debug');
// Act & Assert
expect(LogLevels.isDebugEnabled()).toBe(true);
});
it('returns true when level is trace', () => {
// Arrange
LogLevels.setLevel('trace');
// Act & Assert
expect(LogLevels.isDebugEnabled()).toBe(true);
});
it('returns false when level is info', () => {
// Arrange
LogLevels.setLevel('info');
// Act & Assert
expect(LogLevels.isDebugEnabled()).toBe(false);
});
it('returns false when level is warn', () => {
// Arrange
LogLevels.setLevel('warn');
// Act & Assert
expect(LogLevels.isDebugEnabled()).toBe(false);
});
it('returns false when level is error', () => {
// Arrange
LogLevels.setLevel('error');
// Act & Assert
expect(LogLevels.isDebugEnabled()).toBe(false);
});
});
describe('isTraceEnabled()', () => {
it('returns true when level is trace', () => {
// Arrange
LogLevels.setLevel('trace');
// Act & Assert
expect(LogLevels.isTraceEnabled()).toBe(true);
});
it('returns false when level is debug', () => {
// Arrange
LogLevels.setLevel('debug');
// Act & Assert
expect(LogLevels.isTraceEnabled()).toBe(false);
});
it('returns false when level is info', () => {
// Arrange
LogLevels.setLevel('info');
// Act & Assert
expect(LogLevels.isTraceEnabled()).toBe(false);
});
});
describe('setLevel() and getLevel()', () => {
it('sets and gets log level', () => {
// Arrange & Act
LogLevels.setLevel('debug');
// Assert
expect(LogLevels.getLevel()).toBe('debug');
});
it('changes level from info to error', () => {
// Arrange
LogLevels.setLevel('info');
expect(LogLevels.getLevel()).toBe('info');
// Act
LogLevels.setLevel('error');
// Assert
expect(LogLevels.getLevel()).toBe('error');
});
it('accepts all valid log levels', () => {
const levels = [
'fatal',
'error',
'warn',
'info',
'debug',
'trace',
'silent',
];
levels.forEach((level) => {
// Act
LogLevels.setLevel(level);
// Assert
expect(LogLevels.getLevel()).toBe(level);
});
});
it('affects global logger instance', () => {
// Arrange
LogLevels.setLevel('warn');
// Assert
expect(logger.level).toBe('warn');
expect(LogLevels.getLevel()).toBe('warn');
});
});
});
describe('Child logger inheritance', () => {
it('child logger respects parent level changes', () => {
// Arrange
const childLogger = createChildLogger({ test: 'value' });
LogLevels.setLevel('debug');
// Assert
expect(childLogger.level).toBe('debug');
// Act - change level
LogLevels.setLevel('error');
// Assert
expect(childLogger.level).toBe('error');
});
it('tool logger respects level changes', () => {
// Arrange
const toolLogger = createToolLogger('TestTool');
// Act
LogLevels.setLevel('trace');
// Assert
expect(toolLogger.level).toBe('trace');
});
it('connection logger respects level changes', () => {
// Arrange
const connLogger = createConnectionLogger('session-123');
// Act
LogLevels.setLevel('fatal');
// Assert
expect(connLogger.level).toBe('fatal');
});
});
describe('Logger context isolation', () => {
it('child loggers have independent contexts', () => {
// Arrange
const child1 = createChildLogger({ id: '1', name: 'first' });
const child2 = createChildLogger({ id: '2', name: 'second' });
// Assert
expect(child1.bindings()).toMatchObject({ id: '1', name: 'first' });
expect(child2.bindings()).toMatchObject({ id: '2', name: 'second' });
expect(child1.bindings().id).not.toBe(child2.bindings().id);
});
it('tool loggers have independent contexts', () => {
// Arrange
const tool1 = createToolLogger('Tool1', 'session1:page1');
const tool2 = createToolLogger('Tool2', 'session2:page2');
// Assert
expect(tool1.bindings().tool).toBe('Tool1');
expect(tool2.bindings().tool).toBe('Tool2');
expect(tool1.bindings().sessionId).toBe('session1');
expect(tool2.bindings().sessionId).toBe('session2');
});
});
});
//# sourceMappingURL=logger.spec.js.map