Removed identical local copies from 45 test files. These fixtures are already defined in conftest.py.
441 lines
12 KiB
Python
441 lines
12 KiB
Python
"""Tests for builder commands."""
|
|
|
|
import pytest
|
|
|
|
from mudlib.player import Player, players
|
|
from mudlib.zone import Zone
|
|
from mudlib.zones import get_zone, register_zone, zone_registry
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_state():
|
|
players.clear()
|
|
zone_registry.clear()
|
|
yield
|
|
players.clear()
|
|
zone_registry.clear()
|
|
|
|
|
|
@pytest.fixture
|
|
def zone():
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
z = Zone(name="hub", width=10, height=10, terrain=terrain)
|
|
register_zone("hub", z)
|
|
return z
|
|
|
|
|
|
@pytest.fixture
|
|
def player(zone, mock_writer, mock_reader):
|
|
p = Player(
|
|
name="builder",
|
|
x=5,
|
|
y=5,
|
|
writer=mock_writer,
|
|
reader=mock_reader,
|
|
location=zone,
|
|
is_admin=True,
|
|
)
|
|
zone._contents.append(p)
|
|
players["builder"] = p
|
|
return p
|
|
|
|
|
|
# --- @goto ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_goto_existing_zone(player):
|
|
"""@goto teleports player to named zone's spawn point."""
|
|
from mudlib.commands.build import cmd_goto
|
|
|
|
target = Zone(
|
|
name="forest",
|
|
width=5,
|
|
height=5,
|
|
terrain=[["." for _ in range(5)] for _ in range(5)],
|
|
spawn_x=2,
|
|
spawn_y=3,
|
|
)
|
|
register_zone("forest", target)
|
|
|
|
await cmd_goto(player, "forest")
|
|
|
|
assert player.location is target
|
|
assert player.x == 2
|
|
assert player.y == 3
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_goto_nonexistent_zone(player, mock_writer):
|
|
"""@goto with unknown zone name shows error."""
|
|
from mudlib.commands.build import cmd_goto
|
|
|
|
await cmd_goto(player, "nowhere")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "not found" in written.lower() or "no zone" in written.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_goto_no_args(player, mock_writer):
|
|
"""@goto without arguments shows usage."""
|
|
from mudlib.commands.build import cmd_goto
|
|
|
|
await cmd_goto(player, "")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[0][0][0]
|
|
assert "usage" in written.lower() or "goto" in written.lower()
|
|
|
|
|
|
# --- @dig ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dig_creates_zone(player):
|
|
"""@dig creates a new zone and teleports player there."""
|
|
from mudlib.commands.build import cmd_dig
|
|
|
|
await cmd_dig(player, "cave 8 6")
|
|
|
|
new_zone = get_zone("cave")
|
|
assert new_zone is not None
|
|
assert new_zone.width == 8
|
|
assert new_zone.height == 6
|
|
assert player.location is new_zone
|
|
assert player.x == 0
|
|
assert player.y == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dig_creates_terrain(player):
|
|
"""@dig creates terrain filled with '.' tiles."""
|
|
from mudlib.commands.build import cmd_dig
|
|
|
|
await cmd_dig(player, "mine 5 4")
|
|
|
|
new_zone = get_zone("mine")
|
|
assert new_zone is not None
|
|
assert len(new_zone.terrain) == 4
|
|
assert len(new_zone.terrain[0]) == 5
|
|
assert all(tile == "." for row in new_zone.terrain for tile in row)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dig_zone_not_toroidal(player):
|
|
"""@dig creates bounded (non-toroidal) zones."""
|
|
from mudlib.commands.build import cmd_dig
|
|
|
|
await cmd_dig(player, "room 3 3")
|
|
|
|
new_zone = get_zone("room")
|
|
assert new_zone is not None
|
|
assert new_zone.toroidal is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dig_existing_name(player, mock_writer):
|
|
"""@dig with existing zone name shows error."""
|
|
from mudlib.commands.build import cmd_dig
|
|
|
|
await cmd_dig(player, "hub 5 5")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "already exists" in written.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dig_bad_args(player, mock_writer):
|
|
"""@dig with wrong number of args shows usage."""
|
|
from mudlib.commands.build import cmd_dig
|
|
|
|
await cmd_dig(player, "cave")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[0][0][0]
|
|
assert "usage" in written.lower() or "dig" in written.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dig_non_numeric_dimensions(player, mock_writer):
|
|
"""@dig with non-numeric dimensions shows error."""
|
|
from mudlib.commands.build import cmd_dig
|
|
|
|
await cmd_dig(player, "cave abc def")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[0][0][0]
|
|
assert "numbers" in written.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dig_zero_or_negative_dimensions(player, mock_writer):
|
|
"""@dig with zero or negative dimensions shows error."""
|
|
from mudlib.commands.build import cmd_dig
|
|
|
|
await cmd_dig(player, "cave 0 5")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[0][0][0]
|
|
assert "at least 1x1" in written.lower()
|
|
|
|
mock_writer.write.reset_mock()
|
|
await cmd_dig(player, "cave 5 -2")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[0][0][0]
|
|
assert "at least 1x1" in written.lower()
|
|
|
|
|
|
# --- @save ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_zone(player, zone, tmp_path, mock_writer):
|
|
"""@save writes zone to TOML file."""
|
|
from mudlib.commands.build import cmd_save, set_content_dir
|
|
|
|
set_content_dir(tmp_path)
|
|
zones_dir = tmp_path / "zones"
|
|
zones_dir.mkdir()
|
|
|
|
await cmd_save(player, "")
|
|
|
|
# Check file was created
|
|
saved_file = zones_dir / "hub.toml"
|
|
assert saved_file.exists()
|
|
content = saved_file.read_text()
|
|
assert 'name = "hub"' in content
|
|
|
|
# Check confirmation message
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "saved" in written.lower()
|
|
|
|
|
|
# --- @place ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_place_thing(player, zone, mock_writer):
|
|
"""@place puts a thing at player's position."""
|
|
from mudlib.commands.build import cmd_place
|
|
from mudlib.things import ThingTemplate, thing_templates
|
|
|
|
thing_templates["sign"] = ThingTemplate(
|
|
name="sign",
|
|
description="a wooden sign",
|
|
)
|
|
|
|
await cmd_place(player, "sign")
|
|
|
|
# Check thing is in zone at player's position
|
|
from mudlib.thing import Thing
|
|
|
|
things = [
|
|
obj
|
|
for obj in zone.contents_at(5, 5)
|
|
if isinstance(obj, Thing) and obj.name == "sign"
|
|
]
|
|
assert len(things) == 1
|
|
assert things[0].location is zone
|
|
|
|
# Clean up
|
|
del thing_templates["sign"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_place_unknown_thing(player, mock_writer):
|
|
"""@place with unknown thing name shows error."""
|
|
from mudlib.commands.build import cmd_place
|
|
|
|
await cmd_place(player, "unicorn")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[0][0][0]
|
|
assert "not found" in written.lower() or "unknown" in written.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_place_no_args(player, mock_writer):
|
|
"""@place without arguments shows usage."""
|
|
from mudlib.commands.build import cmd_place
|
|
|
|
await cmd_place(player, "")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[0][0][0]
|
|
assert "usage" in written.lower() or "place" in written.lower()
|
|
|
|
|
|
# --- Permission checks ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_builder_commands_require_admin(zone, mock_writer, mock_reader):
|
|
"""Non-admin players cannot use builder commands."""
|
|
from mudlib.commands import dispatch
|
|
|
|
non_admin = Player(
|
|
name="player",
|
|
x=5,
|
|
y=5,
|
|
writer=mock_writer,
|
|
reader=mock_reader,
|
|
location=zone,
|
|
is_admin=False,
|
|
)
|
|
|
|
await dispatch(non_admin, "@goto hub")
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "permission" in written.lower()
|
|
|
|
mock_writer.write.reset_mock()
|
|
await dispatch(non_admin, "@dig test 5 5")
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "permission" in written.lower()
|
|
|
|
mock_writer.write.reset_mock()
|
|
await dispatch(non_admin, "@save")
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "permission" in written.lower()
|
|
|
|
mock_writer.write.reset_mock()
|
|
await dispatch(non_admin, "@place thing")
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "permission" in written.lower()
|
|
|
|
|
|
# --- @zones ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_zones_lists_registered_zones(player):
|
|
"""@zones lists all registered zones."""
|
|
from mudlib.commands.build import cmd_zones
|
|
|
|
# Register additional zones
|
|
forest = Zone(
|
|
name="forest",
|
|
width=15,
|
|
height=12,
|
|
terrain=[["." for _ in range(15)] for _ in range(12)],
|
|
)
|
|
register_zone("forest", forest)
|
|
|
|
tavern = Zone(
|
|
name="tavern",
|
|
width=8,
|
|
height=6,
|
|
terrain=[["." for _ in range(8)] for _ in range(6)],
|
|
)
|
|
register_zone("tavern", tavern)
|
|
|
|
await cmd_zones(player, "")
|
|
|
|
# Check all zones are listed
|
|
all_output = "".join(call[0][0] for call in player.writer.write.call_args_list)
|
|
assert "hub" in all_output
|
|
assert "forest" in all_output
|
|
assert "tavern" in all_output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_zones_shows_dimensions(player):
|
|
"""@zones shows width x height for each zone."""
|
|
from mudlib.commands.build import cmd_zones
|
|
|
|
forest = Zone(
|
|
name="forest",
|
|
width=20,
|
|
height=15,
|
|
terrain=[["." for _ in range(20)] for _ in range(15)],
|
|
)
|
|
register_zone("forest", forest)
|
|
|
|
await cmd_zones(player, "")
|
|
|
|
all_output = "".join(call[0][0] for call in player.writer.write.call_args_list)
|
|
assert "10x10" in all_output # hub from fixture
|
|
assert "20x15" in all_output # forest
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_zones_highlights_current_zone(player):
|
|
"""@zones marks the player's current zone."""
|
|
from mudlib.commands.build import cmd_zones
|
|
|
|
forest = Zone(
|
|
name="forest",
|
|
width=15,
|
|
height=12,
|
|
terrain=[["." for _ in range(15)] for _ in range(12)],
|
|
)
|
|
register_zone("forest", forest)
|
|
|
|
await cmd_zones(player, "")
|
|
|
|
all_output = "".join(call[0][0] for call in player.writer.write.call_args_list)
|
|
# hub should be marked as current (player is in hub via fixture)
|
|
assert "[here]" in all_output
|
|
# [here] should be on the same line as hub
|
|
hub_line_idx = all_output.find("hub")
|
|
here_idx = all_output.find("[here]")
|
|
assert hub_line_idx < here_idx < hub_line_idx + 50
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_zones_empty_registry(zone, mock_writer, mock_reader):
|
|
"""@zones with no zones shows appropriate message."""
|
|
from mudlib.commands.build import cmd_zones
|
|
|
|
# Create player not in a zone fixture
|
|
zone_registry.clear()
|
|
temp_zone = Zone(
|
|
name="temp",
|
|
width=5,
|
|
height=5,
|
|
terrain=[["." for _ in range(5)] for _ in range(5)],
|
|
)
|
|
p = Player(
|
|
name="builder",
|
|
x=0,
|
|
y=0,
|
|
writer=mock_writer,
|
|
reader=mock_reader,
|
|
location=temp_zone,
|
|
is_admin=True,
|
|
)
|
|
|
|
await cmd_zones(p, "")
|
|
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[0][0][0]
|
|
assert "no zones" in written.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_zones_requires_admin(zone, mock_writer, mock_reader):
|
|
"""Non-admin players cannot use @zones."""
|
|
from mudlib.commands import dispatch
|
|
|
|
non_admin = Player(
|
|
name="player",
|
|
x=5,
|
|
y=5,
|
|
writer=mock_writer,
|
|
reader=mock_reader,
|
|
location=zone,
|
|
is_admin=False,
|
|
)
|
|
|
|
await dispatch(non_admin, "@zones")
|
|
mock_writer.write.assert_called()
|
|
written = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "permission" in written.lower()
|