@@ -228,6 +228,8 @@ def _make_handler(
228228 path = "/hmac/300x200/42/ff/1a" ,
229229 cc_auth = "private, max-age=86400" ,
230230 cc_public = "" ,
231+ cc_error = "public, max-age=10" ,
232+ status = 200 ,
231233 ):
232234 from zodb_pgjsonb_thumborblobloader .auth_handler import _auth_cache
233235 from zodb_pgjsonb_thumborblobloader .auth_handler import AuthImagingHandler
@@ -239,13 +241,17 @@ def _make_handler(
239241 handler .request .path = path
240242 handler .request .headers = {"Cookie" : "auth=abc123" }
241243 handler .context = MagicMock ()
242- handler . context . config . get = lambda key , default = None : {
244+ config = {
243245 "PGTHUMBOR_PLONE_AUTH_URL" : "http://plone:8080/Plone" ,
244246 "PGTHUMBOR_AUTH_CACHE_TTL" : 60 ,
245247 "PGTHUMBOR_CACHE_CONTROL_AUTHENTICATED" : cc_auth ,
246248 "PGTHUMBOR_CACHE_CONTROL_PUBLIC" : cc_public ,
247- }.get (key , default )
249+ }
250+ if cc_error is not None :
251+ config ["PGTHUMBOR_CACHE_CONTROL_ERROR" ] = cc_error
252+ handler .context .config .get = lambda key , default = None : config .get (key , default )
248253 handler ._headers = {}
254+ handler .get_status = lambda : status
249255 return handler
250256
251257 def test_authenticated_request_sets_private (self ):
@@ -322,6 +328,78 @@ def test_finish_skips_header_when_empty_override(self):
322328
323329 assert "Cache-Control" not in headers_set
324330
331+ def test_finish_error_status_gets_microcache (self ):
332+ """4xx response gets a short microcache, not the long-TTL override."""
333+ handler = self ._make_handler (status = 400 )
334+ handler ._cache_control_override = "public, max-age=31536000, immutable"
335+ headers_set = {}
336+ handler .set_header = lambda k , v : headers_set .update ({k : v })
337+
338+ with patch .object (
339+ type (handler ).__mro__ [1 ], "finish" , lambda self , * a , ** kw : None
340+ ):
341+ handler .finish ()
342+
343+ # The long-TTL override MUST NOT leak into the error response.
344+ assert headers_set ["Cache-Control" ] == "public, max-age=10"
345+
346+ def test_finish_error_without_override_still_gets_microcache (self ):
347+ """Error responses get a microcache even if no override was set."""
348+ handler = self ._make_handler (status = 404 )
349+ # No _cache_control_override attribute set at all
350+ headers_set = {}
351+ handler .set_header = lambda k , v : headers_set .update ({k : v })
352+
353+ with patch .object (
354+ type (handler ).__mro__ [1 ], "finish" , lambda self , * a , ** kw : None
355+ ):
356+ handler .finish ()
357+
358+ assert headers_set ["Cache-Control" ] == "public, max-age=10"
359+
360+ def test_finish_5xx_also_microcached (self ):
361+ """5xx responses get the same microcache treatment as 4xx."""
362+ handler = self ._make_handler (status = 503 )
363+ handler ._cache_control_override = "public, max-age=31536000, immutable"
364+ headers_set = {}
365+ handler .set_header = lambda k , v : headers_set .update ({k : v })
366+
367+ with patch .object (
368+ type (handler ).__mro__ [1 ], "finish" , lambda self , * a , ** kw : None
369+ ):
370+ handler .finish ()
371+
372+ assert headers_set ["Cache-Control" ] == "public, max-age=10"
373+
374+ def test_finish_error_microcache_configurable (self ):
375+ """PGTHUMBOR_CACHE_CONTROL_ERROR overrides the default microcache."""
376+ handler = self ._make_handler (
377+ status = 400 , cc_error = "private, max-age=30, must-revalidate"
378+ )
379+ headers_set = {}
380+ handler .set_header = lambda k , v : headers_set .update ({k : v })
381+
382+ with patch .object (
383+ type (handler ).__mro__ [1 ], "finish" , lambda self , * a , ** kw : None
384+ ):
385+ handler .finish ()
386+
387+ assert headers_set ["Cache-Control" ] == "private, max-age=30, must-revalidate"
388+
389+ def test_finish_3xx_leaves_cache_control_alone (self ):
390+ """Redirects: no long-TTL override, no microcache — Thumbor default."""
391+ handler = self ._make_handler (status = 304 )
392+ handler ._cache_control_override = "public, max-age=31536000, immutable"
393+ headers_set = {}
394+ handler .set_header = lambda k , v : headers_set .update ({k : v })
395+
396+ with patch .object (
397+ type (handler ).__mro__ [1 ], "finish" , lambda self , * a , ** kw : None
398+ ):
399+ handler .finish ()
400+
401+ assert "Cache-Control" not in headers_set
402+
325403
326404class TestGetHandlers :
327405 """Test get_handlers() returns correct URL pattern."""
0 commit comments