Files
zopu-code/packages/primitives/src/git-remote-runtime.test.ts
-Puter e744ac4687 Add Zopu dev bootstrap: git runtimes, Codex agent-os, dev agent
A vertical slice to bootstrap the product loop on the canonical zopu-code repo.

Primitives (packages/primitives, all tested + lint/type clean):
- GitRemoteRuntime: Gitea REST client (createIssue, createBranch,
  createPullRequest, listIssues, getIssue) as an Effect context service with
  a fetch-backed transport. Live-verified against puter/zopu-code.
- GitLocalRuntime: clone, addWorktree, commit, push, currentBranch,
  setRemoteUrl over a pluggable Shell (host subprocess now, AgentOS VM exec
  later). Tested against real temp git repos.
- agent-os: Codex support via @agentos-software/codex-cli — codexSoftware
  bundle (codex+git), makeCodexAgentOsConfig(), codexSessionEnv() for the
  OpenAI base URL/key.

Env:
- GITEA_URL/GITEA_TOKEN wired into convex.ts and convex.config.ts.
- .env.example documents the self-hosted git section.

Agents (packages/agents):
- zopu-dev: development agent that creates issues on the canonical repo and
  starts autonomous work runs. git-remote tools (create/list/branch/PR) wired
  to GitRemoteRuntime; start_workflow tool enqueues a work run.
- flue run zopu-dev verified live (lists/creates issues on real Gitea).

Backend (packages/backend):
- workflows.startIssueWork mutation: fetches a Gitea issue server-side and
  admits a queued work run. The Codex VM spawn body is the next step.
2026-07-25 03:18:23 +05:30

125 lines
3.3 KiB
TypeScript

import { Effect } from "effect";
import { describe, expect, it } from "vitest";
import {
createBranch,
createIssue,
createPullRequest,
getIssue,
listIssues,
} from "./git-remote-runtime";
import type { GitRemoteConfig, GitRemoteTransport } from "./git-remote-runtime";
const config: GitRemoteConfig = {
baseUrl: "https://git.example.com",
owner: "puter",
repo: "zopu-code",
token: "secret",
};
const stubTransport = (
responder: (method: string, path: string, body: unknown) => unknown,
status = 200
): GitRemoteTransport => ({
request: ({ body, method, url }) =>
Effect.sync(() => {
const path = url.replace(
`${config.baseUrl}/api/v1/repos/puter/zopu-code`,
""
);
const result = responder(method, path, body);
return {
json: () => Promise.resolve(result),
status,
};
}),
});
const run = <A, E>(eff: Effect.Effect<A, E>) => Effect.runPromise(eff);
describe("GitRemoteRuntime", () => {
it("creates an issue and maps Gitea fields", async () => {
const transport = stubTransport(() => ({
body: "the body",
html_url: "https://git.example.com/puter/zopu-code/issues/1",
number: 1,
state: "open",
title: "Add status control",
}));
const issue = await run(
createIssue(transport, config, {
body: "the body",
title: "Add status control",
})
);
expect(issue.number).toBe(1);
expect(issue.url).toContain("/issues/1");
});
it("maps 401 to Unauthorized", async () => {
const transport = stubTransport(() => ({}), 401);
await expect(run(listIssues(transport, config))).rejects.toMatchObject({
reason: "Unauthorized",
});
});
it("maps 404 to NotFound", async () => {
const transport = stubTransport(() => ({}), 404);
await expect(run(getIssue(transport, config, 7))).rejects.toMatchObject({
reason: "NotFound",
});
});
it("creates a branch", async () => {
const transport = stubTransport((_m, _p, body) => ({
name: (body as { new_branch_name: string }).new_branch_name,
}));
const branch = await run(
createBranch(transport, config, { name: "work/1/fix" })
);
expect(branch.name).toBe("work/1/fix");
});
it("creates a pull request and reads nested ref fields", async () => {
const transport = stubTransport(() => ({
base: { ref: "main" },
head: { ref: "work/1/fix" },
html_url: "https://git.example.com/puter/zopu-code/pulls/2",
number: 2,
state: "open",
title: "Fix thing",
}));
const pr = await run(
createPullRequest(transport, config, {
baseBranch: "main",
body: "",
branch: "work/1/fix",
title: "Fix thing",
})
);
expect(pr.number).toBe(2);
expect(pr.branch).toBe("work/1/fix");
expect(pr.baseBranch).toBe("main");
});
it("maps merged PR state", async () => {
const transport = stubTransport(() => ({
base: { ref: "main" },
head: { ref: "work/1/fix" },
merged: true,
number: 2,
state: "closed",
title: "x",
}));
const pr = await run(
createPullRequest(transport, config, {
baseBranch: "main",
body: "",
branch: "work/1/fix",
title: "x",
})
);
expect(pr.state).toBe("merged");
});
});