paperclip/.github/scripts/tests/check-pr-coauthors.test.mjs

180 lines
6.7 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { checkCoauthors, fetchAllPullRequestCommits } from '../check-pr-coauthors.mjs';
function commit(login, name = null, email = null) {
return {
author: login ? { login } : null,
commit: { author: { name: name ?? login, email: email ?? `${login}@users.noreply.github.com` } },
};
}
test('checkCoauthors: says nothing when every commit is the PR author\'s own', () => {
const result = checkCoauthors(
[commit('tonio-alucema'), commit('tonio-alucema')],
'tonio-alucema'
);
assert.equal(result.passed, true);
assert.deepEqual(result.informational, []);
});
test('checkCoauthors: hands over the trailer when the branch carries someone else\'s commit', () => {
// The case this exists for: a stale contributor PR rebased and landed by a
// maintainer. Squash-merging drops the contributor unless the squash body
// carries their trailer.
const result = checkCoauthors(
[commit('stubbi', 'Jannes Stubbemann'), commit('tonio-alucema')],
'tonio-alucema'
);
assert.equal(result.informational.length, 1);
assert.match(result.informational[0], /Jannes Stubbemann/);
assert.match(
result.informational[0],
/Co-Authored-By: Jannes Stubbemann <stubbi@users\.noreply\.github\.com>/
);
});
test('checkCoauthors: never fails the PR, because the squash message does not exist yet', () => {
// Informational only. The author of the PR cannot satisfy this from the PR,
// so failing here would block work on something unfixable at that point.
const result = checkCoauthors([commit('stubbi')], 'tonio-alucema');
assert.equal(result.passed, true);
});
test('checkCoauthors: matches the PR author case-insensitively', () => {
// GitHub logins are case-insensitive, and PR_AUTHOR does not always arrive
// in the same case as the commit author login.
const result = checkCoauthors([commit('Tonio-Alucema')], 'tonio-alucema');
assert.deepEqual(result.informational, []);
});
test('checkCoauthors: ignores bots', () => {
const result = checkCoauthors(
[commit('github-actions[bot]'), commit('dependabot[bot]')],
'tonio-alucema'
);
assert.deepEqual(result.informational, []);
});
test('checkCoauthors: lists each contributor once, however many commits they wrote', () => {
const result = checkCoauthors(
[commit('stubbi', 'Jannes Stubbemann'), commit('stubbi', 'Jannes Stubbemann')],
'tonio-alucema'
);
const trailers = result.informational[0].match(/Co-Authored-By:/g) ?? [];
assert.equal(trailers.length, 1);
});
test('checkCoauthors: falls back to the raw git author when GitHub matched no account', () => {
// A commit authored with an email GitHub cannot resolve still deserves a
// trailer — that is precisely the identity most likely to be lost.
const result = checkCoauthors(
[{ author: null, commit: { author: { name: 'Ada Lovelace', email: 'ada@example.com' } } }],
'tonio-alucema'
);
assert.match(result.informational[0], /Co-Authored-By: Ada Lovelace <ada@example\.com>/);
});
test('checkCoauthors: skips an unattributable commit rather than emitting a broken trailer', () => {
const result = checkCoauthors(
[{ author: null, commit: { author: { name: 'Nameless', email: null } } }],
'tonio-alucema'
);
assert.deepEqual(result.informational, []);
});
test('checkCoauthors: names the count rather than everyone when several contributed', () => {
const result = checkCoauthors(
[commit('stubbi', 'Jannes Stubbemann'), commit('elJayAdvisor', 'LJ')],
'tonio-alucema'
);
assert.match(result.informational[0], /2 other contributors/);
assert.match(result.informational[0], /Jannes Stubbemann/);
assert.match(result.informational[0], /LJ/);
});
test('checkCoauthors: tolerates a PR with no commits', () => {
assert.deepEqual(checkCoauthors([], 'tonio-alucema').informational, []);
assert.deepEqual(checkCoauthors(undefined, 'tonio-alucema').informational, []);
});
test('fetchAllPullRequestCommits: pages until a short batch', async () => {
const seen = [];
const commits = await fetchAllPullRequestCommits(async (path) => {
seen.push(path);
if (path.endsWith('page=1')) return Array.from({ length: 100 }, () => commit('stubbi'));
return [commit('tonio-alucema')];
}, 'paperclipai/paperclip', 9900, 'token');
assert.equal(commits.length, 101);
assert.equal(seen.length, 2);
});
test('fetchAllPullRequestCommits: stops at the API ceiling instead of looping', async () => {
// `/pulls/{n}/commits` caps at 250 and keeps returning full pages of nothing
// new past that. A branch that large is not what this gate is about, but it
// must not spin.
let calls = 0;
const commits = await fetchAllPullRequestCommits(async () => {
calls += 1;
return Array.from({ length: 100 }, () => commit('stubbi'));
}, 'paperclipai/paperclip', 9900, 'token');
assert.equal(calls, 3);
assert.equal(commits.length, 300);
});
test('checkCoauthors: counts one person once when their git name varies across commits', () => {
// People change their git config. Keying the dedup on the rendered trailer
// would put two lines for the same contributor into the squash body.
const result = checkCoauthors(
[commit('stubbi', 'Jannes Stubbemann'), commit('stubbi', 'J. Stubbemann')],
'tonio-alucema'
);
const trailers = result.informational[0].match(/Co-Authored-By:/g) ?? [];
assert.equal(trailers.length, 1);
assert.doesNotMatch(result.informational[0], /other contributors/);
});
test('checkCoauthors: does not credit the PR author as a co-author of themselves', () => {
// Their own commit, authored with an email GitHub could not match to the
// account. Without the guard they appear in their own trailer list.
const byName = checkCoauthors(
[{ author: null, commit: { author: { name: 'tonio-alucema', email: 'tonio@example.com' } } }],
'tonio-alucema'
);
const byEmail = checkCoauthors(
[{ author: null, commit: { author: { name: 'Tonio', email: 'tonio-alucema@users.noreply.github.com' } } }],
'tonio-alucema'
);
assert.deepEqual(byName.informational, []);
assert.deepEqual(byEmail.informational, []);
});
test('checkCoauthors: counts one person once when some commits matched their account and some did not', () => {
// The mixed case: GitHub resolved one commit to the login and left another
// unmatched, both carrying the same email. Keying on login alone emits two
// trailers for one contributor.
const result = checkCoauthors(
[
commit('stubbi', 'Jannes Stubbemann', 'jannes@example.com'),
{ author: null, commit: { author: { name: 'Jannes Stubbemann', email: 'jannes@example.com' } } },
],
'tonio-alucema'
);
const trailers = result.informational[0].match(/Co-Authored-By:/g) ?? [];
assert.equal(trailers.length, 1);
});