- Add ItemRarity enum with 5 tiers (common, uncommon, rare, epic, legendary) - Add rarity field to Item dataclass with COMMON default - Update Item serialization (to_dict/from_dict) for rarity - Export ItemRarity from models package - Add 24 comprehensive unit tests for Item and ItemRarity Part of Phase 4 Week 2: Inventory & Equipment System (Task 2.1)
388 lines
12 KiB
Python
388 lines
12 KiB
Python
"""
|
|
Unit tests for Item dataclass and ItemRarity enum.
|
|
|
|
Tests item creation, rarity, type checking, and serialization.
|
|
"""
|
|
|
|
import pytest
|
|
from app.models.items import Item
|
|
from app.models.enums import ItemType, ItemRarity, DamageType
|
|
|
|
|
|
class TestItemRarityEnum:
|
|
"""Tests for ItemRarity enum."""
|
|
|
|
def test_rarity_values(self):
|
|
"""Test all rarity values exist and have correct string values."""
|
|
assert ItemRarity.COMMON.value == "common"
|
|
assert ItemRarity.UNCOMMON.value == "uncommon"
|
|
assert ItemRarity.RARE.value == "rare"
|
|
assert ItemRarity.EPIC.value == "epic"
|
|
assert ItemRarity.LEGENDARY.value == "legendary"
|
|
|
|
def test_rarity_from_string(self):
|
|
"""Test creating rarity from string value."""
|
|
assert ItemRarity("common") == ItemRarity.COMMON
|
|
assert ItemRarity("uncommon") == ItemRarity.UNCOMMON
|
|
assert ItemRarity("rare") == ItemRarity.RARE
|
|
assert ItemRarity("epic") == ItemRarity.EPIC
|
|
assert ItemRarity("legendary") == ItemRarity.LEGENDARY
|
|
|
|
def test_rarity_count(self):
|
|
"""Test that there are exactly 5 rarity tiers."""
|
|
assert len(ItemRarity) == 5
|
|
|
|
|
|
class TestItemCreation:
|
|
"""Tests for creating Item instances."""
|
|
|
|
def test_create_basic_item(self):
|
|
"""Test creating a basic item with minimal fields."""
|
|
item = Item(
|
|
item_id="test_item",
|
|
name="Test Item",
|
|
item_type=ItemType.QUEST_ITEM,
|
|
)
|
|
|
|
assert item.item_id == "test_item"
|
|
assert item.name == "Test Item"
|
|
assert item.item_type == ItemType.QUEST_ITEM
|
|
assert item.rarity == ItemRarity.COMMON # Default
|
|
assert item.description == ""
|
|
assert item.value == 0
|
|
assert item.is_tradeable == True
|
|
|
|
def test_item_default_rarity_is_common(self):
|
|
"""Test that items default to COMMON rarity."""
|
|
item = Item(
|
|
item_id="sword_1",
|
|
name="Iron Sword",
|
|
item_type=ItemType.WEAPON,
|
|
)
|
|
|
|
assert item.rarity == ItemRarity.COMMON
|
|
|
|
def test_create_item_with_rarity(self):
|
|
"""Test creating items with different rarity levels."""
|
|
uncommon = Item(
|
|
item_id="sword_2",
|
|
name="Steel Sword",
|
|
item_type=ItemType.WEAPON,
|
|
rarity=ItemRarity.UNCOMMON,
|
|
)
|
|
assert uncommon.rarity == ItemRarity.UNCOMMON
|
|
|
|
rare = Item(
|
|
item_id="sword_3",
|
|
name="Mithril Sword",
|
|
item_type=ItemType.WEAPON,
|
|
rarity=ItemRarity.RARE,
|
|
)
|
|
assert rare.rarity == ItemRarity.RARE
|
|
|
|
epic = Item(
|
|
item_id="sword_4",
|
|
name="Dragon Sword",
|
|
item_type=ItemType.WEAPON,
|
|
rarity=ItemRarity.EPIC,
|
|
)
|
|
assert epic.rarity == ItemRarity.EPIC
|
|
|
|
legendary = Item(
|
|
item_id="sword_5",
|
|
name="Excalibur",
|
|
item_type=ItemType.WEAPON,
|
|
rarity=ItemRarity.LEGENDARY,
|
|
)
|
|
assert legendary.rarity == ItemRarity.LEGENDARY
|
|
|
|
def test_create_weapon(self):
|
|
"""Test creating a weapon with all weapon-specific fields."""
|
|
weapon = Item(
|
|
item_id="fire_sword",
|
|
name="Flame Blade",
|
|
item_type=ItemType.WEAPON,
|
|
rarity=ItemRarity.RARE,
|
|
description="A sword wreathed in flames.",
|
|
value=500,
|
|
damage=25,
|
|
damage_type=DamageType.PHYSICAL,
|
|
crit_chance=0.15,
|
|
crit_multiplier=2.5,
|
|
elemental_damage_type=DamageType.FIRE,
|
|
physical_ratio=0.7,
|
|
elemental_ratio=0.3,
|
|
)
|
|
|
|
assert weapon.is_weapon() == True
|
|
assert weapon.is_elemental_weapon() == True
|
|
assert weapon.damage == 25
|
|
assert weapon.crit_chance == 0.15
|
|
assert weapon.crit_multiplier == 2.5
|
|
assert weapon.elemental_damage_type == DamageType.FIRE
|
|
assert weapon.physical_ratio == 0.7
|
|
assert weapon.elemental_ratio == 0.3
|
|
|
|
def test_create_armor(self):
|
|
"""Test creating armor with defense/resistance."""
|
|
armor = Item(
|
|
item_id="plate_armor",
|
|
name="Steel Plate Armor",
|
|
item_type=ItemType.ARMOR,
|
|
rarity=ItemRarity.UNCOMMON,
|
|
description="Heavy steel armor.",
|
|
value=300,
|
|
defense=15,
|
|
resistance=5,
|
|
)
|
|
|
|
assert armor.is_armor() == True
|
|
assert armor.defense == 15
|
|
assert armor.resistance == 5
|
|
|
|
def test_create_consumable(self):
|
|
"""Test creating a consumable item."""
|
|
potion = Item(
|
|
item_id="health_potion",
|
|
name="Health Potion",
|
|
item_type=ItemType.CONSUMABLE,
|
|
rarity=ItemRarity.COMMON,
|
|
description="Restores 50 HP.",
|
|
value=25,
|
|
)
|
|
|
|
assert potion.is_consumable() == True
|
|
assert potion.is_tradeable == True
|
|
|
|
|
|
class TestItemTypeMethods:
|
|
"""Tests for item type checking methods."""
|
|
|
|
def test_is_weapon(self):
|
|
"""Test is_weapon() method."""
|
|
weapon = Item(item_id="w", name="W", item_type=ItemType.WEAPON)
|
|
armor = Item(item_id="a", name="A", item_type=ItemType.ARMOR)
|
|
|
|
assert weapon.is_weapon() == True
|
|
assert armor.is_weapon() == False
|
|
|
|
def test_is_armor(self):
|
|
"""Test is_armor() method."""
|
|
weapon = Item(item_id="w", name="W", item_type=ItemType.WEAPON)
|
|
armor = Item(item_id="a", name="A", item_type=ItemType.ARMOR)
|
|
|
|
assert armor.is_armor() == True
|
|
assert weapon.is_armor() == False
|
|
|
|
def test_is_consumable(self):
|
|
"""Test is_consumable() method."""
|
|
consumable = Item(item_id="c", name="C", item_type=ItemType.CONSUMABLE)
|
|
weapon = Item(item_id="w", name="W", item_type=ItemType.WEAPON)
|
|
|
|
assert consumable.is_consumable() == True
|
|
assert weapon.is_consumable() == False
|
|
|
|
def test_is_quest_item(self):
|
|
"""Test is_quest_item() method."""
|
|
quest = Item(item_id="q", name="Q", item_type=ItemType.QUEST_ITEM)
|
|
weapon = Item(item_id="w", name="W", item_type=ItemType.WEAPON)
|
|
|
|
assert quest.is_quest_item() == True
|
|
assert weapon.is_quest_item() == False
|
|
|
|
|
|
class TestItemSerialization:
|
|
"""Tests for Item serialization and deserialization."""
|
|
|
|
def test_to_dict_includes_rarity(self):
|
|
"""Test that to_dict() includes rarity as string."""
|
|
item = Item(
|
|
item_id="test",
|
|
name="Test",
|
|
item_type=ItemType.WEAPON,
|
|
rarity=ItemRarity.EPIC,
|
|
description="Test item",
|
|
)
|
|
|
|
data = item.to_dict()
|
|
|
|
assert data["rarity"] == "epic"
|
|
assert data["item_type"] == "weapon"
|
|
|
|
def test_from_dict_parses_rarity(self):
|
|
"""Test that from_dict() parses rarity correctly."""
|
|
data = {
|
|
"item_id": "test",
|
|
"name": "Test",
|
|
"item_type": "weapon",
|
|
"rarity": "legendary",
|
|
"description": "Test item",
|
|
}
|
|
|
|
item = Item.from_dict(data)
|
|
|
|
assert item.rarity == ItemRarity.LEGENDARY
|
|
assert item.item_type == ItemType.WEAPON
|
|
|
|
def test_from_dict_defaults_to_common_rarity(self):
|
|
"""Test that from_dict() defaults to COMMON if rarity missing."""
|
|
data = {
|
|
"item_id": "test",
|
|
"name": "Test",
|
|
"item_type": "weapon",
|
|
"description": "Test item",
|
|
# No rarity field
|
|
}
|
|
|
|
item = Item.from_dict(data)
|
|
|
|
assert item.rarity == ItemRarity.COMMON
|
|
|
|
def test_round_trip_serialization(self):
|
|
"""Test serialization and deserialization preserve all data."""
|
|
original = Item(
|
|
item_id="flame_sword",
|
|
name="Flame Blade",
|
|
item_type=ItemType.WEAPON,
|
|
rarity=ItemRarity.RARE,
|
|
description="A fiery blade.",
|
|
value=500,
|
|
damage=25,
|
|
damage_type=DamageType.PHYSICAL,
|
|
crit_chance=0.12,
|
|
crit_multiplier=2.5,
|
|
elemental_damage_type=DamageType.FIRE,
|
|
physical_ratio=0.7,
|
|
elemental_ratio=0.3,
|
|
defense=0,
|
|
resistance=0,
|
|
required_level=5,
|
|
stat_bonuses={"strength": 3},
|
|
)
|
|
|
|
# Serialize then deserialize
|
|
data = original.to_dict()
|
|
restored = Item.from_dict(data)
|
|
|
|
assert restored.item_id == original.item_id
|
|
assert restored.name == original.name
|
|
assert restored.item_type == original.item_type
|
|
assert restored.rarity == original.rarity
|
|
assert restored.description == original.description
|
|
assert restored.value == original.value
|
|
assert restored.damage == original.damage
|
|
assert restored.damage_type == original.damage_type
|
|
assert restored.crit_chance == original.crit_chance
|
|
assert restored.crit_multiplier == original.crit_multiplier
|
|
assert restored.elemental_damage_type == original.elemental_damage_type
|
|
assert restored.physical_ratio == original.physical_ratio
|
|
assert restored.elemental_ratio == original.elemental_ratio
|
|
assert restored.required_level == original.required_level
|
|
assert restored.stat_bonuses == original.stat_bonuses
|
|
|
|
def test_round_trip_all_rarities(self):
|
|
"""Test round-trip serialization for all rarity levels."""
|
|
for rarity in ItemRarity:
|
|
original = Item(
|
|
item_id=f"item_{rarity.value}",
|
|
name=f"{rarity.value.title()} Item",
|
|
item_type=ItemType.CONSUMABLE,
|
|
rarity=rarity,
|
|
)
|
|
|
|
data = original.to_dict()
|
|
restored = Item.from_dict(data)
|
|
|
|
assert restored.rarity == rarity
|
|
|
|
|
|
class TestItemEquippability:
|
|
"""Tests for item equip requirements."""
|
|
|
|
def test_can_equip_level_requirement(self):
|
|
"""Test level requirement checking."""
|
|
item = Item(
|
|
item_id="high_level_sword",
|
|
name="Epic Sword",
|
|
item_type=ItemType.WEAPON,
|
|
required_level=10,
|
|
)
|
|
|
|
assert item.can_equip(character_level=5) == False
|
|
assert item.can_equip(character_level=10) == True
|
|
assert item.can_equip(character_level=15) == True
|
|
|
|
def test_can_equip_class_requirement(self):
|
|
"""Test class requirement checking."""
|
|
item = Item(
|
|
item_id="mage_staff",
|
|
name="Mage Staff",
|
|
item_type=ItemType.WEAPON,
|
|
required_class="mage",
|
|
)
|
|
|
|
assert item.can_equip(character_level=1, character_class="warrior") == False
|
|
assert item.can_equip(character_level=1, character_class="mage") == True
|
|
|
|
|
|
class TestItemStatBonuses:
|
|
"""Tests for item stat bonus methods."""
|
|
|
|
def test_get_total_stat_bonus(self):
|
|
"""Test getting stat bonuses from items."""
|
|
item = Item(
|
|
item_id="ring_of_power",
|
|
name="Ring of Power",
|
|
item_type=ItemType.ARMOR,
|
|
stat_bonuses={"strength": 5, "constitution": 3},
|
|
)
|
|
|
|
assert item.get_total_stat_bonus("strength") == 5
|
|
assert item.get_total_stat_bonus("constitution") == 3
|
|
assert item.get_total_stat_bonus("dexterity") == 0 # Not in bonuses
|
|
|
|
|
|
class TestItemRepr:
|
|
"""Tests for item string representation."""
|
|
|
|
def test_weapon_repr(self):
|
|
"""Test weapon __repr__ output."""
|
|
weapon = Item(
|
|
item_id="sword",
|
|
name="Iron Sword",
|
|
item_type=ItemType.WEAPON,
|
|
damage=10,
|
|
value=50,
|
|
)
|
|
|
|
repr_str = repr(weapon)
|
|
assert "Iron Sword" in repr_str
|
|
assert "weapon" in repr_str
|
|
|
|
def test_armor_repr(self):
|
|
"""Test armor __repr__ output."""
|
|
armor = Item(
|
|
item_id="armor",
|
|
name="Leather Armor",
|
|
item_type=ItemType.ARMOR,
|
|
defense=5,
|
|
value=30,
|
|
)
|
|
|
|
repr_str = repr(armor)
|
|
assert "Leather Armor" in repr_str
|
|
assert "armor" in repr_str
|
|
|
|
def test_consumable_repr(self):
|
|
"""Test consumable __repr__ output."""
|
|
potion = Item(
|
|
item_id="potion",
|
|
name="Health Potion",
|
|
item_type=ItemType.CONSUMABLE,
|
|
value=10,
|
|
)
|
|
|
|
repr_str = repr(potion)
|
|
assert "Health Potion" in repr_str
|
|
assert "consumable" in repr_str
|