170 lines
6.6 KiB
Python
170 lines
6.6 KiB
Python
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
|
|
assert cache_service._SALES_RESULT_TTL == cache_manager._SALES_CACHE_TTL
|
|
|
|
|
|
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(tmp_path, monkeypatch):
|
|
from services import cache_manager
|
|
|
|
shared_cache = tmp_path / "dashboard_full_cache.pkl"
|
|
stale_cache = tmp_path / "dashboard_full_cache_stale.pkl"
|
|
shared_cache.write_bytes(b"stale")
|
|
monkeypatch.setattr(cache_manager, "_DASHBOARD_SHARED_CACHE_FILE", shared_cache)
|
|
monkeypatch.setattr(cache_manager, "_DASHBOARD_STALE_CACHE_FILE", stale_cache)
|
|
|
|
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,
|
|
}
|
|
assert not shared_cache.exists()
|
|
assert stale_cache.exists()
|
|
|
|
|
|
def test_sales_analysis_shared_page_cache_roundtrip(tmp_path, monkeypatch):
|
|
from routes import sales_routes
|
|
|
|
monkeypatch.setattr(sales_routes, "_SALES_ANALYSIS_PAGE_CACHE_DIR", tmp_path)
|
|
cache_key = "sales_analysis:page_context:test"
|
|
context = {"kpi": {"revenue": 123}, "active_page": "sales"}
|
|
|
|
sales_routes._set_sales_shared_page_context_cache(cache_key, context)
|
|
|
|
assert sales_routes._get_sales_shared_page_context_cache(cache_key) == context
|
|
|
|
|
|
def test_clear_sales_cache_removes_shared_page_cache_files(tmp_path, monkeypatch):
|
|
from services import cache_manager
|
|
|
|
monkeypatch.setattr(cache_manager, "_SALES_ANALYSIS_PAGE_CACHE_DIR", tmp_path)
|
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
|
cache_file = tmp_path / "sales_analysis_page.pkl"
|
|
cache_file.write_bytes(b"stale")
|
|
|
|
cache_manager.clear_sales_cache()
|
|
|
|
assert not cache_file.exists()
|
|
|
|
|
|
def test_daily_sales_shared_view_cache_roundtrip(tmp_path, monkeypatch):
|
|
from routes import daily_sales_routes
|
|
|
|
monkeypatch.setattr(daily_sales_routes, "_DAILY_SALES_VIEW_CACHE_DIR", tmp_path)
|
|
cache_key = "daily_sales:view:test"
|
|
context = {"summary": {"revenue": 456}, "active_page": "daily_sales"}
|
|
|
|
daily_sales_routes._set_shared_daily_view_cache(cache_key, context)
|
|
|
|
assert daily_sales_routes._get_shared_daily_view_cache(cache_key) == context
|
|
|
|
|
|
def test_clear_daily_sales_cache_removes_shared_view_cache_files(tmp_path, monkeypatch):
|
|
from services import cache_manager
|
|
|
|
monkeypatch.setattr(cache_manager, "_DAILY_SALES_VIEW_CACHE_DIR", tmp_path)
|
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
|
cache_file = tmp_path / "daily_sales_view.pkl"
|
|
cache_file.write_bytes(b"stale")
|
|
|
|
cache_manager.clear_daily_sales_cache()
|
|
|
|
assert not cache_file.exists()
|
|
|
|
|
|
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 = {"),
|
|
]
|
|
|
|
|
|
def test_sales_cache_ttl_is_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")
|
|
if "_SALES_CACHE_TTL =" in text:
|
|
assignments.append(path.relative_to(ROOT).as_posix())
|
|
|
|
assert assignments == ["services/cache_manager.py"]
|