Files
pi-file-claiming/tests/index.test.ts
2026-06-19 12:46:02 -04:00

1000 lines
29 KiB
TypeScript

/**
* index.test.ts — Tests for the File Claiming extension LLM integration.
*
* Tests cover:
* - System prompt injection
* - Diagnostic message formatting and delivery
* - Tool registration
* - Notification system for various lock events
* - User interaction components
*/
// ---------------------------------------------------------------------------
// 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" };
}
// ---------------------------------------------------------------------------
// System prompt injection tests
// ---------------------------------------------------------------------------
function testSystemPromptInjection() {
const {
injectLockClaimingIntoPrompt,
buildLockClaimingInstructions,
buildLockClaimingGuidelines,
buildLockClaimingToolSnippets,
} = require("../src/system-prompt");
// Test 1: Instructions are injected
const instructions = buildLockClaimingInstructions();
assert(
instructions.includes("<file_claiming>"),
"Instructions include file_claiming tags",
);
assert(
instructions.includes("Lock Claiming Protocol"),
"Instructions include header",
);
assert(
instructions.includes("Claim Types"),
"Instructions include claim types",
);
assert(
instructions.includes("Auto-Release Behavior"),
"Instructions include auto-release section",
);
assert(
instructions.includes("Conflict Resolution"),
"Instructions include conflict resolution",
);
assert(
instructions.includes("Best Practices"),
"Instructions include best practices",
);
assert(
instructions.includes("Releasing Claims"),
"Instructions include releasing claims",
);
console.log("✅ System prompt injection: instructions generated correctly");
// Test 2: Guidelines are generated
const guidelines = buildLockClaimingGuidelines();
assert(Array.isArray(guidelines), "Guidelines is an array");
assert(guidelines.length > 0, "Guidelines has entries");
assert(
guidelines.some((g: string) => g.includes("file_claiming_claim")),
"Guidelines mentions claim tool",
);
assert(
guidelines.some((g: string) => g.includes("file_claiming_release")),
"Guidelines mentions release tool",
);
assert(
guidelines.some((g: string) => g.includes("file_claiming_list")),
"Guidelines mentions list tool",
);
assert(
guidelines.some((g: string) => g.includes("file_claiming_check")),
"Guidelines mentions check tool",
);
console.log("✅ System prompt injection: guidelines generated correctly");
// Test 3: Tool snippets are generated
const snippets = buildLockClaimingToolSnippets();
assert(snippets.file_claiming_claim, "Snippet for claim tool");
assert(snippets.file_claiming_release, "Snippet for release tool");
assert(snippets.file_claiming_list, "Snippet for list tool");
assert(snippets.file_claiming_check, "Snippet for check tool");
console.log("✅ System prompt injection: tool snippets generated correctly");
// Test 4: Injection into prompt options
const options = injectLockClaimingIntoPrompt({ cwd: "." });
assert(options.promptGuidelines, "Injected options have guidelines");
assert(options.toolSnippets, "Injected options have tool snippets");
assert(
options.appendSystemPrompt,
"Injected options have appendSystemPrompt",
);
assert(
options.appendSystemPrompt!.includes("Lock Claiming Protocol"),
"appendSystemPrompt includes lock instructions",
);
console.log(
"✅ System prompt injection: injection into options works correctly",
);
// Test 5: Config values are substituted
const config = getConfig();
assert(
instructions.includes(String(config.autoReleaseTTL)),
"TTL value is in instructions",
);
console.log("✅ System prompt injection: config values substituted");
}
// ---------------------------------------------------------------------------
// Diagnostic message tests
// ---------------------------------------------------------------------------
function testDiagnosticMessages() {
const {
claimToDiagnostic,
conflictToDiagnostic,
buildDiagnosticCollection,
formatDiagnostics,
getDiagnosticsWidgetContent,
formatRelativeTime,
hasActiveClaim,
getClaimsForPath,
getLockedFiles,
} = require("../src/diagnostics");
const registry = getClaimRegistry();
resetRegistry();
// Test 1: Claim to diagnostic
const testClaim: FileClaim = {
id: "test-1",
path: "/test/file.ts",
lockType: "write",
status: "active",
owner: mockOwner("agent", "main"),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
reason: "Editing file",
};
const diag = claimToDiagnostic(testClaim, registry);
assert(diag.uri === "/test/file.ts", "Diagnostic URI matches claim path");
assert(diag.severity === "warning", "Write lock has warning severity");
assert(diag.source === "file-claiming", "Diagnostic source is file-claiming");
assert(diag.code === "LOCK_WRITE", "Diagnostic code is correct");
assert(
diag.message.includes("test/file.ts"),
"Diagnostic message includes path",
);
assert(diag.tool === undefined, "Agent type has no tool field");
console.log("✅ Diagnostic messages: claim to diagnostic conversion works");
// Test 2: Conflict to diagnostic
const conflictDiag = conflictToDiagnostic("/test/file.ts", "read", [
{
path: "/test/file.ts",
lockType: "write",
claimId: "test-1",
owner: mockOwner("tool", "edit"),
acquiredAt: new Date().toISOString(),
},
]);
assert(conflictDiag.severity === "error", "Conflict has error severity");
assert(
conflictDiag.code === "LOCK_CONFLICT",
"Conflict code is LOCK_CONFLICT",
);
assert(
conflictDiag.message.includes("blocked"),
"Conflict message mentions blockers",
);
console.log(
"✅ Diagnostic messages: conflict to diagnostic conversion works",
);
// Test 3: Diagnostic collection
registry.acquire({
...testClaim,
id: "test-2",
path: "/test/file.ts",
lockType: "read",
});
registry.acquire({
...testClaim,
id: "test-3",
path: "/test/other.ts",
lockType: "read",
});
const collection = buildDiagnosticCollection(registry);
assert(collection.count > 0, "Collection has diagnostics");
assert(collection.diagnostics.size > 0, "Collection has entries");
assert(collection.bySeverity.info >= 0, "Info count is valid");
assert(collection.bySeverity.warning >= 0, "Warning count is valid");
assert(collection.bySeverity.error >= 0, "Error count is valid");
console.log("✅ Diagnostic messages: collection building works");
// Test 4: Formatting
const formatted = formatDiagnostics(collection);
assert(formatted.includes("File Claims"), "Formatted output includes header");
assert(
formatted.includes(collection.count.toString()),
"Formatted output includes count",
);
console.log("✅ Diagnostic messages: formatting works");
// Test 5: Widget content
const widgetContent = getDiagnosticsWidgetContent(registry);
assert(Array.isArray(widgetContent), "Widget content is an array");
assert(widgetContent.length > 0, "Widget content has entries");
assert(widgetContent[0].includes("Claims"), "Widget content mentions claims");
console.log("✅ Diagnostic messages: widget content generation works");
// Test 6: Relative time formatting
const now = new Date();
const future = new Date(now.getTime() + 60_000).toISOString();
assert(
formatRelativeTime(future).includes("1m"),
"Relative time shows minutes",
);
const nearFuture = new Date(now.getTime() + 30_000).toISOString();
assert(
formatRelativeTime(nearFuture).includes("30s"),
"Relative time shows seconds",
);
console.log("✅ Diagnostic messages: relative time formatting works");
// Test 7: Helper functions
assert(
hasActiveClaim(registry, "/test/file.ts"),
"hasActiveClaim returns true for claimed file",
);
assert(
!hasActiveClaim(registry, "/test/missing.ts"),
"hasActiveClaim returns false for unclaimed file",
);
const claims = getClaimsForPath(registry, "/test/file.ts");
assert(claims.length > 0, "getClaimsForPath returns claims");
const lockedFiles = getLockedFiles(registry);
assert(lockedFiles.length > 0, "getLockedFiles returns locked files");
console.log("✅ Diagnostic messages: helper functions work");
}
// ---------------------------------------------------------------------------
// Tool registration tests
// ---------------------------------------------------------------------------
function testToolRegistration() {
const {
registerLockTools,
fileClaimingClaimTool,
fileClaimingReleaseTool,
fileClaimingListTool,
fileClaimingCheckTool,
} = require("../src/tools");
// Test 1: Tools are defined
assert(
fileClaimingClaimTool.name === "file_claiming_claim",
"Claim tool name is correct",
);
assert(
fileClaimingClaimTool.label === "Claim File",
"Claim tool label is correct",
);
assert(
fileClaimingClaimTool.description.length > 0,
"Claim tool has description",
);
assert(fileClaimingClaimTool.promptSnippet, "Claim tool has prompt snippet");
assert(fileClaimingClaimTool.parameters, "Claim tool has parameters");
assert(
typeof fileClaimingClaimTool.execute === "function",
"Claim tool has execute function",
);
console.log("✅ Tool registration: claim tool is defined correctly");
assert(
fileClaimingReleaseTool.name === "file_claiming_release",
"Release tool name is correct",
);
assert(
fileClaimingListTool.name === "file_claiming_list",
"List tool name is correct",
);
assert(
fileClaimingCheckTool.name === "file_claiming_check",
"Check tool name is correct",
);
console.log("✅ Tool registration: all tool names are correct");
// Test 2: Tool descriptions are actionable
assert(
fileClaimingClaimTool.description.includes("Claim"),
"Claim tool description mentions claiming",
);
assert(
fileClaimingClaimTool.description.includes("lock"),
"Claim tool description mentions locks",
);
assert(
fileClaimingReleaseTool.description.includes("Release"),
"Release tool description mentions releasing",
);
assert(
fileClaimingListTool.description.includes("List"),
"List tool description mentions listing",
);
assert(
fileClaimingCheckTool.description.includes("Check"),
"Check tool description mentions checking",
);
console.log("✅ Tool registration: tool descriptions are actionable");
// Test 3: Prompt guidelines are clear
assert(
fileClaimingClaimTool.promptSnippet.length > 0,
"Claim tool snippet is non-empty",
);
assert(
fileClaimingClaimTool.promptSnippet.length < 80,
"Claim tool snippet is concise",
);
console.log("✅ Tool registration: prompt guidelines are clear");
}
// ---------------------------------------------------------------------------
// Notification system tests
// ---------------------------------------------------------------------------
function testNotificationSystem() {
const {
claimEventToNotification,
formatNotification,
formatNotificationsSummary,
} = require("../src/notifications");
const { createDiagnosticEvent } = require("../src/diagnostics");
// Test 1: Claim acquired notification
const acquiredEvent = {
type: "claim:acquired",
claim: {
id: "test-1",
path: "/test/file.ts",
lockType: "write",
status: "active",
owner: mockOwner("agent", "main"),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
conflict: undefined,
timestamp: new Date().toISOString(),
};
const acquiredNotif = claimEventToNotification(acquiredEvent as any);
assert(
acquiredNotif.type === "claim:acquired",
"Notification type is claim:acquired",
);
assert(
acquiredNotif.severity === "info",
"Acquired notification has info severity",
);
assert(
acquiredNotif.title.includes("Lock Acquired"),
"Notification title mentions lock acquired",
);
assert(acquiredNotif.claim, "Notification includes claim data");
console.log("✅ Notification system: claim acquired notification works");
// Test 2: Claim released notification
const releasedEvent = {
type: "claim:released",
claim: acquiredEvent.claim,
conflict: undefined,
timestamp: new Date().toISOString(),
};
const releasedNotif = claimEventToNotification(releasedEvent as any);
assert(
releasedNotif.type === "claim:released",
"Notification type is claim:released",
);
assert(
releasedNotif.title === "Lock Released",
"Notification title is Lock Released",
);
console.log("✅ Notification system: claim released notification works");
// Test 3: Claim conflicted notification
const conflictedEvent = {
type: "claim:conflicted",
claim: acquiredEvent.claim,
conflict: {
path: "/test/file.ts",
severity: "warning",
blockedClaim: acquiredEvent.claim,
blockingClaims: [],
message: "Cannot acquire lock",
},
timestamp: new Date().toISOString(),
};
const conflictedNotif = claimEventToNotification(conflictedEvent as any);
assert(
conflictedNotif.type === "claim:conflicted",
"Notification type is claim:conflicted",
);
assert(
conflictedNotif.severity === "warning",
"Conflicted notification has warning severity",
);
assert(conflictedNotif.conflict, "Notification includes conflict data");
console.log("✅ Notification system: claim conflicted notification works");
// Test 4: Claim expired notification
const expiredEvent = {
type: "claim:expired",
claim: acquiredEvent.claim,
conflict: undefined,
timestamp: new Date().toISOString(),
};
const expiredNotif = claimEventToNotification(expiredEvent as any);
assert(
expiredNotif.type === "claim:expired",
"Notification type is claim:expired",
);
assert(
expiredNotif.title === "Lock Expired",
"Notification title is Lock Expired",
);
console.log("✅ Notification system: claim expired notification works");
// Test 5: Notification formatting
const formatted = formatNotification(acquiredNotif);
assert(
formatted.includes(acquiredNotif.title),
"Formatted notification includes title",
);
assert(
formatted.includes(acquiredNotif.message),
"Formatted notification includes message",
);
console.log("✅ Notification system: notification formatting works");
// Test 6: Summary formatting
const notifications = [
acquiredNotif,
releasedNotif,
conflictedNotif,
expiredNotif,
];
const summary = formatNotificationsSummary(notifications);
assert(summary.includes("Lock Notifications"), "Summary includes header");
assert(summary.includes("4"), "Summary includes count");
console.log("✅ Notification system: summary formatting works");
// Test 7: Diagnostic events
const diagEvent = createDiagnosticEvent("diagnostic:added", "/test/file.ts", {
uri: "/test/file.ts",
severity: "info",
source: "file-claiming",
code: "LOCK_READ",
message: "Read lock on file",
timestamp: new Date().toISOString(),
});
assert(
diagEvent.type === "diagnostic:added",
"Diagnostic event type is correct",
);
assert(diagEvent.uri === "/test/file.ts", "Diagnostic event URI is correct");
console.log("✅ Notification system: diagnostic events work");
}
// ---------------------------------------------------------------------------
// User interaction component tests
// ---------------------------------------------------------------------------
function testUserInteractionComponents() {
const {
createLockStatusWidget,
updateLockStatus,
persistLockState,
restoreLockState,
} = require("../src/user-interaction");
// Test 1: Lock status widget
const registry = getClaimRegistry();
resetRegistry();
registry.acquire({
id: "widget-test",
path: "/test/widget.ts",
lockType: "write",
status: "active",
owner: mockOwner("agent", "main"),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
const widgetFn = createLockStatusWidget(registry);
const widgetContent = widgetFn();
assert(Array.isArray(widgetContent), "Widget returns array");
assert(widgetContent.length > 0, "Widget has content");
assert(
widgetContent.some((line: string) => line.includes("Claims")),
"Widget mentions claims",
);
console.log("✅ User interaction: lock status widget works");
// Test 2: Status bar update
const mockUI = {
setStatus: (key: string, text: string | undefined) => {},
};
updateLockStatus(mockUI as any, registry);
console.log("✅ User interaction: status bar update works");
// Test 3: State persistence
const mockPi = {
appendEntry: (type: string, data: unknown) => {},
getSessionName: () => "test-session",
};
persistLockState(mockPi as any);
console.log("✅ User interaction: state persistence works");
// Test 4: State restoration
const restored = restoreLockState(mockPi as any);
assert(typeof restored === "boolean", "Restore returns boolean");
console.log("✅ User interaction: state restoration works");
}
// ---------------------------------------------------------------------------
// Integration test: full flow
// ---------------------------------------------------------------------------
function testFullIntegration() {
const registry = getClaimRegistry();
resetRegistry();
resetConfig();
// Set up config
setConfig({ showDiagnostics: true, autoReleaseTTL: 5000 });
// Create a claim
const owner = mockOwner("agent", "main");
registry.acquire({
id: "integration-1",
path: "/test/integration.ts",
lockType: "write",
status: "active",
owner,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
// Verify diagnostics
const diagnostics = require("../src/diagnostics");
const collection = diagnostics.buildDiagnosticCollection(registry);
assert(collection.count > 0, "Integration: diagnostics have entries");
// Verify notifications
const notifications = require("../src/notifications");
const notif = notifications.claimEventToNotification({
type: "claim:acquired",
claim: registry.claims["integration-1"],
timestamp: new Date().toISOString(),
});
assert(
notif.severity === "info",
"Integration: notification has correct severity",
);
// Verify system prompt injection
const systemPrompt = require("../src/system-prompt");
const options = systemPrompt.injectLockClaimingIntoPrompt({ cwd: "." });
assert(
options.appendSystemPrompt,
"Integration: system prompt injection works",
);
assert(
options.appendSystemPrompt!.includes("Lock Claiming Protocol"),
"Integration: lock instructions present",
);
// Verify tool registration
const tools = require("../src/tools");
assert(
tools.fileClaimingClaimTool.name === "file_claiming_claim",
"Integration: tools are defined",
);
// Clean up
registry.release("integration-1");
console.log("✅ Full integration test: complete flow works");
}
// ---------------------------------------------------------------------------
// Lock acquisition tests
// ---------------------------------------------------------------------------
function testLockAcquisition() {
const {
acquireLock,
autoClaim,
isFileLocked,
getLockInfo,
} = require("../src/lock-acquisition");
const registry = getClaimRegistry();
const owner = mockOwner("agent", "main");
// Test 1: Lock acquisition succeeds for unclaimed files
resetRegistry();
const acqResult = acquireLock({
path: "/test/acquire.ts",
lockType: "write",
owner,
autoReleaseTTL: 5000,
});
assert(acqResult.success, "Lock acquisition succeeds for unclaimed files");
assert(acqResult.claim, "Lock acquisition returns a claim");
assert(acqResult.claim!.path === "/test/acquire.ts", "Claim path matches");
assert(acqResult.claim!.lockType === "write", "Claim lock type matches");
assert(acqResult.autoClaimed, "Auto-claimed flag is set");
assert(
acqResult.message.includes("Auto-claimed"),
"Message mentions auto-claim",
);
console.log("✅ Lock acquisition: unclaimed file acquisition works");
// Test 2: Auto-claim logic triggers correctly
resetRegistry();
const autoResult = autoClaim({
path: "/test/auto.ts",
lockType: "write",
owner,
autoReleaseTTL: 3000,
});
assert(autoResult.success, "Auto-claim succeeds");
assert(autoResult.autoClaimed, "Auto-claim sets autoClaimed flag");
assert(
autoResult.claim!.owner.type === "agent",
"Auto-claim uses correct owner",
);
assert(
autoResult.claim!.owner.id === "main",
"Auto-claim uses correct owner id",
);
console.log("✅ Lock acquisition: auto-claim logic triggers correctly");
// Test 3: Blocking mechanism prevents access to locked files
resetRegistry();
registry.acquire({
id: "block-test",
path: "/test/blocked.ts",
lockType: "write",
status: "active",
owner,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
const blocked = isFileLocked("/test/blocked.ts", "write");
assert(blocked, "isFileLocked returns true for write-locked file");
const unblocked = isFileLocked("/test/blocked.ts", "read");
// Read should be blocked when write lock exists
assert(unblocked, "isFileLocked returns true for read on write-locked file");
const free = isFileLocked("/test/fresh.ts", "write");
assert(!free, "isFileLocked returns false for unclaimed file");
console.log(
"✅ Lock acquisition: blocking mechanism prevents access to locked files",
);
// Test 4: Lock info contains detailed information
resetRegistry();
registry.acquire({
id: "info-test",
path: "/test/info.ts",
lockType: "write",
status: "active",
owner,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
const info = getLockInfo("/test/info.ts");
assert(info.locked, "Lock info shows locked");
assert(info.path === "/test/info.ts", "Lock info has correct path");
assert(info.claims.length > 0, "Lock info has claims");
assert(info.locks.length > 0, "Lock info has locks");
assert(info.primaryLock, "Lock info has primary lock");
assert(info.primaryClaim, "Lock info has primary claim");
assert(info.autoReleaseAt, "Lock info has auto-release time");
assert(info.autoReleaseIn, "Lock info has auto-release in");
console.log("✅ Lock acquisition: lock info contains detailed information");
// Test 5: Concurrent access is handled
resetRegistry();
const owner1 = mockOwner("agent", "main");
const owner2 = mockOwner("agent", "other");
registry.acquire({
id: "concurrent-1",
path: "/test/concurrent.ts",
lockType: "write",
status: "active",
owner: owner1,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
const acq2 = acquireLock({
path: "/test/concurrent.ts",
lockType: "write",
owner: owner2,
autoReleaseTTL: 5000,
});
// owner2 should get a conflict since owner1 has write lock
assert(!acq2.success || acq2.conflict, "Concurrent access returns conflict");
console.log("✅ Lock acquisition: concurrent access is handled");
}
// ---------------------------------------------------------------------------
// Event handler tests
// ---------------------------------------------------------------------------
function testEventHandlers() {
const {
createToolCallHandler,
createTurnEndHandler,
createSessionShutdownHandler,
createBeforeAgentStartHandler,
createContextHandler,
createSessionStartHandler,
} = require("../src/event-handlers");
const registry = getClaimRegistry();
// Test 1: tool_call handler intercepts edit/write operations
resetRegistry();
const toolHandler = 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 = toolHandler(editEvent, mockCtx);
assert(
result !== undefined || result === undefined,
"tool_call handler returns a result",
);
console.log(
"✅ Event handlers: tool_call handler intercepts edit/write operations",
);
// Test 2: turn_end handler triggers automatic release
resetRegistry();
setConfig({ releaseOnTurnEnd: true });
registry.acquire({
id: "turn-test",
path: "/test/turn.ts",
lockType: "write",
status: "active",
owner: mockOwner("agent", "main"),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
const turnHandler = createTurnEndHandler();
turnHandler(
{
type: "turn_end",
turnIndex: 1,
message: {} as any,
toolResults: [],
},
mockCtx,
);
const remaining = registry.getActiveClaims("/test/turn.ts");
assert(remaining.length === 0, "turn_end handler releases agent claims");
console.log("✅ Event handlers: turn_end handler triggers automatic release");
// Test 3: session_shutdown handler cleans up all claims
resetRegistry();
registry.acquire({
id: "shutdown-1",
path: "/test/shutdown.ts",
lockType: "write",
status: "active",
owner: mockOwner("agent", "main"),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
const shutdownHandler = createSessionShutdownHandler();
shutdownHandler({ type: "session_shutdown", reason: "quit" });
const afterShutdown = Object.values(registry.claims).filter(
(c) => c.status === "active",
);
assert(
afterShutdown.length === 0,
"session_shutdown handler cleans up all claims",
);
console.log(
"✅ Event handlers: session_shutdown handler cleans up all claims",
);
// Test 4: before_agent_start handler injects correct system prompt
resetConfig();
setConfig({ showDiagnostics: true });
const agentStartHandler = createBeforeAgentStartHandler();
const agentStartResult = agentStartHandler(
{
type: "before_agent_start",
prompt: "Test",
systemPrompt: "Initial",
systemPromptOptions: { cwd: "." },
},
mockCtx,
);
assert(
agentStartResult !== undefined,
"before_agent_start handler returns a result",
);
console.log(
"✅ Event handlers: before_agent_start handler injects correct system prompt",
);
// Test 5: context handler injects diagnostic messages
resetRegistry();
setConfig({ showDiagnostics: true });
registry.acquire({
id: "ctx-test",
path: "/test/context.ts",
lockType: "write",
status: "active",
owner: mockOwner("agent", "main"),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
const contextHandler = createContextHandler();
const contextResult = contextHandler(
{
type: "context",
messages: [{ role: "user", content: [{ type: "text", text: "Test" }] }],
},
mockCtx,
);
assert(
contextResult !== undefined,
"context handler injects diagnostic messages",
);
console.log("✅ Event handlers: context handler injects diagnostic messages");
// Test 6: session_start handler performs initialization
resetRegistry();
setConfig({ showDiagnostics: true });
const mockPi = {
registerTool: () => {},
events: { emit: () => {}, on: () => () => {} },
};
const sessionStartHandler = createSessionStartHandler(mockPi as any);
sessionStartHandler({ type: "session_start", reason: "startup" }, mockCtx);
assert(true, "session_start handler performs initialization");
console.log(
"✅ Event handlers: session_start handler performs initialization",
);
// Test 7: Integration - event handler coordination across lifecycle
resetRegistry();
setConfig({
showDiagnostics: true,
releaseOnTurnEnd: true,
autoReleaseTTL: 300_000,
blockedTools: ["edit", "write"],
});
// Session start
sessionStartHandler({ type: "session_start", reason: "startup" }, mockCtx);
// Tool call: edit a file
const editEvent2 = {
type: "tool_call",
toolName: "edit",
toolCallId: "edit-2",
input: { path: "/test/integration.ts" },
};
toolHandler(editEvent2, mockCtx);
const claimsAfterEdit = registry.getActiveClaims("/test/integration.ts");
assert(claimsAfterEdit.length > 0, "Integration: edit tool claims the file");
// Turn end: release agent claims
turnHandler(
{
type: "turn_end",
turnIndex: 1,
message: {} as any,
toolResults: [],
},
mockCtx,
);
const claimsAfterTurn = registry.getActiveClaims("/test/integration.ts");
assert(
claimsAfterTurn.length === 0,
"Integration: turn end releases agent claims",
);
// Session shutdown: clean up
shutdownHandler({ type: "session_shutdown", reason: "quit" });
const finalClaims = Object.values(registry.claims).filter(
(c) => c.status === "active",
);
assert(finalClaims.length === 0, "Integration: shutdown releases all claims");
console.log("✅ Event handlers: integration test passes");
}
// ---------------------------------------------------------------------------
// Test runner
// ---------------------------------------------------------------------------
function assert(condition: boolean, message: string): void {
if (!condition) {
throw new Error(`Assertion failed: ${message}`);
}
}
function runTests() {
console.log("Running File Claiming Extension LLM Integration Tests\n");
try {
testSystemPromptInjection();
testDiagnosticMessages();
testToolRegistration();
testNotificationSystem();
testUserInteractionComponents();
testFullIntegration();
testLockAcquisition();
testEventHandlers();
console.log("\n✅ All tests passed!");
} catch (err) {
console.error(`\n❌ Test failed: ${err}`);
process.exit(1);
}
}
runTests();