Something went wrong. Try again.
A 5e storytelling engine with an LLM DM
Something went wrong. Try again.
Python
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232# pyright: reportOptionalSubscript=false, reportReturnType=false# pyright: reportOperatorIssue=false# Tests subscript load_session results without null-narrowing — setup# guarantees the file exists."""Tests for session state management."""
from pathlib import Path
import pytest
from storied.session import ( extract_wiki_links, format_session_context, load_session, name_to_slug, parse_session, resolve_wiki_link, save_session, update_session,)
@pytest.fixturedef session_base(tmp_path: Path) -> Path: """Create a base directory with player structure.""" (tmp_path / "players" / "test-player").mkdir(parents=True) return tmp_path
@pytest.fixturedef saved_session(session_base: Path) -> dict: """Create and save a session with situation and threads.""" data = { "location": "rusty-anchor", "body": ( "## Situation\n" "At the tavern, talking to Vera.\n\n" "## Present\n" "- [[Vera Blackwater]]\n" "- [[Henrik]]\n\n" "## Open Threads\n" "- Investigate the warehouse\n" "- Find the missing merchant" ), } save_session("test-player", data) return load_session("test-player")
# ── Parsing ──────────────────────────────────────────────────────────────
class TestParseSession: def test_with_frontmatter(self): content = "---\nlocation: tavern\n---\n\n## Situation\nAt the bar." result = parse_session(content) assert result["location"] == "tavern" assert "Situation" in result["body"]
def test_without_frontmatter(self): result = parse_session("Just notes") assert result["body"] == "Just notes"
def test_empty_frontmatter(self): result = parse_session("---\n---\n\nBody here") assert result["body"] == "Body here"
# ── Load / Save ──────────────────────────────────────────────────────────
class TestLoadSaveSession: def test_save_and_load(self, session_base: Path): save_session("test-player", {"location": "docks", "body": "At the docks."}) loaded = load_session("test-player") assert loaded["location"] == "docks" assert "At the docks" in loaded["body"]
def test_load_nonexistent(self, session_base: Path): assert load_session("nobody") is None
def test_save_adds_timestamp(self, session_base: Path): save_session("test-player", {"body": ""}) loaded = load_session("test-player") assert "updated" in loaded
# ── Updates ──────────────────────────────────────────────────────────────
class TestUpdateSession: def test_update_situation(self, session_base: Path, saved_session: dict): result = update_session( "test-player", {"situation": "Escaped to the harbor."}, ) assert "Updated situation" in result loaded = load_session("test-player") assert "Escaped to the harbor" in loaded["body"]
def test_update_threads_from_list(self, session_base: Path, saved_session: dict): update_session( "test-player", {"threads": ["New thread one", "New thread two"]}, ) loaded = load_session("test-player") assert "- New thread one" in loaded["body"] assert "- New thread two" in loaded["body"]
def test_update_present_from_list(self, session_base: Path, saved_session: dict): update_session( "test-player", {"present": ["[[Captain Harrik]]"]}, ) loaded = load_session("test-player") assert "Captain Harrik" in loaded["body"]
def test_update_location(self, session_base: Path, saved_session: dict): result = update_session( "test-player", {"location": "harbor"}, ) assert "location = harbor" in result loaded = load_session("test-player") assert loaded["location"] == "harbor"
def test_creates_session_if_missing(self, session_base: Path): update_session( "test-player", {"situation": "Starting fresh."}, ) loaded = load_session("test-player") assert "Starting fresh" in loaded["body"]
def test_no_changes(self, session_base: Path, saved_session: dict): result = update_session("test-player", {}) assert "No changes" in result
def test_appends_new_section(self, session_base: Path): save_session("test-player", {"body": ""}) update_session( "test-player", {"situation": "Brand new situation."}, ) loaded = load_session("test-player") assert "## Situation" in loaded["body"] assert "Brand new situation" in loaded["body"]
# ── Wiki links ───────────────────────────────────────────────────────────
class TestExtractWikiLinks: def test_single_link(self): assert extract_wiki_links("Talk to [[Vera]]") == ["Vera"]
def test_multiple_links(self): result = extract_wiki_links("[[Vera]] and [[Henrik]] at [[The Rusty Anchor]]") assert result == ["Vera", "Henrik", "The Rusty Anchor"]
def test_no_links(self): assert extract_wiki_links("No links here") == []
def test_empty_string(self): assert extract_wiki_links("") == []
class TestResolveWikiLink: def test_finds_npc(self, tmp_path: Path): npc_dir = tmp_path / "worlds" / "default" / "npcs" npc_dir.mkdir(parents=True) (npc_dir / "Vera Blackwater.md").write_text("---\nname: Vera\n---\n") result = resolve_wiki_link("Vera Blackwater", "default") assert result is not None assert result.name == "Vera Blackwater.md"
def test_finds_location(self, tmp_path: Path): loc_dir = tmp_path / "worlds" / "default" / "locations" loc_dir.mkdir(parents=True) (loc_dir / "The Rusty Anchor.md").write_text( "---\nname: The Rusty Anchor\n---\n" ) result = resolve_wiki_link("The Rusty Anchor", "default") assert result is not None
def test_not_found(self, tmp_path: Path): (tmp_path / "worlds" / "default").mkdir(parents=True) assert resolve_wiki_link("Nobody", "default") is None
def test_priority_order(self, tmp_path: Path): """NPCs are checked before locations.""" for entity_type in ("npcs", "locations"): d = tmp_path / "worlds" / "default" / entity_type d.mkdir(parents=True) (d / "Ambiguous.md").write_text(f"---\ntype: {entity_type}\n---\n") result = resolve_wiki_link("Ambiguous", "default") assert "npcs" in str(result)
# ── Slugification ────────────────────────────────────────────────────────
class TestNameToSlug: def test_simple_name(self): assert name_to_slug("Vera Blackwater") == "vera-blackwater"
def test_special_characters(self): assert name_to_slug("The Rusty Anchor!") == "the-rusty-anchor"
def test_multiple_spaces(self): assert name_to_slug("Captain Harrik") == "captain-harrik"
# ── Context formatting ───────────────────────────────────────────────────
class TestFormatSessionContext: def test_includes_location(self): data = {"location": "harbor", "body": ""} result = format_session_context(data) assert "harbor" in result
def test_includes_body(self): data = {"body": "## Situation\nAt the docks."} result = format_session_context(data) assert "At the docks" in result
def test_no_location(self): data = {"body": "Just a body"} result = format_session_context(data) assert "Location" not in result