Skip to content

Commit

Permalink
test: add API tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Codex- committed Oct 24, 2023
1 parent 94472d7 commit 4b92c7a
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 3 deletions.
221 changes: 221 additions & 0 deletions src/api.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import * as core from "@actions/core";
import * as github from "@actions/github";
import {
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
type SpyInstance,
} from "vitest";

import type { ActionConfig } from "./action.ts";
import { createComment, deleteComment, getCommentIds, init, updateComment } from "./api.ts";

vi.mock("@actions/core");

describe("API", () => {
const cfg: ActionConfig = {
token: "secret",
commandScriptName: "npm",
commentId: "knip-report",
ignoreResults: false,
};
type Octokit = ReturnType<(typeof github)["getOctokit"]>;
let octokit: Octokit;

beforeAll(() => {
octokit = github.getOctokit("token", {
request: {
fetch: vi.fn(async () => {
throw new Error("API calls should be mocked");
}),
},
});
});

beforeEach(() => {
process.env.GITHUB_REPOSITORY = "a/b";

vi.spyOn(core, "getInput").mockReturnValue("");
vi.spyOn(github, "getOctokit").mockReturnValue(octokit);
init(cfg);
});

afterEach(() => {
vi.restoreAllMocks();
});

describe("createComment", () => {
it("should not throw", async () => {
vi.spyOn(octokit.rest.issues, "createComment").mockReturnValue(
Promise.resolve({
data: {},
status: 201,
}) as any,
);

const state = await createComment(123456, "");
expect(state.status).toStrictEqual(201);
});

it("should throw if a non-201 status is returned", async () => {
const errorStatus = 401;
vi.spyOn(octokit.rest.issues, "createComment").mockReturnValue(
Promise.resolve({
data: undefined,
status: errorStatus,
}) as any,
);

await expect(createComment(123456, "")).rejects.toThrow(
`Failed to create comment, expected 201 but received ${errorStatus}`,
);
});
});

describe("getCommentIds", () => {
let listCommentsToReturn: any[];
let listCommentsSpy: SpyInstance;

beforeEach(() => {
// This mock may look like a case of "set foo assert foo is foo" but we don't need to test the
// github API, we're testing that our iterator handling works as expected.
let call = 0;
listCommentsSpy = vi
.spyOn(octokit.rest.issues, "listComments")
.mockImplementation((async () => {
const toReturn = listCommentsToReturn[call];
call++;

if (!toReturn) {
return undefined;
}

return {
data: toReturn,
status: 200,
};
}) as any);

vi.spyOn(octokit.paginate, "iterator").mockImplementation((rest: any) => {
return (async function* () {
const boundRest = rest;
let results: any = await boundRest();
while (results) {
yield results;
results = await boundRest();
}
})();
});
});

it("should return undefined for no results", async () => {
listCommentsToReturn = [[{ id: 0, body: "" }]];
const commentIds = await getCommentIds("knip", 123456);
expect(commentIds).toBeUndefined();
});

it("should return an ID for a single match", async () => {
listCommentsToReturn = [[{ id: 0, body: "" }], [{ id: 123, body: "knip" }]];
const commentIds = await getCommentIds("knip", 123456);
expect(Array.isArray(commentIds)).toStrictEqual(true);
expect(commentIds?.length).toStrictEqual(1);
expect(commentIds!).toContain(123);
});

it("should return an ID for every match", async () => {
listCommentsToReturn = [
[{ id: 0, body: "" }],
[{ id: 123, body: "knip" }],
[{ id: 456, body: "knip" }],
[{ id: 789, body: "knop" }],
];
const commentIds = await getCommentIds("knip", 123456);
expect(Array.isArray(commentIds)).toStrictEqual(true);
expect(commentIds?.length).toStrictEqual(2);
expect(commentIds!).toContain(123);
expect(commentIds!).toContain(456);
});

it("should not return an invalid match", async () => {
listCommentsToReturn = [[{ id: 0, body: "" }], [{ id: 123, body: "knop" }]];
const commentIds = await getCommentIds("knip", 123456);
expect(commentIds).toBeUndefined();
});

it("should throw if a non-201 status is returned", async () => {
listCommentsSpy.mockRestore();

const errorStatus = 401;
vi.spyOn(octokit.rest.issues, "listComments").mockReturnValue(
Promise.resolve({
data: undefined,
status: errorStatus,
} as any),
);

await expect(getCommentIds("knip", 123456)).rejects.toThrow(
`Failed to find comment ID, expected 200 but received ${errorStatus}`,
);
});
});

describe("updateComment", () => {
it("should not throw", async () => {
vi.spyOn(octokit.rest.issues, "updateComment").mockReturnValue(
Promise.resolve({
data: {},
status: 200,
}) as any,
);

const state = await updateComment(123456, "");
expect(state.status).toStrictEqual(200);
});

it("should throw if a non-200 status is returned", async () => {
const errorStatus = 401;
vi.spyOn(octokit.rest.issues, "updateComment").mockReturnValue(
Promise.resolve({
data: undefined,
status: errorStatus,
}) as any,
);

await expect(updateComment(123456, "")).rejects.toThrow(
`Failed to update comment, expected 200 but received ${errorStatus}`,
);
});
});

describe("deleteComment", () => {
it("should not throw", async () => {
vi.spyOn(octokit.rest.issues, "deleteComment").mockReturnValue(
Promise.resolve({
data: {},
status: 204,
}) as any,
);

const state = await deleteComment(123456);
expect(state.status).toStrictEqual(204);
});

it("should throw if a non-204 status is returned", async () => {
const errorStatus = 401;
vi.spyOn(octokit.rest.issues, "deleteComment").mockReturnValue(
Promise.resolve({
data: undefined,
status: errorStatus,
}) as any,
);

await expect(deleteComment(123456)).rejects.toThrow(
`Failed to delete comment, expected 204 but received ${errorStatus}`,
);
});
});
});
5 changes: 2 additions & 3 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as github from "@actions/github";
import type { GitHub } from "@actions/github/lib/utils.ts";
import { type ActionConfig, getConfig } from "./action.ts";

export const GITHUB_COMMENT_MAX_COMMENT_LENGTH = 65535;

type Octokit = InstanceType<typeof GitHub>;
type Octokit = ReturnType<(typeof github)["getOctokit"]>;

let config: ActionConfig;
let octokit: Octokit;
Expand Down Expand Up @@ -99,7 +98,7 @@ export async function deleteComment(commentId: number): Promise<DeleteCommentRes
});

if (response.status !== 204) {
throw new Error(`Failed to update comment, expected 204 but received ${response.status}`);
throw new Error(`Failed to delete comment, expected 204 but received ${response.status}`);
}

return response;
Expand Down
2 changes: 2 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export default defineConfig({
coverage: {
provider: "v8",
reporter: ["text", "lcov"],
include: ["src/**/*.ts"],
all: true, // Can be removed with vitest v1
},
},
});

0 comments on commit 4b92c7a

Please sign in to comment.