""" Unit tests for prompt templates module. """ import pytest from pathlib import Path from app.ai import ( PromptTemplates, PromptTemplateError, get_prompt_templates, render_prompt, ) class TestPromptTemplates: """Tests for PromptTemplates class.""" def setup_method(self): """Set up test fixtures.""" self.templates = PromptTemplates() def test_initialization(self): """Test PromptTemplates initializes correctly.""" assert self.templates is not None assert self.templates.env is not None def test_template_directory_exists(self): """Test that template directory is created.""" assert self.templates.template_dir.exists() def test_get_template_names(self): """Test listing available templates.""" names = self.templates.get_template_names() assert isinstance(names, list) # Should have our 4 core templates assert 'story_action.j2' in names assert 'combat_action.j2' in names assert 'quest_offering.j2' in names assert 'npc_dialogue.j2' in names def test_render_string_simple(self): """Test rendering a simple template string.""" result = self.templates.render_string( "Hello, {{ name }}!", name="Player" ) assert result == "Hello, Player!" def test_render_string_with_filter(self): """Test rendering with custom filter.""" result = self.templates.render_string( "{{ items | format_inventory }}", items=[ {"name": "Sword", "quantity": 1}, {"name": "Potion", "quantity": 3} ] ) assert "Sword" in result assert "Potion (x3)" in result def test_render_story_action_template(self): """Test rendering story_action template.""" result = self.templates.render( "story_action.j2", character={ "name": "Aldric", "level": 5, "player_class": "Warrior", "current_hp": 45, "max_hp": 50, "stats": {"strength": 16, "dexterity": 12}, "skills": [], "effects": [] }, game_state={ "current_location": "The Rusty Anchor", "location_type": "TAVERN", "active_quests": [], "discovered_locations": [] }, action="I look around the tavern for anyone suspicious" ) assert "Aldric" in result assert "Warrior" in result assert "Rusty Anchor" in result assert "suspicious" in result def test_render_combat_action_template(self): """Test rendering combat_action template.""" result = self.templates.render( "combat_action.j2", character={ "name": "Aldric", "level": 5, "player_class": "Warrior", "current_hp": 45, "max_hp": 50, "effects": [] }, combat_state={ "round_number": 2, "current_turn": "Player", "enemies": [ { "name": "Goblin", "current_hp": 8, "max_hp": 15, "effects": [] } ] }, action="swings their sword at the Goblin", action_result={ "hit": True, "damage": 7, "effects_applied": [] }, is_critical=False, is_finishing_blow=False ) assert "Aldric" in result assert "Goblin" in result assert "sword" in result def test_render_quest_offering_template(self): """Test rendering quest_offering template.""" result = self.templates.render( "quest_offering.j2", character={ "name": "Aldric", "level": 3, "player_class": "Warrior", "completed_quests": [] }, game_context={ "current_location": "Village Square", "location_type": "TOWN", "active_quests": [], "world_events": [] }, eligible_quests=[ { "quest_id": "quest_goblin_cave", "name": "Clear the Goblin Cave", "difficulty": "EASY", "quest_giver": "Village Elder", "description": "Goblins have been raiding farms", "narrative_hooks": [ "Farmers complaining about lost livestock" ] } ], recent_actions=["Talked to locals"] ) assert "quest_goblin_cave" in result assert "Clear the Goblin Cave" in result assert "Village Elder" in result def test_render_npc_dialogue_template(self): """Test rendering npc_dialogue template.""" result = self.templates.render( "npc_dialogue.j2", character={ "name": "Aldric", "level": 5, "player_class": "Warrior" }, npc={ "name": "Grizzled Bartender", "role": "Tavern Owner", "personality": "Gruff but kind", "speaking_style": "Short sentences, common slang" }, conversation_topic="What's the latest news around here?", game_state={ "current_location": "The Rusty Anchor", "time_of_day": "Evening", "active_quests": [] } ) assert "Grizzled Bartender" in result assert "Aldric" in result assert "news" in result def test_format_inventory_filter_empty(self): """Test format_inventory filter with empty list.""" result = PromptTemplates._format_inventory([]) assert result == "Empty inventory" def test_format_inventory_filter_single(self): """Test format_inventory filter with single item.""" result = PromptTemplates._format_inventory([ {"name": "Sword", "quantity": 1} ]) assert result == "Sword" def test_format_inventory_filter_multiple(self): """Test format_inventory filter with multiple items.""" result = PromptTemplates._format_inventory([ {"name": "Sword", "quantity": 1}, {"name": "Shield", "quantity": 1}, {"name": "Potion", "quantity": 5} ]) assert "Sword" in result assert "Shield" in result assert "Potion (x5)" in result def test_format_inventory_filter_truncation(self): """Test format_inventory filter truncates long lists.""" items = [{"name": f"Item{i}", "quantity": 1} for i in range(15)] result = PromptTemplates._format_inventory(items, max_items=10) assert "and 5 more items" in result def test_format_stats_filter(self): """Test format_stats filter.""" result = PromptTemplates._format_stats({ "strength": 16, "dexterity": 14 }) assert "Strength: 16" in result assert "Dexterity: 14" in result def test_format_stats_filter_empty(self): """Test format_stats filter with empty dict.""" result = PromptTemplates._format_stats({}) assert result == "No stats available" def test_format_skills_filter(self): """Test format_skills filter.""" result = PromptTemplates._format_skills([ {"name": "Sword Mastery", "level": 3}, {"name": "Shield Block", "level": 2} ]) assert "Sword Mastery (Lv.3)" in result assert "Shield Block (Lv.2)" in result def test_format_skills_filter_empty(self): """Test format_skills filter with empty list.""" result = PromptTemplates._format_skills([]) assert result == "No skills" def test_format_effects_filter(self): """Test format_effects filter.""" result = PromptTemplates._format_effects([ {"name": "Blessed", "remaining_turns": 3}, {"name": "Strength Buff"} ]) assert "Blessed (3 turns)" in result assert "Strength Buff" in result def test_format_effects_filter_empty(self): """Test format_effects filter with empty list.""" result = PromptTemplates._format_effects([]) assert result == "No active effects" def test_truncate_text_filter_short(self): """Test truncate_text filter with short text.""" result = PromptTemplates._truncate_text("Hello", 100) assert result == "Hello" def test_truncate_text_filter_long(self): """Test truncate_text filter with long text.""" long_text = "A" * 150 result = PromptTemplates._truncate_text(long_text, 100) assert len(result) == 100 assert result.endswith("...") def test_format_gold_filter(self): """Test format_gold filter.""" assert PromptTemplates._format_gold(1000) == "1,000 gold" assert PromptTemplates._format_gold(1000000) == "1,000,000 gold" assert PromptTemplates._format_gold(50) == "50 gold" def test_invalid_template_raises_error(self): """Test that invalid template raises PromptTemplateError.""" with pytest.raises(PromptTemplateError): self.templates.render("nonexistent_template.j2") def test_invalid_template_string_raises_error(self): """Test that invalid template string raises PromptTemplateError.""" with pytest.raises(PromptTemplateError): self.templates.render_string("{{ invalid syntax") class TestPromptTemplateConvenienceFunctions: """Tests for module-level convenience functions.""" def test_get_prompt_templates_singleton(self): """Test get_prompt_templates returns singleton.""" templates1 = get_prompt_templates() templates2 = get_prompt_templates() assert templates1 is templates2 def test_render_prompt_function(self): """Test render_prompt convenience function.""" result = render_prompt( "story_action.j2", character={ "name": "Test", "level": 1, "player_class": "Warrior", "current_hp": 10, "max_hp": 10, "stats": {}, "skills": [], "effects": [] }, game_state={ "current_location": "Test Location", "location_type": "TOWN", "active_quests": [] }, action="test action" ) assert "Test" in result assert "test action" in result