Files
openclaw/test/scripts/gh-read.test.ts
2026-05-27 14:33:06 +02:00

90 lines
2.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
buildReadPermissions,
githubJson,
normalizeRepo,
parsePermissionKeys,
parseRepoArg,
resolveGitHubFetchTimeoutMs,
} from "../../scripts/gh-read.js";
describe("gh-read helpers", () => {
it("finds repo from gh args", () => {
expect(parseRepoArg(["pr", "view", "42", "-R", "openclaw/openclaw"])).toBe("openclaw/openclaw");
expect(parseRepoArg(["run", "list", "--repo=openclaw/docs"])).toBe("openclaw/docs");
expect(parseRepoArg(["pr", "view", "42"])).toBeNull();
});
it("normalizes repo strings from common git formats", () => {
expect(normalizeRepo("openclaw/openclaw")).toBe("openclaw/openclaw");
expect(normalizeRepo("github.com/openclaw/openclaw")).toBe("openclaw/openclaw");
expect(normalizeRepo("https://github.com/openclaw/openclaw.git")).toBe("openclaw/openclaw");
expect(normalizeRepo("git@github.com:openclaw/openclaw.git")).toBe("openclaw/openclaw");
expect(normalizeRepo("invalid")).toBeNull();
});
it("builds a read-only permission subset from granted permissions", () => {
expect(
buildReadPermissions(
{
actions: "write",
issues: "read",
administration: "write",
metadata: "read",
statuses: null,
},
["actions", "issues", "metadata", "statuses", "administration"],
),
).toEqual({
administration: "read",
actions: "read",
issues: "read",
metadata: "read",
});
});
it("parses permission key overrides", () => {
expect(parsePermissionKeys(undefined)).toContain("pull_requests");
expect(parsePermissionKeys("actions, contents ,issues")).toEqual([
"actions",
"contents",
"issues",
]);
});
it("aborts stalled GitHub API fetches at the request timeout", async () => {
let signal: AbortSignal | undefined;
const request = githubJson("/app", "token", undefined, {
timeoutMs: 5,
fetchImpl: ((_url, init) => {
signal = init?.signal ?? undefined;
return new Promise(() => {});
}) as typeof fetch,
});
await expect(request).rejects.toThrow(/GitHub API GET \/app exceeded timeout/u);
expect(signal?.aborted).toBe(true);
});
it("times out stalled GitHub API response body reads", async () => {
const response = {
ok: true,
status: 200,
json: () => new Promise(() => {}),
} as Response;
const request = githubJson("/app/installations", "token", undefined, {
timeoutMs: 5,
fetchImpl: (() => Promise.resolve(response)) as typeof fetch,
});
await expect(request).rejects.toThrow(/GitHub API GET \/app\/installations exceeded timeout/u);
});
it("rejects invalid GitHub API timeout values", () => {
expect(resolveGitHubFetchTimeoutMs("1000")).toBe(1000);
expect(() => resolveGitHubFetchTimeoutMs("1s")).toThrow(
/OPENCLAW_GH_READ_FETCH_TIMEOUT_MS must be an integer/u,
);
});
});