marocchino-sticky-pull-requ.../__tests__/main.test.ts
Copilot 14d4f1e429
Move validateExclusiveModes before getBody for fail-fast validation (#1663)
* Initial plan

* feat: refactor option validation to list-based approach and add delete+hide_and_recreate check

Co-authored-by: marocchino <128431+marocchino@users.noreply.github.com>

* feat: add only_update to exclusive modes validation list

Co-authored-by: marocchino <128431+marocchino@users.noreply.github.com>

* refactor: extract validation logic into src/validate.ts

Co-authored-by: marocchino <128431+marocchino@users.noreply.github.com>

* refactor: move validateExclusiveModes before getBody in run()

Co-authored-by: marocchino <128431+marocchino@users.noreply.github.com>

* test: assert getBody is not called when validateExclusiveModes fails

Co-authored-by: marocchino <128431+marocchino@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: marocchino <128431+marocchino@users.noreply.github.com>
2026-03-13 21:05:41 +09:00

328 lines
12 KiB
TypeScript

import {beforeEach, describe, expect, test, vi} from "vitest"
vi.mock("@actions/core", () => ({
info: vi.fn(),
setFailed: vi.fn(),
setOutput: vi.fn(),
}))
vi.mock("@actions/github", () => ({
getOctokit: vi.fn().mockReturnValue({}),
}))
vi.mock("../src/comment", () => ({
commentsEqual: vi.fn(),
createComment: vi.fn(),
deleteComment: vi.fn(),
findPreviousComment: vi.fn(),
getBodyOf: vi.fn(),
minimizeComment: vi.fn(),
updateComment: vi.fn(),
}))
const mockConfig = {
append: false,
deleteOldComment: false,
getBody: vi.fn(),
githubToken: "some-token",
header: "",
hideAndRecreate: false,
hideClassify: "OUTDATED",
hideDetails: false,
hideOldComment: false,
ignoreEmpty: false,
onlyCreateComment: false,
onlyUpdateComment: false,
pullRequestNumber: 123,
recreate: false,
repo: {owner: "marocchino", repo: "sticky-pull-request-comment"},
skipUnchanged: false,
}
vi.mock("../src/config", () => mockConfig)
const flushPromises = () => new Promise<void>(resolve => setTimeout(resolve, 0))
async function runMain(
setup?: (mocks: {comment: typeof import("../src/comment")}) => void,
) {
vi.resetModules()
const comment = await import("../src/comment")
vi.mocked(comment.findPreviousComment).mockResolvedValue(null)
vi.mocked(comment.createComment).mockResolvedValue({data: {id: 456}} as any)
vi.mocked(comment.deleteComment).mockResolvedValue(undefined)
vi.mocked(comment.updateComment).mockResolvedValue(undefined)
vi.mocked(comment.minimizeComment).mockResolvedValue(undefined)
vi.mocked(comment.commentsEqual).mockReturnValue(false)
vi.mocked(comment.getBodyOf).mockReturnValue(undefined)
setup?.({comment})
await import("../src/main")
await flushPromises()
const core = await import("@actions/core")
return {comment, core}
}
beforeEach(() => {
mockConfig.append = false
mockConfig.deleteOldComment = false
mockConfig.getBody = vi.fn().mockResolvedValue("test body")
mockConfig.githubToken = "some-token"
mockConfig.header = ""
mockConfig.hideAndRecreate = false
mockConfig.hideClassify = "OUTDATED"
mockConfig.hideDetails = false
mockConfig.hideOldComment = false
mockConfig.ignoreEmpty = false
mockConfig.onlyCreateComment = false
mockConfig.onlyUpdateComment = false
mockConfig.pullRequestNumber = 123
mockConfig.recreate = false
mockConfig.repo = {owner: "marocchino", repo: "sticky-pull-request-comment"}
mockConfig.skipUnchanged = false
})
describe("run", () => {
test("skips step when pullRequestNumber is NaN", async () => {
mockConfig.pullRequestNumber = NaN
const {comment, core} = await runMain()
expect(core.info).toHaveBeenCalledWith("no pull request numbers given: skip step")
expect(comment.findPreviousComment).not.toHaveBeenCalled()
})
test("skips step when pullRequestNumber is less than 1", async () => {
mockConfig.pullRequestNumber = 0
const {comment, core} = await runMain()
expect(core.info).toHaveBeenCalledWith("no pull request numbers given: skip step")
expect(comment.findPreviousComment).not.toHaveBeenCalled()
})
test("skips step when body is empty and ignoreEmpty is true", async () => {
mockConfig.getBody = vi.fn().mockResolvedValue("")
mockConfig.ignoreEmpty = true
const {comment, core} = await runMain()
expect(core.info).toHaveBeenCalledWith("no body given: skip step by ignoreEmpty")
expect(comment.findPreviousComment).not.toHaveBeenCalled()
})
test("fails when body is empty and no delete or hide flags are set", async () => {
mockConfig.getBody = vi.fn().mockResolvedValue("")
const {core} = await runMain()
expect(core.setFailed).toHaveBeenCalledWith("Either message or path input is required")
})
test("fails when deleteOldComment and recreate are both true", async () => {
mockConfig.deleteOldComment = true
mockConfig.recreate = true
const {core} = await runMain()
expect(core.setFailed).toHaveBeenCalledWith(
"delete and recreate cannot be set to true simultaneously",
)
expect(mockConfig.getBody).not.toHaveBeenCalled()
})
test("fails when deleteOldComment and onlyCreateComment are both true", async () => {
mockConfig.deleteOldComment = true
mockConfig.onlyCreateComment = true
const {core} = await runMain()
expect(core.setFailed).toHaveBeenCalledWith(
"delete and only_create cannot be set to true simultaneously",
)
expect(mockConfig.getBody).not.toHaveBeenCalled()
})
test("fails when deleteOldComment and hideOldComment are both true", async () => {
mockConfig.deleteOldComment = true
mockConfig.hideOldComment = true
const {core} = await runMain()
expect(core.setFailed).toHaveBeenCalledWith(
"delete and hide cannot be set to true simultaneously",
)
expect(mockConfig.getBody).not.toHaveBeenCalled()
})
test("fails when onlyCreateComment and onlyUpdateComment are both true", async () => {
mockConfig.onlyCreateComment = true
mockConfig.onlyUpdateComment = true
const {core} = await runMain()
expect(core.setFailed).toHaveBeenCalledWith(
"only_create and only_update cannot be set to true simultaneously",
)
expect(mockConfig.getBody).not.toHaveBeenCalled()
})
test("fails when hideOldComment and hideAndRecreate are both true", async () => {
mockConfig.hideOldComment = true
mockConfig.hideAndRecreate = true
const {core} = await runMain()
expect(core.setFailed).toHaveBeenCalledWith(
"hide and hide_and_recreate cannot be set to true simultaneously",
)
expect(mockConfig.getBody).not.toHaveBeenCalled()
})
test("fails when deleteOldComment and hideAndRecreate are both true", async () => {
mockConfig.deleteOldComment = true
mockConfig.hideAndRecreate = true
const {core} = await runMain()
expect(core.setFailed).toHaveBeenCalledWith(
"delete and hide_and_recreate cannot be set to true simultaneously",
)
expect(mockConfig.getBody).not.toHaveBeenCalled()
})
test("deletes previous comment when deleteOldComment is true and previous comment exists", async () => {
mockConfig.deleteOldComment = true
const previous = {id: "existing-id", body: "old body"}
const {comment, core} = await runMain(({comment}) => {
vi.mocked(comment.findPreviousComment).mockResolvedValue(previous as any)
})
expect(core.setOutput).toHaveBeenCalledWith("previous_comment_id", "existing-id")
expect(comment.deleteComment).toHaveBeenCalledWith(expect.anything(), "existing-id")
expect(comment.createComment).not.toHaveBeenCalled()
expect(comment.updateComment).not.toHaveBeenCalled()
})
test("skips delete when deleteOldComment is true but no previous comment exists", async () => {
mockConfig.deleteOldComment = true
const {comment, core} = await runMain()
expect(core.setOutput).toHaveBeenCalledWith("previous_comment_id", undefined)
expect(comment.deleteComment).not.toHaveBeenCalled()
expect(comment.createComment).not.toHaveBeenCalled()
expect(comment.updateComment).not.toHaveBeenCalled()
})
test("Updates previous comment when onlyUpdateComment is true and previous comment exists", async () => {
mockConfig.onlyUpdateComment = true
const previous = {id: "existing-id", body: "old body"}
const {comment, core} = await runMain(({comment}) => {
vi.mocked(comment.findPreviousComment).mockResolvedValue(previous as any)
vi.mocked(comment.getBodyOf).mockReturnValue("previous body content")
})
expect(comment.updateComment).toHaveBeenCalledWith(
expect.anything(),
"existing-id",
"test body",
"",
"previous body content",
)
expect(comment.createComment).not.toHaveBeenCalled()
expect(core.setOutput).toHaveBeenCalledWith("previous_comment_id", "existing-id")
})
test("skips creating comment when onlyUpdateComment is true and no previous comment exists", async () => {
mockConfig.onlyUpdateComment = true
const {comment} = await runMain()
expect(comment.createComment).not.toHaveBeenCalled()
expect(comment.updateComment).not.toHaveBeenCalled()
})
test("creates comment when no previous comment exists", async () => {
const {comment, core} = await runMain()
expect(comment.createComment).toHaveBeenCalledWith(
expect.anything(),
{owner: "marocchino", repo: "sticky-pull-request-comment"},
123,
"test body",
"",
)
expect(core.setOutput).toHaveBeenCalledWith("created_comment_id", 456)
})
test("skips update when onlyCreateComment is true and previous comment exists", async () => {
mockConfig.onlyCreateComment = true
const previous = {id: "existing-id", body: "old body"}
const {comment} = await runMain(({comment}) => {
vi.mocked(comment.findPreviousComment).mockResolvedValue(previous as any)
})
expect(comment.updateComment).not.toHaveBeenCalled()
expect(comment.createComment).not.toHaveBeenCalled()
})
test("minimizes previous comment when hideOldComment is true", async () => {
mockConfig.hideOldComment = true
const previous = {id: "existing-id", body: "old body"}
const {comment} = await runMain(({comment}) => {
vi.mocked(comment.findPreviousComment).mockResolvedValue(previous as any)
})
expect(comment.minimizeComment).toHaveBeenCalledWith(
expect.anything(),
"existing-id",
"OUTDATED",
)
expect(comment.updateComment).not.toHaveBeenCalled()
})
test("skips when hideOldComment is true and no previous comment exists", async () => {
mockConfig.hideOldComment = true
const {comment} = await runMain()
expect(comment.minimizeComment).not.toHaveBeenCalled()
expect(comment.createComment).not.toHaveBeenCalled()
expect(comment.updateComment).not.toHaveBeenCalled()
})
test("skips update when skipUnchanged is true and body is unchanged", async () => {
mockConfig.skipUnchanged = true
const previous = {id: "existing-id", body: "old body"}
const {comment} = await runMain(({comment}) => {
vi.mocked(comment.findPreviousComment).mockResolvedValue(previous as any)
vi.mocked(comment.commentsEqual).mockReturnValue(true)
})
expect(comment.updateComment).not.toHaveBeenCalled()
expect(comment.createComment).not.toHaveBeenCalled()
})
test("deletes and recreates comment when recreate is true", async () => {
mockConfig.recreate = true
const previous = {id: "existing-id", body: "old body"}
const {comment, core} = await runMain(({comment}) => {
vi.mocked(comment.findPreviousComment).mockResolvedValue(previous as any)
vi.mocked(comment.getBodyOf).mockReturnValue("previous body content")
})
expect(comment.deleteComment).toHaveBeenCalledWith(expect.anything(), "existing-id")
expect(comment.createComment).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
"test body",
"",
"previous body content",
)
expect(core.setOutput).toHaveBeenCalledWith("created_comment_id", 456)
})
test("minimizes and recreates comment when hideAndRecreate is true", async () => {
mockConfig.hideAndRecreate = true
const previous = {id: "existing-id", body: "old body"}
const {comment, core} = await runMain(({comment}) => {
vi.mocked(comment.findPreviousComment).mockResolvedValue(previous as any)
})
expect(comment.minimizeComment).toHaveBeenCalledWith(
expect.anything(),
"existing-id",
"OUTDATED",
)
expect(comment.createComment).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
"test body",
"",
)
expect(core.setOutput).toHaveBeenCalledWith("created_comment_id", 456)
})
test("updates existing comment by default", async () => {
const previous = {id: "existing-id", body: "old body"}
const {comment} = await runMain(({comment}) => {
vi.mocked(comment.findPreviousComment).mockResolvedValue(previous as any)
vi.mocked(comment.getBodyOf).mockReturnValue("previous body content")
})
expect(comment.updateComment).toHaveBeenCalledWith(
expect.anything(),
"existing-id",
"test body",
"",
"previous body content",
)
})
})