first commit

This commit is contained in:
2025-11-24 23:10:55 -06:00
commit 8315fa51c9
279 changed files with 74600 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
"""
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