mud/tests/test_housing.py

268 lines
7.1 KiB
Python

"""Tests for player housing system."""
import tomllib
import pytest
from mudlib.housing import (
HOME_HEIGHT,
HOME_SPAWN_X,
HOME_SPAWN_Y,
HOME_WIDTH,
_home_zone_name,
create_home_zone,
get_or_create_home,
init_housing,
load_home_zone,
save_home_zone,
)
from mudlib.zones import get_zone, zone_registry
@pytest.fixture(autouse=True)
def _clean_zone_registry():
"""Clear zone registry between tests."""
saved = dict(zone_registry)
zone_registry.clear()
yield
zone_registry.clear()
zone_registry.update(saved)
def test_init_housing_creates_directory(tmp_path):
"""init_housing() creates the zones directory."""
zones_dir = tmp_path / "zones"
assert not zones_dir.exists()
init_housing(zones_dir)
assert zones_dir.exists()
assert zones_dir.is_dir()
def test_home_zone_name():
"""_home_zone_name() returns correct format."""
assert _home_zone_name("Alice") == "home:alice"
assert _home_zone_name("bob") == "home:bob"
assert _home_zone_name("Charlie") == "home:charlie"
def test_create_home_zone(tmp_path):
"""create_home_zone() creates a zone with correct properties."""
init_housing(tmp_path)
zone = create_home_zone("Alice")
# Check basic properties
assert zone.name == "home:alice"
assert zone.description == "Alice's home"
assert zone.width == HOME_WIDTH
assert zone.height == HOME_HEIGHT
assert zone.toroidal is False
assert zone.spawn_x == HOME_SPAWN_X
assert zone.spawn_y == HOME_SPAWN_Y
assert zone.safe is True
assert zone.impassable == {"#", "^", "~"}
# Check terrain dimensions
assert len(zone.terrain) == HOME_HEIGHT
assert all(len(row) == HOME_WIDTH for row in zone.terrain)
# Check border is walls
for x in range(HOME_WIDTH):
assert zone.terrain[0][x] == "#" # top
assert zone.terrain[HOME_HEIGHT - 1][x] == "#" # bottom
for y in range(HOME_HEIGHT):
assert zone.terrain[y][0] == "#" # left
assert zone.terrain[y][HOME_WIDTH - 1] == "#" # right
# Check interior is grass
for y in range(1, HOME_HEIGHT - 1):
for x in range(1, HOME_WIDTH - 1):
assert zone.terrain[y][x] == "."
# Check spawn point is passable
assert zone.terrain[HOME_SPAWN_Y][HOME_SPAWN_X] == "."
def test_create_registers_zone(tmp_path):
"""create_home_zone() registers the zone."""
init_housing(tmp_path)
zone = create_home_zone("Bob")
registered = get_zone("home:bob")
assert registered is zone
def test_save_home_zone(tmp_path):
"""save_home_zone() writes a valid TOML file."""
init_housing(tmp_path)
create_home_zone("Charlie")
zone_file = tmp_path / "charlie.toml"
assert zone_file.exists()
# Verify TOML is valid and contains expected data
with open(zone_file, "rb") as f:
data = tomllib.load(f)
assert data["name"] == "home:charlie"
assert data["description"] == "Charlie's home"
assert data["width"] == HOME_WIDTH
assert data["height"] == HOME_HEIGHT
assert data["toroidal"] is False
assert data["spawn_x"] == HOME_SPAWN_X
assert data["spawn_y"] == HOME_SPAWN_Y
assert data["safe"] is True
# Check terrain
rows = data["terrain"]["rows"]
assert len(rows) == HOME_HEIGHT
assert all(len(row) == HOME_WIDTH for row in rows)
# Check impassable
assert set(data["terrain"]["impassable"]["tiles"]) == {"#", "^", "~"}
def test_load_home_zone(tmp_path):
"""load_home_zone() reads a zone from disk."""
init_housing(tmp_path)
# Create and save a zone
_ = create_home_zone("Dave")
# Clear registry
zone_registry.clear()
# Load it back
loaded = load_home_zone("Dave")
assert loaded is not None
assert loaded.name == "home:dave"
assert loaded.description == "Dave's home"
assert loaded.width == HOME_WIDTH
assert loaded.height == HOME_HEIGHT
assert loaded.toroidal is False
assert loaded.spawn_x == HOME_SPAWN_X
assert loaded.spawn_y == HOME_SPAWN_Y
assert loaded.safe is True
assert loaded.impassable == {"#", "^", "~"}
def test_load_registers_zone(tmp_path):
"""load_home_zone() registers the zone."""
init_housing(tmp_path)
create_home_zone("Eve")
zone_registry.clear()
loaded = load_home_zone("Eve")
registered = get_zone("home:eve")
assert registered is loaded
def test_load_nonexistent_returns_none(tmp_path):
"""load_home_zone() returns None if file doesn't exist."""
init_housing(tmp_path)
result = load_home_zone("Nobody")
assert result is None
def test_round_trip(tmp_path):
"""Create -> save -> load produces equivalent zone."""
init_housing(tmp_path)
original = create_home_zone("Frank")
zone_registry.clear()
loaded = load_home_zone("Frank")
assert loaded is not None
# Compare all fields
assert loaded.name == original.name
assert loaded.description == original.description
assert loaded.width == original.width
assert loaded.height == original.height
assert loaded.toroidal == original.toroidal
assert loaded.spawn_x == original.spawn_x
assert loaded.spawn_y == original.spawn_y
assert loaded.safe == original.safe
assert loaded.impassable == original.impassable
# Compare terrain
assert len(loaded.terrain) == len(original.terrain)
for loaded_row, orig_row in zip(loaded.terrain, original.terrain, strict=True):
assert loaded_row == orig_row
def test_get_or_create_home_creates_new(tmp_path):
"""get_or_create_home() creates a zone on first call."""
init_housing(tmp_path)
zone = get_or_create_home("Grace")
assert zone.name == "home:grace"
assert zone.description == "Grace's home"
def test_get_or_create_home_returns_existing_from_registry(tmp_path):
"""get_or_create_home() returns existing zone from registry."""
init_housing(tmp_path)
first = get_or_create_home("Hank")
second = get_or_create_home("Hank")
assert second is first
def test_get_or_create_home_loads_from_disk(tmp_path):
"""get_or_create_home() loads from disk if not in registry."""
init_housing(tmp_path)
create_home_zone("Iris")
zone_registry.clear()
loaded = get_or_create_home("Iris")
assert loaded.name == "home:iris"
def test_case_insensitive_zone_names(tmp_path):
"""Zone names are lowercased consistently."""
init_housing(tmp_path)
zone1 = create_home_zone("JACK")
zone2 = create_home_zone("Jack")
zone3 = create_home_zone("jack")
# All should reference the same zone name
assert zone1.name == "home:jack"
assert zone2.name == "home:jack"
assert zone3.name == "home:jack"
def test_save_preserves_modifications(tmp_path):
"""save_home_zone() preserves modifications to terrain."""
init_housing(tmp_path)
zone = create_home_zone("Kate")
# Modify terrain
zone.terrain[2][2] = "~" # add water
zone.terrain[3][3] = "^" # add mountain
# Save modifications
save_home_zone("Kate", zone)
# Load and verify
zone_registry.clear()
loaded = load_home_zone("Kate")
assert loaded is not None
assert loaded.terrain[2][2] == "~"
assert loaded.terrain[3][3] == "^"