Files
Code_of_Conquest/api/tests/test_action_prompt_loader.py
2025-11-24 23:10:55 -06:00

315 lines
10 KiB
Python

"""
Tests for ActionPromptLoader service
Tests loading from YAML, filtering by tier and location,
and error handling.
"""
import pytest
import tempfile
import os
from app.services.action_prompt_loader import (
ActionPromptLoader,
ActionPromptLoaderError,
ActionPromptNotFoundError,
)
from app.models.action_prompt import LocationType
from app.ai.model_selector import UserTier
class TestActionPromptLoader:
"""Tests for ActionPromptLoader service."""
@pytest.fixture(autouse=True)
def reset_singleton(self):
"""Reset singleton before each test."""
ActionPromptLoader.reset_instance()
yield
ActionPromptLoader.reset_instance()
@pytest.fixture
def sample_yaml(self):
"""Create a sample YAML file for testing."""
content = """
action_prompts:
- prompt_id: test_free
category: explore
display_text: Free Action
description: Available to all
tier_required: free
context_filter: [town, tavern]
dm_prompt_template: Test template
- prompt_id: test_premium
category: gather_info
display_text: Premium Action
description: Premium only
tier_required: premium
context_filter: [any]
dm_prompt_template: Premium template
- prompt_id: test_elite
category: special
display_text: Elite Action
description: Elite only
tier_required: elite
context_filter: [library]
dm_prompt_template: Elite template
"""
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
f.write(content)
filepath = f.name
yield filepath
os.unlink(filepath)
@pytest.fixture
def loader(self, sample_yaml):
"""Create a loader with sample data."""
loader = ActionPromptLoader()
loader.load_from_yaml(sample_yaml)
return loader
# Loading tests
def test_load_from_yaml(self, sample_yaml):
"""Test loading prompts from YAML file."""
loader = ActionPromptLoader()
count = loader.load_from_yaml(sample_yaml)
assert count == 3
assert loader.is_loaded()
def test_load_file_not_found(self):
"""Test error when file doesn't exist."""
loader = ActionPromptLoader()
with pytest.raises(ActionPromptLoaderError) as exc_info:
loader.load_from_yaml("/nonexistent/path.yaml")
assert "not found" in str(exc_info.value)
def test_load_invalid_yaml(self):
"""Test error when YAML is malformed."""
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
f.write("invalid: yaml: content: [")
filepath = f.name
try:
loader = ActionPromptLoader()
with pytest.raises(ActionPromptLoaderError) as exc_info:
loader.load_from_yaml(filepath)
assert "Invalid YAML" in str(exc_info.value)
finally:
os.unlink(filepath)
def test_load_missing_key(self):
"""Test error when action_prompts key is missing."""
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
f.write("other_key: value")
filepath = f.name
try:
loader = ActionPromptLoader()
with pytest.raises(ActionPromptLoaderError) as exc_info:
loader.load_from_yaml(filepath)
assert "Missing 'action_prompts'" in str(exc_info.value)
finally:
os.unlink(filepath)
# Get methods tests
def test_get_all_actions(self, loader):
"""Test getting all loaded actions."""
actions = loader.get_all_actions()
assert len(actions) == 3
prompt_ids = [a.prompt_id for a in actions]
assert "test_free" in prompt_ids
assert "test_premium" in prompt_ids
assert "test_elite" in prompt_ids
def test_get_action_by_id(self, loader):
"""Test getting action by ID."""
action = loader.get_action_by_id("test_free")
assert action.prompt_id == "test_free"
assert action.display_text == "Free Action"
def test_get_action_by_id_not_found(self, loader):
"""Test error when action ID not found."""
with pytest.raises(ActionPromptNotFoundError):
loader.get_action_by_id("nonexistent")
# Filtering tests
def test_get_available_actions_free_tier(self, loader):
"""Test filtering for free tier user."""
actions = loader.get_available_actions(UserTier.FREE, LocationType.TOWN)
assert len(actions) == 1
assert actions[0].prompt_id == "test_free"
def test_get_available_actions_premium_tier(self, loader):
"""Test filtering for premium tier user."""
actions = loader.get_available_actions(UserTier.PREMIUM, LocationType.TOWN)
# Premium gets: test_free (town) + test_premium (any)
assert len(actions) == 2
prompt_ids = [a.prompt_id for a in actions]
assert "test_free" in prompt_ids
assert "test_premium" in prompt_ids
def test_get_available_actions_elite_tier(self, loader):
"""Test filtering for elite tier user."""
actions = loader.get_available_actions(UserTier.ELITE, LocationType.LIBRARY)
# Elite in library gets: test_premium (any) + test_elite (library)
assert len(actions) == 2
prompt_ids = [a.prompt_id for a in actions]
assert "test_premium" in prompt_ids
assert "test_elite" in prompt_ids
def test_get_available_actions_location_filter(self, loader):
"""Test that location filtering works correctly."""
# In town, no elite actions available
actions = loader.get_available_actions(UserTier.ELITE, LocationType.TOWN)
prompt_ids = [a.prompt_id for a in actions]
assert "test_elite" not in prompt_ids # Only in library
def test_get_actions_by_tier(self, loader):
"""Test getting actions by tier without location filter."""
free_actions = loader.get_actions_by_tier(UserTier.FREE)
premium_actions = loader.get_actions_by_tier(UserTier.PREMIUM)
elite_actions = loader.get_actions_by_tier(UserTier.ELITE)
assert len(free_actions) == 1
assert len(premium_actions) == 2
assert len(elite_actions) == 3
def test_get_actions_by_category(self, loader):
"""Test getting actions by category."""
explore_actions = loader.get_actions_by_category("explore")
special_actions = loader.get_actions_by_category("special")
assert len(explore_actions) == 1
assert explore_actions[0].prompt_id == "test_free"
assert len(special_actions) == 1
assert special_actions[0].prompt_id == "test_elite"
def test_get_locked_actions(self, loader):
"""Test getting locked actions for upgrade prompts."""
# Free user in library sees elite action as locked
locked = loader.get_locked_actions(UserTier.FREE, LocationType.LIBRARY)
# test_premium (any) and test_elite (library) are locked for free
assert len(locked) == 2
prompt_ids = [a.prompt_id for a in locked]
assert "test_premium" in prompt_ids
assert "test_elite" in prompt_ids
# Singleton and reload tests
def test_singleton_pattern(self, sample_yaml):
"""Test that loader is singleton."""
loader1 = ActionPromptLoader()
loader1.load_from_yaml(sample_yaml)
loader2 = ActionPromptLoader()
assert loader1 is loader2
assert loader2.is_loaded()
def test_reload(self, sample_yaml):
"""Test reloading prompts."""
loader = ActionPromptLoader()
loader.load_from_yaml(sample_yaml)
# Modify and reload
count = loader.reload(sample_yaml)
assert count == 3
def test_get_prompt_count(self, loader):
"""Test getting prompt count."""
assert loader.get_prompt_count() == 3
class TestActionPromptLoaderIntegration:
"""Integration tests with actual YAML file."""
@pytest.fixture(autouse=True)
def reset_singleton(self):
"""Reset singleton before each test."""
ActionPromptLoader.reset_instance()
yield
ActionPromptLoader.reset_instance()
def test_load_actual_yaml(self):
"""Test loading the actual action_prompts.yaml file."""
loader = ActionPromptLoader()
filepath = os.path.join(
os.path.dirname(__file__),
'..', 'app', 'data', 'action_prompts.yaml'
)
if os.path.exists(filepath):
count = loader.load_from_yaml(filepath)
# Should have 10 actions
assert count == 10
# Verify tier distribution
free_actions = loader.get_actions_by_tier(UserTier.FREE)
premium_actions = loader.get_actions_by_tier(UserTier.PREMIUM)
elite_actions = loader.get_actions_by_tier(UserTier.ELITE)
assert len(free_actions) == 4 # Only free tier
assert len(premium_actions) == 7 # Free + premium
assert len(elite_actions) == 10 # All
def test_free_tier_town_actions(self):
"""Test free tier actions in town location."""
loader = ActionPromptLoader()
filepath = os.path.join(
os.path.dirname(__file__),
'..', 'app', 'data', 'action_prompts.yaml'
)
if os.path.exists(filepath):
loader.load_from_yaml(filepath)
actions = loader.get_available_actions(UserTier.FREE, LocationType.TOWN)
# Free user in town should have:
# - ask_locals (town/tavern)
# - search_supplies (any)
# - rest_recover (town/tavern/safe_area)
prompt_ids = [a.prompt_id for a in actions]
assert "ask_locals" in prompt_ids
assert "search_supplies" in prompt_ids
assert "rest_recover" in prompt_ids
def test_premium_tier_wilderness_actions(self):
"""Test premium tier actions in wilderness location."""
loader = ActionPromptLoader()
filepath = os.path.join(
os.path.dirname(__file__),
'..', 'app', 'data', 'action_prompts.yaml'
)
if os.path.exists(filepath):
loader.load_from_yaml(filepath)
actions = loader.get_available_actions(UserTier.PREMIUM, LocationType.WILDERNESS)
prompt_ids = [a.prompt_id for a in actions]
# Should include wilderness actions
assert "explore_area" in prompt_ids
assert "make_camp" in prompt_ids
assert "search_supplies" in prompt_ids