test: move provider-sensitive resume coverage behind module mocks
This commit is contained in:
@@ -69,3 +69,93 @@ test('loadConversationForResume rejects oversized transcripts before resume hook
|
|||||||
)
|
)
|
||||||
expect(hookSpy).not.toHaveBeenCalled()
|
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')
|
||||||
|
})
|
||||||
|
|||||||
@@ -4,49 +4,15 @@ import { tmpdir } from 'node:os'
|
|||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deserializeMessagesWithInterruptDetection,
|
|
||||||
loadConversationForResume,
|
loadConversationForResume,
|
||||||
ResumeTranscriptTooLargeError,
|
ResumeTranscriptTooLargeError,
|
||||||
} from './conversationRecovery.ts'
|
} from './conversationRecovery.ts'
|
||||||
|
|
||||||
const tempDirs: string[] = []
|
const tempDirs: string[] = []
|
||||||
const originalSimple = process.env.CLAUDE_CODE_SIMPLE
|
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 sessionId = '00000000-0000-4000-8000-000000001999'
|
||||||
const ts = '2026-04-02T00:00:00.000Z'
|
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 {
|
function id(n: number): string {
|
||||||
return `00000000-0000-4000-8000-${String(n).padStart(12, '0')}`
|
return `00000000-0000-4000-8000-${String(n).padStart(12, '0')}`
|
||||||
@@ -81,7 +47,6 @@ async function writeJsonl(entry: unknown): Promise<string> {
|
|||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
process.env.CLAUDE_CODE_SIMPLE = originalSimple
|
process.env.CLAUDE_CODE_SIMPLE = originalSimple
|
||||||
restoreProviderEnv()
|
|
||||||
await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })))
|
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')
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user