@@ -83,14 +83,16 @@ func TestServeCacheHit(t *testing.T) {
8383 headers := cacheHeaders ([2 ]string {"Content-Type" , "text/plain" })
8484 w := httptest .NewRecorder ()
8585
86- handled , err := httputil .ServeCacheHit (w , headers , body , nil )
86+ handled , n , err := httputil .ServeCacheHit (w , headers , body , nil )
8787 assert .True (t , handled )
88+ assert .Equal (t , int64 (len ("payload" )), n )
8889 assert .NoError (t , err )
8990 assert .True (t , body .closed )
9091
9192 resp := w .Result ()
9293 defer resp .Body .Close ()
9394 assert .Equal (t , http .StatusOK , resp .StatusCode )
95+ assert .Equal (t , "bytes" , resp .Header .Get ("Accept-Ranges" ))
9496 assert .Equal (t , testETag , resp .Header .Get ("ETag" ))
9597 assert .Equal (t , "text/plain" , resp .Header .Get ("Content-Type" ))
9698 data , _ := io .ReadAll (resp .Body )
@@ -101,8 +103,9 @@ func TestServeCacheHit(t *testing.T) {
101103 headers := cacheHeaders ()
102104 w := httptest .NewRecorder ()
103105
104- handled , err := httputil .ServeCacheHit (w , headers , nil , client .ErrNotModified )
106+ handled , n , err := httputil .ServeCacheHit (w , headers , nil , client .ErrNotModified )
105107 assert .True (t , handled )
108+ assert .Equal (t , int64 (0 ), n )
106109 assert .NoError (t , err )
107110
108111 resp := w .Result ()
@@ -116,20 +119,96 @@ func TestServeCacheHit(t *testing.T) {
116119 t .Run ("PreconditionFailed" , func (t * testing.T ) {
117120 w := httptest .NewRecorder ()
118121
119- handled , err := httputil .ServeCacheHit (w , nil , nil , client .ErrPreconditionFailed )
122+ handled , n , err := httputil .ServeCacheHit (w , nil , nil , client .ErrPreconditionFailed )
120123 assert .True (t , handled )
124+ assert .Equal (t , int64 (0 ), n )
121125 assert .NoError (t , err )
122126
123127 resp := w .Result ()
124128 defer resp .Body .Close ()
125129 assert .Equal (t , http .StatusPreconditionFailed , resp .StatusCode )
126130 })
127131
132+ t .Run ("PartialContentWhenRanged" , func (t * testing.T ) {
133+ body := & trackingReader {Reader : strings .NewReader ("2345" )}
134+ headers := cacheHeaders ([2 ]string {"Content-Range" , "bytes 2-5/10" })
135+ w := httptest .NewRecorder ()
136+
137+ handled , n , err := httputil .ServeCacheHit (w , headers , body , nil )
138+ assert .True (t , handled )
139+ assert .Equal (t , int64 (4 ), n )
140+ assert .NoError (t , err )
141+ assert .True (t , body .closed )
142+
143+ resp := w .Result ()
144+ defer resp .Body .Close ()
145+ assert .Equal (t , http .StatusPartialContent , resp .StatusCode )
146+ assert .Equal (t , "bytes 2-5/10" , resp .Header .Get ("Content-Range" ))
147+ assert .Equal (t , "bytes" , resp .Header .Get ("Accept-Ranges" ))
148+ })
149+
150+ t .Run ("RangeNotSatisfiable" , func (t * testing.T ) {
151+ headers := cacheHeaders ([2 ]string {"Content-Range" , "bytes */10" })
152+ w := httptest .NewRecorder ()
153+
154+ handled , n , err := httputil .ServeCacheHit (w , headers , nil , client .ErrRangeNotSatisfiable )
155+ assert .True (t , handled )
156+ assert .Equal (t , int64 (0 ), n )
157+ assert .NoError (t , err )
158+
159+ resp := w .Result ()
160+ defer resp .Body .Close ()
161+ assert .Equal (t , http .StatusRequestedRangeNotSatisfiable , resp .StatusCode )
162+ assert .Equal (t , "bytes */10" , resp .Header .Get ("Content-Range" ))
163+ assert .Equal (t , "bytes" , resp .Header .Get ("Accept-Ranges" ))
164+ })
165+
166+ t .Run ("DecoratorRunsOnFullPartialAndUnsatisfiable" , func (t * testing.T ) {
167+ // The decorator must run before the status is written on the 200, 206 and
168+ // 416 paths, but not on the bodiless 304/412 paths.
169+ cases := []struct {
170+ name string
171+ headers http.Header
172+ body io.ReadCloser
173+ openErr error
174+ wantRun bool
175+ wantStatus int
176+ }{
177+ {name : "Full" , headers : cacheHeaders (), body : & trackingReader {Reader : strings .NewReader ("x" )}, wantRun : true , wantStatus : http .StatusOK },
178+ {name : "Partial" , headers : cacheHeaders ([2 ]string {"Content-Range" , "bytes 0-0/2" }), body : & trackingReader {Reader : strings .NewReader ("x" )}, wantRun : true , wantStatus : http .StatusPartialContent },
179+ {name : "Unsatisfiable" , headers : cacheHeaders ([2 ]string {"Content-Range" , "bytes */2" }), openErr : client .ErrRangeNotSatisfiable , wantRun : true , wantStatus : http .StatusRequestedRangeNotSatisfiable },
180+ {name : "NotModified" , headers : cacheHeaders (), openErr : client .ErrNotModified , wantRun : false , wantStatus : http .StatusNotModified },
181+ {name : "PreconditionFailed" , openErr : client .ErrPreconditionFailed , wantRun : false , wantStatus : http .StatusPreconditionFailed },
182+ }
183+ for _ , tc := range cases {
184+ t .Run (tc .name , func (t * testing.T ) {
185+ w := httptest .NewRecorder ()
186+ var ran bool
187+ decorate := func (rw http.ResponseWriter , _ http.Header ) {
188+ ran = true
189+ rw .Header ().Set ("X-Decorated" , "yes" )
190+ }
191+ handled , _ , err := httputil .ServeCacheHit (w , tc .headers , tc .body , tc .openErr , httputil .WithResponseDecorator (decorate ))
192+ assert .True (t , handled )
193+ assert .NoError (t , err )
194+ assert .Equal (t , tc .wantRun , ran )
195+
196+ resp := w .Result ()
197+ defer resp .Body .Close ()
198+ assert .Equal (t , tc .wantStatus , resp .StatusCode )
199+ if tc .wantRun {
200+ assert .Equal (t , "yes" , resp .Header .Get ("X-Decorated" ))
201+ }
202+ })
203+ }
204+ })
205+
128206 t .Run ("NotHandledForOtherError" , func (t * testing.T ) {
129207 w := httptest .NewRecorder ()
130208
131- handled , err := httputil .ServeCacheHit (w , nil , nil , os .ErrNotExist )
209+ handled , n , err := httputil .ServeCacheHit (w , nil , nil , os .ErrNotExist )
132210 assert .False (t , handled )
211+ assert .Equal (t , int64 (0 ), n )
133212 assert .NoError (t , err )
134213 assert .Equal (t , http .StatusOK , w .Result ().StatusCode ) // response untouched
135214 })
0 commit comments