honcho/.github/scripts/issue-gate.test.js

73 lines
2.8 KiB
JavaScript

'use strict';
// Self-check for the gate decision logic. No framework, no install:
// node .github/scripts/issue-gate.test.js
// Covers checkGate() only — the side-effecting halves (runGate/runSweep) are
// exercised against the real API via `pr-sweeper.yml`'s dry_run dispatch.
const assert = require('node:assert');
const { checkGate, REQUIRED_LABEL, EXEMPT_LABEL } = require('./issue-gate.js');
const pull = (over = {}) => ({
number: 1, state: 'open', draft: false,
user: { type: 'User' }, author_association: 'NONE', labels: [],
...over,
});
// `linked` is the list of issues GitHub resolves as closing references.
const stub = (linked) => ({
graphql: async () => ({
repository: { pullRequest: { closingIssuesReferences: {
nodes: linked.map((i) => ({
number: i.number, state: i.state || 'OPEN',
labels: { nodes: (i.labels || []).map((name) => ({ name })) },
})),
} } },
}),
});
const run = (linked, over) =>
checkGate({ github: stub(linked), owner: 'o', repo: 'r', pr: pull(over) });
const cases = [
['no linked issue fails', () => run([]), (r) => r.passed === false],
['linked but unapproved fails', () => run([{ number: 7 }]), (r) => r.passed === false],
['linked and approved passes',
() => run([{ number: 7, labels: [REQUIRED_LABEL] }]),
(r) => r.passed === true && r.issue === 7],
['approved but closed fails',
() => run([{ number: 7, state: 'CLOSED', labels: [REQUIRED_LABEL] }]),
(r) => r.passed === false],
['picks the approved one out of several',
() => run([{ number: 7 }, { number: 8, labels: [REQUIRED_LABEL] }]),
(r) => r.passed === true && r.issue === 8],
// Exemptions.
['maintainer skips', () => run([], { author_association: 'MEMBER' }), (r) => r.passed === true],
['collaborator skips', () => run([], { author_association: 'COLLABORATOR' }), (r) => r.passed === true],
['bot skips', () => run([], { user: { type: 'Bot' } }), (r) => r.passed === true],
['draft skips', () => run([], { draft: true }), (r) => r.passed === true],
[`${EXEMPT_LABEL} skips`, () => run([], { labels: [{ name: EXEMPT_LABEL }] }), (r) => r.passed === true],
// Regression guard: GitHub hands CONTRIBUTOR to anyone who has previously
// committed, i.e. every returning outside contributor. It must stay gated.
['CONTRIBUTOR is still gated',
() => run([], { author_association: 'CONTRIBUTOR' }),
(r) => r.passed === false],
];
(async () => {
let failed = 0;
for (const [name, thunk, ok] of cases) {
const result = await thunk();
if (ok(result)) {
console.log(` ok ${name}`);
} else {
failed++;
console.log(` FAIL ${name} -> ${JSON.stringify(result)}`);
}
}
assert.strictEqual(failed, 0, `${failed} case(s) failed`);
console.log(`\n${cases.length} passed`);
})();