"""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