diff --git a/tests/test_cache_manager.py b/tests/test_cache_manager.py new file mode 100644 index 0000000..5cbdefe --- /dev/null +++ b/tests/test_cache_manager.py @@ -0,0 +1,100 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_cache_service_reexports_cache_manager_state(): + from services import cache_manager, cache_service + + assert cache_service._SALES_DF_CACHE is cache_manager._SALES_DF_CACHE + assert cache_service._SALES_PROCESSED_CACHE is cache_manager._SALES_PROCESSED_CACHE + assert cache_service._SALES_OPTIONS_CACHE is cache_manager._SALES_OPTIONS_CACHE + assert cache_service._SALES_ANALYSIS_RESULT_CACHE is cache_manager._SALES_ANALYSIS_RESULT_CACHE + assert cache_service._DASHBOARD_DATA_CACHE is cache_manager._DASHBOARD_DATA_CACHE + + +def test_routes_share_sales_cache_manager_state(): + from services import cache_manager + from routes import export_routes, import_routes, sales_routes + + sales_df_cache, sales_processed_cache, clear_for_table = import_routes._get_cache_refs() + + assert sales_routes._SALES_PROCESSED_CACHE is cache_manager._SALES_PROCESSED_CACHE + assert export_routes._get_sales_cache() is cache_manager._SALES_PROCESSED_CACHE + assert sales_df_cache is cache_manager._SALES_DF_CACHE + assert sales_processed_cache is cache_manager._SALES_PROCESSED_CACHE + assert clear_for_table is cache_manager.clear_sales_cache_for_table + + +def test_clear_sales_cache_for_table_removes_table_and_range_aliases(): + from services import cache_manager + + cache_manager.clear_sales_cache() + cache_manager._SALES_DF_CACHE["realtime_sales_monthly"] = object() + cache_manager._SALES_DF_CACHE["other_table"] = object() + cache_manager._SALES_PROCESSED_CACHE.update({ + "realtime_sales_monthly": {"df": "base"}, + "realtime_sales_monthly_1m": {"df": "range"}, + "realtime_sales_monthly_custom_2026-01-01_2026-01-31": {"df": "custom"}, + "other_table": {"df": "other"}, + }) + + cache_manager.clear_sales_cache_for_table("realtime_sales_monthly") + + assert "realtime_sales_monthly" not in cache_manager._SALES_DF_CACHE + assert "other_table" in cache_manager._SALES_DF_CACHE + assert "realtime_sales_monthly" not in cache_manager._SALES_PROCESSED_CACHE + assert "realtime_sales_monthly_1m" not in cache_manager._SALES_PROCESSED_CACHE + assert "realtime_sales_monthly_custom_2026-01-01_2026-01-31" not in cache_manager._SALES_PROCESSED_CACHE + assert "other_table" in cache_manager._SALES_PROCESSED_CACHE + + +def test_set_sales_processed_cache_adds_timestamp_and_aliases(): + from services import cache_manager + + cache_manager.clear_sales_cache() + entry = {"df": "data", "cols": {"date": "日期"}} + + cache_manager.set_sales_processed_cache( + "realtime_sales_monthly_3m", + entry, + aliases=("realtime_sales_monthly",), + ) + + cached = cache_manager._SALES_PROCESSED_CACHE["realtime_sales_monthly_3m"] + assert cached is entry + assert cached["time"] > 0 + assert cache_manager._SALES_PROCESSED_CACHE["realtime_sales_monthly"] is entry + + +def test_dashboard_cache_clear_restores_expected_shape(): + from services import cache_manager + + cache_manager._DASHBOARD_DATA_CACHE["consolidated_data"] = ["stale"] + cache_manager._DASHBOARD_DATA_CACHE["full_data"] = ["stale"] + + cache_manager.clear_dashboard_cache() + + assert cache_manager._DASHBOARD_DATA_CACHE == { + "consolidated_data": None, + "consolidated_timestamp": None, + "today_start": None, + "full_data": None, + "full_timestamp": None, + } + + +def test_cache_dicts_are_only_defined_in_cache_manager(): + assignments = [] + for path in [ROOT / "app.py", *ROOT.glob("routes/*.py"), *ROOT.glob("services/*.py")]: + text = path.read_text(encoding="utf-8") + for marker in ("_SALES_DF_CACHE = {}", "_SALES_PROCESSED_CACHE = {}", "_DASHBOARD_DATA_CACHE = {"): + if marker in text: + assignments.append((path.relative_to(ROOT).as_posix(), marker)) + + assert assignments == [ + ("services/cache_manager.py", "_SALES_DF_CACHE = {}"), + ("services/cache_manager.py", "_SALES_PROCESSED_CACHE = {}"), + ("services/cache_manager.py", "_DASHBOARD_DATA_CACHE = {"), + ]