Newer
Older
TIASDataHub / tests / importers / test_holds.py
"""保留リスト `holds/<取り込み元の名前>.toml` の読み込み(SPEC_03「保留リスト」・PLAN_02).

- 1 件ごとに number・reason・resolve・since を書く
- ファイルが無ければ停止する.空のファイル・`[[hold]]` 0 件は保留 0 件(ユーザー承認済み)
- 形の誤り・番号の重複は停止する
- 最初の保留リスト `holds/tiasjudgemodel.toml` は舌色データセットの重複 5 組のうち 9 枚
"""

from pathlib import Path

import pytest
from conftest import write_holds

from tiasdata.importers.holds import default_holds_dir, load_holds

ENTRY = '[[hold]]\nnumber = {n}\nreason = "r"\nresolve = "s"\nsince = 2026-10-06\n\n'


def _write(directory: Path, name: str, text: str) -> Path:
    path = directory / f"{name}.toml"
    path.write_text(text, encoding="utf-8")
    return path


class TestLoadHolds:
    def test_numbers_are_loaded(self, tmp_path: Path) -> None:
        write_holds(tmp_path, "src", [5, 2, 9])
        holds = load_holds(tmp_path, "src")
        assert holds.numbers == frozenset({2, 5, 9})
        assert holds.filename.endswith("src.toml")

    @pytest.mark.parametrize("text", ["", "# 保留なし\n"])
    def test_empty_file_means_no_holds(self, tmp_path: Path, text: str) -> None:
        _write(tmp_path, "src", text)
        assert load_holds(tmp_path, "src").numbers == frozenset()

    def test_missing_file_stops(self, tmp_path: Path) -> None:
        with pytest.raises(ValueError):
            load_holds(tmp_path, "src")

    def test_message_does_not_contain_path(self, tmp_path: Path) -> None:
        with pytest.raises(ValueError) as excinfo:
            load_holds(tmp_path, "src")
        assert str(tmp_path) not in str(excinfo.value)
        assert tmp_path.as_posix() not in str(excinfo.value)

    @pytest.mark.parametrize(
        "text",
        [
            pytest.param("[[hold]]\nnumber = 3\n", id="missing-fields"),
            pytest.param(ENTRY.format(n='"3"'), id="number-as-string"),
            pytest.param(ENTRY.format(n="3.0"), id="number-as-float"),
            pytest.param(ENTRY.format(n="0"), id="number-zero"),
            pytest.param(ENTRY.format(n="-1"), id="number-negative"),
            pytest.param(ENTRY.format(n="true"), id="number-as-bool"),
            pytest.param(ENTRY.format(n="3") + 'extra = "x"\n', id="unknown-key"),
            pytest.param(
                '[[hold]]\nnumber = 3\nreason = "r"\nresolve = "s"\nsince = "いつか"\n',
                id="since-not-date",
            ),
            pytest.param(
                '[[hold]]\nnumber = 3\nreason = ""\nresolve = "s"\nsince = 2026-10-06\n',
                id="empty-reason",
            ),
            pytest.param('[[hold]]\nnumber = 3\nreason = "r\n', id="broken-toml"),
            pytest.param("[holds]\nnumber = 3\n", id="unknown-table"),
            pytest.param(ENTRY.format(n=3) + ENTRY.format(n=3), id="duplicated-number"),
        ],
    )
    def test_malformed_file_stops(self, tmp_path: Path, text: str) -> None:
        _write(tmp_path, "src", text)
        with pytest.raises(ValueError):
            load_holds(tmp_path, "src")

    @pytest.mark.parametrize("name", ["../src", "a/b", "a\\b", "", "名前"])
    def test_source_name_unusable_as_filename_stops(self, tmp_path: Path, name: str) -> None:
        with pytest.raises(ValueError):
            load_holds(tmp_path, name)


class TestRepositoryHolds:
    def test_default_holds_dir_is_repository_holds(self) -> None:
        directory = default_holds_dir()
        assert directory.name == "holds"
        assert (directory.parent / "pyproject.toml").is_file()

    def test_tiasjudgemodel_hold_list_matches_plan(self) -> None:
        # PLAN_02「舌色データセットの重複 5 組の扱い」:
        # 122/184・548/566・381/382・515/516 の両方と 509(508 は残す)
        holds = load_holds(default_holds_dir(), "tiasjudgemodel")
        assert holds.numbers == frozenset({122, 184, 548, 566, 381, 382, 515, 516, 509})
        assert 508 not in holds.numbers