core/tests/test_comprehension_state.py

173 lines
5.5 KiB
Python

from __future__ import annotations
from decimal import Decimal
import pytest
from generate.comprehension.state import (
ComprehensionState,
ComprehensionStateError,
EntityRef,
ExpectationFrame,
PartialOp,
QuantityRef,
QuestionTargetSlot,
)
def test_canonical_bytes_are_deterministic_for_equal_states() -> None:
first = ComprehensionState(
entities=(
EntityRef(
canonical_name="tina",
gender="female",
first_mention_position=0,
),
),
quantities=(
QuantityRef(
value=Decimal("12.00"),
unit="dollars",
unit_class="currency",
owner_entity="tina",
mention_position=1,
),
),
operations=(
PartialOp(
operator_kind="accumulation_verb",
subject_entity="tina",
object_entity=None,
quantity_index=0,
position=2,
),
),
question_target=QuestionTargetSlot(
kind="continuous_quantity",
entity="tina",
unit_class="currency",
position=3,
),
expectation=ExpectationFrame(
allowed_categories=("residual_modifier", "state_continuation_verb"),
reason="question frame remains open",
),
)
second = ComprehensionState(
entities=(
EntityRef(
canonical_name="tina",
gender="female",
first_mention_position=0,
),
),
quantities=(
QuantityRef(
value=Decimal("12.0"),
unit="dollars",
unit_class="currency",
owner_entity="tina",
mention_position=1,
),
),
operations=(
PartialOp(
operator_kind="accumulation_verb",
subject_entity="tina",
object_entity=None,
quantity_index=0,
position=2,
),
),
question_target=QuestionTargetSlot(
kind="continuous_quantity",
entity="tina",
unit_class="currency",
position=3,
),
expectation=ExpectationFrame(
allowed_categories=("residual_modifier", "state_continuation_verb"),
reason="question frame remains open",
),
)
assert first.canonical_bytes() == second.canonical_bytes()
assert first.canonical_hash() == second.canonical_hash()
assert (
first.canonical_bytes()
== b'{"entities":[{"canonical_name":"tina","first_mention_position":0,"gender":"female"}],"expectation":{"allowed_categories":["residual_modifier","state_continuation_verb"],"reason":"question frame remains open"},"operations":[{"object_entity":null,"operator_kind":"accumulation_verb","position":2,"quantity_index":0,"subject_entity":"tina"}],"quantities":[{"mention_position":1,"owner_entity":"tina","unit":"dollars","unit_class":"currency","value":"12"}],"question_target":{"entity":"tina","kind":"continuous_quantity","position":3,"unit_class":"currency"}}'
)
def test_optional_fields_serialize_as_null() -> None:
state = ComprehensionState(
entities=(),
quantities=(),
operations=(),
question_target=None,
expectation=None,
)
assert (
state.canonical_bytes()
== b'{"entities":[],"expectation":null,"operations":[],"quantities":[],"question_target":null}'
)
def test_entity_ref_refuses_invalid_gender() -> None:
with pytest.raises(ComprehensionStateError, match="EntityRef.gender"):
EntityRef(
canonical_name="tina",
gender="plural", # type: ignore[arg-type]
first_mention_position=0,
)
def test_quantity_ref_refuses_non_decimal_and_missing_unit_shape() -> None:
with pytest.raises(ComprehensionStateError, match="QuantityRef.value"):
QuantityRef(
value="12.0", # type: ignore[arg-type]
unit="dollars",
unit_class="currency",
owner_entity="tina",
mention_position=1,
)
with pytest.raises(
ComprehensionStateError,
match="QuantityRef.unit and QuantityRef.unit_class cannot both be None",
):
QuantityRef(
value=Decimal("12"),
unit=None,
unit_class=None,
owner_entity=None,
mention_position=1,
)
def test_expectation_frame_refuses_empty_or_unknown_categories() -> None:
with pytest.raises(
ComprehensionStateError,
match="ExpectationFrame.allowed_categories must not be empty",
):
ExpectationFrame(allowed_categories=(), reason="x")
with pytest.raises(ComprehensionStateError, match="allowed_categories"):
ExpectationFrame(
allowed_categories=("unsupported",),
reason="x",
)
def test_comprehension_state_refuses_non_tuple_and_wrong_member_types() -> None:
with pytest.raises(ComprehensionStateError, match="entities must be tuple"):
ComprehensionState(
entities=[], # type: ignore[arg-type]
quantities=(),
operations=(),
)
with pytest.raises(ComprehensionStateError, match="quantities\\[0\\]"):
ComprehensionState(
entities=(),
quantities=("bad",), # type: ignore[arg-type]
operations=(),
)