fix(test): isolate openclaw bootstrap mocks

This commit is contained in:
GeonWoo Jeon (Jay)
2026-04-07 22:49:37 +09:00
parent 21ab1a6475
commit 3f5dfd8201
2 changed files with 184 additions and 2 deletions

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, mock } from "bun:test"
import { afterAll, beforeEach, describe, expect, it, mock } from "bun:test"
describe("experimental.session.compacting handler", () => {
function createCompactingHandler(hooks: {
@@ -217,3 +217,181 @@ describe("look_at tool conditional registration", () => {
})
})
})
const mockInitConfigContext = mock(() => {})
const mockDetectExternalSkillPlugin = mock(() => ({ detected: false, pluginName: null }))
const mockGetSkillPluginConflictWarning = mock(() => "")
const mockInjectServerAuthIntoClient = mock(() => {})
const mockLogLegacyPluginStartupWarning = mock(() => {})
const mockLoadPluginConfig = mock(() => ({}))
const mockIsTmuxIntegrationEnabled = mock(
(pluginConfig: { tmux?: { enabled?: boolean } | undefined }) => pluginConfig.tmux?.enabled ?? false,
)
const mockIsInteractiveBashEnabled = mock(() => false)
const mockCreateRuntimeTmuxConfig = mock(() => ({
enabled: false,
layout: "tiled" as const,
main_pane_size: 60,
main_pane_min_width: 80,
agent_pane_min_width: 40,
isolation: "inline" as const,
}))
const mockCreateManagers = mock(() => ({
backgroundManager: { shutdown: async () => {} },
skillMcpManager: { disconnectAll: async () => {} },
configHandler: async () => {},
}))
const mockCreateTools = mock(async () => ({
mergedSkills: [],
availableSkills: [],
filteredTools: {},
}))
const mockCreateHooks = mock(() => ({
disposeHooks: () => {},
compactionContextInjector: undefined,
compactionTodoPreserver: undefined,
claudeCodeHooks: undefined,
}))
const mockCreatePluginDispose = mock(() => async () => {})
const mockCreatePluginInterface = mock(() => ({}))
const mockInitializeOpenClaw = mock(async () => {})
const mockStartTmuxCheck = mock(() => {})
mock.module("./cli/config-manager/config-context", () => ({
initConfigContext: mockInitConfigContext,
}))
mock.module("./shared/external-plugin-detector", () => ({
detectExternalSkillPlugin: mockDetectExternalSkillPlugin,
getSkillPluginConflictWarning: mockGetSkillPluginConflictWarning,
}))
mock.module("./shared", () => ({
injectServerAuthIntoClient: mockInjectServerAuthIntoClient,
log: mock(() => {}),
logLegacyPluginStartupWarning: mockLogLegacyPluginStartupWarning,
}))
mock.module("./plugin-config", () => ({
loadPluginConfig: mockLoadPluginConfig,
}))
mock.module("./create-runtime-tmux-config", () => ({
createRuntimeTmuxConfig: mockCreateRuntimeTmuxConfig,
isTmuxIntegrationEnabled: mockIsTmuxIntegrationEnabled,
isInteractiveBashEnabled: mockIsInteractiveBashEnabled,
}))
mock.module("./create-managers", () => ({
createManagers: mockCreateManagers,
}))
mock.module("./create-tools", () => ({
createTools: mockCreateTools,
}))
mock.module("./create-hooks", () => ({
createHooks: mockCreateHooks,
}))
mock.module("./plugin-dispose", () => ({
createPluginDispose: mockCreatePluginDispose,
}))
mock.module("./plugin-interface", () => ({
createPluginInterface: mockCreatePluginInterface,
}))
mock.module("./plugin-state", () => ({
createModelCacheState: mock(() => ({})),
}))
mock.module("./shared/first-message-variant", () => ({
createFirstMessageVariantGate: mock(() => ({
shouldOverride: () => false,
markApplied: () => {},
markSessionCreated: () => {},
clear: () => {},
})),
}))
mock.module("./openclaw", () => ({
initializeOpenClaw: mockInitializeOpenClaw,
}))
mock.module("./tools/interactive-bash", () => ({
interactive_bash: {},
startBackgroundCheck: mockStartTmuxCheck,
}))
mock.module("./tools/lsp/client", () => ({
lspManager: {
cleanupTempDirectoryClients: async () => {},
},
}))
const { default: OhMyOpenCodePlugin } = await import("./index")
describe("OhMyOpenCodePlugin", () => {
beforeEach(() => {
mockInitConfigContext.mockClear()
mockDetectExternalSkillPlugin.mockClear()
mockGetSkillPluginConflictWarning.mockClear()
mockInjectServerAuthIntoClient.mockClear()
mockLogLegacyPluginStartupWarning.mockClear()
mockLoadPluginConfig.mockClear()
mockIsTmuxIntegrationEnabled.mockClear()
mockIsInteractiveBashEnabled.mockClear()
mockCreateRuntimeTmuxConfig.mockClear()
mockCreateManagers.mockClear()
mockCreateTools.mockClear()
mockCreateHooks.mockClear()
mockCreatePluginDispose.mockClear()
mockCreatePluginInterface.mockClear()
mockInitializeOpenClaw.mockClear()
mockStartTmuxCheck.mockClear()
})
afterAll(() => {
mock.restore()
})
it("starts openclaw during plugin bootstrap when openclaw config exists", async () => {
// given
const openclawConfig = {
enabled: true,
gateways: {},
hooks: {},
replyListener: {
discordBotToken: "discord-token",
},
}
mockLoadPluginConfig.mockReturnValue({
openclaw: openclawConfig,
})
// when
await OhMyOpenCodePlugin({
directory: "/tmp/project",
client: {},
} as Parameters<typeof OhMyOpenCodePlugin>[0])
// then
expect(mockInitializeOpenClaw).toHaveBeenCalledTimes(1)
expect(mockInitializeOpenClaw).toHaveBeenCalledWith(openclawConfig)
})
it("does not start openclaw when openclaw config is absent", async () => {
// given
mockLoadPluginConfig.mockReturnValue({})
// when
await OhMyOpenCodePlugin({
directory: "/tmp/project",
client: {},
} as Parameters<typeof OhMyOpenCodePlugin>[0])
// then
expect(mockInitializeOpenClaw).not.toHaveBeenCalled()
})
})

View File

@@ -7,6 +7,7 @@ import { createHooks } from "./create-hooks"
import { createManagers } from "./create-managers"
import { createRuntimeTmuxConfig, isTmuxIntegrationEnabled } from "./create-runtime-tmux-config"
import { createTools } from "./create-tools"
import { initializeOpenClaw } from "./openclaw"
import { createPluginInterface } from "./plugin-interface"
import { createPluginDispose, type PluginDispose } from "./plugin-dispose"
@@ -15,8 +16,8 @@ import { createModelCacheState } from "./plugin-state"
import { createFirstMessageVariantGate } from "./shared/first-message-variant"
import { injectServerAuthIntoClient, log, logLegacyPluginStartupWarning } from "./shared"
import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector"
import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
import { lspManager } from "./tools/lsp/client"
import { startTmuxCheck } from "./tools"
let activePluginDispose: PluginDispose | null = null
@@ -36,6 +37,9 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
await activePluginDispose?.()
const pluginConfig = loadPluginConfig(ctx.directory, ctx)
if (pluginConfig.openclaw) {
await initializeOpenClaw(pluginConfig.openclaw)
}
const tmuxIntegrationEnabled = isTmuxIntegrationEnabled(pluginConfig)
if (tmuxIntegrationEnabled) {
startTmuxCheck()