"""Tests for dialogue tree data model and TOML loading.""" import tempfile from pathlib import Path import pytest from mudlib.dialogue import ( load_all_dialogues, load_dialogue, ) def test_load_dialogue_from_toml(): """Load a dialogue tree from TOML with correct structure.""" toml_content = """ npc_name = "guard" root_node = "greeting" [nodes.greeting] text = "Halt! State your business." [[nodes.greeting.choices]] text = "I'm just passing through." next_node = "passing" [[nodes.greeting.choices]] text = "I have a delivery." next_node = "delivery" [nodes.passing] text = "Move along then, quickly." [nodes.delivery] text = "Leave it by the gate." """ with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: f.write(toml_content) f.flush() path = Path(f.name) try: tree = load_dialogue(path) assert tree.npc_name == "guard" assert tree.root_node == "greeting" assert len(tree.nodes) == 3 # Check greeting node greeting = tree.nodes["greeting"] assert greeting.id == "greeting" assert greeting.text == "Halt! State your business." assert len(greeting.choices) == 2 assert greeting.choices[0].text == "I'm just passing through." assert greeting.choices[0].next_node == "passing" assert greeting.choices[1].text == "I have a delivery." assert greeting.choices[1].next_node == "delivery" # Check terminal nodes passing = tree.nodes["passing"] assert passing.id == "passing" assert passing.text == "Move along then, quickly." assert len(passing.choices) == 0 delivery = tree.nodes["delivery"] assert delivery.id == "delivery" assert delivery.text == "Leave it by the gate." assert len(delivery.choices) == 0 finally: path.unlink() def test_node_traversal(): """Follow choices from root to next nodes.""" toml_content = """ npc_name = "merchant" root_node = "greeting" [nodes.greeting] text = "Welcome to my shop!" [[nodes.greeting.choices]] text = "What do you sell?" next_node = "wares" [nodes.wares] text = "Potions and scrolls, finest in town." """ with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: f.write(toml_content) f.flush() path = Path(f.name) try: tree = load_dialogue(path) current = tree.nodes[tree.root_node] assert current.text == "Welcome to my shop!" # Follow the first choice next_id = current.choices[0].next_node current = tree.nodes[next_id] assert current.text == "Potions and scrolls, finest in town." finally: path.unlink() def test_terminal_node_ends_conversation(): """Nodes with no choices are terminal.""" toml_content = """ npc_name = "beggar" root_node = "plea" [nodes.plea] text = "Spare a coin?" [[nodes.plea.choices]] text = "Here you go." next_node = "thanks" [nodes.thanks] text = "Bless you, kind soul." """ with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: f.write(toml_content) f.flush() path = Path(f.name) try: tree = load_dialogue(path) thanks = tree.nodes["thanks"] assert len(thanks.choices) == 0 # Terminal node finally: path.unlink() def test_choice_with_condition(): """Choices can have optional conditions.""" toml_content = """ npc_name = "gatekeeper" root_node = "greeting" [nodes.greeting] text = "The gate is locked." [[nodes.greeting.choices]] text = "I have the key." next_node = "unlock" condition = "has_item:gate_key" [nodes.unlock] text = "Very well, pass through." """ with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: f.write(toml_content) f.flush() path = Path(f.name) try: tree = load_dialogue(path) greeting = tree.nodes["greeting"] choice = greeting.choices[0] assert choice.condition == "has_item:gate_key" assert choice.next_node == "unlock" finally: path.unlink() def test_node_with_action(): """Nodes can have optional actions.""" toml_content = """ npc_name = "quest_giver" root_node = "offer" [nodes.offer] text = "Will you help me find my lost cat?" [[nodes.offer.choices]] text = "I'll do it." next_node = "accept" [nodes.accept] text = "Thank you! Here's a map." action = "give_item:cat_map" """ with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: f.write(toml_content) f.flush() path = Path(f.name) try: tree = load_dialogue(path) accept = tree.nodes["accept"] assert accept.action == "give_item:cat_map" finally: path.unlink() def test_load_all_dialogues(): """Load multiple dialogue files from a directory.""" guard_content = """ npc_name = "guard" root_node = "greeting" [nodes.greeting] text = "Halt!" """ merchant_content = """ npc_name = "merchant" root_node = "greeting" [nodes.greeting] text = "Welcome!" """ with tempfile.TemporaryDirectory() as tmpdir: tmppath = Path(tmpdir) (tmppath / "guard.toml").write_text(guard_content) (tmppath / "merchant.toml").write_text(merchant_content) trees = load_all_dialogues(tmppath) assert len(trees) == 2 assert "guard" in trees assert "merchant" in trees assert trees["guard"].npc_name == "guard" assert trees["merchant"].npc_name == "merchant" def test_missing_root_node(): """Raise error if root_node doesn't exist in nodes.""" toml_content = """ npc_name = "broken" root_node = "nonexistent" [nodes.greeting] text = "Hello." """ with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: f.write(toml_content) f.flush() path = Path(f.name) try: with pytest.raises(ValueError, match="root_node.*not found"): load_dialogue(path) finally: path.unlink() def test_missing_next_node(): """Raise error if a choice references a non-existent node.""" toml_content = """ npc_name = "broken" root_node = "greeting" [nodes.greeting] text = "Hello." [[nodes.greeting.choices]] text = "Goodbye." next_node = "nonexistent" """ with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: f.write(toml_content) f.flush() path = Path(f.name) try: with pytest.raises(ValueError, match="next_node.*not found"): load_dialogue(path) finally: path.unlink()