AkurAI Build
Menu

popagent

public

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