Fix Node.js SDK routes and multipart handling (#28573)

This commit is contained in:
yyh
2025-11-24 21:00:40 +08:00
committed by GitHub
parent aab95d0626
commit 034e3e85e9
5 changed files with 111 additions and 30 deletions

View File

@@ -0,0 +1,12 @@
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current",
},
},
],
],
};

View File

@@ -71,7 +71,7 @@ export const routes = {
}, },
stopWorkflow: { stopWorkflow: {
method: "POST", method: "POST",
url: (task_id) => `/workflows/${task_id}/stop`, url: (task_id) => `/workflows/tasks/${task_id}/stop`,
} }
}; };
@@ -94,11 +94,13 @@ export class DifyClient {
stream = false, stream = false,
headerParams = {} headerParams = {}
) { ) {
const isFormData =
(typeof FormData !== "undefined" && data instanceof FormData) ||
(data && data.constructor && data.constructor.name === "FormData");
const headers = { const headers = {
Authorization: `Bearer ${this.apiKey}`,
Authorization: `Bearer ${this.apiKey}`, ...(isFormData ? {} : { "Content-Type": "application/json" }),
"Content-Type": "application/json", ...headerParams,
...headerParams
}; };
const url = `${this.baseUrl}${endpoint}`; const url = `${this.baseUrl}${endpoint}`;
@@ -152,12 +154,7 @@ export class DifyClient {
return this.sendRequest( return this.sendRequest(
routes.fileUpload.method, routes.fileUpload.method,
routes.fileUpload.url(), routes.fileUpload.url(),
data, data
null,
false,
{
"Content-Type": 'multipart/form-data'
}
); );
} }
@@ -179,8 +176,8 @@ export class DifyClient {
getMeta(user) { getMeta(user) {
const params = { user }; const params = { user };
return this.sendRequest( return this.sendRequest(
routes.meta.method, routes.getMeta.method,
routes.meta.url(), routes.getMeta.url(),
null, null,
params params
); );
@@ -320,12 +317,7 @@ export class ChatClient extends DifyClient {
return this.sendRequest( return this.sendRequest(
routes.audioToText.method, routes.audioToText.method,
routes.audioToText.url(), routes.audioToText.url(),
data, data
null,
false,
{
"Content-Type": 'multipart/form-data'
}
); );
} }

View File

@@ -1,9 +1,13 @@
import { DifyClient, BASE_URL, routes } from "."; import { DifyClient, WorkflowClient, BASE_URL, routes } from ".";
import axios from 'axios' import axios from 'axios'
jest.mock('axios') jest.mock('axios')
afterEach(() => {
jest.resetAllMocks()
})
describe('Client', () => { describe('Client', () => {
let difyClient let difyClient
beforeEach(() => { beforeEach(() => {
@@ -27,13 +31,9 @@ describe('Send Requests', () => {
difyClient = new DifyClient('test') difyClient = new DifyClient('test')
}) })
afterEach(() => {
jest.resetAllMocks()
})
it('should make a successful request to the application parameter', async () => { it('should make a successful request to the application parameter', async () => {
const method = 'GET' const method = 'GET'
const endpoint = routes.application.url const endpoint = routes.application.url()
const expectedResponse = { data: 'response' } const expectedResponse = { data: 'response' }
axios.mockResolvedValue(expectedResponse) axios.mockResolvedValue(expectedResponse)
@@ -62,4 +62,80 @@ describe('Send Requests', () => {
errorMessage errorMessage
) )
}) })
it('uses the getMeta route configuration', async () => {
axios.mockResolvedValue({ data: 'ok' })
await difyClient.getMeta('end-user')
expect(axios).toHaveBeenCalledWith({
method: routes.getMeta.method,
url: `${BASE_URL}${routes.getMeta.url()}`,
params: { user: 'end-user' },
headers: {
Authorization: `Bearer ${difyClient.apiKey}`,
'Content-Type': 'application/json',
},
responseType: 'json',
})
})
})
describe('File uploads', () => {
let difyClient
const OriginalFormData = global.FormData
beforeAll(() => {
global.FormData = class FormDataMock {}
})
afterAll(() => {
global.FormData = OriginalFormData
})
beforeEach(() => {
difyClient = new DifyClient('test')
})
it('does not override multipart boundary headers for FormData', async () => {
const form = new FormData()
axios.mockResolvedValue({ data: 'ok' })
await difyClient.fileUpload(form)
expect(axios).toHaveBeenCalledWith({
method: routes.fileUpload.method,
url: `${BASE_URL}${routes.fileUpload.url()}`,
data: form,
params: null,
headers: {
Authorization: `Bearer ${difyClient.apiKey}`,
},
responseType: 'json',
})
})
})
describe('Workflow client', () => {
let workflowClient
beforeEach(() => {
workflowClient = new WorkflowClient('test')
})
it('uses tasks stop path for workflow stop', async () => {
axios.mockResolvedValue({ data: 'stopped' })
await workflowClient.stop('task-1', 'end-user')
expect(axios).toHaveBeenCalledWith({
method: routes.stopWorkflow.method,
url: `${BASE_URL}${routes.stopWorkflow.url('task-1')}`,
data: { user: 'end-user' },
params: null,
headers: {
Authorization: `Bearer ${workflowClient.apiKey}`,
'Content-Type': 'application/json',
},
responseType: 'json',
})
})
}) })

View File

@@ -0,0 +1,6 @@
module.exports = {
testEnvironment: "node",
transform: {
"^.+\\.[tj]sx?$": "babel-jest",
},
};

View File

@@ -18,11 +18,6 @@
"scripts": { "scripts": {
"test": "jest" "test": "jest"
}, },
"jest": {
"transform": {
"^.+\\.[t|j]sx?$": "babel-jest"
}
},
"dependencies": { "dependencies": {
"axios": "^1.3.5" "axios": "^1.3.5"
}, },