Skip to content

Commit 0538859

Browse files
committed
fix: keep api cache after sync
1 parent 4dbe1a1 commit 0538859

3 files changed

Lines changed: 60 additions & 5 deletions

File tree

quantclass_sync_internal/status_store.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,14 +482,32 @@ def _update_product_last_status(log_dir: Path, report: RunReport) -> None:
482482
pass
483483
# 用本轮结果覆盖(同产品多次出现时后面的覆盖前面的)
484484
for item in report.products:
485-
existing[item.product] = {
485+
previous = existing.get(item.product, {}) if isinstance(existing.get(item.product), dict) else {}
486+
api_date_time = previous.get("api_date_time")
487+
api_checked_at = previous.get("api_checked_at")
488+
api_dates = previous.get("api_dates")
489+
# 兼容旧格式:若上一版只有 api_check 的 date_time/checked_at,也在同步覆盖时提升为专用 API 缓存字段。
490+
if previous.get("source") == _SOURCE_API_CHECK:
491+
api_date_time = api_date_time or previous.get("date_time")
492+
api_checked_at = api_checked_at or previous.get("checked_at")
493+
if api_dates is None and api_date_time:
494+
api_dates = [api_date_time]
495+
496+
merged = {
486497
"status": item.status,
487498
"reason_code": item.reason_code,
488499
"error": item.error,
489500
"date_time": item.date_time,
490501
"checked_at": datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
491502
"source": _SOURCE_SYNC,
492503
}
504+
if api_date_time:
505+
merged["api_date_time"] = api_date_time
506+
if api_checked_at:
507+
merged["api_checked_at"] = api_checked_at
508+
if api_dates:
509+
merged["api_dates"] = api_dates
510+
existing[item.product] = merged
493511
# 原子写入
494512
with atomic_temp_path(status_path, tag="last_status") as tmp:
495513
tmp.write_text(json.dumps(existing, ensure_ascii=False, indent=2), encoding="utf-8")

tests/test_data_query.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from datetime import date
77
from pathlib import Path
88

9+
from quantclass_sync_internal.reporting import _append_result, _new_report
910
from quantclass_sync_internal.data_query import (
1011
_days_behind,
1112
_status_color,
@@ -14,7 +15,7 @@
1415
get_run_detail,
1516
get_run_history,
1617
)
17-
from quantclass_sync_internal.status_store import update_api_latest_dates
18+
from quantclass_sync_internal.status_store import _update_product_last_status, update_api_latest_dates
1819

1920

2021
class TestDaysBehind(unittest.TestCase):
@@ -407,6 +408,35 @@ def test_overview_same_api_date_after_check_updates_keeps_no_valid_output_suppre
407408
self.assertEqual(overview[0]["days_behind"], 0)
408409
self.assertEqual(overview[0]["status_color"], "green")
409410

411+
def test_overview_preserves_green_after_sync_when_api_cache_exists(self):
412+
"""同步结果为 up_to_date 时,若保留 API 缓存字段,总览仍应显示绿色。"""
413+
self._write_timestamp("coin-cap", "2026-03-11")
414+
update_api_latest_dates(self.log_dir, {"coin-cap": "2026-03-11"})
415+
report = _new_report("test", mode="network")
416+
_append_result(
417+
report,
418+
product="coin-cap",
419+
status="skipped",
420+
reason_code="up_to_date",
421+
error="本地 timestamp 已是最新(local=2026-03-11, api=2026-03-11)。",
422+
date_time="2026-03-11",
423+
)
424+
_update_product_last_status(self.log_dir, report)
425+
426+
import unittest.mock
427+
with unittest.mock.patch(
428+
"quantclass_sync_internal.data_query.report_dir_path",
429+
return_value=self.log_dir,
430+
):
431+
overview = get_products_overview(
432+
self.data_root,
433+
["coin-cap"],
434+
today=date(2026, 3, 13),
435+
)
436+
437+
self.assertEqual(overview[0]["days_behind"], 0)
438+
self.assertEqual(overview[0]["status_color"], "green")
439+
410440
def test_overview_suppresses_same_date_no_valid_output_even_if_source_was_polluted(self):
411441
"""旧状态文件 source 被污染为 api_check 时,same-date no_valid_output 仍应抑制待更新。"""
412442
self._write_timestamp("coin-cap", "2026-03-10")

tests/test_status_store.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ def test_sync_result_excluded(self):
237237
self.assertIn("prod-a", cache)
238238
self.assertNotIn("prod-b", cache)
239239

240-
def test_source_residual_after_sync_overwrite(self):
241-
"""先 api_check 写入再同步覆盖,旧 source 不残留。"""
240+
def test_sync_overwrite_preserves_api_cache_fields(self):
241+
"""先 api_check 再同步时,保留 API 缓存字段但同步来源仍为 sync。"""
242242
with tempfile.TemporaryDirectory() as tmpdir:
243243
log_dir = Path(tmpdir)
244244
update_api_latest_dates(log_dir, {"prod-a": "2026-03-18"})
@@ -247,8 +247,15 @@ def test_source_residual_after_sync_overwrite(self):
247247
report = _new_report("test", mode="network")
248248
_append_result(report, product="prod-a", status="ok", date_time="2026-03-17")
249249
_update_product_last_status(log_dir, report)
250+
status = json.loads((log_dir / "product_last_status.json").read_text(encoding="utf-8"))
251+
entry = status["prod-a"]
252+
self.assertEqual(entry["source"], _SOURCE_SYNC)
253+
self.assertEqual(entry["date_time"], "2026-03-17")
254+
self.assertEqual(entry["api_date_time"], "2026-03-18")
255+
self.assertIn("api_checked_at", entry)
250256
cache_after = load_api_latest_dates(log_dir)
251-
self.assertNotIn("prod-a", cache_after)
257+
self.assertIn("prod-a", cache_after)
258+
self.assertEqual(cache_after["prod-a"][0], ["2026-03-18"])
252259

253260
def test_api_check_does_not_overwrite_sync_provenance(self):
254261
"""已有 sync 结果时,check_updates 只补 API 缓存,不覆盖同步来源。"""

0 commit comments

Comments
 (0)