From cb87385086c7a3b3092a400c510f62b7efdd1cad Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 15 May 2026 18:36:32 +0900 Subject: [PATCH] test(ci): isolate runtime and rules dependencies --- .../rules-injector/rule-scan-cache.test.ts | 34 ++++++-------- src/hooks/runtime-fallback/dispose.test.ts | 45 +++++++------------ src/hooks/runtime-fallback/hook.ts | 35 +++++++++++---- 3 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/hooks/rules-injector/rule-scan-cache.test.ts b/src/hooks/rules-injector/rule-scan-cache.test.ts index 778cf341a..f6a037a4b 100644 --- a/src/hooks/rules-injector/rule-scan-cache.test.ts +++ b/src/hooks/rules-injector/rule-scan-cache.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"; +import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -8,10 +8,6 @@ function createImportSuffix(): string { } describe("createRuleScanCache", () => { - afterEach(() => { - mock.restore(); - }); - it("returns undefined before set, returns stored value, and clears entries", async () => { // given const { createRuleScanCache } = await import(`./rule-scan-cache${createImportSuffix()}`); @@ -55,7 +51,6 @@ describe("findRuleFiles with scan cache", () => { }); afterEach(() => { - mock.restore(); if (existsSync(testRoot)) { rmSync(testRoot, { recursive: true, force: true }); } @@ -63,29 +58,26 @@ describe("findRuleFiles with scan cache", () => { it("reuses cached directory scan results for identical inputs", async () => { // given - const findRuleFilesRecursive = mock((directoryPath: string, results: string[]) => { - if (directoryPath === expectedRuleDir) { - results.push(expectedRuleFile); - } - }); - - mock.module("./rule-file-scanner", () => ({ - findRuleFilesRecursive, - safeRealpathSync: (filePath: string) => filePath, - })); - const { createRuleScanCache } = await import(`./rule-scan-cache${createImportSuffix()}`); const { findRuleFiles } = await import(`./rule-file-finder${createImportSuffix()}`); const cache = createRuleScanCache(); + const secondRuleFile = join(expectedRuleDir, "python.instructions.md"); + + mkdirSync(expectedRuleDir, { recursive: true }); + writeFileSync(expectedRuleFile, "TypeScript rules\n"); // when const firstCandidates = findRuleFiles(projectRoot, homeDir, currentFile, undefined, cache); - const firstInvocationCount = findRuleFilesRecursive.mock.calls.length; + writeFileSync(secondRuleFile, "Python rules\n"); const secondCandidates = findRuleFiles(projectRoot, homeDir, currentFile, undefined, cache); + const uncachedCandidates = findRuleFiles(projectRoot, homeDir, currentFile); // then - expect(firstCandidates).toEqual(secondCandidates); - expect(firstInvocationCount).toBeGreaterThan(0); - expect(findRuleFilesRecursive).toHaveBeenCalledTimes(firstInvocationCount); + expect(firstCandidates.map((candidate) => candidate.path)).toEqual([expectedRuleFile]); + expect(secondCandidates.map((candidate) => candidate.path)).toEqual([expectedRuleFile]); + expect(uncachedCandidates.map((candidate) => candidate.path).sort()).toEqual([ + expectedRuleFile, + secondRuleFile, + ].sort()); }); }); diff --git a/src/hooks/runtime-fallback/dispose.test.ts b/src/hooks/runtime-fallback/dispose.test.ts index 643b7c8fb..572923a97 100644 --- a/src/hooks/runtime-fallback/dispose.test.ts +++ b/src/hooks/runtime-fallback/dispose.test.ts @@ -1,4 +1,6 @@ -import { afterAll, afterEach, beforeEach, describe, expect, mock, test } from "bun:test" +import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test" +import type { AutoRetryHelpers } from "./auto-retry" +import { createRuntimeFallbackHook } from "./hook" import type { HookDeps, RuntimeFallbackPluginInput } from "./types" let capturedDeps: HookDeps | undefined @@ -16,31 +18,18 @@ const mockCreateAutoRetryHelpers = mock((deps: HookDeps) => { } }) -const mockCreateEventHandler = mock(() => async () => {}) -const mockCreateMessageUpdateHandler = mock(() => async () => {}) -const mockCreateChatMessageHandler = mock(() => async () => {}) +const mockCreateEventHandler = mock((_deps: HookDeps, _helpers: AutoRetryHelpers) => async () => {}) +const mockCreateMessageUpdateHandler = mock((_deps: HookDeps, _helpers: AutoRetryHelpers) => async () => {}) +const mockCreateChatMessageHandler = mock((_deps: HookDeps) => async () => {}) -mock.module("./auto-retry", () => ({ - createAutoRetryHelpers: mockCreateAutoRetryHelpers, -})) - -mock.module("./event-handler", () => ({ - createEventHandler: mockCreateEventHandler, -})) - -mock.module("./message-update-handler", () => ({ - createMessageUpdateHandler: mockCreateMessageUpdateHandler, -})) - -mock.module("./chat-message-handler", () => ({ - createChatMessageHandler: mockCreateChatMessageHandler, -})) - -afterAll(() => { - mock.restore() -}) - -const { createRuntimeFallbackHook } = await import("./hook") +function createHookWithMocks() { + return createRuntimeFallbackHook(createMockContext(), { pluginConfig: {} }, { + createAutoRetryHelpers: mockCreateAutoRetryHelpers, + createEventHandler: mockCreateEventHandler, + createMessageUpdateHandler: mockCreateMessageUpdateHandler, + createChatMessageHandler: mockCreateChatMessageHandler, + }) +} function createMockContext(): RuntimeFallbackPluginInput { return { @@ -109,7 +98,7 @@ describe("createRuntimeFallbackHook dispose", () => { test("#given runtime-fallback hook handles its first event #when dispose() is called #then cleanup interval is cleared", async () => { // given - const hook = createRuntimeFallbackHook(createMockContext(), { pluginConfig: {} }) + const hook = createHookWithMocks() await hook.event({ event: { type: "session.created", properties: {} } }) // when @@ -122,7 +111,7 @@ describe("createRuntimeFallbackHook dispose", () => { test("#given hook with session state data #when dispose() is called #then all Maps and Sets are empty", () => { // given - const hook = createRuntimeFallbackHook(createMockContext(), { pluginConfig: {} }) + const hook = createHookWithMocks() const fallbackTimeout = setTimeout(() => {}, 60_000) capturedDeps?.sessionStates.set("session-1", { @@ -150,7 +139,7 @@ describe("createRuntimeFallbackHook dispose", () => { test("#given hook with pending fallback timeouts #when dispose() is called #then timeouts are cleared before Map is emptied", () => { // given - const hook = createRuntimeFallbackHook(createMockContext(), { pluginConfig: {} }) + const hook = createHookWithMocks() const fallbackTimeout = setTimeout(() => {}, 60_000) capturedDeps?.sessionFallbackTimeouts.set("session-1", fallbackTimeout) diff --git a/src/hooks/runtime-fallback/hook.ts b/src/hooks/runtime-fallback/hook.ts index f3509ab0a..8b23efda2 100644 --- a/src/hooks/runtime-fallback/hook.ts +++ b/src/hooks/runtime-fallback/hook.ts @@ -1,18 +1,37 @@ -import type { HookDeps, RuntimeFallbackHook, RuntimeFallbackInterval, RuntimeFallbackOptions, RuntimeFallbackPluginInput, RuntimeFallbackTimeout } from "./types" -import { DEFAULT_CONFIG } from "./constants" import { createAutoRetryHelpers } from "./auto-retry" +import { createChatMessageHandler } from "./chat-message-handler" +import { DEFAULT_CONFIG } from "./constants" import { createEventHandler } from "./event-handler" import { createMessageUpdateHandler } from "./message-update-handler" -import { createChatMessageHandler } from "./chat-message-handler" +import type { HookDeps, RuntimeFallbackHook, RuntimeFallbackInterval, RuntimeFallbackOptions, RuntimeFallbackPluginInput, RuntimeFallbackTimeout } from "./types" declare function setInterval(callback: () => void, delay?: number): RuntimeFallbackInterval declare function clearInterval(interval: RuntimeFallbackInterval): void declare function clearTimeout(timeout: RuntimeFallbackTimeout): void +type RuntimeFallbackHookFactories = { + createAutoRetryHelpers: typeof createAutoRetryHelpers + createEventHandler: typeof createEventHandler + createMessageUpdateHandler: typeof createMessageUpdateHandler + createChatMessageHandler: typeof createChatMessageHandler +} + +const defaultRuntimeFallbackHookFactories: RuntimeFallbackHookFactories = { + createAutoRetryHelpers, + createEventHandler, + createMessageUpdateHandler, + createChatMessageHandler, +} + export function createRuntimeFallbackHook( ctx: RuntimeFallbackPluginInput, - options?: RuntimeFallbackOptions + options?: RuntimeFallbackOptions, + factoryOverrides: Partial = {}, ): RuntimeFallbackHook { + const factories = { + ...defaultRuntimeFallbackHookFactories, + ...factoryOverrides, + } const config = { enabled: options?.config?.enabled ?? DEFAULT_CONFIG.enabled, retry_on_errors: options?.config?.retry_on_errors ?? DEFAULT_CONFIG.retry_on_errors, @@ -35,10 +54,10 @@ export function createRuntimeFallbackHook( sessionStatusRetryKeys: new Map(), } - const helpers = createAutoRetryHelpers(deps) - const baseEventHandler = createEventHandler(deps, helpers) - const messageUpdateHandler = createMessageUpdateHandler(deps, helpers) - const chatMessageHandler = createChatMessageHandler(deps) + const helpers = factories.createAutoRetryHelpers(deps) + const baseEventHandler = factories.createEventHandler(deps, helpers) + const messageUpdateHandler = factories.createMessageUpdateHandler(deps, helpers) + const chatMessageHandler = factories.createChatMessageHandler(deps) let cleanupInterval: RuntimeFallbackInterval | null = null let intervalStarted = false