test: localize stop hook command mocks

This commit is contained in:
YeonGyu-Kim
2026-04-04 20:34:14 +09:00
parent d7acdfd8f6
commit 7abbf1487f
+28 -31
View File
@@ -1,26 +1,14 @@
import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test"
import { describe, it, expect, mock, beforeEach, afterEach, spyOn } from "bun:test"
import type { ClaudeHooksConfig } from "./types"
import type { StopContext } from "./stop"
import * as dispatchHookModule from "./dispatch-hook"
import * as logger from "../../shared/logger"
import { executeStopHooks } from "./stop"
const mockExecuteHookCommand = mock(() =>
const mockDispatchHook = mock(() =>
Promise.resolve({ exitCode: 0, stdout: "", stderr: "" })
)
mock.module("../../shared/command-executor", () => ({
executeHookCommand: mockExecuteHookCommand,
executeCommand: mock(),
resolveCommandsInText: mock(),
}))
mock.module("../../shared/logger", () => ({
log: () => {},
getLogFilePath: () => "/tmp/test.log",
}))
afterAll(() => { mock.restore() })
const { executeStopHooks } = await import("./stop")
function createStopContext(overrides?: Partial<StopContext>): StopContext {
return {
sessionId: "test-session",
@@ -35,10 +23,19 @@ function createConfig(stopHooks: ClaudeHooksConfig["Stop"]): ClaudeHooksConfig {
describe("executeStopHooks", () => {
beforeEach(() => {
mockExecuteHookCommand.mockReset()
mockExecuteHookCommand.mockImplementation(() =>
mockDispatchHook.mockReset()
mockDispatchHook.mockImplementation(() =>
Promise.resolve({ exitCode: 0, stdout: "", stderr: "" })
)
spyOn(dispatchHookModule, "dispatchHook").mockImplementation(
async (_hook, _stdinJson, _cwd) => await mockDispatchHook()
)
spyOn(logger, "log").mockImplementation(() => {})
})
afterEach(() => {
mock.restore()
})
it("#given parent session #when stop hooks called #then skips execution", async () => {
@@ -50,7 +47,7 @@ describe("executeStopHooks", () => {
const result = await executeStopHooks(ctx, config)
expect(result.block).toBe(false)
expect(mockExecuteHookCommand).not.toHaveBeenCalled()
expect(mockDispatchHook).not.toHaveBeenCalled()
})
it("#given null config #when stop hooks called #then returns non-blocking", async () => {
@@ -59,7 +56,7 @@ describe("executeStopHooks", () => {
const result = await executeStopHooks(ctx, null)
expect(result.block).toBe(false)
expect(mockExecuteHookCommand).not.toHaveBeenCalled()
expect(mockDispatchHook).not.toHaveBeenCalled()
})
it("#given empty stop hooks #when stop hooks called #then returns non-blocking", async () => {
@@ -76,7 +73,7 @@ describe("executeStopHooks", () => {
const config = createConfig([
{ matcher: "*", hooks: [{ type: "command", command: "exit 2" }] },
])
mockExecuteHookCommand.mockResolvedValueOnce({
mockDispatchHook.mockResolvedValueOnce({
exitCode: 2,
stdout: "",
stderr: "blocked reason",
@@ -93,7 +90,7 @@ describe("executeStopHooks", () => {
const config = createConfig([
{ matcher: "*", hooks: [{ type: "command", command: "blocker" }] },
])
mockExecuteHookCommand.mockResolvedValueOnce({
mockDispatchHook.mockResolvedValueOnce({
exitCode: 0,
stdout: JSON.stringify({ decision: "block", reason: "must fix" }),
stderr: "",
@@ -111,7 +108,7 @@ describe("executeStopHooks", () => {
{ matcher: "*", hooks: [{ type: "command", command: "hook-a" }] },
{ matcher: "*", hooks: [{ type: "command", command: "hook-b" }] },
])
mockExecuteHookCommand
mockDispatchHook
.mockResolvedValueOnce({
exitCode: 0,
stdout: JSON.stringify({ suppressOutput: true }),
@@ -126,7 +123,7 @@ describe("executeStopHooks", () => {
const result = await executeStopHooks(ctx, config)
expect(result.block).toBe(false)
expect(mockExecuteHookCommand).toHaveBeenCalledTimes(2)
expect(mockDispatchHook).toHaveBeenCalledTimes(2)
})
it("#given first hook returns stdin passthrough JSON #when multiple hooks #then executes all hooks", async () => {
@@ -140,7 +137,7 @@ describe("executeStopHooks", () => {
{ matcher: "*", hooks: [{ type: "command", command: "check-console-log" }] },
{ matcher: "*", hooks: [{ type: "command", command: "task-complete-notify" }] },
])
mockExecuteHookCommand
mockDispatchHook
.mockResolvedValueOnce({
exitCode: 0,
stdout: JSON.stringify(stdinPassthrough),
@@ -155,7 +152,7 @@ describe("executeStopHooks", () => {
const result = await executeStopHooks(ctx, config)
expect(result.block).toBe(false)
expect(mockExecuteHookCommand).toHaveBeenCalledTimes(2)
expect(mockDispatchHook).toHaveBeenCalledTimes(2)
})
it("#given first hook blocks #when multiple hooks #then stops at blocking hook", async () => {
@@ -164,7 +161,7 @@ describe("executeStopHooks", () => {
{ matcher: "*", hooks: [{ type: "command", command: "blocker" }] },
{ matcher: "*", hooks: [{ type: "command", command: "notifier" }] },
])
mockExecuteHookCommand.mockResolvedValueOnce({
mockDispatchHook.mockResolvedValueOnce({
exitCode: 0,
stdout: JSON.stringify({ decision: "block", reason: "fix first" }),
stderr: "",
@@ -173,7 +170,7 @@ describe("executeStopHooks", () => {
const result = await executeStopHooks(ctx, config)
expect(result.block).toBe(true)
expect(mockExecuteHookCommand).toHaveBeenCalledTimes(1)
expect(mockDispatchHook).toHaveBeenCalledTimes(1)
})
it("#given hook with non-JSON stdout #when stop hooks called #then continues to next hook", async () => {
@@ -182,7 +179,7 @@ describe("executeStopHooks", () => {
{ matcher: "*", hooks: [{ type: "command", command: "hook-a" }] },
{ matcher: "*", hooks: [{ type: "command", command: "hook-b" }] },
])
mockExecuteHookCommand
mockDispatchHook
.mockResolvedValueOnce({
exitCode: 0,
stdout: "not json",
@@ -197,6 +194,6 @@ describe("executeStopHooks", () => {
const result = await executeStopHooks(ctx, config)
expect(result.block).toBe(false)
expect(mockExecuteHookCommand).toHaveBeenCalledTimes(2)
expect(mockDispatchHook).toHaveBeenCalledTimes(2)
})
})