dify/sdks/nodejs-client/src/index.test.ts
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

237 lines
7.2 KiB
TypeScript

import { Readable } from 'node:stream'
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { BASE_URL, ChatClient, DifyClient, WorkflowClient, routes } from './index'
const stubFetch = (): ReturnType<typeof vi.fn> => {
const fetchMock = vi.fn()
vi.stubGlobal('fetch', fetchMock)
return fetchMock
}
const jsonResponse = (body: unknown, init: ResponseInit = {}): Response =>
new Response(JSON.stringify(body), {
status: 200,
...init,
headers: {
'content-type': 'application/json',
...init.headers,
},
})
describe('Client', () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
it('creates a client with default settings', () => {
const difyClient = new DifyClient('test')
expect(difyClient.getHttpClient().getSettings()).toMatchObject({
apiKey: 'test',
baseUrl: BASE_URL,
timeout: 60,
})
})
it('updates the api key', () => {
const difyClient = new DifyClient('test')
difyClient.updateApiKey('test2')
expect(difyClient.getHttpClient().getSettings().apiKey).toBe('test2')
})
})
describe('Send Requests', () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
it('makes a successful request to the application parameter route', async () => {
const fetchMock = stubFetch()
const difyClient = new DifyClient('test')
const method = 'GET'
const endpoint = routes.application.url()
fetchMock.mockResolvedValueOnce(jsonResponse('response'))
const response = await difyClient.sendRequest(method, endpoint)
expect(response).toMatchObject({
status: 200,
data: 'response',
headers: {
'content-type': 'application/json',
},
})
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
expect(url).toBe(`${BASE_URL}${endpoint}`)
expect(init.method).toBe(method)
expect(init.headers).toMatchObject({
Authorization: 'Bearer test',
'User-Agent': 'dify-client-node',
})
})
it('uses the getMeta route configuration', async () => {
const fetchMock = stubFetch()
const difyClient = new DifyClient('test')
fetchMock.mockResolvedValueOnce(jsonResponse({ ok: true }))
await difyClient.getMeta('end-user')
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
expect(url).toBe(`${BASE_URL}${routes.getMeta.url()}?user=end-user`)
expect(init.method).toBe(routes.getMeta.method)
expect(init.headers).toMatchObject({
Authorization: 'Bearer test',
})
})
})
describe('File uploads', () => {
const OriginalFormData = globalThis.FormData
beforeAll(() => {
globalThis.FormData = class FormDataMock extends Readable {
constructor() {
super()
}
override _read() {}
append() {}
getHeaders() {
return {
'content-type': 'multipart/form-data; boundary=test',
}
}
} as unknown as typeof FormData
})
afterAll(() => {
globalThis.FormData = OriginalFormData
})
beforeEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
it('does not override multipart boundary headers for legacy FormData', async () => {
const fetchMock = stubFetch()
const difyClient = new DifyClient('test')
const form = new globalThis.FormData()
fetchMock.mockResolvedValueOnce(jsonResponse({ ok: true }))
await difyClient.fileUpload(form, 'end-user')
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
expect(url).toBe(`${BASE_URL}${routes.fileUpload.url()}`)
expect(init.method).toBe(routes.fileUpload.method)
expect(init.headers).toMatchObject({
Authorization: 'Bearer test',
'content-type': 'multipart/form-data; boundary=test',
})
expect(init.body).not.toBe(form)
expect((init as RequestInit & { duplex?: string }).duplex).toBe('half')
})
})
describe('Workflow client', () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
it('uses tasks stop path for workflow stop', async () => {
const fetchMock = stubFetch()
const workflowClient = new WorkflowClient('test')
fetchMock.mockResolvedValueOnce(jsonResponse({ result: 'success' }))
await workflowClient.stop('task-1', 'end-user')
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
expect(url).toBe(`${BASE_URL}${routes.stopWorkflow.url('task-1')}`)
expect(init.method).toBe(routes.stopWorkflow.method)
expect(init.headers).toMatchObject({
Authorization: 'Bearer test',
'Content-Type': 'application/json',
})
expect(init.body).toBe(JSON.stringify({ user: 'end-user' }))
})
it('maps workflow log filters to service api params', async () => {
const fetchMock = stubFetch()
const workflowClient = new WorkflowClient('test')
fetchMock.mockResolvedValueOnce(jsonResponse({ ok: true }))
await workflowClient.getLogs({
createdAtAfter: '2024-01-01T00:00:00Z',
createdAtBefore: '2024-01-02T00:00:00Z',
createdByEndUserSessionId: 'sess-1',
createdByAccount: 'acc-1',
page: 2,
limit: 10,
})
const [url] = fetchMock.mock.calls[0] as [string, RequestInit]
const parsedUrl = new URL(url)
expect(parsedUrl.origin + parsedUrl.pathname).toBe(`${BASE_URL}/workflows/logs`)
expect(parsedUrl.searchParams.get('created_at__before')).toBe('2024-01-02T00:00:00Z')
expect(parsedUrl.searchParams.get('created_at__after')).toBe('2024-01-01T00:00:00Z')
expect(parsedUrl.searchParams.get('created_by_end_user_session_id')).toBe('sess-1')
expect(parsedUrl.searchParams.get('created_by_account')).toBe('acc-1')
expect(parsedUrl.searchParams.get('page')).toBe('2')
expect(parsedUrl.searchParams.get('limit')).toBe('10')
})
})
describe('Chat client', () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
it('places user in query for suggested messages', async () => {
const fetchMock = stubFetch()
const chatClient = new ChatClient('test')
fetchMock.mockResolvedValueOnce(jsonResponse({ result: 'success', data: [] }))
await chatClient.getSuggested('msg-1', 'end-user')
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
expect(url).toBe(`${BASE_URL}${routes.getSuggested.url('msg-1')}?user=end-user`)
expect(init.method).toBe(routes.getSuggested.method)
expect(init.headers).toMatchObject({
Authorization: 'Bearer test',
})
})
it('uses last_id when listing conversations', async () => {
const fetchMock = stubFetch()
const chatClient = new ChatClient('test')
fetchMock.mockResolvedValueOnce(jsonResponse({ ok: true }))
await chatClient.getConversations('end-user', 'last-1', 10)
const [url] = fetchMock.mock.calls[0] as [string, RequestInit]
expect(url).toBe(
`${BASE_URL}${routes.getConversations.url()}?user=end-user&last_id=last-1&limit=10`,
)
})
it('lists app feedbacks without user params', async () => {
const fetchMock = stubFetch()
const chatClient = new ChatClient('test')
fetchMock.mockResolvedValueOnce(jsonResponse({ data: [] }))
await chatClient.getAppFeedbacks(1, 20)
const [url] = fetchMock.mock.calls[0] as [string, RequestInit]
expect(url).toBe(`${BASE_URL}/app/feedbacks?page=1&limit=20`)
})
})