""" Unit tests for Quest data models. Tests serialization, deserialization, and key model methods. """ import pytest from app.models.quest import ( Quest, QuestObjective, QuestReward, QuestOfferingTriggers, QuestOfferConditions, NPCOfferDialogue, QuestLoreContext, QuestDialogueTemplates, CharacterQuestState, QuestDifficulty, ObjectiveType, QuestStatus, ) class TestQuestObjective: """Tests for QuestObjective dataclass.""" def test_create_kill_objective(self): """Test creating a kill-type objective.""" obj = QuestObjective( objective_id="kill_rats", description="Kill 10 giant rats", objective_type=ObjectiveType.KILL, required_progress=10, target_enemy_type="giant_rat", ) assert obj.objective_id == "kill_rats" assert obj.objective_type == ObjectiveType.KILL assert obj.required_progress == 10 assert not obj.is_complete assert obj.progress_text == "0/10" def test_objective_completion(self): """Test objective completion detection.""" obj = QuestObjective( objective_id="collect_items", description="Collect 5 herbs", objective_type=ObjectiveType.COLLECT, required_progress=5, current_progress=5, ) assert obj.is_complete def test_objective_serialization(self): """Test objective to_dict and from_dict.""" obj = QuestObjective( objective_id="travel_town", description="Travel to the town", objective_type=ObjectiveType.TRAVEL, required_progress=1, target_location_id="crossville_village", ) data = obj.to_dict() assert data["objective_id"] == "travel_town" assert data["objective_type"] == "travel" assert data["target_location_id"] == "crossville_village" restored = QuestObjective.from_dict(data) assert restored.objective_id == obj.objective_id assert restored.objective_type == obj.objective_type assert restored.target_location_id == obj.target_location_id class TestQuestReward: """Tests for QuestReward dataclass.""" def test_create_reward(self): """Test creating a quest reward.""" reward = QuestReward( gold=100, experience=250, items=["sword_epic"], relationship_bonuses={"npc_grom": 10}, unlocks_quests=["quest_sequel"], ) assert reward.gold == 100 assert reward.experience == 250 assert "sword_epic" in reward.items assert reward.relationship_bonuses.get("npc_grom") == 10 def test_reward_serialization(self): """Test reward to_dict and from_dict.""" reward = QuestReward(gold=50, experience=100) data = reward.to_dict() restored = QuestReward.from_dict(data) assert restored.gold == 50 assert restored.experience == 100 class TestQuestOfferingTriggers: """Tests for QuestOfferingTriggers dataclass.""" def test_get_probability(self): """Test probability lookup by location type.""" triggers = QuestOfferingTriggers( location_types=["tavern", "town"], probability_weights={"tavern": 0.35, "town": 0.25}, ) assert triggers.get_probability("tavern") == 0.35 assert triggers.get_probability("town") == 0.25 assert triggers.get_probability("wilderness") == 0.0 # Not defined def test_level_range(self): """Test level range settings.""" triggers = QuestOfferingTriggers( min_character_level=5, max_character_level=15, ) assert triggers.min_character_level == 5 assert triggers.max_character_level == 15 class TestQuest: """Tests for Quest dataclass.""" def test_create_quest(self): """Test creating a complete quest.""" quest = Quest( quest_id="quest_test", name="Test Quest", description="A test quest", difficulty=QuestDifficulty.MEDIUM, quest_giver_npc_ids=["npc_test"], objectives=[ QuestObjective( objective_id="obj1", description="Do something", objective_type=ObjectiveType.KILL, required_progress=5, ) ], rewards=QuestReward(gold=100, experience=200), ) assert quest.quest_id == "quest_test" assert quest.difficulty == QuestDifficulty.MEDIUM assert len(quest.objectives) == 1 assert quest.rewards.gold == 100 def test_can_npc_offer(self): """Test NPC can offer check.""" quest = Quest( quest_id="quest_test", name="Test Quest", description="Test", difficulty=QuestDifficulty.EASY, quest_giver_npc_ids=["npc_grom", "npc_hilda"], ) assert quest.can_npc_offer("npc_grom") assert quest.can_npc_offer("npc_hilda") assert not quest.can_npc_offer("npc_other") def test_get_offer_dialogue(self): """Test getting NPC-specific offer dialogue.""" quest = Quest( quest_id="quest_test", name="Test Quest", description="Test", difficulty=QuestDifficulty.EASY, quest_giver_npc_ids=["npc_grom"], npc_offer_dialogues={ "npc_grom": NPCOfferDialogue( dialogue="Got a problem, friend.", conditions=QuestOfferConditions(min_relationship=30), ) }, ) assert quest.get_offer_dialogue("npc_grom") == "Got a problem, friend." assert quest.get_offer_dialogue("npc_other") == "" def test_get_offer_conditions(self): """Test getting NPC-specific offer conditions.""" quest = Quest( quest_id="quest_test", name="Test Quest", description="Test", difficulty=QuestDifficulty.EASY, quest_giver_npc_ids=["npc_grom"], npc_offer_dialogues={ "npc_grom": NPCOfferDialogue( dialogue="Test", conditions=QuestOfferConditions( min_relationship=50, required_flags=["helped_before"], ), ) }, ) conditions = quest.get_offer_conditions("npc_grom") assert conditions.min_relationship == 50 assert "helped_before" in conditions.required_flags # Non-existent NPC should return default conditions default_conditions = quest.get_offer_conditions("npc_other") assert default_conditions.min_relationship == 0 def test_quest_completion(self): """Test quest completion detection.""" quest = Quest( quest_id="quest_test", name="Test Quest", description="Test", difficulty=QuestDifficulty.EASY, quest_giver_npc_ids=["npc_test"], objectives=[ QuestObjective( objective_id="obj1", description="Task 1", objective_type=ObjectiveType.KILL, required_progress=5, current_progress=5, ), QuestObjective( objective_id="obj2", description="Task 2", objective_type=ObjectiveType.TRAVEL, required_progress=1, current_progress=1, ), ], ) assert quest.is_complete def test_quest_not_complete(self): """Test quest incomplete when objectives remain.""" quest = Quest( quest_id="quest_test", name="Test Quest", description="Test", difficulty=QuestDifficulty.EASY, quest_giver_npc_ids=["npc_test"], objectives=[ QuestObjective( objective_id="obj1", description="Task 1", objective_type=ObjectiveType.KILL, required_progress=5, current_progress=5, ), QuestObjective( objective_id="obj2", description="Task 2", objective_type=ObjectiveType.TRAVEL, required_progress=1, current_progress=0, # Not complete ), ], ) assert not quest.is_complete def test_quest_serialization(self): """Test quest to_dict and from_dict.""" quest = Quest( quest_id="quest_test", name="Test Quest", description="A test description", difficulty=QuestDifficulty.HARD, quest_giver_npc_ids=["npc_grom"], quest_giver_name="Grom", location_id="crossville_tavern", region_id="crossville", objectives=[ QuestObjective( objective_id="kill_rats", description="Kill 10 rats", objective_type=ObjectiveType.KILL, required_progress=10, ) ], rewards=QuestReward(gold=50, experience=100), lore_context=QuestLoreContext( backstory="Long ago...", world_connections=["Connected to main story"], ), dialogue_templates=QuestDialogueTemplates( narrative_hooks=["mentions rats", "looks nervous"], ), tags=["combat", "starter"], ) data = quest.to_dict() assert data["quest_id"] == "quest_test" assert data["difficulty"] == "hard" assert len(data["objectives"]) == 1 assert data["rewards"]["gold"] == 50 assert data["lore_context"]["backstory"] == "Long ago..." restored = Quest.from_dict(data) assert restored.quest_id == quest.quest_id assert restored.difficulty == quest.difficulty assert restored.rewards.gold == quest.rewards.gold assert restored.lore_context.backstory == quest.lore_context.backstory def test_to_offer_dict(self): """Test quest to_offer_dict for UI display.""" quest = Quest( quest_id="quest_test", name="Test Quest", description="Test description", difficulty=QuestDifficulty.EASY, quest_giver_npc_ids=["npc_grom"], quest_giver_name="Grom", objectives=[ QuestObjective( objective_id="obj1", description="Do something", objective_type=ObjectiveType.KILL, required_progress=5, ) ], rewards=QuestReward(gold=50, experience=100), ) offer_data = quest.to_offer_dict() assert offer_data["quest_id"] == "quest_test" assert offer_data["name"] == "Test Quest" assert offer_data["difficulty"] == "easy" assert offer_data["rewards"]["gold"] == 50 assert len(offer_data["objectives"]) == 1 class TestCharacterQuestState: """Tests for CharacterQuestState dataclass.""" def test_create_quest_state(self): """Test creating a quest state.""" state = CharacterQuestState( quest_id="quest_test", status=QuestStatus.ACTIVE, accepted_at="2025-01-15T10:00:00Z", objectives_progress={"obj1": 3, "obj2": 0}, ) assert state.quest_id == "quest_test" assert state.status == QuestStatus.ACTIVE assert state.get_progress("obj1") == 3 assert state.get_progress("obj2") == 0 assert state.get_progress("obj3") == 0 # Non-existent def test_update_progress(self): """Test updating objective progress.""" state = CharacterQuestState( quest_id="quest_test", objectives_progress={"obj1": 0}, ) state.update_progress("obj1", 5) assert state.get_progress("obj1") == 5 state.update_progress("obj1", 3) assert state.get_progress("obj1") == 8 def test_quest_state_serialization(self): """Test quest state to_dict and from_dict.""" state = CharacterQuestState( quest_id="quest_test", status=QuestStatus.COMPLETED, accepted_at="2025-01-15T10:00:00Z", completed_at="2025-01-16T14:30:00Z", objectives_progress={"obj1": 10}, ) data = state.to_dict() assert data["quest_id"] == "quest_test" assert data["status"] == "completed" assert data["completed_at"] == "2025-01-16T14:30:00Z" restored = CharacterQuestState.from_dict(data) assert restored.quest_id == state.quest_id assert restored.status == state.status assert restored.completed_at == state.completed_at