agent-orchestrator/packages/integration-tests/src/workspace-worktree.integrat...

131 lines
4.3 KiB
TypeScript

import { execFile } from "node:child_process";
import { mkdtemp, rm, realpath } from "node:fs/promises";
import { existsSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import worktreePlugin from "@composio/ao-plugin-workspace-worktree";
import type { ProjectConfig, WorkspaceInfo } from "@composio/ao-core";
const execFileAsync = promisify(execFile);
async function git(cwd: string, ...args: string[]): Promise<string> {
const { stdout } = await execFileAsync("git", args, { cwd });
return stdout.trimEnd();
}
describe("workspace-worktree (integration)", () => {
let repoDir: string;
let worktreeBaseDir: string;
let workspace: ReturnType<typeof worktreePlugin.create>;
let project: ProjectConfig;
let createdInfo: WorkspaceInfo;
beforeAll(async () => {
// Create a temp repo with initial commit
const rawRepo = await mkdtemp(join(tmpdir(), "ao-inttest-wt-repo-"));
repoDir = await realpath(rawRepo);
await git(repoDir, "init", "-b", "main");
await git(repoDir, "config", "user.email", "test@test.com");
await git(repoDir, "config", "user.name", "Test");
await execFileAsync("sh", ["-c", "echo hello > README.md"], { cwd: repoDir });
await git(repoDir, "add", ".");
await git(repoDir, "commit", "-m", "initial commit");
// Add "origin" pointing at itself so the plugin's fetch succeeds
await git(repoDir, "remote", "add", "origin", repoDir);
await git(repoDir, "fetch", "origin");
// Create worktree base dir
const rawBase = await mkdtemp(join(tmpdir(), "ao-inttest-wt-base-"));
worktreeBaseDir = await realpath(rawBase);
workspace = worktreePlugin.create({ worktreeDir: worktreeBaseDir });
project = {
name: "inttest",
repo: "test/inttest",
path: repoDir,
defaultBranch: "main",
sessionPrefix: "test",
};
}, 30_000);
afterAll(async () => {
// Clean up worktrees first (must be done before removing repo)
try {
await git(repoDir, "worktree", "prune");
} catch { /* best-effort cleanup */ }
if (repoDir) await rm(repoDir, { recursive: true, force: true }).catch(() => {});
if (worktreeBaseDir) await rm(worktreeBaseDir, { recursive: true, force: true }).catch(() => {});
}, 30_000);
it("creates a worktree workspace", async () => {
createdInfo = await workspace.create({
projectId: "inttest",
sessionId: "session-1",
project,
branch: "feat/test-branch",
});
expect(createdInfo.path).toContain("session-1");
expect(createdInfo.branch).toBe("feat/test-branch");
expect(createdInfo.sessionId).toBe("session-1");
expect(createdInfo.projectId).toBe("inttest");
expect(existsSync(createdInfo.path)).toBe(true);
});
it("worktree is on the correct branch", async () => {
const branch = await git(createdInfo.path, "branch", "--show-current");
expect(branch).toBe("feat/test-branch");
});
it("worktree has the files from main", async () => {
expect(existsSync(join(createdInfo.path, "README.md"))).toBe(true);
});
it("lists the worktree", async () => {
const list = await workspace.list("inttest");
expect(list.length).toBeGreaterThanOrEqual(1);
const found = list.find((w: { sessionId: string }) => w.sessionId === "session-1");
expect(found).toBeDefined();
expect(found!.branch).toBe("feat/test-branch");
});
it("rejects invalid projectId", async () => {
await expect(
workspace.create({
projectId: "../escape",
sessionId: "ok",
project,
branch: "feat/x",
}),
).rejects.toThrow("Invalid projectId");
});
it("rejects invalid sessionId", async () => {
await expect(
workspace.create({
projectId: "inttest",
sessionId: "bad/id",
project,
branch: "feat/x",
}),
).rejects.toThrow("Invalid sessionId");
});
it("destroys the worktree", async () => {
const pathToDestroy = createdInfo.path;
await workspace.destroy(pathToDestroy);
expect(existsSync(pathToDestroy)).toBe(false);
});
it("list returns empty after destroy", async () => {
const list = await workspace.list("inttest");
const found = list.find((w: { sessionId: string }) => w.sessionId === "session-1");
expect(found).toBeUndefined();
});
});