1+ import asyncio
2+ import pytest
3+ from unittest import mock
4+
5+ from async_substrate_interface .utils .cache import CachedFetcher
6+
7+
8+ @pytest .mark .asyncio
9+ async def test_cached_fetcher_fetches_and_caches ():
10+ """Tests that CachedFetcher correctly fetches and caches results."""
11+ # Setup
12+ mock_method = mock .AsyncMock (side_effect = lambda x : f"result_{ x } " )
13+ fetcher = CachedFetcher (max_size = 2 , method = mock_method )
14+
15+ # First call should trigger the method
16+ result1 = await fetcher .execute ("key1" )
17+ assert result1 == "result_key1"
18+ mock_method .assert_awaited_once_with ("key1" )
19+
20+ # Second call with the same key should use the cache
21+ result2 = await fetcher .execute ("key1" )
22+ assert result2 == "result_key1"
23+ # Ensure the method was NOT called again
24+ assert mock_method .await_count == 1
25+
26+ # Third call with a new key triggers a method call
27+ result3 = await fetcher .execute ("key2" )
28+ assert result3 == "result_key2"
29+ assert mock_method .await_count == 2
30+
31+ @pytest .mark .asyncio
32+ async def test_cached_fetcher_handles_inflight_requests ():
33+ """Tests that CachedFetcher waits for in-flight results instead of re-fetching."""
34+ # Create an event to control when the mock returns
35+ event = asyncio .Event ()
36+
37+ async def slow_method (x ):
38+ await event .wait ()
39+ return f"slow_result_{ x } "
40+
41+ fetcher = CachedFetcher (max_size = 2 , method = slow_method )
42+
43+ # Start first request
44+ task1 = asyncio .create_task (fetcher .execute ("key1" ))
45+ await asyncio .sleep (0.1 ) # Let the task start and be inflight
46+
47+ # Second request for the same key while the first is in-flight
48+ task2 = asyncio .create_task (fetcher .execute ("key1" ))
49+ await asyncio .sleep (0.1 )
50+
51+ # Release the inflight request
52+ event .set ()
53+ result1 , result2 = await asyncio .gather (task1 , task2 )
54+ assert result1 == result2 == "slow_result_key1"
55+
56+ @pytest .mark .asyncio
57+ async def test_cached_fetcher_propagates_errors ():
58+ """Tests that CachedFetcher correctly propagates errors."""
59+ async def error_method (x ):
60+ raise ValueError ("Boom!" )
61+
62+ fetcher = CachedFetcher (max_size = 2 , method = error_method )
63+
64+ with pytest .raises (ValueError , match = "Boom!" ):
65+ await fetcher .execute ("key1" )
66+
67+ @pytest .mark .asyncio
68+ async def test_cached_fetcher_eviction ():
69+ """Tests that LRU eviction works in CachedFetcher."""
70+ mock_method = mock .AsyncMock (side_effect = lambda x : f"val_{ x } " )
71+ fetcher = CachedFetcher (max_size = 2 , method = mock_method )
72+
73+ # Fill cache
74+ await fetcher .execute ("key1" )
75+ await fetcher .execute ("key2" )
76+ assert list (fetcher ._cache .cache .keys ()) == list (fetcher ._cache .cache .keys ())
77+
78+ # Insert a new key to trigger eviction
79+ await fetcher .execute ("key3" )
80+ # key1 should be evicted
81+ assert "key1" not in fetcher ._cache .cache
82+ assert "key2" in fetcher ._cache .cache
83+ assert "key3" in fetcher ._cache .cache
0 commit comments