1- import { DEFAULT_TIMEOUT , HttpClient , isBlob } from '../src/httpClient' ;
1+ import {
2+ DEFAULT_TIMEOUT ,
3+ HttpClient ,
4+ isBlob ,
5+ createFormDataOptions ,
6+ } from '../src/httpClient' ;
27import {
38 AxiosHeaders ,
49 AxiosRequestConfig ,
@@ -14,6 +19,7 @@ import {
1419 HttpResponse ,
1520} from '@apimatic/core-interfaces' ;
1621import { FileWrapper } from '@apimatic/file-wrapper' ;
22+ import { createFormData } from '@apimatic/core-interfaces' ;
1723import FormData from 'form-data' ;
1824import fs from 'fs' ;
1925
@@ -138,6 +144,102 @@ describe('HTTP Client', () => {
138144 } ) ;
139145 } ) ;
140146
147+ it ( 'converts request with http form-data containing FormDataWrapper and FileWrapper' , async ( ) => {
148+ const httpClient = new HttpClient ( AbortError ) ;
149+ // Need FileWrapper to trigger multipart mode
150+ const fileWrapper = new FileWrapper (
151+ fs . createReadStream ( 'test/dummy_file.txt' ) ,
152+ {
153+ filename : 'document.txt' ,
154+ headers : { 'content-type' : 'text/plain' } ,
155+ }
156+ ) ;
157+ const formDataWrapper = createFormData (
158+ { userId : 123 , name : 'Test User' } ,
159+ { 'content-type' : 'application/json' }
160+ ) ;
161+ const formDataBody : HttpRequestMultipartFormBody = {
162+ type : 'form-data' ,
163+ content : [
164+ { key : 'file' , value : fileWrapper } ,
165+ { key : 'metadata' , value : formDataWrapper ! } ,
166+ { key : 'param1' , value : 'value1' } ,
167+ ] ,
168+ } ;
169+
170+ const request : HttpRequest = {
171+ method : 'POST' ,
172+ url : 'http://apimatic.hopto.org:3000/test/requestBuilder' ,
173+ headers : { 'test-header' : 'test-value' } ,
174+ body : formDataBody ,
175+ responseType : 'text' ,
176+ } ;
177+
178+ const axiosRequestConfig = httpClient . convertHttpRequest ( request ) ;
179+ expect ( axiosRequestConfig ) . toMatchObject ( {
180+ url : 'http://apimatic.hopto.org:3000/test/requestBuilder' ,
181+ method : 'POST' ,
182+ headers : {
183+ 'test-header' : 'test-value' ,
184+ 'content-type' : new RegExp (
185+ '^multipart/form-data; boundary=--------------------------'
186+ ) ,
187+ } ,
188+ timeout : DEFAULT_TIMEOUT ,
189+ responseType : 'text' ,
190+ data : expect . any ( FormData ) ,
191+ } ) ;
192+ } ) ;
193+
194+ it ( 'converts request with mixed form-data containing multiple FormDataWrappers' , async ( ) => {
195+ const httpClient = new HttpClient ( AbortError ) ;
196+ // Need FileWrapper to trigger multipart mode
197+ const fileWrapper = new FileWrapper (
198+ fs . createReadStream ( 'test/dummy_file.txt' )
199+ ) ;
200+ const formDataWrapper1 = createFormData (
201+ { type : 'document' , status : 'active' } ,
202+ { 'content-type' : 'application/json' }
203+ ) ;
204+ const formDataWrapper2 = createFormData ( {
205+ version : 1 ,
206+ lastModified : '2025-10-31' ,
207+ } ) ;
208+
209+ const formDataBody : HttpRequestMultipartFormBody = {
210+ type : 'form-data' ,
211+ content : [
212+ { key : 'file' , value : fileWrapper } ,
213+ { key : 'metadata' , value : formDataWrapper1 ! } ,
214+ { key : 'version_info' , value : formDataWrapper2 ! } ,
215+ { key : 'description' , value : 'Test upload' } ,
216+ ] ,
217+ } ;
218+
219+ const request : HttpRequest = {
220+ method : 'POST' ,
221+ url : 'http://apimatic.hopto.org:3000/test/upload' ,
222+ headers : { authorization : 'Bearer token123' } ,
223+ body : formDataBody ,
224+ responseType : 'text' ,
225+ } ;
226+
227+ const axiosRequestConfig = httpClient . convertHttpRequest ( request ) ;
228+ expect ( axiosRequestConfig ) . toMatchObject ( {
229+ url : 'http://apimatic.hopto.org:3000/test/upload' ,
230+ method : 'POST' ,
231+ headers : {
232+ authorization : 'Bearer token123' ,
233+ 'content-type' : new RegExp (
234+ '^multipart/form-data; boundary=--------------------------'
235+ ) ,
236+ } ,
237+ timeout : DEFAULT_TIMEOUT ,
238+ responseType : 'text' ,
239+ data : expect . any ( FormData ) ,
240+ } ) ;
241+ } ) ;
242+
141243 it ( 'converts request with http stream body(file stream) and http get method' , async ( ) => {
142244 const httpClient = new HttpClient ( AbortError ) ;
143245 const streamBody : HttpRequestStreamBody = {
@@ -312,6 +414,57 @@ describe('HTTP Client', () => {
312414 } ) ;
313415} ) ;
314416
417+ describe ( 'createFormDataOptions' , ( ) => {
418+ it ( 'should return headers with content type from formDataWrapper headers' , ( ) => {
419+ const formDataWrapper = createFormData (
420+ { key : 'value' } ,
421+ { 'content-type' : 'application/json' }
422+ ) ;
423+
424+ const result = createFormDataOptions ( formDataWrapper ?. headers || { } ) ;
425+
426+ expect ( result ) . toEqual ( {
427+ contentType : 'application/json' ,
428+ header : { } ,
429+ } ) ;
430+ } ) ;
431+
432+ it ( 'should return undefined content type when not provided in headers' , ( ) => {
433+ const formDataWrapper = createFormData ( { key : 'value' } , { } ) ;
434+
435+ const result = createFormDataOptions ( formDataWrapper ?. headers || { } ) ;
436+
437+ expect ( result ) . toEqual ( {
438+ contentType : undefined ,
439+ header : { } ,
440+ } ) ;
441+ } ) ;
442+
443+ it ( 'should handle formDataWrapper with no headers' , ( ) => {
444+ const formDataWrapper = createFormData ( { key : 'value' } ) ;
445+
446+ const result = createFormDataOptions ( formDataWrapper ?. headers || { } ) ;
447+
448+ expect ( result ) . toEqual ( {
449+ header : { } ,
450+ } ) ;
451+ } ) ;
452+
453+ it ( 'should handle case-insensitive content-type header lookup' , ( ) => {
454+ const formDataWrapper = createFormData (
455+ { test : 'data' } ,
456+ { 'Content-Type' : 'application/json; charset=utf-8' }
457+ ) ;
458+
459+ const result = createFormDataOptions ( formDataWrapper ?. headers || { } ) ;
460+
461+ expect ( result ) . toEqual ( {
462+ contentType : 'application/json; charset=utf-8' ,
463+ header : { } ,
464+ } ) ;
465+ } ) ;
466+ } ) ;
467+
315468describe ( 'test blob type' , ( ) => {
316469 test . each ( [
317470 [
0 commit comments