fix(tests): stabilize toast manager and continuation tests

This commit is contained in:
YeonGyu-Kim 2026-02-10 17:19:06 +09:00
parent 2bf11a8ed7
commit 7fe1a653c8
5 changed files with 100 additions and 47 deletions

View File

@ -1,17 +1,20 @@
import { describe, test, expect, beforeEach, mock } from "bun:test" declare const require: (name: string) => any
import { TaskToastManager } from "./manager" const { describe, test, expect, beforeEach, afterEach, mock } = require("bun:test")
import type { ConcurrencyManager } from "../background-agent/concurrency" import type { ConcurrencyManager } from "../background-agent/concurrency"
type TaskToastManagerClass = typeof import("./manager").TaskToastManager
describe("TaskToastManager", () => { describe("TaskToastManager", () => {
let TaskToastManager: TaskToastManagerClass
let mockClient: { let mockClient: {
tui: { tui: {
showToast: ReturnType<typeof mock> showToast: ReturnType<typeof mock>
} }
} }
let toastManager: TaskToastManager let toastManager: InstanceType<TaskToastManagerClass>
let mockConcurrencyManager: ConcurrencyManager let mockConcurrencyManager: ConcurrencyManager
beforeEach(() => { beforeEach(async () => {
mockClient = { mockClient = {
tui: { tui: {
showToast: mock(() => Promise.resolve()), showToast: mock(() => Promise.resolve()),
@ -20,10 +23,18 @@ describe("TaskToastManager", () => {
mockConcurrencyManager = { mockConcurrencyManager = {
getConcurrencyLimit: mock(() => 5), getConcurrencyLimit: mock(() => 5),
} as unknown as ConcurrencyManager } as unknown as ConcurrencyManager
const mod = await import("./manager")
TaskToastManager = mod.TaskToastManager
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
toastManager = new TaskToastManager(mockClient as any, mockConcurrencyManager) toastManager = new TaskToastManager(mockClient as any, mockConcurrencyManager)
}) })
afterEach(() => {
mock.restore()
})
describe("skills in toast message", () => { describe("skills in toast message", () => {
test("should display skills when provided", () => { test("should display skills when provided", () => {
// given - a task with skills // given - a task with skills

View File

@ -217,3 +217,7 @@ export function initTaskToastManager(
instance = new TaskToastManager(client, concurrencyManager) instance = new TaskToastManager(client, concurrencyManager)
return instance return instance
} }
export function _resetTaskToastManagerForTesting(): void {
instance = null
}

View File

@ -1,8 +1,24 @@
import { describe, test, expect } from "bun:test" declare const require: (name: string) => any
const { describe, test, expect, beforeEach, afterEach, spyOn, mock } = require("bun:test")
import { resolveCategoryExecution } from "./category-resolver" import { resolveCategoryExecution } from "./category-resolver"
import type { ExecutorContext } from "./executor-types" import type { ExecutorContext } from "./executor-types"
import * as connectedProvidersCache from "../../shared/connected-providers-cache"
describe("resolveCategoryExecution", () => { describe("resolveCategoryExecution", () => {
let connectedProvidersSpy: ReturnType<typeof spyOn> | undefined
let providerModelsSpy: ReturnType<typeof spyOn> | undefined
beforeEach(() => {
mock.restore()
connectedProvidersSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
})
afterEach(() => {
connectedProvidersSpy?.mockRestore()
providerModelsSpy?.mockRestore()
})
const createMockExecutorContext = (): ExecutorContext => ({ const createMockExecutorContext = (): ExecutorContext => ({
client: {} as any, client: {} as any,
manager: {} as any, manager: {} as any,

View File

@ -1,9 +1,9 @@
declare const require: (name: string) => any const { describe, test, expect, beforeEach, afterEach, mock, spyOn } = require("bun:test")
const { describe, test, expect, beforeEach, afterEach, mock } = require("bun:test")
describe("executeSyncContinuation - toast cleanup error paths", () => { describe("executeSyncContinuation - toast cleanup error paths", () => {
let removeTaskCalls: string[] = [] let removeTaskCalls: string[] = []
let addTaskCalls: any[] = [] let addTaskCalls: any[] = []
let resetToastManager: (() => void) | null = null
beforeEach(() => { beforeEach(() => {
//#given - configure fast timing for all tests //#given - configure fast timing for all tests
@ -19,19 +19,21 @@ describe("executeSyncContinuation - toast cleanup error paths", () => {
removeTaskCalls = [] removeTaskCalls = []
addTaskCalls = [] addTaskCalls = []
//#given - mock task-toast-manager module //#given - initialize real task toast manager (avoid global module mocks)
const mockToastManager = { const { initTaskToastManager, _resetTaskToastManagerForTesting } = require("../../features/task-toast-manager/manager")
addTask: (task: any) => { addTaskCalls.push(task) }, _resetTaskToastManagerForTesting()
removeTask: (id: string) => { removeTaskCalls.push(id) }, resetToastManager = _resetTaskToastManagerForTesting
}
const mockGetTaskToastManager = () => mockToastManager const toastManager = initTaskToastManager({
tui: { showToast: mock(() => Promise.resolve()) },
})
mock.module("../../features/task-toast-manager/index.ts", () => ({ spyOn(toastManager, "addTask").mockImplementation((task: any) => {
getTaskToastManager: mockGetTaskToastManager, addTaskCalls.push(task)
TaskToastManager: class {}, })
initTaskToastManager: () => mockToastManager, spyOn(toastManager, "removeTask").mockImplementation((id: string) => {
})) removeTaskCalls.push(id)
})
//#given - mock other dependencies //#given - mock other dependencies
mock.module("./sync-session-poller.ts", () => ({ mock.module("./sync-session-poller.ts", () => ({
@ -48,7 +50,9 @@ describe("executeSyncContinuation - toast cleanup error paths", () => {
const { __resetTimingConfig } = require("./timing") const { __resetTimingConfig } = require("./timing")
__resetTimingConfig() __resetTimingConfig()
mock.restore() mock.restore()
resetToastManager?.()
resetToastManager = null
}) })
test("removes toast when fetchSyncResult throws", async () => { test("removes toast when fetchSyncResult throws", async () => {
@ -294,14 +298,9 @@ describe("executeSyncContinuation - toast cleanup error paths", () => {
}) })
test("no crash when toastManager is null", async () => { test("no crash when toastManager is null", async () => {
//#given - mock task-toast-manager module to return null //#given - reset toast manager instance to null
const mockGetTaskToastManager = () => null const { _resetTaskToastManagerForTesting } = require("../../features/task-toast-manager/manager")
_resetTaskToastManagerForTesting()
mock.module("../../features/task-toast-manager/index.ts", () => ({
getTaskToastManager: mockGetTaskToastManager,
TaskToastManager: class {},
initTaskToastManager: () => null,
}))
const mockClient = { const mockClient = {
session: { session: {

View File

@ -1132,27 +1132,50 @@ describe("sisyphus-task", () => {
launch: async () => mockTask, launch: async () => mockTask,
} }
let messagesCallCount = 0
const mockClient = { const mockClient = {
session: { session: {
prompt: async () => ({ data: {} }), prompt: async () => ({ data: {} }),
promptAsync: async () => ({ data: {} }), promptAsync: async () => ({ data: {} }),
messages: async () => ({ messages: async () => {
data: [ messagesCallCount++
{ const now = Date.now()
info: { id: "msg_001", role: "user", time: { created: Date.now() } },
parts: [{ type: "text", text: "Continue the task" }], const beforeContinuation = [
}, {
{ info: { id: "msg_001", role: "user", time: { created: now } },
info: { id: "msg_002", role: "assistant", time: { created: Date.now() + 1 }, finish: "end_turn" }, parts: [{ type: "text", text: "Previous context" }],
parts: [{ type: "text", text: "This is the continued task result" }], },
}, {
], info: { id: "msg_002", role: "assistant", time: { created: now + 1 }, finish: "end_turn" },
}), parts: [{ type: "text", text: "Previous result" }],
status: async () => ({ data: { "ses_continue_test": { type: "idle" } } }), },
}, ]
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
app: { if (messagesCallCount === 1) {
agents: async () => ({ data: [] }), return { data: beforeContinuation }
}
return {
data: [
...beforeContinuation,
{
info: { id: "msg_003", role: "user", time: { created: now + 2 } },
parts: [{ type: "text", text: "Continue the task" }],
},
{
info: { id: "msg_004", role: "assistant", time: { created: now + 3 }, finish: "end_turn" },
parts: [{ type: "text", text: "This is the continued task result" }],
},
],
}
},
status: async () => ({ data: { "ses_continue_test": { type: "idle" } } }),
},
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
app: {
agents: async () => ({ data: [] }),
}, },
} }