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
207 lines • 9.38 kB
JavaScript
/**
* Configuration Tests
*
* Tests for centralized configuration module.
* Note: Config is a singleton that reads env vars at module load time,
* so these tests verify the current runtime config rather than testing
* all possible configurations.
*/
import { describe, it, expect } from 'vitest';
import { config, bcConfig, isDevelopment, isProduction, isTest, nodeEnv, logLevel, } from './config.js';
describe('config', () => {
describe('Config singleton', () => {
it('is defined and has getConfig method', () => {
expect(config).toBeDefined();
expect(typeof config.getConfig).toBe('function');
});
it('has nodeEnv accessor', () => {
expect(config.nodeEnv).toBeDefined();
expect(['development', 'production', 'test']).toContain(config.nodeEnv);
});
it('has logLevel accessor', () => {
expect(config.logLevel).toBeDefined();
expect(['debug', 'info', 'warn', 'error']).toContain(config.logLevel);
});
it('has bc accessor with BC configuration', () => {
expect(config.bc).toBeDefined();
expect(config.bc).toHaveProperty('baseUrl');
expect(config.bc).toHaveProperty('username');
expect(config.bc).toHaveProperty('password');
expect(config.bc).toHaveProperty('tenantId');
expect(config.bc).toHaveProperty('timeout');
});
it('has environment check accessors', () => {
expect(typeof config.isDevelopment).toBe('boolean');
expect(typeof config.isProduction).toBe('boolean');
expect(typeof config.isTest).toBe('boolean');
});
it('getConfig returns readonly configuration', () => {
const cfg = config.getConfig();
expect(cfg).toBeDefined();
expect(cfg).toHaveProperty('nodeEnv');
expect(cfg).toHaveProperty('logLevel');
expect(cfg).toHaveProperty('bc');
});
it('environment flags are mutually exclusive', () => {
// At most one should be true
const trueCount = [config.isDevelopment, config.isProduction, config.isTest].filter(Boolean).length;
expect(trueCount).toBeLessThanOrEqual(1);
});
});
describe('BC configuration', () => {
it('bcConfig has required fields', () => {
expect(bcConfig).toBeDefined();
expect(typeof bcConfig.baseUrl).toBe('string');
expect(typeof bcConfig.username).toBe('string');
expect(typeof bcConfig.password).toBe('string');
expect(typeof bcConfig.tenantId).toBe('string');
expect(typeof bcConfig.timeout).toBe('number');
expect(typeof bcConfig.searchTimingWindowMs).toBe('number');
});
it('baseUrl is a valid URL', () => {
expect(() => new URL(bcConfig.baseUrl)).not.toThrow();
});
it('baseUrl is not empty', () => {
expect(bcConfig.baseUrl.length).toBeGreaterThan(0);
});
it('username is not empty', () => {
expect(bcConfig.username.length).toBeGreaterThan(0);
});
it('tenantId has default value', () => {
// Should have a value, either from env or default
expect(bcConfig.tenantId.length).toBeGreaterThan(0);
});
it('timeout is a positive number', () => {
expect(bcConfig.timeout).toBeGreaterThan(0);
expect(Number.isFinite(bcConfig.timeout)).toBe(true);
});
it('searchTimingWindowMs is a positive number', () => {
expect(bcConfig.searchTimingWindowMs).toBeGreaterThan(0);
expect(Number.isFinite(bcConfig.searchTimingWindowMs)).toBe(true);
});
it('bcConfig is same as config.bc', () => {
expect(bcConfig).toBe(config.bc);
});
it('bcConfig properties are defined and stable', () => {
// Config should have stable values throughout test run
const snapshot = {
baseUrl: bcConfig.baseUrl,
username: bcConfig.username,
tenantId: bcConfig.tenantId,
timeout: bcConfig.timeout,
};
// Read again and verify stability
expect(bcConfig.baseUrl).toBe(snapshot.baseUrl);
expect(bcConfig.username).toBe(snapshot.username);
expect(bcConfig.tenantId).toBe(snapshot.tenantId);
expect(bcConfig.timeout).toBe(snapshot.timeout);
});
});
describe('Environment accessors', () => {
it('isDevelopment matches config.isDevelopment', () => {
expect(isDevelopment).toBe(config.isDevelopment);
});
it('isProduction matches config.isProduction', () => {
expect(isProduction).toBe(config.isProduction);
});
it('isTest matches config.isTest', () => {
expect(isTest).toBe(config.isTest);
});
it('nodeEnv matches config.nodeEnv', () => {
expect(nodeEnv).toBe(config.nodeEnv);
});
it('logLevel matches config.logLevel', () => {
expect(logLevel).toBe(config.logLevel);
});
});
describe('Configuration consistency', () => {
it('nodeEnv corresponds to environment flags', () => {
if (config.nodeEnv === 'development') {
expect(config.isDevelopment).toBe(true);
expect(config.isProduction).toBe(false);
expect(config.isTest).toBe(false);
}
else if (config.nodeEnv === 'production') {
expect(config.isDevelopment).toBe(false);
expect(config.isProduction).toBe(true);
expect(config.isTest).toBe(false);
}
else if (config.nodeEnv === 'test') {
expect(config.isDevelopment).toBe(false);
expect(config.isProduction).toBe(false);
expect(config.isTest).toBe(true);
}
});
it('getConfig returns consistent configuration', () => {
const cfg1 = config.getConfig();
const cfg2 = config.getConfig();
// Should return consistent values
expect(cfg1.nodeEnv).toBe(cfg2.nodeEnv);
expect(cfg1.logLevel).toBe(cfg2.logLevel);
expect(cfg1.bc.baseUrl).toBe(cfg2.bc.baseUrl);
expect(cfg1.bc.timeout).toBe(cfg2.bc.timeout);
});
it('bc config has stable values', () => {
const snapshot1 = {
baseUrl: config.bc.baseUrl,
timeout: config.bc.timeout,
tenantId: config.bc.tenantId,
};
// Read again and verify stability
const snapshot2 = {
baseUrl: config.bc.baseUrl,
timeout: config.bc.timeout,
tenantId: config.bc.tenantId,
};
expect(snapshot1).toEqual(snapshot2);
});
});
describe('Type validation', () => {
it('nodeEnv is one of valid values', () => {
expect(['development', 'production', 'test']).toContain(config.nodeEnv);
});
it('logLevel is one of valid values', () => {
expect(['debug', 'info', 'warn', 'error']).toContain(config.logLevel);
});
it('all BC config values have correct types', () => {
expect(typeof bcConfig.baseUrl).toBe('string');
expect(typeof bcConfig.username).toBe('string');
expect(typeof bcConfig.password).toBe('string');
expect(typeof bcConfig.tenantId).toBe('string');
expect(typeof bcConfig.timeout).toBe('number');
expect(typeof bcConfig.searchTimingWindowMs).toBe('number');
});
});
describe('Default values', () => {
it('has sensible default timeout', () => {
// Default should be 30000ms based on code
expect(bcConfig.timeout).toBeGreaterThanOrEqual(1000);
expect(bcConfig.timeout).toBeLessThanOrEqual(120000);
});
it('has sensible default searchTimingWindowMs', () => {
// Default should be 3000ms based on code
expect(bcConfig.searchTimingWindowMs).toBeGreaterThanOrEqual(1000);
expect(bcConfig.searchTimingWindowMs).toBeLessThanOrEqual(30000);
});
it('tenant defaults to "default" if not set', () => {
// Should be "default" or a custom value from env
expect(bcConfig.tenantId.length).toBeGreaterThan(0);
});
});
describe('Runtime configuration', () => {
it('configuration is loaded at module initialization', () => {
// Config should be available immediately
expect(config).toBeDefined();
expect(config.bc).toBeDefined();
expect(config.nodeEnv).toBeDefined();
});
it('exported accessors reference the same config instance', () => {
// Verify that bcConfig is the same as config.bc (already tested above)
// and that environment accessors reference the same underlying config
expect(bcConfig).toBe(config.bc);
expect(nodeEnv).toBe(config.nodeEnv);
expect(logLevel).toBe(config.logLevel);
});
});
});
//# sourceMappingURL=config.spec.js.map