Newer
Older
TIASDataHub / tests / kinds / test_tongue_color.py
"""所見の種類 `tongue_color` の値の型(SPEC_02「kinds」,testing.md 必須対象 4).

- 値は `{"label": <slug>, "severity": "strong" | "mild" | "normal"}`
- slug は `pale`・`light_red`・`red`・`dark_red`・`purple`
- `normal` は `light_red` のときだけ
- 未知の値・未知のキー・無効な組み合わせは拒否する
"""

import json

import pytest
from pydantic import ValidationError

from tiasdata.kinds import (
    TongueColor,
    annotation_value_type,
    dump_annotation_value,
    validate_annotation_value,
)

LABELS = ["pale", "light_red", "red", "dark_red", "purple"]
NOT_LIGHT_RED = [label for label in LABELS if label != "light_red"]


class TestValidValues:
    @pytest.mark.parametrize("label", NOT_LIGHT_RED)
    @pytest.mark.parametrize("severity", ["strong", "mild"])
    def test_non_light_red_with_strong_or_mild_is_accepted(self, label: str, severity: str) -> None:
        value = validate_annotation_value("tongue_color", {"label": label, "severity": severity})
        assert isinstance(value, TongueColor)
        assert value.label == label
        assert value.severity == severity

    def test_light_red_normal_is_accepted(self) -> None:
        value = validate_annotation_value(
            "tongue_color", {"label": "light_red", "severity": "normal"}
        )
        assert value == TongueColor(label="light_red", severity="normal")

    def test_value_type_of_tongue_color(self) -> None:
        assert annotation_value_type("tongue_color") is TongueColor


class TestInvalidValues:
    @pytest.mark.parametrize("label", NOT_LIGHT_RED)
    def test_normal_other_than_light_red_is_rejected(self, label: str) -> None:
        with pytest.raises(ValidationError):
            validate_annotation_value("tongue_color", {"label": label, "severity": "normal"})

    @pytest.mark.parametrize(
        "label", ["Red", "pink", "", "淡紅", "light-red", "lightred", "dark red"]
    )
    def test_unknown_label_is_rejected(self, label: str) -> None:
        with pytest.raises(ValidationError):
            validate_annotation_value("tongue_color", {"label": label, "severity": "mild"})

    @pytest.mark.parametrize("severity", ["Strong", "weak", "", "あり", "none"])
    def test_unknown_severity_is_rejected(self, severity: str) -> None:
        with pytest.raises(ValidationError):
            validate_annotation_value("tongue_color", {"label": "red", "severity": severity})

    def test_unknown_key_is_rejected(self) -> None:
        with pytest.raises(ValidationError):
            validate_annotation_value(
                "tongue_color", {"label": "red", "severity": "mild", "note": "x"}
            )

    @pytest.mark.parametrize("value", [{"label": "red"}, {"severity": "mild"}, {}])
    def test_missing_key_is_rejected(self, value: dict) -> None:
        with pytest.raises(ValidationError):
            validate_annotation_value("tongue_color", value)

    @pytest.mark.parametrize(
        "value",
        [
            {"label": 1, "severity": "mild"},
            {"label": "red", "severity": None},
            {"label": ["red"], "severity": "mild"},
        ],
    )
    def test_non_string_value_is_rejected(self, value: dict) -> None:
        with pytest.raises(ValidationError):
            validate_annotation_value("tongue_color", value)

    def test_unknown_annotation_kind_is_value_error(self) -> None:
        with pytest.raises(ValueError, match="no_such_kind"):
            validate_annotation_value("no_such_kind", {"label": "red", "severity": "mild"})

    def test_asset_kind_has_no_value_type(self) -> None:
        # ファイルの種類は所見の値の型を持たない
        with pytest.raises(ValueError):
            annotation_value_type("tongue_crop_masked")

    def test_model_is_immutable(self) -> None:
        value = TongueColor(label="red", severity="mild")
        with pytest.raises(ValidationError):
            value.label = "pale"  # type: ignore[misc]


class TestDump:
    def test_dump_is_json_with_label_and_severity(self) -> None:
        text = dump_annotation_value(TongueColor(label="dark_red", severity="strong"))
        assert json.loads(text) == {"label": "dark_red", "severity": "strong"}

    def test_dump_is_deterministic(self) -> None:
        # 同じ値は常に同じ文字列(キーの順・空白が固定)
        a = dump_annotation_value(
            validate_annotation_value("tongue_color", {"severity": "mild", "label": "pale"})
        )
        b = dump_annotation_value(
            validate_annotation_value("tongue_color", {"label": "pale", "severity": "mild"})
        )
        assert a == b

    def test_dump_round_trips_through_validation(self) -> None:
        original = TongueColor(label="purple", severity="mild")
        restored = validate_annotation_value(
            "tongue_color", json.loads(dump_annotation_value(original))
        )
        assert restored == original