Ctrl K
ComunidadGuías…

tests/test_one_check_list.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""健檢只能有一份清單,而且要問每天真的會跑的那條路。

2026-09-11 加了 card_production 與 pipeline_ledger,也寫了測試確認「有註冊」:

    source = (SCRIPTS / "health_check_pipeline.py").read_text(...)
    assertIn("check_card_production(),", source)

那個測試通過了,而那兩項在每日訊息裡一次都沒有出現過。因為清單有兩份:
health_check_pipeline 一份(我改的),health_check_notify 一份(排程每天跑的
那支,我沒碰)。反過來 external_dependencies 只在 notify 那份裡,所以手動跑
pipeline 時它不被檢查。

測試守錯了檔案。我當時想的是「有沒有接進 main()」,而系統有兩個 main(),每天
跑的是另一個。又一次:測試覆蓋我想像的機制,不是實際的執行路徑。

所以這份測試不讀原始碼字串,它問執行路徑:notify 會不會跑到每一項。
"""
from __future__ import annotations

import sys
import unittest
from pathlib import Path
from unittest import mock

ROOT = Path(__file__).resolve().parents[1]
SCRIPTS = ROOT / "scripts"
sys.path.insert(0, str(SCRIPTS))

import health_check_pipeline as hc  # noqa: E402


class OneList(unittest.TestCase):
    def test_the_notifier_does_not_keep_its_own_list(self):
        """自己列一份就是第二個會漂走的答案。"""
        source = (SCRIPTS / "health_check_notify.py").read_text(encoding="utf-8")
        self.assertNotIn("hc.check_", source,
                         "notify 又自己列清單了——清單在 health_check_pipeline.CHECKS")
        self.assertIn("hc.run_all()", source)

    def test_every_check_function_is_in_the_list(self):
        """寫了檢查卻沒進清單,等於沒寫。

        比對的是模組裡所有 check_* 函式與 CHECKS 的內容,所以新增一個忘了註冊
        時這裡會紅——不必記得回來改測試。
        """
        defined = {name for name in dir(hc)
                   if name.startswith("check_") and callable(getattr(hc, name))}
        registered = {check.__name__ for check in hc.CHECKS}
        self.assertEqual(defined - registered, set(),
                         "這些檢查沒有被註冊進 CHECKS")

    def test_run_all_actually_calls_each_one(self):
        """問執行路徑,不問原始碼字串。

        原本的守衛是 assertIn("check_card_production(),", source),它守的是
        pipeline 的檔案內容——而每天跑的是 notify。這裡改成把每個檢查換成間諜,
        看 run_all() 有沒有真的叫到。
        """
        called: list[str] = []

        def spy(name):
            def _check():
                called.append(name)
                return {"name": name, "checks": []}
            _check.__name__ = name
            return _check

        spies = tuple(spy(check.__name__) for check in hc.CHECKS)
        with mock.patch.object(hc, "CHECKS", spies):
            hc.run_all()

        self.assertEqual(called, [check.__name__ for check in hc.CHECKS])

    def test_the_notifier_runs_the_same_checks(self):
        """notify 的 main() 會不會跑到每一項——這是每天真的會走的那條路。"""
        import health_check_notify as notify

        called: list[str] = []

        def fake_run_all():
            called.append("run_all")
            return [{"name": check.__name__, "checks": []} for check in hc.CHECKS]

        with mock.patch.object(notify.hc, "run_all", fake_run_all), \
             mock.patch.object(notify, "_inventory_lines", lambda: []), \
             mock.patch.object(notify, "_save_alert_state", lambda _s: None), \
             mock.patch.object(notify, "_load_alert_state", lambda: {}), \
             mock.patch.object(sys, "argv", ["health_check_notify.py", "--dry-run"]):
            notify.main()

        self.assertEqual(called, ["run_all"])


class TheNewChecksReachTheDailyMessage(unittest.TestCase):
    """這一輪加的兩項,具體確認它們在每日那條路上。"""

    def test_card_production_is_registered(self):
        self.assertIn("check_card_production",
                      {c.__name__ for c in hc.CHECKS})

    def test_pipeline_ledger_is_registered(self):
        self.assertIn("check_pipeline_ledger",
                      {c.__name__ for c in hc.CHECKS})

    def test_external_dependencies_is_registered(self):
        """它原本只在 notify 那份清單裡,所以手動跑 pipeline 時不被檢查。"""
        self.assertIn("check_external_dependencies",
                      {c.__name__ for c in hc.CHECKS})

    def test_every_registered_check_has_a_label(self):
        """沒有標籤的話,通知裡會出現內部識別字。決策類的不需要標籤。"""
        import health_check_notify as notify
        names = {c.__name__.replace("check_", "") for c in hc.CHECKS}
        missing = names - set(notify.FAULT_LABELS) - notify.DECISION_SECTIONS
        self.assertEqual(missing, set(), f"這些 section 沒有可讀的標籤:{missing}")


class NoSelfInflictedImportCycle(unittest.TestCase):
    """xkb_paths 是基礎模組,不可以依賴純觀測工具。

    2026-09-11 批次加使用量測時,xkb_paths 也被接上了(它有 __main__,會印路徑)。
    而 xkb_usage 需要 xkb_paths 決定紀錄寫在哪——於是
    xkb_usage → xkb_paths → xkb_usage。Python 剛好不會炸,所以 401 項測試沒有
    一項發現。
    """

    def test_xkb_paths_does_not_import_the_usage_recorder(self):
        source = (SCRIPTS / "xkb_paths.py").read_text(encoding="utf-8")
        self.assertNotIn("import xkb_usage", source)

    def test_the_usage_recorder_is_a_leaf_apart_from_paths(self):
        """觀測可以讀路徑,但不可以被路徑讀。"""
        source = (SCRIPTS / "xkb_usage.py").read_text(encoding="utf-8")
        self.assertIn("import xkb_paths", source)


if __name__ == "__main__":
    unittest.main()