mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 18:58:35 +08:00
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
101 lines
3.7 KiB
TypeScript
101 lines
3.7 KiB
TypeScript
// Testing the SSE helper requires importing the module under test directly.
|
|
// eslint-disable-next-line no-restricted-imports
|
|
import { sseGeneratorPost } from '../base'
|
|
|
|
vi.mock('@/utils/var', () => ({
|
|
basePath: '/app',
|
|
API_PREFIX: '/console/api',
|
|
PUBLIC_API_PREFIX: '/api',
|
|
IS_CE_EDITION: false,
|
|
}))
|
|
|
|
// Minimal streaming Response: a body whose reader yields the given chunks in
|
|
// order, then signals done. ``json()`` backs the non-2xx error path.
|
|
const makeStreamResponse = (chunks: string[], status = 200) => {
|
|
const encoder = new TextEncoder()
|
|
let i = 0
|
|
return {
|
|
status,
|
|
body: {
|
|
getReader: () => ({
|
|
read: () =>
|
|
i < chunks.length
|
|
? Promise.resolve({ done: false, value: encoder.encode(chunks[i++]) })
|
|
: Promise.resolve({ done: true, value: undefined }),
|
|
}),
|
|
},
|
|
json: () => Promise.resolve({ message: 'Server Error' }),
|
|
} as unknown as Response
|
|
}
|
|
|
|
describe('sseGeneratorPost', () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
it('streams plan then result events, hands back an AbortController, and completes', async () => {
|
|
const planFrame = `data: ${JSON.stringify({ event: 'plan', title: 'X', nodes: [] })}\n\n`
|
|
const resultFrame = `data: ${JSON.stringify({ event: 'result', graph: { nodes: [] } })}\n\n`
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(makeStreamResponse([planFrame, resultFrame])))
|
|
|
|
const onPlan = vi.fn()
|
|
const onResult = vi.fn()
|
|
const onCompleted = vi.fn()
|
|
const onError = vi.fn()
|
|
let controller: AbortController | undefined
|
|
|
|
sseGeneratorPost('/workflow-generate/stream', { mode: 'workflow' }, {
|
|
onPlan,
|
|
onResult,
|
|
onCompleted,
|
|
onError,
|
|
getAbortController: (c) => { controller = c },
|
|
})
|
|
|
|
await vi.waitFor(() => expect(onCompleted).toHaveBeenCalledTimes(1))
|
|
|
|
expect(controller).toBeInstanceOf(AbortController)
|
|
expect(onPlan).toHaveBeenCalledWith(expect.objectContaining({ event: 'plan', title: 'X' }))
|
|
expect(onResult).toHaveBeenCalledWith(expect.objectContaining({ event: 'result' }))
|
|
expect(onError).not.toHaveBeenCalled()
|
|
// POSTs to the prefixed stream URL.
|
|
const [calledUrl, calledOpts] = (globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0]!
|
|
expect(String(calledUrl)).toContain('/workflow-generate/stream')
|
|
expect((calledOpts as RequestInit).method).toBe('POST')
|
|
})
|
|
|
|
it('reassembles a JSON frame split across chunk boundaries', async () => {
|
|
const frame = JSON.stringify({ event: 'result', graph: { nodes: [] } })
|
|
const part1 = `data: ${frame.slice(0, 10)}`
|
|
const part2 = `${frame.slice(10)}\n\n`
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(makeStreamResponse([part1, part2])))
|
|
|
|
const onResult = vi.fn()
|
|
const onCompleted = vi.fn()
|
|
sseGeneratorPost('/workflow-generate/stream', {}, { onResult, onCompleted })
|
|
|
|
await vi.waitFor(() => expect(onCompleted).toHaveBeenCalled())
|
|
expect(onResult).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('reports a non-2xx (non-401) response through onError', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(makeStreamResponse([], 500)))
|
|
|
|
const onError = vi.fn()
|
|
sseGeneratorPost('/workflow-generate/stream', {}, { onError })
|
|
|
|
await vi.waitFor(() => expect(onError).toHaveBeenCalledWith('Server Error'))
|
|
})
|
|
|
|
it('reports a rejected fetch through onError', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network down')))
|
|
|
|
const onError = vi.fn()
|
|
sseGeneratorPost('/workflow-generate/stream', {}, { onError })
|
|
|
|
await vi.waitFor(() => expect(onError).toHaveBeenCalled())
|
|
expect(onError.mock.calls[0]![0]).toContain('network down')
|
|
})
|
|
})
|