Skip to content

Commit 47f3b46

Browse files
committed
test: add regression tests for default backend behavior
Adds TestDefaultBackendBehavior class with 6 tests verifying that: - @cache() without backend=None DOES call get_backend_provider() - @cache.minimal/dev/test() presets behave correctly - DecoratorConfig default (backend=None) != explicit backend=None - Explicit backend instances bypass provider lookup These regression tests protect against the bug where checking `config.backend is None` would treat ALL decorators as L1-only (since DecoratorConfig.backend defaults to None).
1 parent ac9313a commit 47f3b46

File tree

1 file changed

+163
-1
lines changed

1 file changed

+163
-1
lines changed

tests/unit/test_l1_only_mode.py

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from __future__ import annotations
1616

1717
import time
18-
from unittest.mock import patch
18+
from unittest.mock import MagicMock, patch
1919

2020

2121
class TestL1OnlyModeBug:
@@ -363,6 +363,168 @@ def changing_func(x: int) -> int:
363363
assert call_count == 2, "Should have re-executed after invalidation"
364364

365365

366+
class TestDefaultBackendBehavior:
367+
"""
368+
CRITICAL: Tests that @cache() WITHOUT backend=None DOES attempt provider lookup.
369+
370+
This is the regression test for the bug where we accidentally made ALL decorators
371+
L1-only by checking `config.backend is None` (which is the default).
372+
"""
373+
374+
def test_default_cache_should_call_backend_provider(self):
375+
"""
376+
@cache() without backend=None SHOULD call get_backend_provider().
377+
378+
This is the INVERSE of L1-only mode - verifies we didn't break default behavior.
379+
"""
380+
from cachekit.decorators import cache
381+
382+
with patch("cachekit.decorators.wrapper.get_backend_provider") as mock_provider:
383+
# Make provider return a mock backend
384+
mock_backend = MagicMock()
385+
mock_provider.return_value.get_backend.return_value = mock_backend
386+
387+
@cache(ttl=60) # NO backend=None - should use provider
388+
def default_func() -> str:
389+
return "result"
390+
391+
# Call the function - this should trigger provider lookup
392+
default_func()
393+
394+
# Backend provider SHOULD have been called
395+
mock_provider.return_value.get_backend.assert_called()
396+
397+
def test_cache_minimal_without_backend_none_should_call_provider(self):
398+
"""
399+
@cache.minimal() without backend=None SHOULD call get_backend_provider().
400+
"""
401+
from cachekit.decorators import cache
402+
403+
with patch("cachekit.decorators.wrapper.get_backend_provider") as mock_provider:
404+
mock_backend = MagicMock()
405+
mock_provider.return_value.get_backend.return_value = mock_backend
406+
407+
@cache.minimal(ttl=60) # NO backend=None
408+
def minimal_func() -> str:
409+
return "result"
410+
411+
minimal_func()
412+
413+
# Backend provider SHOULD have been called
414+
mock_provider.return_value.get_backend.assert_called()
415+
416+
def test_decorator_config_default_backend_should_call_provider(self):
417+
"""
418+
DecoratorConfig() with default backend SHOULD call get_backend_provider().
419+
420+
This specifically tests that DecoratorConfig.backend defaulting to None
421+
does NOT trigger L1-only mode (the bug we fixed).
422+
"""
423+
from cachekit.config import DecoratorConfig
424+
from cachekit.decorators import cache
425+
426+
with patch("cachekit.decorators.wrapper.get_backend_provider") as mock_provider:
427+
mock_backend = MagicMock()
428+
mock_provider.return_value.get_backend.return_value = mock_backend
429+
430+
# DecoratorConfig() has backend=None by DEFAULT - should NOT be L1-only
431+
@cache(config=DecoratorConfig(ttl=60))
432+
def config_func() -> str:
433+
return "result"
434+
435+
config_func()
436+
437+
# Backend provider SHOULD have been called (default != explicit None)
438+
mock_provider.return_value.get_backend.assert_called()
439+
440+
def test_explicit_backend_instance_should_be_used(self):
441+
"""
442+
@cache(backend=explicit_backend) should use that backend, not provider.
443+
"""
444+
from cachekit.decorators import cache
445+
446+
with patch("cachekit.decorators.wrapper.get_backend_provider") as mock_provider:
447+
mock_provider.return_value.get_backend.side_effect = RuntimeError("Should not be called!")
448+
449+
# Create an explicit mock backend
450+
explicit_backend = MagicMock()
451+
explicit_backend.get.return_value = None # Cache miss
452+
453+
@cache(backend=explicit_backend, ttl=60)
454+
def explicit_func() -> str:
455+
return "result"
456+
457+
explicit_func()
458+
459+
# Provider should NOT be called - explicit backend provided
460+
mock_provider.return_value.get_backend.assert_not_called()
461+
462+
def test_dev_and_test_presets_without_backend_none(self):
463+
"""
464+
@cache.dev() and @cache.test() without backend=None SHOULD call provider.
465+
466+
Completes coverage for all intent presets.
467+
"""
468+
from cachekit.decorators import cache
469+
470+
with patch("cachekit.decorators.wrapper.get_backend_provider") as mock_provider:
471+
mock_backend = MagicMock()
472+
mock_provider.return_value.get_backend.return_value = mock_backend
473+
474+
@cache.dev(ttl=60)
475+
def dev_func() -> str:
476+
return "dev"
477+
478+
@cache.test(ttl=60)
479+
def test_func() -> str:
480+
return "test"
481+
482+
dev_func()
483+
test_func()
484+
485+
# Both should have triggered provider lookup
486+
assert mock_provider.return_value.get_backend.call_count >= 2
487+
488+
def test_dev_and_test_presets_with_backend_none(self):
489+
"""
490+
@cache.dev(backend=None) and @cache.test(backend=None) should be L1-only.
491+
492+
Completes L1-only coverage for all intent presets.
493+
"""
494+
from cachekit.decorators import cache
495+
496+
with patch("cachekit.decorators.wrapper.get_backend_provider") as mock_provider:
497+
mock_provider.return_value.get_backend.side_effect = RuntimeError("Should not be called!")
498+
499+
dev_count = 0
500+
501+
@cache.dev(backend=None)
502+
def dev_func() -> str:
503+
nonlocal dev_count
504+
dev_count += 1
505+
return "dev"
506+
507+
test_count = 0
508+
509+
@cache.test(backend=None)
510+
def test_func() -> str:
511+
nonlocal test_count
512+
test_count += 1
513+
return "test"
514+
515+
# Execute twice each - should hit L1 cache
516+
dev_func()
517+
dev_func()
518+
test_func()
519+
test_func()
520+
521+
assert dev_count == 1, f"@cache.dev L1 miss - called {dev_count} times"
522+
assert test_count == 1, f"@cache.test L1 miss - called {test_count} times"
523+
524+
# Provider should NEVER be called
525+
mock_provider.return_value.get_backend.assert_not_called()
526+
527+
366528
class TestL1OnlyModeNoRedisWarnings:
367529
"""
368530
Verify that L1-only mode doesn't produce Redis connection warnings.

0 commit comments

Comments
 (0)