first commit
This commit is contained in:
361
api/tests/test_effects.py
Normal file
361
api/tests/test_effects.py
Normal file
@@ -0,0 +1,361 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user