dify/web/service/base.spec.ts
Crazywoola 8809cc036d
feat(workflow-generator): enhance the AI auto-creation flow end-to-end (#38175)
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>
2026-07-01 02:28:58 +00:00

294 lines
7.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
// eslint-disable-next-line no-restricted-imports
import { del, get, handleStream, patch, post, put } from './base'
describe('handleStream', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('Invalid response data handling', () => {
it('should handle null bufferObj from JSON.parse gracefully', async () => {
// Arrange
const onData = vi.fn()
const onCompleted = vi.fn()
// Create a mock response that returns 'data: null'
const mockReader = {
read: vi.fn()
.mockResolvedValueOnce({
done: false,
value: new TextEncoder().encode('data: null\n'),
})
.mockResolvedValueOnce({
done: true,
value: undefined,
}),
}
const mockResponse = {
ok: true,
body: {
getReader: () => mockReader,
},
} as unknown as Response
// Act
handleStream(mockResponse, onData, onCompleted)
// Wait for the stream to be processed
await new Promise(resolve => setTimeout(resolve, 50))
// Assert
expect(onData).toHaveBeenCalledWith('', true, {
conversationId: undefined,
messageId: '',
errorMessage: 'Invalid response data',
errorCode: 'invalid_data',
})
expect(onCompleted).toHaveBeenCalledWith(true, 'Invalid response data')
})
it('should handle non-object bufferObj from JSON.parse gracefully', async () => {
// Arrange
const onData = vi.fn()
const onCompleted = vi.fn()
// Create a mock response that returns a primitive value
const mockReader = {
read: vi.fn()
.mockResolvedValueOnce({
done: false,
value: new TextEncoder().encode('data: "string"\n'),
})
.mockResolvedValueOnce({
done: true,
value: undefined,
}),
}
const mockResponse = {
ok: true,
body: {
getReader: () => mockReader,
},
} as unknown as Response
// Act
handleStream(mockResponse, onData, onCompleted)
// Wait for the stream to be processed
await new Promise(resolve => setTimeout(resolve, 50))
// Assert
expect(onData).toHaveBeenCalledWith('', true, {
conversationId: undefined,
messageId: '',
errorMessage: 'Invalid response data',
errorCode: 'invalid_data',
})
expect(onCompleted).toHaveBeenCalledWith(true, 'Invalid response data')
})
it('should handle valid message event correctly', async () => {
// Arrange
const onData = vi.fn()
const onCompleted = vi.fn()
const validMessage = {
event: 'message',
answer: 'Hello world',
conversation_id: 'conv-123',
task_id: 'task-456',
id: 'msg-789',
}
const mockReader = {
read: vi.fn()
.mockResolvedValueOnce({
done: false,
value: new TextEncoder().encode(`data: ${JSON.stringify(validMessage)}\n`),
})
.mockResolvedValueOnce({
done: true,
value: undefined,
}),
}
const mockResponse = {
ok: true,
body: {
getReader: () => mockReader,
},
} as unknown as Response
// Act
handleStream(mockResponse, onData, onCompleted)
// Wait for the stream to be processed
await new Promise(resolve => setTimeout(resolve, 50))
// Assert
expect(onData).toHaveBeenCalledWith('Hello world', true, {
conversationId: 'conv-123',
taskId: 'task-456',
messageId: 'msg-789',
})
expect(onCompleted).toHaveBeenCalled()
})
it('should handle error status 400 correctly', async () => {
// Arrange
const onData = vi.fn()
const onCompleted = vi.fn()
const errorMessage = {
status: 400,
message: 'Bad request',
code: 'bad_request',
}
const mockReader = {
read: vi.fn()
.mockResolvedValueOnce({
done: false,
value: new TextEncoder().encode(`data: ${JSON.stringify(errorMessage)}\n`),
})
.mockResolvedValueOnce({
done: true,
value: undefined,
}),
}
const mockResponse = {
ok: true,
body: {
getReader: () => mockReader,
},
} as unknown as Response
// Act
handleStream(mockResponse, onData, onCompleted)
// Wait for the stream to be processed
await new Promise(resolve => setTimeout(resolve, 50))
// Assert
expect(onData).toHaveBeenCalledWith('', false, {
conversationId: undefined,
messageId: '',
errorMessage: 'Bad request',
errorCode: 'bad_request',
})
expect(onCompleted).toHaveBeenCalledWith(true, 'Bad request')
})
it('should handle malformed JSON gracefully', async () => {
// Arrange
const onData = vi.fn()
const onCompleted = vi.fn()
const mockReader = {
read: vi.fn()
.mockResolvedValueOnce({
done: false,
value: new TextEncoder().encode('data: {invalid json}\n'),
})
.mockResolvedValueOnce({
done: true,
value: undefined,
}),
}
const mockResponse = {
ok: true,
body: {
getReader: () => mockReader,
},
} as unknown as Response
// Act
handleStream(mockResponse, onData, onCompleted)
// Wait for the stream to be processed
await new Promise(resolve => setTimeout(resolve, 50))
// Assert - malformed JSON triggers the catch block which calls onData and returns
expect(onData).toHaveBeenCalled()
expect(onCompleted).toHaveBeenCalled()
})
it('should dispatch reasoning_chunk events to onReasoning', async () => {
// Arrange
const onData = vi.fn()
const onCompleted = vi.fn()
const onReasoning = vi.fn()
const reasoningEvent = {
event: 'reasoning_chunk',
task_id: 'task-1',
data: { message_id: 'm-1', reasoning: 'let me think', node_id: 'llm', is_final: false },
}
const mockReader = {
read: vi.fn()
.mockResolvedValueOnce({
done: false,
value: new TextEncoder().encode(`data: ${JSON.stringify(reasoningEvent)}\n`),
})
.mockResolvedValueOnce({
done: true,
value: undefined,
}),
}
const mockResponse = {
ok: true,
body: {
getReader: () => mockReader,
},
} as unknown as Response
// onReasoning is the last positional handler; fill the unused intervening slots.
const interveningNoops = Array.from({ length: 29 }, () => undefined)
// Act
;(handleStream as (...args: unknown[]) => void)(
mockResponse,
onData,
onCompleted,
...interveningNoops,
onReasoning,
)
// Wait for the stream to be processed
await new Promise(resolve => setTimeout(resolve, 50))
// Assert - the full event object is forwarded to onReasoning, answer stays untouched
expect(onReasoning).toHaveBeenCalledWith(reasoningEvent)
expect(onData).not.toHaveBeenCalled()
})
it('should throw error when response is not ok', () => {
// Arrange
const onData = vi.fn()
const mockResponse = {
ok: false,
} as unknown as Response
// Act & Assert
expect(() => handleStream(mockResponse, onData)).toThrow('Network response was not ok')
})
})
})
describe('HTTP methods', () => {
it('should export methods correctly', () => {
expect(typeof get).toBe('function')
expect(typeof post).toBe('function')
expect(typeof put).toBe('function')
expect(typeof patch).toBe('function')
expect(typeof del).toBe('function')
})
})