From d63f49faacd17842e04cd178f00428ea74cd6e20 Mon Sep 17 00:00:00 2001 From: quality Date: Sun, 4 Oct 2026 08:34:23 -0400 Subject: [PATCH] test(bundle): drive /assign's refused writes and empty authorized set through dist/index.js Adds __tests__/bundle/assignArms.test.ts covering assign.ts lines 42, 66 and 79 (could not self assign / no authorized users found / could not add assignees) against the fake GitHub. Lines 29 and 61 are not reachable over HTTP and stay out of scope. Closes #301 Signed-off-by: quality --- __tests__/bundle/assignArms.test.ts | 75 +++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 __tests__/bundle/assignArms.test.ts diff --git a/__tests__/bundle/assignArms.test.ts b/__tests__/bundle/assignArms.test.ts new file mode 100644 index 0000000..3529e86 --- /dev/null +++ b/__tests__/bundle/assignArms.test.ts @@ -0,0 +1,75 @@ +import type { FakeGithub } from './fakeGithub' +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest' + +import { start } from './fakeGithub' +import { comment, helpersFor, membershipReads, repo, token } from './helpers' +import { runBundle } from './runBundle' + +vi.setConfig({ testTimeout: 30_000 }) + +// the /assign failure arms, driven through dist/index.js: the happy paths (self-assign, argument users) live in +// bundle.test.ts. The authorization helpers swallow their own API errors, so the only arms reachable over HTTP +// are the refused writes and the empty authorized set +describe('dist/index.js /assign failure arms', () => { + const assigneesWrite = `POST ${repo}/issues/1/assignees` + let gh: FakeGithub + const { calls } = helpersFor(() => gh) + + beforeAll(async () => { + gh = await start() + }) + afterEach(() => gh.reset()) + afterAll(() => gh.close()) + + async function run(body: string) { + return runBundle({ + eventName: 'issue_comment', + payload: comment(body), + inputs: { ...token, 'prow-commands': '/assign' }, + apiUrl: gh.url, + }) + } + + // the three reads checkCommenterAuth / getOrgCollabCommentUsers make for one user, in order + function membershipOrCommentReads(user: string) { + return [...membershipReads(user), `GET ${repo}/issues/1/comments`] + } + + it('fails the action when the self-assign write is refused', async () => { + gh.route('GET', `${repo}/collaborators/Codertocat`, { status: 204 }) + gh.route('POST', `${repo}/issues/1/assignees`, { status: 500, body: { message: 'boom' } }) + + const result = await run('/assign') + + expect(result.status, result.stdout).toBe(1) + expect(result.errors.some(e => e.includes('could not self assign'))).toBe(true) + expect(calls()).toEqual([...membershipOrCommentReads('Codertocat'), assigneesWrite]) + expect(gh.requestsMatching('POST', /assignees$/)[0].body).toEqual({ assignees: ['Codertocat'] }) + }) + + it('fails the action when none of the argument users is authorized, without writing', async () => { + gh.route('GET', `${repo}/issues/1/comments`, { status: 200, body: [] }) + + const result = await run('/assign @stranger @drifter') + + expect(result.status, result.stdout).toBe(1) + expect(result.errors.some(e => e.includes('no authorized users found'))).toBe(true) + expect(gh.requestsMatching('POST', /./)).toEqual([]) + // the two users are authorized concurrently, so their reads have no fixed order among themselves + expect(calls().sort()).toEqual([...membershipOrCommentReads('stranger'), ...membershipOrCommentReads('drifter')].sort()) + }) + + it('fails the action when the argument users\' assignee write is refused', async () => { + gh.route('GET', '/orgs/Codertocat/members/octocat', { status: 204 }) + gh.route('GET', `${repo}/collaborators/octocat`, { status: 404, body: { message: 'Not Found' } }) + gh.route('GET', `${repo}/issues/1/comments`, { status: 200, body: [] }) + gh.route('POST', `${repo}/issues/1/assignees`, { status: 500, body: { message: 'boom' } }) + + const result = await run('/assign @octocat') + + expect(result.status, result.stdout).toBe(1) + expect(result.errors.some(e => e.includes('could not add assignees'))).toBe(true) + expect(calls()).toEqual([...membershipOrCommentReads('octocat'), assigneesWrite]) + expect(gh.requestsMatching('POST', /assignees$/)[0].body).toEqual({ assignees: ['octocat'] }) + }) +})