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,390 @@
"""
Tests for the GameSession model with solo play support.
Tests cover:
- Solo session creation and serialization
- Multiplayer session creation and serialization
- ConversationEntry with timestamps
- GameState with location_type
- Session type detection
"""
import pytest
from datetime import datetime
from app.models.session import (
GameSession,
GameState,
ConversationEntry,
SessionConfig,
)
from app.models.enums import (
SessionStatus,
SessionType,
LocationType,
)
class TestGameState:
"""Tests for GameState dataclass."""
def test_default_values(self):
"""Test GameState has correct defaults."""
state = GameState()
assert state.current_location == "Crossroads Village"
assert state.location_type == LocationType.TOWN
assert state.discovered_locations == []
assert state.active_quests == []
assert state.world_events == []
def test_to_dict_serializes_location_type(self):
"""Test location_type is serialized as string."""
state = GameState(
current_location="The Rusty Anchor",
location_type=LocationType.TAVERN,
)
data = state.to_dict()
assert data["current_location"] == "The Rusty Anchor"
assert data["location_type"] == "tavern"
def test_from_dict_deserializes_location_type(self):
"""Test location_type is deserialized from string."""
data = {
"current_location": "Dark Forest",
"location_type": "wilderness",
"discovered_locations": ["Town A"],
"active_quests": ["quest_1"],
"world_events": [],
}
state = GameState.from_dict(data)
assert state.current_location == "Dark Forest"
assert state.location_type == LocationType.WILDERNESS
assert state.discovered_locations == ["Town A"]
def test_roundtrip_serialization(self):
"""Test GameState serializes and deserializes correctly."""
state = GameState(
current_location="Ancient Library",
location_type=LocationType.LIBRARY,
discovered_locations=["Town", "Forest"],
active_quests=["quest_1", "quest_2"],
world_events=[{"type": "festival"}],
)
data = state.to_dict()
restored = GameState.from_dict(data)
assert restored.current_location == state.current_location
assert restored.location_type == state.location_type
assert restored.discovered_locations == state.discovered_locations
assert restored.active_quests == state.active_quests
class TestConversationEntry:
"""Tests for ConversationEntry dataclass."""
def test_auto_timestamp(self):
"""Test timestamp is auto-generated."""
entry = ConversationEntry(
turn=1,
character_id="char_123",
character_name="Hero",
action="I explore",
dm_response="You find a chest",
)
assert entry.timestamp
assert entry.timestamp.endswith("Z")
def test_provided_timestamp_preserved(self):
"""Test provided timestamp is not overwritten."""
ts = "2025-11-21T10:30:00Z"
entry = ConversationEntry(
turn=1,
character_id="char_123",
character_name="Hero",
action="I explore",
dm_response="You find a chest",
timestamp=ts,
)
assert entry.timestamp == ts
def test_to_dict_with_quest_offered(self):
"""Test serialization includes quest_offered when present."""
entry = ConversationEntry(
turn=5,
character_id="char_123",
character_name="Hero",
action="Talk to elder",
dm_response="The elder offers you a quest",
quest_offered={
"quest_id": "quest_goblin_cave",
"quest_name": "Clear the Goblin Cave",
},
)
data = entry.to_dict()
assert "quest_offered" in data
assert data["quest_offered"]["quest_id"] == "quest_goblin_cave"
def test_to_dict_without_quest_offered(self):
"""Test serialization omits quest_offered when None."""
entry = ConversationEntry(
turn=1,
character_id="char_123",
character_name="Hero",
action="I explore",
dm_response="You find nothing",
)
data = entry.to_dict()
assert "quest_offered" not in data
def test_from_dict_roundtrip(self):
"""Test ConversationEntry roundtrip serialization."""
entry = ConversationEntry(
turn=3,
character_id="char_456",
character_name="Wizard",
action="Cast fireball",
dm_response="The spell illuminates the cave",
combat_log=[{"action": "attack", "damage": 15}],
quest_offered={"quest_id": "q1"},
)
data = entry.to_dict()
restored = ConversationEntry.from_dict(data)
assert restored.turn == entry.turn
assert restored.character_id == entry.character_id
assert restored.action == entry.action
assert restored.dm_response == entry.dm_response
assert restored.timestamp == entry.timestamp
assert restored.combat_log == entry.combat_log
assert restored.quest_offered == entry.quest_offered
class TestGameSessionSolo:
"""Tests for solo GameSession functionality."""
def test_create_solo_session(self):
"""Test creating a solo session."""
session = GameSession(
session_id="sess_123",
session_type=SessionType.SOLO,
solo_character_id="char_456",
user_id="user_789",
)
assert session.session_type == SessionType.SOLO
assert session.solo_character_id == "char_456"
assert session.user_id == "user_789"
assert session.is_solo() is True
def test_is_solo_method(self):
"""Test is_solo returns correct values."""
solo = GameSession(
session_id="s1",
session_type=SessionType.SOLO,
solo_character_id="c1",
)
multi = GameSession(
session_id="s2",
session_type=SessionType.MULTIPLAYER,
party_member_ids=["c1", "c2"],
)
assert solo.is_solo() is True
assert multi.is_solo() is False
def test_get_character_id_solo(self):
"""Test get_character_id returns solo_character_id for solo sessions."""
session = GameSession(
session_id="sess_123",
session_type=SessionType.SOLO,
solo_character_id="char_456",
)
assert session.get_character_id() == "char_456"
def test_get_character_id_multiplayer(self):
"""Test get_character_id returns current turn character for multiplayer."""
session = GameSession(
session_id="sess_123",
session_type=SessionType.MULTIPLAYER,
party_member_ids=["c1", "c2", "c3"],
turn_order=["c2", "c1", "c3"],
current_turn=1,
)
assert session.get_character_id() == "c1"
def test_to_dict_includes_new_fields(self):
"""Test to_dict includes session_type, solo_character_id, user_id."""
session = GameSession(
session_id="sess_123",
session_type=SessionType.SOLO,
solo_character_id="char_456",
user_id="user_789",
)
data = session.to_dict()
assert data["session_id"] == "sess_123"
assert data["session_type"] == "solo"
assert data["solo_character_id"] == "char_456"
assert data["user_id"] == "user_789"
def test_from_dict_solo_session(self):
"""Test from_dict correctly deserializes solo session."""
data = {
"session_id": "sess_123",
"session_type": "solo",
"solo_character_id": "char_456",
"user_id": "user_789",
"party_member_ids": [],
"turn_number": 5,
"game_state": {
"current_location": "Town",
"location_type": "town",
},
}
session = GameSession.from_dict(data)
assert session.session_id == "sess_123"
assert session.session_type == SessionType.SOLO
assert session.solo_character_id == "char_456"
assert session.user_id == "user_789"
assert session.turn_number == 5
assert session.game_state.location_type == LocationType.TOWN
def test_roundtrip_serialization(self):
"""Test complete roundtrip of solo session."""
session = GameSession(
session_id="sess_test",
session_type=SessionType.SOLO,
solo_character_id="char_hero",
user_id="user_player",
turn_number=10,
game_state=GameState(
current_location="Dark Dungeon",
location_type=LocationType.DUNGEON,
active_quests=["quest_1"],
),
conversation_history=[
ConversationEntry(
turn=1,
character_id="char_hero",
character_name="Hero",
action="Enter dungeon",
dm_response="The darkness swallows you...",
)
],
)
data = session.to_dict()
restored = GameSession.from_dict(data)
assert restored.session_id == session.session_id
assert restored.session_type == session.session_type
assert restored.solo_character_id == session.solo_character_id
assert restored.user_id == session.user_id
assert restored.turn_number == session.turn_number
assert restored.game_state.current_location == session.game_state.current_location
assert restored.game_state.location_type == session.game_state.location_type
assert len(restored.conversation_history) == 1
assert restored.conversation_history[0].action == "Enter dungeon"
def test_repr_solo(self):
"""Test __repr__ for solo session."""
session = GameSession(
session_id="sess_123",
session_type=SessionType.SOLO,
solo_character_id="char_456",
turn_number=5,
)
repr_str = repr(session)
assert "type=solo" in repr_str
assert "char=char_456" in repr_str
assert "turn=5" in repr_str
def test_repr_multiplayer(self):
"""Test __repr__ for multiplayer session."""
session = GameSession(
session_id="sess_123",
session_type=SessionType.MULTIPLAYER,
party_member_ids=["c1", "c2", "c3"],
turn_number=10,
)
repr_str = repr(session)
assert "type=multiplayer" in repr_str
assert "party=3" in repr_str
assert "turn=10" in repr_str
class TestGameSessionBackwardsCompatibility:
"""Tests for backwards compatibility with existing sessions."""
def test_default_session_type_is_solo(self):
"""Test new sessions default to solo type."""
session = GameSession(session_id="test")
assert session.session_type == SessionType.SOLO
def test_from_dict_without_session_type(self):
"""Test from_dict handles missing session_type (defaults to solo)."""
data = {
"session_id": "old_session",
"party_member_ids": ["c1"],
}
session = GameSession.from_dict(data)
assert session.session_type == SessionType.SOLO
def test_from_dict_without_location_type(self):
"""Test from_dict handles missing location_type in game_state."""
data = {
"session_id": "old_session",
"game_state": {
"current_location": "Old Town",
},
}
session = GameSession.from_dict(data)
assert session.game_state.location_type == LocationType.TOWN
def test_existing_methods_still_work(self):
"""Test existing session methods work with new fields."""
session = GameSession(
session_id="test",
session_type=SessionType.SOLO,
solo_character_id="char_1",
)
# Test existing methods
assert session.is_in_combat() is False
session.update_activity()
assert session.last_activity
# Add conversation entry
entry = ConversationEntry(
turn=1,
character_id="char_1",
character_name="Hero",
action="test",
dm_response="response",
)
session.add_conversation_entry(entry)
assert len(session.conversation_history) == 1
class TestLocationTypeEnum:
"""Tests for LocationType enum."""
def test_all_location_types_defined(self):
"""Test all expected location types exist."""
expected = ["town", "tavern", "wilderness", "dungeon", "ruins", "library", "safe_area"]
actual = [lt.value for lt in LocationType]
assert sorted(actual) == sorted(expected)
def test_location_type_from_string(self):
"""Test LocationType can be created from string."""
assert LocationType("town") == LocationType.TOWN
assert LocationType("wilderness") == LocationType.WILDERNESS
assert LocationType("dungeon") == LocationType.DUNGEON
class TestSessionTypeEnum:
"""Tests for SessionType enum."""
def test_session_types_defined(self):
"""Test session types are defined correctly."""
assert SessionType.SOLO.value == "solo"
assert SessionType.MULTIPLAYER.value == "multiplayer"