Fix commit-aware PR resolution across forges
This commit is contained in:
@@ -74,6 +74,35 @@ const CONFLICTING_PR = {
|
||||
ci: "failure",
|
||||
};
|
||||
|
||||
function currentPullRequestApi(input: {
|
||||
number: number;
|
||||
state: "open" | "closed";
|
||||
headRef: string;
|
||||
headSha: string;
|
||||
merged?: boolean;
|
||||
}) {
|
||||
return {
|
||||
number: input.number,
|
||||
html_url: `https://gitea.com/example-user/sample-repo/pulls/${input.number}`,
|
||||
title: "Historical pull request",
|
||||
body: "",
|
||||
state: input.state,
|
||||
merged: input.merged ?? false,
|
||||
mergeable: true,
|
||||
updated_at: "2026-06-26T10:00:00Z",
|
||||
labels: [],
|
||||
head: {
|
||||
ref: input.headRef,
|
||||
sha: input.headSha,
|
||||
repo: { id: 1, owner: { login: "example-user" } },
|
||||
},
|
||||
base: {
|
||||
ref: "main",
|
||||
repo: { id: 1, owner: { login: "example-user" } },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const STATUS_PR_VIEW = {
|
||||
id: 161482,
|
||||
index: 5,
|
||||
@@ -1048,6 +1077,43 @@ describe("createGiteaService", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves terminal PR check details by explicit change request number", async () => {
|
||||
const headSha = "8888888888888888888888888888888888888888";
|
||||
const terminalView = {
|
||||
...STATUS_PR_VIEW,
|
||||
index: 8,
|
||||
state: "closed",
|
||||
head: "feat/recently-closed",
|
||||
headSha,
|
||||
hasMerged: true,
|
||||
mergedAt: "2026-06-28T17:00:00Z",
|
||||
};
|
||||
const { service, calls } = makeService(
|
||||
(args) => {
|
||||
if (args[0] === "pr" && args[1] === "8") return ok(JSON.stringify(terminalView));
|
||||
if (args[0] === "api" && args[1].endsWith(`/commits/${headSha}/status`)) {
|
||||
return ok(JSON.stringify(SAMPLE_COMBINED_STATUS));
|
||||
}
|
||||
throw new Error(`unexpected call: ${args.join(" ")}`);
|
||||
},
|
||||
{ resolveCurrentBranch: async () => "feat/recently-closed" },
|
||||
);
|
||||
|
||||
const details = await service.getCheckDetails({
|
||||
cwd: "/repo",
|
||||
repoOwner: "example-user",
|
||||
repoName: "sample-repo",
|
||||
checkRunId: 2,
|
||||
changeRequestNumber: 8,
|
||||
});
|
||||
|
||||
expect(details).toMatchObject({ checkRunId: 2, name: "ci/lint", status: "pending" });
|
||||
expect(calls).toEqual([
|
||||
["pr", "8", "-o", "json"],
|
||||
["api", `repos/example-user/sample-repo/commits/${headSha}/status`],
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves Gitea Actions check details addressed only by workflowRunId", async () => {
|
||||
const { service } = makeService(
|
||||
(args) => {
|
||||
@@ -1172,6 +1238,9 @@ describe("createGiteaService", () => {
|
||||
const { service, calls } = makeService(
|
||||
(args) => {
|
||||
if (args[0] === "pr" && args[1] === "list") return ok(JSON.stringify([]));
|
||||
if (args[0] === "api" && args[1].includes("/pulls?state=all")) {
|
||||
return ok(JSON.stringify([]));
|
||||
}
|
||||
throw new Error(`unexpected call: ${args.join(" ")}`);
|
||||
},
|
||||
{
|
||||
@@ -1187,13 +1256,9 @@ describe("createGiteaService", () => {
|
||||
checkRunId: 2,
|
||||
}),
|
||||
).rejects.toThrow("Gitea pull request for branch feat/nonexistent was not found");
|
||||
expect(
|
||||
calls
|
||||
.filter((args) => args[0] === "pr" && args[1] === "list")
|
||||
.map((args) => [argValue(args, "--state"), argValue(args, "--page")]),
|
||||
).toEqual([
|
||||
["open", "1"],
|
||||
["all", undefined],
|
||||
expect(calls[1]).toEqual([
|
||||
"api",
|
||||
"repos/example-user/sample-repo/pulls?state=all&sort=recentupdate&page=1&limit=50",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1337,7 +1402,7 @@ describe("createGiteaService", () => {
|
||||
expect(status?.checksStatus).toBe("failure");
|
||||
});
|
||||
|
||||
it("finds the current branch PR on the second open pull-request page", async () => {
|
||||
it("finds the open current-branch PR even when its remote head SHA differs", async () => {
|
||||
const firstPage = Array.from({ length: 50 }, (_, index) => ({
|
||||
...OPEN_PR,
|
||||
index: String(100 + index),
|
||||
@@ -1367,6 +1432,7 @@ describe("createGiteaService", () => {
|
||||
const status = await service.getCurrentPullRequestStatus({
|
||||
cwd: "/repo",
|
||||
headRef: "feat/sample-change",
|
||||
headSha: "9999999999999999999999999999999999999999",
|
||||
});
|
||||
|
||||
expect(status?.number).toBe(5);
|
||||
@@ -1381,18 +1447,18 @@ describe("createGiteaService", () => {
|
||||
});
|
||||
|
||||
it("falls back to the recent all-state PR window for a recently closed current branch PR", async () => {
|
||||
const closedPr = {
|
||||
...OPEN_PR,
|
||||
index: "8",
|
||||
const headSha = "8888888888888888888888888888888888888888";
|
||||
const closedPr = currentPullRequestApi({
|
||||
number: 8,
|
||||
state: "closed",
|
||||
url: "https://gitea.com/example-user/sample-repo/pulls/8",
|
||||
head: "feat/recently-closed",
|
||||
};
|
||||
headRef: "feat/recently-closed",
|
||||
headSha,
|
||||
});
|
||||
const { service, calls } = makeService((args) => {
|
||||
if (args[0] === "pr" && args[1] === "list" && argValue(args, "--state") === "open") {
|
||||
return ok(JSON.stringify([]));
|
||||
}
|
||||
if (args[0] === "pr" && args[1] === "list" && argValue(args, "--state") === "all") {
|
||||
if (args[0] === "api" && args[1].includes("/pulls?state=all")) {
|
||||
return ok(JSON.stringify([closedPr]));
|
||||
}
|
||||
throw new Error(`unexpected call: ${args.join(" ")}`);
|
||||
@@ -1401,22 +1467,79 @@ describe("createGiteaService", () => {
|
||||
const status = await service.getCurrentPullRequestStatus({
|
||||
cwd: "/repo",
|
||||
headRef: "feat/recently-closed",
|
||||
headSha,
|
||||
});
|
||||
|
||||
expect(status).toMatchObject({ number: 8, headRefName: "feat/recently-closed" });
|
||||
expect(
|
||||
calls
|
||||
.filter((args) => args[0] === "pr" && args[1] === "list")
|
||||
.map((args) => [argValue(args, "--state"), argValue(args, "--page")]),
|
||||
).toEqual([
|
||||
["open", "1"],
|
||||
["all", undefined],
|
||||
expect(calls[1]).toEqual([
|
||||
"api",
|
||||
"repos/example-user/sample-repo/pulls?state=all&sort=recentupdate&page=1&limit=50",
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses tea repository context when origin identity is unavailable", async () => {
|
||||
const headSha = "8888888888888888888888888888888888888888";
|
||||
const closedPr = currentPullRequestApi({
|
||||
number: 8,
|
||||
state: "closed",
|
||||
headRef: "feat/recently-closed",
|
||||
headSha,
|
||||
});
|
||||
const { service, calls } = makeService(
|
||||
(args) => {
|
||||
if (args[0] === "pr" && args[1] === "list") return ok("[]");
|
||||
if (args[0] === "api" && args[1].startsWith("repos/{owner}/{repo}/pulls?")) {
|
||||
return ok(JSON.stringify([closedPr]));
|
||||
}
|
||||
throw new Error(`unexpected call: ${args.join(" ")}`);
|
||||
},
|
||||
{ resolveRemoteUrl: async () => null },
|
||||
);
|
||||
|
||||
const status = await service.getCurrentPullRequestStatus({
|
||||
cwd: "/repo",
|
||||
headRef: "feat/recently-closed",
|
||||
headSha,
|
||||
});
|
||||
|
||||
expect(status).toMatchObject({ number: 8, headRefName: "feat/recently-closed" });
|
||||
expect(calls[1]).toEqual([
|
||||
"api",
|
||||
"repos/{owner}/{repo}/pulls?state=all&sort=recentupdate&page=1&limit=50",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not attach a stale Gitea-family PR after a same-name branch advances", async () => {
|
||||
const stale = currentPullRequestApi({
|
||||
number: 8,
|
||||
state: "closed",
|
||||
headRef: "dev",
|
||||
headSha: "1111111111111111111111111111111111111111",
|
||||
merged: true,
|
||||
});
|
||||
const { service } = makeService((args) => {
|
||||
if (args[0] === "pr" && args[1] === "list") return ok("[]");
|
||||
if (args[0] === "api" && args[1].includes("/pulls?state=all")) {
|
||||
return ok(JSON.stringify([stale]));
|
||||
}
|
||||
throw new Error(`unexpected call: ${args.join(" ")}`);
|
||||
});
|
||||
|
||||
await expect(
|
||||
service.getCurrentPullRequestStatus({
|
||||
cwd: "/repo",
|
||||
headRef: "dev",
|
||||
headSha: "2222222222222222222222222222222222222222",
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when no PR matches the current branch", async () => {
|
||||
const { service, calls } = makeService((args) => {
|
||||
if (args[0] === "pr" && args[1] === "list") return ok(JSON.stringify([]));
|
||||
if (args[0] === "api" && args[1].includes("/pulls?state=all")) {
|
||||
return ok(JSON.stringify([]));
|
||||
}
|
||||
throw new Error(`unexpected call: ${args.join(" ")}`);
|
||||
});
|
||||
|
||||
@@ -1426,13 +1549,9 @@ describe("createGiteaService", () => {
|
||||
});
|
||||
|
||||
expect(status).toBeNull();
|
||||
expect(
|
||||
calls
|
||||
.filter((args) => args[0] === "pr" && args[1] === "list")
|
||||
.map((args) => [argValue(args, "--state"), argValue(args, "--page")]),
|
||||
).toEqual([
|
||||
["open", "1"],
|
||||
["all", undefined],
|
||||
expect(calls[1]).toEqual([
|
||||
"api",
|
||||
"repos/example-user/sample-repo/pulls?state=all&sort=recentupdate&page=1&limit=50",
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user