mirror of
https://github.com/Heretek-OpenCode/heretek-opencode.git
synced 2026-11-09 02:29:40 -10:00
- distro: Dockerfile (multi-arch Bun, :4096, unprivileged, /api/info probe), compat.json (sdk 1.0.0 / host 2.0.26 / gateway 1.0.0), sync.ts drift report, README - sdk: backwards-compatible contract expansion (Lane, GateCheck, DiffBudget, MCPToolResult, PackManifest, ReadinessResult, JudgeReport, JournalEntry), typed host client with wait->poll fallback, deterministic gate runner (sha256 outputs, timeout as exit 124, budget check), testkit mocks, 24 tests green
70 lines
3.1 KiB
TypeScript
70 lines
3.1 KiB
TypeScript
import { afterEach, describe, expect, test } from "bun:test";
|
|
import { HostClient, HostClientError } from "../src/host/client.js";
|
|
import { startMockHost, type MockHost } from "../src/testkit/index.js";
|
|
|
|
let mock: MockHost | null = null;
|
|
afterEach(async () => {
|
|
if (mock) {
|
|
await mock.close();
|
|
mock = null;
|
|
}
|
|
});
|
|
|
|
describe("host client", () => {
|
|
test("prompt posts text and returns inbox id", async () => {
|
|
mock = startMockHost([
|
|
{ method: "POST", path: "/api/session/s1/prompt", status: 200, body: { data: { id: "inbox-1" } } },
|
|
]);
|
|
const client = new HostClient({ baseUrl: mock.url });
|
|
const res = await client.prompt("s1", "hello");
|
|
expect(res.inboxId).toBe("inbox-1");
|
|
expect(mock.requests[0]).toMatchObject({ method: "POST", path: "/api/session/s1/prompt" });
|
|
});
|
|
|
|
test("wait uses experimental endpoint when present", async () => {
|
|
mock = startMockHost([
|
|
{ method: "POST", path: "/api/experimental/session/s1/wait", status: 204, body: null },
|
|
]);
|
|
const client = new HostClient({ baseUrl: mock.url });
|
|
await client.wait("s1", 5000);
|
|
expect(mock.requests.map((r) => r.path)).toEqual(["/api/experimental/session/s1/wait"]);
|
|
});
|
|
|
|
test("wait falls back to polling message list when wait endpoint is missing", async () => {
|
|
const messages = [{ id: "m1", role: "user" }];
|
|
const fetchImpl: typeof fetch = (async (url: string | URL | Request, init?: RequestInit): Promise<Response> => {
|
|
const u = new URL(typeof url === "string" ? url : url instanceof URL ? url.toString() : (url as Request).url);
|
|
if (u.pathname === "/api/experimental/session/s1/wait") return new Response("nope", { status: 404 });
|
|
if (u.pathname === "/api/session/s1/message") {
|
|
return Response.json({ data: (init as unknown) ? messages : messages });
|
|
}
|
|
return new Response("not found", { status: 404 });
|
|
}) as typeof fetch;
|
|
const client = new HostClient({ baseUrl: "http://x", fetchImpl, pollIntervalMs: 10 });
|
|
await client.wait("s1", 2000);
|
|
});
|
|
|
|
test("diff returns file diffs", async () => {
|
|
const diffs = [{ file: "a.ts", patch: "@@..", additions: 1, deletions: 0, status: "modified" }];
|
|
mock = startMockHost([{ method: "GET", path: "/api/session/s1/diff", status: 200, body: { data: diffs } }]);
|
|
const client = new HostClient({ baseUrl: mock.url });
|
|
expect(await client.diff("s1")).toEqual(diffs);
|
|
});
|
|
|
|
test("interrupt + listMessages", async () => {
|
|
mock = startMockHost([
|
|
{ method: "POST", path: "/api/session/s1/interrupt", status: 200, body: { data: true } },
|
|
{ method: "GET", path: "/api/session/s1/message", status: 200, body: { data: [{ id: "m1", role: "user" }] } },
|
|
]);
|
|
const client = new HostClient({ baseUrl: mock.url });
|
|
await client.interrupt("s1");
|
|
expect(await client.listMessages("s1")).toHaveLength(1);
|
|
});
|
|
|
|
test("HTTP errors surface as HostClientError", async () => {
|
|
mock = startMockHost([{ method: "GET", path: "/api/session/nope/diff", status: 404, body: { error: "gone" } }]);
|
|
const client = new HostClient({ baseUrl: mock.url });
|
|
await expect(client.diff("nope")).rejects.toBeInstanceOf(HostClientError);
|
|
});
|
|
});
|