import { Effect } from "effect"; import { describe, expect, it } from "vitest"; import { decodeIssueWorkspaceResult, makeIssueWorkspacePlan, transitionWorkspaceRun, WorkspaceExecutionError, WorkspaceStateError, WorkspaceValidationError, } from "./project-workspace"; const makeInput = () => ({ artifacts: [ { content: "# Work\n", path: "work.md", }, ], branchName: undefined, checkoutPath: undefined, contextFiles: [ { content: "# Project\n", kind: "readme" as const, path: "README.md", }, ], defaultBranch: "main", issueBody: "Make the project run in an isolated checkout.", issueId: "projectIssues|abc123", issueNumber: 7, issueTitle: "Use a real project checkout", sourceUrl: "https://git.example.test/puter/project", }); const validationFailure = (input: unknown): WorkspaceValidationError => { const error = Effect.runSync(Effect.flip(makeIssueWorkspacePlan(input))); expect(error).toBeInstanceOf(WorkspaceValidationError); return error; }; describe("project workspace primitives", () => { it("plans a deterministic checkout and keeps control files outside the repo", () => { const plan = Effect.runSync(makeIssueWorkspacePlan(makeInput())); const secondPlan = Effect.runSync(makeIssueWorkspacePlan(makeInput())); expect(plan.branchName).toBe("work/issue-7-abc123"); expect(plan.checkoutPath).toBe("/workspace/repository"); expect(plan).toEqual(secondPlan); expect(plan.operations).toEqual( expect.arrayContaining([ { _tag: "WriteFile", content: expect.any(String), path: "/workspace/control/issue.md", }, { _tag: "WriteFile", content: "# Project\n", path: "/workspace/control/context/README.md", }, { _tag: "WriteFile", content: "# Work\n", path: "/workspace/control/artifacts/work.md", }, ]) ); expect( plan.operations.some( (operation) => operation._tag === "Exec" && operation.command.includes("git clone") ) ).toBe(true); }); it("rejects unsafe sources and control-file paths", () => { const missingSource = { ...makeInput(), sourceUrl: undefined }; expect(validationFailure(missingSource).reason).toBe("MissingSource"); const invalidSource = makeInput(); invalidSource.sourceUrl = "ssh://git@example.test/project"; expect(validationFailure(invalidSource).reason).toBe("InvalidSourceUrl"); const invalidPath = makeInput(); const [artifact] = invalidPath.artifacts; if (!artifact) { throw new Error("test artifact missing"); } artifact.path = "../work.md"; expect(validationFailure(invalidPath).reason).toBe("InvalidPath"); }); it("rejects a checkout result that does not prove the planned branch", () => { const plan = Effect.runSync(makeIssueWorkspacePlan(makeInput())); const error = Effect.runSync( Effect.flip( decodeIssueWorkspaceResult({ command: { exitCode: 0, stderr: "", stdout: "work/issue-7-other deadbeef\n", }, plan, }) ) ); expect(error).toBeInstanceOf(WorkspaceExecutionError); expect(error.reason).toBe("InvalidResult"); }); it("accepts the planned checkout result and enforces run transitions", () => { const plan = Effect.runSync(makeIssueWorkspacePlan(makeInput())); const result = Effect.runSync( decodeIssueWorkspaceResult({ command: { exitCode: 0, stderr: "", stdout: `${plan.branchName} deadbeef1234567\n`, }, plan, }) ); expect(result).toMatchObject({ branchName: plan.branchName, checkoutPath: "/workspace/repository", headSha: "deadbeef1234567", }); expect( Effect.runSync(transitionWorkspaceRun({ from: "queued", to: "working" })) ).toBe("working"); const error = Effect.runSync( Effect.flip(transitionWorkspaceRun({ from: "completed", to: "working" })) ); expect(error).toBeInstanceOf(WorkspaceStateError); }); });