From 7abbf1487f2c7d74547435160c40229726e57199 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 4 Apr 2026 20:34:14 +0900 Subject: [PATCH] test: localize stop hook command mocks --- src/hooks/claude-code-hooks/stop.test.ts | 59 +++++++++++------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/hooks/claude-code-hooks/stop.test.ts b/src/hooks/claude-code-hooks/stop.test.ts index 4a7b45941..92ac03936 100644 --- a/src/hooks/claude-code-hooks/stop.test.ts +++ b/src/hooks/claude-code-hooks/stop.test.ts @@ -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 { 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) }) })