From 0c046cb35f49de812e3c1d3287eb50a22141a362 Mon Sep 17 00:00:00 2001 From: quality Date: Sun, 4 Oct 2026 04:27:41 -0400 Subject: [PATCH 1/3] test(bundle): drive owners-label's early-return and failure arms through dist/index.js Adds __tests__/bundle/ownersLabelArms.test.ts: five pull_request synchronize runs against the fake GitHub covering the arms of src/plugins/ownersLabel.ts the bundle suite never reached - already carrying every declared label, labels the repository lacks, a known/unknown mix, a failing repository label list and a payload without a pull request. npm run test:coverage:e2e: ownersLabel.ts 80 -> 100 % lines, 73.33 -> 100 % branches. Signed-off-by: quality --- __tests__/bundle/ownersLabelArms.test.ts | 136 +++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 __tests__/bundle/ownersLabelArms.test.ts diff --git a/__tests__/bundle/ownersLabelArms.test.ts b/__tests__/bundle/ownersLabelArms.test.ts new file mode 100644 index 00000000..591b5be8 --- /dev/null +++ b/__tests__/bundle/ownersLabelArms.test.ts @@ -0,0 +1,136 @@ +import type { FakeGithub } from './fakeGithub' +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' + +import pullReqOpenedEvent from '../fixtures/pullReq/pullReqOpenedEvent.json' +import { blobSha } from '../utils/ownersFixtures' +import { start } from './fakeGithub' +import { helpersFor, ownersProbe, ownersReads, repo, token } from './helpers' +import { runBundle } from './runBundle' + +vi.setConfig({ testTimeout: 30_000 }) + +// the owners-label arms that stop short of a label write, driven through dist/index.js on +// `synchronize` (require-matching-label, blunderbuss and tide all skip that action, so the +// call list is the OWNERS reads, the plugin's own reads and approve's probe) +describe('dist/index.js owners-label', () => { + const ownersFiles: Record = { + 'OWNERS': 'reviewers:\n- alice\n', + 'sdk/OWNERS': 'reviewers:\n- bob\nlabels:\n- area/sdk\n- area/docs\n', + } + const ownersBlobs = [`GET ${repo}/git/blobs/${blobSha('OWNERS')}`, `GET ${repo}/git/blobs/${blobSha('sdk/OWNERS')}`].sort() + const issueRead = `GET ${repo}/issues/1` + const labelsRead = `GET ${repo}/labels?per_page=100` + const labelsPost = `POST ${repo}/issues/1/labels` + let gh: FakeGithub + const { calls, routeOwners } = helpersFor(() => gh) + + beforeAll(async () => { + gh = await start() + }) + beforeEach(() => gh.mergeQueueFallback({ pullRequestId: 'PR_none', headOid: pullReqOpenedEvent.pull_request.head.sha, enabled: false })) + afterEach(() => gh.reset()) + afterAll(() => gh.close()) + + function synchronize() { + return runBundle({ + eventName: 'pull_request', + payload: { ...pullReqOpenedEvent, action: 'synchronize' }, + inputs: token, + apiUrl: gh.url, + }) + } + + function routeIssueLabels(...names: string[]) { + gh.route('GET', `${repo}/issues/1`, { status: 200, body: { labels: names.map(name => ({ name })) } }) + } + + function routeRepoLabels(...names: string[]) { + gh.route('GET', `${repo}/labels`, { status: 200, body: names.map(name => ({ name })) }) + } + + function labelPosts() { + return gh.requestsMatching('POST', /\/issues\/1\/labels$/).map(r => r.body) + } + + it('already carrying every declared label (compared case-insensitively): reads nothing more and writes nothing', async () => { + routeOwners(ownersFiles, ['sdk/x.go']) + routeIssueLabels('Area/SDK', 'AREA/docs', 'kind/bug') + routeRepoLabels('area/sdk', 'area/docs') + + const result = await synchronize() + + expect(result.status, result.stdout).toBe(0) + expect(result.errors).toEqual([]) + expect(labelPosts()).toEqual([]) + const recorded = calls() + expect(recorded.slice(0, 4)).toEqual(ownersReads) + expect(recorded.slice(4, 6).sort()).toEqual(ownersBlobs) + // the repository's label list is never consulted once nothing is missing + expect(recorded.slice(6)).toEqual([issueRead, ownersProbe]) + }) + + it('declared labels the repository does not have: logs each skip and writes nothing', async () => { + routeOwners(ownersFiles, ['sdk/x.go']) + routeIssueLabels() + routeRepoLabels('kind/bug') + + const result = await synchronize() + + expect(result.status, result.stdout).toBe(0) + expect(result.errors).toEqual([]) + expect(labelPosts()).toEqual([]) + expect(result.stdout).toContain(`owners-label: skipping label area/sdk declared in OWNERS: repository doesn't have it (run label-sync)`) + expect(result.stdout).toContain(`owners-label: skipping label area/docs declared in OWNERS: repository doesn't have it (run label-sync)`) + expect(calls().slice(6)).toEqual([issueRead, labelsRead, ownersProbe]) + }) + + it('a mix of known and unknown declared labels: adds only the known ones', async () => { + routeOwners(ownersFiles, ['sdk/x.go']) + routeIssueLabels('kind/bug') + routeRepoLabels('kind/bug', 'Area/Docs') + gh.route('POST', `${repo}/issues/1/labels`, { status: 200, body: [] }) + + const result = await synchronize() + + expect(result.status, result.stdout).toBe(0) + expect(result.errors).toEqual([]) + // the repository's spelling decides whether a label is known, the OWNERS spelling is what gets applied + expect(labelPosts()).toEqual([{ labels: ['area/docs'] }]) + expect(result.stdout).toContain(`owners-label: skipping label area/sdk declared in OWNERS`) + expect(result.stdout).not.toContain(`skipping label area/docs`) + expect(calls().slice(6)).toEqual([issueRead, labelsRead, labelsPost, ownersProbe]) + }) + + it('a failing repository label list fails the run with the cause and still lets the later handlers run', async () => { + routeOwners(ownersFiles, ['sdk/x.go']) + routeIssueLabels() + gh.route('GET', `${repo}/labels`, { status: 500, body: { message: 'boom' } }) + + const result = await synchronize() + + expect(result.status, result.stdout).toBe(1) + expect(result.errors).toHaveLength(1) + expect(result.errors[0]).toMatch(/^error handling pull_request event: could not list the repository labels: /) + expect(result.errors[0]).toContain('boom') + expect(labelPosts()).toEqual([]) + // approve's default-branch probe follows regardless: the handlers run in order and the rejections are collected + expect(calls().slice(6)).toEqual([issueRead, labelsRead, ownersProbe]) + }) + + it('a pull_request payload without a pull request fails the run naming the payload', async () => { + const { pull_request: _pr, ...withoutPullRequest } = pullReqOpenedEvent + + const result = await runBundle({ + eventName: 'pull_request', + payload: { ...withoutPullRequest, action: 'synchronize' }, + inputs: token, + apiUrl: gh.url, + }) + + expect(result.status, result.stdout).toBe(1) + expect(result.errors).toHaveLength(1) + expect(result.errors[0]).toContain('error handling pull_request event: ') + expect(result.errors[0]).toContain('github context payload missing pull request: ') + expect(calls()).toEqual([]) + }) +}) From 60eb572ea69e6f9bdd87fe20e6ee632e7776c678 Mon Sep 17 00:00:00 2001 From: quality Date: Wed, 7 Oct 2026 22:12:15 -0400 Subject: [PATCH 2/3] test(bundle): drop the known/unknown declared-labels mix case Signed-off-by: quality --- __tests__/bundle/ownersLabelArms.test.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/__tests__/bundle/ownersLabelArms.test.ts b/__tests__/bundle/ownersLabelArms.test.ts index 591b5be8..969a1471 100644 --- a/__tests__/bundle/ownersLabelArms.test.ts +++ b/__tests__/bundle/ownersLabelArms.test.ts @@ -20,7 +20,6 @@ describe('dist/index.js owners-label', () => { const ownersBlobs = [`GET ${repo}/git/blobs/${blobSha('OWNERS')}`, `GET ${repo}/git/blobs/${blobSha('sdk/OWNERS')}`].sort() const issueRead = `GET ${repo}/issues/1` const labelsRead = `GET ${repo}/labels?per_page=100` - const labelsPost = `POST ${repo}/issues/1/labels` let gh: FakeGithub const { calls, routeOwners } = helpersFor(() => gh) @@ -84,23 +83,6 @@ describe('dist/index.js owners-label', () => { expect(calls().slice(6)).toEqual([issueRead, labelsRead, ownersProbe]) }) - it('a mix of known and unknown declared labels: adds only the known ones', async () => { - routeOwners(ownersFiles, ['sdk/x.go']) - routeIssueLabels('kind/bug') - routeRepoLabels('kind/bug', 'Area/Docs') - gh.route('POST', `${repo}/issues/1/labels`, { status: 200, body: [] }) - - const result = await synchronize() - - expect(result.status, result.stdout).toBe(0) - expect(result.errors).toEqual([]) - // the repository's spelling decides whether a label is known, the OWNERS spelling is what gets applied - expect(labelPosts()).toEqual([{ labels: ['area/docs'] }]) - expect(result.stdout).toContain(`owners-label: skipping label area/sdk declared in OWNERS`) - expect(result.stdout).not.toContain(`skipping label area/docs`) - expect(calls().slice(6)).toEqual([issueRead, labelsRead, labelsPost, ownersProbe]) - }) - it('a failing repository label list fails the run with the cause and still lets the later handlers run', async () => { routeOwners(ownersFiles, ['sdk/x.go']) routeIssueLabels() From 9650d0e28af92dbd49d1b86faf15d0b5b68fefe5 Mon Sep 17 00:00:00 2001 From: quality Date: Thu, 8 Oct 2026 22:51:46 -0400 Subject: [PATCH 3/3] test(bundle): route the repository label list through the shared repoLabels helper Signed-off-by: quality --- __tests__/bundle/ownersLabelArms.test.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/__tests__/bundle/ownersLabelArms.test.ts b/__tests__/bundle/ownersLabelArms.test.ts index 969a1471..cc41e673 100644 --- a/__tests__/bundle/ownersLabelArms.test.ts +++ b/__tests__/bundle/ownersLabelArms.test.ts @@ -4,7 +4,7 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } import pullReqOpenedEvent from '../fixtures/pullReq/pullReqOpenedEvent.json' import { blobSha } from '../utils/ownersFixtures' import { start } from './fakeGithub' -import { helpersFor, ownersProbe, ownersReads, repo, token } from './helpers' +import { helpersFor, ownersProbe, ownersReads, repo, repoLabels, token } from './helpers' import { runBundle } from './runBundle' vi.setConfig({ testTimeout: 30_000 }) @@ -43,10 +43,6 @@ describe('dist/index.js owners-label', () => { gh.route('GET', `${repo}/issues/1`, { status: 200, body: { labels: names.map(name => ({ name })) } }) } - function routeRepoLabels(...names: string[]) { - gh.route('GET', `${repo}/labels`, { status: 200, body: names.map(name => ({ name })) }) - } - function labelPosts() { return gh.requestsMatching('POST', /\/issues\/1\/labels$/).map(r => r.body) } @@ -54,7 +50,7 @@ describe('dist/index.js owners-label', () => { it('already carrying every declared label (compared case-insensitively): reads nothing more and writes nothing', async () => { routeOwners(ownersFiles, ['sdk/x.go']) routeIssueLabels('Area/SDK', 'AREA/docs', 'kind/bug') - routeRepoLabels('area/sdk', 'area/docs') + gh.route('GET', `${repo}/labels`, repoLabels('area/sdk', 'area/docs')) const result = await synchronize() @@ -71,7 +67,7 @@ describe('dist/index.js owners-label', () => { it('declared labels the repository does not have: logs each skip and writes nothing', async () => { routeOwners(ownersFiles, ['sdk/x.go']) routeIssueLabels() - routeRepoLabels('kind/bug') + gh.route('GET', `${repo}/labels`, repoLabels('kind/bug')) const result = await synchronize()