兼容Debug EML SSE断流兜底

This commit is contained in:
andy
2026-07-12 19:26:52 +08:00
parent 9d095cfd53
commit 807d045526
17 changed files with 556 additions and 47 deletions

View File

@@ -41,6 +41,27 @@ function mockSseResponse(chunks: string[], status = 200): Response {
})
}
function mockErroredSseResponse(chunksBeforeError: string[], error: Error, status = 200): Response {
const encoder = new TextEncoder()
let nextChunkIndex = 0
const stream = new ReadableStream<Uint8Array>({
pull(controller) {
if (nextChunkIndex < chunksBeforeError.length) {
controller.enqueue(encoder.encode(chunksBeforeError[nextChunkIndex]))
nextChunkIndex += 1
return
}
controller.error(error)
},
})
return new Response(stream, {
status,
headers: {
'Content-Type': 'text/event-stream',
},
})
}
describe('debugEmlService', () => {
beforeEach(() => {
vi.restoreAllMocks()
@@ -275,4 +296,86 @@ describe('debugEmlService', () => {
message: 'SuperAgent 调用失败。',
} satisfies Partial<DebugEmlUploadError>)
})
it('polls backend run status when SSE closes before final result', async () => {
const fetchMock = vi.spyOn(globalThis, 'fetch')
fetchMock.mockResolvedValueOnce(
mockSseResponse([
'event: debug_stage\n',
'data: {"debug_run_id":"90005","status":"CALLING_SUPERAGENT","safe_summary":"阶段:调用 SuperAgent Open API。"}\n\n',
]),
)
fetchMock.mockResolvedValueOnce(
mockJsonResponse({
debug_run_id: '90005',
source_message_id: '30005',
uploaded_media: [],
superagent_session_id: 'session-poll-1',
superagent_run_id: 'run-poll-1',
superagent_raw_answer: '{"route_code":"S10"}',
superagent_parsed_json: { route_code: 'S10' },
superagent_trace_events: [],
warnings: [],
status: 'SUPERAGENT_SUCCEEDED',
}),
)
const file = new File(['From: guest@example.test'], 'booking.eml', { type: 'message/rfc822' })
const result = await uploadDebugEmlSuperAgentRunStream({
hotelId: 'HOTEL-TEST',
debugUploadKey: 'manual-debug-key',
file,
})
expect(fetchMock).toHaveBeenCalledTimes(2)
expect(fetchMock.mock.calls[1]?.[0]).toBe('/api/system/debug/eml-superagent-runs/90005')
expect(fetchMock.mock.calls[1]?.[1]).toMatchObject({
method: 'GET',
headers: {
Accept: 'application/json',
'X-TH-Hotel-Debug-Upload-Key': 'manual-debug-key',
},
})
expect(result.superagent_run_id).toBe('run-poll-1')
expect(result.superagent_parsed_json).toEqual({ route_code: 'S10' })
})
it('polls backend run status when SSE stream errors after sending debug run id', async () => {
const fetchMock = vi.spyOn(globalThis, 'fetch')
fetchMock.mockResolvedValueOnce(
mockErroredSseResponse(
[
'event: debug_stage\n',
'data: {"debug_run_id":"90006","status":"CALLING_SUPERAGENT","safe_summary":"阶段:调用 SuperAgent Open API。"}\n\n',
],
new Error('stream reset by proxy'),
),
)
fetchMock.mockResolvedValueOnce(
mockJsonResponse({
debug_run_id: '90006',
source_message_id: '30006',
uploaded_media: [],
superagent_session_id: 'session-poll-error-1',
superagent_run_id: 'run-poll-error-1',
superagent_raw_answer: '{"route_code":"S10"}',
superagent_parsed_json: { route_code: 'S10' },
superagent_trace_events: [],
warnings: [],
status: 'SUPERAGENT_SUCCEEDED',
}),
)
const file = new File(['From: guest@example.test'], 'booking.eml', { type: 'message/rfc822' })
const result = await uploadDebugEmlSuperAgentRunStream({
hotelId: 'HOTEL-TEST',
debugUploadKey: 'manual-debug-key',
file,
})
expect(fetchMock).toHaveBeenCalledTimes(2)
expect(fetchMock.mock.calls[1]?.[0]).toBe('/api/system/debug/eml-superagent-runs/90006')
expect(result.superagent_run_id).toBe('run-poll-error-1')
expect(result.superagent_parsed_json).toEqual({ route_code: 'S10' })
})
})

View File

@@ -83,6 +83,7 @@ function createResult() {
],
warnings: ['SuperAgent 返回非 JSON 时会展示原始回答'],
status: 'SUPERAGENT_SUCCEEDED',
safe_error_summary: null,
}
}