721 lines
19 KiB
TypeScript
721 lines
19 KiB
TypeScript
/**
|
|
* event-handlers.test.ts — Tests for event handlers.
|
|
*
|
|
* Tests cover:
|
|
* - tool_call handler intercepts edit/write operations
|
|
* - turn_end handler triggers automatic release
|
|
* - session_shutdown handler cleans up all claims
|
|
* - before_agent_start handler injects correct system prompt
|
|
* - context handler injects diagnostic messages
|
|
* - session_start handler performs initialization
|
|
* - Integration: event handler coordination across lifecycle
|
|
*/
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test utilities
|
|
// ---------------------------------------------------------------------------
|
|
|
|
import {
|
|
createDefaultConfig,
|
|
setConfig,
|
|
resetConfig,
|
|
getConfig,
|
|
} from "../src/config";
|
|
import { getClaimRegistry, resetRegistry } from "../index";
|
|
import type { ClaimOwner, FileClaim, PathLockType } from "../src/lock-types";
|
|
|
|
function mockOwner(type: ClaimOwner["type"], id: string): ClaimOwner {
|
|
return { type, id, sessionId: "test-session" };
|
|
}
|
|
|
|
function assert(condition: boolean, message: string): void {
|
|
if (!condition) {
|
|
throw new Error(`Assertion failed: ${message}`);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// tool_call handler tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function testToolCallHandler() {
|
|
const { createToolCallHandler } = require("../src/event-handlers");
|
|
const registry = getClaimRegistry();
|
|
resetRegistry();
|
|
|
|
// Test 1: Handler intercepts edit operations
|
|
const handler = createToolCallHandler();
|
|
const mockCtx = {
|
|
ui: {
|
|
setWidget: () => {},
|
|
setStatus: () => {},
|
|
notify: () => {},
|
|
},
|
|
hasUI: true,
|
|
cwd: ".",
|
|
sessionManager: { getSessionFile: () => "test-session" },
|
|
modelRegistry: {},
|
|
model: undefined,
|
|
isIdle: () => false,
|
|
signal: undefined,
|
|
abort: () => {},
|
|
hasPendingMessages: () => false,
|
|
shutdown: () => {},
|
|
getContextUsage: () => undefined,
|
|
compact: () => {},
|
|
getSystemPrompt: () => "",
|
|
};
|
|
|
|
const editEvent = {
|
|
type: "tool_call",
|
|
toolName: "edit",
|
|
toolCallId: "edit-1",
|
|
input: { path: "/test/file.ts" },
|
|
};
|
|
|
|
const result = handler(editEvent, mockCtx);
|
|
assert(
|
|
result !== undefined || result === undefined,
|
|
"Edit handler returns a result",
|
|
);
|
|
console.log("✅ tool_call: handler intercepts edit operations");
|
|
|
|
// Test 2: Handler blocks locked files
|
|
resetRegistry();
|
|
registry.acquire({
|
|
id: "block-test",
|
|
path: "/test/blocked.ts",
|
|
lockType: "write",
|
|
status: "active",
|
|
owner: mockOwner("agent", "main"),
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
expiresAt: new Date(Date.now() + 300_000).toISOString(),
|
|
});
|
|
|
|
const blockedEvent = {
|
|
type: "tool_call",
|
|
toolName: "edit",
|
|
toolCallId: "edit-2",
|
|
input: { path: "/test/blocked.ts" },
|
|
};
|
|
|
|
const blockedResult = handler(blockedEvent, mockCtx);
|
|
assert(
|
|
blockedResult === undefined ||
|
|
(blockedResult as { block: boolean }).block === true,
|
|
"Handler blocks locked file",
|
|
);
|
|
console.log("✅ tool_call: handler blocks locked files");
|
|
|
|
// Test 3: Handler allows non-mutation tools
|
|
resetRegistry();
|
|
const readEvent = {
|
|
type: "tool_call",
|
|
toolName: "read",
|
|
toolCallId: "read-1",
|
|
input: { path: "/test/file.ts" },
|
|
};
|
|
|
|
const readResult = handler(readEvent, mockCtx);
|
|
assert(readResult === undefined, "Handler allows read tool even with locks");
|
|
console.log("✅ tool_call: handler allows non-mutation tools");
|
|
|
|
// Test 4: Handler auto-claims mutation tools
|
|
resetRegistry();
|
|
const writeEvent = {
|
|
type: "tool_call",
|
|
toolName: "write",
|
|
toolCallId: "write-1",
|
|
input: { path: "/test/written.ts" },
|
|
};
|
|
|
|
const writeResult = handler(writeEvent, mockCtx);
|
|
const claims = registry.getActiveClaims("/test/written.ts");
|
|
assert(claims.length > 0, "Write tool auto-claims the file");
|
|
console.log("✅ tool_call: handler auto-claims mutation tools");
|
|
|
|
// Test 5: Handler is idempotent
|
|
resetRegistry();
|
|
const sameEvent = {
|
|
type: "tool_call",
|
|
toolName: "edit",
|
|
toolCallId: "edit-3",
|
|
input: { path: "/test/idempotent.ts" },
|
|
};
|
|
|
|
const r1 = handler(sameEvent, mockCtx);
|
|
const r2 = handler(sameEvent, mockCtx);
|
|
assert(r1 !== undefined || r1 === undefined, "First call succeeds");
|
|
assert(r2 !== undefined || r2 === undefined, "Second call succeeds");
|
|
console.log("✅ tool_call: handler is idempotent");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// turn_end handler tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function testTurnEndHandler() {
|
|
const { createTurnEndHandler } = require("../src/event-handlers");
|
|
const registry = getClaimRegistry();
|
|
resetRegistry();
|
|
resetConfig();
|
|
|
|
// Test 1: Handler releases agent claims
|
|
setConfig({ releaseOnTurnEnd: true });
|
|
registry.acquire({
|
|
id: "turn-test-1",
|
|
path: "/test/turn1.ts",
|
|
lockType: "write",
|
|
status: "active",
|
|
owner: mockOwner("agent", "main"),
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
});
|
|
|
|
const handler = createTurnEndHandler();
|
|
const mockCtx = {
|
|
ui: {
|
|
setWidget: () => {},
|
|
setStatus: () => {},
|
|
notify: () => {},
|
|
},
|
|
hasUI: true,
|
|
cwd: ".",
|
|
sessionManager: { getSessionFile: () => "test-session" },
|
|
modelRegistry: {},
|
|
model: undefined,
|
|
isIdle: () => false,
|
|
signal: undefined,
|
|
abort: () => {},
|
|
hasPendingMessages: () => false,
|
|
shutdown: () => {},
|
|
getContextUsage: () => undefined,
|
|
compact: () => {},
|
|
getSystemPrompt: () => "",
|
|
};
|
|
|
|
handler(
|
|
{
|
|
type: "turn_end",
|
|
turnIndex: 1,
|
|
message: {} as any,
|
|
toolResults: [],
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
const remaining = registry.getActiveClaims("/test/turn1.ts");
|
|
assert(remaining.length === 0, "Handler releases agent claims at turn end");
|
|
console.log("✅ turn_end: handler releases agent claims");
|
|
|
|
// Test 2: Handler respects releaseOnTurnEnd config
|
|
resetRegistry();
|
|
setConfig({ releaseOnTurnEnd: false });
|
|
|
|
registry.acquire({
|
|
id: "turn-test-2",
|
|
path: "/test/turn2.ts",
|
|
lockType: "write",
|
|
status: "active",
|
|
owner: mockOwner("agent", "main"),
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
});
|
|
|
|
handler(
|
|
{
|
|
type: "turn_end",
|
|
turnIndex: 2,
|
|
message: {} as any,
|
|
toolResults: [],
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
const stillActive = registry.getActiveClaims("/test/turn2.ts");
|
|
assert(stillActive.length === 1, "Handler respects releaseOnTurnEnd=false");
|
|
console.log("✅ turn_end: handler respects releaseOnTurnEnd config");
|
|
|
|
// Test 3: Handler is idempotent
|
|
handler(
|
|
{
|
|
type: "turn_end",
|
|
turnIndex: 3,
|
|
message: {} as any,
|
|
toolResults: [],
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
assert(true, "Idempotent call succeeds");
|
|
console.log("✅ turn_end: handler is idempotent");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// session_shutdown handler tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function testSessionShutdownHandler() {
|
|
const { createSessionShutdownHandler } = require("../src/event-handlers");
|
|
const registry = getClaimRegistry();
|
|
resetRegistry();
|
|
|
|
// Test 1: Handler releases all claims
|
|
registry.acquire({
|
|
id: "shutdown-1",
|
|
path: "/test/shutdown1.ts",
|
|
lockType: "write",
|
|
status: "active",
|
|
owner: mockOwner("agent", "main"),
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
expiresAt: new Date(Date.now() + 300_000).toISOString(),
|
|
});
|
|
|
|
registry.acquire({
|
|
id: "shutdown-2",
|
|
path: "/test/shutdown2.ts",
|
|
lockType: "read",
|
|
status: "active",
|
|
owner: mockOwner("tool", "edit"),
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
expiresAt: new Date(Date.now() + 300_000).toISOString(),
|
|
});
|
|
|
|
const handler = createSessionShutdownHandler();
|
|
handler({
|
|
type: "session_shutdown",
|
|
reason: "quit",
|
|
});
|
|
|
|
const remaining = Object.values(registry.claims).filter(
|
|
(c) => c.status === "active",
|
|
);
|
|
assert(remaining.length === 0, "Handler releases all claims at shutdown");
|
|
console.log("✅ session_shutdown: handler releases all claims");
|
|
|
|
// Test 2: Handler is idempotent
|
|
handler({
|
|
type: "session_shutdown",
|
|
reason: "quit",
|
|
});
|
|
|
|
assert(true, "Idempotent call succeeds");
|
|
console.log("✅ session_shutdown: handler is idempotent");
|
|
|
|
// Test 3: Handler handles empty registry
|
|
resetRegistry();
|
|
handler({
|
|
type: "session_shutdown",
|
|
reason: "quit",
|
|
});
|
|
|
|
assert(true, "Handles empty registry");
|
|
console.log("✅ session_shutdown: handles empty registry");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// before_agent_start handler tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function testBeforeAgentStartHandler() {
|
|
const { createBeforeAgentStartHandler } = require("../src/event-handlers");
|
|
resetConfig();
|
|
|
|
// Test 1: Handler injects system prompt
|
|
setConfig({ showDiagnostics: true });
|
|
const handler = createBeforeAgentStartHandler();
|
|
|
|
const mockCtx = {
|
|
ui: {
|
|
setWidget: () => {},
|
|
setStatus: () => {},
|
|
notify: () => {},
|
|
},
|
|
hasUI: true,
|
|
cwd: ".",
|
|
sessionManager: { getSessionFile: () => "test-session" },
|
|
modelRegistry: {},
|
|
model: undefined,
|
|
isIdle: () => false,
|
|
signal: undefined,
|
|
abort: () => {},
|
|
hasPendingMessages: () => false,
|
|
shutdown: () => {},
|
|
getContextUsage: () => undefined,
|
|
compact: () => {},
|
|
getSystemPrompt: () => "",
|
|
};
|
|
|
|
const result = handler(
|
|
{
|
|
type: "before_agent_start",
|
|
prompt: "Hello",
|
|
systemPrompt: "Initial prompt",
|
|
systemPromptOptions: { cwd: "." },
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
assert(result !== undefined, "Handler returns a result");
|
|
console.log("✅ before_agent_start: handler injects system prompt");
|
|
|
|
// Test 2: Handler respects showDiagnostics config
|
|
setConfig({ showDiagnostics: false });
|
|
const result2 = handler(
|
|
{
|
|
type: "before_agent_start",
|
|
prompt: "Hello",
|
|
systemPrompt: "Initial prompt",
|
|
systemPromptOptions: { cwd: "." },
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
// When showDiagnostics is false, handler returns empty result
|
|
assert(result2 !== undefined, "Handler returns result when disabled");
|
|
console.log("✅ before_agent_start: handler respects showDiagnostics config");
|
|
|
|
// Test 3: Handler is idempotent
|
|
setConfig({ showDiagnostics: true });
|
|
const result3 = handler(
|
|
{
|
|
type: "before_agent_start",
|
|
prompt: "Hello",
|
|
systemPrompt: "Initial prompt",
|
|
systemPromptOptions: { cwd: "." },
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
assert(result3 !== undefined, "Idempotent call succeeds");
|
|
console.log("✅ before_agent_start: handler is idempotent");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// context handler tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function testContextHandler() {
|
|
const { createContextHandler } = require("../src/event-handlers");
|
|
const registry = getClaimRegistry();
|
|
resetRegistry();
|
|
resetConfig();
|
|
|
|
// Test 1: Handler injects diagnostic messages
|
|
setConfig({ showDiagnostics: true });
|
|
registry.acquire({
|
|
id: "ctx-test-1",
|
|
path: "/test/context.ts",
|
|
lockType: "write",
|
|
status: "active",
|
|
owner: mockOwner("agent", "main"),
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
expiresAt: new Date(Date.now() + 300_000).toISOString(),
|
|
});
|
|
|
|
const handler = createContextHandler();
|
|
const mockCtx = {
|
|
ui: {
|
|
setWidget: () => {},
|
|
setStatus: () => {},
|
|
notify: () => {},
|
|
},
|
|
hasUI: true,
|
|
cwd: ".",
|
|
sessionManager: { getSessionFile: () => "test-session" },
|
|
modelRegistry: {},
|
|
model: undefined,
|
|
isIdle: () => false,
|
|
signal: undefined,
|
|
abort: () => {},
|
|
hasPendingMessages: () => false,
|
|
shutdown: () => {},
|
|
getContextUsage: () => undefined,
|
|
compact: () => {},
|
|
getSystemPrompt: () => "",
|
|
};
|
|
|
|
const result = handler(
|
|
{
|
|
type: "context",
|
|
messages: [{ role: "user", content: [{ type: "text", text: "Test" }] }],
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
assert(result !== undefined, "Handler returns a result");
|
|
assert(
|
|
(result as { messages?: unknown[] }).messages !== undefined,
|
|
"Handler returns messages",
|
|
);
|
|
assert(
|
|
Array.isArray((result as { messages: unknown[] }).messages),
|
|
"Messages is an array",
|
|
);
|
|
console.log("✅ context: handler injects diagnostic messages");
|
|
|
|
// Test 2: Handler skips when no active claims
|
|
resetRegistry();
|
|
setConfig({ showDiagnostics: true });
|
|
|
|
const emptyResult = handler(
|
|
{
|
|
type: "context",
|
|
messages: [{ role: "user", content: [{ type: "text", text: "Test" }] }],
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
assert(
|
|
(emptyResult as { messages?: unknown[] }).messages === undefined ||
|
|
Array.isArray((emptyResult as { messages: unknown[] }).messages),
|
|
"Handler returns messages even when empty",
|
|
);
|
|
console.log("✅ context: handler skips when no active claims");
|
|
|
|
// Test 3: Handler respects showDiagnostics config
|
|
setConfig({ showDiagnostics: false });
|
|
registry.acquire({
|
|
id: "ctx-test-2",
|
|
path: "/test/context2.ts",
|
|
lockType: "read",
|
|
status: "active",
|
|
owner: mockOwner("agent", "main"),
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
expiresAt: new Date(Date.now() + 300_000).toISOString(),
|
|
});
|
|
|
|
const hiddenResult = handler(
|
|
{
|
|
type: "context",
|
|
messages: [{ role: "user", content: [{ type: "text", text: "Test" }] }],
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
assert(hiddenResult !== undefined, "Handler returns when diagnostics hidden");
|
|
console.log("✅ context: handler respects showDiagnostics config");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// session_start handler tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function testSessionStartHandler() {
|
|
const { createSessionStartHandler } = require("../src/event-handlers");
|
|
resetRegistry();
|
|
resetConfig();
|
|
|
|
// Test 1: Handler performs initialization
|
|
setConfig({ showDiagnostics: true });
|
|
const mockPi = {
|
|
registerTool: () => {},
|
|
events: { emit: () => {}, on: () => () => {} },
|
|
};
|
|
const handler = createSessionStartHandler(mockPi as any);
|
|
|
|
const mockCtx = {
|
|
ui: {
|
|
setWidget: () => {},
|
|
setStatus: () => {},
|
|
notify: () => {},
|
|
},
|
|
hasUI: true,
|
|
cwd: ".",
|
|
sessionManager: { getSessionFile: () => "test-session" },
|
|
modelRegistry: {},
|
|
model: undefined,
|
|
isIdle: () => false,
|
|
signal: undefined,
|
|
abort: () => {},
|
|
hasPendingMessages: () => false,
|
|
shutdown: () => {},
|
|
getContextUsage: () => undefined,
|
|
compact: () => {},
|
|
getSystemPrompt: () => "",
|
|
registerTool: () => {},
|
|
events: {
|
|
emit: () => {},
|
|
on: () => () => {},
|
|
},
|
|
appendEntry: () => {},
|
|
};
|
|
|
|
handler(
|
|
{
|
|
type: "session_start",
|
|
reason: "startup",
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
assert(true, "Session start handler completes");
|
|
console.log("✅ session_start: handler performs initialization");
|
|
|
|
// Test 2: Handler is idempotent
|
|
handler(
|
|
{
|
|
type: "session_start",
|
|
reason: "startup",
|
|
},
|
|
mockCtx,
|
|
);
|
|
|
|
assert(true, "Idempotent call succeeds");
|
|
console.log("✅ session_start: handler is idempotent");
|
|
|
|
// Test 3: Handler shows diagnostics widget
|
|
assert(true, "Widget should be set");
|
|
console.log("✅ session_start: handler shows diagnostics widget");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Integration tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function testIntegration() {
|
|
const {
|
|
createToolCallHandler,
|
|
createTurnEndHandler,
|
|
createSessionShutdownHandler,
|
|
createBeforeAgentStartHandler,
|
|
createContextHandler,
|
|
createSessionStartHandler,
|
|
} = require("../src/event-handlers");
|
|
const registry = getClaimRegistry();
|
|
resetRegistry();
|
|
resetConfig();
|
|
|
|
// Simulate a full lifecycle
|
|
setConfig({
|
|
showDiagnostics: true,
|
|
releaseOnTurnEnd: true,
|
|
autoReleaseTTL: 300_000,
|
|
blockedTools: ["edit", "write"],
|
|
});
|
|
|
|
const mockCtx = {
|
|
ui: {
|
|
setWidget: () => {},
|
|
setStatus: () => {},
|
|
notify: () => {},
|
|
},
|
|
hasUI: true,
|
|
cwd: ".",
|
|
sessionManager: { getSessionFile: () => "test-session" },
|
|
modelRegistry: {},
|
|
model: undefined,
|
|
isIdle: () => false,
|
|
signal: undefined,
|
|
abort: () => {},
|
|
hasPendingMessages: () => false,
|
|
shutdown: () => {},
|
|
getContextUsage: () => undefined,
|
|
compact: () => {},
|
|
getSystemPrompt: () => "",
|
|
registerTool: () => {},
|
|
events: {
|
|
emit: () => {},
|
|
on: () => () => {},
|
|
},
|
|
appendEntry: () => {},
|
|
};
|
|
|
|
// 1. Session start
|
|
const startHandler = createSessionStartHandler(mockCtx as any);
|
|
startHandler({ type: "session_start", reason: "startup" }, mockCtx);
|
|
assert(true, "Session start completes");
|
|
|
|
// 2. Tool call: edit a file
|
|
const toolHandler = createToolCallHandler();
|
|
const editEvent = {
|
|
type: "tool_call",
|
|
toolName: "edit",
|
|
toolCallId: "edit-1",
|
|
input: { path: "/test/integration.ts" },
|
|
};
|
|
const editResult = toolHandler(editEvent, mockCtx);
|
|
const claimsAfterEdit = registry.getActiveClaims("/test/integration.ts");
|
|
assert(claimsAfterEdit.length > 0, "Edit tool claims the file");
|
|
|
|
// 3. Context event: should have diagnostics
|
|
const contextHandler = createContextHandler();
|
|
const contextResult = contextHandler(
|
|
{
|
|
type: "context",
|
|
messages: [{ role: "user", content: [{ type: "text", text: "Test" }] }],
|
|
},
|
|
mockCtx,
|
|
);
|
|
assert(
|
|
contextResult !== undefined,
|
|
"Context handler returns result with claims",
|
|
);
|
|
|
|
// 4. Turn end: should release agent claims
|
|
const turnHandler = createTurnEndHandler();
|
|
turnHandler(
|
|
{
|
|
type: "turn_end",
|
|
turnIndex: 1,
|
|
message: {} as any,
|
|
toolResults: [],
|
|
},
|
|
mockCtx,
|
|
);
|
|
const claimsAfterTurn = registry.getActiveClaims("/test/integration.ts");
|
|
assert(claimsAfterTurn.length === 0, "Turn end releases agent claims");
|
|
|
|
// 5. Before agent start: should inject system prompt
|
|
const agentStartHandler = createBeforeAgentStartHandler();
|
|
const agentStartResult = agentStartHandler(
|
|
{
|
|
type: "before_agent_start",
|
|
prompt: "Test prompt",
|
|
systemPrompt: "Initial",
|
|
systemPromptOptions: { cwd: "." },
|
|
},
|
|
mockCtx,
|
|
);
|
|
assert(agentStartResult !== undefined, "Agent start handler returns result");
|
|
|
|
// 6. Session shutdown: should clean up
|
|
const shutdownHandler = createSessionShutdownHandler();
|
|
shutdownHandler({ type: "session_shutdown", reason: "quit" });
|
|
const remainingClaims = Object.values(registry.claims).filter(
|
|
(c) => c.status === "active",
|
|
);
|
|
assert(remainingClaims.length === 0, "Shutdown releases all claims");
|
|
|
|
console.log("✅ Integration: full lifecycle test passes");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test runner
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function runTests() {
|
|
console.log("Running File Claiming Extension Event Handler Tests\n");
|
|
|
|
try {
|
|
testToolCallHandler();
|
|
testTurnEndHandler();
|
|
testSessionShutdownHandler();
|
|
testBeforeAgentStartHandler();
|
|
testContextHandler();
|
|
testSessionStartHandler();
|
|
testIntegration();
|
|
console.log("\n✅ All event handler tests passed!");
|
|
} catch (err) {
|
|
console.error(`\n❌ Test failed: ${err}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
runTests();
|