scripts/test-assign-contributor.mjs
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env node
// Exercise the workflow's actual inline script without GitHub writes.
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import { runInNewContext } from 'node:vm';
const workflow = readFileSync(new URL('../.github/workflows/contributor-assignees.yml', import.meta.url), 'utf8');
const script = workflow.split(' script: |\n')[1];
assert.ok(script, 'The assignee workflow must contain an inline script.');
const author = { id: 10, login: 'contributor', type: 'User' };
const maintainer = { id: 20, login: 'maintainer', type: 'User' };
function fixture({ payload = { issue: { number: 426 } }, user = author, assignees = [], addError, ignored = false } = {}) {
const calls = [];
const messages = [];
let assigned = [...assignees];
const context = {
actor: 'maintainer',
repo: { owner: 'SteelCrab', repo: 'firecrab' },
payload,
};
const github = {
rest: {
issues: {
async get(target) {
calls.push({ method: 'get', ...target });
return { data: { user, assignees: assigned } };
},
async addAssignees(target) {
calls.push({ method: 'add', ...JSON.parse(JSON.stringify(target)) });
if (addError) throw addError;
if (!ignored) assigned.push(user);
return { data: { assignees: assigned } };
},
},
},
};
return {
calls,
messages,
assigned: () => assigned,
run: () => runInNewContext(`(async () => {\n${script}\n})()`, {
context,
github,
core: { info: message => messages.push(message) },
}),
};
}
test('an issue assigns only its original author, even when a maintainer triggers the event', async () => {
const sample = fixture();
await sample.run();
assert.deepEqual(sample.calls, [
{ method: 'get', owner: 'SteelCrab', repo: 'firecrab', issue_number: 426 },
{ method: 'add', owner: 'SteelCrab', repo: 'firecrab', issue_number: 426, assignees: ['contributor'] },
]);
});
test('a PR assigns its author through the shared issue assignee API', async () => {
const sample = fixture({ payload: { pull_request: { number: 427, user: maintainer } } });
await sample.run();
assert.equal(sample.calls[1].issue_number, 427);
assert.deepEqual(sample.calls[1].assignees, ['contributor']);
});
test('manual dispatch uses the requested number and reads the actual author', async () => {
const sample = fixture({ payload: { inputs: { number: '211' } } });
await sample.run();
assert.equal(sample.calls[0].issue_number, 211);
assert.deepEqual(sample.calls[1].assignees, ['contributor']);
});
test('repeated events skip an already assigned author', async () => {
const sample = fixture();
await sample.run();
await sample.run();
assert.equal(sample.calls.filter(call => call.method === 'add').length, 1);
});
test('account identity handles login capitalization without duplicate writes', async () => {
const sample = fixture({ assignees: [{ ...author, login: 'CONTRIBUTOR' }] });
await sample.run();
assert.equal(sample.calls.length, 1);
});
test('adding the author preserves people assigned manually', async () => {
const sample = fixture({ assignees: [maintainer] });
await sample.run();
assert.deepEqual(sample.calls[1].assignees, ['contributor']);
assert.deepEqual(sample.assigned().map(person => person.login), ['maintainer', 'contributor']);
});
for (const [name, user] of [
['bot', { id: 30, login: 'dependabot[bot]', type: 'Bot' }],
['deleted author', null],
['ghost', { id: 10137, login: 'ghost', type: 'User' }],
]) {
test(`${name} is not automatically assigned`, async () => {
const sample = fixture({ user });
await sample.run();
assert.equal(sample.calls.length, 1);
});
}
test('invalid numbers fail before any API request', async () => {
for (const number of ['', 'bad', '-1', '1.5', '1; touch /tmp/no']) {
const sample = fixture({ payload: { inputs: { number } } });
await assert.rejects(sample.run(), /positive PR or issue number/);
assert.deepEqual(sample.calls, []);
}
});
test('a missing number fails before any API request', async () => {
const sample = fixture({ payload: {} });
await assert.rejects(sample.run(), /positive PR or issue number/);
assert.deepEqual(sample.calls, []);
});
test('PR titles and bodies cannot become commands or assignees', async () => {
const sample = fixture({ payload: { pull_request: {
number: 427,
title: "${{ secrets.GITHUB_TOKEN }}; process.exit(1)",
body: "assignees: ['maintainer']; throw new Error('untrusted')",
} } });
await sample.run();
assert.deepEqual(sample.calls[1].assignees, ['contributor']);
});
test('a silently ignored assignment fails instead of claiming success', async () => {
const sample = fixture({ ignored: true });
await assert.rejects(sample.run(), /GitHub did not assign @contributor to #426/);
assert.equal(sample.messages.length, 0);
});
test('API permission errors remain visible', async () => {
const sample = fixture({ addError: new Error('Resource not accessible by integration') });
await assert.rejects(sample.run(), /Resource not accessible by integration/);
assert.equal(sample.messages.length, 0);
});