import { randomUUID } from "node:crypto"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { agents, companies, createDb, heartbeatRuns } from "@paperclipai/db"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase, } from "./helpers/embedded-postgres.js"; import { heartbeatService } from "../services/heartbeat.ts"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; if (!embeddedPostgresSupport.supported) { console.warn( `Skipping embedded Postgres heartbeat list tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`, ); } describeEmbeddedPostgres("heartbeat list", () => { let db!: ReturnType; let tempDb: Awaited> | null = null; beforeAll(async () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-heartbeat-list-"); db = createDb(tempDb.connectionString); }, 20_000); afterEach(async () => { await db.delete(heartbeatRuns); await db.delete(agents); await db.delete(companies); }); afterAll(async () => { await tempDb?.cleanup(); }); it("returns runs even when the linked db schema lacks processGroupId", async () => { const companyId = randomUUID(); const agentId = randomUUID(); const runId = randomUUID(); await db.insert(companies).values({ id: companyId, name: "Paperclip", issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, requireBoardApprovalForNewAgents: false, }); await db.insert(agents).values({ id: agentId, companyId, name: "CodexCoder", role: "engineer", status: "running", adapterType: "codex_local", adapterConfig: {}, runtimeConfig: {}, permissions: {}, }); await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, invocationSource: "assignment", status: "running", contextSnapshot: { issueId: randomUUID() }, }); const originalDescriptor = Object.getOwnPropertyDescriptor(heartbeatRuns, "processGroupId"); Object.defineProperty(heartbeatRuns, "processGroupId", { value: undefined, configurable: true, writable: true, }); try { const runs = await heartbeatService(db).list(companyId, agentId, 5); expect(runs).toHaveLength(1); expect(runs[0]?.id).toBe(runId); expect(runs[0]?.processGroupId ?? null).toBeNull(); } finally { if (originalDescriptor) { Object.defineProperty(heartbeatRuns, "processGroupId", originalDescriptor); } else { delete (heartbeatRuns as Record).processGroupId; } } }); });