11import { getTestClient } from '../../mock-builders/mock' ;
22import { NativeHandlers } from '../../native' ;
3- import { installNativeMultipartInterceptor } from '../installNativeMultipartInterceptor' ;
3+ import {
4+ installNativeMultipartAdapter ,
5+ wrapAxiosAdapterWithNativeMultipart ,
6+ } from '../installNativeMultipartAdapter' ;
47
5- describe ( 'installNativeMultipartInterceptor ' , ( ) => {
8+ describe ( 'installNativeMultipartAdapter ' , ( ) => {
69 const originalMultipartUpload = NativeHandlers . multipartUpload ;
710
811 beforeEach ( ( ) => {
@@ -14,13 +17,18 @@ describe('installNativeMultipartInterceptor', () => {
1417 } ) ;
1518 } ) ;
1619
20+ const preserveRequestData = ( client : ReturnType < typeof getTestClient > ) => {
21+ client . axiosInstance . defaults . transformRequest = [ ( data ) => data ] ;
22+ } ;
23+
1724 afterEach ( ( ) => {
1825 NativeHandlers . multipartUpload = originalMultipartUpload ;
1926 jest . clearAllMocks ( ) ;
2027 } ) ;
2128
2229 it ( 'routes multipart requests through the native handler' , async ( ) => {
2330 const client = getTestClient ( ) ;
31+ preserveRequestData ( client ) ;
2432 const defaultAdapter = jest . fn ( ) . mockResolvedValue ( {
2533 config : { } ,
2634 data : 'default' ,
@@ -31,7 +39,7 @@ describe('installNativeMultipartInterceptor', () => {
3139
3240 client . axiosInstance . defaults . adapter = defaultAdapter ;
3341
34- const dispose = installNativeMultipartInterceptor ( client ) ;
42+ installNativeMultipartAdapter ( client ) ;
3543 const formData = {
3644 _parts : [
3745 [
@@ -82,12 +90,11 @@ describe('installNativeMultipartInterceptor', () => {
8290 } ) ,
8391 ) ;
8492 expect ( response . status ) . toBe ( 200 ) ;
85-
86- dispose ( ) ;
8793 } ) ;
8894
89- it ( 'leaves non-multipart requests on the default adapter' , async ( ) => {
95+ it ( 'leaves non-multipart requests on the fallback adapter' , async ( ) => {
9096 const client = getTestClient ( ) ;
97+ preserveRequestData ( client ) ;
9198 const defaultAdapter = jest . fn ( ) . mockResolvedValue ( {
9299 config : { } ,
93100 data : 'default' ,
@@ -98,14 +105,12 @@ describe('installNativeMultipartInterceptor', () => {
98105
99106 client . axiosInstance . defaults . adapter = defaultAdapter ;
100107
101- const dispose = installNativeMultipartInterceptor ( client ) ;
108+ installNativeMultipartAdapter ( client ) ;
102109
103110 await client . axiosInstance . post ( '/messages' , { text : 'hello' } ) ;
104111
105112 expect ( defaultAdapter ) . toHaveBeenCalled ( ) ;
106113 expect ( NativeHandlers . multipartUpload ) . not . toHaveBeenCalled ( ) ;
107-
108- dispose ( ) ;
109114 } ) ;
110115
111116 it ( 'forwards native upload progress to axios upload progress callbacks' , async ( ) => {
@@ -124,7 +129,8 @@ describe('installNativeMultipartInterceptor', () => {
124129 } ) ;
125130
126131 const client = getTestClient ( ) ;
127- const dispose = installNativeMultipartInterceptor ( client ) ;
132+ preserveRequestData ( client ) ;
133+ installNativeMultipartAdapter ( client ) ;
128134 const onUploadProgress = jest . fn ( ) ;
129135 const uploadProgress = jest . fn ( ) ;
130136 const formData = {
@@ -173,12 +179,11 @@ describe('installNativeMultipartInterceptor', () => {
173179 } ,
174180 } ) ,
175181 ) ;
176-
177- dispose ( ) ;
178182 } ) ;
179183
180- it ( 'removes the interceptor on dispose ' , async ( ) => {
184+ it ( 'uses the final config after user request interceptors run ' , async ( ) => {
181185 const client = getTestClient ( ) ;
186+ preserveRequestData ( client ) ;
182187 const defaultAdapter = jest . fn ( ) . mockResolvedValue ( {
183188 config : { } ,
184189 data : 'default' ,
@@ -189,9 +194,65 @@ describe('installNativeMultipartInterceptor', () => {
189194
190195 client . axiosInstance . defaults . adapter = defaultAdapter ;
191196
192- const dispose = installNativeMultipartInterceptor ( client ) ;
197+ const interceptorId = client . axiosInstance . interceptors . request . use ( ( config ) => ( {
198+ ...config ,
199+ headers : {
200+ ...config . headers ,
201+ 'X-CDN-Route' : 'custom-cdn' ,
202+ } ,
203+ url : '/uploads/file' ,
204+ } ) ) ;
193205
194- dispose ( ) ;
206+ installNativeMultipartAdapter ( client ) ;
207+ const formData = {
208+ _parts : [
209+ [
210+ 'file' ,
211+ {
212+ name : 'test.jpg' ,
213+ type : 'image/jpeg' ,
214+ uri : 'file:///tmp/test.jpg' ,
215+ } ,
216+ ] ,
217+ ] ,
218+ } ;
219+
220+ await client . axiosInstance . post ( '/uploads/image' , formData , {
221+ headers : {
222+ Authorization : 'token' ,
223+ } ,
224+ } ) ;
225+
226+ expect ( NativeHandlers . multipartUpload ) . toHaveBeenCalledWith (
227+ expect . objectContaining ( {
228+ headers : expect . objectContaining ( {
229+ Authorization : 'token' ,
230+ 'X-CDN-Route' : 'custom-cdn' ,
231+ } ) ,
232+ url : expect . stringContaining ( '/uploads/file' ) ,
233+ } ) ,
234+ ) ;
235+ expect ( defaultAdapter ) . not . toHaveBeenCalled ( ) ;
236+
237+ client . axiosInstance . interceptors . request . eject ( interceptorId ) ;
238+ } ) ;
239+
240+ it ( 'installs only once per client' , async ( ) => {
241+ const client = getTestClient ( ) ;
242+ preserveRequestData ( client ) ;
243+ const defaultAdapter = jest . fn ( ) . mockResolvedValue ( {
244+ config : { } ,
245+ data : 'default' ,
246+ headers : { } ,
247+ status : 200 ,
248+ statusText : 'OK' ,
249+ } ) ;
250+
251+ client . axiosInstance . defaults . adapter = defaultAdapter ;
252+
253+ installNativeMultipartAdapter ( client ) ;
254+ const installedAdapter = client . axiosInstance . defaults . adapter ;
255+ installNativeMultipartAdapter ( client ) ;
195256
196257 const formData = {
197258 _parts : [
@@ -208,7 +269,47 @@ describe('installNativeMultipartInterceptor', () => {
208269
209270 await client . axiosInstance . post ( '/uploads/image' , formData ) ;
210271
211- expect ( defaultAdapter ) . toHaveBeenCalled ( ) ;
212- expect ( NativeHandlers . multipartUpload ) . not . toHaveBeenCalled ( ) ;
272+ expect ( client . axiosInstance . defaults . adapter ) . toBe ( installedAdapter ) ;
273+ expect ( defaultAdapter ) . not . toHaveBeenCalled ( ) ;
274+ expect ( NativeHandlers . multipartUpload ) . toHaveBeenCalled ( ) ;
275+ } ) ;
276+
277+ it ( 'composes explicitly with a custom adapter' , async ( ) => {
278+ const client = getTestClient ( ) ;
279+ preserveRequestData ( client ) ;
280+ const customAdapter = jest . fn ( ) . mockResolvedValue ( {
281+ config : { } ,
282+ data : 'custom' ,
283+ headers : { } ,
284+ status : 200 ,
285+ statusText : 'OK' ,
286+ } ) ;
287+
288+ client . axiosInstance . defaults . adapter = wrapAxiosAdapterWithNativeMultipart (
289+ client ,
290+ customAdapter ,
291+ ) ;
292+
293+ const multipartFormData = {
294+ _parts : [
295+ [
296+ 'file' ,
297+ {
298+ name : 'test.jpg' ,
299+ type : 'image/jpeg' ,
300+ uri : 'file:///tmp/test.jpg' ,
301+ } ,
302+ ] ,
303+ ] ,
304+ } ;
305+
306+ await client . axiosInstance . post ( '/uploads/image' , multipartFormData ) ;
307+
308+ expect ( NativeHandlers . multipartUpload ) . toHaveBeenCalled ( ) ;
309+ expect ( customAdapter ) . not . toHaveBeenCalled ( ) ;
310+
311+ await client . axiosInstance . post ( '/messages' , { text : 'hello' } ) ;
312+
313+ expect ( customAdapter ) . toHaveBeenCalled ( ) ;
213314 } ) ;
214315} ) ;
0 commit comments