fix(loaders): coerce combatant level with Pydantic-consistent rules

Feat resolution read level with an isinstance(int) check and fell back
to 1, while Pydantic accepts numeric strings and lossless floats: a
level-4 sheet authored as "4" got Toughness for level 1. _parse_level
now runs the value through TypeAdapter(int) (same lax coercion as the
model) and raises CombatantLoadError for values the model would reject.
This commit is contained in:
2026-09-20 17:05:36 +02:00
parent b8e96294a6
commit 9adc87c9e1
2 changed files with 43 additions and 3 deletions
+22
View File
@@ -146,6 +146,28 @@ class TestFeatures:
c = load_combatant(_write(tmp_path, data))
assert c.features[0].effects == toughness(7).effects
def test_toughness_numeric_string_level(self, tmp_path: Path) -> None:
data = _minimal_payload(level="4", features=["Toughness"])
c = load_combatant(_write(tmp_path, data))
assert c.level == 4
assert c.features[0].effects == toughness(4).effects
def test_toughness_lossless_float_level(self, tmp_path: Path) -> None:
data = _minimal_payload(level=4.0, features=["Toughness"])
c = load_combatant(_write(tmp_path, data))
assert c.level == 4
assert c.features[0].effects == toughness(4).effects
def test_non_numeric_level_raises(self, tmp_path: Path) -> None:
data = _minimal_payload(level="abc", features=["Toughness"])
with pytest.raises(CombatantLoadError, match="level must be an integer"):
load_combatant(_write(tmp_path, data))
def test_fractional_level_raises(self, tmp_path: Path) -> None:
data = _minimal_payload(level=4.5, features=["Toughness"])
with pytest.raises(CombatantLoadError, match="level must be an integer"):
load_combatant(_write(tmp_path, data))
def test_level_default_one(self, tmp_path: Path) -> None:
data = _minimal_payload(level=1, features=["Toughness"])
c = load_combatant(_write(tmp_path, data))