"""Tests for furnish and unfurnish commands.""" from unittest.mock import AsyncMock, MagicMock import pytest from mudlib.housing import init_housing from mudlib.player import Player from mudlib.thing import Thing from mudlib.zone import Zone from mudlib.zones import register_zone, zone_registry @pytest.fixture(autouse=True) def _clean_registries(): """Clear zone registry between tests.""" saved = dict(zone_registry) zone_registry.clear() yield zone_registry.clear() zone_registry.update(saved) def _make_zone(name="overworld", width=20, height=20): """Create a zone for testing.""" terrain = [["." for _ in range(width)] for _ in range(height)] zone = Zone( name=name, description=name, width=width, height=height, terrain=terrain, toroidal=True, ) register_zone(name, zone) return zone def _make_player(name="tester", zone=None, x=5, y=5): """Create a player with mock writer.""" mock_writer = MagicMock() mock_writer.write = MagicMock() mock_writer.drain = AsyncMock() return Player(name=name, location=zone, x=x, y=y, writer=mock_writer) @pytest.mark.asyncio async def test_furnish_places_item(tmp_path): """furnish moves an item from inventory to the zone at player position.""" from mudlib.commands.furnish import cmd_furnish init_housing(tmp_path) # Create home zone and player home_zone = _make_zone("home:alice", width=9, height=9) player = _make_player("alice", zone=home_zone, x=4, y=4) player.home_zone = "home:alice" # Give player a chair chair = Thing(name="chair", description="A wooden chair") chair.move_to(player) # Furnish the chair await cmd_furnish(player, "chair") # Chair should be in the zone at player position assert chair.location is home_zone assert chair.x == 4 assert chair.y == 4 assert chair not in player.contents # Player should get feedback player.writer.write.assert_called() output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "chair" in output.lower() @pytest.mark.asyncio async def test_furnish_not_in_home_zone(): """furnish fails if player is not in their home zone.""" from mudlib.commands.furnish import cmd_furnish overworld = _make_zone("overworld") player = _make_player("alice", zone=overworld) player.home_zone = "home:alice" chair = Thing(name="chair") chair.move_to(player) await cmd_furnish(player, "chair") # Chair should still be in inventory assert chair.location is player assert chair in player.contents # Player should get error message player.writer.write.assert_called() output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "home zone" in output.lower() @pytest.mark.asyncio async def test_furnish_item_not_in_inventory(): """furnish fails if item is not in player inventory.""" from mudlib.commands.furnish import cmd_furnish home_zone = _make_zone("home:alice", width=9, height=9) player = _make_player("alice", zone=home_zone) player.home_zone = "home:alice" await cmd_furnish(player, "chair") # Player should get error message player.writer.write.assert_called() output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "don't have" in output.lower() or "not carrying" in output.lower() @pytest.mark.asyncio async def test_furnish_no_args(): """furnish fails with usage message if no args provided.""" from mudlib.commands.furnish import cmd_furnish home_zone = _make_zone("home:alice", width=9, height=9) player = _make_player("alice", zone=home_zone) player.home_zone = "home:alice" await cmd_furnish(player, "") # Player should get usage message player.writer.write.assert_called() output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "usage" in output.lower() or "furnish" in output.lower() @pytest.mark.asyncio async def test_unfurnish_picks_up_item(tmp_path): """unfurnish moves furniture from zone to player inventory.""" from mudlib.commands.furnish import cmd_unfurnish init_housing(tmp_path) # Create home zone with furniture home_zone = _make_zone("home:bob", width=9, height=9) player = _make_player("bob", zone=home_zone, x=4, y=4) player.home_zone = "home:bob" # Place a table at player position table = Thing(name="table", description="A wooden table") table.move_to(home_zone, x=4, y=4) # Unfurnish the table await cmd_unfurnish(player, "table") # Table should be in inventory assert table.location is player assert table in player.contents assert table not in home_zone._contents # Player should get feedback player.writer.write.assert_called() output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "table" in output.lower() @pytest.mark.asyncio async def test_unfurnish_not_in_home_zone(): """unfurnish fails if player is not in their home zone.""" from mudlib.commands.furnish import cmd_unfurnish overworld = _make_zone("overworld") player = _make_player("bob", zone=overworld) player.home_zone = "home:bob" # Place a table table = Thing(name="table") table.move_to(overworld, x=5, y=5) await cmd_unfurnish(player, "table") # Table should still be on ground assert table.location is overworld assert table not in player.contents # Player should get error message player.writer.write.assert_called() output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "home zone" in output.lower() @pytest.mark.asyncio async def test_unfurnish_nothing_at_position(): """unfurnish fails if no matching furniture at player position.""" from mudlib.commands.furnish import cmd_unfurnish home_zone = _make_zone("home:bob", width=9, height=9) player = _make_player("bob", zone=home_zone, x=4, y=4) player.home_zone = "home:bob" # No furniture at position await cmd_unfurnish(player, "table") # Player should get error message player.writer.write.assert_called() output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert ( "no" in output.lower() or "don't see" in output.lower() or "can't find" in output.lower() ) @pytest.mark.asyncio async def test_unfurnish_no_args(): """unfurnish fails with usage message if no args provided.""" from mudlib.commands.furnish import cmd_unfurnish home_zone = _make_zone("home:bob", width=9, height=9) player = _make_player("bob", zone=home_zone) player.home_zone = "home:bob" await cmd_unfurnish(player, "") # Player should get usage message player.writer.write.assert_called() output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "usage" in output.lower() or "unfurnish" in output.lower() @pytest.mark.asyncio async def test_furnish_saves_zone(tmp_path): """furnish persists furniture to the zone TOML file.""" import tomllib from mudlib.commands.furnish import cmd_furnish init_housing(tmp_path) # Create home zone and player home_zone = _make_zone("home:charlie", width=9, height=9) player = _make_player("charlie", zone=home_zone, x=4, y=4) player.home_zone = "home:charlie" # Give player a lamp lamp = Thing(name="lamp", description="A brass lamp") lamp.move_to(player) # Furnish it await cmd_furnish(player, "lamp") # Check TOML file zone_file = tmp_path / "charlie.toml" assert zone_file.exists() with open(zone_file, "rb") as f: data = tomllib.load(f) # Should have furniture entry furniture = data.get("furniture", []) assert len(furniture) == 1 assert furniture[0]["template"] == "lamp" assert furniture[0]["x"] == 4 assert furniture[0]["y"] == 4