import { describe, it, expect, beforeEach, afterAll } from "vitest"; import { prisma, resetDb } from "./helpers.js"; import { acquireLock, releaseLock, currentLockRunId, isTerminal } from "../../src/lock.js"; describe("ProjectAgentLock WellFormed (integration, ADR-0002)", () => { beforeEach(async () => { await resetDb(); }); it("acquireLock + currentLockRunId round-trip", async () => { const project = await prisma.project.create({ data: { id: "p-lock-1", name: "Test", workspaceDir: "/tmp/x" }, }); const run = await prisma.agentRun.create({ data: { projectId: project.id, entrypoint: "FEISHU", status: "ACTIVE", prompt: "x", model: "m", provider: "mock", metadata: {} }, }); await acquireLock(prisma, project.id, run.id, null); const holder = await currentLockRunId(prisma, project.id); expect(holder).toBe(run.id); await releaseLock(prisma, run.id); const after = await currentLockRunId(prisma, project.id); expect(after).toBeNull(); }); it("acquireLock fails when project already locked (exclusivity)", async () => { const project = await prisma.project.create({ data: { id: "p-lock-2", name: "Test", workspaceDir: "/tmp/x" }, }); const run1 = await prisma.agentRun.create({ data: { projectId: project.id, entrypoint: "FEISHU", status: "ACTIVE", prompt: "x", model: "m", provider: "mock", metadata: {} }, }); const run2 = await prisma.agentRun.create({ data: { projectId: project.id, entrypoint: "FEISHU", status: "ACTIVE", prompt: "y", model: "m", provider: "mock", metadata: {} }, }); await acquireLock(prisma, project.id, run1.id, null); await expect(acquireLock(prisma, project.id, run2.id, null)).rejects.toThrow(); }); it("currentLockRunId throws when a terminal run holds the lock (WellFormed)", async () => { const project = await prisma.project.create({ data: { id: "p-lock-3", name: "Test", workspaceDir: "/tmp/x" }, }); // Create a run that is already COMPLETED (terminal), then manually insert a lock. const run = await prisma.agentRun.create({ data: { projectId: project.id, entrypoint: "FEISHU", status: "COMPLETED", prompt: "x", model: "m", provider: "mock", metadata: {}, finishedAt: new Date() }, }); await prisma.projectAgentLock.create({ data: { projectId: project.id, runId: run.id }, }); // WellFormed invariant: reading a lock held by a terminal run must throw. await expect(currentLockRunId(prisma, project.id)).rejects.toThrow(/lock invariant violated/); }); it("releaseLock is idempotent", async () => { const project = await prisma.project.create({ data: { id: "p-lock-4", name: "Test", workspaceDir: "/tmp/x" }, }); const run = await prisma.agentRun.create({ data: { projectId: project.id, entrypoint: "FEISHU", status: "ACTIVE", prompt: "x", model: "m", provider: "mock", metadata: {} }, }); await acquireLock(prisma, project.id, run.id, null); await releaseLock(prisma, run.id); // Second release is a no-op. await releaseLock(prisma, run.id); expect(await currentLockRunId(prisma, project.id)).toBeNull(); }); it("isTerminal matches spec RunState.Terminal", () => { expect(isTerminal("ACTIVE")).toBe(false); expect(isTerminal("WAITING_FOR_USER")).toBe(false); expect(isTerminal("COMPLETED")).toBe(true); expect(isTerminal("FAILED")).toBe(true); expect(isTerminal("TIMED_OUT")).toBe(true); expect(isTerminal("CANCELED")).toBe(true); }); }); afterAll(async () => { await prisma.$disconnect(); });