Skip to main content
Glama
test-improvements.js12.3 kB
/** * 綜合測試文件 * 驗證所有性能、可靠性和新功能改進 */ import { QueryCache } from './src/utils/query-cache.js'; import { withTimeout, TIMEOUT_CONFIG } from './src/utils/timeout.js'; import { RateLimiter } from './src/security/rate-limiter.js'; import { sanitizeText, validateConversationId, validateNumberRange, validateArray } from './src/security/input-validator.js'; import { Logger, createLogger } from './src/utils/logger.js'; import { MetricsCollector } from './src/monitoring/metrics.js'; import { HealthChecker } from './src/health/index.js'; import { AuditLogger } from './src/security/audit-log.js'; console.log('🧪 開始測試所有改進功能...\n'); let testsRun = 0; let testsPassed = 0; let testsFailed = 0; function test(name, fn) { testsRun++; try { fn(); console.log(`✅ ${name}`); testsPassed++; } catch (error) { console.error(`❌ ${name}`); console.error(` 錯誤: ${error.message}`); testsFailed++; } } function assert(condition, message) { if (!condition) { throw new Error(message || 'Assertion failed'); } } function assertEquals(actual, expected, message) { if (actual !== expected) { throw new Error(message || `Expected ${expected} but got ${actual}`); } } // ==================== 查詢緩存測試 ==================== console.log('📦 測試查詢緩存系統...'); test('QueryCache: 創建實例', () => { const cache = new QueryCache(10, 1000); assert(cache.maxSize === 10); assert(cache.maxAge === 1000); }); test('QueryCache: 設置和獲取查詢', () => { const cache = new QueryCache(10, 60000); const keywords = [{ word: 'test', weight: 1 }]; const result = { data: 'cached result' }; cache.setQuery(keywords, 'conv1', {}, result); const retrieved = cache.getQuery(keywords, 'conv1', {}); assert(retrieved !== null); assert(retrieved.data === 'cached result'); }); test('QueryCache: 緩存命中統計', () => { const cache = new QueryCache(10, 60000); const keywords = [{ word: 'hello', weight: 1 }]; cache.setQuery(keywords, 'conv1', {}, { data: 'test' }); cache.getQuery(keywords, 'conv1', {}); cache.getQuery(keywords, 'conv1', {}); cache.getQuery([{ word: 'nonexistent', weight: 1 }], 'conv1', {}); const stats = cache.getStats(); assertEquals(stats.hits, 2); assertEquals(stats.misses, 1); }); test('QueryCache: 失效對話緩存', () => { const cache = new QueryCache(10, 60000); cache.setQuery([{ word: 'test', weight: 1 }], 'conv1', {}, { data: '1' }); cache.setQuery([{ word: 'test', weight: 1 }], 'conv2', {}, { data: '2' }); const invalidated = cache.invalidateConversation('conv1'); assert(invalidated === 1); const result = cache.getQuery([{ word: 'test', weight: 1 }], 'conv1', {}); assert(result === null); }); // ==================== 超時處理測試 ==================== console.log('\n⏱️ 測試超時處理...'); test('Timeout: 正常完成', async () => { const promise = Promise.resolve('success'); const result = await withTimeout(promise, 1000, 'Test timeout'); assertEquals(result, 'success'); }); test('Timeout: 超時錯誤', async () => { const slowPromise = new Promise(resolve => setTimeout(() => resolve('late'), 100)); try { await withTimeout(slowPromise, 10, 'Should timeout'); throw new Error('Should have thrown timeout error'); } catch (error) { assert(error.message.includes('timeout')); } }); test('Timeout: 配置常量存在', () => { assert(typeof TIMEOUT_CONFIG.SEARCH === 'number'); assert(typeof TIMEOUT_CONFIG.BACKUP === 'number'); assert(TIMEOUT_CONFIG.SEARCH > 0); }); // ==================== 速率限制測試 ==================== console.log('\n🚦 測試速率限制器...'); test('RateLimiter: 創建實例', () => { const limiter = new RateLimiter(5, 1000); assert(limiter.maxRequests === 5); }); test('RateLimiter: 允許正常請求', () => { const limiter = new RateLimiter(10, 60000); const result = limiter.checkLimit('user1', 'tool1'); assert(result.allowed === true); assert(result.remaining === 9); }); test('RateLimiter: 超過限制阻止請求', () => { const limiter = new RateLimiter(3, 60000); for (let i = 0; i < 3; i++) { limiter.checkLimit('user2', 'tool1'); } const result = limiter.checkLimit('user2', 'tool1'); assert(result.allowed === false); assertEquals(result.remaining, 0); }); test('RateLimiter: 獲取統計信息', () => { const limiter = new RateLimiter(10, 60000); limiter.checkLimit('user3', 'tool1'); limiter.checkLimit('user4', 'tool1'); const stats = limiter.getStats(); assert(stats.activeConversations >= 2); }); test('RateLimiter: 解除阻止', () => { const limiter = new RateLimiter(2, 60000); limiter.checkLimit('user5', 'tool1'); limiter.checkLimit('user5', 'tool1'); limiter.checkLimit('user5', 'tool1'); // 觸發阻止 limiter.unblock('user5'); const result = limiter.checkLimit('user5', 'tool1'); assert(result.allowed === true); }); // ==================== 輸入驗證測試 ==================== console.log('\n🛡️ 測試輸入驗證...'); test('Input: 清理有效文本', () => { const result = sanitizeText('Hello World!'); assertEquals(result, 'Hello World!'); }); test('Input: 移除控制字符', () => { const dirty = 'Hello\x00\x01World'; const result = sanitizeText(dirty); assertEquals(result, 'HelloWorld'); }); test('Input: 拒絕過長文本', () => { const longText = 'a'.repeat(200000); try { sanitizeText(longText, { maxLength: 100000 }); throw new Error('Should have thrown error'); } catch (error) { assert(error.message.includes('too long')); } }); test('Input: 驗證有效對話ID', () => { const result = validateConversationId('valid-conversation_123'); assertEquals(result, 'valid-conversation_123'); }); test('Input: 拒絕無效對話ID', () => { try { validateConversationId('invalid@conversation!'); throw new Error('Should have thrown error'); } catch (error) { assert(error.message.includes('Invalid')); } }); test('Input: 驗證數字範圍', () => { const result = validateNumberRange(5, 0, 10); assertEquals(result, 5); }); test('Input: 拒絕超範圍數字', () => { try { validateNumberRange(15, 0, 10); throw new Error('Should have thrown error'); } catch (error) { assert(error.message.includes('between')); } }); test('Input: 驗證數組', () => { const result = validateArray([1, 2, 3], { maxLength: 5 }); assertEquals(result.length, 3); }); // ==================== 日誌系統測試 ==================== console.log('\n📝 測試結構化日誌系統...'); test('Logger: 創建實例', () => { const logger = createLogger('test-module'); assert(logger.context === 'test-module'); }); test('Logger: 設置最低級別', () => { const logger = new Logger('test', 'WARN'); assert(logger.minLevel === 'WARN'); assert(logger.shouldLog('ERROR') === true); assert(logger.shouldLog('INFO') === false); }); test('Logger: 創建子日誌器', () => { const logger = createLogger('parent'); const child = logger.child('child'); assert(child.context === 'parent.child'); }); // ==================== 性能指標測試 ==================== console.log('\n📊 測試性能指標收集器...'); test('Metrics: 創建實例', () => { const metrics = new MetricsCollector(); assert(metrics.requestCount === 0); }); test('Metrics: 記錄請求', () => { const metrics = new MetricsCollector(); metrics.recordRequest({ duration: 100, success: true, toolName: 'test' }); assert(metrics.requestCount === 1); assert(metrics.successCount === 1); }); test('Metrics: 記錄失敗請求', () => { const metrics = new MetricsCollector(); metrics.recordRequest({ duration: 200, success: false, toolName: 'test', error: new Error('test') }); assert(metrics.errorCount === 1); }); test('Metrics: 獲取統計信息', () => { const metrics = new MetricsCollector(); metrics.recordRequest({ duration: 50, success: true, toolName: 'tool1' }); metrics.recordRequest({ duration: 100, success: true, toolName: 'tool1' }); const stats = metrics.getMetrics(); assert(stats.requests.total === 2); assert(parseFloat(stats.latency.avg) > 0); }); test('Metrics: 緩存統計', () => { const metrics = new MetricsCollector(); metrics.recordCacheAccess(true); metrics.recordCacheAccess(true); metrics.recordCacheAccess(false); const stats = metrics.getMetrics(); assertEquals(stats.cache.hits, 2); assertEquals(stats.cache.misses, 1); }); test('Metrics: 按工具分類統計', () => { const metrics = new MetricsCollector(); metrics.recordRequest({ duration: 100, success: true, toolName: 'search', conversationId: 'test' }); metrics.recordRequest({ duration: 150, success: true, toolName: 'search', conversationId: 'test' }); const stats = metrics.getMetrics(); assert(stats.toolStats.search.count === 2); }); // ==================== 健康檢查測試 ==================== console.log('\n💚 測試健康檢查系統...'); test('Health: 創建實例', () => { const managers = { shortTerm: new Map(), longTerm: new Map() }; const metrics = new MetricsCollector(); const checker = new HealthChecker(managers, metrics); assert(checker !== null); }); test('Health: 註冊檢查', () => { const managers = { shortTerm: new Map(), longTerm: new Map() }; const metrics = new MetricsCollector(); const checker = new HealthChecker(managers, metrics); checker.registerCheck('custom', async () => ({ status: 'healthy', message: 'OK' })); assert(checker.checks.has('custom')); }); test('Health: 執行健康檢查', async () => { const managers = { shortTerm: new Map(), longTerm: new Map() }; const metrics = new MetricsCollector(); const checker = new HealthChecker(managers, metrics); const result = await checker.checkHealth(); assert(result.status !== undefined); assert(result.timestamp !== undefined); }); test('Health: 獲取簡單狀態', async () => { const managers = { shortTerm: new Map(), longTerm: new Map() }; const metrics = new MetricsCollector(); const checker = new HealthChecker(managers, metrics); const result = await checker.getSimpleHealth(); assert(result.status !== undefined); assert(typeof result.uptime === 'number'); }); // ==================== 審計日誌測試 ==================== console.log('\n🔍 測試審計日誌系統...'); test('Audit: 創建實例', () => { const audit = new AuditLogger('/tmp/test-audit.log', { enableFileLogging: false }); assert(audit !== null); }); test('Audit: 記錄事件', async () => { const audit = new AuditLogger('/tmp/test-audit.log', { enableFileLogging: false, enableConsoleLogging: false }); await audit.log({ type: 'tool_call', conversationId: 'test', toolName: 'test_tool', success: true }); assert(audit.buffer.length === 1); }); test('Audit: 緩衝區刷新', async () => { const audit = new AuditLogger('/tmp/test-audit2.log', { enableFileLogging: false, enableConsoleLogging: false }); for (let i = 0; i < 5; i++) { await audit.log({ type: 'test', conversationId: 'test', success: true }); } assert(audit.buffer.length > 0); await audit.flush(); // 由於禁用了文件記錄,緩衝區應該被清空 }); // ==================== 總結 ==================== console.log('\n' + '='.repeat(60)); console.log('測試完成!'); console.log('='.repeat(60)); console.log(`總計: ${testsRun} 個測試`); console.log(`✅ 通過: ${testsPassed}`); console.log(`❌ 失敗: ${testsFailed}`); console.log('='.repeat(60)); if (testsFailed > 0) { process.exit(1); } else { console.log('\n🎉 所有測試通過!所有改進功能運作正常。'); process.exit(0); }

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/win10ogod/memory-mcp-server'

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