chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
// Local unit test for duplicate-prs.js -- mocks the GitHub client and runs the
|
||||
// real decision logic. No network. The script paginates a GraphQL search and
|
||||
// then closes/labels/comments the newer PRs for each over-subscribed issue.
|
||||
|
||||
const path = require("path");
|
||||
const script = require(path.resolve(".github/workflows/duplicate-prs.js"));
|
||||
|
||||
// Build a PR node shaped like the GraphQL response. `issues` is the list of
|
||||
// closing-issue references; `assoc` is the authorAssociation; `labels` is the
|
||||
// label name list.
|
||||
function pr({ number, createdAt, author = "ext", assoc = "CONTRIBUTOR", issues = [], labels = [] }) {
|
||||
return {
|
||||
number,
|
||||
createdAt,
|
||||
url: `https://example/pr/${number}`,
|
||||
author: { login: author },
|
||||
authorAssociation: assoc,
|
||||
labels: { nodes: labels.map((name) => ({ name })) },
|
||||
closingIssuesReferences: { nodes: issues.map((n) => ({ number: n })) },
|
||||
};
|
||||
}
|
||||
|
||||
// Run the script against a set of PR nodes; returns the side effects.
|
||||
async function run(nodes) {
|
||||
const closed = [];
|
||||
const labeled = [];
|
||||
const commented = [];
|
||||
let calls = 0;
|
||||
const github = {
|
||||
// Single page: first call returns the nodes, then stop.
|
||||
graphql: async () => {
|
||||
const done = calls++ > 0;
|
||||
return {
|
||||
rateLimit: { remaining: 4999, resetAt: "n/a" },
|
||||
search: {
|
||||
pageInfo: { hasNextPage: !done, endCursor: "c" },
|
||||
nodes: done ? [] : nodes,
|
||||
},
|
||||
};
|
||||
},
|
||||
rest: {
|
||||
pulls: {
|
||||
update: async ({ pull_number, state }) => closed.push({ pull_number, state }),
|
||||
},
|
||||
issues: {
|
||||
addLabels: async ({ issue_number, labels }) => labeled.push({ issue_number, labels }),
|
||||
createComment: async ({ issue_number, body }) => commented.push({ issue_number, body }),
|
||||
},
|
||||
},
|
||||
};
|
||||
const context = { repo: { owner: "omnigent-ai", repo: "omnigent" } };
|
||||
await script({ context, github });
|
||||
return {
|
||||
closed: closed.map((c) => c.pull_number).sort((a, b) => a - b),
|
||||
labeled: labeled.map((l) => l.issue_number).sort((a, b) => a - b),
|
||||
commented,
|
||||
};
|
||||
}
|
||||
|
||||
function assert(name, cond, detail) {
|
||||
console.log(`${cond ? "PASS" : "FAIL"} ${name}${detail ? " -- " + detail : ""}`);
|
||||
if (!cond) process.exitCode = 1;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
// 1. Two community PRs on the same issue: keep oldest (#1), close newer (#2).
|
||||
let r = await run([
|
||||
pr({ number: 1, createdAt: "2026-06-01T00:00:00Z", issues: [100] }),
|
||||
pr({ number: 2, createdAt: "2026-06-02T00:00:00Z", issues: [100] }),
|
||||
]);
|
||||
assert("closes the newer duplicate, keeps the oldest",
|
||||
JSON.stringify(r.closed) === JSON.stringify([2]) &&
|
||||
JSON.stringify(r.labeled) === JSON.stringify([2]) &&
|
||||
r.commented.length === 1 && r.commented[0].body.includes("#1"),
|
||||
JSON.stringify(r));
|
||||
|
||||
// 2. Three PRs on one issue: keep oldest, close the other two.
|
||||
r = await run([
|
||||
pr({ number: 5, createdAt: "2026-06-03T00:00:00Z", issues: [7] }),
|
||||
pr({ number: 3, createdAt: "2026-06-01T00:00:00Z", issues: [7] }),
|
||||
pr({ number: 4, createdAt: "2026-06-02T00:00:00Z", issues: [7] }),
|
||||
]);
|
||||
assert("keeps oldest of three, closes the other two",
|
||||
JSON.stringify(r.closed) === JSON.stringify([4, 5]), JSON.stringify(r));
|
||||
|
||||
// 3. Single PR per issue: nothing closed.
|
||||
r = await run([
|
||||
pr({ number: 1, createdAt: "2026-06-01T00:00:00Z", issues: [1] }),
|
||||
pr({ number: 2, createdAt: "2026-06-02T00:00:00Z", issues: [2] }),
|
||||
]);
|
||||
assert("distinct issues -> no closures", r.closed.length === 0, JSON.stringify(r));
|
||||
|
||||
// 4a. Maintainer PR (older) is the keeper -> newer community duplicate closes.
|
||||
r = await run([
|
||||
pr({ number: 1, createdAt: "2026-06-01T00:00:00Z", issues: [9], assoc: "MEMBER" }),
|
||||
pr({ number: 2, createdAt: "2026-06-02T00:00:00Z", issues: [9] }),
|
||||
]);
|
||||
assert("maintainer keeper -> newer community duplicate is closed",
|
||||
JSON.stringify(r.closed) === JSON.stringify([2]), JSON.stringify(r));
|
||||
|
||||
// 4b. Community PR (older) keeper, maintainer PR (newer) duplicate -> the
|
||||
// maintainer PR is flagged (heads-up comment + label) but never closed.
|
||||
r = await run([
|
||||
pr({ number: 1, createdAt: "2026-06-01T00:00:00Z", issues: [9] }),
|
||||
pr({ number: 2, createdAt: "2026-06-02T00:00:00Z", issues: [9], assoc: "MEMBER" }),
|
||||
]);
|
||||
assert("maintainer duplicate is flagged, not closed",
|
||||
r.closed.length === 0 &&
|
||||
JSON.stringify(r.labeled) === JSON.stringify([2]) &&
|
||||
r.commented.length === 1 &&
|
||||
r.commented[0].issue_number === 2 &&
|
||||
r.commented[0].body.includes("won't be auto-closed"),
|
||||
JSON.stringify(r));
|
||||
|
||||
// 4c. Two maintainer PRs on one issue -> neither is closed; the newer one is
|
||||
// flagged with the heads-up comment.
|
||||
r = await run([
|
||||
pr({ number: 1, createdAt: "2026-06-01T00:00:00Z", issues: [9], assoc: "OWNER" }),
|
||||
pr({ number: 2, createdAt: "2026-06-02T00:00:00Z", issues: [9], assoc: "COLLABORATOR" }),
|
||||
]);
|
||||
assert("two maintainer PRs -> none closed, newer flagged",
|
||||
r.closed.length === 0 &&
|
||||
JSON.stringify(r.labeled) === JSON.stringify([2]) &&
|
||||
r.commented.length === 1 && r.commented[0].issue_number === 2,
|
||||
JSON.stringify(r));
|
||||
|
||||
// 4d. Mixed group: maintainer keeper + two community duplicates -> both close.
|
||||
r = await run([
|
||||
pr({ number: 1, createdAt: "2026-06-01T00:00:00Z", issues: [9], assoc: "MEMBER" }),
|
||||
pr({ number: 2, createdAt: "2026-06-02T00:00:00Z", issues: [9] }),
|
||||
pr({ number: 3, createdAt: "2026-06-03T00:00:00Z", issues: [9] }),
|
||||
]);
|
||||
assert("maintainer keeper + 2 community dupes -> both community closed",
|
||||
JSON.stringify(r.closed) === JSON.stringify([2, 3]), JSON.stringify(r));
|
||||
|
||||
// 5. Already-labeled duplicate is skipped (filtered before grouping).
|
||||
r = await run([
|
||||
pr({ number: 1, createdAt: "2026-06-01T00:00:00Z", issues: [9] }),
|
||||
pr({ number: 2, createdAt: "2026-06-02T00:00:00Z", issues: [9], labels: ["duplicate"] }),
|
||||
]);
|
||||
assert("already-labeled duplicate is skipped", r.closed.length === 0, JSON.stringify(r));
|
||||
|
||||
// 6. PR referencing multiple issues is ambiguous -> skipped.
|
||||
r = await run([
|
||||
pr({ number: 1, createdAt: "2026-06-01T00:00:00Z", issues: [9] }),
|
||||
pr({ number: 2, createdAt: "2026-06-02T00:00:00Z", issues: [9, 10] }),
|
||||
]);
|
||||
assert("multi-issue PR is skipped, no duplicate group forms", r.closed.length === 0, JSON.stringify(r));
|
||||
|
||||
// 7. PR with no issue reference is ignored.
|
||||
r = await run([
|
||||
pr({ number: 1, createdAt: "2026-06-01T00:00:00Z", issues: [9] }),
|
||||
pr({ number: 2, createdAt: "2026-06-02T00:00:00Z", issues: [] }),
|
||||
]);
|
||||
assert("PR with no issue reference is ignored", r.closed.length === 0, JSON.stringify(r));
|
||||
})();
|
||||
Reference in New Issue
Block a user