@@ -17,6 +17,9 @@ function createMockHttpExchange() {
1717 statusCode : 0 ,
1818 headers : { } as Record < string , string > ,
1919 } ) ;
20+ const originalResume = res . resume . bind ( res ) ;
21+ const resume = vi . fn ( ( ) => originalResume ( ) ) ;
22+ res . resume = resume as typeof res . resume ;
2023 const req = {
2124 on : ( event : string , handler : ( ...args : unknown [ ] ) => void ) => {
2225 if ( event === "error" ) {
@@ -27,18 +30,40 @@ function createMockHttpExchange() {
2730 end : ( ) => undefined ,
2831 destroy : ( ) => res . destroy ( ) ,
2932 } as const ;
30- return { req, res } ;
33+ return { req, res, resume } ;
3134}
3235
33- function mockRedirectExchange ( params : { location ?: string } ) {
34- const { req, res } = createMockHttpExchange ( ) ;
36+ function mockRedirectExchange ( params : { body ?: string ; location ?: string } ) {
37+ const { req, res, resume } = createMockHttpExchange ( ) ;
3538 res . statusCode = 302 ;
3639 res . headers = params . location ? { location : params . location } : { } ;
3740 return {
3841 req,
42+ resume,
43+ send ( cb : ( value : unknown ) => void ) {
44+ setImmediate ( ( ) => {
45+ cb ( res as unknown ) ;
46+ if ( params . body ) {
47+ res . write ( params . body ) ;
48+ }
49+ res . end ( ) ;
50+ } ) ;
51+ } ,
52+ } ;
53+ }
54+
55+ function mockHttpStatusExchange ( params : { body ?: string ; statusCode : number } ) {
56+ const { req, res, resume } = createMockHttpExchange ( ) ;
57+ res . statusCode = params . statusCode ;
58+ return {
59+ req,
60+ resume,
3961 send ( cb : ( value : unknown ) => void ) {
4062 setImmediate ( ( ) => {
4163 cb ( res as unknown ) ;
64+ if ( params . body ) {
65+ res . write ( params . body ) ;
66+ }
4267 res . end ( ) ;
4368 } ) ;
4469 } ,
@@ -209,21 +234,74 @@ describe("media store redirects", () => {
209234 expect ( getRequestHeaders ( 1 ) . get ( "authorization" ) ) . toBe ( "Bearer secret" ) ;
210235 } ) ;
211236
237+ it ( "drains ignored redirect response bodies before following redirects" , async ( ) => {
238+ let redirectResume : ReturnType < typeof vi . fn > | undefined ;
239+ let call = 0 ;
240+ mockRequest . mockImplementation ( ( _url , _opts , cb ) => {
241+ call += 1 ;
242+ if ( call === 1 ) {
243+ const exchange = mockRedirectExchange ( {
244+ body : "ignored redirect response body" ,
245+ location : "https://example.com/final" ,
246+ } ) ;
247+ redirectResume = exchange . resume ;
248+ exchange . send ( cb ) ;
249+ return exchange . req ;
250+ }
251+
252+ const exchange = mockSuccessfulTextExchange ( {
253+ text : "redirected" ,
254+ contentType : "text/plain" ,
255+ } ) ;
256+ exchange . send ( cb ) ;
257+ return exchange . req ;
258+ } ) ;
259+
260+ await saveMediaSource ( "https://example.com/start" ) ;
261+
262+ expect ( redirectResume ) . toHaveBeenCalledOnce ( ) ;
263+ } ) ;
264+
212265 it ( "fails when redirect response omits location header" , async ( ) => {
266+ let redirectResume : ReturnType < typeof vi . fn > | undefined ;
213267 mockRequest . mockImplementationOnce ( ( _url , _opts , cb ) => {
214- const exchange = mockRedirectExchange ( { } ) ;
268+ const exchange = mockRedirectExchange ( { body : "ignored redirect response body" } ) ;
269+ redirectResume = exchange . resume ;
215270 exchange . send ( cb ) ;
216271 return exchange . req ;
217272 } ) ;
218273 await expectRedirectSaveFailure ( "Redirect loop or missing Location header" ) ;
274+ expect ( redirectResume ) . toHaveBeenCalledOnce ( ) ;
219275 } ) ;
220276
221277 it ( "fails when redirect location is malformed" , async ( ) => {
278+ let redirectResume : ReturnType < typeof vi . fn > | undefined ;
222279 mockRequest . mockImplementationOnce ( ( _url , _opts , cb ) => {
223- const exchange = mockRedirectExchange ( { location : "http://[" } ) ;
280+ const exchange = mockRedirectExchange ( {
281+ body : "ignored redirect response body" ,
282+ location : "http://[" ,
283+ } ) ;
284+ redirectResume = exchange . resume ;
224285 exchange . send ( cb ) ;
225286 return exchange . req ;
226287 } ) ;
227288 await expectRedirectSaveFailure ( "Invalid redirect Location header" ) ;
289+ expect ( redirectResume ) . toHaveBeenCalledOnce ( ) ;
290+ } ) ;
291+
292+ it ( "drains ignored HTTP error response bodies before failing" , async ( ) => {
293+ let errorResume : ReturnType < typeof vi . fn > | undefined ;
294+ mockRequest . mockImplementationOnce ( ( _url , _opts , cb ) => {
295+ const exchange = mockHttpStatusExchange ( {
296+ body : "ignored error response body" ,
297+ statusCode : 500 ,
298+ } ) ;
299+ errorResume = exchange . resume ;
300+ exchange . send ( cb ) ;
301+ return exchange . req ;
302+ } ) ;
303+
304+ await expectRedirectSaveFailure ( "HTTP 500 downloading media" ) ;
305+ expect ( errorResume ) . toHaveBeenCalledOnce ( ) ;
228306 } ) ;
229307} ) ;
0 commit comments