import { describe, expect, it, vi } from "vitest"; import { createCancelStaleQueuedRun, createDispatchResolvedInteractionIfCurrent, createEvaluateScheduledRetryGate, createPromoteDueScheduledRetries, createPromoteScheduledRetry, } from "./use-cases.js"; import type { DueRetryRun, RunDispatchWriter, ScheduledRetryReader } from "./ports.js"; function fakeReader(dueRuns: DueRetryRun[] = []): ScheduledRetryReader & { evaluateCalls: unknown[] } { const evaluateCalls: unknown[] = []; return { evaluateCalls, async nextScheduledRetryAt() { return null; }, async evaluateScheduledRetryGate(input) { evaluateCalls.push(input); return { allowed: true }; }, async listDueRetries() { return dueRuns; }, }; } function fakeWriter(overrides: Partial = {}): RunDispatchWriter & { promoteCalls: unknown[]; cancelCalls: unknown[]; dispatchCalls: unknown[]; } { const promoteCalls: unknown[] = []; const cancelCalls: unknown[] = []; const dispatchCalls: unknown[] = []; return { promoteCalls, cancelCalls, dispatchCalls, async promoteOrCancelDueRetry(input) { promoteCalls.push(input); return { outcome: "promoted", postCommitEffects: [] }; }, async cancelStaleQueuedRun(input) { cancelCalls.push(input); return { outcome: "not_stale" }; }, async dispatchResolvedInteractionIfCurrent(input) { dispatchCalls.push(input); return { dispatched: true, resultPromise: input.dispatch(() => {}) }; }, ...overrides, }; } describe("createEvaluateScheduledRetryGate", () => { it("maps identifiers and a default clock onto the semantic reader operation", async () => { const reader = fakeReader(); const before = Date.now(); const result = await createEvaluateScheduledRetryGate({ reader })({ runId: "run-1", companyId: "company-1", retryReasonOverride: "max_turns_continuation", }); expect(result).toEqual({ allowed: true }); expect(reader.evaluateCalls).toHaveLength(1); const call = reader.evaluateCalls[0] as { now: Date }; expect(call).toMatchObject({ runId: "run-1", companyId: "company-1", retryReasonOverride: "max_turns_continuation", }); expect(call.now.getTime()).toBeGreaterThanOrEqual(before); }); }); describe("createPromoteScheduledRetry", () => { it("passes only semantic identifiers and the clock to the atomic operation", async () => { const writer = fakeWriter(); const now = new Date("2026-01-01T00:00:00.000Z"); const result = await createPromoteScheduledRetry({ writer })({ runId: "run-1", companyId: "company-1", now, }); expect(result).toEqual({ outcome: "promoted", postCommitEffects: [] }); expect(writer.promoteCalls).toEqual([{ runId: "run-1", companyId: "company-1", now }]); }); it("passes a gate-suppressed outcome through without a persistence row", async () => { const writer = fakeWriter({ promoteOrCancelDueRetry: vi.fn(async () => ({ outcome: "gate_suppressed" as const, reason: "agent paused", errorCode: "agent_not_invokable" as const, })), }); const result = await createPromoteScheduledRetry({ writer })({ runId: "run-1", companyId: "company-1", }); expect(result).toEqual({ outcome: "gate_suppressed", reason: "agent paused", errorCode: "agent_not_invokable", }); }); }); describe("createPromoteDueScheduledRetries", () => { it("keeps due order and caps a sweep at 50 runs", async () => { const dueRuns = Array.from({ length: 75 }, (_, i) => ({ runId: `run-${i}`, companyId: "company-1", })); const reader = fakeReader(dueRuns); const writer = fakeWriter(); const result = await createPromoteDueScheduledRetries({ reader, promoteScheduledRetry: createPromoteScheduledRetry({ writer }), })({ cutoff: null }); expect(result.promoted).toBe(50); expect(result.runIds).toEqual(dueRuns.slice(0, 50).map(({ runId }) => runId)); }); }); describe("createCancelStaleQueuedRun", () => { it("delegates the complete read-decide-cancel operation to the writer", async () => { const cancelStaleQueuedRun = vi.fn(async () => ({ outcome: "cancelled" as const, reason: "issue reassigned", errorCode: "issue_assignee_changed" as const, postCommitEffects: [], })); const writer = fakeWriter({ cancelStaleQueuedRun, }); const now = new Date("2026-01-01T00:00:00.000Z"); const result = await createCancelStaleQueuedRun({ writer })({ runId: "run-1", companyId: "company-1", expectedStatus: "queued", now, }); expect(result.outcome).toBe("cancelled"); expect(cancelStaleQueuedRun).toHaveBeenCalledWith({ runId: "run-1", companyId: "company-1", expectedStatus: "queued", now, }); }); }); describe("createDispatchResolvedInteractionIfCurrent", () => { it("delegates the lock, validation, cancellation, and dispatch boundary", async () => { const writer = fakeWriter(); const dispatch = vi.fn(async () => "started"); const result = await createDispatchResolvedInteractionIfCurrent({ writer })({ runId: "run-1", companyId: "company-1", expectedStatus: "running", reservationId: "reservation-1", enforceContinuationOwnership: false, dispatch, }); expect(result.dispatched).toBe(true); expect(writer.dispatchCalls).toHaveLength(1); expect(writer.dispatchCalls[0]).toMatchObject({ enforceContinuationOwnership: false }); }); });