first commit
This commit is contained in:
321
api/tests/test_prompt_templates.py
Normal file
321
api/tests/test_prompt_templates.py
Normal file
@@ -0,0 +1,321 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user