diff --git a/server/src/__tests__/heartbeat-process-metadata.test.ts b/server/src/__tests__/heartbeat-process-metadata.test.ts index 0b16cfd206..cc654c9a6e 100644 --- a/server/src/__tests__/heartbeat-process-metadata.test.ts +++ b/server/src/__tests__/heartbeat-process-metadata.test.ts @@ -1,12 +1,12 @@ import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; -import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { agents, companies, createDb, heartbeatRuns, startEmbeddedPostgresTestDatabase, type Db } from "@paperclipai/db"; import * as processes from "../services/hot-restart.js"; import * as adapters from "../adapters/index.js"; import * as orchestration from "../services/environment-run-orchestrator.js"; import * as compatibility from "../services/legacy-sandbox-workspace.js"; -import * as cancellation from "@paperclipai/adapter-utils/adapter-run-cancellation"; +import * as gitCredentials from "../services/git-credentials.js"; import { bindAdapterRunStop, hasAdapterRunCancellation } from "@paperclipai/adapter-utils/adapter-run-cancellation"; import * as executionTargets from "@paperclipai/adapter-utils/execution-target"; import { heartbeatService, persistHeartbeatRunProcessMetadata } from "../services/heartbeat.js"; @@ -22,6 +22,12 @@ describe("heartbeat process identity persistence", () => { await db.insert(agents).values({ id: agentId, companyId, name: "Runner", role: "engineer", status: "idle", adapterType: "codex_local" }); }, 60_000); afterEach(() => vi.restoreAllMocks()); + beforeEach(() => { + // These tests exercise process/cancellation ownership, not Git transport. + // Their synthetic targets deliberately have no remote command runner. + vi.spyOn(executionTargets, "prepareGitHubExecutionEnvironment").mockImplementation(async (input) => input.env); + vi.spyOn(gitCredentials, "resolveManagedGitHubIdentitySelection").mockResolvedValue({ configured: true }); + }); afterAll(async () => { await database?.cleanup(); }); async function running() { const [run] = await db.insert(heartbeatRuns).values({ companyId, agentId, status: "running", invocationSource: "on_demand", startedAt: new Date(), @@ -103,7 +109,7 @@ describe("heartbeat process identity persistence", () => { } finally { release(); await heartbeat.drainActiveRunExecutions(); } }, 30_000); - it.each([false, true])("cancels the remote adapter and waits for teardown (scope lookup raced: %s)", async (scopeLookupRaced) => { + it("cancels the remote adapter and waits for teardown before acknowledging Stop", async () => { const originalOrchestrator = orchestration.environmentRunOrchestrator; vi.spyOn(orchestration, "environmentRunOrchestrator").mockImplementation((...args) => { const actual = originalOrchestrator(...args); @@ -125,14 +131,17 @@ describe("heartbeat process identity persistence", () => { execute: async (input) => { expect(hasAdapterRunCancellation(input.runId)).toBe(true); const cleanup = await bindAdapterRunStop(input.runId, async () => { - expect((await heartbeat.getRun(input.runId))?.status).toBe("cancelled"); + const run = await heartbeat.getRun(input.runId); + expect(run?.status).toBe("running"); + expect(run?.resultJson?.executionCancellation).toMatchObject({ state: "requested" }); stopped(); }); ready(); await interrupted; await teardown; await cleanup(); - return { exitCode: 143, signal: "SIGTERM", timedOut: false }; + return { exitCode: 143, signal: "SIGTERM", timedOut: false, + resultJson: { executionCancellation: { state: "acknowledged" } } }; }, } as ReturnType); let pending: Promise | undefined; @@ -140,9 +149,6 @@ describe("heartbeat process identity persistence", () => { const queued = await heartbeat.invoke(agentId, "on_demand", {}, "manual"); expect(queued).not.toBeNull(); await started; - // Model the initial lookup occurring before registration. The final - // lookup after persisting cancellation must still notify the scope. - if (scopeLookupRaced) vi.spyOn(cancellation, "hasAdapterRunCancellation").mockReturnValueOnce(false); let acknowledged = false; pending = heartbeat.cancelRun(queued!.id).then((result) => { acknowledged = true; return result; }); await interrupted; diff --git a/server/src/__tests__/run-trust-preset.test.ts b/server/src/__tests__/run-trust-preset.test.ts index d323f0f2b7..4bf3def655 100644 --- a/server/src/__tests__/run-trust-preset.test.ts +++ b/server/src/__tests__/run-trust-preset.test.ts @@ -53,7 +53,7 @@ it("dispatch retains raw trust before workspace and broker setup", () => { "utf8", ); const start = heartbeat.indexOf( - "const retainedTrust = await resolveAndRetainRunTrustPreset", + "const retainedTrust = await ", ); const end = heartbeat.indexOf( "const config = parseObject(agent.adapterConfig);", @@ -61,6 +61,7 @@ it("dispatch retains raw trust before workspace and broker setup", () => { ); const dispatch = heartbeat.slice(start, end); expect(start).toBeGreaterThan(0); + expect(dispatch).toContain("resolveAndRetainRunTrustPreset(db,"); expect(dispatch).toContain( "executionWorkspacePolicy: projectContext.executionWorkspacePolicy", );