819 lines
27 KiB
JavaScript
819 lines
27 KiB
JavaScript
const assert = require("assert");
|
|
const path = require("path");
|
|
|
|
const mergeBatch = require(path.join(__dirname, "..", "merge_batch.cjs"));
|
|
|
|
const BASE_SHA = "1".repeat(40);
|
|
const HEAD_SHA = "2".repeat(40);
|
|
const BLOB_SHA = "3".repeat(40);
|
|
const ZERO_SHA = "0".repeat(40);
|
|
|
|
function makeCheckRun(name, status, conclusion, startedAt, id, suiteId = 1) {
|
|
return {
|
|
name,
|
|
status,
|
|
conclusion,
|
|
started_at: startedAt,
|
|
completed_at: startedAt,
|
|
created_at: startedAt,
|
|
id,
|
|
app: { id: 15368 },
|
|
check_suite: { id: suiteId },
|
|
};
|
|
}
|
|
|
|
function evidenceSnapshot(overrides = {}) {
|
|
return {
|
|
audit: { counts: { error: 0, warning: 0, info: 0 } },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
{
|
|
const parsed = mergeBatch.parsePrList("450, 449 446");
|
|
assert.deepStrictEqual(parsed, [450, 449, 446]);
|
|
}
|
|
|
|
{
|
|
const parsed = mergeBatch.parseArgs([
|
|
"--prs",
|
|
"450",
|
|
"--reviewed-head",
|
|
HEAD_SHA,
|
|
"--reviewed-head",
|
|
BASE_SHA,
|
|
"--dry-run",
|
|
]);
|
|
assert.deepStrictEqual(parsed.reviewedHeads, [HEAD_SHA, BASE_SHA]);
|
|
assert.strictEqual(parsed.dryRun, true);
|
|
assert.throws(
|
|
() => mergeBatch.parseArgs(["--reviewed-head", HEAD_SHA.slice(0, 12)]),
|
|
/exact 40-character lowercase commit SHA/,
|
|
);
|
|
}
|
|
|
|
{
|
|
const summary = mergeBatch.extractSummaryBlock(`Summary line 1\nSummary line 2\n\n## Change Classification\n- [ ] Skill PR`);
|
|
assert.strictEqual(summary, "Summary line 1\nSummary line 2");
|
|
}
|
|
|
|
{
|
|
const aliases = mergeBatch.getRequiredCheckAliases({ hasSkillChanges: true });
|
|
assert.ok(aliases.some((entry) => !Array.isArray(entry) && entry.aliases.includes("review")));
|
|
assert.ok(aliases.some((entry) => entry.aliases.includes("pr-policy")));
|
|
assert.ok(aliases.some((entry) => entry.aliases.includes("pr-evidence")));
|
|
}
|
|
|
|
{
|
|
const runs = [
|
|
makeCheckRun("pr-policy", "completed", "failure", "2026-04-01T10:00:00Z", 1),
|
|
makeCheckRun("pr-policy", "completed", "success", "2026-04-01T10:10:00Z", 2),
|
|
makeCheckRun("source-validation", "in_progress", null, "2026-04-01T10:11:00Z", 3),
|
|
makeCheckRun("review", "completed", "success", "2026-04-01T10:12:00Z", 4),
|
|
];
|
|
const summaries = mergeBatch.summarizeRequiredCheckRuns(runs, [
|
|
["pr-policy"],
|
|
["source-validation"],
|
|
["review", "Skill Review & Optimize"],
|
|
]);
|
|
|
|
assert.deepStrictEqual(
|
|
summaries.map((entry) => entry.state),
|
|
["success", "pending", "success"],
|
|
);
|
|
|
|
const latest = mergeBatch.selectLatestCheckRuns(runs);
|
|
assert.strictEqual(latest.get("pr-policy").conclusion, "success");
|
|
|
|
const olderLateFailure = {
|
|
...makeCheckRun("artifact-preview", "completed", "failure", "2026-04-01T10:00:00Z", 7),
|
|
created_at: "2026-04-01T10:00:00Z",
|
|
completed_at: "2026-04-01T10:20:00Z",
|
|
};
|
|
const newerPending = {
|
|
...makeCheckRun("artifact-preview", "in_progress", null, "2026-04-01T10:10:00Z", 8),
|
|
created_at: "2026-04-01T10:10:00Z",
|
|
completed_at: null,
|
|
};
|
|
assert.strictEqual(
|
|
mergeBatch.selectLatestCheckRuns([olderLateFailure, newerPending]).get("artifact-preview").id,
|
|
8,
|
|
"run creation order must win over a stale run's later completion time",
|
|
);
|
|
|
|
const spoofed = { ...makeCheckRun("pr-policy", "completed", "success", "2026-04-01T10:20:00Z", 6), app: { id: 999 } };
|
|
assert.deepStrictEqual(
|
|
mergeBatch.summarizeRequiredCheckRuns([spoofed], [["pr-policy"]]).map((entry) => entry.state),
|
|
["missing"],
|
|
"required checks must come from the GitHub Actions app",
|
|
);
|
|
|
|
const skippedGate = makeCheckRun("pr-evidence", "completed", "skipped", "2026-04-01T10:13:00Z", 5);
|
|
assert.deepStrictEqual(
|
|
mergeBatch.summarizeRequiredCheckRuns([skippedGate], [["pr-evidence"]]).map((entry) => entry.state),
|
|
["failed"],
|
|
"a skipped deterministic gate must never pass",
|
|
);
|
|
}
|
|
|
|
{
|
|
const skippedReview = makeCheckRun("Skill Review / review", "completed", "skipped", "2026-04-01T10:00:00Z", 10);
|
|
const manualReview = makeCheckRun("Skill Review / manual-review-required", "completed", "success", "2026-04-01T10:01:00Z", 11);
|
|
const withoutAttestation = mergeBatch.getRequiredCheckAliases(
|
|
{ hasSkillChanges: true },
|
|
{ allowManualReview: false },
|
|
).at(-1);
|
|
const withAttestation = mergeBatch.getRequiredCheckAliases(
|
|
{ hasSkillChanges: true },
|
|
{ allowManualReview: true },
|
|
).at(-1);
|
|
|
|
assert.deepStrictEqual(
|
|
mergeBatch.summarizeRequiredCheckRuns([skippedReview], [withoutAttestation]).map((entry) => entry.state),
|
|
["failed"],
|
|
"a skipped semantic review must never pass",
|
|
);
|
|
assert.deepStrictEqual(
|
|
mergeBatch.summarizeRequiredCheckRuns([skippedReview, manualReview], [withoutAttestation]).map((entry) => entry.state),
|
|
["failed"],
|
|
"manual review must not count without an exact-head attestation",
|
|
);
|
|
assert.deepStrictEqual(
|
|
mergeBatch.summarizeRequiredCheckRuns([skippedReview, manualReview], [withAttestation]).map((entry) => entry.state),
|
|
["success"],
|
|
"manual review may satisfy the check only after exact-head attestation",
|
|
);
|
|
|
|
const failedReview = makeCheckRun("Skill Review / review", "completed", "failure", "2026-04-01T10:02:00Z", 12);
|
|
assert.deepStrictEqual(
|
|
mergeBatch.summarizeRequiredCheckRuns([failedReview, manualReview], [withAttestation]).map((entry) => entry.state),
|
|
["failed"],
|
|
"manual attestation must not override a real failed semantic review",
|
|
);
|
|
const skippedManual = makeCheckRun("Skill Review / manual-review-required", "completed", "skipped", "2026-04-01T10:03:00Z", 13);
|
|
assert.deepStrictEqual(
|
|
mergeBatch.summarizeRequiredCheckRuns([skippedReview, skippedManual], [withAttestation]).map((entry) => entry.state),
|
|
["missing"],
|
|
"a skipped manual-review job must not pass",
|
|
);
|
|
}
|
|
|
|
{
|
|
assert.strictEqual(mergeBatch.isRetryableMergeError(new Error("Base branch was modified")), true);
|
|
assert.strictEqual(mergeBatch.isRetryableMergeError(new Error("Something else")), false);
|
|
}
|
|
|
|
{
|
|
const literalArg = "safe&echo injected";
|
|
const stdout = mergeBatch.runCommand(
|
|
process.execPath,
|
|
["-e", "process.stdout.write(process.argv[1])", literalArg],
|
|
path.join(__dirname, "..", "..", ".."),
|
|
{ capture: true },
|
|
);
|
|
assert.strictEqual(stdout, literalArg);
|
|
}
|
|
|
|
|
|
{
|
|
const raw = Buffer.from(
|
|
`:000000 100644 ${ZERO_SHA} ${BLOB_SHA} A\0skills/example/SKILL.md\0` +
|
|
`:100644 100644 ${BLOB_SHA} ${HEAD_SHA} R100\0skills/example/references/old.md\0skills/example/references/new.md\0`,
|
|
"utf8",
|
|
);
|
|
const records = mergeBatch.parseRawDiff(raw);
|
|
assert.strictEqual(records.length, 2);
|
|
assert.deepStrictEqual(records[0], {
|
|
status: "A",
|
|
old_path: null,
|
|
new_path: "skills/example/SKILL.md",
|
|
old_mode: "000000",
|
|
new_mode: "100644",
|
|
old_oid: ZERO_SHA,
|
|
new_oid: BLOB_SHA,
|
|
similarity: null,
|
|
});
|
|
assert.strictEqual(records[1].status, "R");
|
|
assert.strictEqual(records[1].old_path, "skills/example/references/old.md");
|
|
assert.strictEqual(records[1].new_path, "skills/example/references/new.md");
|
|
assert.strictEqual(records[1].similarity, 100);
|
|
assert.throws(() => mergeBatch.parseRawDiff(raw.subarray(0, raw.length - 1)), /final NUL/);
|
|
assert.throws(() => mergeBatch.parseRawDiff(Buffer.alloc(0)), /empty/);
|
|
assert.deepStrictEqual(mergeBatch.parseRawDiff(Buffer.alloc(0), { allowEmpty: true }), []);
|
|
|
|
const mixedWidth = Buffer.from(
|
|
`:100644 100644 ${BLOB_SHA} ${"4".repeat(64)} M\0skills/example/SKILL.md\0`,
|
|
"utf8",
|
|
);
|
|
assert.throws(() => mergeBatch.parseRawDiff(mixedWidth), /Malformed raw Git diff header/);
|
|
|
|
const invalidUtf8 = Buffer.concat([
|
|
Buffer.from(`:000000 100644 ${ZERO_SHA} ${BLOB_SHA} A\0skills/example/references/`, "ascii"),
|
|
Buffer.from([0xff, 0]),
|
|
]);
|
|
assert.throws(() => mergeBatch.parseRawDiff(invalidUtf8), /canonical UTF-8/);
|
|
}
|
|
|
|
function workflowFixture(overrides = {}) {
|
|
return {
|
|
id: 100,
|
|
path: ".github/workflows/ci.yml",
|
|
state: "active",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function runFixture(overrides = {}) {
|
|
return {
|
|
id: 200,
|
|
workflow_id: 100,
|
|
path: ".github/workflows/ci.yml",
|
|
event: "pull_request",
|
|
head_sha: HEAD_SHA,
|
|
head_branch: "feature/example",
|
|
head_repository: { full_name: "contributor/repo" },
|
|
repository: { full_name: "owner/repo" },
|
|
pull_requests: [{ number: 450 }],
|
|
check_suite_id: 900,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
{
|
|
const valid = mergeBatch.validateActionRequiredRuns(
|
|
[runFixture()],
|
|
[workflowFixture()],
|
|
450,
|
|
HEAD_SHA,
|
|
);
|
|
assert.strictEqual(valid.length, 1);
|
|
|
|
const emptyMetadataIdentity = {
|
|
headRefName: "feature/example",
|
|
headRepository: "contributor/repo",
|
|
baseRepository: "owner/repo",
|
|
};
|
|
const emptyMetadata = mergeBatch.validateActionRequiredRuns(
|
|
[runFixture({ pull_requests: [] })],
|
|
[workflowFixture()],
|
|
450,
|
|
HEAD_SHA,
|
|
undefined,
|
|
emptyMetadataIdentity,
|
|
);
|
|
assert.strictEqual(emptyMetadata.length, 1);
|
|
for (const [label, identity] of [
|
|
["wrong branch", { ...emptyMetadataIdentity, headRefName: "feature/other" }],
|
|
["wrong fork", { ...emptyMetadataIdentity, headRepository: "attacker/repo" }],
|
|
["wrong base repository", { ...emptyMetadataIdentity, baseRepository: "attacker/base" }],
|
|
]) {
|
|
assert.throws(
|
|
() => mergeBatch.validateActionRequiredRuns(
|
|
[runFixture({ pull_requests: [] })],
|
|
[workflowFixture()],
|
|
450,
|
|
HEAD_SHA,
|
|
undefined,
|
|
identity,
|
|
),
|
|
/exact fork identity does not match/,
|
|
label,
|
|
);
|
|
}
|
|
for (const [label, run, identity = emptyMetadataIdentity] of [
|
|
["missing pull request metadata", runFixture({ pull_requests: undefined })],
|
|
["non-array pull request metadata", runFixture({ pull_requests: {} })],
|
|
["nonempty malformed pull request metadata", runFixture({ pull_requests: [{}] })],
|
|
["missing run branch", runFixture({ pull_requests: [], head_branch: undefined })],
|
|
["missing run fork", runFixture({ pull_requests: [], head_repository: undefined })],
|
|
["missing run base repository", runFixture({ pull_requests: [], repository: undefined })],
|
|
["missing captured branch", runFixture({ pull_requests: [] }), { ...emptyMetadataIdentity, headRefName: undefined }],
|
|
["missing captured fork", runFixture({ pull_requests: [] }), { ...emptyMetadataIdentity, headRepository: undefined }],
|
|
["missing captured base repository", runFixture({ pull_requests: [] }), { ...emptyMetadataIdentity, baseRepository: undefined }],
|
|
]) {
|
|
assert.throws(
|
|
() => mergeBatch.validateActionRequiredRuns(
|
|
[run],
|
|
[workflowFixture()],
|
|
450,
|
|
HEAD_SHA,
|
|
undefined,
|
|
identity,
|
|
),
|
|
/exact fork identity does not match/,
|
|
label,
|
|
);
|
|
}
|
|
|
|
for (const [label, run, workflows, pattern] of [
|
|
["unrelated PR", runFixture({ pull_requests: [{ number: 451 }] }), [workflowFixture()], /does not contain #450/],
|
|
["wrong SHA", runFixture({ head_sha: BASE_SHA }), [workflowFixture()], /head SHA/],
|
|
["wrong event", runFixture({ event: "push" }), [workflowFixture()], /not pull_request/],
|
|
["unknown path", runFixture({ path: ".github/workflows/evil.yml" }), [workflowFixture()], /not allowlisted/],
|
|
["ID mismatch", runFixture({ workflow_id: 101 }), [workflowFixture()], /workflow ID/],
|
|
["path mismatch", runFixture(), [workflowFixture({ path: ".github/workflows/codeql.yml" })], /mapping/],
|
|
]) {
|
|
assert.throws(
|
|
() => mergeBatch.validateActionRequiredRuns([run], workflows, 450, HEAD_SHA),
|
|
pattern,
|
|
label,
|
|
);
|
|
}
|
|
}
|
|
|
|
function approvalDependencies(overrides = {}) {
|
|
const record = {
|
|
status: "A",
|
|
old_path: null,
|
|
new_path: "skills/example/SKILL.md",
|
|
old_mode: "000000",
|
|
new_mode: "100644",
|
|
old_oid: ZERO_SHA,
|
|
new_oid: BLOB_SHA,
|
|
};
|
|
return {
|
|
fetchPullRequestObjects() {},
|
|
resolveMergeBase() { return BASE_SHA; },
|
|
readRawChangeRecords() { return [record]; },
|
|
resolveBlobSizes() { return new Map([[BLOB_SHA, 100]]); },
|
|
getEvaluatorOid() { return "4".repeat(40); },
|
|
recomputeChangedSkillEvidence() { return { blocking: false, reasons: [] }; },
|
|
loadPullRequestDetails() {
|
|
return { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
|
|
},
|
|
listWorkflowDefinitions() { return [workflowFixture()]; },
|
|
listActionRequiredRuns() { return [runFixture()]; },
|
|
approveWorkflowRun() {},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
{
|
|
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
|
|
const supportRecord = {
|
|
status: "M",
|
|
old_path: "skills/example/references/guide.md",
|
|
new_path: "skills/example/references/guide.md",
|
|
old_mode: "100644",
|
|
new_mode: "100644",
|
|
old_oid: BASE_SHA,
|
|
new_oid: BLOB_SHA,
|
|
};
|
|
const dependencies = approvalDependencies({
|
|
readRawChangeRecords() { return [supportRecord]; },
|
|
resolveBlobSizes() { return new Map([[BASE_SHA, 100], [BLOB_SHA, 100]]); },
|
|
});
|
|
assert.throws(
|
|
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, { dependencies }),
|
|
/--reviewed-head/,
|
|
"skill support content must require an exact-head maintainer attestation",
|
|
);
|
|
const approved = mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
|
|
dependencies,
|
|
reviewedHeads: [HEAD_SHA],
|
|
dryRun: true,
|
|
});
|
|
assert.strictEqual(approved.policy.requiresHumanReview, true);
|
|
assert.deepStrictEqual(approved.policy.canonicalSkillChanges, []);
|
|
}
|
|
|
|
{
|
|
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
|
|
let approvals = 0;
|
|
let tupleReads = 0;
|
|
const dependencies = approvalDependencies({
|
|
loadPullRequestDetails() {
|
|
tupleReads += 1;
|
|
return { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
|
|
},
|
|
approveWorkflowRun() { approvals += 1; },
|
|
});
|
|
assert.throws(
|
|
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, { dependencies }),
|
|
/--reviewed-head/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
|
|
dependencies,
|
|
reviewedHeads: [BASE_SHA],
|
|
}),
|
|
/--reviewed-head/,
|
|
"a stale but full reviewed head must fail closed",
|
|
);
|
|
assert.strictEqual(approvals, 0);
|
|
|
|
const result = mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
|
|
dependencies,
|
|
reviewedHeads: [HEAD_SHA],
|
|
});
|
|
assert.strictEqual(approvals, 1);
|
|
assert.strictEqual(tupleReads, 2);
|
|
assert.deepStrictEqual(result.approvedRuns.map((run) => run.id), [200]);
|
|
}
|
|
|
|
{
|
|
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
|
|
let approvals = 0;
|
|
const result = mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
|
|
dependencies: approvalDependencies({ approveWorkflowRun() { approvals += 1; } }),
|
|
reviewedHeads: [HEAD_SHA],
|
|
dryRun: true,
|
|
});
|
|
assert.strictEqual(approvals, 0);
|
|
assert.deepStrictEqual(result.approvedRuns, []);
|
|
assert.strictEqual(result.runs.length, 1);
|
|
}
|
|
|
|
{
|
|
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
|
|
let approvals = 0;
|
|
const dependencies = approvalDependencies({
|
|
listActionRequiredRuns() {
|
|
return [runFixture({ pull_requests: [{ number: 999 }] })];
|
|
},
|
|
approveWorkflowRun() { approvals += 1; },
|
|
});
|
|
assert.throws(
|
|
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
|
|
dependencies,
|
|
reviewedHeads: [HEAD_SHA],
|
|
}),
|
|
/does not contain #450/,
|
|
);
|
|
assert.strictEqual(approvals, 0);
|
|
}
|
|
|
|
{
|
|
const calls = [];
|
|
mergeBatch.fetchPullRequestObjects("/repo", BASE_SHA, HEAD_SHA, {
|
|
runCommand(command, args) { calls.push([command, args]); },
|
|
});
|
|
assert.deepStrictEqual(calls[0], [
|
|
"git",
|
|
["fetch", "--no-tags", "--no-write-fetch-head", "origin", BASE_SHA, HEAD_SHA],
|
|
]);
|
|
assert.ok(calls.every(([, args]) => !args.includes("checkout")));
|
|
assert.deepStrictEqual(calls.slice(1).map(([, args]) => args), [
|
|
["cat-file", "-e", `${BASE_SHA}^{commit}`],
|
|
["cat-file", "-e", `${HEAD_SHA}^{commit}`],
|
|
]);
|
|
}
|
|
|
|
{
|
|
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
|
|
let approvals = 0;
|
|
const dependencies = approvalDependencies({
|
|
loadPullRequestDetails() {
|
|
return { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: BASE_SHA };
|
|
},
|
|
approveWorkflowRun() { approvals += 1; },
|
|
});
|
|
assert.throws(
|
|
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
|
|
dependencies,
|
|
reviewedHeads: [HEAD_SHA],
|
|
}),
|
|
/head changed before approvals/,
|
|
);
|
|
assert.strictEqual(approvals, 0);
|
|
}
|
|
|
|
{
|
|
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
|
|
let approvals = 0;
|
|
let blobReads = 0;
|
|
const dependencies = approvalDependencies({
|
|
resolveBlobSizes() { blobReads += 1; return new Map([[BLOB_SHA, 100]]); },
|
|
classifyChangeRecords() {
|
|
return { approvalSafe: false, reasons: ["record_0:new_executable_mode"] };
|
|
},
|
|
approveWorkflowRun() { approvals += 1; },
|
|
});
|
|
assert.throws(
|
|
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
|
|
dependencies,
|
|
reviewedHeads: [HEAD_SHA],
|
|
}),
|
|
/not fork-approval-safe/,
|
|
);
|
|
assert.strictEqual(approvals, 0);
|
|
assert.strictEqual(blobReads, 0, "structurally unsafe diffs must fail before blob expansion");
|
|
}
|
|
|
|
{
|
|
const prDetails = {
|
|
number: 450,
|
|
baseRefName: "main",
|
|
baseRefOid: BASE_SHA,
|
|
headRefOid: HEAD_SHA,
|
|
headRefName: "maintenance/internal",
|
|
headRepository: { nameWithOwner: "OWNER/REPO" },
|
|
};
|
|
let classifications = 0;
|
|
const dependencies = approvalDependencies({
|
|
classifyChangeRecords() {
|
|
classifications += 1;
|
|
return {
|
|
approvalSafe: false,
|
|
reasons: ["record_0:new_unapproved_path"],
|
|
requiresHumanReview: false,
|
|
canonicalSkillChanges: [],
|
|
};
|
|
},
|
|
listActionRequiredRuns() { return []; },
|
|
loadPullRequestDetails() { return prDetails; },
|
|
});
|
|
const result = mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
|
|
dependencies,
|
|
reviewedHeads: [HEAD_SHA],
|
|
});
|
|
assert.strictEqual(result.sameRepository, true);
|
|
assert.strictEqual(classifications, 2);
|
|
assert.deepStrictEqual(result.runs, []);
|
|
}
|
|
|
|
{
|
|
const record = {
|
|
status: "M",
|
|
old_path: "skills/example/SKILL.md",
|
|
new_path: "skills/example/SKILL.md",
|
|
old_mode: "100644",
|
|
new_mode: "100644",
|
|
old_oid: BASE_SHA,
|
|
new_oid: BLOB_SHA,
|
|
similarity: null,
|
|
};
|
|
const report = {
|
|
schema_version: 1,
|
|
base_ref: BASE_SHA,
|
|
head_ref: HEAD_SHA,
|
|
base_oid: BASE_SHA,
|
|
head_oid: HEAD_SHA,
|
|
blocking: false,
|
|
reasons: [],
|
|
changes: [{
|
|
change_type: "modified",
|
|
before: evidenceSnapshot(),
|
|
after: evidenceSnapshot(),
|
|
records: [record],
|
|
}],
|
|
};
|
|
assert.strictEqual(
|
|
mergeBatch.validateChangedSkillEvidence(report, {
|
|
mergeBaseOid: BASE_SHA,
|
|
headOid: HEAD_SHA,
|
|
rawRecords: [record],
|
|
}),
|
|
report,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateChangedSkillEvidence(
|
|
{ ...report, head_oid: BLOB_SHA },
|
|
{ mergeBaseOid: BASE_SHA, headOid: HEAD_SHA, rawRecords: [record] },
|
|
),
|
|
/head_oid does not match/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateChangedSkillEvidence(
|
|
{ ...report, changes: [{ ...report.changes[0], records: [record, record] }] },
|
|
{ mergeBaseOid: BASE_SHA, headOid: HEAD_SHA, rawRecords: [record] },
|
|
),
|
|
/duplicate Git records/,
|
|
);
|
|
const orphan = { ...record, old_path: "skills/orphan/assets/note.md", new_path: "skills/orphan/assets/note.md" };
|
|
assert.throws(
|
|
() => mergeBatch.validateChangedSkillEvidence(
|
|
{ ...report, changes: [] },
|
|
{ mergeBaseOid: BASE_SHA, headOid: HEAD_SHA, rawRecords: [orphan] },
|
|
),
|
|
/exact skill-content Git diff/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateChangedSkillEvidence(
|
|
{ ...report, changes: [{ ...report.changes[0], after: evidenceSnapshot({ audit: { counts: { error: -1, warning: 0, info: 0 } } }) }] },
|
|
{ mergeBaseOid: BASE_SHA, headOid: HEAD_SHA, rawRecords: [record] },
|
|
),
|
|
/missing or invalid/,
|
|
);
|
|
}
|
|
|
|
{
|
|
const protection = {
|
|
required_status_checks: {
|
|
strict: true,
|
|
checks: ["pr-policy", "pr-evidence", "source-validation", "artifact-preview"].map((context) => ({
|
|
context,
|
|
app_id: 15368,
|
|
})),
|
|
},
|
|
enforce_admins: { enabled: true },
|
|
required_pull_request_reviews: { required_approving_review_count: 0 },
|
|
allow_force_pushes: { enabled: false },
|
|
allow_deletions: { enabled: false },
|
|
};
|
|
assert.strictEqual(mergeBatch.validateEffectiveMainProtection(protection, []), true);
|
|
assert.throws(
|
|
() => mergeBatch.validateEffectiveMainProtection(
|
|
{ ...protection, required_status_checks: { strict: false, checks: protection.required_status_checks.checks } },
|
|
[],
|
|
),
|
|
/exact app-bound strict checks/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateEffectiveMainProtection(
|
|
{ ...protection, required_status_checks: { strict: true, checks: [{ context: "pr-policy", app_id: 15368 }] } },
|
|
[],
|
|
),
|
|
/exact app-bound strict checks/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateEffectiveMainProtection(
|
|
{ ...protection, required_pull_request_reviews: null },
|
|
[],
|
|
),
|
|
/through pull requests/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateEffectiveMainProtection(
|
|
{ ...protection, allow_force_pushes: null },
|
|
[],
|
|
),
|
|
/disable force pushes/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateEffectiveMainProtection(
|
|
{
|
|
...protection,
|
|
required_pull_request_reviews: {
|
|
required_approving_review_count: 0,
|
|
bypass_pull_request_allowances: { apps: [{ id: 1 }] },
|
|
},
|
|
},
|
|
[],
|
|
),
|
|
/bypass allowances/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateEffectiveMainProtection(
|
|
protection,
|
|
[{
|
|
id: 9,
|
|
enforcement: "active",
|
|
target: "branch",
|
|
conditions: { ref_name: { include: ["refs/heads/main"], exclude: [] } },
|
|
bypass_actors: [{ actor_id: 1 }],
|
|
}],
|
|
),
|
|
/bypass actors/,
|
|
);
|
|
assert.strictEqual(
|
|
mergeBatch.validateEffectiveMainProtection(protection, [{
|
|
id: 10,
|
|
enforcement: "active",
|
|
target: "tag",
|
|
bypass_actors: [{ actor_id: 1 }],
|
|
}]),
|
|
true,
|
|
"tag rulesets must not block main",
|
|
);
|
|
assert.strictEqual(
|
|
mergeBatch.validateEffectiveMainProtection(protection, [{
|
|
id: 11,
|
|
enforcement: "evaluate",
|
|
target: "branch",
|
|
conditions: { ref_name: { include: ["refs/heads/main"], exclude: [] } },
|
|
bypass_actors: [{ actor_id: 1 }],
|
|
}]),
|
|
true,
|
|
"non-enforcing evaluation rulesets must not block main",
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateEffectiveMainProtection(protection, [{
|
|
id: 12,
|
|
enforcement: "active",
|
|
target: "branch",
|
|
conditions: { ref_name: { include: ["~DEFAULT_BRANCH"], exclude: [] } },
|
|
bypass_actors: [],
|
|
rules: [{ type: "merge_queue" }],
|
|
}]),
|
|
/does not support deferred merge queues/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.validateEffectiveMainProtection(protection, [{
|
|
id: 13,
|
|
enforcement: "active",
|
|
target: "branch",
|
|
conditions: { ref_name: { include: ["refs/heads/ma[i]n"], exclude: [] } },
|
|
bypass_actors: [{ actor_id: 1 }],
|
|
}]),
|
|
/bypass actors/,
|
|
"unsupported fnmatch syntax must fail closed as potentially applicable to main",
|
|
);
|
|
}
|
|
|
|
{
|
|
const calls = [];
|
|
const state = mergeBatch.loadEffectiveMainProtection("/repo", "owner/repo", {
|
|
runGhApiJson(_root, args, options = {}) {
|
|
calls.push([args[0], options]);
|
|
if (args[0].endsWith("/branches/main/protection")) {
|
|
return {
|
|
required_status_checks: {
|
|
strict: true,
|
|
checks: ["pr-policy", "pr-evidence", "source-validation", "artifact-preview"].map((context) => ({ context, app_id: 15368 })),
|
|
},
|
|
enforce_admins: { enabled: true },
|
|
required_pull_request_reviews: { required_approving_review_count: 0 },
|
|
allow_force_pushes: { enabled: false },
|
|
allow_deletions: { enabled: false },
|
|
};
|
|
}
|
|
if (args[0].includes("/rulesets?")) {
|
|
return [[{ id: 101 }], [{ id: 202 }]];
|
|
}
|
|
return { id: Number(args[0].split("/").at(-1)), enforcement: "active", target: "branch" };
|
|
},
|
|
});
|
|
assert.deepStrictEqual(state.rulesets.map((item) => item.id), [101, 202]);
|
|
const paginated = calls.find(([endpoint]) => endpoint.includes("/rulesets?"));
|
|
assert.strictEqual(paginated[1].paginate, true);
|
|
assert.strictEqual(paginated[1].slurp, true);
|
|
}
|
|
|
|
{
|
|
const prDetails = {
|
|
number: 450,
|
|
title: "feat: safe change",
|
|
body: "Summary",
|
|
headRefOid: HEAD_SHA,
|
|
};
|
|
let captured;
|
|
const merged = mergeBatch.mergePullRequestImmediately("/repo", "owner/repo", prDetails, {
|
|
runCommand(command, args, _cwd, options) {
|
|
captured = { command, args, payload: JSON.parse(options.input) };
|
|
return JSON.stringify({ merged: true, sha: BLOB_SHA, message: "Pull Request successfully merged" });
|
|
},
|
|
});
|
|
assert.strictEqual(merged.merged, true);
|
|
assert.strictEqual(captured.command, "gh");
|
|
assert.ok(captured.args.includes("PUT"));
|
|
assert.strictEqual(captured.payload.sha, HEAD_SHA);
|
|
assert.strictEqual(captured.payload.merge_method, "squash");
|
|
assert.throws(
|
|
() => mergeBatch.mergePullRequestImmediately("/repo", "owner/repo", prDetails, {
|
|
runCommand() { return JSON.stringify({ merged: false, message: "queued" }); },
|
|
}),
|
|
/was not merged immediately: queued/,
|
|
);
|
|
}
|
|
|
|
{
|
|
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
|
|
let approvals = 0;
|
|
const dependencies = approvalDependencies({
|
|
recomputeChangedSkillEvidence() {
|
|
return { blocking: true, reasons: ["example:score_decreased:90->80"] };
|
|
},
|
|
approveWorkflowRun() { approvals += 1; },
|
|
});
|
|
assert.throws(
|
|
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
|
|
dependencies,
|
|
reviewedHeads: [HEAD_SHA],
|
|
}),
|
|
/trusted changed-skill evidence is blocking/,
|
|
);
|
|
assert.strictEqual(approvals, 0);
|
|
}
|
|
|
|
{
|
|
const expected = { baseOid: BASE_SHA, headOid: HEAD_SHA };
|
|
assert.throws(
|
|
() => mergeBatch.assertUnchangedTuple(
|
|
{ number: 450, baseRefName: "main", baseRefOid: BLOB_SHA, headRefOid: HEAD_SHA },
|
|
expected,
|
|
"before merge",
|
|
450,
|
|
),
|
|
/base\/head changed before merge/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.pullRequestTuple(
|
|
{ number: 450, baseRefName: "develop", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA },
|
|
),
|
|
/must target main/,
|
|
);
|
|
assert.throws(
|
|
() => mergeBatch.pullRequestTuple({
|
|
number: 450,
|
|
baseRefName: "main",
|
|
baseRefOid: BASE_SHA,
|
|
headRefOid: HEAD_SHA,
|
|
autoMergeRequest: { enabledAt: "2026-07-13T00:00:00Z" },
|
|
}),
|
|
/deferred auto-merge enabled/,
|
|
);
|
|
}
|
|
|
|
console.log("ok");
|