Files
codeql-action/pr-checks/post-comment.test.ts
2026-10-08 23:02:25 -05:00

207 lines
5.5 KiB
TypeScript

#!/usr/bin/env npx tsx
/*
Tests for post-comment.ts.
*/
import * as assert from "node:assert/strict";
import { describe, it } from "node:test";
import { type ApiClient } from "./api-client";
import {
findExistingComment,
parseOptions,
resolveToken,
} from "./post-comment";
/** A baseline set of valid CLI flags, as an array, for use in parseOptions tests. */
const validFlags = [
"--body",
"hello",
"--marker",
"<!-- marker -->",
"--action-condition",
"true",
"--action-if-true",
"insert",
"--action-if-false",
"delete",
"--issue-id",
"42",
"--repository",
"github/codeql-action",
];
/** Returns a copy of `validFlags` with the value following `flag` replaced by `value`. */
function withFlag(flag: string, value: string): string[] {
const flags = [...validFlags];
const index = flags.indexOf(flag);
flags[index + 1] = value;
return flags;
}
/** Returns a copy of `validFlags` with `flag` (and its value) removed entirely. */
function withoutFlag(flag: string): string[] {
const flags = [...validFlags];
const index = flags.indexOf(flag);
flags.splice(index, 2);
return flags;
}
describe("parseOptions", async () => {
await it("parses valid flags", () => {
const options = parseOptions(validFlags);
assert.deepEqual(options, {
body: "hello",
marker: "<!-- marker -->",
actionCondition: true,
actionIfTrue: "insert",
actionIfFalse: "delete",
issueId: 42,
repository: { owner: "github", repo: "codeql-action" },
});
});
await it("parses action-condition=false", () => {
const options = parseOptions(withFlag("--action-condition", "false"));
assert.equal(options.actionCondition, false);
});
await it("rejects a missing --body", () => {
assert.throws(
() => parseOptions(withoutFlag("--body")),
/Missing required flag --body/,
);
});
await it("rejects a missing --marker", () => {
assert.throws(
() => parseOptions(withoutFlag("--marker")),
/Missing required flag --marker/,
);
});
await it("rejects an invalid --action-condition value", () => {
assert.throws(
() => parseOptions(withFlag("--action-condition", "yes")),
/Invalid value 'yes' for --action-condition/,
);
});
await it("rejects an invalid --action-if-true value", () => {
assert.throws(
() => parseOptions(withFlag("--action-if-true", "bogus")),
/Invalid value 'bogus' for --action-if-true/,
);
});
await it("rejects an invalid --action-if-false value", () => {
assert.throws(
() => parseOptions(withFlag("--action-if-false", "bogus")),
/Invalid value 'bogus' for --action-if-false/,
);
});
await it("rejects a missing --issue-id", () => {
assert.throws(
() => parseOptions(withoutFlag("--issue-id")),
/Missing required flag --issue-id/,
);
});
await it("rejects a non-numeric --issue-id", () => {
assert.throws(
() => parseOptions(withFlag("--issue-id", "not-a-number")),
/Invalid value 'not-a-number' for --issue-id/,
);
});
await it("rejects a missing --repository", () => {
assert.throws(
() => parseOptions(withoutFlag("--repository")),
/Missing required flag --repository/,
);
});
await it("rejects a --repository that isn't in the form 'owner/repo'", () => {
assert.throws(
() => parseOptions(withFlag("--repository", "not-a-slug")),
/Invalid value 'not-a-slug' for --repository/,
);
});
});
describe("resolveToken", async () => {
await it("reads the token from GH_TOKEN", () => {
assert.equal(
resolveToken({ GH_TOKEN: "gh-token", GITHUB_TOKEN: "gha-token" }),
"gh-token",
);
});
await it("falls back to GITHUB_TOKEN", () => {
assert.equal(resolveToken({ GITHUB_TOKEN: "gha-token" }), "gha-token");
});
await it("throws when no token is set", () => {
assert.throws(() => resolveToken({}), /Missing authentication token/);
});
});
/** A minimal fake of the subset of `ApiClient` used by post-comment.ts. */
function fakeClient(existingComments: Array<{ id: number; body?: string }>) {
const calls: {
createComment: unknown[];
updateComment: unknown[];
deleteComment: unknown[];
} = { createComment: [], updateComment: [], deleteComment: [] };
const client = {
paginate: async () => existingComments,
rest: {
issues: {
listComments: () => {},
createComment: async (params: unknown) => {
calls.createComment.push(params);
},
updateComment: async (params: unknown) => {
calls.updateComment.push(params);
},
deleteComment: async (params: unknown) => {
calls.deleteComment.push(params);
},
},
},
};
return { client: client as unknown as ApiClient, calls };
}
describe("findExistingComment", async () => {
await it("finds the id of the first comment containing the marker", async () => {
const { client } = fakeClient([
{ id: 1, body: "unrelated" },
{ id: 2, body: "<!-- marker -->\nhello" },
{ id: 3, body: "<!-- marker -->\nanother" },
]);
const id = await findExistingComment(
client,
{ owner: "owner", repo: "repo" },
1,
"<!-- marker -->",
);
assert.equal(id, 2);
});
await it("returns undefined when no comment matches", async () => {
const { client } = fakeClient([{ id: 1, body: "unrelated" }]);
const id = await findExistingComment(
client,
{ owner: "owner", repo: "repo" },
1,
"<!-- marker -->",
);
assert.equal(id, undefined);
});
});