Files
zopu-code/packages/primitives/src/project-workspace.test.ts
T

142 lines
4.1 KiB
TypeScript

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);
});
});