Skip to main content
Glama
config-management.test.tsโ€ข20.7 kB
import { describe, it } from 'node:test'; import { strict as assert } from 'node:assert'; /** * Unit tests for configuration management * * Tests the following components: * - ConfigManager * - Default configuration values * - Environment variable handling * - Rules engine auto-detection */ describe('Configuration Management Unit Tests', () => { describe('ConfigManager', () => { it('should validate default configuration values', () => { const defaultConfig = { zebrunnerUrl: '', zebrunnerLogin: '', zebrunnerToken: '', enableRulesEngine: false, rulesFilePath: 'mcp-zebrunner-rules.md', checkpointsFilePath: 'test_case_analysis_checkpoints.md', maxPageSize: 100, debugMode: false, requestTimeout: 30000, maxRetries: 3 }; assert.equal(typeof defaultConfig.enableRulesEngine, 'boolean', 'enableRulesEngine should be boolean'); assert.equal(typeof defaultConfig.maxPageSize, 'number', 'maxPageSize should be number'); assert.equal(typeof defaultConfig.debugMode, 'boolean', 'debugMode should be boolean'); assert.ok(defaultConfig.maxPageSize > 0, 'maxPageSize should be positive'); assert.ok(defaultConfig.requestTimeout > 0, 'requestTimeout should be positive'); assert.ok(defaultConfig.maxRetries >= 0, 'maxRetries should be non-negative'); }); it('should validate mandatory configuration fields', () => { const mandatoryFields = ['zebrunnerUrl', 'zebrunnerLogin', 'zebrunnerToken']; const config = { zebrunnerUrl: 'https://test.zebrunner.com', zebrunnerLogin: 'testuser', zebrunnerToken: 'testtoken', enableRulesEngine: true }; mandatoryFields.forEach(field => { assert.ok(config.hasOwnProperty(field), `${field} should be present in config`); if (field !== 'enableRulesEngine') { assert.ok(config[field as keyof typeof config], `${field} should have value`); } }); }); it('should validate environment variable parsing', () => { const mockEnvVars = { 'ZEBRUNNER_URL': 'https://test.zebrunner.com', 'ZEBRUNNER_LOGIN': 'testuser', 'ZEBRUNNER_TOKEN': 'abc123token', 'ENABLE_RULES_ENGINE': 'true', 'MAX_PAGE_SIZE': '200', 'DEBUG_MODE': 'false', 'REQUEST_TIMEOUT': '45000' }; const parseEnvVar = (key: string, defaultValue: any) => { const value = mockEnvVars[key]; if (!value) return defaultValue; if (typeof defaultValue === 'boolean') { return value.toLowerCase() === 'true'; } if (typeof defaultValue === 'number') { const parsed = parseInt(value, 10); return isNaN(parsed) ? defaultValue : parsed; } return value; }; assert.equal(parseEnvVar('ZEBRUNNER_URL', ''), 'https://test.zebrunner.com', 'should parse string env var'); assert.equal(parseEnvVar('ENABLE_RULES_ENGINE', false), true, 'should parse boolean env var'); assert.equal(parseEnvVar('MAX_PAGE_SIZE', 100), 200, 'should parse number env var'); assert.equal(parseEnvVar('NONEXISTENT', 'default'), 'default', 'should return default for missing env var'); }); it('should validate configuration precedence', () => { const defaultConfig = { maxPageSize: 100, enableRulesEngine: false, debugMode: false }; const envConfig = { maxPageSize: 200, enableRulesEngine: true }; const autoDetectedConfig = { enableRulesEngine: true }; // Simulate precedence: env > auto-detected > defaults const finalConfig = { ...defaultConfig, ...autoDetectedConfig, ...envConfig }; assert.equal(finalConfig.maxPageSize, 200, 'env var should override default'); assert.equal(finalConfig.enableRulesEngine, true, 'env var should override auto-detected'); assert.equal(finalConfig.debugMode, false, 'should keep default when not overridden'); }); }); describe('Rules Engine Auto-Detection', () => { it('should validate meaningful content detection', () => { const contentExamples = [ { content: '# Test Case Review Rules\n\n## Structure\n- Rule 1\n- Rule 2', meaningful: true }, { content: '', meaningful: false }, { content: ' \n\n \t ', meaningful: false }, { content: '# Empty File\n\n<!-- No content -->', meaningful: false }, { content: '# Rules\n\n', meaningful: false }, { content: 'Some actual rules content here', meaningful: true } ]; const hasMeaningfulContent = (content: string): boolean => { const trimmed = content.trim(); if (trimmed.length === 0) return false; // Remove markdown headers, comments, and whitespace-only lines const withoutMarkdown = trimmed .replace(/^#.*$/gm, '') // Remove headers .replace(/<!--.*?-->/gs, '') // Remove comments .replace(/^\s*$/gm, '') // Remove empty lines .trim(); return withoutMarkdown.length > 10; // Require substantial content }; contentExamples.forEach(example => { const result = hasMeaningfulContent(example.content); assert.equal(result, example.meaningful, `Content "${example.content.substring(0, 30)}..." should ${example.meaningful ? 'be' : 'not be'} meaningful`); }); }); it('should validate file existence checking', () => { const fileScenarios = [ { path: 'mcp-zebrunner-rules.md', exists: true, readable: true, shouldEnable: true }, { path: 'nonexistent-file.md', exists: false, readable: false, shouldEnable: false }, { path: 'unreadable-file.md', exists: true, readable: false, shouldEnable: false } ]; fileScenarios.forEach(scenario => { const canEnable = scenario.exists && scenario.readable; assert.equal(canEnable, scenario.shouldEnable, `Rules engine should ${scenario.shouldEnable ? 'be enabled' : 'not be enabled'} for ${scenario.path}`); }); }); it('should validate rules file path resolution', () => { const rootPath = '/project/root'; const rulesFileName = 'mcp-zebrunner-rules.md'; const expectedPath = `${rootPath}/${rulesFileName}`; const resolvePath = (root: string, filename: string) => { return `${root}/${filename}`; }; const resolvedPath = resolvePath(rootPath, rulesFileName); assert.equal(resolvedPath, expectedPath, 'should resolve path correctly'); }); it('should validate auto-detection logic', () => { const scenarios = [ { envVar: 'true', fileExists: true, hasContent: true, expected: true, reason: 'env var overrides' }, { envVar: 'false', fileExists: true, hasContent: true, expected: false, reason: 'env var overrides' }, { envVar: undefined, fileExists: true, hasContent: true, expected: true, reason: 'auto-detect enables' }, { envVar: undefined, fileExists: true, hasContent: false, expected: false, reason: 'no meaningful content' }, { envVar: undefined, fileExists: false, hasContent: false, expected: false, reason: 'file does not exist' } ]; scenarios.forEach(scenario => { let result: boolean; if (scenario.envVar !== undefined) { result = scenario.envVar === 'true'; } else { result = scenario.fileExists && scenario.hasContent; } assert.equal(result, scenario.expected, `Should ${scenario.expected ? 'enable' : 'disable'} rules engine: ${scenario.reason}`); }); }); }); describe('Environment Variable Validation', () => { it('should validate URL format', () => { const urlExamples = [ { url: 'https://test.zebrunner.com', valid: true }, { url: 'http://localhost:8080', valid: true }, { url: 'https://test.zebrunner.com:443', valid: true }, { url: 'invalid-url', valid: false }, { url: 'ftp://not-http.com', valid: false }, { url: '', valid: false } ]; const isValidUrl = (url: string): boolean => { if (!url) return false; try { const parsed = new URL(url); return parsed.protocol === 'http:' || parsed.protocol === 'https:'; } catch { return false; } }; urlExamples.forEach(example => { const result = isValidUrl(example.url); assert.equal(result, example.valid, `URL "${example.url}" should ${example.valid ? 'be valid' : 'be invalid'}`); }); }); it('should validate numeric environment variables', () => { const numericEnvVars = [ { key: 'MAX_PAGE_SIZE', value: '100', expected: 100, valid: true }, { key: 'REQUEST_TIMEOUT', value: '30000', expected: 30000, valid: true }, { key: 'MAX_RETRIES', value: '3', expected: 3, valid: true }, { key: 'INVALID_NUMBER', value: 'not-a-number', expected: NaN, valid: false }, { key: 'NEGATIVE_NUMBER', value: '-5', expected: -5, valid: false }, { key: 'ZERO', value: '0', expected: 0, valid: true } ]; const parseNumericEnvVar = (value: string, min: number = 0): { parsed: number, valid: boolean } => { const parsed = parseInt(value, 10); const valid = !isNaN(parsed) && parsed >= min; return { parsed, valid }; }; numericEnvVars.forEach(envVar => { const result = parseNumericEnvVar(envVar.value); assert.equal(result.parsed, envVar.expected, `Should parse ${envVar.key} correctly`); if (envVar.key !== 'INVALID_NUMBER' && envVar.key !== 'NEGATIVE_NUMBER') { assert.equal(result.valid, envVar.valid, `${envVar.key} validity should match expected`); } }); }); it('should validate boolean environment variables', () => { const booleanEnvVars = [ { value: 'true', expected: true }, { value: 'TRUE', expected: true }, { value: 'True', expected: true }, { value: 'false', expected: false }, { value: 'FALSE', expected: false }, { value: 'False', expected: false }, { value: '1', expected: false }, // Only 'true' should be true { value: '0', expected: false }, { value: '', expected: false }, { value: 'yes', expected: false } ]; const parseBooleanEnvVar = (value: string): boolean => { return value.toLowerCase() === 'true'; }; booleanEnvVars.forEach(envVar => { const result = parseBooleanEnvVar(envVar.value); assert.equal(result, envVar.expected, `Value "${envVar.value}" should parse to ${envVar.expected}`); }); }); }); describe('Configuration Validation', () => { it('should validate complete configuration', () => { const completeConfig = { zebrunnerUrl: 'https://test.zebrunner.com', zebrunnerLogin: 'testuser', zebrunnerToken: 'abc123token', enableRulesEngine: true, rulesFilePath: 'mcp-zebrunner-rules.md', checkpointsFilePath: 'test_case_analysis_checkpoints.md', maxPageSize: 100, debugMode: false, requestTimeout: 30000, maxRetries: 3 }; const validateConfig = (config: any) => { const errors: string[] = []; if (!config.zebrunnerUrl) errors.push('zebrunnerUrl is required'); if (!config.zebrunnerLogin) errors.push('zebrunnerLogin is required'); if (!config.zebrunnerToken) errors.push('zebrunnerToken is required'); if (config.maxPageSize <= 0) errors.push('maxPageSize must be positive'); if (config.requestTimeout <= 0) errors.push('requestTimeout must be positive'); if (config.maxRetries < 0) errors.push('maxRetries must be non-negative'); return { valid: errors.length === 0, errors }; }; const validation = validateConfig(completeConfig); assert.ok(validation.valid, 'complete config should be valid'); assert.equal(validation.errors.length, 0, 'should have no validation errors'); }); it('should validate incomplete configuration', () => { const incompleteConfig = { zebrunnerUrl: '', zebrunnerLogin: 'testuser', // zebrunnerToken missing maxPageSize: -1, requestTimeout: 0 }; const validateConfig = (config: any) => { const errors: string[] = []; if (!config.zebrunnerUrl) errors.push('zebrunnerUrl is required'); if (!config.zebrunnerLogin) errors.push('zebrunnerLogin is required'); if (!config.zebrunnerToken) errors.push('zebrunnerToken is required'); if (config.maxPageSize <= 0) errors.push('maxPageSize must be positive'); if (config.requestTimeout <= 0) errors.push('requestTimeout must be positive'); return { valid: errors.length === 0, errors }; }; const validation = validateConfig(incompleteConfig); assert.ok(!validation.valid, 'incomplete config should be invalid'); assert.ok(validation.errors.length > 0, 'should have validation errors'); assert.ok(validation.errors.includes('zebrunnerUrl is required'), 'should detect missing URL'); assert.ok(validation.errors.includes('zebrunnerToken is required'), 'should detect missing token'); assert.ok(validation.errors.includes('maxPageSize must be positive'), 'should detect invalid page size'); }); it('should validate configuration limits', () => { const configLimits = { maxPageSize: { min: 1, max: 1000, recommended: 100 }, requestTimeout: { min: 1000, max: 300000, recommended: 30000 }, maxRetries: { min: 0, max: 10, recommended: 3 } }; const validateLimits = (value: number, limits: any) => { return { withinRange: value >= limits.min && value <= limits.max, isRecommended: value === limits.recommended, tooLow: value < limits.min, tooHigh: value > limits.max }; }; const pageSize200 = validateLimits(200, configLimits.maxPageSize); assert.ok(pageSize200.withinRange, '200 should be within page size range'); assert.ok(!pageSize200.isRecommended, '200 should not be recommended page size'); const timeout5000 = validateLimits(5000, configLimits.requestTimeout); assert.ok(timeout5000.withinRange, '5000ms should be within timeout range'); const retries15 = validateLimits(15, configLimits.maxRetries); assert.ok(retries15.tooHigh, '15 retries should be too high'); }); }); describe('Error Handling', () => { it('should handle missing environment file gracefully', () => { const defaultConfig = { zebrunnerUrl: '', zebrunnerLogin: '', zebrunnerToken: '', enableRulesEngine: false }; // Simulate missing .env file const loadConfig = (envFileExists: boolean) => { if (!envFileExists) { return { ...defaultConfig, loaded: false }; } return { ...defaultConfig, zebrunnerUrl: 'loaded-url', loaded: true }; }; const configWithoutEnv = loadConfig(false); assert.ok(!configWithoutEnv.loaded, 'should indicate env file not loaded'); assert.equal(configWithoutEnv.zebrunnerUrl, '', 'should use default values'); const configWithEnv = loadConfig(true); assert.ok(configWithEnv.loaded, 'should indicate env file loaded'); assert.equal(configWithEnv.zebrunnerUrl, 'loaded-url', 'should use loaded values'); }); it('should handle malformed environment variables', () => { const malformedEnvVars = { 'MAX_PAGE_SIZE': 'not-a-number', 'ENABLE_RULES_ENGINE': 'maybe', 'REQUEST_TIMEOUT': 'invalid-timeout' }; const parseWithFallback = (key: string, defaultValue: any) => { const value = malformedEnvVars[key as keyof typeof malformedEnvVars]; if (!value) return defaultValue; try { if (typeof defaultValue === 'number') { const parsed = parseInt(value, 10); return isNaN(parsed) ? defaultValue : parsed; } if (typeof defaultValue === 'boolean') { return value.toLowerCase() === 'true'; } return value; } catch { return defaultValue; } }; assert.equal(parseWithFallback('MAX_PAGE_SIZE', 100), 100, 'should fallback for invalid number'); assert.equal(parseWithFallback('ENABLE_RULES_ENGINE', false), false, 'should fallback for invalid boolean'); assert.equal(parseWithFallback('REQUEST_TIMEOUT', 30000), 30000, 'should fallback for malformed number'); }); it('should provide helpful error messages', () => { const configErrors = [ { field: 'zebrunnerUrl', value: '', message: 'ZEBRUNNER_URL is required. Please set it in your .env file.', suggestion: 'Example: ZEBRUNNER_URL=https://your-instance.zebrunner.com' }, { field: 'maxPageSize', value: 2000, message: 'MAX_PAGE_SIZE (2000) exceeds maximum allowed value (1000).', suggestion: 'Set MAX_PAGE_SIZE to a value between 1 and 1000.' }, { field: 'rulesFile', value: 'missing', message: 'Rules file (rulesFile) not found or empty.', suggestion: 'Create mcp-zebrunner-rules.md with validation rules or set ENABLE_RULES_ENGINE=false' } ]; configErrors.forEach(error => { assert.ok(error.message.length > 0, 'Error message should not be empty'); assert.ok(error.suggestion.length > 0, 'Error suggestion should be provided'); const referencesFieldOrValue = error.message.includes(error.field) || error.message.includes(error.value.toString()) || error.message.toUpperCase().includes(error.field.toUpperCase()); assert.ok(referencesFieldOrValue, `Error message "${error.message}" should reference field "${error.field}" or value "${error.value}"`); }); }); }); describe('Configuration Loading Scenarios', () => { it('should handle development environment', () => { const devConfig = { zebrunnerUrl: 'http://localhost:8080', zebrunnerLogin: 'dev-user', zebrunnerToken: 'dev-token', debugMode: true, maxPageSize: 10, // Smaller for development requestTimeout: 60000 // Longer for debugging }; assert.ok(devConfig.debugMode, 'development should enable debug mode'); assert.ok(devConfig.maxPageSize < 50, 'development should use smaller page size'); assert.ok(devConfig.requestTimeout > 30000, 'development should use longer timeout'); }); it('should handle production environment', () => { const prodConfig = { zebrunnerUrl: 'https://prod.zebrunner.com', zebrunnerLogin: 'prod-user', zebrunnerToken: 'secure-prod-token', debugMode: false, maxPageSize: 100, requestTimeout: 30000, maxRetries: 3 }; assert.ok(!prodConfig.debugMode, 'production should disable debug mode'); assert.ok(prodConfig.zebrunnerUrl.startsWith('https://'), 'production should use HTTPS'); assert.ok(prodConfig.maxRetries <= 3, 'production should limit retries'); }); it('should handle CI/CD environment', () => { const ciConfig = { zebrunnerUrl: 'https://ci.zebrunner.com', zebrunnerLogin: 'ci-user', zebrunnerToken: 'ci-token', enableRulesEngine: false, // Disabled in CI maxPageSize: 50, requestTimeout: 45000, // Slightly longer for CI maxRetries: 5 // More retries for flaky CI }; assert.ok(!ciConfig.enableRulesEngine, 'CI should disable rules engine'); assert.ok(ciConfig.maxRetries > 3, 'CI should allow more retries'); assert.ok(ciConfig.requestTimeout > 30000, 'CI should use longer timeout'); }); }); });

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/maksimsarychau/mcp-zebrunner'

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