src/testHelpers.ts

import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import type { DashboardBot, DashboardMemory, DashboardPublicHttpsState, DashboardScreenShareSessionManager } from "./dashboard.ts"; import { createDashboardServer } from "./dashboard.ts"; import { Store } from "./store/store.ts";

type ErrorLike = { code?: unknown; message?: unknown; };

type TestDashboardServerOptions = { dashboardToken?: string; appConfigOverrides?: Record<string, unknown>; publicHttpsState?: DashboardPublicHttpsState | null; botOverrides?: Partial & Record<string, unknown>; memoryOverrides?: Partial & Record<string, unknown>; screenShareSessionManager?: DashboardScreenShareSessionManager | null; };

type TestDashboardServerResult = { baseUrl: string; bot: DashboardBot & { appliedSettings: unknown[] }; memory: DashboardMemory; store: Store; ingestCalls: unknown[]; memoryCalls: unknown[]; };

/**

  • Removes a temporary directory with retry + GC on EBUSY.
  • On bun+Windows, prepared SQLite statements created while exercising the Store
  • may keep WAL/SHM file handles open briefly after db.close(), causing
  • fs.rm to fail with EBUSY. Forcing GC between attempts releases the stray
  • handles. This is a test-only helper — production shutdown paths don't care
  • about removing the DB directory.
  • Tests should use this instead of calling fs.rm directly. */ export async function rmTempDir(dir: string, { attempts = 20, delayMs = 50 } = {}) { for (let i = 0; i < attempts; i++) { try { await fs.rm(dir, { recursive: true, force: true }); return; } catch (error) { if (i === attempts - 1) throw error; const bunGlobal = (globalThis as { Bun?: { gc?: (sync: boolean) => void } }).Bun; if (typeof bunGlobal?.gc === "function") { bunGlobal.gc(true); } await new Promise((resolve) => setTimeout(resolve, delayMs)); } } }

function isListenPermissionError(error: unknown): boolean { const errorLike = (typeof error === "object" && error !== null ? error : {}) as ErrorLike; const code = String(errorLike.code || "").toUpperCase(); const message = String(errorLike.message || "");

return ( code === "EPERM" || code === "EACCES" || (code === "EADDRINUSE" && /port\s+0\s+in\s+use/i.test(message)) || /listen\s+EPERM|listen\s+EACCES/i.test(message) ); }

export async function withDashboardServer( { dashboardToken = "", appConfigOverrides = {}, publicHttpsState = null, botOverrides = {}, memoryOverrides = {}, screenShareSessionManager = null }: TestDashboardServerOptions = {}, run: (context: TestDashboardServerResult) => Promise ): Promise<{ skipped: boolean; reason?: string }> { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clanker-dashboard-test-")); const dbPath = path.join(dir, "clanker.db"); const store = new Store(dbPath); store.init();

const ingestCalls = []; const memoryCalls = [];

const appliedSettings: unknown[] = []; const bot: DashboardBot & { appliedSettings: unknown[] } = { appliedSettings, async applyRuntimeSettings(nextSettings) { appliedSettings.push(nextSettings); return nextSettings; }, getRuntimeState() { return { connected: true, replyQueuePending: 0 }; }, getGuilds() { return []; }, getGuildChannels() { return []; }, async ingestVoiceStreamFrame(payload) { ingestCalls.push(payload); return { accepted: true, reason: "ok" }; }, ...botOverrides };

const memory: DashboardMemory = { async readMemoryMarkdown() { return "# memory"; }, async refreshMemoryMarkdown() { return true; }, async purgeGuildMemory({ guildId } = {}) { return { ok: true, reason: "deleted", guildId: String(guildId || "").trim() || null, durableFactsDeleted: 0, durableFactVectorsDeleted: 0, conversationMessagesDeleted: 0, conversationVectorsDeleted: 0, reflectionEventsDeleted: 0, journalEntriesDeleted: 0, journalFilesTouched: 0, summaryRefreshed: true }; }, loadFactProfile() { return { participantProfiles: [], selfFacts: [], loreFacts: [], userFacts: [], relevantFacts: [], guidanceFacts: [] }; }, loadUserFactProfile() { return { userFacts: [] }; }, loadGuildFactProfile() { return { selfFacts: [], loreFacts: [] }; }, async loadBehavioralFactsForPrompt() { return []; }, async searchDurableFacts(payload) { memoryCalls.push(payload); return [{ fact: "remember this" }]; }, ...memoryOverrides };

const appConfig = { dashboardHost: "127.0.0.1", dashboardPort: 0, dashboardToken, publicApiToken: "", ...appConfigOverrides };

const publicHttpsEntrypoint = publicHttpsState ? { getState() { return publicHttpsState; } } : null;

let dashboard = null; try { dashboard = createDashboardServer({ appConfig, store, bot, memory, publicHttpsEntrypoint, screenShareSessionManager });

const address = dashboard.server.address();
const port = typeof address === "object" && address ? address.port : null;
if (!Number.isInteger(port) || port <= 0) {
  throw new Error("dashboard test server did not provide a valid port");
}

await run({
  baseUrl: `http://127.0.0.1:${port}`,
  bot,
  memory,
  store,
  ingestCalls,
  memoryCalls
});

} catch (error) { if (isListenPermissionError(error)) { return { skipped: true, reason: "listen_permission_denied" }; } throw error; } finally { if (dashboard?.server) { await new Promise((resolve) => { dashboard.server.close(() => { resolve(); }); }); } store.close(); await rmTempDir(dir); }

return { skipped: false }; }