Ctrl K
ComunidadGuías…

tests/test_it_does_not_plan_what_it_cannot_write.py

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

Archivo original ↗Preguntar a KiBot
"""沒有 frontmatter 就沒有地方寫分類——那要說「做不到」,不是「待辦」。

索引 1,706 列裡有 9 列的檔案沒有 YAML frontmatter(github stars、youtube、抓取
失敗的空殼)。這些檔的分類落在 build_search_index.sh 的預設值,所以它們永遠在
「清單外」那一份名單裡。

第一版把它們照樣排進計畫,於是報告每次都說「要重新分類 3 筆」,--apply 之後那 3
筆還在。這是這個專案反覆出現的同一個形狀:**把「做不到」寫成「待辦」**,看起來像
還沒輪到,實際上永遠不會發生。(記憶 xkb-gate-asked-the-impossible:放行率 0 不是
門檻太嚴,是資料根本無法滿足。)
"""
from __future__ import annotations

import io
import sys
import tempfile
import unittest
from contextlib import redirect_stdout
from pathlib import Path
from unittest import mock

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

import xkb_recategorize as rc  # noqa: E402


class FilesWithoutFrontmatterAreReportedNotPlanned(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.addCleanup(self.tmp.cleanup)
        self.root = Path(self.tmp.name)

    def _run(self, files: list[Path]) -> str:
        item = {"category": "general", "title": "每次都說要改的那一筆",
                "relative_path": str(files[0])}
        out = io.StringIO()
        with mock.patch.object(rc, "load_items", return_value=[item]), \
             mock.patch.object(rc, "off_taxonomy", return_value=[item]), \
             mock.patch.object(rc.xkb_index, "files_for_item", return_value=files), \
             mock.patch.object(rc.category_classifier, "taxonomy",
                               return_value=["99-general"]), \
             mock.patch.object(rc.category_classifier, "proposals", return_value={}), \
             mock.patch.object(rc, "gather_promoted", return_value=[]), \
             mock.patch.object(sys, "argv", ["xkb_recategorize.py"]), \
             redirect_stdout(out):
            rc.main()
        return out.getvalue()

    def test_a_file_without_frontmatter_is_not_listed_as_work_to_do(self):
        path = self.root / "every-app__open-seo.md"
        path.write_text("# every-app/open-seo\n\n- **Category:** seo-geo\n",
                        encoding="utf-8")

        output = self._run([path])

        self.assertIn("沒有 YAML frontmatter", output)
        self.assertNotIn("要重新分類", output,
                         "寫不進去的不該列成待辦——報告會每次重複一個不會發生的承諾")

    def test_it_does_not_call_the_classifier_for_a_file_it_cannot_write(self):
        """連 LLM 都不該花。判斷完也沒有地方寫。"""
        path = self.root / "no-frontmatter.md"
        path.write_text("# 只有正文\n", encoding="utf-8")

        with mock.patch.object(rc.category_classifier, "classify_content") as spy:
            self._run([path])

        spy.assert_not_called()

    def test_a_group_with_one_writable_file_is_still_planned(self):
        """一組裡只要有一個檔寫得進去就算可做——別把整組一起放棄。"""
        plain = self.root / "bookmark.md"
        plain.write_text("# 沒有 frontmatter\n", encoding="utf-8")
        card = self.root / "card.md"
        card.write_text("---\ncategory: general\ntitle: t\n---\n# t\n",
                        encoding="utf-8")

        with mock.patch.object(
                rc.category_classifier, "classify_content",
                return_value={"category": "99-general", "confidence": "high",
                              "llm": True, "proposed_category": "",
                              "proposal_count": 0}):
            output = self._run([card, plain])

        self.assertIn("要重新分類", output)
        self.assertNotIn("沒有 YAML frontmatter", output)


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