diff --git a/backend/scripts/clear_thumbnail_cache.py b/backend/scripts/clear_thumbnail_cache.py index a44c9df..a566df8 100644 --- a/backend/scripts/clear_thumbnail_cache.py +++ b/backend/scripts/clear_thumbnail_cache.py @@ -110,20 +110,26 @@ async def clear_thumbnail_for_resource(resource_id: str) -> bool: return deleted > 0 +async def clear_thumbnails_for_resources(resource_ids: list[str]) -> int: + """Clear thumbnail caches for all resources within one event loop.""" + cleared = 0 + for resource_id in resource_ids: + try: + if await clear_thumbnail_for_resource(resource_id): + cleared += 1 + except Exception as exc: + logger.error(f"Failed for {resource_id}: {exc}") + raise + return cleared + + def main(): if len(sys.argv) < 2: print("Usage: python scripts/clear_thumbnail_cache.py RESOURCE_ID [RESOURCE_ID ...]") sys.exit(1) resource_ids = sys.argv[1:] - cleared = 0 - for rid in resource_ids: - try: - if asyncio.run(clear_thumbnail_for_resource(rid)): - cleared += 1 - except Exception as e: - logger.error(f"Failed for {rid}: {e}") - raise + cleared = asyncio.run(clear_thumbnails_for_resources(resource_ids)) print(f"Cleared cache for {cleared}/{len(resource_ids)} resource(s)") diff --git a/backend/tests/scripts/test_clear_thumbnail_cache.py b/backend/tests/scripts/test_clear_thumbnail_cache.py new file mode 100644 index 0000000..062cc8f --- /dev/null +++ b/backend/tests/scripts/test_clear_thumbnail_cache.py @@ -0,0 +1,49 @@ +import asyncio +from unittest.mock import patch + +import pytest + +import scripts.clear_thumbnail_cache as clear_thumbnail_cache + + +@pytest.mark.asyncio +async def test_clear_multiple_resources_uses_one_event_loop(): + calls: list[tuple[str, asyncio.AbstractEventLoop]] = [] + + async def fake_clear(resource_id: str) -> bool: + calls.append((resource_id, asyncio.get_running_loop())) + return resource_id != "unr-missing" + + with patch.object( + clear_thumbnail_cache, + "clear_thumbnail_for_resource", + side_effect=fake_clear, + ): + cleared = await clear_thumbnail_cache.clear_thumbnails_for_resources( + ["unr-one", "unr-missing", "unr-two"] + ) + + assert cleared == 2 + assert [resource_id for resource_id, _loop in calls] == [ + "unr-one", + "unr-missing", + "unr-two", + ] + assert len({id(loop) for _resource_id, loop in calls}) == 1 + + +def test_main_runs_one_batch_and_prints_summary(capsys): + with ( + patch.object( + clear_thumbnail_cache.sys, + "argv", + ["clear_thumbnail_cache.py", "unr-one", "unr-two"], + ), + patch.object(clear_thumbnail_cache.asyncio, "run", return_value=1) as mock_run, + ): + clear_thumbnail_cache.main() + + batch_coroutine = mock_run.call_args.args[0] + batch_coroutine.close() + mock_run.assert_called_once() + assert "Cleared cache for 1/2 resource(s)" in capsys.readouterr().out