Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
EnhancedIndexManager.telemetry.test.tsโ€ข10.6 kB
/** * Tests for EnhancedIndexManager telemetry functionality * * Validates telemetry tracking, metrics aggregation, and reporting */ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; // Mock modules before importing jest.unstable_mockModule('../../../../src/utils/logger.js', () => ({ logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() } })); jest.unstable_mockModule('../../../../src/config/ConfigManager.js', () => ({ ConfigManager: { getInstance: jest.fn(() => ({ getConfig: jest.fn(() => ({ elements: { enhanced_index: { enabled: true, limits: { maxTriggersPerElement: 50, maxTriggerLength: 50, maxKeywordsToCheck: 100 }, telemetry: { enabled: true, sampleRate: 1.0, // 100% sampling for tests metricsInterval: 100 // Fast interval for tests } } } })) })) } })); const { logger } = await import('../../../../src/utils/logger.js'); const { EnhancedIndexManager } = await import('../../../../src/portfolio/EnhancedIndexManager.js'); type ElementDefinition = import('../../../../src/portfolio/EnhancedIndexManager.js').ElementDefinition; describe('EnhancedIndexManager Telemetry', () => { let manager: any; let originalEnv: string | undefined; beforeEach(() => { // Save original env originalEnv = process.env.DOLLHOUSE_TELEMETRY_ENABLED; // Enable telemetry for tests process.env.DOLLHOUSE_TELEMETRY_ENABLED = 'true'; // Get manager instance manager = EnhancedIndexManager.getInstance(); // Clear mocks jest.clearAllMocks(); }); afterEach(() => { // Restore original env if (originalEnv !== undefined) { process.env.DOLLHOUSE_TELEMETRY_ENABLED = originalEnv; } else { delete process.env.DOLLHOUSE_TELEMETRY_ENABLED; } // Clear telemetry timer if (manager.telemetryTimer) { clearTimeout(manager.telemetryTimer); manager.telemetryTimer = null; } }); describe('telemetry tracking', () => { it('should track metrics when telemetry is enabled', () => { const triggers: Record<string, string[]> = {}; const elementDef: ElementDefinition = { core: { name: 'test-element', type: 'persona' }, search: { triggers: ['debug', 'troubleshoot', 'fix'] } }; // Call extractActionTriggers which includes telemetry manager.extractActionTriggers(elementDef, 'test-element', triggers); // Should log telemetry data expect(logger.debug).toHaveBeenCalledWith( expect.stringContaining('Telemetry:'), expect.objectContaining({ duration: expect.any(Number), elementName: 'test-element', elementType: 'persona', triggersExtracted: 3, uniqueTriggers: 3 }) ); }); it('should not track metrics when telemetry is disabled', () => { // Disable telemetry process.env.DOLLHOUSE_TELEMETRY_ENABLED = 'false'; const triggers: Record<string, string[]> = {}; const elementDef: ElementDefinition = { core: { name: 'test-element', type: 'persona' }, search: { triggers: ['debug'] } }; manager.extractActionTriggers(elementDef, 'test-element', triggers); // Should not log telemetry expect(logger.debug).not.toHaveBeenCalledWith( expect.stringContaining('Telemetry:'), expect.anything() ); }); it('should respect sampling rate', () => { // Mock Math.random to control sampling const originalRandom = Math.random; let callCount = 0; // Make first call pass sampling (< 0.1), rest fail Math.random = jest.fn(() => { callCount++; return callCount === 1 ? 0.05 : 0.5; }); // Create a new manager with low sample rate jest.replaceProperty(manager, 'isTelemetryEnabled', () => true); const triggers: Record<string, string[]> = {}; const elementDef: ElementDefinition = { core: { name: 'test-element', type: 'persona' }, search: { triggers: ['debug'] } }; // First call should be tracked (random = 0.05 < 0.1) manager.extractActionTriggers(elementDef, 'test-element', triggers); // Second call should not be tracked (random = 0.5 > 0.1) manager.extractActionTriggers(elementDef, 'test-element2', triggers); // Restore original random Math.random = originalRandom; // Only first call should be tracked expect(logger.debug).toHaveBeenCalledTimes(1); }); }); describe('metrics aggregation', () => { it('should aggregate metrics correctly', () => { const triggers: Record<string, string[]> = {}; // Process multiple elements for (let i = 0; i < 3; i++) { const elementDef: ElementDefinition = { core: { name: `element-${i}`, type: 'persona' }, search: { triggers: ['debug', 'test'] } }; manager.extractActionTriggers(elementDef, `element-${i}`, triggers); } // Check internal metrics const metrics = manager.telemetryMetrics.get('extractActionTriggers'); expect(metrics).toBeDefined(); expect(metrics.count).toBe(3); expect(metrics.avgDuration).toBeGreaterThan(0); expect(metrics.minDuration).toBeLessThanOrEqual(metrics.maxDuration); }); it('should track duration statistics', () => { const triggers: Record<string, string[]> = {}; const elementDef: ElementDefinition = { core: { name: 'test-element', type: 'persona' }, search: { triggers: Array.from({ length: 20 }, (_, i) => `trigger${i}`) } }; // Process element with many triggers (should take longer) manager.extractActionTriggers(elementDef, 'test-element', triggers); const metrics = manager.telemetryMetrics.get('extractActionTriggers'); expect(metrics).toBeDefined(); expect(metrics.totalDuration).toBeGreaterThan(0); expect(metrics.maxDuration).toBeGreaterThan(0); expect(metrics.minDuration).toBeGreaterThan(0); }); }); describe('telemetry reporting', () => { it('should schedule periodic reporting', (done) => { const triggers: Record<string, string[]> = {}; const elementDef: ElementDefinition = { core: { name: 'test-element', type: 'persona' }, search: { triggers: ['debug'] } }; // Process element to trigger telemetry manager.extractActionTriggers(elementDef, 'test-element', triggers); // Check that timer was set expect(manager.telemetryTimer).toBeDefined(); // Wait for report setTimeout(() => { expect(logger.info).toHaveBeenCalledWith( 'Telemetry Report', expect.objectContaining({ timestamp: expect.stringMatching(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/), metrics: expect.objectContaining({ extractActionTriggers: expect.objectContaining({ count: expect.any(Number), avgDuration: expect.any(Number) }) }) }) ); // Metrics should be cleared after reporting expect(manager.telemetryMetrics.size).toBe(0); done(); }, 150); // Wait longer than metricsInterval (100ms) }); it('should not report when no metrics collected', (done) => { // Clear any existing metrics manager.telemetryMetrics.clear(); // Manually trigger report manager.reportTelemetry(); // Should not log report expect(logger.info).not.toHaveBeenCalledWith( 'Telemetry Report', expect.anything() ); done(); }); }); describe('telemetry configuration', () => { it('should load telemetry config from ConfigManager', () => { // Config should be loaded in constructor expect(manager.constructor.VERB_EXTRACTION_CONFIG.telemetry).toEqual({ enabled: true, sampleRate: 1.0, metricsInterval: 100 }); }); it('should handle missing telemetry config gracefully', () => { // Mock ConfigManager to return no enhanced_index config const { ConfigManager } = require('../../../../src/config/ConfigManager.js'); ConfigManager.getInstance.mockReturnValue({ getConfig: jest.fn(() => ({ elements: {} })) }); // Should not throw expect(() => { manager.loadEnhancedIndexConfig(); }).not.toThrow(); // Should log warning expect(logger.warn).toHaveBeenCalledWith( expect.stringContaining('Failed to load enhanced index configuration'), expect.anything() ); }); }); describe('edge cases', () => { it('should handle null/undefined elementDef without telemetry errors', () => { const triggers: Record<string, string[]> = {}; // Should not throw expect(() => { manager.extractActionTriggers(null, 'test', triggers); manager.extractActionTriggers(undefined, 'test', triggers); }).not.toThrow(); // Should not track telemetry for null/undefined expect(logger.debug).not.toHaveBeenCalledWith( expect.stringContaining('Telemetry:'), expect.anything() ); }); it('should handle concurrent telemetry operations', () => { const triggers: Record<string, string[]> = {}; // Process multiple elements concurrently const promises = Array.from({ length: 5 }, (_, i) => { const elementDef: ElementDefinition = { core: { name: `concurrent-${i}`, type: 'persona' }, search: { triggers: [`trigger-${i}`] } }; return Promise.resolve( manager.extractActionTriggers(elementDef, `concurrent-${i}`, triggers) ); }); return Promise.all(promises).then(() => { // All operations should be tracked const metrics = manager.telemetryMetrics.get('extractActionTriggers'); expect(metrics).toBeDefined(); expect(metrics.count).toBe(5); }); }); }); });

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/DollhouseMCP/DollhouseMCP'

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