From bd27f934d39b775365cf229e719fd6b32108f25b Mon Sep 17 00:00:00 2001 From: Gavan Lamb Date: Wed, 12 Jun 2024 21:23:40 +1000 Subject: [PATCH] feat(GH-8): Add client for GitHub checks --- __tests__/client/githubChecksClient.test.ts | 102 ++++++++++++++++++++ src/clients/githubChecksClient.ts | 58 +++++++++++ 2 files changed, 160 insertions(+) create mode 100644 __tests__/client/githubChecksClient.test.ts create mode 100644 src/clients/githubChecksClient.ts diff --git a/__tests__/client/githubChecksClient.test.ts b/__tests__/client/githubChecksClient.test.ts new file mode 100644 index 0000000..72074cb --- /dev/null +++ b/__tests__/client/githubChecksClient.test.ts @@ -0,0 +1,102 @@ +import { CheckRunStatus } from "../../src/types/checkRunStatus"; +import { CheckConclusion } from "../../src/types/checkConclusion"; + +describe("createCheck", () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + + it("should not suppress exceptions if client code throws", async () => { + const errorMessage = "Error encountered when calling octokit client"; + const createOctokitClientMock = jest.fn(() => { + throw new Error(errorMessage); + }); + jest.doMock("../../src/clients/octokit", () => ({ createOctokitClient: createOctokitClientMock })); + + const { createCheck } = await import("../../src/clients/githubChecksClient"); + const exec = createCheck( + "owner", + "repo", + "check-name", + "headSha", + CheckRunStatus.Completed, + CheckConclusion.Success, + "Check Title", + "Check Body" + ); + + await expect(exec).rejects.toThrow(errorMessage); + expect(createOctokitClientMock).toHaveBeenCalledTimes(1); + }); + + it("should throw an error if the check run creation fails", async () => { + const debugMock = jest.fn(); + jest.doMock("@actions/core", () => ({debug: debugMock})); + + const createOctokitClientMock = jest.fn(() => ({rest:{ checks:{ create:jest.fn(() => ({ status: 500 }))}}})); + jest.doMock("../../src/clients/octokit", () => ({createOctokitClient: createOctokitClientMock})); + + const owner = "owner"; + const repo = "repo"; + const headSha = "headSha"; + + const { createCheck } = await import("../../src/clients/githubChecksClient"); + const exec = createCheck( + owner, + repo, + "check-name", + headSha, + CheckRunStatus.Completed, + CheckConclusion.Success, + "Check Title", + "Check Body" + ); + + await expect(exec).rejects.toThrow(`Failed to create the check run for:\n\towner: ${owner}\n\trepo: ${repo}\n\theadSha: ${headSha}`); + expect(createOctokitClientMock).toHaveBeenCalledTimes(1); + expect(debugMock).toHaveBeenCalledWith("Going to call the rest endpoint to delete an issue comment with the following details:\n" + + "\towner: owner\n" + + "\trepo: repo\n" + + "\tname: check-name\n" + + "\theadSha: headSha\n" + + "\tstatus: completed\n" + + "\tconclusion: success\n" + + "\ttitle: Check Title\n" + + "\tbody: Check Body"); + expect(debugMock).toHaveBeenCalledWith("Called the rest endpoint to create check run"); + }); + + it("should create a check run successfully", async () => { + const debugMock = jest.fn(); + jest.doMock("@actions/core", () => ({debug: debugMock})); + + const createOctokitClientMock = jest.fn(() => ({ rest:{ checks:{ create: jest.fn(() => ({ status: 201 })) }}})); + jest.doMock("../../src/clients/octokit", () => ({createOctokitClient: createOctokitClientMock})); + + const { createCheck } = await import("../../src/clients/githubChecksClient"); + await createCheck( + "owner", + "repo", + "check-name", + "headSha", + CheckRunStatus.Completed, + CheckConclusion.Success, + "Check Title", + "Check Body" + ); + + expect(createOctokitClientMock).toHaveBeenCalledTimes(1); + expect(debugMock).toHaveBeenCalledWith("Going to call the rest endpoint to delete an issue comment with the following details:\n" + + "\towner: owner\n" + + "\trepo: repo\n" + + "\tname: check-name\n" + + "\theadSha: headSha\n" + + "\tstatus: completed\n" + + "\tconclusion: success\n" + + "\ttitle: Check Title\n" + + "\tbody: Check Body"); + expect(debugMock).toHaveBeenCalledWith("Called the rest endpoint to create check run"); + expect(debugMock).toHaveBeenCalledWith("Check run created successfully."); + }); +}); diff --git a/src/clients/githubChecksClient.ts b/src/clients/githubChecksClient.ts new file mode 100644 index 0000000..79fc3e3 --- /dev/null +++ b/src/clients/githubChecksClient.ts @@ -0,0 +1,58 @@ +import { debug } from "@actions/core"; +import { CheckRunStatus } from "../types/checkRunStatus"; +import { CheckConclusion } from "../types/checkConclusion"; +import { createOctokitClient } from "./octokit"; +/** + * Create an issue comment on a PR + * @param owner owner of the repo + * @param repo repo name + * @param name the name of the check run + * @param headSha headSha + * @param status the status of the check + * @param conclusion status of the conclusion + * @param title title of the check run + * @param body the body of the check run + * @returns {Promise} Resolves when the action is complete + * @throws Error when the response indicates the request was unsuccessful. + */ +async function createCheck( + owner: string, + repo: string, + name: string, + headSha: string, + status: CheckRunStatus, + conclusion: CheckConclusion, + title: string, + body: string): Promise +{ + const client = await createOctokitClient(); + + debug(`Going to call the rest endpoint to delete an issue comment with the following details:\n\towner: ${owner}\n\trepo: ${repo}\n\tname: ${name}\n\theadSha: ${headSha}\n\tstatus: ${status}\n\tconclusion: ${conclusion}\n\ttitle: ${title}\n\tbody: ${body}`); + const response = await client.rest.checks.create({ + owner, + repo, + name, + head_sha: headSha, + details_url: undefined, + external_id: undefined, + status, + started_at: Date.now(), + conclusion, + completed_at: Date.now(), + output: { + title, + summary: body, + text: body + } + }); + debug('Called the rest endpoint to create check run'); + + if(response.status === 201) + debug('Check run created successfully.'); + else + throw new Error(`Failed to create the check run for:\n\towner: ${owner}\n\trepo: ${repo}\n\theadSha: ${headSha}`); +} + +export { + createCheck +};