Files
clawdbot/src/hooks/internal-hooks.test.ts
2026-01-17 01:31:57 +00:00

230 lines
7.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
clearInternalHooks,
createInternalHookEvent,
getRegisteredEventKeys,
registerInternalHook,
triggerInternalHook,
unregisterInternalHook,
type InternalHookEvent,
} from './internal-hooks.js';
describe('internal-hooks', () => {
beforeEach(() => {
clearInternalHooks();
});
afterEach(() => {
clearInternalHooks();
});
describe('registerInternalHook', () => {
it('should register a hook handler', () => {
const handler = vi.fn();
registerInternalHook('command:new', handler);
const keys = getRegisteredEventKeys();
expect(keys).toContain('command:new');
});
it('should allow multiple handlers for the same event', () => {
const handler1 = vi.fn();
const handler2 = vi.fn();
registerInternalHook('command:new', handler1);
registerInternalHook('command:new', handler2);
const keys = getRegisteredEventKeys();
expect(keys).toContain('command:new');
});
});
describe('unregisterInternalHook', () => {
it('should unregister a specific handler', () => {
const handler1 = vi.fn();
const handler2 = vi.fn();
registerInternalHook('command:new', handler1);
registerInternalHook('command:new', handler2);
unregisterInternalHook('command:new', handler1);
const event = createInternalHookEvent('command', 'new', 'test-session');
void triggerInternalHook(event);
expect(handler1).not.toHaveBeenCalled();
expect(handler2).toHaveBeenCalled();
});
it('should clean up empty handler arrays', () => {
const handler = vi.fn();
registerInternalHook('command:new', handler);
unregisterInternalHook('command:new', handler);
const keys = getRegisteredEventKeys();
expect(keys).not.toContain('command:new');
});
});
describe('triggerInternalHook', () => {
it('should trigger handlers for general event type', async () => {
const handler = vi.fn();
registerInternalHook('command', handler);
const event = createInternalHookEvent('command', 'new', 'test-session');
await triggerInternalHook(event);
expect(handler).toHaveBeenCalledWith(event);
});
it('should trigger handlers for specific event action', async () => {
const handler = vi.fn();
registerInternalHook('command:new', handler);
const event = createInternalHookEvent('command', 'new', 'test-session');
await triggerInternalHook(event);
expect(handler).toHaveBeenCalledWith(event);
});
it('should trigger both general and specific handlers', async () => {
const generalHandler = vi.fn();
const specificHandler = vi.fn();
registerInternalHook('command', generalHandler);
registerInternalHook('command:new', specificHandler);
const event = createInternalHookEvent('command', 'new', 'test-session');
await triggerInternalHook(event);
expect(generalHandler).toHaveBeenCalledWith(event);
expect(specificHandler).toHaveBeenCalledWith(event);
});
it('should handle async handlers', async () => {
const handler = vi.fn(async () => {
await new Promise((resolve) => setTimeout(resolve, 10));
});
registerInternalHook('command:new', handler);
const event = createInternalHookEvent('command', 'new', 'test-session');
await triggerInternalHook(event);
expect(handler).toHaveBeenCalledWith(event);
});
it('should catch and log errors from handlers', async () => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
const errorHandler = vi.fn(() => {
throw new Error('Handler failed');
});
const successHandler = vi.fn();
registerInternalHook('command:new', errorHandler);
registerInternalHook('command:new', successHandler);
const event = createInternalHookEvent('command', 'new', 'test-session');
await triggerInternalHook(event);
expect(errorHandler).toHaveBeenCalled();
expect(successHandler).toHaveBeenCalled();
expect(consoleError).toHaveBeenCalledWith(
expect.stringContaining('Internal hook error'),
expect.stringContaining('Handler failed')
);
consoleError.mockRestore();
});
it('should not throw if no handlers are registered', async () => {
const event = createInternalHookEvent('command', 'new', 'test-session');
await expect(triggerInternalHook(event)).resolves.not.toThrow();
});
});
describe('createInternalHookEvent', () => {
it('should create a properly formatted event', () => {
const event = createInternalHookEvent('command', 'new', 'test-session', {
foo: 'bar',
});
expect(event.type).toBe('command');
expect(event.action).toBe('new');
expect(event.sessionKey).toBe('test-session');
expect(event.context).toEqual({ foo: 'bar' });
expect(event.timestamp).toBeInstanceOf(Date);
});
it('should use empty context if not provided', () => {
const event = createInternalHookEvent('command', 'new', 'test-session');
expect(event.context).toEqual({});
});
});
describe('getRegisteredEventKeys', () => {
it('should return all registered event keys', () => {
registerInternalHook('command:new', vi.fn());
registerInternalHook('command:stop', vi.fn());
registerInternalHook('session:start', vi.fn());
const keys = getRegisteredEventKeys();
expect(keys).toContain('command:new');
expect(keys).toContain('command:stop');
expect(keys).toContain('session:start');
});
it('should return empty array when no handlers are registered', () => {
const keys = getRegisteredEventKeys();
expect(keys).toEqual([]);
});
});
describe('clearInternalHooks', () => {
it('should remove all registered handlers', () => {
registerInternalHook('command:new', vi.fn());
registerInternalHook('command:stop', vi.fn());
clearInternalHooks();
const keys = getRegisteredEventKeys();
expect(keys).toEqual([]);
});
});
describe('integration', () => {
it('should handle a complete hook lifecycle', async () => {
const results: InternalHookEvent[] = [];
const handler = vi.fn((event: InternalHookEvent) => {
results.push(event);
});
// Register
registerInternalHook('command:new', handler);
// Trigger
const event1 = createInternalHookEvent('command', 'new', 'session-1');
await triggerInternalHook(event1);
const event2 = createInternalHookEvent('command', 'new', 'session-2');
await triggerInternalHook(event2);
// Verify
expect(results).toHaveLength(2);
expect(results[0].sessionKey).toBe('session-1');
expect(results[1].sessionKey).toBe('session-2');
// Unregister
unregisterInternalHook('command:new', handler);
// Trigger again - should not call handler
const event3 = createInternalHookEvent('command', 'new', 'session-3');
await triggerInternalHook(event3);
expect(results).toHaveLength(2);
});
});
});