fix(loaders): resource pools validated consistently before grant merge
_merge_resource_grants silently dropped non-int pool values when features were present (a "5" pool vanished, leaving only the feat grant) while Pydantic coerced the same value when features were absent. _validated_resources now runs the JSON resources through TypeAdapter(dict[str, int]) -- the same lax coercion as Combatant.resources -- so both paths coerce-or-fail identically, and grants are added to the coerced ints.
This commit is contained in:
@@ -221,6 +221,46 @@ class TestSpells:
|
||||
assert c.spells == []
|
||||
|
||||
|
||||
class TestResourcePools:
|
||||
def test_string_pool_coerced_without_features(self, tmp_path: Path) -> None:
|
||||
data = _minimal_payload(resources={"channel_energy": "5"})
|
||||
c = load_combatant(_write(tmp_path, data))
|
||||
assert c.resources == {"channel_energy": 5}
|
||||
|
||||
def test_string_pool_coerced_with_granting_feature(self, tmp_path: Path) -> None:
|
||||
data = _minimal_payload(
|
||||
resources={"channel_energy": "5"},
|
||||
features=["Exalted of the Society (cleric, pathfinder society)"],
|
||||
)
|
||||
c = load_combatant(_write(tmp_path, data))
|
||||
assert c.resources == {"channel_energy": 6}
|
||||
|
||||
def test_non_numeric_pool_value_raises_with_feature(self, tmp_path: Path) -> None:
|
||||
data = _minimal_payload(
|
||||
resources={"channel_energy": "lots"}, features=["Iron Will"]
|
||||
)
|
||||
with pytest.raises(CombatantLoadError, match="resources must be an object"):
|
||||
load_combatant(_write(tmp_path, data))
|
||||
|
||||
def test_non_numeric_pool_value_raises_without_feature(self, tmp_path: Path) -> None:
|
||||
data = _minimal_payload(resources={"channel_energy": "lots"})
|
||||
with pytest.raises(CombatantLoadError, match="resources must be an object"):
|
||||
load_combatant(_write(tmp_path, data))
|
||||
|
||||
def test_null_resources_raises(self, tmp_path: Path) -> None:
|
||||
data = _minimal_payload(resources=None)
|
||||
with pytest.raises(CombatantLoadError, match="resources must be an object"):
|
||||
load_combatant(_write(tmp_path, data))
|
||||
|
||||
def test_grant_added_to_int_pool(self, tmp_path: Path) -> None:
|
||||
data = _minimal_payload(
|
||||
resources={"channel_energy": 2},
|
||||
features=["Exalted of the Society (cleric, pathfinder society)"],
|
||||
)
|
||||
c = load_combatant(_write(tmp_path, data))
|
||||
assert c.resources == {"channel_energy": 3}
|
||||
|
||||
|
||||
class TestSpellSlots:
|
||||
def test_numeric_string_keys_converted(self, tmp_path: Path) -> None:
|
||||
data = _minimal_payload(spell_slots={"1": 4, "2": 3})
|
||||
|
||||
Reference in New Issue
Block a user