implementation SessionStart and SessionEnd hook

This commit is contained in:
DennisYu07 2026-03-03 23:39:57 -08:00
parent 7cde98e238
commit f0cc28f80f
11 changed files with 2099 additions and 7 deletions

View file

@ -8,6 +8,10 @@ import { vi, describe, it, expect, beforeEach } from 'vitest';
import { clearCommand } from './clearCommand.js';
import { type CommandContext } from './types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import {
SessionEndReason,
SessionStartSource,
} from '@qwen-code/qwen-code-core';
// Mock the telemetry service
vi.mock('@qwen-code/qwen-code-core', async () => {
@ -26,10 +30,19 @@ describe('clearCommand', () => {
let mockContext: CommandContext;
let mockResetChat: ReturnType<typeof vi.fn>;
let mockStartNewSession: ReturnType<typeof vi.fn>;
let mockFireSessionEndEvent: ReturnType<typeof vi.fn>;
let mockFireSessionStartEvent: ReturnType<typeof vi.fn>;
let mockGetHookSystem: ReturnType<typeof vi.fn>;
beforeEach(() => {
mockResetChat = vi.fn().mockResolvedValue(undefined);
mockStartNewSession = vi.fn().mockReturnValue('new-session-id');
mockFireSessionEndEvent = vi.fn().mockResolvedValue(undefined);
mockFireSessionStartEvent = vi.fn().mockResolvedValue(undefined);
mockGetHookSystem = vi.fn().mockReturnValue({
fireSessionEndEvent: mockFireSessionEndEvent,
fireSessionStartEvent: mockFireSessionStartEvent,
});
vi.clearAllMocks();
mockContext = createMockCommandContext({
@ -40,6 +53,11 @@ describe('clearCommand', () => {
resetChat: mockResetChat,
}) as unknown as GeminiClient,
startNewSession: mockStartNewSession,
getHookSystem: mockGetHookSystem,
getDebugLogger: () => ({
warn: vi.fn(),
}),
getModel: () => 'test-model',
},
},
session: {
@ -75,6 +93,50 @@ describe('clearCommand', () => {
expect(mockContext.ui.clear).toHaveBeenCalled();
});
it('should fire SessionEnd event before clearing and SessionStart event after clearing', async () => {
if (!clearCommand.action) {
throw new Error('clearCommand must have an action.');
}
await clearCommand.action(mockContext, '');
expect(mockGetHookSystem).toHaveBeenCalled();
expect(mockFireSessionEndEvent).toHaveBeenCalledWith(
SessionEndReason.Clear,
);
expect(mockFireSessionStartEvent).toHaveBeenCalledWith(
SessionStartSource.Clear,
'test-model',
);
// SessionEnd should be called before SessionStart
const sessionEndCallOrder =
mockFireSessionEndEvent.mock.invocationCallOrder[0];
const sessionStartCallOrder =
mockFireSessionStartEvent.mock.invocationCallOrder[0];
expect(sessionEndCallOrder).toBeLessThan(sessionStartCallOrder);
});
it('should handle hook errors gracefully and continue execution', async () => {
if (!clearCommand.action) {
throw new Error('clearCommand must have an action.');
}
mockFireSessionEndEvent.mockRejectedValue(
new Error('SessionEnd hook failed'),
);
mockFireSessionStartEvent.mockRejectedValue(
new Error('SessionStart hook failed'),
);
await clearCommand.action(mockContext, '');
// Should still complete the clear operation despite hook errors
expect(mockStartNewSession).toHaveBeenCalledTimes(1);
expect(mockResetChat).toHaveBeenCalledTimes(1);
expect(mockContext.ui.clear).toHaveBeenCalledTimes(1);
});
it('should not attempt to reset chat if config service is not available', async () => {
if (!clearCommand.action) {
throw new Error('clearCommand must have an action.');