@@ -18,6 +18,8 @@ package engines
1818
1919import (
2020 "bytes"
21+ "errors"
22+ "io"
2123 "net/http"
2224 "sync"
2325 "testing"
@@ -338,6 +340,37 @@ func TestPrepareRevalidationRequestNoRange(t *testing.T) {
338340 }
339341}
340342
343+ func TestPrepareRevalidationRequestDefaultRangeInclusiveEnd (t * testing.T ) {
344+ logger .SetLogger (logging .ConsoleLogger (level .Error ))
345+ r , _ := http .NewRequest (http .MethodGet , "http://127.0.0.1/" , nil )
346+
347+ o := & bo.Options {DearticulateUpstreamRanges : true }
348+ r = request .SetResources (r , request .NewResources (o , nil , nil , nil , nil , nil ))
349+
350+ pr := proxyRequest {
351+ Request : r ,
352+ upstreamRequest : r ,
353+ cachingPolicy : & CachingPolicy {},
354+ upstreamResponse : & http.Response {},
355+ cacheDocument : & HTTPDocument {
356+ ContentLength : 100 ,
357+ Ranges : byterange.Ranges {byterange.Range {Start : 30 , End : 40 }},
358+ },
359+ cacheStatus : status .LookupStatusPartialHit ,
360+ // no wantedRanges — triggers default range using ContentLength
361+ }
362+ pr .prepareRevalidationRequest ()
363+
364+ // with no wantedRanges, the default wanted range is 0 to ContentLength-1 (99)
365+ // revalRanges = neededRanges.CalculateDeltas(wr, cl) with nil neededRanges gives wr back
366+ // that's 1 range so rh = revalRanges.String() = "bytes=0-99"
367+ v := pr .revalidationRequest .Header .Get (headers .NameRange )
368+ expected := "bytes=0-99"
369+ if v != expected {
370+ t .Errorf ("expected %s got %s" , expected , v )
371+ }
372+ }
373+
341374func TestPrepareUpstreamRequests (t * testing.T ) {
342375 logger .SetLogger (logging .ConsoleLogger (level .Error ))
343376 r , _ := http .NewRequest (http .MethodGet , "http://127.0.0.1/" , nil )
@@ -400,3 +433,87 @@ func TestReconstituteResponses(t *testing.T) {
400433 t .Errorf ("expected %d got %d" , 0 , len (pr .originRequests ))
401434 }
402435}
436+
437+ // errReader is an io.ReadCloser that always returns an error
438+ type errReader struct { err error }
439+
440+ func (e * errReader ) Read ([]byte ) (int , error ) { return 0 , e .err }
441+ func (e * errReader ) Close () error { return nil }
442+
443+ func TestReconstituteResponsesReadError (t * testing.T ) {
444+ logger .SetLogger (logging .ConsoleLogger (level .Error ))
445+
446+ r1 , _ := http .NewRequest (http .MethodGet , "http://127.0.0.1/" , nil )
447+ r2 , _ := http .NewRequest (http .MethodGet , "http://127.0.0.1/" , nil )
448+ baseReq , _ := http .NewRequest (http .MethodGet , "http://127.0.0.1/" , nil )
449+ baseReq = request .SetResources (baseReq , request .NewResources (& bo.Options {}, nil , nil , nil , nil , nil ))
450+
451+ readErr := errors .New ("simulated read error" )
452+
453+ pr := & proxyRequest {
454+ mapLock : & sync.Mutex {},
455+ cachingPolicy : & CachingPolicy {},
456+ originRequests : []* http.Request {r1 , r2 },
457+ originResponses : []* http.Response {
458+ {
459+ StatusCode : http .StatusPartialContent ,
460+ Header : http.Header {headers .NameContentRange : []string {"bytes 0-3/10" }},
461+ Body : & errReader {err : readErr },
462+ },
463+ {
464+ StatusCode : http .StatusPartialContent ,
465+ Header : http.Header {headers .NameContentRange : []string {"bytes 4-9/10" }},
466+ Body : io .NopCloser (bytes .NewReader ([]byte ("456789" ))),
467+ },
468+ },
469+ cacheDocument : & HTTPDocument {ContentLength : 10 },
470+ }
471+ pr .Request = baseReq
472+
473+ // should not panic; the error reader's goroutine logs and returns
474+ pr .reconstituteResponses ()
475+
476+ // the second response should still have been processed
477+ if pr .upstreamResponse == nil {
478+ t .Error ("expected upstream response to be set" )
479+ }
480+ }
481+
482+ func TestReconstituteResponsesRevalidationReadError (t * testing.T ) {
483+ logger .SetLogger (logging .ConsoleLogger (level .Error ))
484+
485+ r1 , _ := http .NewRequest (http .MethodGet , "http://127.0.0.1/" , nil )
486+ reval , _ := http .NewRequest (http .MethodGet , "http://127.0.0.1/" , nil )
487+ baseReq , _ := http .NewRequest (http .MethodGet , "http://127.0.0.1/" , nil )
488+ baseReq = request .SetResources (baseReq , request .NewResources (& bo.Options {}, nil , nil , nil , nil , nil ))
489+
490+ readErr := errors .New ("simulated revalidation read error" )
491+
492+ pr := & proxyRequest {
493+ mapLock : & sync.Mutex {},
494+ cachingPolicy : & CachingPolicy {},
495+ revalidationRequest : reval ,
496+ revalidationResponse : & http.Response {
497+ StatusCode : http .StatusPartialContent ,
498+ Header : http.Header {headers .NameContentRange : []string {"bytes 0-4/10" }},
499+ Body : & errReader {err : readErr },
500+ },
501+ originRequests : []* http.Request {r1 },
502+ originResponses : []* http.Response {
503+ {
504+ StatusCode : http .StatusPartialContent ,
505+ Header : http.Header {headers .NameContentRange : []string {"bytes 5-9/10" }},
506+ Body : io .NopCloser (bytes .NewReader ([]byte ("56789" ))),
507+ },
508+ },
509+ cacheDocument : & HTTPDocument {ContentLength : 10 },
510+ }
511+ pr .Request = baseReq
512+
513+ // should not panic; the revalidation error reader's goroutine logs and returns
514+ pr .reconstituteResponses ()
515+
516+ if pr .upstreamResponse == nil {
517+ t .Error ("expected upstream response to be set" )
518+ }
519+ }
0 commit comments