first commit

This commit is contained in:
2025-11-24 23:10:55 -06:00
commit 8315fa51c9
279 changed files with 74600 additions and 0 deletions

361
api/tests/test_effects.py Normal file
View 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