"""所見の種類 `review`(要確認の印)の値の型(2026-10-08 のユーザー判断.testing.md 必須対象 4).
- 値は `{"reason": str, "resolve": str, "since": "YYYY-MM-DD", "numbers": [int, ...]}`
- 未知のキーは拒否.strict(型の暗黙の変換をしない)
- since は日付.numbers は 1 個以上・1 以上・昇順・重複なし
"""
import json
import pytest
from pydantic import ValidationError
from tiasdata.kinds import (
REVIEW_KIND,
Review,
annotation_value_type,
dump_annotation_value,
validate_annotation_value,
)
VALID = {"reason": "理由", "resolve": "確かめ方", "since": "2026-10-06", "numbers": [122, 184]}
def _with(**changes: object) -> dict[str, object]:
value = dict(VALID)
value.update(changes)
return value
class TestValidValues:
def test_kind_name_is_review(self) -> None:
assert REVIEW_KIND == "review"
assert annotation_value_type("review") is Review
def test_valid_value_is_accepted(self) -> None:
value = validate_annotation_value("review", VALID)
assert isinstance(value, Review)
assert value.numbers == [122, 184]
assert value.since == "2026-10-06"
def test_single_number_is_accepted(self) -> None:
assert validate_annotation_value("review", _with(numbers=[1])).numbers == [1]
def test_dump_is_stable_json_and_round_trips(self) -> None:
value = validate_annotation_value("review", VALID)
text = dump_annotation_value(value)
assert json.loads(text) == VALID
# 同じ値は常に同じ文字列(キーの順を固定し空白を入れない)
reordered = dict(reversed(list(VALID.items())))
assert dump_annotation_value(validate_annotation_value("review", reordered)) == text
assert " " not in text.replace("確かめ方", "").replace("理由", "")
class TestRejected:
@pytest.mark.parametrize(
"value",
[
pytest.param(_with(extra="x"), id="unknown-key"),
pytest.param({k: v for k, v in VALID.items() if k != "reason"}, id="missing-reason"),
pytest.param({k: v for k, v in VALID.items() if k != "resolve"}, id="missing-resolve"),
pytest.param({k: v for k, v in VALID.items() if k != "since"}, id="missing-since"),
pytest.param({k: v for k, v in VALID.items() if k != "numbers"}, id="missing-numbers"),
pytest.param(_with(reason=""), id="empty-reason"),
pytest.param(_with(resolve=""), id="empty-resolve"),
pytest.param(_with(reason=1), id="reason-not-str"),
pytest.param(_with(since="2026/10/06"), id="since-slash"),
pytest.param(_with(since="2026-13-01"), id="since-invalid-month"),
pytest.param(_with(since="2026-02-30"), id="since-invalid-day"),
pytest.param(_with(since="2026-10-06T00:00:00"), id="since-datetime"),
pytest.param(_with(since="いつか"), id="since-not-date"),
pytest.param(_with(numbers=[]), id="numbers-empty"),
pytest.param(_with(numbers=[0]), id="numbers-zero"),
pytest.param(_with(numbers=[-1, 3]), id="numbers-negative"),
pytest.param(_with(numbers=[184, 122]), id="numbers-not-sorted"),
pytest.param(_with(numbers=[122, 122]), id="numbers-duplicated"),
pytest.param(_with(numbers=["122"]), id="numbers-str-strict"),
pytest.param(_with(numbers=[1.0]), id="numbers-float-strict"),
pytest.param(_with(numbers=[True]), id="numbers-bool-strict"),
pytest.param(_with(numbers=122), id="numbers-not-list"),
],
)
def test_invalid_value_is_rejected(self, value: dict[str, object]) -> None:
with pytest.raises(ValidationError):
validate_annotation_value("review", value)
def test_value_is_frozen(self) -> None:
value = validate_annotation_value("review", VALID)
with pytest.raises(ValidationError):
value.reason = "別" # type: ignore[misc]