Skip to main content
Glama
test_cache.py9.13 kB
"""Test caching infrastructure for audio file metadata.""" from collections.abc import Generator from pathlib import Path from unittest.mock import AsyncMock import pytest from mcp_server_whisper.infrastructure.cache import ( clear_global_cache, get_cached_audio_file_support, get_global_cache_info, ) from mcp_server_whisper.models import FilePathSupportParams class TestGlobalCache: """Test suite for global audio file cache using async-lru.""" @pytest.fixture(autouse=True) def clear_cache_before_each_test(self) -> Generator[None, None, None]: """Clear cache before each test to ensure isolation.""" clear_global_cache() yield clear_global_cache() @pytest.fixture def sample_file_info(self) -> FilePathSupportParams: """Create sample file metadata.""" return FilePathSupportParams( file_name="test.mp3", transcription_support=["whisper-1"], chat_support=["gpt-4o-audio-preview"], modified_time=100.0, size_bytes=1000, format="mp3", duration_seconds=60.0, ) @pytest.mark.anyio async def test_cache_miss_calls_function(self, sample_file_info: FilePathSupportParams) -> None: """Test that cache miss calls the underlying function.""" mock_func = AsyncMock(return_value=sample_file_info) result = await get_cached_audio_file_support( file_path="/audio/test.mp3", mtime=100.0, get_support_func=mock_func, ) assert result == sample_file_info mock_func.assert_called_once_with(Path("/audio/test.mp3")) @pytest.mark.anyio async def test_cache_hit_reuses_result(self, sample_file_info: FilePathSupportParams) -> None: """Test that cache hit reuses cached result without calling function again.""" mock_func = AsyncMock(return_value=sample_file_info) # First call - cache miss result1 = await get_cached_audio_file_support( file_path="/audio/test.mp3", mtime=100.0, get_support_func=mock_func, ) # Second call with same parameters - cache hit result2 = await get_cached_audio_file_support( file_path="/audio/test.mp3", mtime=100.0, get_support_func=mock_func, ) assert result1 == sample_file_info assert result2 == sample_file_info # Function should only be called once (cache hit on second call) mock_func.assert_called_once() @pytest.mark.anyio async def test_cache_invalidation_on_mtime_change(self, sample_file_info: FilePathSupportParams) -> None: """Test that cache is invalidated when file mtime changes.""" mock_func = AsyncMock(return_value=sample_file_info) # First call with mtime=100.0 await get_cached_audio_file_support( file_path="/audio/test.mp3", mtime=100.0, get_support_func=mock_func, ) # Second call with different mtime - should trigger new function call await get_cached_audio_file_support( file_path="/audio/test.mp3", mtime=200.0, # Different mtime get_support_func=mock_func, ) # Function should be called twice (once per mtime) assert mock_func.call_count == 2 @pytest.mark.anyio async def test_cache_different_files_separately(self, sample_file_info: FilePathSupportParams) -> None: """Test that different files are cached separately.""" mock_func = AsyncMock(return_value=sample_file_info) # Cache two different files await get_cached_audio_file_support( file_path="/audio/file1.mp3", mtime=100.0, get_support_func=mock_func, ) await get_cached_audio_file_support( file_path="/audio/file2.mp3", mtime=100.0, get_support_func=mock_func, ) # Both files should trigger function calls (different cache keys) assert mock_func.call_count == 2 @pytest.mark.anyio async def test_cache_respects_maxsize(self, sample_file_info: FilePathSupportParams) -> None: """Test that cache evicts old entries when maxsize is reached.""" mock_func = AsyncMock(return_value=sample_file_info) # Fill cache beyond maxsize (32) - add 35 entries for i in range(35): await get_cached_audio_file_support( file_path=f"/audio/file{i}.mp3", mtime=float(i), get_support_func=mock_func, ) # Check that cache size is capped info = get_global_cache_info() assert info.currsize <= 32 @pytest.mark.anyio async def test_cache_with_different_functions(self, sample_file_info: FilePathSupportParams) -> None: """Test that cache distinguishes between different support functions.""" mock_func1 = AsyncMock(return_value=sample_file_info) mock_func2 = AsyncMock(return_value=sample_file_info) # Same file, same mtime, but different function await get_cached_audio_file_support("/audio/test.mp3", 100.0, mock_func1) await get_cached_audio_file_support("/audio/test.mp3", 100.0, mock_func2) # Both functions should be called (different cache keys) mock_func1.assert_called_once() mock_func2.assert_called_once() def test_clear_global_cache(self) -> None: """Test clearing the global cache.""" clear_global_cache() info = get_global_cache_info() assert info.currsize == 0 def test_cache_info_structure(self) -> None: """Test that cache info returns proper statistics namedtuple.""" info = get_global_cache_info() # Should have all expected attributes assert hasattr(info, "hits") assert hasattr(info, "misses") assert hasattr(info, "maxsize") assert hasattr(info, "currsize") # Initial state assert info.hits == 0 assert info.misses == 0 assert info.maxsize == 32 assert info.currsize == 0 @pytest.mark.anyio async def test_cache_info_tracks_hits_and_misses(self, sample_file_info: FilePathSupportParams) -> None: """Test that cache info correctly tracks hits and misses.""" mock_func = AsyncMock(return_value=sample_file_info) # First access - miss await get_cached_audio_file_support("/audio/test.mp3", 100.0, mock_func) info_after_miss = get_global_cache_info() assert info_after_miss.misses == 1 assert info_after_miss.hits == 0 # Second access - hit await get_cached_audio_file_support("/audio/test.mp3", 100.0, mock_func) info_after_hit = get_global_cache_info() assert info_after_hit.misses == 1 # Still 1 miss assert info_after_hit.hits == 1 # Now 1 hit @pytest.mark.anyio async def test_cache_returns_correct_data_type(self, sample_file_info: FilePathSupportParams) -> None: """Test that cache returns the correct data type.""" mock_func = AsyncMock(return_value=sample_file_info) result = await get_cached_audio_file_support("/audio/test.mp3", 100.0, mock_func) assert isinstance(result, FilePathSupportParams) assert result.file_name == "test.mp3" assert result.size_bytes == 1000 assert result.format == "mp3" @pytest.mark.anyio async def test_cache_with_none_duration(self) -> None: """Test caching file info with None duration.""" file_info = FilePathSupportParams( file_name="test.mp4", transcription_support=["whisper-1"], chat_support=None, modified_time=100.0, size_bytes=500, format="mp4", duration_seconds=None, # No duration ) mock_func = AsyncMock(return_value=file_info) result1 = await get_cached_audio_file_support("/audio/test.mp4", 100.0, mock_func) result2 = await get_cached_audio_file_support("/audio/test.mp4", 100.0, mock_func) assert result1 == result2 assert result1.duration_seconds is None # Should hit cache mock_func.assert_called_once() @pytest.mark.anyio async def test_concurrent_cache_access(self, sample_file_info: FilePathSupportParams) -> None: """Test that cache handles concurrent access correctly.""" import anyio mock_func = AsyncMock(return_value=sample_file_info) # Make multiple concurrent calls async def access_cache(): return await get_cached_audio_file_support("/audio/test.mp3", 100.0, mock_func) async with anyio.create_task_group() as tg: for _ in range(5): tg.start_soon(access_cache) # All results should be the same # Function might be called multiple times due to race conditions, # but that's okay - cache is eventually consistent assert mock_func.call_count >= 1

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/arcaputo3/mcp-server-whisper'

If you have feedback or need assistance with the MCP directory API, please join our Discord server