diff --git a/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts b/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts index cc575e18..51c0f3a2 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts @@ -113,22 +113,22 @@ describe("executeCompact lock management", () => { fakeTimeouts.restore() }) - test("clears lock on successful summarize completion", async () => { + test.serial("clears lock on successful summarize completion", async () => { // given: Valid session with providerID/modelID autoCompactState.errorDataBySession.set(sessionID, { errorType: "token_limit", currentTokens: 100000, maxTokens: 200000, }) - + // when: Execute compaction successfully await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + // then: Lock should be cleared expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) }) - test("clears lock when summarize throws exception", async () => { + test.serial("clears lock when summarize throws exception", async () => { // given: Summarize will fail mockClient.session.summarize = mock(() => Promise.reject(new Error("Network timeout")), @@ -138,21 +138,21 @@ describe("executeCompact lock management", () => { currentTokens: 100000, maxTokens: 200000, }) - + // when: Execute compaction await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + // then: Lock should still be cleared despite exception expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) }) - test("shows toast when lock already held", async () => { + test.serial("shows toast when lock already held", async () => { // given: Lock already held autoCompactState.compactionInProgress.add(sessionID) - + // when: Try to execute compaction await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + // then: Toast should be shown with warning message expect(mockClient.tui.showToast).toHaveBeenCalledWith( expect.objectContaining({ @@ -163,12 +163,12 @@ describe("executeCompact lock management", () => { }), }), ) - + // then: compactionInProgress should still have the lock expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(true) }) - test("clears lock when fixEmptyMessages path executes", async () => { + test.serial("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, { @@ -177,16 +177,16 @@ describe("executeCompact lock management", () => { currentTokens: 100000, maxTokens: 200000, }) - + //#when - Execute compaction (fixEmptyMessages will be called) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + //#then - Lock should be cleared expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) readMessagesSpy.mockRestore() }) - test("clears lock when truncation is sufficient", async () => { + test.serial("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, { @@ -194,12 +194,12 @@ describe("executeCompact lock management", () => { currentTokens: 250000, maxTokens: 200000, }) - + const experimental = { truncate_all_tool_outputs: false, aggressive_truncation: true, } - + //#when - Execute compaction with experimental flag await executeCompact( sessionID, @@ -209,34 +209,34 @@ describe("executeCompact lock management", () => { directory, experimental, ) - + //#then - Lock should be cleared even on early return expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) readMessagesSpy.mockRestore() }) - test("prevents concurrent compaction attempts", async () => { + test.serial("prevents concurrent compaction attempts", async () => { // given: Lock already held (simpler test) autoCompactState.compactionInProgress.add(sessionID) - + // when: Try to execute compaction while lock is held await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + // then: Toast should be shown const toastCalls = (mockClient.tui.showToast as any).mock.calls const blockedToast = toastCalls.find( (call: any) => call[0]?.body?.title === "Compact In Progress", ) expect(blockedToast).toBeDefined() - + // then: Lock should still be held (not cleared by blocked attempt) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(true) }) - test("clears lock after max recovery attempts exhausted", async () => { + test.serial("clears lock after max recovery attempts exhausted", async () => { // given: All retry/revert attempts exhausted mockClient.session.messages = mock(() => Promise.resolve({ data: [] })) - + // Max out all attempts autoCompactState.retryStateBySession.set(sessionID, { attempt: 5, @@ -250,22 +250,22 @@ describe("executeCompact lock management", () => { currentTokens: 100000, maxTokens: 200000, }) - + // when: Execute compaction await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + // then: Should show failure toast const toastCalls = (mockClient.tui.showToast as any).mock.calls const failureToast = toastCalls.find( (call: any) => call[0]?.body?.title === "Auto Compact Failed", ) expect(failureToast).toBeDefined() - + // then: Lock should still be cleared expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) }) - test("clears lock when client.tui.showToast throws", async () => { + test.serial("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")), @@ -275,15 +275,15 @@ describe("executeCompact lock management", () => { currentTokens: 100000, maxTokens: 200000, }) - + // when: Execute compaction await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + // then: Lock should be cleared even if toast fails expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) }) - test("clears lock when promptAsync in continuation throws", async () => { + test.serial("clears lock when promptAsync in continuation throws", async () => { // given: promptAsync will fail during continuation mockClient.session.promptAsync = mock(() => Promise.reject(new Error("Prompt failed")), @@ -293,26 +293,26 @@ describe("executeCompact lock management", () => { currentTokens: 100000, maxTokens: 200000, }) - + // when: Execute compaction await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + // Wait for setTimeout callback await fakeTimeouts.advanceBy(600) - + // 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("falls through to summarize when truncation is insufficient", async () => { + test.serial("falls through to summarize when truncation is insufficient", async () => { // given: Over token limit with truncation returning insufficient autoCompactState.errorDataBySession.set(sessionID, { errorType: "token_limit", currentTokens: 250000, maxTokens: 200000, }) - + const truncateSpy = spyOn(storage, "truncateUntilTargetTokens").mockResolvedValue({ success: true, sufficient: false, @@ -325,13 +325,13 @@ describe("executeCompact lock management", () => { { toolName: "Bash", originalSize: 2000 }, ], }) - + // when: Execute compaction await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + // then: Truncation was attempted expect(truncateSpy).toHaveBeenCalled() - + // then: Summarize should be called (fall through from insufficient truncation) expect(mockClient.session.summarize).toHaveBeenCalledWith( expect.objectContaining({ @@ -339,21 +339,21 @@ describe("executeCompact lock management", () => { body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true }, }), ) - + // then: Lock should be cleared expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) - + truncateSpy.mockRestore() }) - test("does NOT call summarize when truncation is sufficient", async () => { + test.serial("does NOT call summarize when truncation is sufficient", async () => { // given: Over token limit with truncation returning sufficient autoCompactState.errorDataBySession.set(sessionID, { errorType: "token_limit", currentTokens: 250000, maxTokens: 200000, }) - + const truncateSpy = spyOn(storage, "truncateUntilTargetTokens").mockResolvedValue({ success: true, sufficient: true, @@ -365,25 +365,25 @@ describe("executeCompact lock management", () => { { toolName: "Read", originalSize: 30000 }, ], }) - + // when: Execute compaction await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) - + // Wait for setTimeout callback await fakeTimeouts.advanceBy(600) - + // then: Truncation was attempted expect(truncateSpy).toHaveBeenCalled() - + // then: Summarize should NOT be called (early return from sufficient truncation) expect(mockClient.session.summarize).not.toHaveBeenCalled() - + // then: promptAsync should be called (Continue after successful truncation) expect(mockClient.session.promptAsync).toHaveBeenCalled() - + // then: Lock should be cleared expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) - + truncateSpy.mockRestore() }) })