@@ -134,6 +134,93 @@ describe('createSsrfGuardedFetchWithDispatcher (undici.request backed)', () => {
134134 expect ( await response . text ( ) ) . toBe ( 'final-body' )
135135 } )
136136
137+ it . each ( [
138+ [ 307 , 'POST' ] ,
139+ [ 308 , 'POST' ] ,
140+ [ 301 , 'PUT' ] ,
141+ [ 302 , 'PUT' ] ,
142+ ] as const ) ( 'replays a Request body through same-origin %s redirects' , async ( status , method ) => {
143+ const payloads : string [ ] = [ ]
144+ mockUndiciRequest . mockImplementation ( async ( _url , options : { body : Buffer | Readable } ) => {
145+ const chunks : Buffer [ ] = [ ]
146+ for await ( const chunk of options . body instanceof Readable ? options . body : [ options . body ] ) {
147+ chunks . push ( Buffer . from ( chunk ) )
148+ }
149+ payloads . push ( Buffer . concat ( chunks ) . toString ( ) )
150+ return payloads . length < 3
151+ ? undiciReply ( status , { location : `/hop-${ payloads . length } ` } , byteStream ( '' ) )
152+ : undiciReply ( 200 , { } , byteStream ( 'done' ) )
153+ } )
154+ const transport = createSsrfGuardedFetchWithDispatcher ( { profile : 'configuredEndpoint' } )
155+ try {
156+ const response = await transport . fetch (
157+ new Request ( 'https://api.example.com/start' , {
158+ method,
159+ headers : { 'content-type' : 'application/json' } ,
160+ body : '{"payload":"replay"}' ,
161+ } )
162+ )
163+ expect ( await response . text ( ) ) . toBe ( 'done' )
164+ expect ( payloads ) . toEqual ( Array ( 3 ) . fill ( '{"payload":"replay"}' ) )
165+ expect ( response . redirected ) . toBe ( true )
166+ expect ( mockUndiciRequest . mock . calls . map ( ( [ , options ] ) => options . method ) ) . toEqual (
167+ Array ( 3 ) . fill ( method )
168+ )
169+ } finally {
170+ await transport . dispatcher . destroy ( )
171+ }
172+ } )
173+
174+ it . each ( [ 'manual' , 'error' ] as const ) (
175+ 'keeps Request bodies streaming in %s mode' ,
176+ async ( redirect ) => {
177+ const request = new Request ( 'https://api.example.com/upload' , {
178+ method : 'POST' ,
179+ redirect,
180+ body : 'payload' ,
181+ } )
182+ const transport = createSsrfGuardedFetchWithDispatcher ( { profile : 'configuredEndpoint' } )
183+ mockUndiciRequest . mockImplementationOnce ( async ( _url , options : { body : Readable } ) => {
184+ expect ( options . body ) . toBeInstanceOf ( Readable )
185+ const chunks : Buffer [ ] = [ ]
186+ for await ( const chunk of options . body ) chunks . push ( Buffer . from ( chunk ) )
187+ expect ( Buffer . concat ( chunks ) . toString ( ) ) . toBe ( 'payload' )
188+ return undiciReply ( 200 , { } , byteStream ( 'done' ) )
189+ } )
190+ try {
191+ expect ( await ( await transport . fetch ( request ) ) . text ( ) ) . toBe ( 'done' )
192+ } finally {
193+ await transport . dispatcher . destroy ( )
194+ }
195+ }
196+ )
197+
198+ it ( 'does not read the Request body when init supplies a replacement' , async ( ) => {
199+ const request = new Request ( 'https://api.example.com/upload' , {
200+ method : 'POST' ,
201+ body : 'original' ,
202+ } )
203+ const clone = vi . spyOn ( request , 'clone' )
204+ const bodyOverride = vi . fn ( ) . mockReturnValueOnce ( 'replacement' ) . mockReturnValue ( undefined )
205+ mockUndiciRequest . mockResolvedValueOnce ( undiciReply ( 200 , { } , byteStream ( 'done' ) ) )
206+ const transport = createSsrfGuardedFetchWithDispatcher ( { profile : 'configuredEndpoint' } )
207+ try {
208+ const response = await transport . fetch ( request , {
209+ get body ( ) {
210+ return bodyOverride ( )
211+ } ,
212+ } )
213+ expect ( await response . text ( ) ) . toBe ( 'done' )
214+ expect ( mockUndiciRequest . mock . calls [ 0 ] [ 1 ] . body ) . toBe ( 'replacement' )
215+ expect ( request . bodyUsed ) . toBe ( false )
216+ expect ( clone ) . not . toHaveBeenCalled ( )
217+ expect ( bodyOverride ) . toHaveBeenCalledTimes ( 1 )
218+ } finally {
219+ await request . body ?. cancel ( )
220+ await transport . dispatcher . destroy ( )
221+ }
222+ } )
223+
137224 it ( 'supports buffered reads (.json()) through the constructed body' , async ( ) => {
138225 mockUndiciRequest . mockResolvedValueOnce (
139226 undiciReply (
0 commit comments