""" Tests for LootEntry model with hybrid loot support. Tests the extended LootEntry dataclass that supports both static and procedural loot types with backward compatibility. """ import pytest from app.models.enemy import LootEntry, LootType class TestLootEntryBackwardCompatibility: """Test that existing YAML format still works.""" def test_from_dict_defaults_to_static(self): """Old-style entries without loot_type should default to STATIC.""" entry_data = { "item_id": "rusty_dagger", "drop_chance": 0.15, } entry = LootEntry.from_dict(entry_data) assert entry.loot_type == LootType.STATIC assert entry.item_id == "rusty_dagger" assert entry.drop_chance == 0.15 assert entry.quantity_min == 1 assert entry.quantity_max == 1 def test_from_dict_with_all_old_fields(self): """Test entry with all old-style fields.""" entry_data = { "item_id": "gold_coin", "drop_chance": 0.50, "quantity_min": 1, "quantity_max": 3, } entry = LootEntry.from_dict(entry_data) assert entry.loot_type == LootType.STATIC assert entry.item_id == "gold_coin" assert entry.drop_chance == 0.50 assert entry.quantity_min == 1 assert entry.quantity_max == 3 def test_to_dict_includes_loot_type(self): """Serialization should include loot_type.""" entry = LootEntry( loot_type=LootType.STATIC, item_id="health_potion", drop_chance=0.2 ) data = entry.to_dict() assert data["loot_type"] == "static" assert data["item_id"] == "health_potion" assert data["drop_chance"] == 0.2 class TestLootEntryStaticType: """Test static loot entries.""" def test_static_entry_creation(self): """Test creating a static loot entry.""" entry = LootEntry( loot_type=LootType.STATIC, item_id="goblin_ear", drop_chance=0.60, quantity_min=1, quantity_max=2 ) assert entry.loot_type == LootType.STATIC assert entry.item_id == "goblin_ear" assert entry.item_type is None assert entry.rarity_bonus == 0.0 def test_static_from_dict_explicit(self): """Test parsing explicit static entry.""" entry_data = { "loot_type": "static", "item_id": "health_potion_small", "drop_chance": 0.10, } entry = LootEntry.from_dict(entry_data) assert entry.loot_type == LootType.STATIC assert entry.item_id == "health_potion_small" def test_static_to_dict_omits_procedural_fields(self): """Static entries should omit procedural-only fields.""" entry = LootEntry( loot_type=LootType.STATIC, item_id="gold_coin", drop_chance=0.5 ) data = entry.to_dict() assert "item_id" in data assert "item_type" not in data assert "rarity_bonus" not in data class TestLootEntryProceduralType: """Test procedural loot entries.""" def test_procedural_entry_creation(self): """Test creating a procedural loot entry.""" entry = LootEntry( loot_type=LootType.PROCEDURAL, item_type="weapon", drop_chance=0.10, rarity_bonus=0.15 ) assert entry.loot_type == LootType.PROCEDURAL assert entry.item_type == "weapon" assert entry.rarity_bonus == 0.15 assert entry.item_id is None def test_procedural_from_dict(self): """Test parsing procedural entry from dict.""" entry_data = { "loot_type": "procedural", "item_type": "armor", "drop_chance": 0.08, "rarity_bonus": 0.05, } entry = LootEntry.from_dict(entry_data) assert entry.loot_type == LootType.PROCEDURAL assert entry.item_type == "armor" assert entry.drop_chance == 0.08 assert entry.rarity_bonus == 0.05 def test_procedural_to_dict_includes_item_type(self): """Procedural entries should include item_type and rarity_bonus.""" entry = LootEntry( loot_type=LootType.PROCEDURAL, item_type="weapon", drop_chance=0.15, rarity_bonus=0.10 ) data = entry.to_dict() assert data["loot_type"] == "procedural" assert data["item_type"] == "weapon" assert data["rarity_bonus"] == 0.10 assert "item_id" not in data def test_procedural_default_rarity_bonus(self): """Procedural entries default to 0.0 rarity bonus.""" entry_data = { "loot_type": "procedural", "item_type": "weapon", "drop_chance": 0.10, } entry = LootEntry.from_dict(entry_data) assert entry.rarity_bonus == 0.0 class TestLootTypeEnum: """Test LootType enum values.""" def test_static_value(self): """Test STATIC enum value.""" assert LootType.STATIC.value == "static" def test_procedural_value(self): """Test PROCEDURAL enum value.""" assert LootType.PROCEDURAL.value == "procedural" def test_from_string(self): """Test creating enum from string.""" assert LootType("static") == LootType.STATIC assert LootType("procedural") == LootType.PROCEDURAL def test_invalid_string_raises(self): """Test that invalid string raises ValueError.""" with pytest.raises(ValueError): LootType("invalid") class TestLootEntryRoundTrip: """Test serialization/deserialization round trips.""" def test_static_round_trip(self): """Static entry should survive round trip.""" original = LootEntry( loot_type=LootType.STATIC, item_id="health_potion_small", drop_chance=0.15, quantity_min=1, quantity_max=2 ) data = original.to_dict() restored = LootEntry.from_dict(data) assert restored.loot_type == original.loot_type assert restored.item_id == original.item_id assert restored.drop_chance == original.drop_chance assert restored.quantity_min == original.quantity_min assert restored.quantity_max == original.quantity_max def test_procedural_round_trip(self): """Procedural entry should survive round trip.""" original = LootEntry( loot_type=LootType.PROCEDURAL, item_type="weapon", drop_chance=0.25, rarity_bonus=0.15, quantity_min=1, quantity_max=1 ) data = original.to_dict() restored = LootEntry.from_dict(data) assert restored.loot_type == original.loot_type assert restored.item_type == original.item_type assert restored.drop_chance == original.drop_chance assert restored.rarity_bonus == original.rarity_bonus