Skip to content

Commit df81cf1

Browse files
committed
test(task): strengthen abortSignal identity assertions and add sequential-request test
1 parent 8b83fda commit df81cf1

1 file changed

Lines changed: 93 additions & 4 deletions

File tree

src/core/task/__tests__/Task.spec.ts

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,7 +1812,7 @@ describe("Cline", () => {
18121812

18131813
expect(summarizeConversation).toHaveBeenCalled()
18141814
const [options] = vi.mocked(summarizeConversation).mock.calls.at(-1)!
1815-
expect(options.metadata?.abortSignal).toBeInstanceOf(AbortSignal)
1815+
expect(options.metadata?.abortSignal).toBe(task.currentRequestAbortController!.signal)
18161816
})
18171817

18181818
it("should omit abortSignal from condenseContext metadata when no current request exists", async () => {
@@ -1899,7 +1899,7 @@ describe("Cline", () => {
18991899
const [, , metadata] = createMessageSpy.mock.calls[0]!
19001900

19011901
expect(metadata).toBeDefined()
1902-
expect(metadata!.abortSignal).toBeInstanceOf(AbortSignal)
1902+
expect(metadata!.abortSignal).toBe(task.currentRequestAbortController!.signal)
19031903
})
19041904

19051905
it("should invoke abort on currentRequestAbortController during first-chunk wait", async () => {
@@ -2136,7 +2136,7 @@ describe("Cline", () => {
21362136
const [, , metadata] = createMessageSpy.mock.calls[0]!
21372137
expect(metadata).toBeDefined()
21382138
expect("abortSignal" in metadata!).toBe(true)
2139-
expect(metadata!.abortSignal).toBeInstanceOf(AbortSignal)
2139+
expect(metadata!.abortSignal).toBe(task.currentRequestAbortController!.signal)
21402140
})
21412141

21422142
it("should keep createMessage abortSignal metadata unaborted before cancellation", async () => {
@@ -2197,11 +2197,100 @@ describe("Cline", () => {
21972197
await iterator.next()
21982198

21992199
const [, , metadata] = createMessageSpy.mock.calls[0]!
2200-
expect(metadata?.abortSignal).toBeInstanceOf(AbortSignal)
2200+
expect(metadata?.abortSignal).toBe(task.currentRequestAbortController!.signal)
22012201
expect(metadata?.abortSignal?.aborted).toBe(false)
22022202
})
22032203
})
22042204

2205+
it("should create a fresh AbortController for each sequential request", async () => {
2206+
const task = new Task({
2207+
provider: mockProvider,
2208+
apiConfiguration: mockApiConfig,
2209+
task: "test task",
2210+
startTask: false,
2211+
})
2212+
2213+
vi.spyOn(task as any, "getSystemPrompt").mockResolvedValue("mock system prompt")
2214+
vi.spyOn(task.api, "getModel").mockReturnValue({
2215+
id: mockApiConfig.apiModelId!,
2216+
info: {
2217+
supportsImages: false,
2218+
supportsPromptCache: true,
2219+
contextWindow: 200000,
2220+
maxTokens: 4096,
2221+
inputPrice: 0.3,
2222+
outputPrice: 1.5,
2223+
} as ModelInfo,
2224+
})
2225+
2226+
const providerState = await mockProvider.getState()
2227+
vi.spyOn(mockProvider, "getState").mockResolvedValue({
2228+
...providerState,
2229+
apiConfiguration: mockApiConfig,
2230+
autoApprovalEnabled: true,
2231+
requestDelaySeconds: 0,
2232+
})
2233+
2234+
let callCount = 0
2235+
const mockStreamFactory = () => {
2236+
return {
2237+
async *[Symbol.asyncIterator]() {
2238+
yield { type: "text", text: `response ${callCount}` }
2239+
},
2240+
async next() {
2241+
callCount++
2242+
return { done: true, value: { type: "text", text: `response ${callCount - 1}` } }
2243+
},
2244+
async return() {
2245+
return { done: true, value: undefined }
2246+
},
2247+
async throw(e: any) {
2248+
throw e
2249+
},
2250+
[Symbol.asyncDispose]: async () => {},
2251+
} as AsyncGenerator<ApiStreamChunk>
2252+
}
2253+
2254+
const createMessageSpy = vi
2255+
.spyOn(task.api, "createMessage")
2256+
.mockImplementation(() => mockStreamFactory())
2257+
2258+
task.apiConversationHistory = [
2259+
{
2260+
role: "user" as const,
2261+
content: [{ type: "text" as const, text: "test message" }],
2262+
ts: Date.now(),
2263+
},
2264+
] as any
2265+
2266+
// First request
2267+
const iterator1 = task.attemptApiRequest(0)
2268+
await iterator1.next()
2269+
2270+
expect(createMessageSpy).toHaveBeenCalledTimes(1)
2271+
const [, , metadata1] = createMessageSpy.mock.calls[0]!
2272+
const signal1 = metadata1!.abortSignal
2273+
expect(signal1).toBeDefined()
2274+
expect(signal1!.aborted).toBe(false)
2275+
2276+
// Simulate request completion and cancellation to clear the controller
2277+
task.cancelCurrentRequest()
2278+
2279+
// Second request should create a fresh AbortController with a new signal
2280+
callCount = 0
2281+
const iterator2 = task.attemptApiRequest(0)
2282+
await iterator2.next()
2283+
2284+
expect(createMessageSpy).toHaveBeenCalledTimes(2)
2285+
const [, , metadata2] = createMessageSpy.mock.calls[1]!
2286+
const signal2 = metadata2!.abortSignal
2287+
2288+
// Signals should be different instances (fresh controller per request)
2289+
expect(signal2).not.toBe(signal1)
2290+
expect(signal2).toBe(task.currentRequestAbortController!.signal)
2291+
expect(signal2!.aborted).toBe(false)
2292+
})
2293+
22052294
it("should propagate AbortController signal through attemptApiRequest context-window retry path", async () => {
22062295
const task = new Task({
22072296
provider: mockProvider,

0 commit comments

Comments
 (0)