Menu
popagent
publicLatest change fa391bb84299efc38430196d6f7156e2c978d1b7 - Respect the autonomy pause in follow-ups and refresh scheduled workspaces by AkurAI Build
import { afterAll, describe, expect, test } from "bun:test";
import { execFile } from "node:child_process";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { syncWorkspaceCheckout } from "./workspace-sync";
const execute = promisify(execFile);
const cleanupPaths: string[] = [];
afterAll(async () => {
await Promise.all(cleanupPaths.map((path) => rm(path, { recursive: true, force: true })));
});
async function temporaryDirectory(): Promise<string> {
const path = await mkdtemp(join(tmpdir(), "popagent-workspace-sync-"));
cleanupPaths.push(path);
return path;
}
/** An origin repository with two commits, and a clone stopped at the first. */
async function cloneBehindOrigin(): Promise<{ origin: string; clone: string; head: string }> {
const origin = await temporaryDirectory();
await execute("git", ["init", "-b", "main", origin]);
await execute("git", ["-C", origin, "config", "user.name", "Sync Test"]);
await execute("git", ["-C", origin, "config", "user.email", "sync@example.invalid"]);
await writeFile(join(origin, "version.txt"), "1.0.49\n");
await execute("git", ["-C", origin, "add", "."]);
await execute("git", ["-C", origin, "commit", "-m", "first"]);
const parent = await temporaryDirectory();
const clone = join(parent, "checkout");
await execute("git", ["clone", origin, clone]);
await writeFile(join(origin, "version.txt"), "1.0.57\n");
await execute("git", ["-C", origin, "commit", "-am", "second"]);
const { stdout } = await execute("git", ["-C", origin, "rev-parse", "HEAD"]);
return { origin, clone, head: stdout.trim() };
}
const headOf = async (path: string) => (await execute("git", ["-C", path, "rev-parse", "HEAD"])).stdout.trim();
describe("syncWorkspaceCheckout", () => {
test("fast-forwards a clean tracking checkout to its remote", async () => {
const { clone, head } = await cloneBehindOrigin();
expect(await headOf(clone)).not.toBe(head);
expect(await syncWorkspaceCheckout(clone)).toBe("synced");
expect(await headOf(clone)).toBe(head);
expect(await Bun.file(join(clone, "version.txt")).text()).toBe("1.0.57\n");
});
test("never touches a checkout with uncommitted work", async () => {
const { clone, head } = await cloneBehindOrigin();
const before = await headOf(clone);
await writeFile(join(clone, "version.txt"), "local edit\n");
expect(await syncWorkspaceCheckout(clone)).toBe("dirty");
expect(await headOf(clone)).toBe(before);
expect(await headOf(clone)).not.toBe(head);
expect(await Bun.file(join(clone, "version.txt")).text()).toBe("local edit\n");
});
test("reports rather than throws for a non-repository, a missing path, and a checkout with no upstream", async () => {
expect(await syncWorkspaceCheckout(await temporaryDirectory())).toBe("skipped");
expect(await syncWorkspaceCheckout(join(await temporaryDirectory(), "absent"))).toBe("skipped");
const detached = await temporaryDirectory();
await execute("git", ["init", "-b", "main", detached]);
await execute("git", ["-C", detached, "config", "user.name", "Sync Test"]);
await execute("git", ["-C", detached, "config", "user.email", "sync@example.invalid"]);
await writeFile(join(detached, "a.txt"), "a\n");
await execute("git", ["-C", detached, "add", "."]);
await execute("git", ["-C", detached, "commit", "-m", "only"]);
expect(await syncWorkspaceCheckout(detached)).toBe("skipped");
});
test("does not rewind a checkout that is ahead of its remote", async () => {
const { clone } = await cloneBehindOrigin();
await execute("git", ["-C", clone, "config", "user.name", "Sync Test"]);
await execute("git", ["-C", clone, "config", "user.email", "sync@example.invalid"]);
await writeFile(join(clone, "local.txt"), "local commit\n");
await execute("git", ["-C", clone, "add", "."]);
await execute("git", ["-C", clone, "commit", "-m", "diverged"]);
const before = await headOf(clone);
expect(await syncWorkspaceCheckout(clone)).toBe("diverged");
expect(await headOf(clone)).toBe(before);
});
});