201 lines
8.2 KiB
Python
201 lines
8.2 KiB
Python
"""
|
|
Unit tests for OriginService - character origin loading and validation.
|
|
|
|
Tests verify that origins load correctly from YAML, contain all required
|
|
data, and provide proper narrative hooks for the AI DM.
|
|
"""
|
|
|
|
import pytest
|
|
from app.services.origin_service import OriginService, get_origin_service
|
|
from app.models.origins import Origin, StartingLocation, StartingBonus
|
|
|
|
|
|
class TestOriginService:
|
|
"""Test suite for OriginService functionality."""
|
|
|
|
@pytest.fixture
|
|
def origin_service(self):
|
|
"""Create a fresh OriginService instance for each test."""
|
|
service = OriginService()
|
|
service.clear_cache()
|
|
return service
|
|
|
|
def test_service_initializes(self, origin_service):
|
|
"""Test that OriginService initializes correctly."""
|
|
assert origin_service is not None
|
|
assert origin_service.data_file.exists()
|
|
|
|
def test_singleton_pattern(self):
|
|
"""Test that get_origin_service returns a singleton."""
|
|
service1 = get_origin_service()
|
|
service2 = get_origin_service()
|
|
assert service1 is service2
|
|
|
|
def test_load_all_origins(self, origin_service):
|
|
"""Test loading all origins from YAML file."""
|
|
origins = origin_service.load_all_origins()
|
|
|
|
assert len(origins) == 4
|
|
origin_ids = [origin.id for origin in origins]
|
|
assert "soul_revenant" in origin_ids
|
|
assert "memory_thief" in origin_ids
|
|
assert "shadow_apprentice" in origin_ids
|
|
assert "escaped_captive" in origin_ids
|
|
|
|
def test_load_origin_by_id(self, origin_service):
|
|
"""Test loading a specific origin by ID."""
|
|
origin = origin_service.load_origin("soul_revenant")
|
|
|
|
assert origin is not None
|
|
assert origin.id == "soul_revenant"
|
|
assert origin.name == "Soul Revenant"
|
|
assert isinstance(origin.description, str)
|
|
assert len(origin.description) > 0
|
|
|
|
def test_origin_not_found(self, origin_service):
|
|
"""Test that loading non-existent origin returns None."""
|
|
origin = origin_service.load_origin("nonexistent_origin")
|
|
assert origin is None
|
|
|
|
def test_origin_has_starting_location(self, origin_service):
|
|
"""Test that all origins have valid starting locations."""
|
|
origins = origin_service.load_all_origins()
|
|
|
|
for origin in origins:
|
|
assert origin.starting_location is not None
|
|
assert isinstance(origin.starting_location, StartingLocation)
|
|
assert origin.starting_location.id
|
|
assert origin.starting_location.name
|
|
assert origin.starting_location.region
|
|
assert origin.starting_location.description
|
|
|
|
def test_origin_has_narrative_hooks(self, origin_service):
|
|
"""Test that all origins have narrative hooks for AI DM."""
|
|
origins = origin_service.load_all_origins()
|
|
|
|
for origin in origins:
|
|
assert origin.narrative_hooks is not None
|
|
assert isinstance(origin.narrative_hooks, list)
|
|
assert len(origin.narrative_hooks) > 0, f"{origin.id} has no narrative hooks"
|
|
|
|
def test_origin_has_starting_bonus(self, origin_service):
|
|
"""Test that all origins have starting bonuses."""
|
|
origins = origin_service.load_all_origins()
|
|
|
|
for origin in origins:
|
|
assert origin.starting_bonus is not None
|
|
assert isinstance(origin.starting_bonus, StartingBonus)
|
|
assert origin.starting_bonus.trait
|
|
assert origin.starting_bonus.description
|
|
assert origin.starting_bonus.effect
|
|
|
|
def test_soul_revenant_details(self, origin_service):
|
|
"""Test Soul Revenant origin has correct details."""
|
|
origin = origin_service.load_origin("soul_revenant")
|
|
|
|
assert origin.name == "Soul Revenant"
|
|
assert "centuries" in origin.description.lower()
|
|
assert origin.starting_location.id == "forgotten_crypt"
|
|
assert "Deathless Resolve" in origin.starting_bonus.trait
|
|
assert any("past" in hook.lower() for hook in origin.narrative_hooks)
|
|
|
|
def test_memory_thief_details(self, origin_service):
|
|
"""Test Memory Thief origin has correct details."""
|
|
origin = origin_service.load_origin("memory_thief")
|
|
|
|
assert origin.name == "Memory Thief"
|
|
assert "memory" in origin.description.lower()
|
|
assert origin.starting_location.id == "thornfield_plains"
|
|
assert "Blank Slate" in origin.starting_bonus.trait
|
|
assert any("memory" in hook.lower() for hook in origin.narrative_hooks)
|
|
|
|
def test_shadow_apprentice_details(self, origin_service):
|
|
"""Test Shadow Apprentice origin has correct details."""
|
|
origin = origin_service.load_origin("shadow_apprentice")
|
|
|
|
assert origin.name == "Shadow Apprentice"
|
|
assert "master" in origin.description.lower()
|
|
assert origin.starting_location.id == "shadowfen"
|
|
assert "Trained in Shadows" in origin.starting_bonus.trait
|
|
assert any("master" in hook.lower() for hook in origin.narrative_hooks)
|
|
|
|
def test_escaped_captive_details(self, origin_service):
|
|
"""Test Escaped Captive origin has correct details."""
|
|
origin = origin_service.load_origin("escaped_captive")
|
|
|
|
assert origin.name == "The Escaped Captive"
|
|
assert "prison" in origin.description.lower() or "ironpeak" in origin.description.lower()
|
|
assert origin.starting_location.id == "ironpeak_pass"
|
|
assert "Hardened Survivor" in origin.starting_bonus.trait
|
|
assert any("prison" in hook.lower() or "past" in hook.lower() for hook in origin.narrative_hooks)
|
|
|
|
def test_origin_serialization(self, origin_service):
|
|
"""Test that origins can be serialized to dict and back."""
|
|
original = origin_service.load_origin("soul_revenant")
|
|
|
|
# Serialize to dict
|
|
origin_dict = original.to_dict()
|
|
assert isinstance(origin_dict, dict)
|
|
assert origin_dict["id"] == "soul_revenant"
|
|
assert origin_dict["name"] == "Soul Revenant"
|
|
|
|
# Deserialize from dict
|
|
restored = Origin.from_dict(origin_dict)
|
|
assert restored.id == original.id
|
|
assert restored.name == original.name
|
|
assert restored.description == original.description
|
|
assert restored.starting_location.id == original.starting_location.id
|
|
assert restored.starting_bonus.trait == original.starting_bonus.trait
|
|
|
|
def test_caching_works(self, origin_service):
|
|
"""Test that caching improves performance on repeated loads."""
|
|
# First load
|
|
origin1 = origin_service.load_origin("soul_revenant")
|
|
|
|
# Second load should come from cache
|
|
origin2 = origin_service.load_origin("soul_revenant")
|
|
|
|
# Should be the exact same instance from cache
|
|
assert origin1 is origin2
|
|
|
|
def test_cache_clear(self, origin_service):
|
|
"""Test that cache clearing works correctly."""
|
|
# Load origins to populate cache
|
|
origin_service.load_all_origins()
|
|
assert len(origin_service._origins_cache) > 0
|
|
|
|
# Clear cache
|
|
origin_service.clear_cache()
|
|
assert len(origin_service._origins_cache) == 0
|
|
assert origin_service._all_origins_loaded is False
|
|
|
|
def test_reload_origins(self, origin_service):
|
|
"""Test that reloading clears cache and reloads from file."""
|
|
# Load origins
|
|
origins1 = origin_service.load_all_origins()
|
|
|
|
# Reload
|
|
origins2 = origin_service.reload_origins()
|
|
|
|
# Should have same content but be fresh instances
|
|
assert len(origins1) == len(origins2)
|
|
assert all(o1.id == o2.id for o1, o2 in zip(origins1, origins2))
|
|
|
|
def test_get_all_origin_ids(self, origin_service):
|
|
"""Test getting list of all origin IDs."""
|
|
origin_ids = origin_service.get_all_origin_ids()
|
|
|
|
assert isinstance(origin_ids, list)
|
|
assert len(origin_ids) == 4
|
|
assert "soul_revenant" in origin_ids
|
|
assert "memory_thief" in origin_ids
|
|
assert "shadow_apprentice" in origin_ids
|
|
assert "escaped_captive" in origin_ids
|
|
|
|
def test_get_origin_by_id_alias(self, origin_service):
|
|
"""Test that get_origin_by_id is an alias for load_origin."""
|
|
origin1 = origin_service.load_origin("soul_revenant")
|
|
origin2 = origin_service.get_origin_by_id("soul_revenant")
|
|
|
|
assert origin1 is origin2
|