Files
PaperClipAI/ui/src/api/execution-workspaces.test.ts
T
DottaandPaperclip f329b24ee1 feat: give tasks durable project-optional workspaces (#15749)
Give tasks durable company-scoped workspace bindings independent of project assignment, channel defaults and authorized repository preparation. Reuse verified input seeds and sparse output recovery while preserving trust, privacy and controller provisioning boundaries.

Full CI and E2E, Superagent, legacy checks and Greptile Apex 5/5 passed on reviewed head 753680b61c. All review findings are resolved; the PR records focused verification and historical acceptance limits.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-12 20:45:18 -05:00

131 lines
4.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const mockApi = vi.hoisted(() => ({
get: vi.fn(),
post: vi.fn(),
}));
vi.mock("./client", () => ({
api: mockApi,
}));
import { executionWorkspacesApi } from "./execution-workspaces";
describe("executionWorkspacesApi.listSummaries", () => {
beforeEach(() => {
mockApi.get.mockReset();
mockApi.get.mockResolvedValue([]);
});
it("requests the lightweight summary payload", async () => {
await executionWorkspacesApi.listSummaries("company-1", {
projectId: "project-1",
reuseEligible: true,
});
expect(mockApi.get).toHaveBeenCalledWith(
"/companies/company-1/execution-workspaces?projectId=project-1&reuseEligible=true&summary=true",
);
});
it("delegates task selection eligibility to the server", async () => {
await executionWorkspacesApi.listSummaries("company-1", { selectableForTask: true });
expect(mockApi.get).toHaveBeenCalledWith(
"/companies/company-1/execution-workspaces?selectableForTask=true&summary=true",
);
});
it("requests and normalizes the bounded overview payload", async () => {
mockApi.get.mockResolvedValue({
items: [
{
key: "execution:workspace-1",
kind: "execution_workspace",
workspaceId: "workspace-1",
workspaceName: "Workspace 1",
projectId: "project-1",
projectName: "Paperclip App",
mode: "isolated_workspace",
strategyType: "git_worktree",
cwd: "/tmp/workspace-1",
branchName: "PAP-1",
lastUpdatedAt: "2026-06-25T01:00:00.000Z",
projectWorkspaceId: null,
executionWorkspaceId: "workspace-1",
executionWorkspaceStatus: "active",
serviceCount: 1,
runningServiceCount: 1,
primaryServiceUrl: "http://localhost:3100",
primaryServiceUrlRunning: true,
primaryService: {
id: "service-1",
serviceName: "web",
status: "running",
url: "http://localhost:3100",
port: 3100,
healthStatus: "healthy",
updatedAt: "2026-06-25T01:01:00.000Z",
},
hasRuntimeConfig: true,
linkedIssueCount: 1,
linkedIssues: [
{
id: "issue-1",
identifier: "PAP-1",
title: "Linked task",
status: "todo",
priority: "medium",
updatedAt: "2026-06-25T01:02:00.000Z",
},
],
},
],
total: 1,
limit: 25,
offset: 10,
hasMore: false,
nextOffset: null,
});
const overview = await executionWorkspacesApi.listOverview("company-1", {
status: ["active", "idle"],
limit: 25,
offset: 10,
});
expect(mockApi.get).toHaveBeenCalledWith(
"/companies/company-1/workspace-overview?status=active%2Cidle&limit=25&offset=10",
);
expect(overview.items[0]!.lastUpdatedAt).toBeInstanceOf(Date);
expect(overview.items[0]!.primaryService?.updatedAt).toBeInstanceOf(Date);
expect(overview.items[0]!.linkedIssues[0]!.updatedAt).toBeInstanceOf(Date);
});
});
describe("executionWorkspacesApi.reconcile", () => {
beforeEach(() => {
mockApi.post.mockReset();
mockApi.post.mockResolvedValue({});
});
// Regression pin (PAP-1705): the frontend path must match the reviewed, OpenAPI-documented
// backend contract `POST /execution-workspaces/:id/reconcile-branch` (S4 / PAP-1586). A bare
// `/reconcile` 404s both recovery-card actions. If the two sides drift, this test fails.
it("posts forward reconcile to the /reconcile-branch route", async () => {
await executionWorkspacesApi.reconcile("workspace-1", { mode: "forward" });
expect(mockApi.post).toHaveBeenCalledWith("/execution-workspaces/workspace-1/reconcile-branch", {
mode: "forward",
});
});
it("posts break-glass override reconcile to the /reconcile-branch route", async () => {
await executionWorkspacesApi.reconcile("workspace-1", { mode: "override", reason: "operator note" });
expect(mockApi.post).toHaveBeenCalledWith("/execution-workspaces/workspace-1/reconcile-branch", {
mode: "override",
reason: "operator note",
});
});
});