Newer
Older
TIASDataHub / tests / importers / test_reviews.py
"""要確認リスト `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)