158 lines
6.2 KiB
TypeScript
158 lines
6.2 KiB
TypeScript
import { afterEach, describe, expect, test } from "bun:test";
|
|
import { withGhDefaultRendered } from "./gh/default-render";
|
|
import { ghScopedHelp } from "./gh/help";
|
|
import { prReady } from "./gh/pr-ready";
|
|
import type { GitHubPullRequest } from "./gh/types";
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
|
|
function pullRequest(overrides: Partial<GitHubPullRequest> = {}): GitHubPullRequest {
|
|
return {
|
|
id: 1748,
|
|
node_id: "PR_kwDO_ready_1748",
|
|
number: 1748,
|
|
title: "PaC source artifact",
|
|
body: "body",
|
|
state: "open",
|
|
html_url: "https://github.com/pikasTech/unidesk/pull/1748",
|
|
draft: true,
|
|
head: { ref: "fix/1746-pac-source-artifact", sha: "a".repeat(40) },
|
|
base: { ref: "master", sha: "b".repeat(40) },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function installFetch(handler: (url: URL, init: RequestInit | undefined) => Response | Promise<Response>): string[] {
|
|
const calls: string[] = [];
|
|
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
const url = new URL(typeof input === "string" || input instanceof URL ? input.toString() : input.url);
|
|
calls.push(`${init?.method ?? "GET"} ${url.pathname}`);
|
|
return handler(url, init);
|
|
}) as typeof fetch;
|
|
return calls;
|
|
}
|
|
|
|
describe("gh pr ready", () => {
|
|
test("marks an open draft PR ready through the shared GraphQL client", async () => {
|
|
const calls = installFetch(async (url, init) => {
|
|
if (url.pathname.endsWith("/pulls/1748")) return Response.json(pullRequest());
|
|
expect(url.pathname).toBe("/graphql");
|
|
expect(new Headers(init?.headers).get("authorization")).toBe("Bearer test-token");
|
|
const payload = JSON.parse(String(init?.body)) as { query: string; variables: { pullRequestId: string } };
|
|
expect(payload.query).toContain("markPullRequestReadyForReview");
|
|
expect(payload.variables.pullRequestId).toBe("PR_kwDO_ready_1748");
|
|
return Response.json({
|
|
data: {
|
|
markPullRequestReadyForReview: {
|
|
pullRequest: {
|
|
id: "PR_kwDO_ready_1748",
|
|
number: 1748,
|
|
title: "PaC source artifact",
|
|
state: "OPEN",
|
|
isDraft: false,
|
|
url: "https://github.com/pikasTech/unidesk/pull/1748",
|
|
headRefName: "fix/1746-pac-source-artifact",
|
|
baseRefName: "master",
|
|
updatedAt: "2026-07-11T04:30:00Z",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
const result = await prReady("pikasTech/unidesk", "test-token", 1748, false);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.action).toBe("marked-ready");
|
|
expect(result.changed).toBe(true);
|
|
expect(result.graphQl).toBe(true);
|
|
expect((result.pullRequest as { draft?: boolean }).draft).toBe(false);
|
|
expect(calls).toEqual(["GET /repos/pikasTech/unidesk/pulls/1748", "POST /graphql"]);
|
|
});
|
|
|
|
test("is idempotent when the open PR is already ready", async () => {
|
|
const calls = installFetch(() => Response.json(pullRequest({ draft: false })));
|
|
|
|
const result = await prReady("pikasTech/unidesk", "test-token", 1748, false);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.action).toBe("already-ready");
|
|
expect(result.changed).toBe(false);
|
|
expect(result.graphQl).toBe(false);
|
|
expect(calls).toEqual(["GET /repos/pikasTech/unidesk/pulls/1748"]);
|
|
const rendered = withGhDefaultRendered(["pr", "ready", "1748"], result) as { renderedText: string };
|
|
expect(rendered.renderedText).toContain("gh pr ready (already-ready)");
|
|
});
|
|
|
|
test("dry-run reads the PR and exposes a bounded mutation plan without writing", async () => {
|
|
const calls = installFetch(() => Response.json(pullRequest()));
|
|
|
|
const result = await prReady("pikasTech/unidesk", "test-token", 1748, true);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.action).toBe("would-mark-ready");
|
|
expect(result.planned).toBe(true);
|
|
expect(result.changed).toBe(false);
|
|
expect(result.request).toEqual({
|
|
method: "POST",
|
|
path: "/graphql",
|
|
mutation: "markPullRequestReadyForReview",
|
|
variables: { pullRequestId: "present" },
|
|
});
|
|
expect(calls).toEqual(["GET /repos/pikasTech/unidesk/pulls/1748"]);
|
|
});
|
|
|
|
test("rejects a closed PR before mutation with a typed validation error", async () => {
|
|
const calls = installFetch(() => Response.json(pullRequest({ state: "closed" })));
|
|
|
|
const result = await prReady("pikasTech/unidesk", "test-token", 1748, false);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.degradedReason).toBe("validation-failed");
|
|
expect(result.runnerDisposition).toBe("business-failed");
|
|
expect(result.phase).toBe("validate-pr-state");
|
|
expect(calls).toEqual(["GET /repos/pikasTech/unidesk/pulls/1748"]);
|
|
});
|
|
|
|
test("preserves the typed pr-not-found REST failure", async () => {
|
|
installFetch(() => Response.json({ message: "Not Found" }, { status: 404 }));
|
|
|
|
const result = await prReady("pikasTech/unidesk", "test-token", 404, false);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.degradedReason).toBe("pr-not-found");
|
|
expect(result.runnerDisposition).toBe("business-failed");
|
|
expect(result.phase).toBe("fetch-pr");
|
|
});
|
|
|
|
test("preserves typed mutation permission failures", async () => {
|
|
installFetch((url) => {
|
|
if (url.pathname.endsWith("/pulls/1748")) return Response.json(pullRequest());
|
|
return Response.json(
|
|
{ message: "Resource not accessible by integration" },
|
|
{ status: 403, headers: { "x-accepted-oauth-scopes": "repo" } },
|
|
);
|
|
});
|
|
|
|
const result = await prReady("pikasTech/unidesk", "test-token", 1748, false);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.degradedReason).toBe("scope-insufficient");
|
|
expect(result.runnerDisposition).toBe("infra-blocked");
|
|
expect(result.phase).toBe("mark-ready");
|
|
});
|
|
|
|
test("publishes bounded scoped help for the command", () => {
|
|
const help = ghScopedHelp(["pr", "ready", "--help"]) as { usage: string[]; notes: string[] };
|
|
expect(help.usage).toEqual([
|
|
"bun scripts/cli.ts gh pr ready <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--dry-run]",
|
|
]);
|
|
expect(help.notes.join("\n")).toContain("markPullRequestReadyForReview");
|
|
expect(help.notes.join("\n")).toContain("不改变 GitHub 状态");
|
|
});
|
|
});
|