import { describe, expect, it } from "vitest"; import type { AutonomyProjectRuntime } from "../src/services/autonomyRuntime.js"; import { buildAutoHumanAttentionPriority, PORTFOLIO_CATALOG_CACHE_MS, shouldRefreshPortfolioCatalog, summarizeDeliveryActivity, summarizeLiveExecution } from "../src/services/portfolioControlPlane.js"; const autoProject = (patch: Partial = {}): AutonomyProjectRuntime => ({ projectId: "project-db-id", projectSlug: "demo", projectName: "Demo", enabled: true, status: "running", lifecycle: "ready", pendingWorkCount: 3, completedWorkCount: 1, maxWorkItemsPerRun: 5, maxRunMinutes: 30, stalled: false, ...patch, }); describe("portfolio delivery activity semantics", () => { it("counts stored delivery phases separately from executor liveness", () => { expect(summarizeDeliveryActivity({ draft: 2, approved: 3, executing: 4, validating: 5, pull_request_open: 6, merged: 7, verified: 8, released: 9, })).toEqual({ active: 9, queued: 5, waitingExternal: 6, mergedAwaitingVerification: 7, completed: 17, }); }); it("defaults missing delivery buckets to zero", () => { expect(summarizeDeliveryActivity({})).toEqual({ active: 0, queued: 0, waitingExternal: 0, mergedAwaitingVerification: 0, completed: 0, }); }); }); describe("confirmed execution", () => { const now = Date.parse("2026-09-09T12:00:00Z"); const working = autoProject({ activeWorkKind: "implement", activeRunId: "run-1", executorRunId: "run-1", executorStatus: "working", executorLastSeenAt: new Date(now - 1000).toISOString(), leaseExpiresAt: new Date(now + 60000).toISOString() }); it("requires enabled state, valid lease and fresh working heartbeat together", () => { expect(summarizeLiveExecution([working], 120, now).verifiedActiveProjects).toBe(1); for (const patch of [{ enabled: false }, { executorStatus: "stale" as const }, { executorStatus: "idle" as const }, { activeWorkKind: undefined }, { leaseExpiresAt: new Date(now).toISOString() }, { executorLastSeenAt: new Date(now - 121000).toISOString() }, { executorLastSeenAt: "invalid" }, { executorLastSeenAt: new Date(now + 1000).toISOString() }]) { expect(summarizeLiveExecution([{ ...working, ...patch }], 120, now).verifiedActiveProjects).toBe(0); } }); it("distinguishes paused automation from orphaned claims", () => { expect(summarizeLiveExecution([{ ...working, executorRunId: "different-run" }], 120, now).verifiedActiveProjects).toBe(0); expect(summarizeLiveExecution([{ ...working, activeRunId: undefined }], 120, now).verifiedActiveProjects).toBe(0); expect(summarizeLiveExecution([{ ...working, enabled: false }], 120, now)).toMatchObject({ verifiedActiveProjects: 0, enabledProjects: 0, unconfirmedLeaseProjects: 0 }); expect(summarizeLiveExecution([{ ...working, executorStatus: "offline" }], 120, now).unconfirmedLeaseProjects).toBe(1); }); }); describe("portfolio Auto human attention", () => { it("surfaces true human Auto blockers as portfolio attention", () => { const priority = buildAutoHumanAttentionPriority([ autoProject({ status: "blocked", blockerKind: "human", blockerReason: "Owner decision required" }), autoProject({ projectId: "retry-db", projectSlug: "retry", projectName: "Retry", status: "blocked", blockerKind: "retryable", blockerReason: "Runner timeout" }), ]); expect(priority).toMatchObject({ id: "auto-human-blockers", projectId: "project-db-id", requiresHumanAttention: true, count: 1, route: "/projects/demo/attention", }); }); it("does not turn retryable or disabled Auto state into human attention", () => { expect(buildAutoHumanAttentionPriority([ autoProject({ status: "blocked", blockerKind: "retryable" }), autoProject({ enabled: false, status: "blocked", blockerKind: "human" }), ])).toBeUndefined(); }); }); describe("portfolio catalog freshness", () => { it("serves a recent durable catalog before asking Gitea for another inventory", () => { const now = Date.parse("2026-09-13T12:00:00.000Z"); expect(shouldRefreshPortfolioCatalog(new Date(now - PORTFOLIO_CATALOG_CACHE_MS), now)).toBe(false); expect(shouldRefreshPortfolioCatalog(new Date(now - PORTFOLIO_CATALOG_CACHE_MS - 1), now)).toBe(true); expect(shouldRefreshPortfolioCatalog(undefined, now)).toBe(true); }); });