Path: blob/main/src/vs/workbench/contrib/chat/test/common/chatDebugServiceImpl.test.ts
13406 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js';7import { errorHandler } from '../../../../../base/common/errors.js';8import { URI } from '../../../../../base/common/uri.js';9import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';10import { ChatDebugLogLevel, IChatDebugEvent, IChatDebugGenericEvent, IChatDebugLogProvider, IChatDebugModelTurnEvent, IChatDebugResolvedEventContent, IChatDebugToolCallEvent } from '../../common/chatDebugService.js';11import { ChatDebugServiceImpl } from '../../common/chatDebugServiceImpl.js';12import { LocalChatSessionUri } from '../../common/model/chatUri.js';1314suite('ChatDebugServiceImpl', () => {15const disposables = ensureNoDisposablesAreLeakedInTestSuite();1617let service: ChatDebugServiceImpl;1819const session1 = URI.parse('vscode-chat-session://local/session-1');20const session2 = URI.parse('vscode-chat-session://local/session-2');21const sessionA = LocalChatSessionUri.forSession('a');22const sessionB = LocalChatSessionUri.forSession('b');23const sessionGeneric = URI.parse('vscode-chat-session://local/session');24const nonLocalSession = URI.parse('some-other-scheme://authority/session-1');25const copilotCliSession = URI.parse('copilotcli:/test-session-id');26const claudeCodeSession = URI.parse('claude-code:/test-session-id');2728setup(() => {29service = disposables.add(new ChatDebugServiceImpl());30});3132suite('addEvent and getEvents', () => {33test('should add and retrieve events', () => {34const event: IChatDebugGenericEvent = {35kind: 'generic',36sessionResource: session1,37created: new Date(),38name: 'test-event',39level: ChatDebugLogLevel.Info,40};4142service.addEvent(event);4344assert.deepStrictEqual(service.getEvents(), [event]);45});4647test('should filter events by sessionResource', () => {48const event1: IChatDebugGenericEvent = {49kind: 'generic',50sessionResource: session1,51created: new Date(),52name: 'event-1',53level: ChatDebugLogLevel.Info,54};55const event2: IChatDebugGenericEvent = {56kind: 'generic',57sessionResource: session2,58created: new Date(),59name: 'event-2',60level: ChatDebugLogLevel.Warning,61};6263service.addEvent(event1);64service.addEvent(event2);6566assert.deepStrictEqual(service.getEvents(session1), [event1]);67assert.deepStrictEqual(service.getEvents(session2), [event2]);68assert.strictEqual(service.getEvents().length, 2);69});7071test('should fire onDidAddEvent when event is added', () => {72const firedEvents: IChatDebugEvent[] = [];73disposables.add(service.onDidAddEvent(e => firedEvents.push(e)));7475const event: IChatDebugGenericEvent = {76kind: 'generic',77sessionResource: session1,78created: new Date(),79name: 'test',80level: ChatDebugLogLevel.Info,81};82service.addEvent(event);8384assert.deepStrictEqual(firedEvents, [event]);85});8687test('should handle different event kinds', () => {88const toolCall: IChatDebugToolCallEvent = {89kind: 'toolCall',90sessionResource: session1,91created: new Date(),92toolName: 'readFile',93toolCallId: 'call-1',94input: '{"path": "/foo.ts"}',95output: 'file contents',96result: 'success',97durationInMillis: 42,98};99const modelTurn: IChatDebugModelTurnEvent = {100kind: 'modelTurn',101sessionResource: session1,102created: new Date(),103model: 'gpt-4',104inputTokens: 100,105outputTokens: 50,106totalTokens: 150,107durationInMillis: 1200,108};109110service.addEvent(toolCall);111service.addEvent(modelTurn);112113const events = service.getEvents(session1);114assert.strictEqual(events.length, 2);115assert.strictEqual(events[0].kind, 'toolCall');116assert.strictEqual(events[1].kind, 'modelTurn');117});118});119120suite('log', () => {121test('should create a generic event with defaults', () => {122const firedEvents: IChatDebugEvent[] = [];123disposables.add(service.onDidAddEvent(e => firedEvents.push(e)));124125service.log(session1, 'Some name', 'Some details');126127assert.strictEqual(firedEvents.length, 1);128const event = firedEvents[0];129assert.strictEqual(event.kind, 'generic');130assert.strictEqual(event.sessionResource.toString(), session1.toString());131assert.strictEqual((event as IChatDebugGenericEvent).name, 'Some name');132assert.strictEqual((event as IChatDebugGenericEvent).details, 'Some details');133assert.strictEqual((event as IChatDebugGenericEvent).level, ChatDebugLogLevel.Info);134});135136test('should accept custom level and options', () => {137const firedEvents: IChatDebugEvent[] = [];138disposables.add(service.onDidAddEvent(e => firedEvents.push(e)));139140service.log(session1, 'warning-event', 'oh no', ChatDebugLogLevel.Warning, {141id: 'my-id',142category: 'testing',143parentEventId: 'parent-1',144});145146const event = firedEvents[0] as IChatDebugGenericEvent;147assert.strictEqual(event.level, ChatDebugLogLevel.Warning);148assert.strictEqual(event.id, 'my-id');149assert.strictEqual(event.category, 'testing');150assert.strictEqual(event.parentEventId, 'parent-1');151});152153test('should not log events for ineligible session schemes', () => {154const firedEvents: IChatDebugEvent[] = [];155disposables.add(service.onDidAddEvent(e => firedEvents.push(e)));156157service.log(nonLocalSession, 'should-be-skipped', 'details');158159assert.strictEqual(firedEvents.length, 0);160assert.strictEqual(service.getEvents(nonLocalSession).length, 0);161});162163test('should log events for copilotcli sessions', () => {164const firedEvents: IChatDebugEvent[] = [];165disposables.add(service.onDidAddEvent(e => firedEvents.push(e)));166167service.log(copilotCliSession, 'cli-event', 'details');168169assert.strictEqual(firedEvents.length, 1);170assert.strictEqual(service.getEvents(copilotCliSession).length, 1);171});172173test('should log events for claude-code sessions', () => {174const firedEvents: IChatDebugEvent[] = [];175disposables.add(service.onDidAddEvent(e => firedEvents.push(e)));176177service.log(claudeCodeSession, 'claude-event', 'details');178179assert.strictEqual(firedEvents.length, 1);180assert.strictEqual(service.getEvents(claudeCodeSession).length, 1);181});182});183184suite('getSessionResources', () => {185test('should return unique session resources', () => {186service.addEvent({ kind: 'generic', sessionResource: sessionA, created: new Date(), name: 'e1', level: ChatDebugLogLevel.Info });187service.addEvent({ kind: 'generic', sessionResource: sessionB, created: new Date(), name: 'e2', level: ChatDebugLogLevel.Info });188service.addEvent({ kind: 'generic', sessionResource: sessionA, created: new Date(), name: 'e3', level: ChatDebugLogLevel.Info });189190const resources = service.getSessionResources();191assert.strictEqual(resources.length, 2);192});193194test('should return empty array when no events', () => {195assert.deepStrictEqual(service.getSessionResources(), []);196});197});198199suite('clear', () => {200test('should clear all events', () => {201service.addEvent({ kind: 'generic', sessionResource: sessionA, created: new Date(), name: 'e', level: ChatDebugLogLevel.Info });202service.addEvent({ kind: 'generic', sessionResource: sessionB, created: new Date(), name: 'e', level: ChatDebugLogLevel.Info });203204service.clear();205206assert.strictEqual(service.getEvents().length, 0);207});208});209210suite('MAX_EVENTS_PER_SESSION cap', () => {211test('should evict oldest events when exceeding per-session cap', () => {212// The max per session is 10_000. Add more than that to a single session.213for (let i = 0; i < 10_001; i++) {214service.addEvent({ kind: 'generic', sessionResource: sessionGeneric, created: new Date(), name: `event-${i}`, level: ChatDebugLogLevel.Info });215}216217const events = service.getEvents();218assert.ok(events.length <= 10_000, 'Should not exceed MAX_EVENTS_PER_SESSION');219// The first event should have been evicted220assert.ok(!(events as IChatDebugGenericEvent[]).find(e => e.name === 'event-0'), 'Event-0 should have been evicted');221// The last event should be present222assert.ok((events as IChatDebugGenericEvent[]).find(e => e.name === 'event-10000'), 'Last event should be present');223});224225test('should evict oldest session when exceeding MAX_SESSIONS', () => {226// MAX_SESSIONS is 5 — add events to 6 different sessions227const sessions: URI[] = [];228for (let i = 0; i < 6; i++) {229const uri = URI.parse(`vscode-chat-session://local/session-lru-${i}`);230sessions.push(uri);231service.addEvent({ kind: 'generic', sessionResource: uri, created: new Date(), name: `event-${i}`, level: ChatDebugLogLevel.Info });232}233234const resources = service.getSessionResources();235assert.strictEqual(resources.length, 5, 'Should not exceed MAX_SESSIONS');236// The first session should have been evicted237assert.ok(!resources.some(r => r.toString() === sessions[0].toString()), 'Session-0 should have been evicted');238assert.strictEqual(service.getEvents(sessions[0]).length, 0, 'Events from evicted session should be gone');239// The last session should be present240assert.ok(resources.some(r => r.toString() === sessions[5].toString()), 'Session-5 should be present');241});242243test('should use LRU eviction — recently-used sessions are kept', () => {244// Fill to MAX_SESSIONS (5)245const sessions: URI[] = [];246for (let i = 0; i < 5; i++) {247const uri = URI.parse(`vscode-chat-session://local/session-lru2-${i}`);248sessions.push(uri);249service.addEvent({ kind: 'generic', sessionResource: uri, created: new Date(), name: `init-${i}`, level: ChatDebugLogLevel.Info });250}251252// Touch session-0 so it moves to the back of the LRU order253service.addEvent({ kind: 'generic', sessionResource: sessions[0], created: new Date(), name: 'touch', level: ChatDebugLogLevel.Info });254255// Add a 6th session — session-1 (the true LRU) should be evicted, not session-0256const session6 = URI.parse('vscode-chat-session://local/session-lru2-5');257service.addEvent({ kind: 'generic', sessionResource: session6, created: new Date(), name: 'new', level: ChatDebugLogLevel.Info });258259const resources = service.getSessionResources();260assert.strictEqual(resources.length, 5);261assert.ok(resources.some(r => r.toString() === sessions[0].toString()), 'Session-0 should be kept (recently used)');262assert.ok(!resources.some(r => r.toString() === sessions[1].toString()), 'Session-1 should be evicted (LRU)');263assert.ok(resources.some(r => r.toString() === session6.toString()), 'Session-5 should be present');264});265});266267suite('activeSessionResource', () => {268test('should default to undefined', () => {269assert.strictEqual(service.activeSessionResource, undefined);270});271272test('should be settable', () => {273service.activeSessionResource = session1;274275assert.strictEqual(service.activeSessionResource, session1);276});277});278279suite('registerProvider', () => {280test('should register and unregister a provider', async () => {281const extSession = URI.parse('vscode-chat-session://local/ext-session');282const provider: IChatDebugLogProvider = {283provideChatDebugLog: async () => [{284kind: 'generic',285sessionResource: extSession,286created: new Date(),287name: 'from-provider',288level: ChatDebugLogLevel.Info,289}],290};291292const reg = service.registerProvider(provider);293await service.invokeProviders(extSession);294295const events = service.getEvents(extSession);296assert.ok(events.some(e => e.kind === 'generic' && (e as IChatDebugGenericEvent).name === 'from-provider'));297298reg.dispose();299});300301test('provider returning undefined should not add events', async () => {302const emptySession = URI.parse('vscode-chat-session://local/empty-session');303const provider: IChatDebugLogProvider = {304provideChatDebugLog: async () => undefined,305};306307disposables.add(service.registerProvider(provider));308await service.invokeProviders(emptySession);309310assert.strictEqual(service.getEvents(emptySession).length, 0);311});312313test('provider errors should be handled gracefully', async () => {314const errorSession = URI.parse('vscode-chat-session://local/error-session');315const provider: IChatDebugLogProvider = {316provideChatDebugLog: async () => { throw new Error('boom'); },317};318319disposables.add(service.registerProvider(provider));320// Suppress the expected onUnexpectedError from _invokeProvider321const origHandler = errorHandler.getUnexpectedErrorHandler();322errorHandler.setUnexpectedErrorHandler(() => { });323try {324await service.invokeProviders(errorSession);325} finally {326errorHandler.setUnexpectedErrorHandler(origHandler);327}328assert.strictEqual(service.getEvents(errorSession).length, 0);329});330});331332suite('invokeProviders', () => {333test('should invoke multiple providers and merge events', async () => {334const providerA: IChatDebugLogProvider = {335provideChatDebugLog: async () => [{336kind: 'generic',337sessionResource: sessionGeneric,338created: new Date(),339name: 'from-A',340level: ChatDebugLogLevel.Info,341}],342};343const providerB: IChatDebugLogProvider = {344provideChatDebugLog: async () => [{345kind: 'generic',346sessionResource: sessionGeneric,347created: new Date(),348name: 'from-B',349level: ChatDebugLogLevel.Info,350}],351};352353disposables.add(service.registerProvider(providerA));354disposables.add(service.registerProvider(providerB));355await service.invokeProviders(sessionGeneric);356357const names = (service.getEvents(sessionGeneric) as IChatDebugGenericEvent[]).map(e => e.name);358assert.ok(names.includes('from-A'));359assert.ok(names.includes('from-B'));360});361362test('should cancel previous invocation for same session', async () => {363let cancelledToken: CancellationToken | undefined;364365const provider: IChatDebugLogProvider = {366provideChatDebugLog: async (_sessionResource, token) => {367cancelledToken = token;368return [];369},370};371372disposables.add(service.registerProvider(provider));373374// First invocation375await service.invokeProviders(sessionGeneric);376const firstToken = cancelledToken!;377378// Second invocation for same session should cancel the first379await service.invokeProviders(sessionGeneric);380assert.strictEqual(firstToken.isCancellationRequested, true);381});382383test('should fire onDidClearProviderEvents when clearing provider events', async () => {384const clearedSessions: URI[] = [];385disposables.add(service.onDidClearProviderEvents(sessionResource => clearedSessions.push(sessionResource)));386387const provider: IChatDebugLogProvider = {388provideChatDebugLog: async (sessionResource) => [{389kind: 'generic',390sessionResource,391created: new Date(),392name: 'provider-event',393level: ChatDebugLogLevel.Info,394}],395};396397disposables.add(service.registerProvider(provider));398399// First invocation clears empty set and fires clear event400await service.invokeProviders(sessionGeneric);401assert.strictEqual(clearedSessions.length, 1, 'Clear event should fire on first invocation');402403// Second invocation clears provider events from first invocation404await service.invokeProviders(sessionGeneric);405assert.strictEqual(clearedSessions.length, 2, 'Clear event should fire on second invocation');406assert.strictEqual(clearedSessions[1].toString(), sessionGeneric.toString());407});408409test('should not cancel invocations for different sessions', async () => {410const tokens: Map<string, CancellationToken> = new Map();411412const provider: IChatDebugLogProvider = {413provideChatDebugLog: async (sessionResource, token) => {414tokens.set(sessionResource.toString(), token);415return [];416},417};418419disposables.add(service.registerProvider(provider));420421await service.invokeProviders(sessionA);422await service.invokeProviders(sessionB);423424const tokenA = tokens.get(sessionA.toString())!;425assert.strictEqual(tokenA.isCancellationRequested, false, 'session-a token should not be cancelled');426});427428test('should not invoke providers for ineligible session schemes', async () => {429let providerCalled = false;430431const provider: IChatDebugLogProvider = {432provideChatDebugLog: async () => {433providerCalled = true;434return [{435kind: 'generic',436sessionResource: nonLocalSession,437created: new Date(),438name: 'should-not-appear',439level: ChatDebugLogLevel.Info,440}];441},442};443444disposables.add(service.registerProvider(provider));445await service.invokeProviders(nonLocalSession);446447assert.strictEqual(providerCalled, false);448assert.strictEqual(service.getEvents(nonLocalSession).length, 0);449});450451test('should invoke providers for copilotcli sessions', async () => {452let providerCalled = false;453454const provider: IChatDebugLogProvider = {455provideChatDebugLog: async () => {456providerCalled = true;457return [{458kind: 'generic',459sessionResource: copilotCliSession,460created: new Date(),461name: 'cli-provider-event',462level: ChatDebugLogLevel.Info,463}];464},465};466467disposables.add(service.registerProvider(provider));468await service.invokeProviders(copilotCliSession);469470assert.strictEqual(providerCalled, true);471assert.ok(service.getEvents(copilotCliSession).length > 0);472});473474test('should invoke providers for claude-code sessions', async () => {475let providerCalled = false;476477const provider: IChatDebugLogProvider = {478provideChatDebugLog: async () => {479providerCalled = true;480return [{481kind: 'generic',482sessionResource: claudeCodeSession,483created: new Date(),484name: 'claude-provider-event',485level: ChatDebugLogLevel.Info,486}];487},488};489490disposables.add(service.registerProvider(provider));491await service.invokeProviders(claudeCodeSession);492493assert.strictEqual(providerCalled, true);494assert.ok(service.getEvents(claudeCodeSession).length > 0);495});496497test('newly registered provider should be invoked for active sessions', async () => {498// Start an invocation before the provider is registered499const firstProvider: IChatDebugLogProvider = {500provideChatDebugLog: async () => [],501};502disposables.add(service.registerProvider(firstProvider));503await service.invokeProviders(sessionGeneric);504505// Now register a new provider — it should be invoked for the active session506const lateEvents: IChatDebugEvent[] = [];507const lateProvider: IChatDebugLogProvider = {508provideChatDebugLog: async () => {509const event: IChatDebugGenericEvent = {510kind: 'generic',511sessionResource: sessionGeneric,512created: new Date(),513name: 'late-provider-event',514level: ChatDebugLogLevel.Info,515};516lateEvents.push(event);517return [event];518},519};520521disposables.add(service.registerProvider(lateProvider));522523// Give it a tick to let the async invocation complete524await new Promise(resolve => setTimeout(resolve, 10));525526assert.ok(lateEvents.length > 0, 'Late provider should have been invoked');527});528});529530suite('resolveEvent', () => {531test('should delegate to provider with resolveChatDebugLogEvent', async () => {532const resolved: IChatDebugResolvedEventContent = {533kind: 'text',534value: 'resolved detail text',535};536537const provider: IChatDebugLogProvider = {538provideChatDebugLog: async () => undefined,539resolveChatDebugLogEvent: async (eventId) => {540if (eventId === 'my-event') {541return resolved;542}543return undefined;544},545};546547disposables.add(service.registerProvider(provider));548549const result = await service.resolveEvent('my-event');550assert.deepStrictEqual(result, resolved);551});552553test('should return undefined if no provider resolves the event', async () => {554const provider: IChatDebugLogProvider = {555provideChatDebugLog: async () => undefined,556resolveChatDebugLogEvent: async () => undefined,557};558559disposables.add(service.registerProvider(provider));560561const result = await service.resolveEvent('nonexistent');562assert.strictEqual(result, undefined);563});564565test('should return undefined when no providers registered', async () => {566const result = await service.resolveEvent('any-id');567assert.strictEqual(result, undefined);568});569570test('should return first non-undefined resolution from multiple providers', async () => {571const provider1: IChatDebugLogProvider = {572provideChatDebugLog: async () => undefined,573resolveChatDebugLogEvent: async () => undefined,574};575const provider2: IChatDebugLogProvider = {576provideChatDebugLog: async () => undefined,577resolveChatDebugLogEvent: async () => ({ kind: 'text', value: 'from provider 2' }),578};579580disposables.add(service.registerProvider(provider1));581disposables.add(service.registerProvider(provider2));582583const result = await service.resolveEvent('any');584assert.deepStrictEqual(result, { kind: 'text', value: 'from provider 2' });585});586});587588suite('endSession', () => {589test('should cancel and remove the CTS for a session', async () => {590let capturedToken: CancellationToken | undefined;591592const provider: IChatDebugLogProvider = {593provideChatDebugLog: async (_sessionResource, token) => {594capturedToken = token;595return [];596},597};598599disposables.add(service.registerProvider(provider));600await service.invokeProviders(sessionGeneric);601602assert.ok(capturedToken);603assert.strictEqual(capturedToken.isCancellationRequested, false);604605service.endSession(sessionGeneric);606607assert.strictEqual(capturedToken.isCancellationRequested, true);608});609610test('should be safe to call for unknown session', () => {611// Should not throw612service.endSession(URI.parse('vscode-chat-session://local/nonexistent'));613});614615test('late provider should not be invoked for ended session', async () => {616const firstProvider: IChatDebugLogProvider = {617provideChatDebugLog: async () => [],618};619disposables.add(service.registerProvider(firstProvider));620await service.invokeProviders(sessionGeneric);621622service.endSession(sessionGeneric);623624let lateCalled = false;625const lateProvider: IChatDebugLogProvider = {626provideChatDebugLog: async () => {627lateCalled = true;628return [];629},630};631disposables.add(service.registerProvider(lateProvider));632633await new Promise(resolve => setTimeout(resolve, 10));634assert.strictEqual(lateCalled, false, 'Late provider should not be invoked for ended session');635});636});637638suite('dispose', () => {639test('should cancel active invocations on dispose', async () => {640let capturedToken: CancellationToken | undefined;641642const provider: IChatDebugLogProvider = {643provideChatDebugLog: async (_sessionResource, token) => {644capturedToken = token;645return [];646},647};648649disposables.add(service.registerProvider(provider));650await service.invokeProviders(sessionGeneric);651652const cts = new CancellationTokenSource();653disposables.add(cts);654655service.dispose();656657assert.ok(capturedToken);658assert.strictEqual(capturedToken.isCancellationRequested, true);659});660});661662suite('event deduplication', () => {663test('should deduplicate events with the same ID, keeping the richer kind', () => {664const userMsg: IChatDebugEvent = {665kind: 'userMessage',666id: 'shared-id-1',667sessionResource: session1,668created: new Date('2026-01-01T00:00:00Z'),669message: 'hello',670sections: [],671};672const subagent: IChatDebugEvent = {673kind: 'subagentInvocation',674id: 'shared-id-1',675sessionResource: session1,676created: new Date('2026-01-01T00:00:01Z'),677agentName: 'Explore',678};679service.addEvent(userMsg);680service.addEvent(subagent);681682const events = service.getEvents(session1);683assert.strictEqual(events.length, 1);684assert.strictEqual(events[0].kind, 'subagentInvocation');685});686687test('should keep richer event when it arrives first', () => {688const subagent: IChatDebugEvent = {689kind: 'subagentInvocation',690id: 'shared-id-2',691sessionResource: session1,692created: new Date('2026-01-01T00:00:00Z'),693agentName: 'Explore',694};695const userMsg: IChatDebugEvent = {696kind: 'userMessage',697id: 'shared-id-2',698sessionResource: session1,699created: new Date('2026-01-01T00:00:01Z'),700message: 'hello',701sections: [],702};703service.addEvent(subagent);704service.addEvent(userMsg);705706const events = service.getEvents(session1);707assert.strictEqual(events.length, 1);708assert.strictEqual(events[0].kind, 'subagentInvocation');709});710711test('should not fire onDidAddEvent for skipped duplicates', () => {712const firedKinds: string[] = [];713disposables.add(service.onDidAddEvent(e => firedKinds.push(e.kind)));714715const subagent: IChatDebugEvent = {716kind: 'subagentInvocation',717id: 'shared-id-3',718sessionResource: session1,719created: new Date('2026-01-01T00:00:00Z'),720agentName: 'Explore',721};722const userMsg: IChatDebugEvent = {723kind: 'userMessage',724id: 'shared-id-3',725sessionResource: session1,726created: new Date('2026-01-01T00:00:01Z'),727message: 'hello',728sections: [],729};730service.addEvent(subagent);731service.addEvent(userMsg); // should be skipped732733assert.deepStrictEqual(firedKinds, ['subagentInvocation']);734});735736test('should allow events without IDs to coexist', () => {737const event1: IChatDebugGenericEvent = {738kind: 'generic',739sessionResource: session1,740created: new Date('2026-01-01T00:00:00Z'),741name: 'a',742level: ChatDebugLogLevel.Info,743};744const event2: IChatDebugGenericEvent = {745kind: 'generic',746sessionResource: session1,747created: new Date('2026-01-01T00:00:01Z'),748name: 'b',749level: ChatDebugLogLevel.Info,750};751service.addEvent(event1);752service.addEvent(event2);753754const events = service.getEvents(session1);755assert.strictEqual(events.length, 2);756});757});758});759760761