"""Batch 2 (V6b): the next 32 PC-sheet spells added to data/spells/. Given: the registry directory with the 11 original spells plus batch 1 (43) When: load_spell_registry / load_spell is called Then: every batch-2 spell loads with the expected level/school/range/save, the registry grew by exactly 32 over batch 1, and every effect validates against the schema (parsing raises on any unknown field value or condition). """ from __future__ import annotations from pathlib import Path import pytest from pf1e_simulator.conditions import CONDITIONS_BY_NAME from pf1e_simulator.spells import ( AreaEffect, BuffEffect, ConditionEffect, DamageEffect, HealEffect, OneShotEffect, SpellEffect, SpellSpec, SummonEffect, load_spell, load_spell_registry, ) _SPELLS_DIR = Path(__file__).resolve().parent.parent / "data" / "spells" _BATCH1_COUNT = 43 # name -> (file stem, level, school, range, save) _BATCH2: dict[str, tuple[str, int, str, str, str]] = { "Enhanced Diplomacy": ("enhanced_diplomacy", 0, "divination", "touch", "will"), "Face of the Devourer": ("face_of_the_devourer", 1, "transmutation", "touch", "fort"), "Flurry of Snowballs": ("flurry_of_snowballs", 2, "evocation", "close", "ref"), "Forbid Action": ("forbid_action", 1, "enchantment", "close", "will"), "Grasp": ("grasp", 0, "divination", "personal", "none"), "Guidance": ("guidance", 0, "divination", "touch", "will"), "Heroism": ("heroism", 2, "enchantment", "touch", "will"), "Hex Vulnerability": ("hex_vulnerability", 1, "necromancy", "close", "will"), "Hidden Diplomacy": ("hidden_diplomacy", 1, "enchantment", "close", "will"), "Hide from Undead": ("hide_from_undead", 1, "abjuration", "touch", "will"), "Ice Slick": ("ice_slick", 2, "evocation", "close", "ref"), "Ill Omen": ("ill_omen", 1, "enchantment", "close", "none"), "Inflict Light Wounds": ("inflict_light_wounds", 1, "necromancy", "touch", "will"), "Ironskin": ("ironskin", 2, "transmutation", "personal", "none"), "Light": ("light", 0, "evocation", "touch", "none"), "Mage Hand": ("mage_hand", 0, "transmutation", "close", "none"), "Make Whole": ("make_whole", 2, "transmutation", "close", "will"), "Masterwork Transformation": ( "masterwork_transformation", 2, "transmutation", "touch", "none", ), "Mending": ("mending", 0, "transmutation", "close", "will"), "Message": ("message", 0, "transmutation", "medium", "none"), "Mirror Image": ("mirror_image", 2, "illusion", "personal", "none"), "Oath of Anonymity": ("oath_of_anonymity", 0, "illusion", "personal", "none"), "Obscuring Mist": ("obscuring_mist", 1, "conjuration", "close", "none"), "Open/Close": ("open_close", 0, "transmutation", "close", "will"), "Owl's Wisdom": ("owls_wisdom", 2, "transmutation", "touch", "will"), "Prestidigitation": ("prestidigitation", 0, "universal", "close", "none"), "Protection from Evil": ("protection_from_evil", 1, "abjuration", "touch", "will"), "Protection from Evil, Communal": ( "protection_from_evil_communal", 2, "abjuration", "touch", "will", ), "Protection from Law": ("protection_from_law", 1, "abjuration", "touch", "will"), "Protection from Law, Communal": ( "protection_from_law_communal", 2, "abjuration", "touch", "will", ), "Purify Food and Drink": ("purify_food_and_drink", 0, "transmutation", "close", "will"), "Putrefy Food and Drink": ("putrefy_food_and_drink", 0, "transmutation", "close", "will"), } _EFFECT_TYPES = ( DamageEffect, ConditionEffect, HealEffect, BuffEffect, OneShotEffect, AreaEffect, SummonEffect, ) def _walk_effects(effects: tuple[SpellEffect, ...]) -> list[SpellEffect]: flat: list[SpellEffect] = [] for effect in effects: flat.append(effect) if isinstance(effect, AreaEffect): flat.extend(_walk_effects(effect.effects)) return flat class TestBatch2Loading: """Each batch-2 spell loads with the expected metadata.""" @pytest.mark.parametrize( ("name", "stem", "level", "school", "range_", "save"), [ (name, *fields) for name, fields in sorted(_BATCH2.items()) ], ) def test_spell_fields( self, name: str, stem: str, level: int, school: str, range_: str, save: str ) -> None: spell = load_spell(_SPELLS_DIR / f"{stem}.json") assert spell.name == name assert spell.level == level assert spell.school == school assert spell.range == range_ assert spell.save == save assert spell.base_dc == 10 assert spell.description def test_registry_grew_by_exactly_32(self) -> None: reg = load_spell_registry(_SPELLS_DIR) # Batch 3 adds more spells; the exact total is pinned there. assert len(reg) >= _BATCH1_COUNT + 32 assert set(_BATCH2) <= set(reg) def test_every_effect_validates(self) -> None: reg = load_spell_registry(_SPELLS_DIR) for name in _BATCH2: spell: SpellSpec = reg[name] for effect in _walk_effects(spell.effects): assert isinstance(effect, _EFFECT_TYPES) if isinstance(effect, ConditionEffect): assert effect.condition_name in CONDITIONS_BY_NAME if isinstance(effect, BuffEffect): assert effect.modifiers