Initial commit
This commit is contained in:
270
tests/test-utils.ts
Normal file
270
tests/test-utils.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* test-utils.ts — Shared test utilities and mocks for Pi session simulation.
|
||||
*
|
||||
* Provides:
|
||||
* - Mock owners and claims for test scenarios
|
||||
* - Mock Pi session simulation
|
||||
* - Temporary directory creation for integration tests
|
||||
* - Assertion helpers
|
||||
*
|
||||
* @module file-claiming/test-utils
|
||||
*/
|
||||
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
||||
import type { ClaimOwner, FileClaim } from "../src/lock-types";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Default test session ID. */
|
||||
export const TEST_SESSION_ID = "test-session-001";
|
||||
|
||||
/** Default owner for test claims. */
|
||||
export const TEST_OWNER: ClaimOwner = {
|
||||
type: "agent",
|
||||
id: "test-agent",
|
||||
sessionId: TEST_SESSION_ID,
|
||||
};
|
||||
|
||||
/** Alternative session IDs for multi-session tests. */
|
||||
export const SESSION_A = "session-alpha";
|
||||
export const SESSION_B = "session-beta";
|
||||
export const SESSION_C = "session-gamma";
|
||||
|
||||
/** Common test file paths. */
|
||||
export const TEST_FILE_A = "/tmp/test-claiming/file-a.ts";
|
||||
export const TEST_FILE_B = "/tmp/test-claiming/file-b.ts";
|
||||
export const TEST_FILE_C = "/tmp/test-claiming/file-c.ts";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Factory helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a mock ClaimOwner for use in tests.
|
||||
*/
|
||||
export function mockOwner(
|
||||
type: ClaimOwner["type"] = "agent",
|
||||
id: string = "test-agent",
|
||||
sessionId: string = TEST_SESSION_ID,
|
||||
): ClaimOwner {
|
||||
return { type, id, sessionId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a FileClaim for use in tests.
|
||||
*/
|
||||
export function createTestClaim(overrides: Partial<FileClaim> = {}): FileClaim {
|
||||
const now = new Date().toISOString();
|
||||
return {
|
||||
id: randomUUID(),
|
||||
path: TEST_FILE_A,
|
||||
lockType: "write",
|
||||
status: "active",
|
||||
owner: TEST_OWNER,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
expiresAt: new Date(Date.now() + 300_000).toISOString(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// Cache for lazy-loaded modules
|
||||
let _resetRegistryCache: (() => void) | null = null;
|
||||
let _getActiveClaimsCache: (() => FileClaim[]) | null = null;
|
||||
|
||||
/**
|
||||
* Reset the registry and config.
|
||||
* Uses dynamic import for ESM compatibility.
|
||||
*/
|
||||
export async function resetRegistry(): Promise<void> {
|
||||
try {
|
||||
if (!_resetRegistryCache) {
|
||||
const mod = await import("../index");
|
||||
const cfg = await import("../src/config");
|
||||
const fn = () => {
|
||||
mod.resetRegistry();
|
||||
cfg.resetConfig();
|
||||
};
|
||||
_resetRegistryCache = fn;
|
||||
fn();
|
||||
} else {
|
||||
_resetRegistryCache();
|
||||
}
|
||||
} catch {
|
||||
// If index.ts can't be loaded, just try config
|
||||
try {
|
||||
const cfg = await import("../src/config");
|
||||
cfg.resetConfig();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current active claims from the registry.
|
||||
*/
|
||||
export async function getActiveClaims(): Promise<FileClaim[]> {
|
||||
try {
|
||||
if (!_getActiveClaimsCache) {
|
||||
const mod = await import("../index");
|
||||
const fn = () => {
|
||||
const registry = mod.getClaimRegistry();
|
||||
return Object.values(registry.claims).filter(
|
||||
(c: any) => c.status === "active",
|
||||
);
|
||||
};
|
||||
_getActiveClaimsCache = fn;
|
||||
return fn();
|
||||
}
|
||||
return _getActiveClaimsCache();
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock Pi API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Mock Pi extension API for testing event handlers and tool registration.
|
||||
*/
|
||||
export function createMockPi(): any {
|
||||
const handlers: Record<string, Array<(...args: any[]) => any>> = {};
|
||||
const entries: Array<{ type: string; data?: unknown }> = [];
|
||||
const registeredTools: string[] = [];
|
||||
|
||||
return {
|
||||
events: {
|
||||
emit: (type: string, data?: unknown) => {
|
||||
const h = handlers[type] ?? [];
|
||||
for (const handler of h) handler(data);
|
||||
},
|
||||
on: (type: string, handler: (...args: any[]) => any) => {
|
||||
(handlers[type] ??= []).push(handler);
|
||||
return () => {
|
||||
const idx = handlers[type]?.indexOf(handler) ?? -1;
|
||||
if (idx >= 0) handlers[type]!.splice(idx, 1);
|
||||
};
|
||||
},
|
||||
},
|
||||
registerTool: (_tool: any) => {
|
||||
registeredTools.push(_tool.name ?? "unknown");
|
||||
},
|
||||
registerCommand: (_name: string, _def: any) => {},
|
||||
appendEntry: (type: string, data?: unknown) => {
|
||||
entries.push({ type, data });
|
||||
},
|
||||
getSessionName: () => "test-session",
|
||||
on: (type: string, handler: (...args: any[]) => any) => {
|
||||
(handlers[type] ??= []).push(handler);
|
||||
return () => {
|
||||
const idx = handlers[type]?.indexOf(handler) ?? -1;
|
||||
if (idx >= 0) handlers[type]!.splice(idx, 1);
|
||||
};
|
||||
},
|
||||
_handlers: handlers,
|
||||
_entries: entries,
|
||||
_registeredTools: registeredTools,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock ExtensionContext for testing event handlers.
|
||||
*/
|
||||
export function createMockContext(overrides: Record<string, any> = {}): any {
|
||||
return {
|
||||
ui: {
|
||||
setWidget: () => {},
|
||||
setStatus: () => {},
|
||||
notify: () => {},
|
||||
select: async () => "View all claims",
|
||||
input: async () => "/test/path.ts",
|
||||
confirm: async () => true,
|
||||
},
|
||||
hasUI: true,
|
||||
cwd: "/tmp/test-claiming",
|
||||
sessionManager: {
|
||||
getSessionFile: () => TEST_SESSION_ID,
|
||||
},
|
||||
modelRegistry: {},
|
||||
model: undefined,
|
||||
isIdle: () => false,
|
||||
signal: undefined,
|
||||
abort: () => {},
|
||||
hasPendingMessages: () => false,
|
||||
shutdown: () => {},
|
||||
getContextUsage: () => undefined,
|
||||
compact: () => {},
|
||||
getSystemPrompt: () => "",
|
||||
registerTool: () => {},
|
||||
events: {
|
||||
emit: () => {},
|
||||
on: () => () => {},
|
||||
},
|
||||
appendEntry: () => {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Temporary directory helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a temporary directory for file-based integration tests.
|
||||
*/
|
||||
export function createTempDir(prefix: string = "file-claiming-test-"): string {
|
||||
return mkdtempSync(join(tmpdir(), prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a temporary directory and all its contents.
|
||||
*/
|
||||
export function cleanupTempDir(dir: string): void {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Assertion helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Assert function for test validation.
|
||||
*/
|
||||
export function assert(condition: boolean, message: string): void {
|
||||
if (!condition) {
|
||||
throw new Error(`Assertion failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure the execution time of a synchronous function.
|
||||
*/
|
||||
export function measureTimeSync<T>(fn: () => T): {
|
||||
result: T;
|
||||
elapsedMs: number;
|
||||
} {
|
||||
const start = performance.now();
|
||||
const result = fn();
|
||||
const elapsedMs = performance.now() - start;
|
||||
return { result, elapsedMs };
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure the execution time of an async function.
|
||||
*/
|
||||
export async function measureTime<T>(
|
||||
fn: () => Promise<T>,
|
||||
): Promise<{ result: T; elapsedMs: number }> {
|
||||
const start = performance.now();
|
||||
const result = await fn();
|
||||
const elapsedMs = performance.now() - start;
|
||||
return { result, elapsedMs };
|
||||
}
|
||||
Reference in New Issue
Block a user