test(auto-compact): localize fake timers per async case
Stop patching global timers in every lock-management test. Use scoped fake timers only in continuation tests so lock/notification assertions remain deterministic in CI.
This commit is contained in:
parent
4aec627b33
commit
d618678844
@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test"
|
||||
import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test"
|
||||
import { executeCompact } from "./executor"
|
||||
import type { AutoCompactState } from "./types"
|
||||
import * as storage from "./storage"
|
||||
@ -78,7 +78,6 @@ function createFakeTimeouts(): FakeTimeouts {
|
||||
describe("executeCompact lock management", () => {
|
||||
let autoCompactState: AutoCompactState
|
||||
let mockClient: any
|
||||
let fakeTimeouts: FakeTimeouts
|
||||
const sessionID = "test-session-123"
|
||||
const directory = "/test/dir"
|
||||
const msg = { providerID: "anthropic", modelID: "claude-opus-4-6" }
|
||||
@ -106,14 +105,9 @@ describe("executeCompact lock management", () => {
|
||||
},
|
||||
}
|
||||
|
||||
fakeTimeouts = createFakeTimeouts()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
fakeTimeouts.restore()
|
||||
})
|
||||
|
||||
test.serial("clears lock on successful summarize completion", async () => {
|
||||
test("clears lock on successful summarize completion", async () => {
|
||||
// given: Valid session with providerID/modelID
|
||||
autoCompactState.errorDataBySession.set(sessionID, {
|
||||
errorType: "token_limit",
|
||||
@ -128,7 +122,7 @@ describe("executeCompact lock management", () => {
|
||||
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
|
||||
})
|
||||
|
||||
test.serial("clears lock when summarize throws exception", async () => {
|
||||
test("clears lock when summarize throws exception", async () => {
|
||||
// given: Summarize will fail
|
||||
mockClient.session.summarize = mock(() =>
|
||||
Promise.reject(new Error("Network timeout")),
|
||||
@ -146,7 +140,7 @@ describe("executeCompact lock management", () => {
|
||||
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
|
||||
})
|
||||
|
||||
test.serial("shows toast when lock already held", async () => {
|
||||
test("shows toast when lock already held", async () => {
|
||||
// given: Lock already held
|
||||
autoCompactState.compactionInProgress.add(sessionID)
|
||||
|
||||
@ -168,7 +162,7 @@ describe("executeCompact lock management", () => {
|
||||
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(true)
|
||||
})
|
||||
|
||||
test.serial("clears lock when fixEmptyMessages path executes", async () => {
|
||||
test("clears lock when fixEmptyMessages path executes", async () => {
|
||||
//#given - Empty content error scenario with no messages in storage
|
||||
const readMessagesSpy = spyOn(messagesReader, "readMessages").mockReturnValue([])
|
||||
autoCompactState.errorDataBySession.set(sessionID, {
|
||||
@ -186,7 +180,7 @@ describe("executeCompact lock management", () => {
|
||||
readMessagesSpy.mockRestore()
|
||||
})
|
||||
|
||||
test.serial("clears lock when truncation is sufficient", async () => {
|
||||
test("clears lock when truncation is sufficient", async () => {
|
||||
//#given - Aggressive truncation scenario with no messages in storage
|
||||
const readMessagesSpy = spyOn(messagesReader, "readMessages").mockReturnValue([])
|
||||
autoCompactState.errorDataBySession.set(sessionID, {
|
||||
@ -215,7 +209,7 @@ describe("executeCompact lock management", () => {
|
||||
readMessagesSpy.mockRestore()
|
||||
})
|
||||
|
||||
test.serial("prevents concurrent compaction attempts", async () => {
|
||||
test("prevents concurrent compaction attempts", async () => {
|
||||
// given: Lock already held (simpler test)
|
||||
autoCompactState.compactionInProgress.add(sessionID)
|
||||
|
||||
@ -233,7 +227,7 @@ describe("executeCompact lock management", () => {
|
||||
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(true)
|
||||
})
|
||||
|
||||
test.serial("clears lock after max recovery attempts exhausted", async () => {
|
||||
test("clears lock after max recovery attempts exhausted", async () => {
|
||||
// given: All retry/revert attempts exhausted
|
||||
mockClient.session.messages = mock(() => Promise.resolve({ data: [] }))
|
||||
|
||||
@ -265,7 +259,7 @@ describe("executeCompact lock management", () => {
|
||||
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
|
||||
})
|
||||
|
||||
test.serial("clears lock when client.tui.showToast throws", async () => {
|
||||
test("clears lock when client.tui.showToast throws", async () => {
|
||||
// given: Toast will fail (this should never happen but testing robustness)
|
||||
mockClient.tui.showToast = mock(() =>
|
||||
Promise.reject(new Error("Toast failed")),
|
||||
@ -283,7 +277,7 @@ describe("executeCompact lock management", () => {
|
||||
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
|
||||
})
|
||||
|
||||
test.serial("clears lock when promptAsync in continuation throws", async () => {
|
||||
test("clears lock when promptAsync in continuation throws", async () => {
|
||||
// given: promptAsync will fail during continuation
|
||||
mockClient.session.promptAsync = mock(() =>
|
||||
Promise.reject(new Error("Prompt failed")),
|
||||
@ -294,18 +288,23 @@ describe("executeCompact lock management", () => {
|
||||
maxTokens: 200000,
|
||||
})
|
||||
|
||||
// when: Execute compaction
|
||||
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
|
||||
const fakeTimeouts = createFakeTimeouts()
|
||||
try {
|
||||
// when: Execute compaction
|
||||
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
|
||||
|
||||
// Wait for setTimeout callback
|
||||
await fakeTimeouts.advanceBy(600)
|
||||
// Wait for setTimeout callback
|
||||
await fakeTimeouts.advanceBy(600)
|
||||
} finally {
|
||||
fakeTimeouts.restore()
|
||||
}
|
||||
|
||||
// then: Lock should be cleared
|
||||
// The continuation happens in setTimeout, but lock is cleared in finally before that
|
||||
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
|
||||
})
|
||||
|
||||
test.serial("falls through to summarize when truncation is insufficient", async () => {
|
||||
test("falls through to summarize when truncation is insufficient", async () => {
|
||||
// given: Over token limit with truncation returning insufficient
|
||||
autoCompactState.errorDataBySession.set(sessionID, {
|
||||
errorType: "token_limit",
|
||||
@ -346,7 +345,7 @@ describe("executeCompact lock management", () => {
|
||||
truncateSpy.mockRestore()
|
||||
})
|
||||
|
||||
test.serial("does NOT call summarize when truncation is sufficient", async () => {
|
||||
test("does NOT call summarize when truncation is sufficient", async () => {
|
||||
// given: Over token limit with truncation returning sufficient
|
||||
autoCompactState.errorDataBySession.set(sessionID, {
|
||||
errorType: "token_limit",
|
||||
@ -366,11 +365,16 @@ describe("executeCompact lock management", () => {
|
||||
],
|
||||
})
|
||||
|
||||
// when: Execute compaction
|
||||
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
|
||||
const fakeTimeouts = createFakeTimeouts()
|
||||
try {
|
||||
// when: Execute compaction
|
||||
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
|
||||
|
||||
// Wait for setTimeout callback
|
||||
await fakeTimeouts.advanceBy(600)
|
||||
// Wait for setTimeout callback
|
||||
await fakeTimeouts.advanceBy(600)
|
||||
} finally {
|
||||
fakeTimeouts.restore()
|
||||
}
|
||||
|
||||
// then: Truncation was attempted
|
||||
expect(truncateSpy).toHaveBeenCalled()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user