diff --git a/__mocks__/@actions/github.ts b/__mocks__/@actions/github.ts new file mode 100644 index 0000000..5ff9a86 --- /dev/null +++ b/__mocks__/@actions/github.ts @@ -0,0 +1,32 @@ +export const context = { + payload: { + pull_request: { + number: 123, + }, + }, + repo: { + owner: "monalisa", + repo: "helloworld", + }, +}; + +const mockApi = { + issues: { + addLabels: jest.fn(), + removeLabel: jest.fn(), + }, + paginate: jest.fn(), + pulls: { + get: jest.fn().mockResolvedValue({}), + listFiles: { + endpoint: { + merge: jest.fn().mockReturnValue({}), + }, + }, + }, + repos: { + getContents: jest.fn(), + }, +}; + +export const GitHub = jest.fn().mockImplementation(() => mockApi); diff --git a/__tests__/fixtures/only_pdfs.yml b/__tests__/fixtures/only_pdfs.yml new file mode 100644 index 0000000..1bcce76 --- /dev/null +++ b/__tests__/fixtures/only_pdfs.yml @@ -0,0 +1,2 @@ +touched-a-pdf-file: + - any: ['*.pdf'] diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts new file mode 100644 index 0000000..f4e895d --- /dev/null +++ b/__tests__/main.test.ts @@ -0,0 +1,116 @@ +import { run } from "../src/labeler"; +import { GitHub } from "@actions/github"; +import * as core from "@actions/core"; + +const fs = jest.requireActual("fs"); + +jest.mock("@actions/core"); +jest.mock("@actions/github"); + +const gh = new GitHub("_"); +const addLabelsMock = jest.spyOn(gh.issues, "addLabels"); +const removeLabelMock = jest.spyOn(gh.issues, "removeLabel"); +const reposMock = jest.spyOn(gh.repos, "getContents"); +const paginateMock = jest.spyOn(gh, "paginate"); +const getPullMock = jest.spyOn(gh.pulls, "get"); + +const yamlFixtures = { + "only_pdfs.yml": fs.readFileSync("__tests__/fixtures/only_pdfs.yml"), +}; + +afterAll(() => jest.restoreAllMocks()); + +describe("run", () => { + it("adds labels to PRs that match our glob patterns", async () => { + usingLabelerConfigYaml("only_pdfs.yml"); + mockGitHubResponseChangedFiles("foo.pdf"); + + await run(); + + expect(removeLabelMock).toHaveBeenCalledTimes(0); + expect(addLabelsMock).toHaveBeenCalledTimes(1); + expect(addLabelsMock).toHaveBeenCalledWith({ + owner: "monalisa", + repo: "helloworld", + issue_number: 123, + labels: ["touched-a-pdf-file"], + }); + }); + + it("does not add labels to PRs that do not match our glob patterns", async () => { + usingLabelerConfigYaml("only_pdfs.yml"); + mockGitHubResponseChangedFiles("foo.txt"); + + await run(); + + expect(removeLabelMock).toHaveBeenCalledTimes(0); + expect(addLabelsMock).toHaveBeenCalledTimes(0); + }); + + it("(with sync-labels: true) it deletes preexisting PR labels that no longer match the glob pattern", async () => { + let mockInput = { + "repo-token": "foo", + "configuration-path": "bar", + "sync-labels": true, + }; + + jest + .spyOn(core, "getInput") + .mockImplementation((name: string, ...opts) => mockInput[name]); + + usingLabelerConfigYaml("only_pdfs.yml"); + mockGitHubResponseChangedFiles("foo.txt"); + getPullMock.mockResolvedValue({ + data: { + labels: [{ name: "touched-a-pdf-file" }], + }, + }); + + await run(); + + expect(addLabelsMock).toHaveBeenCalledTimes(0); + expect(removeLabelMock).toHaveBeenCalledTimes(1); + expect(removeLabelMock).toHaveBeenCalledWith({ + owner: "monalisa", + repo: "helloworld", + issue_number: 123, + name: "touched-a-pdf-file", + }); + }); + + it("(with sync-labels: false) it issues no delete calls even when there are preexisting PR labels that no longer match the glob pattern", async () => { + let mockInput = { + "repo-token": "foo", + "configuration-path": "bar", + "sync-labels": false, + }; + + jest + .spyOn(core, "getInput") + .mockImplementation((name: string, ...opts) => mockInput[name]); + + usingLabelerConfigYaml("only_pdfs.yml"); + mockGitHubResponseChangedFiles("foo.txt"); + getPullMock.mockResolvedValue({ + data: { + labels: [{ name: "touched-a-pdf-file" }], + }, + }); + + await run(); + + expect(addLabelsMock).toHaveBeenCalledTimes(0); + expect(removeLabelMock).toHaveBeenCalledTimes(0); + }); +}); + +function usingLabelerConfigYaml(fixtureName: keyof typeof yamlFixtures): void { + reposMock.mockResolvedValue({ + data: { content: yamlFixtures[fixtureName], encoding: "utf8" }, + }); +} + +function mockGitHubResponseChangedFiles(...files: string[]): void { + const returnValue = files.map((f) => ({ filename: f })); + paginateMock.mockReturnValue(returnValue); +} diff --git a/tsconfig.json b/tsconfig.json index 9cde892..0018cde 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -59,5 +59,5 @@ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ }, - "exclude": ["node_modules", "**/*.test.ts"] + "exclude": ["node_modules", "__tests__", "__mocks__"] }