""" Unit tests for Effect dataclass. Tests all effect types, tick() method, stacking, and serialization. """ import pytest from app.models.effects import Effect from app.models.enums import EffectType, StatType def test_effect_creation(): """Test creating an Effect instance.""" effect = Effect( effect_id="burn_1", name="Burning", effect_type=EffectType.DOT, duration=3, power=5, ) assert effect.effect_id == "burn_1" assert effect.name == "Burning" assert effect.effect_type == EffectType.DOT assert effect.duration == 3 assert effect.power == 5 assert effect.stacks == 1 assert effect.max_stacks == 5 def test_dot_effect_tick(): """Test DOT (damage over time) effect ticking.""" effect = Effect( effect_id="poison_1", name="Poisoned", effect_type=EffectType.DOT, duration=3, power=10, stacks=2, ) result = effect.tick() assert result["effect_type"] == "dot" assert result["value"] == 20 # 10 power × 2 stacks assert result["expired"] == False assert effect.duration == 2 # Reduced by 1 def test_hot_effect_tick(): """Test HOT (heal over time) effect ticking.""" effect = Effect( effect_id="regen_1", name="Regeneration", effect_type=EffectType.HOT, duration=5, power=8, stacks=1, ) result = effect.tick() assert result["effect_type"] == "hot" assert result["value"] == 8 # 8 power × 1 stack assert result["expired"] == False assert effect.duration == 4 def test_stun_effect_tick(): """Test STUN effect ticking.""" effect = Effect( effect_id="stun_1", name="Stunned", effect_type=EffectType.STUN, duration=1, power=0, ) result = effect.tick() assert result["effect_type"] == "stun" assert result.get("stunned") == True assert effect.duration == 0 def test_shield_effect_tick(): """Test SHIELD effect ticking.""" effect = Effect( effect_id="shield_1", name="Shield", effect_type=EffectType.SHIELD, duration=3, power=50, ) result = effect.tick() assert result["effect_type"] == "shield" assert result["shield_remaining"] == 50 assert effect.duration == 2 def test_buff_effect_tick(): """Test BUFF effect ticking.""" effect = Effect( effect_id="str_buff_1", name="Strength Boost", effect_type=EffectType.BUFF, duration=4, power=5, stat_affected=StatType.STRENGTH, stacks=2, ) result = effect.tick() assert result["effect_type"] == "buff" assert result["stat_affected"] == "strength" assert result["stat_modifier"] == 10 # 5 power × 2 stacks assert effect.duration == 3 def test_debuff_effect_tick(): """Test DEBUFF effect ticking.""" effect = Effect( effect_id="weak_1", name="Weakened", effect_type=EffectType.DEBUFF, duration=2, power=3, stat_affected=StatType.STRENGTH, ) result = effect.tick() assert result["effect_type"] == "debuff" assert result["stat_affected"] == "strength" assert result["stat_modifier"] == 3 assert effect.duration == 1 def test_effect_expiration(): """Test that effect expires when duration reaches 0.""" effect = Effect( effect_id="burn_1", name="Burning", effect_type=EffectType.DOT, duration=1, power=5, ) result = effect.tick() assert result["expired"] == True assert effect.duration == 0 def test_effect_stacking(): """Test apply_stack() increases stacks up to max.""" effect = Effect( effect_id="poison_1", name="Poison", effect_type=EffectType.DOT, duration=3, power=5, max_stacks=5, ) assert effect.stacks == 1 effect.apply_stack() assert effect.stacks == 2 effect.apply_stack() assert effect.stacks == 3 # Apply 3 more to reach max effect.apply_stack() effect.apply_stack() effect.apply_stack() assert effect.stacks == 5 # Capped at max_stacks # Try to apply one more - should still be 5 effect.apply_stack() assert effect.stacks == 5 def test_shield_damage_absorption(): """Test reduce_shield() method.""" effect = Effect( effect_id="shield_1", name="Shield", effect_type=EffectType.SHIELD, duration=3, power=50, stacks=1, ) # Shield absorbs 20 damage remaining = effect.reduce_shield(20) assert remaining == 0 # All damage absorbed assert effect.power == 30 # Shield reduced by 20 # Shield absorbs 20 more remaining = effect.reduce_shield(20) assert remaining == 0 assert effect.power == 10 # Shield takes 15 damage, breaks completely remaining = effect.reduce_shield(15) assert remaining == 5 # 5 damage passes through assert effect.power == 0 assert effect.duration == 0 # Effect expires def test_shield_with_stacks(): """Test shield absorption with multiple stacks.""" effect = Effect( effect_id="shield_1", name="Shield", effect_type=EffectType.SHIELD, duration=3, power=20, stacks=3, ) # Total shield = 20 × 3 = 60 # Apply 50 damage remaining = effect.reduce_shield(50) assert remaining == 0 # All absorbed # Shield reduced: 50 / 3 stacks = 16.67 per stack assert effect.power < 20 # Power reduced def test_effect_serialization(): """Test to_dict() serialization.""" effect = Effect( effect_id="burn_1", name="Burning", effect_type=EffectType.DOT, duration=3, power=5, stacks=2, max_stacks=5, source="fireball", ) data = effect.to_dict() assert data["effect_id"] == "burn_1" assert data["name"] == "Burning" assert data["effect_type"] == "dot" assert data["duration"] == 3 assert data["power"] == 5 assert data["stacks"] == 2 assert data["max_stacks"] == 5 assert data["source"] == "fireball" def test_effect_deserialization(): """Test from_dict() deserialization.""" data = { "effect_id": "regen_1", "name": "Regeneration", "effect_type": "hot", "duration": 5, "power": 10, "stat_affected": None, "stacks": 1, "max_stacks": 5, "source": "potion", } effect = Effect.from_dict(data) assert effect.effect_id == "regen_1" assert effect.name == "Regeneration" assert effect.effect_type == EffectType.HOT assert effect.duration == 5 assert effect.power == 10 assert effect.stacks == 1 def test_effect_with_stat_deserialization(): """Test deserializing effect with stat_affected.""" data = { "effect_id": "buff_1", "name": "Strength Boost", "effect_type": "buff", "duration": 3, "power": 5, "stat_affected": "strength", "stacks": 1, "max_stacks": 5, "source": "spell", } effect = Effect.from_dict(data) assert effect.stat_affected == StatType.STRENGTH assert effect.effect_type == EffectType.BUFF def test_effect_round_trip_serialization(): """Test that serialization and deserialization preserve data.""" original = Effect( effect_id="test_effect", name="Test Effect", effect_type=EffectType.DEBUFF, duration=10, power=15, stat_affected=StatType.CONSTITUTION, stacks=3, max_stacks=5, source="enemy_spell", ) # Serialize then deserialize data = original.to_dict() restored = Effect.from_dict(data) assert restored.effect_id == original.effect_id assert restored.name == original.name assert restored.effect_type == original.effect_type assert restored.duration == original.duration assert restored.power == original.power assert restored.stat_affected == original.stat_affected assert restored.stacks == original.stacks assert restored.max_stacks == original.max_stacks assert restored.source == original.source def test_effect_repr(): """Test string representation.""" effect = Effect( effect_id="poison_1", name="Poison", effect_type=EffectType.DOT, duration=3, power=5, stacks=2, ) repr_str = repr(effect) assert "Poison" in repr_str assert "dot" in repr_str def test_non_shield_reduce_shield(): """Test that reduce_shield() on non-SHIELD effects returns full damage.""" effect = Effect( effect_id="burn_1", name="Burning", effect_type=EffectType.DOT, duration=3, power=5, ) remaining = effect.reduce_shield(50) assert remaining == 50 # All damage passes through for non-shield effects