test(core): update main test setup and core tests

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-04-10 15:53:25 +09:00
parent 0e74f5a749
commit fa1b4650ce
3 changed files with 227 additions and 125 deletions
+113 -62
View File
@@ -1,8 +1,12 @@
/// <reference types="bun-types" /> /// <reference types="bun-types" />
import { afterAll, beforeEach, describe, expect, it, mock, spyOn } from "bun:test" import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test"
import type { PluginInput } from "@opencode-ai/plugin"
import { OhMyOpenCodeConfigSchema } from "./config/schema/oh-my-opencode-config"
import { createManagers } from "./create-managers"
import * as openclawRuntimeDispatch from "./openclaw/runtime-dispatch" import * as openclawRuntimeDispatch from "./openclaw/runtime-dispatch"
import { createModelCacheState } from "./plugin-state"
const markServerRunningInProcess = mock(() => {}) const markServerRunningInProcess = mock(() => {})
let backgroundManagerOptions: { let backgroundManagerOptions: {
@@ -10,57 +14,63 @@ let backgroundManagerOptions: {
} | null = null } | null = null
const trackedPaneBySession = new Map<string, string>() const trackedPaneBySession = new Map<string, string>()
mock.module("./features/background-agent", () => ({ class MockBackgroundManager {
BackgroundManager: class BackgroundManager { constructor(
constructor(_ctx: unknown, _config: unknown, options: typeof backgroundManagerOptions) { _ctx: PluginInput,
backgroundManagerOptions = options _config?: unknown,
options?: {
tmuxConfig?: unknown
onSubagentSessionCreated?: (event: { sessionID: string; parentID: string; title: string }) => Promise<void>
onShutdown?: () => void | Promise<void>
enableParentSessionNotifications?: boolean
},
) {
backgroundManagerOptions = options ?? null
}
}
class MockSkillMcpManager {
constructor(..._args: unknown[]) {}
}
class MockTmuxSessionManager {
constructor(_ctx: PluginInput, _config: unknown) {}
async cleanup(): Promise<void> {}
async onSessionCreated(event: { properties?: { info?: { id?: string } } }): Promise<void> {
const sessionID = event.properties?.info?.id
if (sessionID) {
trackedPaneBySession.set(sessionID, `%pane-${sessionID}`)
} }
}, }
}))
mock.module("./features/skill-mcp-manager", () => ({ getTrackedPaneId(sessionID: string): string | undefined {
SkillMcpManager: class SkillMcpManager { return trackedPaneBySession.get(sessionID)
constructor(..._args: unknown[]) {} }
}, }
}))
mock.module("./features/task-toast-manager", () => ({ function createConfigHandler(): ReturnType<typeof import("./plugin-handlers").createConfigHandler> {
initTaskToastManager: mock(() => {}), return async () => {}
})) }
mock.module("./features/tmux-subagent", () => ({ function initTaskToastManager(): ReturnType<typeof import("./features/task-toast-manager").initTaskToastManager> {
TmuxSessionManager: class TmuxSessionManager { return {} as ReturnType<typeof import("./features/task-toast-manager").initTaskToastManager>
constructor(..._args: unknown[]) {} }
async cleanup(): Promise<void> {} function registerManagerForCleanup(): void {}
async onSessionCreated(event: { properties?: { info?: { id?: string } } }): Promise<void> {
const sessionID = event.properties?.info?.id
if (sessionID) {
trackedPaneBySession.set(sessionID, `%pane-${sessionID}`)
}
}
getTrackedPaneId(sessionID: string): string | undefined { function createDeps(): NonNullable<Parameters<typeof createManagers>[0]["deps"]> {
return trackedPaneBySession.get(sessionID) return {
} BackgroundManagerClass: MockBackgroundManager as typeof import("./features/background-agent").BackgroundManager,
}, SkillMcpManagerClass: MockSkillMcpManager as typeof import("./features/skill-mcp-manager").SkillMcpManager,
})) TmuxSessionManagerClass: MockTmuxSessionManager as typeof import("./features/tmux-subagent").TmuxSessionManager,
initTaskToastManagerFn: initTaskToastManager,
mock.module("./features/background-agent/process-cleanup", () => ({ registerManagerForCleanupFn: registerManagerForCleanup,
registerManagerForCleanup: mock(() => {}), createConfigHandlerFn: createConfigHandler,
})) markServerRunningInProcessFn: markServerRunningInProcess,
}
mock.module("./plugin-handlers", () => ({ }
createConfigHandler: mock(() => ({ kind: "config-handler" })),
}))
mock.module("./shared/tmux/tmux-utils/server-health", () => ({
isServerRunning: mock(async () => true),
markServerRunningInProcess,
resetServerCheck: mock(() => {}),
}))
const { createManagers } = await import("./create-managers")
function createTmuxConfig(enabled: boolean) { function createTmuxConfig(enabled: boolean) {
return { return {
@@ -73,28 +83,67 @@ function createTmuxConfig(enabled: boolean) {
} }
} }
function createContext(directory: string): PluginInput {
const shell = Object.assign(
() => {
throw new Error("shell should not be called in this test")
},
{
braces: () => [],
escape: (input: string) => input,
env() {
return shell
},
cwd() {
return shell
},
nothrow() {
return shell
},
throws() {
return shell
},
},
)
return {
project: {
id: "project-id",
worktree: directory,
time: { created: Date.now() },
},
directory,
worktree: directory,
serverUrl: new URL("http://localhost:4096"),
$: shell,
client: {} as PluginInput["client"],
}
}
describe("createManagers", () => { describe("createManagers", () => {
const dispatchOpenClawEvent = spyOn(openclawRuntimeDispatch, "dispatchOpenClawEvent") let dispatchOpenClawEvent: ReturnType<typeof spyOn>
beforeEach(() => { beforeEach(() => {
dispatchOpenClawEvent = spyOn(openclawRuntimeDispatch, "dispatchOpenClawEvent")
markServerRunningInProcess.mockClear() markServerRunningInProcess.mockClear()
dispatchOpenClawEvent.mockReset() dispatchOpenClawEvent.mockReset()
backgroundManagerOptions = null backgroundManagerOptions = null
trackedPaneBySession.clear() trackedPaneBySession.clear()
}) })
afterAll(() => { afterEach(() => {
mock.restore() dispatchOpenClawEvent.mockRestore()
}) })
it("#given tmux integration is disabled #when managers are created #then it does not mark the tmux server as running", () => { it("#given tmux integration is disabled #when managers are created #then it does not mark the tmux server as running", () => {
const args = { const args = {
ctx: { directory: "/tmp", client: {} }, ctx: createContext("/tmp"),
pluginConfig: {}, pluginConfig: OhMyOpenCodeConfigSchema.parse({}),
tmuxConfig: createTmuxConfig(false), tmuxConfig: createTmuxConfig(false),
modelCacheState: {}, modelCacheState: createModelCacheState(),
backgroundNotificationHookEnabled: false, backgroundNotificationHookEnabled: false,
} as Parameters<typeof createManagers>[0] deps: createDeps(),
}
createManagers(args) createManagers(args)
@@ -103,12 +152,13 @@ describe("createManagers", () => {
it("#given tmux integration is enabled #when managers are created #then it marks the tmux server as running", () => { it("#given tmux integration is enabled #when managers are created #then it marks the tmux server as running", () => {
const args = { const args = {
ctx: { directory: "/tmp", client: {} }, ctx: createContext("/tmp"),
pluginConfig: {}, pluginConfig: OhMyOpenCodeConfigSchema.parse({}),
tmuxConfig: createTmuxConfig(true), tmuxConfig: createTmuxConfig(true),
modelCacheState: {}, modelCacheState: createModelCacheState(),
backgroundNotificationHookEnabled: false, backgroundNotificationHookEnabled: false,
} as Parameters<typeof createManagers>[0] deps: createDeps(),
}
createManagers(args) createManagers(args)
@@ -117,18 +167,19 @@ describe("createManagers", () => {
it("#given openclaw is enabled #when the background session-created callback runs #then it dispatches openclaw with the tracked pane id", async () => { it("#given openclaw is enabled #when the background session-created callback runs #then it dispatches openclaw with the tracked pane id", async () => {
const args = { const args = {
ctx: { directory: "/tmp/project", client: {} }, ctx: createContext("/tmp/project"),
pluginConfig: { pluginConfig: OhMyOpenCodeConfigSchema.parse({
openclaw: { openclaw: {
enabled: true, enabled: true,
gateways: {}, gateways: {},
hooks: {}, hooks: {},
}, },
}, }),
tmuxConfig: createTmuxConfig(true), tmuxConfig: createTmuxConfig(true),
modelCacheState: {}, modelCacheState: createModelCacheState(),
backgroundNotificationHookEnabled: false, backgroundNotificationHookEnabled: false,
} as Parameters<typeof createManagers>[0] deps: createDeps(),
}
createManagers(args) createManagers(args)
+66 -62
View File
@@ -1,4 +1,4 @@
import { afterAll, beforeEach, describe, expect, it, mock } from "bun:test" import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"
describe("experimental.session.compacting handler", () => { describe("experimental.session.compacting handler", () => {
function createCompactingHandler(hooks: { function createCompactingHandler(hooks: {
@@ -257,83 +257,87 @@ const mockCreatePluginInterface = mock(() => ({}))
const mockInitializeOpenClaw = mock(async () => {}) const mockInitializeOpenClaw = mock(async () => {})
const mockStartTmuxCheck = mock(() => {}) const mockStartTmuxCheck = mock(() => {})
mock.module("./cli/config-manager/config-context", () => ({ let OhMyOpenCodePlugin: (typeof import("./index"))["default"]
initConfigContext: mockInitConfigContext,
}))
mock.module("./shared/external-plugin-detector", () => ({ function installIndexModuleMocks(): void {
detectExternalSkillPlugin: mockDetectExternalSkillPlugin, mock.module("./cli/config-manager/config-context", () => ({
getSkillPluginConflictWarning: mockGetSkillPluginConflictWarning, initConfigContext: mockInitConfigContext,
})) }))
mock.module("./shared", () => ({ mock.module("./shared/external-plugin-detector", () => ({
injectServerAuthIntoClient: mockInjectServerAuthIntoClient, detectExternalSkillPlugin: mockDetectExternalSkillPlugin,
log: mock(() => {}), getSkillPluginConflictWarning: mockGetSkillPluginConflictWarning,
logLegacyPluginStartupWarning: mockLogLegacyPluginStartupWarning, }))
}))
mock.module("./plugin-config", () => ({ mock.module("./shared", () => ({
loadPluginConfig: mockLoadPluginConfig, injectServerAuthIntoClient: mockInjectServerAuthIntoClient,
})) log: mock(() => {}),
logLegacyPluginStartupWarning: mockLogLegacyPluginStartupWarning,
}))
mock.module("./create-runtime-tmux-config", () => ({ mock.module("./plugin-config", () => ({
createRuntimeTmuxConfig: mockCreateRuntimeTmuxConfig, loadPluginConfig: mockLoadPluginConfig,
isTmuxIntegrationEnabled: mockIsTmuxIntegrationEnabled, }))
isInteractiveBashEnabled: mockIsInteractiveBashEnabled,
}))
mock.module("./create-managers", () => ({ mock.module("./create-runtime-tmux-config", () => ({
createManagers: mockCreateManagers, createRuntimeTmuxConfig: mockCreateRuntimeTmuxConfig,
})) isTmuxIntegrationEnabled: mockIsTmuxIntegrationEnabled,
isInteractiveBashEnabled: mockIsInteractiveBashEnabled,
}))
mock.module("./create-tools", () => ({ mock.module("./create-managers", () => ({
createTools: mockCreateTools, createManagers: mockCreateManagers,
})) }))
mock.module("./create-hooks", () => ({ mock.module("./create-tools", () => ({
createHooks: mockCreateHooks, createTools: mockCreateTools,
})) }))
mock.module("./plugin-dispose", () => ({ mock.module("./create-hooks", () => ({
createPluginDispose: mockCreatePluginDispose, createHooks: mockCreateHooks,
})) }))
mock.module("./plugin-interface", () => ({ mock.module("./plugin-dispose", () => ({
createPluginInterface: mockCreatePluginInterface, createPluginDispose: mockCreatePluginDispose,
})) }))
mock.module("./plugin-state", () => ({ mock.module("./plugin-interface", () => ({
createModelCacheState: mock(() => ({})), createPluginInterface: mockCreatePluginInterface,
})) }))
mock.module("./shared/first-message-variant", () => ({ mock.module("./plugin-state", () => ({
createFirstMessageVariantGate: mock(() => ({ createModelCacheState: mock(() => ({})),
shouldOverride: () => false, }))
markApplied: () => {},
markSessionCreated: () => {},
clear: () => {},
})),
}))
mock.module("./openclaw", () => ({ mock.module("./shared/first-message-variant", () => ({
initializeOpenClaw: mockInitializeOpenClaw, createFirstMessageVariantGate: mock(() => ({
})) shouldOverride: () => false,
markApplied: () => {},
markSessionCreated: () => {},
clear: () => {},
})),
}))
mock.module("./tools/interactive-bash", () => ({ mock.module("./openclaw", () => ({
interactive_bash: {}, initializeOpenClaw: mockInitializeOpenClaw,
startBackgroundCheck: mockStartTmuxCheck, }))
}))
mock.module("./tools/lsp/client", () => ({ mock.module("./tools/interactive-bash", () => ({
lspManager: { interactive_bash: {},
cleanupTempDirectoryClients: async () => {}, startBackgroundCheck: mockStartTmuxCheck,
}, }))
}))
const { default: OhMyOpenCodePlugin } = await import("./index") }
async function importFreshIndexModule(): Promise<typeof import("./index")> {
return import(`./index?test=${Date.now()}-${Math.random()}`)
}
describe("OhMyOpenCodePlugin", () => { describe("OhMyOpenCodePlugin", () => {
beforeEach(() => { beforeEach(async () => {
mock.restore()
installIndexModuleMocks()
;({ default: OhMyOpenCodePlugin } = await importFreshIndexModule())
mockInitConfigContext.mockClear() mockInitConfigContext.mockClear()
mockDetectExternalSkillPlugin.mockClear() mockDetectExternalSkillPlugin.mockClear()
mockGetSkillPluginConflictWarning.mockClear() mockGetSkillPluginConflictWarning.mockClear()
@@ -352,7 +356,7 @@ describe("OhMyOpenCodePlugin", () => {
mockStartTmuxCheck.mockClear() mockStartTmuxCheck.mockClear()
}) })
afterAll(() => { afterEach(() => {
mock.restore() mock.restore()
}) })
+48 -1
View File
@@ -1,10 +1,57 @@
import { beforeEach } from "bun:test" import { afterEach, beforeEach, mock } from "bun:test"
import { rmSync } from "node:fs"
import { _resetForTesting as resetClaudeSessionState } from "./src/features/claude-code-session-state/state" import { _resetForTesting as resetClaudeSessionState } from "./src/features/claude-code-session-state/state"
import { _resetTaskToastManagerForTesting as resetTaskToastManager } from "./src/features/task-toast-manager/manager"
import { _resetForTesting as resetModelFallbackState } from "./src/hooks/model-fallback/hook" import { _resetForTesting as resetModelFallbackState } from "./src/hooks/model-fallback/hook"
import { _resetMemCacheForTesting as resetConnectedProvidersCache } from "./src/shared/connected-providers-cache" import { _resetMemCacheForTesting as resetConnectedProvidersCache } from "./src/shared/connected-providers-cache"
import { getOmoOpenCodeCacheDir } from "./src/shared/data-path"
import { installModuleMockLifecycle } from "./src/testing/module-mock-lifecycle"
const { restoreModuleMocks } = installModuleMockLifecycle(mock)
let environmentSnapshot: NodeJS.ProcessEnv = { ...process.env }
let workingDirectorySnapshot = process.cwd()
function cleanupOmoCacheDir(cacheDir: string): void {
rmSync(cacheDir, { recursive: true, force: true })
}
beforeEach(() => { beforeEach(() => {
environmentSnapshot = { ...process.env }
workingDirectorySnapshot = process.cwd()
process.env.OMO_DISABLE_POSTHOG = "true"
cleanupOmoCacheDir(getOmoOpenCodeCacheDir())
resetClaudeSessionState() resetClaudeSessionState()
resetTaskToastManager()
resetModelFallbackState() resetModelFallbackState()
resetConnectedProvidersCache() resetConnectedProvidersCache()
}) })
afterEach(() => {
const currentCacheDir = getOmoOpenCodeCacheDir()
for (const key of Object.keys(process.env)) {
if (!(key in environmentSnapshot)) {
delete process.env[key]
}
}
for (const [key, value] of Object.entries(environmentSnapshot)) {
if (value === undefined) {
delete process.env[key]
continue
}
process.env[key] = value
}
if (process.cwd() !== workingDirectorySnapshot) {
process.chdir(workingDirectorySnapshot)
}
cleanupOmoCacheDir(currentCacheDir)
cleanupOmoCacheDir(getOmoOpenCodeCacheDir())
resetTaskToastManager()
resetConnectedProvidersCache()
mock.restore()
restoreModuleMocks()
})