From 3bc30fbacdae7bda8a5f6373bc00e79ef6f82c5c Mon Sep 17 00:00:00 2001 From: "hivecommons-hive[bot]" Date: Sun, 27 Sep 2026 08:16:01 -0400 Subject: [PATCH] test(plugins): cover the error and fallback branches of lgtmBinding, ownersLabel and requireMatchingLabel Adds __tests__/plugins/pluginErrorPaths.test.ts (5 tests): - lgtmOnPullRequest binds as 'unknown' with no target_url when the labeled payload has no sender and no html_url (lgtmBinding.ts:177) - ownersLabel fails with 'could not list the repository labels' when GET /labels returns 500 (ownersLabel.ts:70) - requireMatchingLabel fails without an issue or pull request in the payload (requireMatchingLabel.ts:184) - requireMatchingLabel names the rule when GET /issues/:n/comments returns 500 (requireMatchingLabel.ts:245) - a bot comment with body: null is treated as unmarked (requireMatchingLabel.ts:248) lgtmBinding.ts and ownersLabel.ts reach 100% lines and branches; requireMatchingLabel.ts 100% lines, 98.46% branches. Signed-off-by: hivecommons-hive[bot] --- __tests__/plugins/pluginErrorPaths.test.ts | 114 +++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 __tests__/plugins/pluginErrorPaths.test.ts diff --git a/__tests__/plugins/pluginErrorPaths.test.ts b/__tests__/plugins/pluginErrorPaths.test.ts new file mode 100644 index 00000000..38618b96 --- /dev/null +++ b/__tests__/plugins/pluginErrorPaths.test.ts @@ -0,0 +1,114 @@ +import { Buffer } from 'node:buffer' +import { http } from 'msw' +import { setupServer } from 'msw/node' +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest' + +import { lgtmOnPullRequest } from '../../src/plugins/lgtmBinding' +import { ownersLabel } from '../../src/plugins/ownersLabel' +import { requireMatchingLabel } from '../../src/plugins/requireMatchingLabel' +import issuesLabeledEvent from '../fixtures/issues/issuesLabeledEvent.json' +import labelFileContents from '../fixtures/labels/labelFileContentsResp.json' +import pullReqOpenedEvent from '../fixtures/pullReq/pullReqOpenedEvent.json' +import * as utils from '../testUtils' +import { prHandlers } from '../utils/ownersFixtures' + +const server = setupServer() +beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) +beforeEach(() => utils.setupActionsEnv()) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) + +const repo = `${utils.api}/repos/Codertocat/Hello-World` +const headSha = pullReqOpenedEvent.pull_request.head.sha + +describe('lgtmOnPullRequest without a sender', () => { + it('binds the label as "unknown" and omits the target url when the payload carries neither', async () => { + server.use(...utils.noOrgOrRepoConfigExcept()) + const status = new utils.ObserveRequest() + server.use(http.post(`${repo}/statuses/${headSha}`, utils.mockResponse(201, {}, status))) + + const { html_url: _url, ...pullRequest } = pullReqOpenedEvent.pull_request + const payload = { ...pullReqOpenedEvent, action: 'labeled', label: { name: 'lgtm' }, pull_request: pullRequest } as Record + delete payload.sender + + await expect(lgtmOnPullRequest(new utils.MockContext(payload))).resolves.toBeUndefined() + + await expect(status.called()).resolves.toBe('called') + expect(await status.body()).toEqual({ + state: 'success', + context: 'prow/lgtm', + description: `lgtm by unknown at ${headSha.slice(0, 7)}`, + }) + }) +}) + +describe('ownersLabel when the repository labels cannot be listed', () => { + it('fails with the listing error', async () => { + server.use( + ...prHandlers({ 'sdk/OWNERS': 'labels:\n- area/sdk\n' }, ['sdk/x.go']), + http.get(`${repo}/issues/1`, utils.mockResponse(200, { labels: [] })), + http.get(`${repo}/labels`, utils.mockResponse(500, { message: 'boom' })), + ) + + await expect(ownersLabel(new utils.MockContext({ ...structuredClone(pullReqOpenedEvent), action: 'opened' }))) + .rejects + .toThrow(/^could not list the repository labels: .*boom/) + }) +}) + +describe('requireMatchingLabel error paths', () => { + const project = 'Codertocat/.project:prow.yaml' + + function serveKindRule() { + const file = structuredClone(labelFileContents) + file.content = Buffer.from( + 'require_matching_label:\n - { regexp: "^kind/", missing_label: needs-kind, missing_comment: "Please add a kind label." }\n', + ).toString('base64') + server.use( + http.get(utils.contentsUrl(project), utils.mockResponse(200, file)), + ...utils.noOrgOrRepoConfigExcept(project), + ) + } + + it('fails without an issue or pull request in the payload', async () => { + await expect(requireMatchingLabel(new utils.MockContext({ action: 'opened' }))).rejects.toThrow( + 'github context payload missing issue or pull request', + ) + }) + + it('names the rule when the comments cannot be listed', async () => { + serveKindRule() + const payload = structuredClone(issuesLabeledEvent) + payload.action = 'opened' + payload.issue.labels = [] + server.use( + http.get(`${repo}/issues/1`, utils.mockResponse(200, { labels: [] })), + utils.repoHasLabels(['needs-kind']), + http.post(`${repo}/issues/1/labels`, utils.mockResponse(200, [])), + http.get(`${repo}/issues/1/comments`, utils.mockResponse(500, { message: 'boom' })), + ) + + await expect(requireMatchingLabel(new utils.MockContext(payload))).rejects.toThrow( + /^require-matching-label needs-kind: could not list comments: .*boom/, + ) + }) + + it('treats a bot comment without a body as unmarked and deletes nothing', async () => { + serveKindRule() + const payload = structuredClone(issuesLabeledEvent) + payload.action = 'labeled' + payload.label = { ...payload.label, name: 'kind/bug' } + const deleteComment = new utils.ObserveRequest() + server.use( + http.get(`${repo}/issues/1`, utils.mockResponse(200, { labels: [{ name: 'kind/bug' }, { name: 'needs-kind' }] })), + utils.repoHasLabels(['needs-kind', 'kind/bug']), + http.delete(`${repo}/issues/1/labels/:name`, utils.mockResponse(200, [])), + http.get(`${repo}/issues/1/comments`, utils.mockResponse(200, [{ id: 11, body: null, user: { login: 'github-actions[bot]', type: 'Bot' } }])), + http.delete(`${repo}/issues/comments/:id`, utils.mockResponse(204, null, deleteComment)), + ) + + await expect(requireMatchingLabel(new utils.MockContext(payload))).resolves.toBeUndefined() + + await expect(deleteComment.notCalled()).resolves.toBe('not called') + }) +})