Merge pull request #3058 from code-yeongyu/fix/p1-11-remove-as-any-tests
fix(tests): remove as any from new test files
This commit is contained in:
@@ -31,6 +31,8 @@ function createPluginConfig(overrides: Partial<OhMyOpenCodeConfig> = {}): OhMyOp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let setAdditionalAllowedMcpEnvVarsSpy: ReturnType<typeof spyOn> | undefined
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(agents, "createBuiltinAgents" as any).mockResolvedValue({
|
spyOn(agents, "createBuiltinAgents" as any).mockResolvedValue({
|
||||||
sisyphus: { name: "sisyphus", prompt: "test", mode: "primary" },
|
sisyphus: { name: "sisyphus", prompt: "test", mode: "primary" },
|
||||||
@@ -57,7 +59,7 @@ beforeEach(() => {
|
|||||||
spyOn(agentLoader, "loadProjectAgents" as any).mockReturnValue({})
|
spyOn(agentLoader, "loadProjectAgents" as any).mockReturnValue({})
|
||||||
|
|
||||||
spyOn(mcpLoader, "loadMcpConfigs" as any).mockResolvedValue({ servers: {} })
|
spyOn(mcpLoader, "loadMcpConfigs" as any).mockResolvedValue({ servers: {} })
|
||||||
spyOn(mcpLoader, "setAdditionalAllowedMcpEnvVars").mockImplementation(() => {})
|
setAdditionalAllowedMcpEnvVarsSpy = spyOn(mcpLoader, "setAdditionalAllowedMcpEnvVars").mockImplementation(() => {})
|
||||||
|
|
||||||
spyOn(pluginLoader, "loadAllPluginComponents" as any).mockResolvedValue({
|
spyOn(pluginLoader, "loadAllPluginComponents" as any).mockResolvedValue({
|
||||||
commands: {},
|
commands: {},
|
||||||
@@ -104,7 +106,7 @@ afterEach(() => {
|
|||||||
;(agentLoader.loadUserAgents as any)?.mockRestore?.()
|
;(agentLoader.loadUserAgents as any)?.mockRestore?.()
|
||||||
;(agentLoader.loadProjectAgents as any)?.mockRestore?.()
|
;(agentLoader.loadProjectAgents as any)?.mockRestore?.()
|
||||||
;(mcpLoader.loadMcpConfigs as any)?.mockRestore?.()
|
;(mcpLoader.loadMcpConfigs as any)?.mockRestore?.()
|
||||||
;(mcpLoader.setAdditionalAllowedMcpEnvVars as any)?.mockRestore?.()
|
setAdditionalAllowedMcpEnvVarsSpy?.mockRestore()
|
||||||
;(pluginLoader.loadAllPluginComponents as any)?.mockRestore?.()
|
;(pluginLoader.loadAllPluginComponents as any)?.mockRestore?.()
|
||||||
;(mcpModule.createBuiltinMcps as any)?.mockRestore?.()
|
;(mcpModule.createBuiltinMcps as any)?.mockRestore?.()
|
||||||
;(shared.log as any)?.mockRestore?.()
|
;(shared.log as any)?.mockRestore?.()
|
||||||
|
|||||||
+102
-67
@@ -7,6 +7,60 @@ import { clearPendingModelFallback, createModelFallbackHook } from "../hooks/mod
|
|||||||
import { getSessionPromptParams, setSessionPromptParams } from "../shared/session-prompt-params-state"
|
import { getSessionPromptParams, setSessionPromptParams } from "../shared/session-prompt-params-state"
|
||||||
|
|
||||||
type EventInput = { event: { type: string; properties?: unknown } }
|
type EventInput = { event: { type: string; properties?: unknown } }
|
||||||
|
type EventHandlerArgs = Parameters<typeof createEventHandler>[0]
|
||||||
|
type EventHandlerInput = Parameters<ReturnType<typeof createEventHandler>>[0]
|
||||||
|
type ChatMessageHandlerArgs = Parameters<typeof createChatMessageHandler>[0]
|
||||||
|
|
||||||
|
function asEventHandlerInput(input: EventInput): EventHandlerInput {
|
||||||
|
return input as unknown as EventHandlerInput
|
||||||
|
}
|
||||||
|
|
||||||
|
function asEventHandlerContext(ctx: unknown): EventHandlerArgs["ctx"] {
|
||||||
|
return ctx as unknown as EventHandlerArgs["ctx"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function asChatMessageHandlerContext(ctx: unknown): ChatMessageHandlerArgs["ctx"] {
|
||||||
|
return ctx as unknown as ChatMessageHandlerArgs["ctx"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function asPluginConfig(config: unknown): EventHandlerArgs["pluginConfig"] {
|
||||||
|
return config as unknown as EventHandlerArgs["pluginConfig"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function asChatPluginConfig(config: unknown): ChatMessageHandlerArgs["pluginConfig"] {
|
||||||
|
return config as unknown as ChatMessageHandlerArgs["pluginConfig"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEventHandlerManagers(
|
||||||
|
overrides: Record<string, unknown> = {},
|
||||||
|
): EventHandlerArgs["managers"] {
|
||||||
|
return {
|
||||||
|
...({} as EventHandlerArgs["managers"]),
|
||||||
|
tmuxSessionManager: {
|
||||||
|
onSessionCreated: async () => {},
|
||||||
|
onSessionDeleted: async () => {},
|
||||||
|
},
|
||||||
|
...overrides,
|
||||||
|
} as unknown as EventHandlerArgs["managers"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEventHandlerHooks(
|
||||||
|
overrides: Record<string, unknown>,
|
||||||
|
): EventHandlerArgs["hooks"] {
|
||||||
|
return {
|
||||||
|
...({} as EventHandlerArgs["hooks"]),
|
||||||
|
...overrides,
|
||||||
|
} as unknown as EventHandlerArgs["hooks"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createChatMessageHandlerHooks(
|
||||||
|
overrides: Record<string, unknown>,
|
||||||
|
): ChatMessageHandlerArgs["hooks"] {
|
||||||
|
return {
|
||||||
|
...({} as ChatMessageHandlerArgs["hooks"]),
|
||||||
|
...overrides,
|
||||||
|
} as unknown as ChatMessageHandlerArgs["hooks"]
|
||||||
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
_resetForTesting()
|
_resetForTesting()
|
||||||
@@ -429,12 +483,12 @@ describe("createEventHandler - event forwarding", () => {
|
|||||||
const sessionID = "ses_forward_delete_event"
|
const sessionID = "ses_forward_delete_event"
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
await eventHandler({
|
await eventHandler(asEventHandlerInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.deleted",
|
type: "session.deleted",
|
||||||
properties: { info: { id: sessionID } },
|
properties: { info: { id: sessionID } },
|
||||||
},
|
},
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(forwardedEvents.length).toBe(1)
|
expect(forwardedEvents.length).toBe(1)
|
||||||
@@ -471,12 +525,12 @@ describe("createEventHandler - event forwarding", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
await eventHandler({
|
await eventHandler(asEventHandlerInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.deleted",
|
type: "session.deleted",
|
||||||
properties: { info: { id: sessionID } },
|
properties: { info: { id: sessionID } },
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(getSessionPromptParams(sessionID)).toBeUndefined()
|
expect(getSessionPromptParams(sessionID)).toBeUndefined()
|
||||||
@@ -495,7 +549,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
const modelFallback = createModelFallbackHook()
|
const modelFallback = createModelFallbackHook()
|
||||||
|
|
||||||
const eventHandler = createEventHandler({
|
const eventHandler = createEventHandler({
|
||||||
ctx: {
|
ctx: asEventHandlerContext({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -509,41 +563,37 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any,
|
}),
|
||||||
pluginConfig: {} as any,
|
pluginConfig: asPluginConfig({}),
|
||||||
firstMessageVariantGate: {
|
firstMessageVariantGate: {
|
||||||
markSessionCreated: () => {},
|
markSessionCreated: () => {},
|
||||||
clear: () => {},
|
clear: () => {},
|
||||||
},
|
},
|
||||||
managers: {
|
managers: createEventHandlerManagers({
|
||||||
tmuxSessionManager: {
|
|
||||||
onSessionCreated: async () => {},
|
|
||||||
onSessionDeleted: async () => {},
|
|
||||||
},
|
|
||||||
skillMcpManager: {
|
skillMcpManager: {
|
||||||
disconnectSession: async () => {},
|
disconnectSession: async () => {},
|
||||||
},
|
},
|
||||||
} as any,
|
}),
|
||||||
hooks: {
|
hooks: createEventHandlerHooks({
|
||||||
modelFallback,
|
modelFallback,
|
||||||
stopContinuationGuard: { isStopped: () => false },
|
stopContinuationGuard: { isStopped: () => false },
|
||||||
} as any,
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const chatMessageHandler = createChatMessageHandler({
|
const chatMessageHandler = createChatMessageHandler({
|
||||||
ctx: {
|
ctx: asChatMessageHandlerContext({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async () => ({}),
|
showToast: async () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any,
|
}),
|
||||||
pluginConfig: {} as any,
|
pluginConfig: asChatPluginConfig({}),
|
||||||
firstMessageVariantGate: {
|
firstMessageVariantGate: {
|
||||||
shouldOverride: () => false,
|
shouldOverride: () => false,
|
||||||
markApplied: () => {},
|
markApplied: () => {},
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: createChatMessageHandlerHooks({
|
||||||
modelFallback,
|
modelFallback,
|
||||||
stopContinuationGuard: null,
|
stopContinuationGuard: null,
|
||||||
keywordDetector: null,
|
keywordDetector: null,
|
||||||
@@ -551,7 +601,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
autoSlashCommand: null,
|
autoSlashCommand: null,
|
||||||
startWork: null,
|
startWork: null,
|
||||||
ralphLoop: null,
|
ralphLoop: null,
|
||||||
} as any,
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const retryStatus = {
|
const retryStatus = {
|
||||||
@@ -561,7 +611,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
next: 476,
|
next: 476,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
await eventHandler({
|
await eventHandler(asEventHandlerInput({
|
||||||
event: {
|
event: {
|
||||||
type: "message.updated",
|
type: "message.updated",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -575,10 +625,10 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
//#when - first retry key is handled
|
//#when - first retry key is handled
|
||||||
await eventHandler({
|
await eventHandler(asEventHandlerInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.status",
|
type: "session.status",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -586,7 +636,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
status: retryStatus,
|
status: retryStatus,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
const firstOutput = { message: {}, parts: [] as Array<{ type: string; text?: string }> }
|
const firstOutput = { message: {}, parts: [] as Array<{ type: string; text?: string }> }
|
||||||
await chatMessageHandler(
|
await chatMessageHandler(
|
||||||
@@ -599,7 +649,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
//#when - session recovers to non-retry idle state
|
//#when - session recovers to non-retry idle state
|
||||||
await eventHandler({
|
await eventHandler(asEventHandlerInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.status",
|
type: "session.status",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -607,10 +657,10 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
status: { type: "idle" },
|
status: { type: "idle" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
//#when - same retry key appears again after recovery
|
//#when - same retry key appears again after recovery
|
||||||
await eventHandler({
|
await eventHandler(asEventHandlerInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.status",
|
type: "session.status",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -618,7 +668,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
status: retryStatus,
|
status: retryStatus,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(abortCalls).toEqual([sessionID, sessionID])
|
expect(abortCalls).toEqual([sessionID, sessionID])
|
||||||
@@ -634,7 +684,7 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
const callOrder: string[] = []
|
const callOrder: string[] = []
|
||||||
|
|
||||||
const eventHandler = createEventHandler({
|
const eventHandler = createEventHandler({
|
||||||
ctx: {
|
ctx: asEventHandlerContext({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -649,29 +699,24 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any,
|
}),
|
||||||
pluginConfig: {} as any,
|
pluginConfig: asPluginConfig({}),
|
||||||
firstMessageVariantGate: {
|
firstMessageVariantGate: {
|
||||||
markSessionCreated: () => {},
|
markSessionCreated: () => {},
|
||||||
clear: () => {},
|
clear: () => {},
|
||||||
},
|
},
|
||||||
managers: {
|
managers: createEventHandlerManagers(),
|
||||||
tmuxSessionManager: {
|
hooks: createEventHandlerHooks({
|
||||||
onSessionCreated: async () => {},
|
|
||||||
onSessionDeleted: async () => {},
|
|
||||||
},
|
|
||||||
} as any,
|
|
||||||
hooks: {
|
|
||||||
sessionRecovery: {
|
sessionRecovery: {
|
||||||
isRecoverableError: () => true,
|
isRecoverableError: () => true,
|
||||||
handleSessionRecovery: async () => true,
|
handleSessionRecovery: async () => true,
|
||||||
},
|
},
|
||||||
stopContinuationGuard: { isStopped: () => false },
|
stopContinuationGuard: { isStopped: () => false },
|
||||||
} as any,
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
await eventHandler({
|
await eventHandler(asEventHandlerInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.error",
|
type: "session.error",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -680,7 +725,7 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
error: { name: "Error", message: "tool_result block(s) that are not immediately" },
|
error: { name: "Error", message: "tool_result block(s) that are not immediately" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
//#then - summarize (compaction) must be called before prompt (continue)
|
//#then - summarize (compaction) must be called before prompt (continue)
|
||||||
expect(callOrder).toEqual(["summarize", "prompt"])
|
expect(callOrder).toEqual(["summarize", "prompt"])
|
||||||
@@ -693,7 +738,7 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
const callOrder: string[] = []
|
const callOrder: string[] = []
|
||||||
|
|
||||||
const eventHandler = createEventHandler({
|
const eventHandler = createEventHandler({
|
||||||
ctx: {
|
ctx: asEventHandlerContext({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -708,29 +753,24 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any,
|
}),
|
||||||
pluginConfig: {} as any,
|
pluginConfig: asPluginConfig({}),
|
||||||
firstMessageVariantGate: {
|
firstMessageVariantGate: {
|
||||||
markSessionCreated: () => {},
|
markSessionCreated: () => {},
|
||||||
clear: () => {},
|
clear: () => {},
|
||||||
},
|
},
|
||||||
managers: {
|
managers: createEventHandlerManagers(),
|
||||||
tmuxSessionManager: {
|
hooks: createEventHandlerHooks({
|
||||||
onSessionCreated: async () => {},
|
|
||||||
onSessionDeleted: async () => {},
|
|
||||||
},
|
|
||||||
} as any,
|
|
||||||
hooks: {
|
|
||||||
sessionRecovery: {
|
sessionRecovery: {
|
||||||
isRecoverableError: () => true,
|
isRecoverableError: () => true,
|
||||||
handleSessionRecovery: async () => true,
|
handleSessionRecovery: async () => true,
|
||||||
},
|
},
|
||||||
stopContinuationGuard: { isStopped: () => false },
|
stopContinuationGuard: { isStopped: () => false },
|
||||||
} as any,
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
await eventHandler({
|
await eventHandler(asEventHandlerInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.error",
|
type: "session.error",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -739,7 +779,7 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
error: { name: "Error", message: "tool_result block(s) that are not immediately" },
|
error: { name: "Error", message: "tool_result block(s) that are not immediately" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
//#then - continue is still sent even when compaction fails
|
//#then - continue is still sent even when compaction fails
|
||||||
expect(callOrder).toEqual(["summarize", "prompt"])
|
expect(callOrder).toEqual(["summarize", "prompt"])
|
||||||
@@ -750,7 +790,7 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
const runtimeFallbackCalls: EventInput[] = []
|
const runtimeFallbackCalls: EventInput[] = []
|
||||||
|
|
||||||
const eventHandler = createEventHandler({
|
const eventHandler = createEventHandler({
|
||||||
ctx: {
|
ctx: asEventHandlerContext({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -758,19 +798,14 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
prompt: async () => ({}),
|
prompt: async () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any,
|
}),
|
||||||
pluginConfig: {} as any,
|
pluginConfig: asPluginConfig({}),
|
||||||
firstMessageVariantGate: {
|
firstMessageVariantGate: {
|
||||||
markSessionCreated: () => {},
|
markSessionCreated: () => {},
|
||||||
clear: () => {},
|
clear: () => {},
|
||||||
},
|
},
|
||||||
managers: {
|
managers: createEventHandlerManagers(),
|
||||||
tmuxSessionManager: {
|
hooks: createEventHandlerHooks({
|
||||||
onSessionCreated: async () => {},
|
|
||||||
onSessionDeleted: async () => {},
|
|
||||||
},
|
|
||||||
} as any,
|
|
||||||
hooks: {
|
|
||||||
autoUpdateChecker: {
|
autoUpdateChecker: {
|
||||||
event: async () => {
|
event: async () => {
|
||||||
throw new Error("upstream hook failed")
|
throw new Error("upstream hook failed")
|
||||||
@@ -782,13 +817,13 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
stopContinuationGuard: { isStopped: () => false },
|
stopContinuationGuard: { isStopped: () => false },
|
||||||
} as any,
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
let thrownError: unknown
|
let thrownError: unknown
|
||||||
try {
|
try {
|
||||||
await eventHandler({
|
await eventHandler(asEventHandlerInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.error",
|
type: "session.error",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -796,7 +831,7 @@ describe("createEventHandler - session recovery compaction", () => {
|
|||||||
error: { name: "Error", message: "retry me" },
|
error: { name: "Error", message: "retry me" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any)
|
}))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
thrownError = error
|
thrownError = error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,55 @@ import { createEventHandler } from "./event"
|
|||||||
import { createChatMessageHandler } from "./chat-message"
|
import { createChatMessageHandler } from "./chat-message"
|
||||||
import { createModelFallbackHook } from "../hooks/model-fallback/hook"
|
import { createModelFallbackHook } from "../hooks/model-fallback/hook"
|
||||||
import { createRuntimeFallbackHook } from "../hooks/runtime-fallback"
|
import { createRuntimeFallbackHook } from "../hooks/runtime-fallback"
|
||||||
|
import type { RuntimeFallbackPluginInput } from "../hooks/runtime-fallback/types"
|
||||||
import { _resetForTesting } from "../features/claude-code-session-state"
|
import { _resetForTesting } from "../features/claude-code-session-state"
|
||||||
import { _resetForTesting as _resetModelFallbackForTesting } from "../hooks/model-fallback/hook"
|
import { _resetForTesting as _resetModelFallbackForTesting } from "../hooks/model-fallback/hook"
|
||||||
import { SessionCategoryRegistry } from "../shared/session-category-registry"
|
import { SessionCategoryRegistry } from "../shared/session-category-registry"
|
||||||
|
|
||||||
|
type EventHandlerArgs = Parameters<typeof createEventHandler>[0]
|
||||||
|
type ChatMessageHandlerArgs = Parameters<typeof createChatMessageHandler>[0]
|
||||||
|
type HarnessContext = EventHandlerArgs["ctx"] & RuntimeFallbackPluginInput
|
||||||
|
type HarnessEventInput = Parameters<ReturnType<typeof createHarness>["eventHandler"]>[0]
|
||||||
|
|
||||||
|
function asHarnessEventInput(input: unknown): HarnessEventInput {
|
||||||
|
return input as unknown as HarnessEventInput
|
||||||
|
}
|
||||||
|
|
||||||
|
function asHarnessContext(ctx: unknown): HarnessContext {
|
||||||
|
return ctx as unknown as HarnessContext
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEventHandlerManagers(
|
||||||
|
overrides: Record<string, unknown> = {},
|
||||||
|
): EventHandlerArgs["managers"] {
|
||||||
|
return {
|
||||||
|
...({} as EventHandlerArgs["managers"]),
|
||||||
|
tmuxSessionManager: {
|
||||||
|
onSessionCreated: async () => {},
|
||||||
|
onSessionDeleted: async () => {},
|
||||||
|
},
|
||||||
|
...overrides,
|
||||||
|
} as unknown as EventHandlerArgs["managers"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEventHandlerHooks(
|
||||||
|
overrides: Record<string, unknown>,
|
||||||
|
): EventHandlerArgs["hooks"] {
|
||||||
|
return {
|
||||||
|
...({} as EventHandlerArgs["hooks"]),
|
||||||
|
...overrides,
|
||||||
|
} as unknown as EventHandlerArgs["hooks"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createChatMessageHandlerHooks(
|
||||||
|
overrides: Record<string, unknown>,
|
||||||
|
): ChatMessageHandlerArgs["hooks"] {
|
||||||
|
return {
|
||||||
|
...({} as ChatMessageHandlerArgs["hooks"]),
|
||||||
|
...overrides,
|
||||||
|
} as unknown as ChatMessageHandlerArgs["hooks"]
|
||||||
|
}
|
||||||
|
|
||||||
const PRIMARY_MODEL = {
|
const PRIMARY_MODEL = {
|
||||||
providerID: PROVIDER_ID,
|
providerID: PROVIDER_ID,
|
||||||
modelID: "claude-opus-4-6",
|
modelID: "claude-opus-4-6",
|
||||||
@@ -59,7 +104,7 @@ function createPluginConfig(mode: HarnessMode) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
}
|
} as unknown as EventHandlerArgs["pluginConfig"]
|
||||||
}
|
}
|
||||||
|
|
||||||
function createHarness(args: {
|
function createHarness(args: {
|
||||||
@@ -72,7 +117,7 @@ function createHarness(args: {
|
|||||||
const promptAsyncCalls: PromptAsyncCall[] = []
|
const promptAsyncCalls: PromptAsyncCall[] = []
|
||||||
const pluginConfig = createPluginConfig(args.mode)
|
const pluginConfig = createPluginConfig(args.mode)
|
||||||
|
|
||||||
const ctx = {
|
const ctx = asHarnessContext({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -119,7 +164,7 @@ function createHarness(args: {
|
|||||||
showToast: async () => ({}),
|
showToast: async () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any
|
})
|
||||||
|
|
||||||
const hooks: Record<string, unknown> = {
|
const hooks: Record<string, unknown> = {
|
||||||
stopContinuationGuard: null,
|
stopContinuationGuard: null,
|
||||||
@@ -145,38 +190,34 @@ function createHarness(args: {
|
|||||||
timeout_seconds: args.sessionTimeoutMs ? 30 : 0,
|
timeout_seconds: args.sessionTimeoutMs ? 30 : 0,
|
||||||
notify_on_fallback: false,
|
notify_on_fallback: false,
|
||||||
},
|
},
|
||||||
pluginConfig,
|
pluginConfig: pluginConfig as unknown as EventHandlerArgs["pluginConfig"],
|
||||||
...(args.sessionTimeoutMs ? { session_timeout_ms: args.sessionTimeoutMs } : {}),
|
...(args.sessionTimeoutMs ? { session_timeout_ms: args.sessionTimeoutMs } : {}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventHandler = createEventHandler({
|
const eventHandler = createEventHandler({
|
||||||
ctx,
|
ctx,
|
||||||
pluginConfig: pluginConfig as any,
|
pluginConfig: pluginConfig as unknown as EventHandlerArgs["pluginConfig"],
|
||||||
firstMessageVariantGate: {
|
firstMessageVariantGate: {
|
||||||
markSessionCreated: () => {},
|
markSessionCreated: () => {},
|
||||||
clear: () => {},
|
clear: () => {},
|
||||||
},
|
},
|
||||||
managers: {
|
managers: createEventHandlerManagers({
|
||||||
tmuxSessionManager: {
|
|
||||||
onSessionCreated: async () => {},
|
|
||||||
onSessionDeleted: async () => {},
|
|
||||||
},
|
|
||||||
skillMcpManager: {
|
skillMcpManager: {
|
||||||
disconnectSession: async () => {},
|
disconnectSession: async () => {},
|
||||||
},
|
},
|
||||||
} as any,
|
}),
|
||||||
hooks: hooks as any,
|
hooks: createEventHandlerHooks(hooks),
|
||||||
})
|
})
|
||||||
|
|
||||||
const chatMessageHandler = createChatMessageHandler({
|
const chatMessageHandler = createChatMessageHandler({
|
||||||
ctx,
|
ctx,
|
||||||
pluginConfig: pluginConfig as any,
|
pluginConfig: pluginConfig as unknown as ChatMessageHandlerArgs["pluginConfig"],
|
||||||
firstMessageVariantGate: {
|
firstMessageVariantGate: {
|
||||||
shouldOverride: () => false,
|
shouldOverride: () => false,
|
||||||
markApplied: () => {},
|
markApplied: () => {},
|
||||||
},
|
},
|
||||||
hooks: hooks as any,
|
hooks: createChatMessageHandlerHooks(hooks),
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -192,7 +233,7 @@ async function primeMainSession(
|
|||||||
eventHandler: ReturnType<typeof createHarness>["eventHandler"],
|
eventHandler: ReturnType<typeof createHarness>["eventHandler"],
|
||||||
sessionID: string,
|
sessionID: string,
|
||||||
) {
|
) {
|
||||||
await eventHandler({
|
await eventHandler(asHarnessEventInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.created",
|
type: "session.created",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -202,9 +243,9 @@ async function primeMainSession(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
|
|
||||||
await eventHandler({
|
await eventHandler(asHarnessEventInput({
|
||||||
event: {
|
event: {
|
||||||
type: "message.updated",
|
type: "message.updated",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -221,7 +262,7 @@ async function primeMainSession(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendNextMessage(
|
async function sendNextMessage(
|
||||||
@@ -241,7 +282,7 @@ async function triggerSessionError(
|
|||||||
eventHandler: ReturnType<typeof createHarness>["eventHandler"],
|
eventHandler: ReturnType<typeof createHarness>["eventHandler"],
|
||||||
sessionID: string,
|
sessionID: string,
|
||||||
) {
|
) {
|
||||||
await eventHandler({
|
await eventHandler(asHarnessEventInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.error",
|
type: "session.error",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -256,14 +297,14 @@ async function triggerSessionError(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function triggerSessionStatusRetry(
|
async function triggerSessionStatusRetry(
|
||||||
eventHandler: ReturnType<typeof createHarness>["eventHandler"],
|
eventHandler: ReturnType<typeof createHarness>["eventHandler"],
|
||||||
sessionID: string,
|
sessionID: string,
|
||||||
) {
|
) {
|
||||||
await eventHandler({
|
await eventHandler(asHarnessEventInput({
|
||||||
event: {
|
event: {
|
||||||
type: "session.status",
|
type: "session.status",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -279,14 +320,14 @@ async function triggerSessionStatusRetry(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function triggerAssistantMessageError(
|
async function triggerAssistantMessageError(
|
||||||
eventHandler: ReturnType<typeof createHarness>["eventHandler"],
|
eventHandler: ReturnType<typeof createHarness>["eventHandler"],
|
||||||
sessionID: string,
|
sessionID: string,
|
||||||
) {
|
) {
|
||||||
await eventHandler({
|
await eventHandler(asHarnessEventInput({
|
||||||
event: {
|
event: {
|
||||||
type: "message.updated",
|
type: "message.updated",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -307,7 +348,7 @@ async function triggerAssistantMessageError(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ import {
|
|||||||
} from "./tmux-utils"
|
} from "./tmux-utils"
|
||||||
import { isInsideTmuxEnvironment } from "./tmux-utils/environment"
|
import { isInsideTmuxEnvironment } from "./tmux-utils/environment"
|
||||||
|
|
||||||
|
function createFetchMock(responseFactory: () => Promise<Response>): typeof fetch & ReturnType<typeof mock> {
|
||||||
|
const fetchMock = mock(async (_input: RequestInfo | URL, _init?: RequestInit) => responseFactory())
|
||||||
|
const preconnect = globalThis.fetch.preconnect?.bind(globalThis.fetch)
|
||||||
|
return Object.assign(fetchMock, {
|
||||||
|
preconnect,
|
||||||
|
}) as typeof fetch & ReturnType<typeof mock>
|
||||||
|
}
|
||||||
|
|
||||||
describe("isInsideTmux", () => {
|
describe("isInsideTmux", () => {
|
||||||
test("returns true when TMUX env is set", () => {
|
test("returns true when TMUX env is set", () => {
|
||||||
// given
|
// given
|
||||||
@@ -66,7 +74,7 @@ describe("isServerRunning", () => {
|
|||||||
|
|
||||||
test("returns true when server responds OK", async () => {
|
test("returns true when server responds OK", async () => {
|
||||||
// given
|
// given
|
||||||
globalThis.fetch = mock(async () => ({ ok: true })) as any
|
globalThis.fetch = createFetchMock(async () => new Response(null, { status: 200 }))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await isServerRunning("http://localhost:4096")
|
const result = await isServerRunning("http://localhost:4096")
|
||||||
@@ -77,9 +85,9 @@ describe("isServerRunning", () => {
|
|||||||
|
|
||||||
test("returns false when server not reachable", async () => {
|
test("returns false when server not reachable", async () => {
|
||||||
// given
|
// given
|
||||||
globalThis.fetch = mock(async () => {
|
globalThis.fetch = createFetchMock(async () => {
|
||||||
throw new Error("ECONNREFUSED")
|
throw new Error("ECONNREFUSED")
|
||||||
}) as any
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await isServerRunning("http://localhost:4096")
|
const result = await isServerRunning("http://localhost:4096")
|
||||||
@@ -90,7 +98,7 @@ describe("isServerRunning", () => {
|
|||||||
|
|
||||||
test("returns false when fetch returns not ok", async () => {
|
test("returns false when fetch returns not ok", async () => {
|
||||||
// given
|
// given
|
||||||
globalThis.fetch = mock(async () => ({ ok: false })) as any
|
globalThis.fetch = createFetchMock(async () => new Response(null, { status: 500 }))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await isServerRunning("http://localhost:4096")
|
const result = await isServerRunning("http://localhost:4096")
|
||||||
@@ -101,7 +109,7 @@ describe("isServerRunning", () => {
|
|||||||
|
|
||||||
test("caches successful result", async () => {
|
test("caches successful result", async () => {
|
||||||
// given
|
// given
|
||||||
const fetchMock = mock(async () => ({ ok: true })) as any
|
const fetchMock = createFetchMock(async () => new Response(null, { status: 200 }))
|
||||||
globalThis.fetch = fetchMock
|
globalThis.fetch = fetchMock
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -114,9 +122,9 @@ describe("isServerRunning", () => {
|
|||||||
|
|
||||||
test("does not cache failed result", async () => {
|
test("does not cache failed result", async () => {
|
||||||
// given
|
// given
|
||||||
const fetchMock = mock(async () => {
|
const fetchMock = createFetchMock(async () => {
|
||||||
throw new Error("ECONNREFUSED")
|
throw new Error("ECONNREFUSED")
|
||||||
}) as any
|
})
|
||||||
globalThis.fetch = fetchMock
|
globalThis.fetch = fetchMock
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -129,7 +137,7 @@ describe("isServerRunning", () => {
|
|||||||
|
|
||||||
test("uses different cache for different URLs", async () => {
|
test("uses different cache for different URLs", async () => {
|
||||||
// given
|
// given
|
||||||
const fetchMock = mock(async () => ({ ok: true })) as any
|
const fetchMock = createFetchMock(async () => new Response(null, { status: 200 }))
|
||||||
globalThis.fetch = fetchMock
|
globalThis.fetch = fetchMock
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -150,7 +158,7 @@ describe("resetServerCheck", () => {
|
|||||||
test("allows re-checking after reset", async () => {
|
test("allows re-checking after reset", async () => {
|
||||||
// given
|
// given
|
||||||
const originalFetch = globalThis.fetch
|
const originalFetch = globalThis.fetch
|
||||||
const fetchMock = mock(async () => ({ ok: true })) as any
|
const fetchMock = createFetchMock(async () => new Response(null, { status: 200 }))
|
||||||
globalThis.fetch = fetchMock
|
globalThis.fetch = fetchMock
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -182,7 +190,7 @@ describe("markServerRunningInProcess", () => {
|
|||||||
|
|
||||||
test("skips HTTP fetch when marked as running in-process", async () => {
|
test("skips HTTP fetch when marked as running in-process", async () => {
|
||||||
// given
|
// given
|
||||||
const fetchMock = mock(async () => ({ ok: true })) as any
|
const fetchMock = createFetchMock(async () => new Response(null, { status: 200 }))
|
||||||
globalThis.fetch = fetchMock
|
globalThis.fetch = fetchMock
|
||||||
markServerRunningInProcess()
|
markServerRunningInProcess()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user