"""保留リスト `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