UNPKG

vvlad1973-telegram-framework

Version:
330 lines (268 loc) 8.85 kB
'use strict'; /** * Test suite for Scenario user tracing functionality * Tests memory leak prevention, tracing events, and integration */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { Scenario } from '../src/classes/scenario.js'; import SimpleLogger from '@vvlad1973/simple-logger'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Mock logger that suppresses output during tests class TestLogger extends SimpleLogger { constructor() { super(); } trace() {} info() {} debug() {} warn() {} error() {} } // Mock user tracing service class MockUserTracingService { constructor() { this.traces = new Map(); this.trackedUsers = new Set(); } isTracing(userId) { return this.trackedUsers.has(userId); } trace(userId, event, data) { if (!this.traces.has(userId)) { this.traces.set(userId, []); } this.traces.get(userId).push({ timestamp: Date.now(), event, data, }); } enableTracing(userId) { this.trackedUsers.add(userId); } disableTracing(userId) { this.trackedUsers.delete(userId); } getTraces(userId) { return this.traces.get(userId) || []; } clear() { this.traces.clear(); this.trackedUsers.clear(); } } // Test dialog content const dialogContent = ` [ { "_id": "START_DIALOG", "received": { "type": "command", "text": "start" }, "actions": [ { "action": "testAction", "options": { "message": "Hello from start dialog" } } ] }, { "_id": "NESTED_DIALOG", "state": "active", "actions": [ { "action": "doDialog", "options": { "dialog": "CHILD_DIALOG" } } ] }, { "_id": "CHILD_DIALOG", "actions": [ { "action": "testAction", "options": { "message": "Child dialog executed" } } ] }, { "_id": "SWITCH_DIALOG", "received": { "type": "message" }, "actions": [ { "action": "doSwitch", "options": { "value": "test" }, "cases": [ { "case": "test", "actions": [ { "action": "testAction", "options": { "message": "Switch case matched" } } ] } ] } ] } ] `.trim(); describe('Scenario User Tracing', () => { let dialogPath; beforeEach(() => { dialogPath = join(__dirname, 'test-dialogs.jsonc'); fs.writeFileSync(dialogPath, dialogContent, 'utf8'); }); afterEach(() => { if (fs.existsSync(dialogPath)) { fs.unlinkSync(dialogPath); } }); it('should cleanup context after successful execution', async () => { const scenario = new Scenario(new TestLogger()); const tracingService = new MockUserTracingService(); scenario.userTracing = tracingService; tracingService.enableTracing(12345); await scenario.loadDialogs([dialogPath]); scenario.actions['testAction'] = async () => true; const request = { type: 'command', contents: 'start' }; const user = { id: 12345 }; const context = {}; await scenario.process(request, user, context); // Access private field via reflection (for testing only) const traceContext = scenario['#traceContext']; expect(traceContext).toBeUndefined(); }); it('should cleanup context after error', async () => { const scenario = new Scenario(new TestLogger()); const tracingService = new MockUserTracingService(); scenario.userTracing = tracingService; tracingService.enableTracing(67890); await scenario.loadDialogs([dialogPath]); scenario.actions['testAction'] = async () => { throw new Error('Test error'); }; const request = { type: 'command', contents: 'start' }; const user = { id: 67890 }; const context = {}; await scenario.process(request, user, context); const traceContext = scenario['#traceContext']; expect(traceContext).toBeUndefined(); }); it('should capture tracing events correctly', async () => { const scenario = new Scenario(new TestLogger()); const tracingService = new MockUserTracingService(); scenario.userTracing = tracingService; tracingService.enableTracing(111); await scenario.loadDialogs([dialogPath]); scenario.actions['testAction'] = async () => true; const request = { type: 'command', contents: 'start' }; const user = { id: 111 }; const context = {}; await scenario.process(request, user, context); const traces = tracingService.getTraces(111); expect(traces.length).toBeGreaterThan(0); const events = traces.map((t) => t.event); expect(events).toContain('request_received'); expect(events).toContain('dialog_matched'); expect(events).toContain('action_start'); expect(events).toContain('action_result'); expect(events).toContain('request_complete'); }); it('should work without userTracing service', async () => { const scenario = new Scenario(new TestLogger()); // userTracing is null by default await scenario.loadDialogs([dialogPath]); scenario.actions['testAction'] = async () => true; const request = { type: 'command', contents: 'start' }; const user = { id: 222 }; const context = {}; // Should not throw error await expect(scenario.process(request, user, context)).resolves.toBeUndefined(); }); it('should trace only for enabled users', async () => { const scenario = new Scenario(new TestLogger()); const tracingService = new MockUserTracingService(); scenario.userTracing = tracingService; tracingService.enableTracing(333); await scenario.loadDialogs([dialogPath]); scenario.actions['testAction'] = async () => true; const request = { type: 'command', contents: 'start' }; await scenario.process(request, { id: 333 }, {}); await scenario.process(request, { id: 444 }, {}); const traces333 = tracingService.getTraces(333); const traces444 = tracingService.getTraces(444); expect(traces333.length).toBeGreaterThan(0); expect(traces444.length).toBe(0); }); it('should trace doDialog correctly', async () => { const scenario = new Scenario(new TestLogger()); const tracingService = new MockUserTracingService(); scenario.userTracing = tracingService; tracingService.enableTracing(555); await scenario.loadDialogs([dialogPath]); scenario.actions['testAction'] = async () => true; const request = { type: 'message', contents: 'test' }; const user = { id: 555, state: 'active' }; const context = {}; await scenario.process(request, user, context); const traces = tracingService.getTraces(555); const events = traces.map((t) => t.event); expect(events).toContain('do_dialog'); expect(events).toContain('do_dialog_return'); }); it('should trace doSwitch correctly', async () => { const scenario = new Scenario(new TestLogger()); const tracingService = new MockUserTracingService(); scenario.userTracing = tracingService; tracingService.enableTracing(666); await scenario.loadDialogs([dialogPath]); scenario.actions['testAction'] = async () => true; const request = { type: 'message', contents: 'anything' }; const user = { id: 666 }; const context = {}; await scenario.process(request, user, context); const traces = tracingService.getTraces(666); const events = traces.map((t) => t.event); expect(events).toContain('switch_start'); expect(events).toContain('switch_case_matched'); }); it('should format large strings in traces', async () => { const scenario = new Scenario(new TestLogger()); const tracingService = new MockUserTracingService(); scenario.userTracing = tracingService; tracingService.enableTracing(777); await scenario.loadDialogs([dialogPath]); scenario.actions['testAction'] = async () => true; const largeString = 'x'.repeat(300); const request = { type: 'command', contents: largeString }; const user = { id: 777 }; const context = {}; await scenario.process(request, user, context); const traces = tracingService.getTraces(777); const requestReceived = traces.find((t) => t.event === 'request_received'); expect(requestReceived).toBeDefined(); const contentsData = requestReceived.data.request.contents; expect(contentsData).toHaveProperty('_type', 'string'); expect(contentsData).toHaveProperty('_len', 300); expect(contentsData).toHaveProperty('_preview'); }); });