diff --git a/src/cli/doctor/formatter.test.ts b/src/cli/doctor/formatter.test.ts index 5884997af..e0c280995 100644 --- a/src/cli/doctor/formatter.test.ts +++ b/src/cli/doctor/formatter.test.ts @@ -1,6 +1,10 @@ -import { afterEach, describe, expect, it, mock } from "bun:test" +import { afterEach, afterAll, describe, expect, it, mock } from "bun:test" import type { DoctorResult } from "./types" +const realFormatDefault = await import("./format-default") +const realFormatStatus = await import("./format-status") +const realFormatVerbose = await import("./format-verbose") + function createDoctorResult(): DoctorResult { return { results: [ @@ -44,6 +48,12 @@ describe("formatter", () => { mock.restore() }) + afterAll(() => { + mock.module("./format-default", () => ({ ...realFormatDefault })) + mock.module("./format-status", () => ({ ...realFormatStatus })) + mock.module("./format-verbose", () => ({ ...realFormatVerbose })) + }) + describe("formatDoctorOutput", () => { it("dispatches to default formatter for default mode", async () => { //#given diff --git a/src/cli/doctor/runner.test.ts b/src/cli/doctor/runner.test.ts index ca96b0794..e2070d1cc 100644 --- a/src/cli/doctor/runner.test.ts +++ b/src/cli/doctor/runner.test.ts @@ -1,6 +1,9 @@ -import { afterEach, describe, expect, it, mock } from "bun:test" +import { afterEach, afterAll, describe, expect, it, mock } from "bun:test" import type { CheckDefinition, CheckResult, DoctorResult, SystemInfo, ToolsSummary } from "./types" +const realChecks = await import("./checks") +const realFormatter = await import("./formatter") + function createSystemInfo(): SystemInfo { return { opencodeVersion: "1.0.200", @@ -47,6 +50,11 @@ describe("runner", () => { mock.restore() }) + afterAll(() => { + mock.module("./checks", () => ({ ...realChecks })) + mock.module("./formatter", () => ({ ...realFormatter })) + }) + describe("runCheck", () => { it("returns fail result with issue when check throws", async () => { //#given diff --git a/src/cli/mcp-oauth/login.test.ts b/src/cli/mcp-oauth/login.test.ts index 917652f76..6d9998a1e 100644 --- a/src/cli/mcp-oauth/login.test.ts +++ b/src/cli/mcp-oauth/login.test.ts @@ -1,4 +1,6 @@ -import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test" +import { describe, it, expect, beforeEach, afterEach, afterAll, mock } from "bun:test" + +const realMcpOauthProvider = await import("../../features/mcp-oauth/provider") const mockLogin = mock(() => Promise.resolve({ accessToken: "test-token", expiresAt: 1710000000 })) @@ -11,6 +13,10 @@ mock.module("../../features/mcp-oauth/provider", () => ({ }, })) +afterAll(() => { + mock.module("../../features/mcp-oauth/provider", () => ({ ...realMcpOauthProvider })) +}) + const { login } = await import("./login") describe("login command", () => { diff --git a/src/cli/run/integration.test.ts b/src/cli/run/integration.test.ts index 1cbfa0847..c38e72ea9 100644 --- a/src/cli/run/integration.test.ts +++ b/src/cli/run/integration.test.ts @@ -1,10 +1,13 @@ -import { describe, it, expect, mock, spyOn, beforeEach, afterEach } from "bun:test" +import { describe, it, expect, mock, spyOn, beforeEach, afterEach, afterAll } from "bun:test" import type { RunResult } from "./types" import { createJsonOutputManager } from "./json-output" import { resolveSession } from "./session-resolver" import { executeOnCompleteHook } from "./on-complete-hook" import type { OpencodeClient } from "./types" +const realSdk = await import("@opencode-ai/sdk") +const realPortUtils = await import("../../shared/port-utils") + const mockServerClose = mock(() => {}) const mockCreateOpencode = mock(() => Promise.resolve({ @@ -17,16 +20,23 @@ const mockIsPortAvailable = mock(() => Promise.resolve(true)) const mockGetAvailableServerPort = mock(() => Promise.resolve({ port: 9999, wasAutoSelected: false })) mock.module("@opencode-ai/sdk", () => ({ + ...realSdk, createOpencode: mockCreateOpencode, createOpencodeClient: mockCreateOpencodeClient, })) mock.module("../../shared/port-utils", () => ({ + ...realPortUtils, isPortAvailable: mockIsPortAvailable, getAvailableServerPort: mockGetAvailableServerPort, DEFAULT_SERVER_PORT: 4096, })) +afterAll(() => { + mock.module("@opencode-ai/sdk", () => ({ ...realSdk })) + mock.module("../../shared/port-utils", () => ({ ...realPortUtils })) +}) + const { createServerConnection } = await import("./server-connection") interface MockWriteStream { diff --git a/src/cli/run/server-connection.test.ts b/src/cli/run/server-connection.test.ts index 100154a0e..f9cabc870 100644 --- a/src/cli/run/server-connection.test.ts +++ b/src/cli/run/server-connection.test.ts @@ -1,4 +1,7 @@ -import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test" +import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test" + +const realSdk = await import("@opencode-ai/sdk") +const realPortUtils = await import("../../shared/port-utils") const originalConsole = globalThis.console @@ -15,16 +18,23 @@ const mockGetAvailableServerPort = mock(() => Promise.resolve({ port: 4096, wasA const mockConsoleLog = mock(() => {}) mock.module("@opencode-ai/sdk", () => ({ + ...realSdk, createOpencode: mockCreateOpencode, createOpencodeClient: mockCreateOpencodeClient, })) mock.module("../../shared/port-utils", () => ({ + ...realPortUtils, isPortAvailable: mockIsPortAvailable, getAvailableServerPort: mockGetAvailableServerPort, DEFAULT_SERVER_PORT: 4096, })) +afterAll(() => { + mock.module("@opencode-ai/sdk", () => ({ ...realSdk })) + mock.module("../../shared/port-utils", () => ({ ...realPortUtils })) +}) + const { createServerConnection } = await import("./server-connection") describe("createServerConnection", () => { diff --git a/src/features/claude-code-mcp-loader/loader.test.ts b/src/features/claude-code-mcp-loader/loader.test.ts index 848c7aed5..711a721c3 100644 --- a/src/features/claude-code-mcp-loader/loader.test.ts +++ b/src/features/claude-code-mcp-loader/loader.test.ts @@ -6,15 +6,20 @@ import { tmpdir } from "os" const TEST_DIR = join(tmpdir(), "mcp-loader-test-" + Date.now()) const TEST_HOME = join(TEST_DIR, "home") +const realOs = await import("os") +const realShared = await import("../../shared") + describe("getSystemMcpServerNames", () => { beforeEach(() => { mkdirSync(TEST_DIR, { recursive: true }) mkdirSync(TEST_HOME, { recursive: true }) mock.module("os", () => ({ + ...realOs, homedir: () => TEST_HOME, tmpdir, })) mock.module("../../shared", () => ({ + ...realShared, getClaudeConfigDir: () => join(TEST_HOME, ".claude"), })) }) @@ -22,6 +27,9 @@ describe("getSystemMcpServerNames", () => { afterEach(() => { mock.restore() rmSync(TEST_DIR, { recursive: true, force: true }) + + mock.module("os", () => ({ ...realOs })) + mock.module("../../shared", () => ({ ...realShared })) }) it("returns empty set when no .mcp.json files exist", async () => { diff --git a/src/features/skill-mcp-manager/manager.test.ts b/src/features/skill-mcp-manager/manager.test.ts index f65aa5c55..8e5d015a7 100644 --- a/src/features/skill-mcp-manager/manager.test.ts +++ b/src/features/skill-mcp-manager/manager.test.ts @@ -1,8 +1,13 @@ -import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test" +import { describe, it, expect, beforeEach, afterEach, mock, spyOn, afterAll } from "bun:test" import { SkillMcpManager } from "./manager" import type { SkillMcpClientInfo, SkillMcpServerContext } from "./types" import type { ClaudeCodeMcpServer } from "../claude-code-mcp-loader/types" +const realStreamableHttp = await import( + "@modelcontextprotocol/sdk/client/streamableHttp.js" +) +const realMcpOauthProvider = await import("../mcp-oauth/provider") + // Mock the MCP SDK transports to avoid network calls const mockHttpConnect = mock(() => Promise.reject(new Error("Mocked HTTP connection failure"))) const mockHttpClose = mock(() => Promise.resolve()) @@ -37,6 +42,13 @@ mock.module("../mcp-oauth/provider", () => ({ }, })) +afterAll(() => { + mock.module("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({ + ...realStreamableHttp, + })) + mock.module("../mcp-oauth/provider", () => ({ ...realMcpOauthProvider })) +}) + diff --git a/src/features/tmux-subagent/manager.test.ts b/src/features/tmux-subagent/manager.test.ts index 954a9d8b2..1ab57cbcc 100644 --- a/src/features/tmux-subagent/manager.test.ts +++ b/src/features/tmux-subagent/manager.test.ts @@ -1,9 +1,13 @@ -import { describe, test, expect, mock, beforeEach } from 'bun:test' +import { describe, test, expect, mock, beforeEach, afterAll } from 'bun:test' import type { TmuxConfig } from '../../config/schema' import type { WindowState, PaneAction } from './types' import type { ActionResult, ExecuteContext } from './action-executor' import type { TmuxUtilDeps } from './manager' +const realPaneStateQuerier = await import('./pane-state-querier') +const realActionExecutor = await import('./action-executor') +const realSharedTmux = await import('../../shared/tmux') + type ExecuteActionsResult = { success: boolean spawnedPaneId?: string @@ -71,6 +75,12 @@ mock.module('../../shared/tmux', () => { } }) +afterAll(() => { + mock.module('./pane-state-querier', () => ({ ...realPaneStateQuerier })) + mock.module('./action-executor', () => ({ ...realActionExecutor })) + mock.module('../../shared/tmux', () => ({ ...realSharedTmux })) +}) + const trackedSessions = new Set() function createMockContext(overrides?: { diff --git a/src/hooks/anthropic-context-window-limit-recovery/storage.test.ts b/src/hooks/anthropic-context-window-limit-recovery/storage.test.ts index d5797590f..301af8cb4 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/storage.test.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/storage.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect, mock, beforeEach } from "bun:test" +import { describe, test, expect, mock, beforeEach, afterAll } from "bun:test" import { truncateUntilTargetTokens } from "./storage" import * as storage from "./storage" @@ -11,6 +11,10 @@ mock.module("./storage", () => { } }) +afterAll(() => { + mock.module("./storage", () => ({ ...storage })) +}) + describe("truncateUntilTargetTokens", () => { const sessionID = "test-session" diff --git a/src/hooks/auto-update-checker/hook/background-update-check.test.ts b/src/hooks/auto-update-checker/hook/background-update-check.test.ts index 8d3009b5d..3f073b1fd 100644 --- a/src/hooks/auto-update-checker/hook/background-update-check.test.ts +++ b/src/hooks/auto-update-checker/hook/background-update-check.test.ts @@ -1,4 +1,11 @@ -import { describe, it, expect, mock, beforeEach } from "bun:test" +import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test" + +const realChecker = await import("../checker") +const realVersionChannel = await import("../version-channel") +const realCache = await import("../cache") +const realConfigManager = await import("../../../cli/config-manager") +const realUpdateToasts = await import("./update-toasts") +const realLogger = await import("../../../shared/logger") // Mock modules before importing const mockFindPluginEntry = mock(() => null as any) @@ -39,6 +46,15 @@ mock.module("../../../shared/logger", () => ({ log: () => {}, })) +afterAll(() => { + mock.module("../checker", () => ({ ...realChecker })) + mock.module("../version-channel", () => ({ ...realVersionChannel })) + mock.module("../cache", () => ({ ...realCache })) + mock.module("../../../cli/config-manager", () => ({ ...realConfigManager })) + mock.module("./update-toasts", () => ({ ...realUpdateToasts })) + mock.module("../../../shared/logger", () => ({ ...realLogger })) +}) + const { runBackgroundUpdateCheck } = await import("./background-update-check") describe("runBackgroundUpdateCheck", () => { diff --git a/src/hooks/claude-code-hooks/stop.test.ts b/src/hooks/claude-code-hooks/stop.test.ts index 431b90eb4..9834be85e 100644 --- a/src/hooks/claude-code-hooks/stop.test.ts +++ b/src/hooks/claude-code-hooks/stop.test.ts @@ -1,7 +1,10 @@ -import { describe, it, expect, mock, beforeEach } from "bun:test" +import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test" import type { ClaudeHooksConfig } from "./types" import type { StopContext } from "./stop" +const realCommandExecutor = await import("../../shared/command-executor") +const realLogger = await import("../../shared/logger") + const mockExecuteHookCommand = mock(() => Promise.resolve({ exitCode: 0, stdout: "", stderr: "" }) ) @@ -17,6 +20,11 @@ mock.module("../../shared/logger", () => ({ getLogFilePath: () => "/tmp/test.log", })) +afterAll(() => { + mock.module("../../shared/command-executor", () => ({ ...realCommandExecutor })) + mock.module("../../shared/logger", () => ({ ...realLogger })) +}) + const { executeStopHooks } = await import("./stop") function createStopContext(overrides?: Partial): StopContext { diff --git a/src/hooks/comment-checker/cli.test.ts b/src/hooks/comment-checker/cli.test.ts index 4c7b3bef2..1c75d672c 100644 --- a/src/hooks/comment-checker/cli.test.ts +++ b/src/hooks/comment-checker/cli.test.ts @@ -1,10 +1,19 @@ -import { describe, test, expect, mock } from "bun:test" +import { describe, test, expect, mock, afterAll } from "bun:test" import { chmodSync, mkdtempSync, writeFileSync } from "node:fs" import { join } from "node:path" import { tmpdir } from "node:os" import type { PendingCall } from "./types" +const realCli = await import("./cli") +const cliTsHref = new URL("./cli.ts", import.meta.url).href + +afterAll(() => { + mock.module("./cli", () => ({ ...realCli })) + mock.module("./cli.ts", () => ({ ...realCli })) + mock.module(cliTsHref, () => ({ ...realCli })) +}) + function createMockInput() { return { session_id: "test", diff --git a/src/hooks/comment-checker/hook.apply-patch.test.ts b/src/hooks/comment-checker/hook.apply-patch.test.ts index ec1b4cd8b..46f3171c9 100644 --- a/src/hooks/comment-checker/hook.apply-patch.test.ts +++ b/src/hooks/comment-checker/hook.apply-patch.test.ts @@ -1,4 +1,6 @@ -import { describe, it, expect, mock, beforeEach } from "bun:test" +import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test" + +const realCliRunner = await import("./cli-runner") const processApplyPatchEditsWithCli = mock(async () => {}) @@ -10,6 +12,10 @@ mock.module("./cli-runner", () => ({ processApplyPatchEditsWithCli, })) +afterAll(() => { + mock.module("./cli-runner", () => ({ ...realCliRunner })) +}) + const { createCommentCheckerHooks } = await import("./hook") describe("comment-checker apply_patch integration", () => { diff --git a/src/hooks/compaction-context-injector/index.test.ts b/src/hooks/compaction-context-injector/index.test.ts index a2813916f..737a4e5bb 100644 --- a/src/hooks/compaction-context-injector/index.test.ts +++ b/src/hooks/compaction-context-injector/index.test.ts @@ -1,4 +1,6 @@ -import { describe, expect, it, mock } from "bun:test" +import { describe, expect, it, mock, afterAll } from "bun:test" + +const realSystemDirective = await import("../../shared/system-directive") mock.module("../../shared/system-directive", () => ({ createSystemDirective: (type: string) => `[DIRECTIVE:${type}]`, @@ -14,6 +16,10 @@ mock.module("../../shared/system-directive", () => ({ }, })) +afterAll(() => { + mock.module("../../shared/system-directive", () => ({ ...realSystemDirective })) +}) + import { createCompactionContextInjector } from "./index" import { TaskHistory } from "../../features/background-agent/task-history" diff --git a/src/hooks/rules-injector/injector.test.ts b/src/hooks/rules-injector/injector.test.ts index e07b7fc42..36b815f1e 100644 --- a/src/hooks/rules-injector/injector.test.ts +++ b/src/hooks/rules-injector/injector.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"; +import { afterEach, beforeEach, describe, expect, it, mock, afterAll } from "bun:test"; import * as fs from "node:fs"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import * as os from "node:os"; @@ -6,6 +6,10 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { RULES_INJECTOR_STORAGE } from "./constants"; +const realNodeFs = await import("node:fs"); +const realNodeOs = await import("node:os"); +const realMatcher = await import("./matcher"); + type StatSnapshot = { mtimeMs: number; size: number }; let trackedRulePath = ""; @@ -56,6 +60,12 @@ mock.module("./matcher", () => ({ isDuplicateByContentHash: (hash: string, cache: Set) => cache.has(hash), })); +afterAll(() => { + mock.module("node:fs", () => ({ ...realNodeFs })); + mock.module("node:os", () => ({ ...realNodeOs })); + mock.module("./matcher", () => ({ ...realMatcher })); +}); + function createOutput(): { title: string; output: string; metadata: unknown } { return { title: "tool", output: "", metadata: {} }; } diff --git a/src/tools/lsp/client.test.ts b/src/tools/lsp/client.test.ts index 8c805d144..c750f3dea 100644 --- a/src/tools/lsp/client.test.ts +++ b/src/tools/lsp/client.test.ts @@ -2,7 +2,9 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs" import { join } from "node:path" import { tmpdir } from "node:os" -import { describe, it, expect, spyOn, mock, beforeEach, afterEach } from "bun:test" +import { describe, it, expect, spyOn, mock, beforeEach, afterEach, afterAll } from "bun:test" + +const realJsonRpcNode = await import("vscode-jsonrpc/node") mock.module("vscode-jsonrpc/node", () => ({ createMessageConnection: () => { @@ -12,6 +14,10 @@ mock.module("vscode-jsonrpc/node", () => ({ StreamMessageWriter: function StreamMessageWriter() {}, })) +afterAll(() => { + mock.module("vscode-jsonrpc/node", () => ({ ...realJsonRpcNode })) +}) + import { LSPClient, lspManager, validateCwd } from "./client" import type { ResolvedServer } from "./types" diff --git a/src/tools/session-manager/storage.test.ts b/src/tools/session-manager/storage.test.ts index 76507867a..1f7a400c3 100644 --- a/src/tools/session-manager/storage.test.ts +++ b/src/tools/session-manager/storage.test.ts @@ -1,9 +1,11 @@ -import { describe, test, expect, beforeEach, afterEach, mock } from "bun:test" +import { describe, test, expect, beforeEach, afterEach, afterAll, mock } from "bun:test" import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs" import { join } from "node:path" import { tmpdir } from "node:os" import { randomUUID } from "node:crypto" +const realConstants = await import("./constants") + const TEST_DIR = join(tmpdir(), `omo-test-session-manager-${randomUUID()}`) const TEST_MESSAGE_STORAGE = join(TEST_DIR, "message") const TEST_PART_STORAGE = join(TEST_DIR, "part") @@ -26,6 +28,10 @@ mock.module("./constants", () => ({ TOOL_NAME_PREFIX: "session_", })) +afterAll(() => { + mock.module("./constants", () => ({ ...realConstants })) +}) + const { getAllSessions, getMessageDir, sessionExists, readSessionMessages, readSessionTodos, getSessionInfo } = await import("./storage") diff --git a/src/tools/skill/tools.test.ts b/src/tools/skill/tools.test.ts index e5ce213e9..a4b5bb479 100644 --- a/src/tools/skill/tools.test.ts +++ b/src/tools/skill/tools.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach, mock, spyOn } from "bun:test" +import { describe, it, expect, beforeEach, mock, spyOn, afterAll } from "bun:test" import type { ToolContext } from "@opencode-ai/plugin/tool" import * as fs from "node:fs" import { createSkillTool } from "./tools" @@ -8,6 +8,8 @@ import type { Tool as McpTool } from "@modelcontextprotocol/sdk/types.js" const originalReadFileSync = fs.readFileSync.bind(fs) +const realNodeFs = await import("node:fs") + mock.module("node:fs", () => ({ ...fs, readFileSync: (path: string, encoding?: string) => { @@ -21,6 +23,10 @@ Test skill body content` }, })) +afterAll(() => { + mock.module("node:fs", () => ({ ...realNodeFs })) +}) + function createMockSkill(name: string, options: { agent?: string } = {}): LoadedSkill { return { name,