# 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.fixture def 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.fixture def 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