Skip to main content
Glama
util.test.ts18.3 kB
/** * Unit tests for MCP bridge utility modules */ import { describe, it, expect, beforeEach, afterEach } from 'bun:test' import { tmpdir } from 'node:os' import { mkdir, rm, readFile } from 'node:fs/promises' import { join } from 'node:path' // Config utilities import { CONFIG, CONFIG_LIMITS, CONFIG_PRESETS, validateConfig, getConfigSummary } from '../util/config' // Language utilities import { LANGUAGE_MAPPINGS, getLanguageInfo, mimeFromLangOrPath, fenceLang } from '../util/language' // Logger utilities import { initLogger, logInfo, logWarn, logError } from '../util/logger' describe('Config Utilities', () => { describe('CONFIG object', () => { it('should have default values', () => { expect(CONFIG.DOLPHIN_API_URL).toBeDefined() expect(CONFIG.SERVER_NAME).toBe('dolphin-mcp') expect(CONFIG.SERVER_VERSION).toBeDefined() expect(CONFIG.LOG_LEVEL).toBe('info') }) it('should have valid concurrency limits', () => { expect(CONFIG.MAX_CONCURRENT_SNIPPET_FETCH).toBeGreaterThanOrEqual(CONFIG_LIMITS.MIN_CONCURRENT) expect(CONFIG.MAX_CONCURRENT_SNIPPET_FETCH).toBeLessThanOrEqual(CONFIG_LIMITS.MAX_CONCURRENT) }) it('should have valid timeout settings', () => { expect(CONFIG.SNIPPET_FETCH_TIMEOUT_MS).toBeGreaterThanOrEqual(CONFIG_LIMITS.MIN_TIMEOUT) expect(CONFIG.SNIPPET_FETCH_TIMEOUT_MS).toBeLessThanOrEqual(CONFIG_LIMITS.MAX_TIMEOUT) }) it('should have valid retry settings', () => { expect(CONFIG.SNIPPET_FETCH_RETRY_ATTEMPTS).toBeGreaterThanOrEqual(CONFIG_LIMITS.MIN_RETRIES) expect(CONFIG.SNIPPET_FETCH_RETRY_ATTEMPTS).toBeLessThanOrEqual(CONFIG_LIMITS.MAX_RETRIES) }) }) describe('CONFIG_LIMITS', () => { it('should define valid concurrency limits', () => { expect(CONFIG_LIMITS.MIN_CONCURRENT).toBe(1) expect(CONFIG_LIMITS.MAX_CONCURRENT).toBe(12) expect(CONFIG_LIMITS.RECOMMENDED_CONCURRENT).toBe(8) expect(CONFIG_LIMITS.RECOMMENDED_CONCURRENT).toBeGreaterThanOrEqual(CONFIG_LIMITS.MIN_CONCURRENT) expect(CONFIG_LIMITS.RECOMMENDED_CONCURRENT).toBeLessThanOrEqual(CONFIG_LIMITS.MAX_CONCURRENT) }) it('should define valid timeout limits', () => { expect(CONFIG_LIMITS.MIN_TIMEOUT).toBe(500) expect(CONFIG_LIMITS.MAX_TIMEOUT).toBe(10000) expect(CONFIG_LIMITS.MIN_TIMEOUT).toBeLessThan(CONFIG_LIMITS.MAX_TIMEOUT) }) it('should define valid retry limits', () => { expect(CONFIG_LIMITS.MIN_RETRIES).toBe(0) expect(CONFIG_LIMITS.MAX_RETRIES).toBe(3) expect(CONFIG_LIMITS.MIN_RETRIES).toBeLessThanOrEqual(CONFIG_LIMITS.MAX_RETRIES) }) }) describe('CONFIG_PRESETS', () => { it('should have conservative preset', () => { const preset = CONFIG_PRESETS.CONSERVATIVE expect(preset.MAX_CONCURRENT_SNIPPET_FETCH).toBe(4) expect(preset.SNIPPET_FETCH_TIMEOUT_MS).toBe(1500) expect(preset.SNIPPET_FETCH_RETRY_ATTEMPTS).toBe(1) }) it('should have recommended preset', () => { const preset = CONFIG_PRESETS.RECOMMENDED expect(preset.MAX_CONCURRENT_SNIPPET_FETCH).toBe(CONFIG_LIMITS.RECOMMENDED_CONCURRENT) expect(preset.SNIPPET_FETCH_TIMEOUT_MS).toBe(2000) expect(preset.SNIPPET_FETCH_RETRY_ATTEMPTS).toBe(1) }) it('should have performance preset', () => { const preset = CONFIG_PRESETS.PERFORMANCE expect(preset.MAX_CONCURRENT_SNIPPET_FETCH).toBe(10) expect(preset.SNIPPET_FETCH_TIMEOUT_MS).toBe(3000) expect(preset.SNIPPET_FETCH_RETRY_ATTEMPTS).toBe(2) }) it('should have all presets within valid ranges', () => { Object.values(CONFIG_PRESETS).forEach(preset => { expect(preset.MAX_CONCURRENT_SNIPPET_FETCH).toBeGreaterThanOrEqual(CONFIG_LIMITS.MIN_CONCURRENT) expect(preset.MAX_CONCURRENT_SNIPPET_FETCH).toBeLessThanOrEqual(CONFIG_LIMITS.MAX_CONCURRENT) expect(preset.SNIPPET_FETCH_TIMEOUT_MS).toBeGreaterThanOrEqual(CONFIG_LIMITS.MIN_TIMEOUT) expect(preset.SNIPPET_FETCH_TIMEOUT_MS).toBeLessThanOrEqual(CONFIG_LIMITS.MAX_TIMEOUT) expect(preset.SNIPPET_FETCH_RETRY_ATTEMPTS).toBeGreaterThanOrEqual(CONFIG_LIMITS.MIN_RETRIES) expect(preset.SNIPPET_FETCH_RETRY_ATTEMPTS).toBeLessThanOrEqual(CONFIG_LIMITS.MAX_RETRIES) }) }) }) describe('validateConfig', () => { it('should return empty warnings for valid config', () => { const warnings = validateConfig() // Should either be empty or only have acceptable warnings // (In normal operation, default config should be valid) expect(Array.isArray(warnings)).toBe(true) }) it('should validate URL format', () => { // Note: We can't directly test this without modifying CONFIG, // but we can verify the function works const warnings = validateConfig() // Check that URL validation runs (no crash) expect(warnings).toBeDefined() }) it('should validate log level', () => { const warnings = validateConfig() // Valid log levels: debug, info, warn, error // Current config should have valid log level const logLevelWarnings = warnings.filter(w => w.includes('LOG_LEVEL')) // If log level is valid (which it should be by default), no warnings if (CONFIG.LOG_LEVEL === 'debug' || CONFIG.LOG_LEVEL === 'info' || CONFIG.LOG_LEVEL === 'warn' || CONFIG.LOG_LEVEL === 'error') { expect(logLevelWarnings.length).toBe(0) } }) }) describe('getConfigSummary', () => { it('should return configuration summary', () => { const summary = getConfigSummary() expect(summary).toBeDefined() expect(summary.dolphin_api_url).toBe(CONFIG.DOLPHIN_API_URL) expect(summary.server_name).toBe(CONFIG.SERVER_NAME) expect(summary.server_version).toBe(CONFIG.SERVER_VERSION) expect(summary.log_level).toBe(CONFIG.LOG_LEVEL) expect(summary.max_concurrent_snippet_fetch).toBe(CONFIG.MAX_CONCURRENT_SNIPPET_FETCH) expect(summary.snippet_fetch_timeout_ms).toBe(CONFIG.SNIPPET_FETCH_TIMEOUT_MS) expect(summary.snippet_fetch_retry_attempts).toBe(CONFIG.SNIPPET_FETCH_RETRY_ATTEMPTS) }) it('should return summary with correct types', () => { const summary = getConfigSummary() expect(typeof summary.dolphin_api_url).toBe('string') expect(typeof summary.server_name).toBe('string') expect(typeof summary.server_version).toBe('string') expect(typeof summary.log_level).toBe('string') expect(typeof summary.max_concurrent_snippet_fetch).toBe('number') expect(typeof summary.snippet_fetch_timeout_ms).toBe('number') expect(typeof summary.snippet_fetch_retry_attempts).toBe('number') }) }) }) describe('Language Utilities', () => { describe('LANGUAGE_MAPPINGS', () => { it('should have Python mapping', () => { const pythonMapping = LANGUAGE_MAPPINGS.find(m => m.languageNames.includes('python')) expect(pythonMapping).toBeDefined() expect(pythonMapping?.extensions).toContain('.py') expect(pythonMapping?.mimeType).toBe('text/x-python') expect(pythonMapping?.fenceName).toBe('python') }) it('should have TypeScript/JavaScript mapping', () => { const tsMapping = LANGUAGE_MAPPINGS.find(m => m.languageNames.includes('typescript')) expect(tsMapping).toBeDefined() expect(tsMapping?.extensions).toContain('.ts') expect(tsMapping?.extensions).toContain('.tsx') expect(tsMapping?.extensions).toContain('.js') expect(tsMapping?.extensions).toContain('.jsx') expect(tsMapping?.mimeType).toBe('text/x-typescript') expect(tsMapping?.fenceName).toBe('ts') }) it('should have Markdown mapping', () => { const mdMapping = LANGUAGE_MAPPINGS.find(m => m.languageNames.includes('markdown')) expect(mdMapping).toBeDefined() expect(mdMapping?.extensions).toContain('.md') expect(mdMapping?.mimeType).toBe('text/markdown') expect(mdMapping?.fenceName).toBe('markdown') }) it('should have JSON mapping', () => { const jsonMapping = LANGUAGE_MAPPINGS.find(m => m.languageNames.includes('json')) expect(jsonMapping).toBeDefined() expect(jsonMapping?.extensions).toContain('.json') expect(jsonMapping?.mimeType).toBe('application/json') expect(jsonMapping?.fenceName).toBe('json') }) it('should have HTML mapping', () => { const htmlMapping = LANGUAGE_MAPPINGS.find(m => m.languageNames.includes('html')) expect(htmlMapping).toBeDefined() expect(htmlMapping?.extensions).toContain('.html') expect(htmlMapping?.extensions).toContain('.htm') expect(htmlMapping?.mimeType).toBe('text/html') expect(htmlMapping?.fenceName).toBe('html') }) it('should have CSS mapping', () => { const cssMapping = LANGUAGE_MAPPINGS.find(m => m.languageNames.includes('css')) expect(cssMapping).toBeDefined() expect(cssMapping?.extensions).toContain('.css') expect(cssMapping?.mimeType).toBe('text/css') expect(cssMapping?.fenceName).toBe('css') }) it('should have YAML mapping', () => { const yamlMapping = LANGUAGE_MAPPINGS.find(m => m.languageNames.includes('yaml')) expect(yamlMapping).toBeDefined() expect(yamlMapping?.extensions).toContain('.yaml') expect(yamlMapping?.extensions).toContain('.yml') expect(yamlMapping?.mimeType).toBe('text/yaml') expect(yamlMapping?.fenceName).toBe('yaml') }) }) describe('getLanguageInfo', () => { it('should detect Python from language name', () => { const info = getLanguageInfo('python') expect(info.mimeType).toBe('text/x-python') expect(info.fenceName).toBe('python') }) it('should detect Python from file path', () => { const info = getLanguageInfo(undefined, 'test.py') expect(info.mimeType).toBe('text/x-python') expect(info.fenceName).toBe('python') }) it('should detect TypeScript from language name', () => { const info = getLanguageInfo('typescript') expect(info.mimeType).toBe('text/x-typescript') expect(info.fenceName).toBe('ts') }) it('should detect TypeScript from .ts file', () => { const info = getLanguageInfo(undefined, 'app.ts') expect(info.mimeType).toBe('text/x-typescript') expect(info.fenceName).toBe('ts') }) it('should detect JavaScript from .js file', () => { const info = getLanguageInfo(undefined, 'app.js') // JS and TS share the same mapping expect(info.mimeType).toBe('text/x-typescript') expect(info.fenceName).toBe('ts') }) it('should detect JSON from file extension', () => { const info = getLanguageInfo(undefined, 'package.json') expect(info.mimeType).toBe('application/json') expect(info.fenceName).toBe('json') }) it('should detect Markdown from file extension', () => { const info = getLanguageInfo(undefined, 'README.md') expect(info.mimeType).toBe('text/markdown') expect(info.fenceName).toBe('markdown') }) it('should handle case-insensitive language names', () => { const info1 = getLanguageInfo('PYTHON') const info2 = getLanguageInfo('Python') const info3 = getLanguageInfo('python') expect(info1.mimeType).toBe(info2.mimeType) expect(info2.mimeType).toBe(info3.mimeType) expect(info1.fenceName).toBe(info2.fenceName) expect(info2.fenceName).toBe(info3.fenceName) }) it('should handle case-insensitive file paths', () => { const info1 = getLanguageInfo(undefined, 'Test.PY') const info2 = getLanguageInfo(undefined, 'test.py') expect(info1.mimeType).toBe(info2.mimeType) expect(info1.fenceName).toBe(info2.fenceName) }) it('should default to plain text for unknown language', () => { const info = getLanguageInfo('unknown-lang') expect(info.mimeType).toBe('text/plain') expect(info.fenceName).toBeUndefined() }) it('should default to plain text for unknown extension', () => { const info = getLanguageInfo(undefined, 'file.xyz') expect(info.mimeType).toBe('text/plain') expect(info.fenceName).toBeUndefined() }) it('should prefer language name over path', () => { // Pass Python language but .ts path const info = getLanguageInfo('python', 'app.ts') // Should use Python (language name takes precedence) expect(info.mimeType).toBe('text/x-python') expect(info.fenceName).toBe('python') }) it('should handle no arguments', () => { const info = getLanguageInfo() expect(info.mimeType).toBe('text/plain') expect(info.fenceName).toBeUndefined() }) it('should handle paths with directories', () => { const info = getLanguageInfo(undefined, '/path/to/file.py') expect(info.mimeType).toBe('text/x-python') expect(info.fenceName).toBe('python') }) it('should handle short language aliases', () => { const info1 = getLanguageInfo('py') // Short for python const info2 = getLanguageInfo('ts') // Short for typescript const info3 = getLanguageInfo('js') // Short for javascript const info4 = getLanguageInfo('md') // Short for markdown expect(info1.fenceName).toBe('python') expect(info2.fenceName).toBe('ts') expect(info3.fenceName).toBe('ts') expect(info4.fenceName).toBe('markdown') }) }) describe('mimeFromLangOrPath', () => { it('should return MIME type from language', () => { expect(mimeFromLangOrPath('python')).toBe('text/x-python') expect(mimeFromLangOrPath('typescript')).toBe('text/x-typescript') expect(mimeFromLangOrPath('json')).toBe('application/json') }) it('should return MIME type from path', () => { expect(mimeFromLangOrPath(undefined, 'test.py')).toBe('text/x-python') expect(mimeFromLangOrPath(undefined, 'app.ts')).toBe('text/x-typescript') expect(mimeFromLangOrPath(undefined, 'config.json')).toBe('application/json') }) it('should default to text/plain', () => { expect(mimeFromLangOrPath('unknown')).toBe('text/plain') expect(mimeFromLangOrPath(undefined, 'file.xyz')).toBe('text/plain') }) }) describe('fenceLang', () => { it('should return fence name from language', () => { expect(fenceLang('python')).toBe('python') expect(fenceLang('typescript')).toBe('ts') expect(fenceLang('markdown')).toBe('markdown') }) it('should return fence name from path', () => { expect(fenceLang(undefined, 'test.py')).toBe('python') expect(fenceLang(undefined, 'app.ts')).toBe('ts') expect(fenceLang(undefined, 'README.md')).toBe('markdown') }) it('should return undefined for unknown language', () => { expect(fenceLang('unknown')).toBeUndefined() expect(fenceLang(undefined, 'file.xyz')).toBeUndefined() }) }) }) describe('Logger Utilities', () => { let testLogDir: string beforeEach(async () => { // Create a temporary log directory for testing testLogDir = join(tmpdir(), `mcp-test-logs-${Date.now()}`) await mkdir(testLogDir, { recursive: true }) }) afterEach(async () => { // Clean up test log directory try { await rm(testLogDir, { recursive: true, force: true }) } catch { // Ignore cleanup errors } }) describe('initLogger', () => { it('should create log directory', async () => { // initLogger creates the logs directory await expect(initLogger()).resolves.toBeUndefined() // Should not throw expect(true).toBe(true) }) it('should be idempotent', async () => { // Should work even if called multiple times await initLogger() await initLogger() await initLogger() expect(true).toBe(true) }) }) describe('logging functions', () => { beforeEach(async () => { await initLogger() }) it('should log info messages', async () => { await expect( logInfo('test_event', 'Test info message') ).resolves.toBeUndefined() }) it('should log info with metadata', async () => { await expect( logInfo('test_event', 'Test message', { key: 'value' }) ).resolves.toBeUndefined() }) it('should log info with context', async () => { await expect( logInfo('test_event', 'Test message', undefined, { requestId: '123' }) ).resolves.toBeUndefined() }) it('should log info with both metadata and context', async () => { await expect( logInfo( 'test_event', 'Test message', { data: 'value' }, { requestId: '123' } ) ).resolves.toBeUndefined() }) it('should log warning messages', async () => { await expect( logWarn('warning_event', 'Test warning message') ).resolves.toBeUndefined() }) it('should log warning with metadata', async () => { await expect( logWarn('warning_event', 'Test warning', { code: 404 }) ).resolves.toBeUndefined() }) it('should log error messages', async () => { await expect( logError('error_event', 'Test error message') ).resolves.toBeUndefined() }) it('should log error with metadata', async () => { await expect( logError('error_event', 'Test error', { stack: 'stack trace' }) ).resolves.toBeUndefined() }) it('should handle complex metadata objects', async () => { const complexMeta = { nested: { deep: { value: 123 } }, array: [1, 2, 3], nullValue: null, boolValue: true } await expect( logInfo('complex_event', 'Complex metadata', complexMeta) ).resolves.toBeUndefined() }) it('should handle empty strings', async () => { await expect( logInfo('', '') ).resolves.toBeUndefined() }) it('should handle special characters in messages', async () => { await expect( logInfo('special_chars', 'Message with "quotes" and \\backslashes\\') ).resolves.toBeUndefined() }) }) })

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/plasticbeachllc/dolphin-mcp'

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