| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
| import { |
| clearInternalHooks, |
| createInternalHookEvent, |
| getRegisteredEventKeys, |
| isAgentBootstrapEvent, |
| registerInternalHook, |
| triggerInternalHook, |
| unregisterInternalHook, |
| type AgentBootstrapHookContext, |
| type InternalHookEvent, |
| } from "./internal-hooks.js"; |
|
|
| describe("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("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("isAgentBootstrapEvent", () => { |
| it("returns true for agent:bootstrap events with expected context", () => { |
| const context: AgentBootstrapHookContext = { |
| workspaceDir: "/tmp", |
| bootstrapFiles: [], |
| }; |
| const event = createInternalHookEvent("agent", "bootstrap", "test-session", context); |
| expect(isAgentBootstrapEvent(event)).toBe(true); |
| }); |
|
|
| it("returns false for non-bootstrap events", () => { |
| const event = createInternalHookEvent("command", "new", "test-session"); |
| expect(isAgentBootstrapEvent(event)).toBe(false); |
| }); |
| }); |
|
|
| 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); |
| }); |
|
|
| |
| registerInternalHook("command:new", handler); |
|
|
| |
| const event1 = createInternalHookEvent("command", "new", "session-1"); |
| await triggerInternalHook(event1); |
|
|
| const event2 = createInternalHookEvent("command", "new", "session-2"); |
| await triggerInternalHook(event2); |
|
|
| |
| expect(results).toHaveLength(2); |
| expect(results[0].sessionKey).toBe("session-1"); |
| expect(results[1].sessionKey).toBe("session-2"); |
|
|
| |
| unregisterInternalHook("command:new", handler); |
|
|
| |
| const event3 = createInternalHookEvent("command", "new", "session-3"); |
| await triggerInternalHook(event3); |
|
|
| expect(results).toHaveLength(2); |
| }); |
| }); |
| }); |
|
|