From 279cd1a7e1ef36085debc7114ec15b94e31c1bb0 Mon Sep 17 00:00:00 2001 From: Juan Camilo Date: Tue, 7 Apr 2026 12:01:17 +0200 Subject: [PATCH] test: move provider-sensitive resume coverage behind module mocks --- src/utils/conversationRecovery.hooks.test.ts | 90 ++++++++++++++++ src/utils/conversationRecovery.test.ts | 105 ------------------- 2 files changed, 90 insertions(+), 105 deletions(-) diff --git a/src/utils/conversationRecovery.hooks.test.ts b/src/utils/conversationRecovery.hooks.test.ts index 4f466aae..b19ae255 100644 --- a/src/utils/conversationRecovery.hooks.test.ts +++ b/src/utils/conversationRecovery.hooks.test.ts @@ -69,3 +69,93 @@ test('loadConversationForResume rejects oversized transcripts before resume hook ) expect(hookSpy).not.toHaveBeenCalled() }) + +test('deserializeMessagesWithInterruptDetection strips thinking blocks only for OpenAI-compatible providers', async () => { + const serializedMessages = [ + user(id(10), 'hello'), + { + type: 'assistant', + uuid: id(11), + parentUuid: id(10), + timestamp: ts, + cwd: '/tmp', + sessionId, + version: 'test', + message: { + role: 'assistant', + content: [ + { type: 'thinking', thinking: 'secret reasoning' }, + { type: 'text', text: 'visible reply' }, + ], + }, + }, + { + type: 'assistant', + uuid: id(12), + parentUuid: id(11), + timestamp: ts, + cwd: '/tmp', + sessionId, + version: 'test', + message: { + role: 'assistant', + content: [{ type: 'thinking', thinking: 'only hidden reasoning' }], + }, + }, + user(id(13), 'follow up'), + ] + + mock.module('./model/providers.js', () => ({ + getAPIProvider: () => 'openai', + isOpenAICompatibleProvider: (provider: string) => + provider === 'openai' || + provider === 'gemini' || + provider === 'github' || + provider === 'codex', + })) + + const openaiModule = await import(`./conversationRecovery.ts?provider=openai-${Date.now()}`) + const thirdParty = openaiModule.deserializeMessagesWithInterruptDetection(serializedMessages as never[]) + const thirdPartyAssistantMessages = thirdParty.messages.filter( + message => message.type === 'assistant', + ) + + expect(thirdPartyAssistantMessages).toHaveLength(2) + expect(thirdPartyAssistantMessages[0]?.message?.content).toEqual([ + { type: 'text', text: 'visible reply' }, + ]) + expect( + JSON.stringify(thirdPartyAssistantMessages.map(message => message.message?.content)), + ).not.toContain('secret reasoning') + expect( + JSON.stringify(thirdPartyAssistantMessages.map(message => message.message?.content)), + ).not.toContain('only hidden reasoning') + + mock.restore() + mock.module('./model/providers.js', () => ({ + getAPIProvider: () => 'bedrock', + isOpenAICompatibleProvider: (provider: string) => + provider === 'openai' || + provider === 'gemini' || + provider === 'github' || + provider === 'codex', + })) + + const bedrockModule = await import(`./conversationRecovery.ts?provider=bedrock-${Date.now()}`) + const anthropicCompatible = bedrockModule.deserializeMessagesWithInterruptDetection(serializedMessages as never[]) + const anthropicAssistantMessages = anthropicCompatible.messages.filter( + message => message.type === 'assistant', + ) + + expect(anthropicAssistantMessages).toHaveLength(2) + expect(anthropicAssistantMessages[0]?.message?.content).toEqual([ + { type: 'thinking', thinking: 'secret reasoning' }, + { type: 'text', text: 'visible reply' }, + ]) + expect( + JSON.stringify(anthropicAssistantMessages.map(message => message.message?.content)), + ).toContain('secret reasoning') + expect( + JSON.stringify(anthropicAssistantMessages.map(message => message.message?.content)), + ).not.toContain('only hidden reasoning') +}) diff --git a/src/utils/conversationRecovery.test.ts b/src/utils/conversationRecovery.test.ts index 4e44550b..88a1b0c5 100644 --- a/src/utils/conversationRecovery.test.ts +++ b/src/utils/conversationRecovery.test.ts @@ -4,49 +4,15 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { - deserializeMessagesWithInterruptDetection, loadConversationForResume, ResumeTranscriptTooLargeError, } from './conversationRecovery.ts' const tempDirs: string[] = [] const originalSimple = process.env.CLAUDE_CODE_SIMPLE -const originalProviderEnv = { - CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI, - CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI, - CLAUDE_CODE_USE_GITHUB: process.env.CLAUDE_CODE_USE_GITHUB, - CLAUDE_CODE_USE_BEDROCK: process.env.CLAUDE_CODE_USE_BEDROCK, - CLAUDE_CODE_USE_VERTEX: process.env.CLAUDE_CODE_USE_VERTEX, - CLAUDE_CODE_USE_FOUNDRY: process.env.CLAUDE_CODE_USE_FOUNDRY, -} const sessionId = '00000000-0000-4000-8000-000000001999' const ts = '2026-04-02T00:00:00.000Z' -function restoreProviderEnv() { - for (const [key, value] of Object.entries(originalProviderEnv)) { - if (value === undefined) { - delete process.env[key] - } else { - process.env[key] = value - } - } -} - -function clearProviderEnv() { - delete process.env.CLAUDE_CODE_USE_OPENAI - delete process.env.CLAUDE_CODE_USE_GEMINI - delete process.env.CLAUDE_CODE_USE_GITHUB - delete process.env.CLAUDE_CODE_USE_BEDROCK - delete process.env.CLAUDE_CODE_USE_VERTEX - delete process.env.CLAUDE_CODE_USE_FOUNDRY -} - -function setProviderEnv(provider: 'openai' | 'bedrock') { - clearProviderEnv() - if (provider === 'openai') process.env.CLAUDE_CODE_USE_OPENAI = '1' - if (provider === 'bedrock') process.env.CLAUDE_CODE_USE_BEDROCK = '1' -} - function id(n: number): string { return `00000000-0000-4000-8000-${String(n).padStart(12, '0')}` @@ -81,7 +47,6 @@ async function writeJsonl(entry: unknown): Promise { afterEach(async () => { process.env.CLAUDE_CODE_SIMPLE = originalSimple - restoreProviderEnv() await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true }))) }) @@ -113,74 +78,4 @@ test('loadConversationForResume rejects oversized reconstructed transcripts', as ) }) -test('deserializeMessagesWithInterruptDetection strips thinking blocks only for OpenAI-compatible providers', () => { - const serializedMessages = [ - user(id(10), 'hello'), - { - type: 'assistant', - uuid: id(11), - parentUuid: id(10), - timestamp: ts, - cwd: '/tmp', - sessionId, - version: 'test', - message: { - role: 'assistant', - content: [ - { type: 'thinking', thinking: 'secret reasoning' }, - { type: 'text', text: 'visible reply' }, - ], - }, - }, - { - type: 'assistant', - uuid: id(12), - parentUuid: id(11), - timestamp: ts, - cwd: '/tmp', - sessionId, - version: 'test', - message: { - role: 'assistant', - content: [{ type: 'thinking', thinking: 'only hidden reasoning' }], - }, - }, - user(id(13), 'follow up'), - ] - - setProviderEnv('openai') - const thirdParty = deserializeMessagesWithInterruptDetection(serializedMessages as never[]) - const thirdPartyAssistantMessages = thirdParty.messages.filter( - message => message.type === 'assistant', - ) - - expect(thirdPartyAssistantMessages).toHaveLength(2) - expect(thirdPartyAssistantMessages[0]?.message?.content).toEqual([ - { type: 'text', text: 'visible reply' }, - ]) - expect( - JSON.stringify(thirdPartyAssistantMessages.map(message => message.message?.content)), - ).not.toContain('secret reasoning') - expect( - JSON.stringify(thirdPartyAssistantMessages.map(message => message.message?.content)), - ).not.toContain('only hidden reasoning') - - setProviderEnv('bedrock') - const anthropicCompatible = deserializeMessagesWithInterruptDetection(serializedMessages as never[]) - const anthropicAssistantMessages = anthropicCompatible.messages.filter( - message => message.type === 'assistant', - ) - - expect(anthropicAssistantMessages).toHaveLength(2) - expect(anthropicAssistantMessages[0]?.message?.content).toEqual([ - { type: 'thinking', thinking: 'secret reasoning' }, - { type: 'text', text: 'visible reply' }, - ]) - expect( - JSON.stringify(anthropicAssistantMessages.map(message => message.message?.content)), - ).toContain('secret reasoning') - expect( - JSON.stringify(anthropicAssistantMessages.map(message => message.message?.content)), - ).not.toContain('only hidden reasoning') -})