Skip to content

Commit 7f458fc

Browse files
committed
test(task): add queued message processing tests after condense; ensure cross-task isolation
1 parent 7c35be4 commit 7f458fc

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed

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

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ vi.mock("../../environment/getEnvironmentDetails", () => ({
148148

149149
vi.mock("../../ignore/RooIgnoreController")
150150

151+
vi.mock("../../condense", async (importOriginal) => {
152+
const actual = (await importOriginal()) as any
153+
return {
154+
...actual,
155+
summarizeConversation: vi.fn().mockResolvedValue({
156+
messages: [{ role: "user", content: [{ type: "text", text: "continued" }], ts: Date.now() }],
157+
summary: "summary",
158+
cost: 0,
159+
newContextTokens: 1,
160+
}),
161+
}
162+
})
151163
// Mock storagePathManager to prevent dynamic import issues.
152164
vi.mock("../../../utils/storage", () => ({
153165
getTaskDirectoryPath: vi
@@ -1777,3 +1789,124 @@ describe("Cline", () => {
17771789
})
17781790
})
17791791
})
1792+
1793+
describe("Queued message processing after condense", () => {
1794+
function createProvider(): any {
1795+
const storageUri = { fsPath: path.join(os.tmpdir(), "test-storage") }
1796+
const ctx = {
1797+
globalState: {
1798+
get: vi.fn().mockImplementation((_key: keyof GlobalState) => undefined),
1799+
update: vi.fn().mockResolvedValue(undefined),
1800+
keys: vi.fn().mockReturnValue([]),
1801+
},
1802+
globalStorageUri: storageUri,
1803+
workspaceState: {
1804+
get: vi.fn().mockImplementation((_key) => undefined),
1805+
update: vi.fn().mockResolvedValue(undefined),
1806+
keys: vi.fn().mockReturnValue([]),
1807+
},
1808+
secrets: {
1809+
get: vi.fn().mockResolvedValue(undefined),
1810+
store: vi.fn().mockResolvedValue(undefined),
1811+
delete: vi.fn().mockResolvedValue(undefined),
1812+
},
1813+
extensionUri: { fsPath: "/mock/extension/path" },
1814+
extension: { packageJSON: { version: "1.0.0" } },
1815+
} as unknown as vscode.ExtensionContext
1816+
1817+
const output = {
1818+
appendLine: vi.fn(),
1819+
append: vi.fn(),
1820+
clear: vi.fn(),
1821+
show: vi.fn(),
1822+
hide: vi.fn(),
1823+
dispose: vi.fn(),
1824+
}
1825+
1826+
const provider = new ClineProvider(ctx, output as any, "sidebar", new ContextProxy(ctx)) as any
1827+
provider.postMessageToWebview = vi.fn().mockResolvedValue(undefined)
1828+
provider.postStateToWebview = vi.fn().mockResolvedValue(undefined)
1829+
provider.getState = vi.fn().mockResolvedValue({})
1830+
return provider
1831+
}
1832+
1833+
const apiConfig: ProviderSettings = {
1834+
apiProvider: "anthropic",
1835+
apiModelId: "claude-3-5-sonnet-20241022",
1836+
apiKey: "test-api-key",
1837+
} as any
1838+
1839+
it("processes queued message after condense completes", async () => {
1840+
const provider = createProvider()
1841+
const task = new Task({
1842+
provider,
1843+
apiConfiguration: apiConfig,
1844+
task: "initial task",
1845+
startTask: false,
1846+
})
1847+
1848+
// Make condense fast + deterministic
1849+
vi.spyOn(task as any, "getSystemPrompt").mockResolvedValue("system")
1850+
const submitSpy = vi.spyOn(task, "submitUserMessage").mockResolvedValue(undefined)
1851+
1852+
// Queue a message during condensing
1853+
task.messageQueueService.addMessage("queued text", ["img1.png"])
1854+
1855+
// Use fake timers to capture setTimeout(0) in processQueuedMessages
1856+
vi.useFakeTimers()
1857+
await task.condenseContext()
1858+
1859+
// Flush the microtask that submits the queued message
1860+
vi.runAllTimers()
1861+
vi.useRealTimers()
1862+
1863+
expect(submitSpy).toHaveBeenCalledWith("queued text", ["img1.png"])
1864+
expect(task.messageQueueService.isEmpty()).toBe(true)
1865+
})
1866+
1867+
it("does not cross-drain queues between separate tasks", async () => {
1868+
const providerA = createProvider()
1869+
const providerB = createProvider()
1870+
1871+
const taskA = new Task({
1872+
provider: providerA,
1873+
apiConfiguration: apiConfig,
1874+
task: "task A",
1875+
startTask: false,
1876+
})
1877+
const taskB = new Task({
1878+
provider: providerB,
1879+
apiConfiguration: apiConfig,
1880+
task: "task B",
1881+
startTask: false,
1882+
})
1883+
1884+
vi.spyOn(taskA as any, "getSystemPrompt").mockResolvedValue("system")
1885+
vi.spyOn(taskB as any, "getSystemPrompt").mockResolvedValue("system")
1886+
1887+
const spyA = vi.spyOn(taskA, "submitUserMessage").mockResolvedValue(undefined)
1888+
const spyB = vi.spyOn(taskB, "submitUserMessage").mockResolvedValue(undefined)
1889+
1890+
taskA.messageQueueService.addMessage("A message")
1891+
taskB.messageQueueService.addMessage("B message")
1892+
1893+
// Condense in task A should only drain A's queue
1894+
vi.useFakeTimers()
1895+
await taskA.condenseContext()
1896+
vi.runAllTimers()
1897+
vi.useRealTimers()
1898+
1899+
expect(spyA).toHaveBeenCalledWith("A message", undefined)
1900+
expect(spyB).not.toHaveBeenCalled()
1901+
expect(taskB.messageQueueService.isEmpty()).toBe(false)
1902+
1903+
// Now condense in task B should drain B's queue
1904+
vi.useFakeTimers()
1905+
await taskB.condenseContext()
1906+
vi.runAllTimers()
1907+
vi.useRealTimers()
1908+
1909+
expect(spyB).toHaveBeenCalledWith("B message", undefined)
1910+
expect(taskB.messageQueueService.isEmpty()).toBe(true)
1911+
})
1912+
})

0 commit comments

Comments
 (0)