Thing templates can now define verbs in TOML using [verbs] section with module:function references. Verbs are resolved at spawn time and bound to the spawned object instance using functools.partial. Works for both Thing and Container instances.
245 lines
6.9 KiB
Python
245 lines
6.9 KiB
Python
"""Tests for TOML verb support in thing templates."""
|
|
|
|
import textwrap
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
from mudlib.player import Player
|
|
from mudlib.things import (
|
|
Container,
|
|
ThingTemplate,
|
|
load_thing_template,
|
|
spawn_thing,
|
|
)
|
|
from mudlib.zone import Zone
|
|
|
|
|
|
# Test handler functions
|
|
async def _test_verb_handler(obj, player, args):
|
|
"""Test verb handler that sends a message."""
|
|
await player.send(f"verb on {obj.name} with args={args}\r\n")
|
|
|
|
|
|
async def _another_test_handler(obj, player, args):
|
|
"""Another test handler."""
|
|
await player.send(f"another verb on {obj.name}\r\n")
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_writer():
|
|
writer = MagicMock()
|
|
writer.write = MagicMock()
|
|
writer.drain = AsyncMock()
|
|
return writer
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_reader():
|
|
return MagicMock()
|
|
|
|
|
|
@pytest.fixture
|
|
def test_zone():
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
return Zone(name="testzone", width=10, height=10, terrain=terrain)
|
|
|
|
|
|
@pytest.fixture
|
|
def player(mock_reader, mock_writer, test_zone):
|
|
return Player(
|
|
name="TestPlayer",
|
|
x=5,
|
|
y=5,
|
|
reader=mock_reader,
|
|
writer=mock_writer,
|
|
location=test_zone,
|
|
)
|
|
|
|
|
|
def test_thing_template_default_empty_verbs():
|
|
"""ThingTemplate has empty verbs dict by default."""
|
|
template = ThingTemplate(name="test", description="a test")
|
|
assert template.verbs == {}
|
|
|
|
|
|
def test_load_template_with_verbs(tmp_path):
|
|
"""load_thing_template parses [verbs] section from TOML."""
|
|
toml = tmp_path / "key.toml"
|
|
toml.write_text(
|
|
textwrap.dedent("""\
|
|
name = "key"
|
|
description = "a brass key"
|
|
[verbs]
|
|
unlock = "tests.test_toml_verbs:_test_verb_handler"
|
|
""")
|
|
)
|
|
template = load_thing_template(toml)
|
|
assert template.verbs == {"unlock": "tests.test_toml_verbs:_test_verb_handler"}
|
|
|
|
|
|
def test_load_template_without_verbs_section(tmp_path):
|
|
"""load_thing_template works without [verbs] section (backwards compatible)."""
|
|
toml = tmp_path / "rock.toml"
|
|
toml.write_text(
|
|
textwrap.dedent("""\
|
|
name = "rock"
|
|
description = "a smooth stone"
|
|
portable = true
|
|
""")
|
|
)
|
|
template = load_thing_template(toml)
|
|
assert template.verbs == {}
|
|
|
|
|
|
def test_load_template_with_multiple_verbs(tmp_path):
|
|
"""load_thing_template handles multiple verbs."""
|
|
toml = tmp_path / "multi.toml"
|
|
toml.write_text(
|
|
textwrap.dedent("""\
|
|
name = "door"
|
|
description = "a wooden door"
|
|
[verbs]
|
|
unlock = "tests.test_toml_verbs:_test_verb_handler"
|
|
open = "tests.test_toml_verbs:_another_test_handler"
|
|
""")
|
|
)
|
|
template = load_thing_template(toml)
|
|
assert len(template.verbs) == 2
|
|
assert "unlock" in template.verbs
|
|
assert "open" in template.verbs
|
|
|
|
|
|
def test_spawn_thing_registers_verbs(test_zone, tmp_path):
|
|
"""spawn_thing registers verbs on spawned instance."""
|
|
toml = tmp_path / "key.toml"
|
|
toml.write_text(
|
|
textwrap.dedent("""\
|
|
name = "key"
|
|
description = "a brass key"
|
|
[verbs]
|
|
unlock = "tests.test_toml_verbs:_test_verb_handler"
|
|
""")
|
|
)
|
|
template = load_thing_template(toml)
|
|
thing = spawn_thing(template, test_zone, x=5, y=5)
|
|
|
|
assert thing.has_verb("unlock")
|
|
|
|
|
|
def test_spawn_container_registers_verbs(test_zone, tmp_path):
|
|
"""spawn_thing registers verbs on Container instances too."""
|
|
toml = tmp_path / "chest.toml"
|
|
toml.write_text(
|
|
textwrap.dedent("""\
|
|
name = "chest"
|
|
description = "a wooden chest"
|
|
capacity = 10
|
|
[verbs]
|
|
unlock = "tests.test_toml_verbs:_test_verb_handler"
|
|
""")
|
|
)
|
|
template = load_thing_template(toml)
|
|
container = spawn_thing(template, test_zone, x=5, y=5)
|
|
|
|
assert isinstance(container, Container)
|
|
assert container.has_verb("unlock")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_spawned_verb_handler_receives_correct_args(test_zone, player, tmp_path):
|
|
"""Spawned thing's verb handler can be called and receives (player, args)."""
|
|
toml = tmp_path / "key.toml"
|
|
toml.write_text(
|
|
textwrap.dedent("""\
|
|
name = "key"
|
|
description = "a brass key"
|
|
[verbs]
|
|
unlock = "tests.test_toml_verbs:_test_verb_handler"
|
|
""")
|
|
)
|
|
template = load_thing_template(toml)
|
|
thing = spawn_thing(template, test_zone, x=5, y=5)
|
|
|
|
# Call the verb handler
|
|
handler = thing.get_verb("unlock")
|
|
assert handler is not None
|
|
await handler(player, "door")
|
|
|
|
# Check that the message was sent
|
|
player.writer.write.assert_called()
|
|
call_args = player.writer.write.call_args[0][0]
|
|
assert "verb on key with args=door" in call_args
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_verb_handler_receives_correct_object_instance(
|
|
test_zone, player, tmp_path
|
|
):
|
|
"""Handler receives the correct object instance as first arg (via partial)."""
|
|
toml = tmp_path / "key.toml"
|
|
toml.write_text(
|
|
textwrap.dedent("""\
|
|
name = "brass key"
|
|
description = "a brass key"
|
|
[verbs]
|
|
unlock = "tests.test_toml_verbs:_test_verb_handler"
|
|
""")
|
|
)
|
|
template = load_thing_template(toml)
|
|
thing = spawn_thing(template, test_zone, x=5, y=5)
|
|
|
|
# Call the verb handler
|
|
handler = thing.get_verb("unlock")
|
|
assert handler is not None
|
|
await handler(player, "chest")
|
|
|
|
# Check that the object name appears in the message (verifying correct object)
|
|
player.writer.write.assert_called()
|
|
call_args = player.writer.write.call_args[0][0]
|
|
assert "verb on brass key with args=chest" in call_args
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_multiple_verbs_all_registered(test_zone, player, tmp_path):
|
|
"""Multiple verbs in TOML all get registered."""
|
|
toml = tmp_path / "door.toml"
|
|
toml.write_text(
|
|
textwrap.dedent("""\
|
|
name = "door"
|
|
description = "a wooden door"
|
|
[verbs]
|
|
unlock = "tests.test_toml_verbs:_test_verb_handler"
|
|
open = "tests.test_toml_verbs:_another_test_handler"
|
|
""")
|
|
)
|
|
template = load_thing_template(toml)
|
|
thing = spawn_thing(template, test_zone, x=5, y=5)
|
|
|
|
# Both verbs should be registered
|
|
assert thing.has_verb("unlock")
|
|
assert thing.has_verb("open")
|
|
|
|
# Both should work
|
|
unlock_handler = thing.get_verb("unlock")
|
|
assert unlock_handler is not None
|
|
await unlock_handler(player, "test1")
|
|
player.writer.write.assert_called()
|
|
|
|
open_handler = thing.get_verb("open")
|
|
assert open_handler is not None
|
|
await open_handler(player, "test2")
|
|
call_args = player.writer.write.call_args[0][0]
|
|
assert "another verb on door" in call_args
|
|
|
|
|
|
def test_bad_handler_ref_skipped(test_zone):
|
|
"""A TOML verb with bad handler ref is skipped, not a crash."""
|
|
template = ThingTemplate(
|
|
name="bad",
|
|
description="oops",
|
|
portable=True,
|
|
verbs={"zap": "nonexistent.module:fake_handler"},
|
|
)
|
|
thing = spawn_thing(template, test_zone, x=5, y=5)
|
|
assert not thing.has_verb("zap")
|