mud/tests/test_verbs.py
Jared Miller 9c480f8d47
Remove duplicate mock_writer/mock_reader fixtures
Removed identical local copies from 45 test files. These fixtures
are already defined in conftest.py.
2026-02-16 15:29:21 -05:00

228 lines
6.5 KiB
Python

"""Tests for the verb system on Object."""
import pytest
from mudlib.object import Object
from mudlib.player import Player
from mudlib.thing import Thing
from mudlib.verbs import find_object, verb
from mudlib.zone import Zone
@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_object_starts_with_empty_verbs():
"""Object starts with empty _verbs dict."""
obj = Object(name="rock")
assert obj._verbs == {}
def test_register_verb():
"""register_verb adds a verb to the _verbs dict."""
async def test_handler(obj, player, args):
pass
obj = Object(name="fountain")
obj.register_verb("drink", test_handler)
assert "drink" in obj._verbs
assert obj._verbs["drink"] is test_handler
def test_get_verb_returns_handler():
"""get_verb returns the registered handler."""
async def test_handler(obj, player, args):
pass
obj = Object(name="fountain")
obj.register_verb("drink", test_handler)
assert obj.get_verb("drink") is test_handler
def test_get_verb_returns_none_for_unknown():
"""get_verb returns None for unknown verb."""
obj = Object(name="rock")
assert obj.get_verb("nonexistent") is None
def test_has_verb_returns_true_when_present():
"""has_verb returns True when verb is registered."""
async def test_handler(obj, player, args):
pass
obj = Object(name="fountain")
obj.register_verb("drink", test_handler)
assert obj.has_verb("drink") is True
def test_has_verb_returns_false_when_absent():
"""has_verb returns False when verb is not registered."""
obj = Object(name="rock")
assert obj.has_verb("drink") is False
def test_verb_decorator_marks_method():
"""@verb decorator marks method with _verb_name attribute."""
class TestObject(Object):
@verb("test")
async def test_verb(self, player, args):
pass
assert hasattr(TestObject.test_verb, "_verb_name")
assert TestObject.test_verb._verb_name == "test"
def test_verb_decorator_auto_registers_on_instantiation():
"""Class with @verb methods auto-registers verbs on instantiation."""
class Fountain(Thing):
@verb("drink")
async def drink(self, player, args):
pass
fountain = Fountain(name="fountain")
assert fountain.has_verb("drink")
assert fountain.get_verb("drink") is not None
def test_multiple_verbs_on_same_class():
"""Multiple verbs on same class all register."""
class Fountain(Thing):
@verb("drink")
async def drink(self, player, args):
pass
@verb("splash")
async def splash(self, player, args):
pass
fountain = Fountain(name="fountain")
assert fountain.has_verb("drink")
assert fountain.has_verb("splash")
@pytest.mark.asyncio
async def test_verb_handler_can_be_called(player):
"""Verb handler can be called as an async function."""
called = False
received_args = None
class Fountain(Thing):
@verb("drink")
async def drink(self, player, args):
nonlocal called, received_args
called = True
received_args = args
fountain = Fountain(name="fountain")
handler = fountain.get_verb("drink")
assert handler is not None
# handler is a bound method, so we don't pass self/fountain again
await handler(player, "from the left side")
assert called
assert received_args == "from the left side"
def test_verb_registered_on_parent_class_propagates_to_subclass():
"""Verb registered on parent class propagates to subclass instances."""
class BaseFountain(Thing):
@verb("drink")
async def drink(self, player, args):
pass
class FancyFountain(BaseFountain):
@verb("splash")
async def splash(self, player, args):
pass
fancy = FancyFountain(name="fancy fountain")
assert fancy.has_verb("drink")
assert fancy.has_verb("splash")
def test_find_object_by_name_in_inventory(player):
"""find_object finds object by name in inventory."""
sword = Thing(name="sword", location=player)
found = find_object("sword", player)
assert found is sword
def test_find_object_by_alias_in_inventory(player):
"""find_object finds object by alias in inventory."""
can = Thing(name="pepsi can", aliases=["can", "pepsi"], location=player)
found = find_object("can", player)
assert found is can
def test_find_object_by_name_on_ground(player, test_zone):
"""find_object finds object by name on ground."""
rock = Thing(name="rock", location=test_zone, x=5, y=5)
found = find_object("rock", player)
assert found is rock
def test_find_object_by_alias_on_ground(player, test_zone):
"""find_object finds object by alias on ground."""
can = Thing(
name="pepsi can", aliases=["can", "pepsi"], location=test_zone, x=5, y=5
)
found = find_object("pepsi", player)
assert found is can
def test_find_object_prefers_inventory_over_ground(player, test_zone):
"""find_object prefers inventory over ground when matching object in both."""
Thing(name="sword", location=test_zone, x=5, y=5)
inv_sword = Thing(name="sword", location=player)
found = find_object("sword", player)
assert found is inv_sword
def test_find_object_returns_none_when_nothing_matches(player):
"""find_object returns None when nothing matches."""
found = find_object("nonexistent", player)
assert found is None
def test_find_object_searches_all_objects_not_just_things(player, test_zone):
"""find_object searches all Objects, not just Things."""
class CustomObject(Object):
def __init__(self, name, aliases=None, **kwargs):
super().__init__(name=name, **kwargs)
self.aliases = aliases or []
custom = CustomObject(name="widget", aliases=["gadget"], location=player)
found = find_object("widget", player)
assert found is custom
found_by_alias = find_object("gadget", player)
assert found_by_alias is custom
def test_find_object_skips_self(player, test_zone):
"""find_object should not return the querying player."""
player.move_to(test_zone, x=0, y=0)
result = find_object(player.name, player)
assert result is None