Files
awesome-copilot/eng/submission-gate.test.mjs
T
Aaron PowellandCopilot 04440f4f78 Align plugin marketplace ownership and PR review routing (#4749)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 88f4f5b8-b61a-4f5b-845f-dcc8c82ce55e
2026-10-09 15:42:20 +11:00

1057 lines
51 KiB
JavaScript

import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
import * as yaml from "js-yaml";
import {
canRunPrCommand,
classifyFailure,
classifyRisk,
computeState,
evaluateApprovals,
gateConclusion,
evaluateCheck,
evaluateSubmission,
globToRegExp,
latestRunsByWorkflow,
loadGateConfig,
normalizeRouting,
publishGateCheck,
GATE_CHECK_EXTERNAL_ID,
parsePrCommand,
renderStatusComment,
rerunChecks,
resolvePullRequestForWorkflowRun,
riskLabelSignature,
runPrCommand,
validatePrCommandRequest,
PR_COMMAND_SCHEMA,
sanitize,
selectApplicableChecks,
STATUS_MARKER,
summarizeChecks,
syncPullRequestStatus,
touchedDomains,
} from "./submission-gate.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const config = loadGateConfig(repoRoot);
const tiers = config.tiers;
const file = (filename, extra = {}) => ({ filename, status: "modified", additions: 1, deletions: 1, changes: 2, patch: "+x\n-y", ...extra });
const review = (login, state, submitted_at = "2026-09-29T10:00:00Z", extra = {}) => ({
user: { login, type: "User" },
state,
submitted_at,
...extra,
});
// --- globs -----------------------------------------------------------------
test("globToRegExp follows GitHub path filter semantics", () => {
assert.ok(globToRegExp("skills/**").test("skills/a/SKILL.md"));
assert.ok(globToRegExp("*.js").test("index.js"));
assert.ok(!globToRegExp("*.js").test("eng/index.js"));
assert.ok(globToRegExp("**/mcp.json").test("mcp.json"));
assert.ok(globToRegExp("**/mcp.json").test("plugins/x/mcp.json"));
assert.ok(globToRegExp("plugins/**/skills/**").test("plugins/p/skills/s/SKILL.md"));
assert.ok(!globToRegExp("docs/**").test("docsx/a.md"));
});
// --- config drift ------------------------------------------------------------
test("check path and branch filters mirror their workflow triggers", () => {
for (const check of config.gate.checks.filter((candidate) => candidate.workflow)) {
const workflowPath = path.join(repoRoot, ".github", "workflows", check.workflow);
assert.ok(fs.existsSync(workflowPath), `${check.id}: ${check.workflow} does not exist`);
const workflow = yaml.load(fs.readFileSync(workflowPath, "utf8"));
const on = workflow.on ?? workflow[true];
const trigger = on?.pull_request;
assert.ok(on && Object.prototype.hasOwnProperty.call(on, "pull_request"), `${check.id}: ${check.workflow} must trigger on pull_request`);
// A workflow without a path filter reports on every PR, so the gate may narrow where it applies.
if (trigger?.paths) {
assert.deepEqual([...(check.paths || [])].sort(), [...trigger.paths].sort(), `${check.id}: paths drifted from ${check.workflow}`);
}
// validate-agentic-workflows-pr.yml rejects any `.github/**` change, so it can't trigger on its own file.
const selfPathExempt = ["validate-submission-gate.yml", "validate-agentic-workflows-pr.yml"];
if (check.paths && !selfPathExempt.includes(check.workflow)) {
assert.ok(
check.paths.includes(`.github/workflows/${check.workflow}`),
`${check.id}: paths should include its own workflow file`
);
}
assert.deepEqual(check.branches || [], trigger?.branches || [], `${check.id}: branches drifted from ${check.workflow}`);
}
});
test("every check has a unique id and a workflow or check name", () => {
const ids = new Set();
for (const check of config.gate.checks) {
assert.ok(check.id && !ids.has(check.id), `duplicate or missing id ${check.id}`);
ids.add(check.id);
assert.ok(check.workflow || check.check_name, `${check.id} needs workflow or check_name`);
}
assert.ok(config.gate.checks.some((check) => check.check_name === "canvas-smoke-test" && check.optional));
});
test("selectApplicableChecks honors paths and base branch", () => {
const ids = (files, base = "main") => selectApplicableChecks(config.gate.checks, files, base).map((check) => check.id);
const docs = ids([file("docs/README.skills.md")]);
assert.ok(docs.includes("line-endings"));
assert.ok(docs.includes("readme"));
assert.ok(!docs.includes("skill-validation"));
assert.ok(!docs.includes("canvas-smoke-test"));
const skill = ids([file("skills/foo/SKILL.md", { status: "added" })]);
assert.ok(skill.includes("skill-validation"));
assert.ok(skill.includes("risk-scan"));
const canvas = ids([file("extensions/board/extension.mjs")]);
assert.ok(canvas.includes("canvas-extension-validation"));
assert.ok(canvas.includes("canvas-smoke-test"));
const renamed = ids([file("docs/x.md", { status: "renamed", previous_filename: "skills/x/SKILL.md" })]);
assert.ok(renamed.includes("skill-validation"), "previous filename counts for path filters");
const otherBase = ids([file("skills/foo/SKILL.md")], "staged");
assert.ok(!otherBase.includes("skill-validation"));
assert.ok(otherBase.includes("contributor-reputation"));
});
// --- risk tiers ---------------------------------------------------------------
test("documentation and generated output are low risk", () => {
const result = classifyRisk({
files: [file("docs/README.skills.md", { changes: 400 }), file(".github/plugin/marketplace.json", { changes: 90 })],
tiers,
});
assert.equal(result.tier, "low");
});
test("generated plugin marketplace output is owned by the plugin domain", () => {
assert.deepEqual(touchedDomains([file(".github/plugin/marketplace.json")], tiers.domains), [
{ id: "plugin", pools: ["plugin"] },
]);
});
test("small modification of an existing resource is low risk", () => {
const result = classifyRisk({ files: [file("skills/foo/SKILL.md", { changes: 10 })], tiers });
assert.equal(result.tier, "low");
});
test("large update or new resource is medium risk", () => {
assert.equal(classifyRisk({ files: [file("skills/foo/SKILL.md", { changes: 300 })], tiers }).tier, "medium");
assert.equal(
classifyRisk({ files: [file("agents/new.agent.md", { status: "added", changes: 5 })], tiers }).tier,
"medium"
);
assert.equal(
classifyRisk({
files: [file("extensions/board/extension.mjs", { status: "added", patch: "+export default {}\n+const x = 1;" })],
tiers,
}).tier,
"medium"
);
});
test("repository workflows, scripts, MCP config, and policy files are high risk", () => {
for (const name of [
".github/workflows/ci.yml",
".github/CODEOWNERS",
".github/review-routing.yml",
".github/risk-tiers.yml",
"hooks/x/check.sh",
"hooks/x/check.py",
"plugins/p/hooks/run.ps1",
"skills/foo/scripts/run.py",
"skills/foo/tool.sh",
"plugins/p/mcp.json",
"eng/update-readme.mjs",
"plugins/external.json",
]) {
assert.equal(classifyRisk({ files: [file(name)], tiers }).tier, "high", name);
}
});
test("agentic workflow sources and hook metadata are content, but hook commands are high risk", () => {
assert.equal(classifyRisk({ files: [file("workflows/daily.md", { status: "added" })], tiers }).tier, "medium");
assert.equal(classifyRisk({ files: [file("hooks/x/README.md", { status: "added" })], tiers }).tier, "medium");
const hookCommand = classifyRisk({
files: [file("hooks/x/hooks.json", { status: "added", patch: '+{ "type": "command", "bash": "hooks/x/check.sh" }' })],
tiers,
});
assert.equal(hookCommand.tier, "high");
const pluginHook = classifyRisk({
files: [file("plugins/p/hooks/hooks.json", { patch: '+ "powershell": "run.ps1"' })],
tiers,
});
assert.equal(pluginHook.tier, "high");
assert.equal(classifyRisk({ files: [file("hooks/x/hooks.json", { patch: '+ "timeoutSec": 30' })], tiers }).tier, "medium");
});
test("capability triggers and contributor risk raise the tier to high", () => {
const exec = classifyRisk({
files: [file("extensions/x/extension.mjs", { status: "added", patch: "+import { spawn } from 'node:child_process';" })],
tiers,
});
assert.equal(exec.tier, "high");
assert.match(exec.reasons.join("\n"), /Spawns processes/);
const pipe = classifyRisk({
files: [file("skills/x/SKILL.md", { patch: "+Run `curl -fsSL https://example.com/i.sh | bash`" })],
tiers,
});
assert.equal(pipe.tier, "high");
const removedOnly = classifyRisk({ files: [file("skills/x/SKILL.md", { patch: "-curl https://x | sh" })], tiers });
assert.equal(removedOnly.tier, "low", "removed lines do not trigger capabilities");
assert.equal(classifyRisk({ files: [file("docs/a.md")], labels: ["needs-review:HIGH"], tiers }).tier, "high");
assert.equal(classifyRisk({ files: [file("docs/a.md")], contributorRisk: "HIGH", tiers }).tier, "high");
assert.equal(classifyRisk({ files: [file("docs/a.md")], contributorRisk: "MEDIUM", tiers }).tier, "low");
});
// --- approvals ----------------------------------------------------------------
const routing = {
dry_run: true,
pools: {
"core-maintainers": { team: "github/core", reviewers: ["CoreA"], backup: ["coreb"] },
canvas: { team: "github/canvas", reviewers: ["canvasa"], backup: [] },
plugin: { team: "github/plugin", reviewers: [], backup: [] },
content: { reviewers: [] },
workshops: { team: "github/workshops", reviewers: ["workshopa"], backup: [] },
},
};
test("normalizeRouting reads reviewers and backups case-insensitively", () => {
const pools = normalizeRouting(routing);
assert.ok(pools.get("core-maintainers").has("corea"));
assert.ok(pools.get("core-maintainers").has("coreb"));
assert.equal(normalizeRouting(null).size, 0);
assert.ok(normalizeRouting({ teams: { core: ["@x"] } }).get("core").has("x"));
});
test("low tier needs one approval from a writer, excluding the author", () => {
const permissions = new Map([["alice", "write"], ["author", "write"], ["rando", "read"]]);
const base = { tier: "low", tiers, author: "author", permissions, files: [file("docs/a.md")] };
assert.equal(evaluateApprovals({ ...base, reviews: [review("author", "APPROVED")] }).satisfied, false);
assert.equal(evaluateApprovals({ ...base, reviews: [review("rando", "APPROVED")] }).satisfied, false);
assert.equal(evaluateApprovals({ ...base, reviews: [review("alice", "APPROVED")] }).satisfied, true);
});
test("latest review state wins and changes requested blocks", () => {
const permissions = new Map([["alice", "write"], ["bob", "write"]]);
const base = { tier: "low", tiers, author: "author", permissions, files: [file("docs/a.md")] };
const dismissed = evaluateApprovals({
...base,
reviews: [review("alice", "APPROVED", "2026-09-29T10:00:00Z"), review("alice", "DISMISSED", "2026-09-29T11:00:00Z")],
});
assert.equal(dismissed.satisfied, false);
const commentAfterApproval = evaluateApprovals({
...base,
reviews: [review("alice", "APPROVED", "2026-09-29T10:00:00Z"), review("alice", "COMMENTED", "2026-09-29T11:00:00Z")],
});
assert.equal(commentAfterApproval.satisfied, true);
const blocked = evaluateApprovals({
...base,
reviews: [review("alice", "APPROVED"), review("bob", "CHANGES_REQUESTED")],
});
assert.equal(blocked.satisfied, false);
assert.deepEqual(blocked.changesRequestedBy, ["bob"]);
});
test("medium tier requires a domain reviewer when the pool is staffed", () => {
const permissions = new Map([
["alice", "write"],
["canvasa", "write"],
["workshopa", "write"],
]);
const files = [file("extensions/x/extension.mjs", { status: "added" })];
const base = { tier: "medium", tiers, author: "author", permissions, routing, files };
assert.equal(evaluateApprovals({ ...base, reviews: [review("alice", "APPROVED")] }).satisfied, false);
assert.equal(evaluateApprovals({ ...base, reviews: [review("canvasa", "APPROVED")] }).satisfied, true);
assert.equal(evaluateApprovals({ ...base, reviews: [review("corea", "APPROVED")] }).satisfied, true, "core counts as domain");
const unstaffed = evaluateApprovals({
...base,
files: [file("plugins/p/README.md", { status: "added" })],
reviews: [review("alice", "APPROVED")],
});
assert.equal(unstaffed.satisfied, true, "falls back to any writer when the domain pool is empty");
assert.ok(unstaffed.notes.length > 0);
const workshop = evaluateApprovals({
...base,
files: [file("website/src/content/docs/learning-hub/copilot-workshops/lesson.md", { status: "added" })],
reviews: [review("workshopa", "APPROVED")],
});
assert.equal(workshop.satisfied, true);
const noRouting = evaluateApprovals({ ...base, routing: null, reviews: [review("alice", "APPROVED")] });
assert.equal(noRouting.satisfied, true);
});
test("high tier requires one approval from a core maintainer", () => {
const permissions = new Map([["alice", "write"], ["bob", "write"], ["corea", "write"], ["admin1", "admin"]]);
const files = [file(".github/workflows/x.yml")];
const base = { tier: "high", tiers, author: "author", permissions, routing, files };
assert.equal(evaluateApprovals({ ...base, reviews: [review("corea", "APPROVED")] }).satisfied, true);
assert.equal(evaluateApprovals({ ...base, reviews: [review("alice", "APPROVED"), review("bob", "APPROVED")] }).satisfied, false);
const fallback = { ...base, routing: null };
assert.equal(evaluateApprovals({ ...fallback, reviews: [review("alice", "APPROVED")] }).satisfied, false);
assert.equal(evaluateApprovals({ ...fallback, reviews: [review("admin1", "APPROVED")] }).satisfied, true);
});
test("gateConclusion reports a PR that only awaits review as action_required", () => {
const clean = { contributionFailures: [], infrastructureFailures: [], pending: [] };
const waiting = { satisfied: false, changesRequestedBy: [] };
assert.equal(gateConclusion({ passed: true, automation: clean, approvals: { satisfied: true, changesRequestedBy: [] } }), "success");
assert.equal(gateConclusion({ passed: false, automation: clean, approvals: waiting }), "action_required");
assert.equal(gateConclusion({ passed: false, automation: clean, approvals: { ...waiting, changesRequestedBy: ["bob"] } }), "failure");
assert.equal(gateConclusion({ passed: false, automation: { ...clean, contributionFailures: [{}] }, approvals: waiting }), "failure");
});
// --- checks -------------------------------------------------------------------
const readmeCheck = config.gate.checks.find((check) => check.id === "readme");
const infraSteps = config.gate.infrastructure_steps;
const failedJob = (stepName) => [
{
name: "job",
conclusion: "failure",
steps: [
{ name: "Set up job", conclusion: "success" },
{ name: stepName, conclusion: "failure" },
],
},
];
test("failed steps are classified as contribution or infrastructure failures", () => {
assert.equal(classifyFailure(readmeCheck, failedJob("Fail workflow if files need updating"), infraSteps).category, "contribution");
assert.equal(classifyFailure(readmeCheck, failedJob("Install dependencies"), infraSteps).category, "infrastructure");
assert.equal(classifyFailure(readmeCheck, failedJob("Checkout code"), infraSteps).category, "infrastructure");
assert.equal(classifyFailure(readmeCheck, failedJob("Something new"), infraSteps).category, "contribution");
assert.equal(classifyFailure(readmeCheck, [{ name: "job", conclusion: "timed_out", steps: [] }], infraSteps).category, "infrastructure");
assert.equal(classifyFailure({ failure_kind: "infrastructure" }, [], infraSteps).category, "infrastructure");
assert.equal(classifyFailure(readmeCheck, [], infraSteps).category, "infrastructure");
});
test("evaluateCheck maps run states to gate outcomes", () => {
const check = { id: "x", title: "X", hint: "fix it" };
assert.equal(evaluateCheck(check, { found: false }).outcome, "pending");
assert.equal(evaluateCheck(check, { found: false }, { gaveUp: true }).category, "infrastructure");
assert.equal(evaluateCheck({ ...check, optional: true }, { found: false }, { gaveUp: true }).outcome, "skipped");
assert.equal(evaluateCheck(check, { found: true, status: "in_progress" }).outcome, "pending");
assert.equal(evaluateCheck(check, { found: true, status: "in_progress" }, { gaveUp: true }).category, "infrastructure");
assert.equal(evaluateCheck(check, { found: true, status: "completed", conclusion: "success" }).outcome, "pass");
const skipped = evaluateCheck(check, { found: true, status: "completed", conclusion: "skipped" });
assert.equal(skipped.outcome, "failure", "a skipped required check validated nothing");
assert.equal(skipped.category, "infrastructure");
const skippedOk = { found: true, status: "completed", conclusion: "skipped" };
assert.equal(evaluateCheck({ ...check, allow_skip: true }, skippedOk).outcome, "skipped");
assert.equal(evaluateCheck({ ...check, optional: true }, skippedOk).outcome, "skipped");
assert.equal(evaluateCheck({ ...check, required: false }, skippedOk).outcome, "skipped");
assert.equal(config.gate.checks.find((c) => c.id === "contributor-reputation").allow_skip, true, "skips for bot authors");
assert.equal(evaluateCheck(check, { found: true, status: "completed", conclusion: "cancelled" }).category, "infrastructure");
assert.equal(evaluateCheck(check, { found: true, status: "completed", conclusion: "action_required" }).category, "infrastructure");
const advisory = evaluateCheck({ ...check, required: false, failure_kind: "infrastructure" }, { found: true, status: "completed", conclusion: "failure" });
assert.equal(advisory.required, false);
assert.equal(summarizeChecks([advisory]).warnings.length, 1);
assert.equal(summarizeChecks([advisory]).infrastructureFailures.length, 0, "advisory failures never block");
});
test("state machine precedence", () => {
const approvals = { changesRequestedBy: [], satisfied: false, reviewers: [] };
const automation = (overrides = {}) => ({ contributionFailures: [], pending: [], infrastructureFailures: [], ...overrides });
assert.equal(computeState({ automation: automation({ contributionFailures: [{}], pending: [{}] }), approvals }), "requires-submitter-fixes");
assert.equal(computeState({ automation: automation(), approvals: { ...approvals, changesRequestedBy: ["x"] } }), "requires-submitter-fixes");
assert.equal(computeState({ automation: automation({ pending: [{}] }), approvals: { ...approvals, satisfied: true } }), "awaiting-automation");
assert.equal(computeState({ automation: automation({ infrastructureFailures: [{}] }), approvals }), "awaiting-automation");
assert.equal(computeState({ automation: automation(), approvals: { ...approvals, satisfied: true } }), "approved");
assert.equal(computeState({ automation: automation(), approvals: { ...approvals, reviewers: ["a"] } }), "review-in-progress");
assert.equal(computeState({ automation: automation(), approvals }), "ready-for-review");
});
test("latestRunsByWorkflow keeps the newest PR-triggered run per workflow", () => {
const latest = latestRunsByWorkflow([
{ id: 1, path: ".github/workflows/a.yml", event: "pull_request", created_at: "2026-09-29T10:00:00Z" },
{ id: 2, path: ".github/workflows/a.yml", event: "pull_request", created_at: "2026-09-29T11:00:00Z" },
{ id: 3, path: ".github/workflows/b.yml", event: "push", created_at: "2026-09-29T12:00:00Z" },
]);
assert.equal(latest.get("a.yml").id, 2);
assert.ok(!latest.has("b.yml"));
});
// --- commands and rendering ------------------------------------------------------
test("parsePrCommand only accepts supported commands at the start of the comment", () => {
assert.deepEqual(parsePrCommand("/rerun-checks"), { command: "rerun-checks" });
assert.deepEqual(parsePrCommand("/Request-Review please\nthanks"), { command: "request-review" });
assert.equal(parsePrCommand("\n/rerun-checks"), null, "matches the workflow's startsWith filter");
assert.equal(parsePrCommand(" /rerun-checks"), null);
assert.equal(parsePrCommand("please /rerun-checks"), null);
assert.equal(parsePrCommand("/rerun-checksx"), null);
assert.equal(parsePrCommand(""), null);
});
test("canRunPrCommand allows the author and writers only", () => {
assert.ok(canRunPrCommand({ commenter: "Author", prAuthor: "author", permission: "read" }));
assert.ok(canRunPrCommand({ commenter: "m", prAuthor: "author", permission: "maintain" }));
assert.ok(!canRunPrCommand({ commenter: "x", prAuthor: "author", permission: "triage" }));
assert.ok(!canRunPrCommand({ commenter: "", prAuthor: "", permission: "read" }));
});
test("sanitize neutralizes mentions, HTML, and table breaks", () => {
assert.equal(sanitize("@team <b>|x"), "@\u200bteam &lt;b&gt;\\|x");
assert.equal(sanitize("a".repeat(10), 5).length, 5);
});
// --- orchestration with a fake GitHub client --------------------------------------
function fakeGithub({ pr, files = [], reviews = [], runs = [], jobs = {}, checkRuns = [], comments = [], reactions = [], permissions = {} }) {
const calls = [];
const record = (name, fn) => async (params) => {
calls.push({ name, params });
return fn(params);
};
const rest = {
pulls: {
get: record("pulls.get", async ({ pull_number }) => ({ data: { ...pr, number: pull_number } })),
listFiles: "pulls.listFiles",
listReviews: "pulls.listReviews",
list: "pulls.list",
},
actions: {
listWorkflowRunsForRepo: "actions.listWorkflowRunsForRepo",
listJobsForWorkflowRun: "actions.listJobsForWorkflowRun",
getJobForWorkflowRun: record("actions.getJobForWorkflowRun", async () => {
throw Object.assign(new Error("nope"), { status: 404 });
}),
reRunWorkflowFailedJobs: record("actions.reRunWorkflowFailedJobs", async () => ({})),
reRunWorkflow: record("actions.reRunWorkflow", async () => ({})),
createWorkflowDispatch: record("actions.createWorkflowDispatch", async () => ({})),
},
checks: {
listForRef: record("checks.listForRef", async ({ check_name }) => ({
data: { check_runs: checkRuns.filter((checkRun) => !check_name || checkRun.name === check_name) },
})),
create: record("checks.create", async () => ({ data: { id: 999 } })),
update: record("checks.update", async () => ({ data: {} })),
},
repos: {
getCollaboratorPermissionLevel: record("repos.getCollaboratorPermissionLevel", async ({ username }) => ({
data: { permission: permissions[username] || "read" },
})),
},
issues: {
addLabels: record("issues.addLabels", async () => ({})),
removeLabel: record("issues.removeLabel", async () => ({})),
listComments: "issues.listComments",
updateComment: record("issues.updateComment", async () => ({})),
createComment: record("issues.createComment", async () => ({})),
getComment: record("issues.getComment", async ({ comment_id }) => {
const found = comments.find((comment) => comment.id === comment_id);
if (!found) throw Object.assign(new Error("Not Found"), { status: 404 });
return { data: found };
}),
},
reactions: {
listForIssueComment: "reactions.listForIssueComment",
createForIssueComment: record("reactions.createForIssueComment", async () => ({})),
},
};
const pages = {
"pulls.listFiles": files,
"pulls.listReviews": reviews,
"pulls.list": [pr],
"actions.listWorkflowRunsForRepo": runs,
"issues.listComments": comments,
"reactions.listForIssueComment": reactions,
};
return {
calls,
rest,
paginate: async (method, params) => {
calls.push({ name: method, params });
if (method === "actions.listJobsForWorkflowRun") return jobs[params.run_id] || [];
return pages[method] || [];
},
};
}
const basePr = {
number: 7,
state: "open",
head: { sha: "a".repeat(40), ref: "feature", repo: { full_name: "fork/awesome-copilot" } },
base: { ref: "main", repo: { full_name: "github/awesome-copilot" } },
user: { login: "author" },
labels: [{ name: "review-due:2026-10-01" }, { name: "merge-risk:high" }],
requested_reviewers: [{ login: "alice" }],
requested_teams: [],
};
const run = (id, workflow, conclusion, status = "completed") => ({
id,
path: `.github/workflows/${workflow}`,
name: workflow,
event: "pull_request",
status,
conclusion,
created_at: "2026-09-29T10:00:00Z",
html_url: `https://example.test/runs/${id}`,
});
test("evaluateSubmission reaches approved when checks pass and approvals exist", async () => {
const github = fakeGithub({
pr: basePr,
files: [file("docs/a.md")],
reviews: [review("alice", "APPROVED")],
permissions: { alice: "write" },
runs: [
run(1, "check-line-endings.yml", "success"),
run(90, "codespell.yml", "success"),
run(2, "validate-readme.yml", "success"),
run(3, "contributor-check.yml", "success"),
run(4, "pr-duplicate-check.lock.yml", "failure"),
run(5, "pr-quality-signal.lock.yml", "skipped"),
],
});
const evaluation = await evaluateSubmission(github, {
owner: "github",
repo: "awesome-copilot",
pullNumber: 7,
config,
readContributorRisk: () => "LOW",
});
assert.equal(evaluation.risk.tier, "low");
assert.equal(evaluation.state, "approved");
assert.equal(evaluation.passed, true);
assert.equal(evaluation.automation.warnings.length, 1, "duplicate-scan infra failure is advisory");
assert.equal(evaluation.reviewAssignment.due, "2026-10-01");
await syncPullRequestStatus(github, { owner: "github", repo: "awesome-copilot", evaluation });
const added = github.calls.find((call) => call.name === "issues.addLabels");
assert.deepEqual(added.params.labels.sort(), ["approved", "merge-risk:low"]);
const removed = github.calls.filter((call) => call.name === "issues.removeLabel").map((call) => call.params.name);
assert.deepEqual(removed, ["merge-risk:high"]);
const created = github.calls.find((call) => call.name === "issues.createComment");
assert.ok(created.params.body.startsWith(STATUS_MARKER));
});
test("evaluateSubmission separates contribution and infrastructure failures", async () => {
const github = fakeGithub({
pr: basePr,
files: [file("skills/x/SKILL.md", { status: "added" })],
runs: [
run(1, "check-line-endings.yml", "success"),
run(90, "codespell.yml", "success"),
run(2, "validate-readme.yml", "failure"),
run(3, "contributor-check.yml", "success"),
run(4, "validate-skills.yml", "failure"),
run(5, "skill-check.yml", "success"),
run(6, "pr-risk-scan.yml", "cancelled"),
],
jobs: {
2: failedJob("Fail workflow if files need updating"),
4: failedJob("Install dependencies"),
},
});
const evaluation = await evaluateSubmission(github, {
owner: "github",
repo: "awesome-copilot",
pullNumber: 7,
config,
finalized: true,
readContributorRisk: () => null,
});
assert.deepEqual(evaluation.automation.contributionFailures.map((r) => r.id), ["readme"]);
assert.deepEqual(
evaluation.automation.infrastructureFailures.map((r) => r.id).sort(),
["risk-scan", "skill-validation"]
);
assert.equal(evaluation.state, "requires-submitter-fixes");
const body = renderStatusComment(evaluation);
assert.match(body, /Contribution failure/);
assert.match(body, /Infrastructure failure/);
assert.match(body, /npm start/);
assert.match(body, /\/rerun-checks/);
assert.match(body, /2026-10-01/);
});
test("evaluateSubmission waits for pending checks in gate mode", async () => {
let clock = 0;
let polls = 0;
const runs = [
run(1, "check-line-endings.yml", null, "in_progress"),
run(90, "codespell.yml", "success"),
run(3, "contributor-check.yml", "success"),
run(4, "pr-duplicate-check.lock.yml", "success"),
run(5, "pr-quality-signal.lock.yml", "success"),
];
const github = fakeGithub({ pr: basePr, files: [file("LICENSE")], runs });
const originalPaginate = github.paginate;
github.paginate = async (method, params) => {
if (method === "actions.listWorkflowRunsForRepo") {
polls += 1;
if (polls >= 2) runs[0] = run(1, "check-line-endings.yml", "success");
}
return originalPaginate(method, params);
};
const evaluation = await evaluateSubmission(github, {
owner: "github",
repo: "awesome-copilot",
pullNumber: 7,
config,
wait: true,
now: () => clock,
sleep: async (ms) => {
clock += ms;
},
readContributorRisk: () => null,
});
assert.equal(polls, 2);
assert.equal(evaluation.automation.pending.length, 0);
assert.equal(evaluation.state, "ready-for-review");
});
test("unreported required checks become infrastructure failures after the grace period", async () => {
let clock = 0;
const github = fakeGithub({ pr: basePr, files: [file("LICENSE")], runs: [run(3, "contributor-check.yml", "success")] });
const evaluation = await evaluateSubmission(github, {
owner: "github",
repo: "awesome-copilot",
pullNumber: 7,
config,
wait: true,
now: () => clock,
sleep: async (ms) => {
clock += ms;
},
readContributorRisk: () => null,
});
const lineEndings = evaluation.automation.results.find((result) => result.id === "line-endings");
assert.equal(lineEndings.category, "infrastructure");
assert.ok(clock >= config.gate.wait.report_grace_minutes * 60_000);
assert.ok(clock < config.gate.wait.timeout_minutes * 60_000);
});
test("rerunChecks re-runs failed workflows and the gate but skips approval-gated runs", async () => {
const github = fakeGithub({
pr: basePr,
runs: [
run(1, "validate-readme.yml", "failure"),
run(2, "check-line-endings.yml", "success"),
run(3, "contributor-check.yml", "action_required"),
run(4, "submission-gate.yml", "failure"),
run(5, "pr-risk-scan.yml", null, "in_progress"),
],
});
const result = await rerunChecks(github, { owner: "github", repo: "awesome-copilot", headSha: basePr.head.sha });
assert.deepEqual(result.rerun, ["validate-readme.yml", "submission-gate.yml"]);
assert.equal(result.skipped.length, 1);
assert.ok(github.calls.some((call) => call.name === "actions.reRunWorkflow" && call.params.run_id === 4));
});
test("syncPullRequestStatus leaves state labels to external plugin intake and updates the comment in place", async () => {
const pr = { ...basePr, labels: [{ name: "external-plugin" }, { name: "ready-for-review" }] };
const github = fakeGithub({
pr,
comments: [
{ id: 1, user: { login: "someone" }, body: STATUS_MARKER },
{ id: 2, user: { login: "github-actions[bot]" }, body: `${STATUS_MARKER}\nold` },
],
});
const evaluation = {
pr,
headSha: pr.head.sha,
labels: ["external-plugin", "ready-for-review"],
risk: { tier: "high", reasons: ["`plugins/external.json` is a high-risk path"] },
automation: summarizeChecks([]),
approvals: { required: 2, requirement: "2 approvals", approvers: [], changesRequestedBy: [], reviewers: [], missing: ["2 more approval(s)"], notes: [], satisfied: false },
state: "awaiting-automation",
reviewAssignment: { users: [], teams: [], due: null },
};
await syncPullRequestStatus(github, { owner: "github", repo: "awesome-copilot", evaluation });
assert.deepEqual(github.calls.find((call) => call.name === "issues.addLabels").params.labels, ["merge-risk:high"]);
assert.ok(!github.calls.some((call) => call.name === "issues.removeLabel"));
const updated = github.calls.find((call) => call.name === "issues.updateComment");
assert.equal(updated.params.comment_id, 2);
assert.ok(!github.calls.some((call) => call.name === "issues.createComment"));
});
test("resolvePullRequestForWorkflowRun requires an exact head match", async () => {
const github = fakeGithub({ pr: basePr });
const workflowRun = {
head_sha: basePr.head.sha,
head_branch: "feature",
head_repository: { full_name: "fork/awesome-copilot" },
pull_requests: [],
};
assert.equal(await resolvePullRequestForWorkflowRun(github, { owner: "github", repo: "awesome-copilot", workflowRun }), 7);
assert.equal(
await resolvePullRequestForWorkflowRun(github, {
owner: "github",
repo: "awesome-copilot",
workflowRun: { ...workflowRun, head_sha: "b".repeat(40) },
}),
null
);
});
// --- review hardening ------------------------------------------------------------
test("text files without a scannable diff or a truncated file list fail closed to high", () => {
const noPatch = classifyRisk({ files: [file("extensions/x/extension.mjs", { patch: undefined })], tiers });
assert.equal(noPatch.tier, "high");
assert.match(noPatch.reasons.join("\n"), /no diff available/);
assert.equal(classifyRisk({ files: [file("skills/x/guide.pdf", { patch: undefined, status: "added" })], tiers }).tier, "medium");
assert.equal(classifyRisk({ files: [file("docs/a.md", { patch: undefined })], tiers }).tier, "low", "outside capability scope");
assert.equal(classifyRisk({ files: [file("docs/a.md")], tiers, incompleteFiles: true }).tier, "high");
});
test("low tier requires an approval from an owner of the changed resource", () => {
const permissions = new Map([["alice", "write"], ["canvasa", "write"], ["corea", "write"]]);
const base = { tier: "low", tiers, author: "author", permissions, routing };
const canvas = { ...base, files: [file("extensions/x/README.md")] };
assert.equal(evaluateApprovals({ ...canvas, reviews: [review("alice", "APPROVED")] }).satisfied, false);
assert.equal(evaluateApprovals({ ...canvas, reviews: [review("canvasa", "APPROVED")] }).satisfied, true);
const docs = { ...base, files: [file("docs/a.md")] };
assert.equal(evaluateApprovals({ ...docs, reviews: [review("alice", "APPROVED")] }).satisfied, false, "core pools own docs");
assert.equal(evaluateApprovals({ ...docs, reviews: [review("corea", "APPROVED")] }).satisfied, true);
const unstaffed = evaluateApprovals({ ...base, files: [file("skills/x/SKILL.md")], reviews: [review("alice", "APPROVED")] });
assert.equal(unstaffed.satisfied, true, "unstaffed content pool falls back to any writer");
});
test("approvals fail closed when a reviewer's permission can't be read", async () => {
const github = fakeGithub({ pr: basePr, files: [file("docs/a.md")], reviews: [review("alice", "APPROVED", undefined, { author_association: "COLLABORATOR" })] });
github.rest.repos.getCollaboratorPermissionLevel = async () => {
throw Object.assign(new Error("forbidden"), { status: 403 });
};
const evaluation = await evaluateSubmission(github, { owner: "github", repo: "awesome-copilot", pullNumber: 7, config, finalized: true, readContributorRisk: () => null });
assert.deepEqual(evaluation.approvals.approvers, []);
});
test("evaluateSubmission blocks when GitHub truncates the changed file list", async () => {
const github = fakeGithub({ pr: { ...basePr, changed_files: 3001 }, files: [file("docs/a.md")] });
const evaluation = await evaluateSubmission(github, { owner: "github", repo: "awesome-copilot", pullNumber: 7, config, finalized: true, readContributorRisk: () => null });
assert.equal(evaluation.risk.tier, "high");
const truncated = evaluation.automation.infrastructureFailures.find((result) => result.id === "changed-files");
assert.ok(truncated);
assert.equal(evaluation.passed, false);
});
test("evaluateSubmission is stale when the head differs from the expected or re-read head", async () => {
const expected = await evaluateSubmission(fakeGithub({ pr: basePr }), {
owner: "github", repo: "awesome-copilot", pullNumber: 7, config, expectedHeadSha: "b".repeat(40), readContributorRisk: () => null,
});
assert.equal(expected.stale, true);
const github = fakeGithub({ pr: basePr, files: [file("docs/a.md")] });
let gets = 0;
github.rest.pulls.get = async ({ pull_number }) => {
gets += 1;
return { data: { ...basePr, number: pull_number, head: { ...basePr.head, sha: gets === 1 ? basePr.head.sha : "c".repeat(40) } } };
};
const moved = await evaluateSubmission(github, { owner: "github", repo: "awesome-copilot", pullNumber: 7, config, readContributorRisk: () => null });
assert.equal(moved.stale, true);
assert.notEqual(moved.passed, true);
});
test("a submission-gate check not published by the writer is flagged as tampering", async () => {
const github = fakeGithub({
pr: basePr,
files: [file("docs/a.md")],
reviews: [review("alice", "APPROVED")],
permissions: { alice: "write" },
checkRuns: [{ id: 5, name: "submission-gate", external_id: "", html_url: "https://example.test/impostor" }],
runs: [run(1, "check-line-endings.yml", "success"), run(90, "codespell.yml", "success"), run(2, "validate-readme.yml", "success"), run(3, "contributor-check.yml", "success")],
});
const evaluation = await evaluateSubmission(github, { owner: "github", repo: "awesome-copilot", pullNumber: 7, config, finalized: true, readContributorRisk: () => null });
assert.equal(evaluation.risk.tier, "high");
assert.ok(evaluation.automation.contributionFailures.some((result) => result.id === "gate-integrity"));
assert.equal(evaluation.passed, false);
await publishGateCheck(github, { owner: "github", repo: "awesome-copilot", evaluation });
const created = github.calls.find((call) => call.name === "checks.create");
assert.equal(created.params.name, "submission-gate");
assert.equal(created.params.external_id, GATE_CHECK_EXTERNAL_ID);
assert.equal(created.params.conclusion, "failure");
});
test("publishGateCheck updates the writer's check run and reports pending as in progress", async () => {
const ours = { id: 42, name: "submission-gate", external_id: GATE_CHECK_EXTERNAL_ID };
const github = fakeGithub({ pr: basePr, checkRuns: [ours] });
const evaluation = {
headSha: basePr.head.sha,
state: "approved",
passed: true,
risk: { tier: "low" },
automation: summarizeChecks([]),
approvals: { satisfied: true, missing: [] },
};
await publishGateCheck(github, { owner: "github", repo: "awesome-copilot", evaluation, detailsUrl: "https://example.test/run" });
const updated = github.calls.find((call) => call.name === "checks.update");
assert.equal(updated.params.check_run_id, 42);
assert.equal(updated.params.conclusion, "success");
assert.equal(updated.params.details_url, "https://example.test/run");
const pending = { ...evaluation, state: "awaiting-automation", passed: false, automation: summarizeChecks([{ id: "x", title: "X", required: true, outcome: "pending" }]) };
const github2 = fakeGithub({ pr: basePr });
await publishGateCheck(github2, { owner: "github", repo: "awesome-copilot", evaluation: pending });
const created = github2.calls.find((call) => call.name === "checks.create");
assert.equal(created.params.status, "in_progress");
assert.equal(created.params.conclusion, undefined);
});
test("syncPullRequestStatus does not write when the head or reviews changed", async () => {
const evaluation = {
pr: basePr,
headSha: "b".repeat(40),
labels: [],
reviewsSignature: "",
risk: { tier: "low", reasons: [] },
automation: summarizeChecks([]),
approvals: { required: 1, requirement: "1 approval", approvers: [], changesRequestedBy: [], reviewers: [], missing: [], notes: [], satisfied: true },
state: "approved",
passed: true,
reviewAssignment: { users: [], teams: [], due: null },
};
const moved = fakeGithub({ pr: basePr });
assert.deepEqual(await syncPullRequestStatus(moved, { owner: "github", repo: "awesome-copilot", evaluation, publishCheck: true }), {
updated: false,
reason: "head-changed",
});
assert.ok(!moved.calls.some((call) => /addLabels|removeLabel|Comment|checks\.(create|update)/.test(call.name)));
const reviewed = fakeGithub({ pr: basePr, reviews: [review("alice", "CHANGES_REQUESTED")] });
const result = await syncPullRequestStatus(reviewed, {
owner: "github",
repo: "awesome-copilot",
evaluation: { ...evaluation, headSha: basePr.head.sha },
publishCheck: true,
});
assert.equal(result.reason, "reviews-changed");
assert.ok(!reviewed.calls.some((call) => /addLabels|checks\.create/.test(call.name)));
});
test("syncPullRequestStatus publishes the gate check even when label or comment writes fail", async () => {
const evaluation = {
pr: basePr,
headSha: basePr.head.sha,
labels: [],
reviewsSignature: "",
risk: { tier: "low", reasons: [] },
automation: summarizeChecks([]),
approvals: { required: 1, requirement: "1 approval", approvers: [], changesRequestedBy: [], reviewers: [], missing: ["1 more approval"], notes: [], satisfied: false },
state: "review-in-progress",
passed: false,
reviewAssignment: { users: [], teams: [], due: null },
};
const github = fakeGithub({ pr: basePr });
github.rest.issues.addLabels = async () => {
throw Object.assign(new Error("Resource not accessible"), { status: 403 });
};
github.rest.issues.createComment = async () => {
throw Object.assign(new Error("Server Error"), { status: 502 });
};
await assert.rejects(
syncPullRequestStatus(github, { owner: "github", repo: "awesome-copilot", evaluation, publishCheck: true }),
/labels: Resource not accessible; status comment: Server Error/
);
assert.ok(github.calls.some((call) => call.name === "checks.create"), "check is published before label and comment writes");
});
test("a successful contributor check with an unreadable artifact fails closed", async () => {
const runs = [
run(1, "check-line-endings.yml", "success"),
run(90, "codespell.yml", "success"),
run(2, "validate-readme.yml", "success"),
run(3, "contributor-check.yml", "success"),
];
const approved = { reviews: [review("alice", "APPROVED")], permissions: { alice: "write" } };
const options = { owner: "github", repo: "awesome-copilot", pullNumber: 7, config, finalized: true, readContributorRisk: () => null };
const lost = await evaluateSubmission(
fakeGithub({ pr: basePr, files: [file("docs/a.md")], runs, jobs: { 3: [{ name: "pr-check", conclusion: "success" }] }, ...approved }),
options
);
assert.deepEqual(lost.automation.infrastructureFailures.map((r) => r.id), ["contributor-risk-signal"]);
assert.equal(lost.passed, false);
const skipped = await evaluateSubmission(
fakeGithub({ pr: basePr, files: [file("docs/a.md")], runs, jobs: { 3: [{ name: "pr-check", conclusion: "skipped" }] }, ...approved }),
options
);
assert.equal(skipped.automation.infrastructureFailures.length, 0, "a skipped PR job carries no signal");
assert.equal(skipped.passed, true);
});
test("evaluation and final write are discarded when the base branch or risk labels change", async () => {
const github = fakeGithub({ pr: basePr, files: [file("docs/a.md")] });
let gets = 0;
github.rest.pulls.get = async ({ pull_number }) => {
gets += 1;
return { data: { ...basePr, number: pull_number, base: { ...basePr.base, ref: gets === 1 ? "main" : "staged" } } };
};
const retargeted = await evaluateSubmission(github, { owner: "github", repo: "awesome-copilot", pullNumber: 7, config, readContributorRisk: () => null });
assert.equal(retargeted.stale, true);
const evaluation = await evaluateSubmission(fakeGithub({ pr: basePr, files: [file("docs/a.md")] }), {
owner: "github", repo: "awesome-copilot", pullNumber: 7, config, finalized: true, readContributorRisk: () => null,
});
assert.equal(evaluation.baseRef, "main");
assert.equal(evaluation.riskLabelSignature, "");
const rebased = fakeGithub({ pr: { ...basePr, base: { ...basePr.base, ref: "staged" } } });
assert.equal((await syncPullRequestStatus(rebased, { owner: "github", repo: "awesome-copilot", evaluation })).reason, "base-changed");
const flagged = fakeGithub({ pr: { ...basePr, labels: [...basePr.labels, { name: "needs-review:HIGH" }] } });
assert.equal((await syncPullRequestStatus(flagged, { owner: "github", repo: "awesome-copilot", evaluation })).reason, "labels-changed");
assert.ok(!flagged.calls.some((call) => /addLabels|removeLabel|Comment|checks\./.test(call.name)));
assert.equal(riskLabelSignature(["b", "needs-review:HIGH", "a"], ["needs-review:HIGH"]), "needs-review:HIGH");
});
test("publishGateCheck corrects copies of the writer's check that claim success for a failing evaluation", async () => {
const older = { id: 10, name: "submission-gate", external_id: GATE_CHECK_EXTERNAL_ID, conclusion: "failure" };
const forged = { id: 11, name: "submission-gate", external_id: GATE_CHECK_EXTERNAL_ID, conclusion: "success" };
const github = fakeGithub({ pr: basePr, checkRuns: [older, forged] });
const evaluation = {
headSha: basePr.head.sha,
state: "review-in-progress",
passed: false,
risk: { tier: "high" },
automation: summarizeChecks([]),
approvals: { satisfied: false, missing: ["1 more approval"] },
};
const logs = [];
await publishGateCheck(github, { owner: "github", repo: "awesome-copilot", evaluation, log: (line) => logs.push(line) });
const updates = github.calls.filter((call) => call.name === "checks.update");
assert.deepEqual(updates.map((call) => call.params.check_run_id), [11]);
assert.equal(updates[0].params.conclusion, "action_required");
assert.ok(logs.some((line) => /Correcting 1/.test(line)));
});
test("validatePrCommandRequest only accepts well-formed requests from the triggering run", () => {
const request = { schema_version: PR_COMMAND_SCHEMA, pr_number: 7, comment_id: 55, run_id: "123" };
assert.deepEqual(validatePrCommandRequest(request, { workflowRunId: 123 }), { prNumber: 7, commentId: 55 });
assert.throws(() => validatePrCommandRequest({ ...request, run_id: "124" }, { workflowRunId: 123 }), /run_id/);
assert.throws(() => validatePrCommandRequest({ ...request, pr_number: "7" }, { workflowRunId: 123 }), /pr_number/);
assert.throws(() => validatePrCommandRequest({ ...request, comment_id: 0 }, { workflowRunId: 123 }), /comment_id/);
assert.throws(() => validatePrCommandRequest({ ...request, schema_version: "x" }, { workflowRunId: 123 }), /schema_version/);
assert.throws(() => validatePrCommandRequest(null, { workflowRunId: 123 }), /not an object/);
});
const commandComment = (body, login = "author", extra = {}) => ({
id: 55,
body,
user: { login, type: "User" },
issue_url: "https://api.github.com/repos/github/awesome-copilot/issues/7",
...extra,
});
const commandOptions = { owner: "github", repo: "awesome-copilot", prNumber: 7, commentId: 55, config };
test("runPrCommand re-reads the comment, PR, and permission before writing", async () => {
const request = fakeGithub({ pr: basePr, comments: [commandComment("/request-review")] });
assert.deepEqual(await runPrCommand(request, commandOptions), { status: "handled", command: "request-review" });
assert.deepEqual(request.calls.find((call) => call.name === "issues.addLabels").params.labels, ["needs-reviewer"]);
const dispatched = request.calls.find((call) => call.name === "actions.createWorkflowDispatch");
assert.equal(dispatched.params.workflow_id, "review-routing.yml");
assert.deepEqual(dispatched.params.inputs, { pr_number: "7" });
assert.ok(request.calls.some((call) => call.name === "reactions.createForIssueComment"));
assert.ok(request.calls.some((call) => call.name === "issues.createComment"));
const order = request.calls.map((call) => (call.name === "reactions.createForIssueComment" ? `reaction:${call.params.content}` : call.name));
assert.ok(order.indexOf("reaction:eyes") < order.indexOf("issues.addLabels"), "claim marker is written before acting");
assert.ok(order.indexOf("reaction:rocket") > order.indexOf("issues.createComment"), "done marker is written after the reply");
const rerun = fakeGithub({ pr: basePr, comments: [commandComment("/rerun-checks", "maint")], permissions: { maint: "maintain" } });
assert.equal((await runPrCommand(rerun, commandOptions)).command, "rerun-checks");
assert.equal(rerun.calls.find((call) => call.name === "actions.createWorkflowDispatch").params.workflow_id, "submission-gate-writer.yml");
});
test("runPrCommand ignores outsiders, closed PRs, edits, replays, and mismatched comments", async () => {
const writes = /addLabels|createComment|createForIssueComment|createWorkflowDispatch|reRun/;
const outsider = fakeGithub({ pr: basePr, comments: [commandComment("/rerun-checks", "stranger")] });
assert.equal((await runPrCommand(outsider, commandOptions)).status, "ignored");
assert.ok(!outsider.calls.some((call) => writes.test(call.name)));
const closed = fakeGithub({ pr: { ...basePr, state: "closed" }, comments: [commandComment("/rerun-checks")] });
assert.equal((await runPrCommand(closed, commandOptions)).reason, "PR is not open");
const edited = fakeGithub({ pr: basePr, comments: [commandComment("thanks!")] });
assert.equal((await runPrCommand(edited, commandOptions)).reason, "no supported command");
const replay = fakeGithub({
pr: basePr,
comments: [commandComment("/request-review")],
reactions: [
{ content: "eyes", user: { login: "github-actions[bot]" } },
{ content: "rocket", user: { login: "github-actions[bot]" } },
],
});
assert.equal((await runPrCommand(replay, commandOptions)).reason, "already handled");
assert.ok(!replay.calls.some((call) => writes.test(call.name)));
const elsewhere = fakeGithub({
pr: basePr,
comments: [commandComment("/request-review", "author", { issue_url: "https://api.github.com/repos/github/awesome-copilot/issues/70" })],
});
await assert.rejects(runPrCommand(elsewhere, commandOptions), /does not belong/);
});
test("runPrCommand retries a command that was claimed but never completed", async () => {
const claimedOnly = fakeGithub({
pr: basePr,
comments: [commandComment("/request-review")],
reactions: [{ content: "eyes", user: { login: "github-actions[bot]" } }],
});
assert.equal((await runPrCommand(claimedOnly, commandOptions)).status, "handled");
const failing = fakeGithub({ pr: basePr, comments: [commandComment("/request-review")] });
failing.rest.issues.createComment = async () => {
throw Object.assign(new Error("boom"), { status: 502 });
};
await assert.rejects(runPrCommand(failing, commandOptions), /boom/);
const reactionsWritten = failing.calls.filter((call) => call.name === "reactions.createForIssueComment").map((call) => call.params.content);
assert.deepEqual(reactionsWritten, ["eyes"], "a failed run leaves only the claim marker, so it can be retried");
});
test("advisory checks that are still pending do not hold the gate", () => {
const advisory = evaluateCheck({ id: "quality", required: false }, { found: true, status: "in_progress" });
const required = evaluateCheck({ id: "build" }, { found: true, status: "completed", conclusion: "success" });
const automation = summarizeChecks([advisory, required]);
assert.equal(automation.pending.length, 0);
assert.equal(automation.advisoryPending.length, 1);
const approvals = { satisfied: true, reviewers: [], changesRequestedBy: [] };
assert.equal(computeState({ automation, approvals }), "approved");
const requiredPending = summarizeChecks([evaluateCheck({ id: "build" }, { found: true, status: "queued" })]);
assert.equal(computeState({ automation: requiredPending, approvals }), "awaiting-automation");
});
test("loadGateConfig fails closed on an unparsable review-routing.yml but tolerates a missing one", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "gate-config-"));
try {
fs.mkdirSync(path.join(dir, ".github"));
for (const name of ["submission-gate.yml", "risk-tiers.yml"]) {
fs.copyFileSync(path.join(repoRoot, ".github", name), path.join(dir, ".github", name));
}
assert.equal(loadGateConfig(dir).routing, null, "missing routing file falls back");
const routingPath = path.join(dir, ".github", "review-routing.yml");
fs.writeFileSync(routingPath, "pools: [unclosed\n");
assert.throws(() => loadGateConfig(dir), /review-routing\.yml could not be parsed/);
fs.writeFileSync(routingPath, "pools:\n - core\n");
assert.throws(() => loadGateConfig(dir), /`pools` must be a mapping/);
fs.writeFileSync(routingPath, "- just\n- a list\n");
assert.throws(() => loadGateConfig(dir), /must be a mapping/);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});