"""要確認リスト `reviews/<取り込み元の名前>.toml` の読み込み(SPEC_03「要確認リスト」・PLAN_02).
- 1 件ごとに number・reason・resolve・since を書く
- ファイルが無ければ停止する.空のファイル・`[[review]]` 0 件は要確認 0 件(ユーザー承認済み)
- 形の誤り・番号の重複は停止する
- 最初の要確認リスト `reviews/tiasjudgemodel.toml` は舌色データセットの重複 5 組のうち 9 枚
"""
from pathlib import Path
import pytest
from conftest import write_reviews
from tiasdata.importers.reviews import default_reviews_dir, load_reviews
ENTRY = '[[review]]\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 TestLoadReviews:
def test_numbers_are_loaded(self, tmp_path: Path) -> None:
write_reviews(tmp_path, "src", [5, 2, 9])
reviews = load_reviews(tmp_path, "src")
assert reviews.numbers == frozenset({2, 5, 9})
assert reviews.filename.endswith("src.toml")
@pytest.mark.parametrize("text", ["", "# 要確認なし\n"])
def test_empty_file_means_no_reviews(self, tmp_path: Path, text: str) -> None:
_write(tmp_path, "src", text)
assert load_reviews(tmp_path, "src").numbers == frozenset()
def test_missing_file_stops(self, tmp_path: Path) -> None:
with pytest.raises(ValueError):
load_reviews(tmp_path, "src")
def test_message_does_not_contain_path(self, tmp_path: Path) -> None:
with pytest.raises(ValueError) as excinfo:
load_reviews(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("[[review]]\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(
'[[review]]\nnumber = 3\nreason = "r"\nresolve = "s"\nsince = "いつか"\n',
id="since-not-date",
),
pytest.param(
'[[review]]\nnumber = 3\nreason = ""\nresolve = "s"\nsince = 2026-10-06\n',
id="empty-reason",
),
pytest.param('[[review]]\nnumber = 3\nreason = "r\n', id="broken-toml"),
pytest.param("[reviews]\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_reviews(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_reviews(tmp_path, name)
class TestRepositoryReviews:
def test_default_reviews_dir_is_repository_reviews(self) -> None:
directory = default_reviews_dir()
assert directory.name == "reviews"
assert (directory.parent / "pyproject.toml").is_file()
def test_tiasjudgemodel_review_list_matches_plan(self) -> None:
# PLAN_02「舌色データセットの重複 5 組の扱い」:
# 122/184・548/566・381/382・515/516 の両方と 509(508 は残す)
reviews = load_reviews(default_reviews_dir(), "tiasjudgemodel")
assert reviews.numbers == frozenset({122, 184, 548, 566, 381, 382, 515, 516, 509})
assert 508 not in reviews.numbers
def test_repository_has_no_holds_dir(self) -> None:
# 2026-10-08: 保留リスト(holds/)は要確認リスト(reviews/)に置き換えた
assert not (default_reviews_dir().parent / "holds").exists()
def test_repository_review_list_uses_lf(self) -> None:
data = (default_reviews_dir() / "tiasjudgemodel.toml").read_bytes()
assert b"\r\n" not in data
class TestReviewEntries:
def test_entries_keep_reason_resolve_and_since(self, tmp_path: Path) -> None:
_write(
tmp_path,
"src",
'[[review]]\nnumber = 4\nreason = "理由"\nresolve = "確かめ方"\nsince = 2026-10-06\n',
)
entry = load_reviews(tmp_path, "src").entries[4]
assert (entry.number, entry.reason, entry.resolve, entry.since.isoformat()) == (
4,
"理由",
"確かめ方",
"2026-10-06",
)
def test_message_for_duplicated_number_is_one_line(self, tmp_path: Path) -> None:
_write(tmp_path, "src", ENTRY.format(n=3) + ENTRY.format(n=3))
with pytest.raises(ValueError) as excinfo:
load_reviews(tmp_path, "src")
assert "\n" not in str(excinfo.value)
assert str(tmp_path) not in str(excinfo.value)