"""Tests for crafting commands.""" from unittest.mock import AsyncMock, MagicMock import pytest from mudlib.commands.crafting import cmd_craft, cmd_recipes from mudlib.crafting import Recipe, recipes from mudlib.player import Player from mudlib.things import ThingTemplate, spawn_thing, thing_templates from mudlib.zone import Zone from mudlib.zones import register_zone, zone_registry @pytest.fixture(autouse=True) def _clean_registries(): """Snapshot and restore registries to prevent test leakage.""" saved_zones = dict(zone_registry) saved_templates = dict(thing_templates) saved_recipes = dict(recipes) zone_registry.clear() thing_templates.clear() recipes.clear() yield zone_registry.clear() zone_registry.update(saved_zones) thing_templates.clear() thing_templates.update(saved_templates) recipes.clear() recipes.update(saved_recipes) def _make_zone(name="overworld", width=20, height=20): """Create a test zone.""" 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 test 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_craft_success(): """Craft with ingredients: consumed, result in inventory.""" zone = _make_zone() player = _make_player(zone=zone) # Register templates plank_template = ThingTemplate(name="plank", description="A wooden plank") nail_template = ThingTemplate(name="nail", description="A small nail") table_template = ThingTemplate(name="table", description="A sturdy table") thing_templates["plank"] = plank_template thing_templates["nail"] = nail_template thing_templates["table"] = table_template # Add ingredients to player inventory plank1 = spawn_thing(plank_template, player) plank2 = spawn_thing(plank_template, player) nail1 = spawn_thing(nail_template, player) # Register recipe recipe = Recipe( name="wooden_table", description="Craft a table", ingredients=["plank", "plank", "nail"], result="table", ) recipes["wooden_table"] = recipe # Craft the item await cmd_craft(player, "wooden_table") # Check success message was sent assert player.writer.write.called output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "table" in output.lower() # Check ingredients were consumed inventory = player.contents assert plank1 not in inventory assert plank2 not in inventory assert nail1 not in inventory # Check result was added to inventory table_in_inventory = [obj for obj in inventory if obj.name == "table"] assert len(table_in_inventory) == 1 @pytest.mark.asyncio async def test_craft_missing_ingredients(): """Error message listing what's missing.""" zone = _make_zone() player = _make_player(zone=zone) # Register templates plank_template = ThingTemplate(name="plank", description="A wooden plank") table_template = ThingTemplate(name="table", description="A sturdy table") thing_templates["plank"] = plank_template thing_templates["table"] = table_template # Add only one plank (recipe needs two) spawn_thing(plank_template, player) # Register recipe needing two planks recipe = Recipe( name="wooden_table", description="Craft a table", ingredients=["plank", "plank"], result="table", ) recipes["wooden_table"] = recipe # Try to craft await cmd_craft(player, "wooden_table") # Check error message assert player.writer.write.called output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "missing" in output.lower() or "need" in output.lower() @pytest.mark.asyncio async def test_craft_unknown_recipe(): """Error for nonexistent recipe.""" zone = _make_zone() player = _make_player(zone=zone) await cmd_craft(player, "nonexistent_recipe") assert player.writer.write.called output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "unknown" in output.lower() or "not found" in output.lower() @pytest.mark.asyncio async def test_craft_no_args(): """Error with usage when no args provided.""" zone = _make_zone() player = _make_player(zone=zone) await cmd_craft(player, "") assert player.writer.write.called output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "usage" in output.lower() or "craft" in output.lower() @pytest.mark.asyncio async def test_craft_unknown_result_template(): """Recipe result template not in thing_templates.""" zone = _make_zone() player = _make_player(zone=zone) # Register template for ingredient plank_template = ThingTemplate(name="plank", description="A wooden plank") thing_templates["plank"] = plank_template # Add ingredient to inventory spawn_thing(plank_template, player) # Register recipe with unknown result template recipe = Recipe( name="broken_recipe", description="This recipe has a broken result", ingredients=["plank"], result="unknown_item", # This template doesn't exist ) recipes["broken_recipe"] = recipe # Try to craft await cmd_craft(player, "broken_recipe") # Check error message assert player.writer.write.called output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "error" in output.lower() or "unknown" in output.lower() @pytest.mark.asyncio async def test_recipes_list(): """Shows available recipes.""" zone = _make_zone() player = _make_player(zone=zone) # Register some recipes recipes["wooden_table"] = Recipe( name="wooden_table", description="Craft a table", ingredients=["plank", "plank"], result="table", ) recipes["wooden_chair"] = Recipe( name="wooden_chair", description="Craft a chair", ingredients=["plank"], result="chair", ) await cmd_recipes(player, "") assert player.writer.write.called output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "wooden_table" in output.lower() assert "wooden_chair" in output.lower() @pytest.mark.asyncio async def test_recipes_detail(): """Shows specific recipe details.""" zone = _make_zone() player = _make_player(zone=zone) # Register a recipe recipes["wooden_table"] = Recipe( name="wooden_table", description="Craft a sturdy table", ingredients=["plank", "plank", "nail", "nail"], result="table", ) await cmd_recipes(player, "wooden_table") assert player.writer.write.called output = "".join(call.args[0] for call in player.writer.write.call_args_list) assert "wooden_table" in output.lower() assert "plank" in output.lower() assert "nail" in output.lower() assert "table" in output.lower()