mud/tests/test_safe_zones.py
Jared Miller 9c480f8d47
Remove duplicate mock_writer/mock_reader fixtures
Removed identical local copies from 45 test files. These fixtures
are already defined in conftest.py.
2026-02-16 15:29:21 -05:00

223 lines
4.7 KiB
Python

"""Tests for safe zone combat prevention."""
import pathlib
import tempfile
import pytest
from mudlib.combat.engine import active_encounters, get_encounter
from mudlib.entity import Mob
from mudlib.player import Player, players
from mudlib.zone import Zone
from mudlib.zones import load_zone
@pytest.fixture(autouse=True)
def clear_state():
"""Clear global state between tests."""
players.clear()
active_encounters.clear()
yield
players.clear()
active_encounters.clear()
@pytest.fixture
def safe_zone():
terrain = [["." for _ in range(10)] for _ in range(10)]
return Zone(
name="sanctuary",
width=10,
height=10,
terrain=terrain,
safe=True,
)
@pytest.fixture
def unsafe_zone():
terrain = [["." for _ in range(10)] for _ in range(10)]
return Zone(
name="wilderness",
width=10,
height=10,
terrain=terrain,
safe=False,
)
# --- Zone property tests ---
def test_zone_safe_default_false():
"""Zones are unsafe by default."""
zone = Zone(name="test")
assert zone.safe is False
def test_zone_safe_true():
"""Zone can be created as safe."""
zone = Zone(name="test", safe=True)
assert zone.safe is True
# --- TOML loading ---
def test_load_zone_safe_flag():
"""Load a zone with safe = true from TOML."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
f.write(
"""
name = "sanctuary"
description = "a peaceful place"
width = 3
height = 3
safe = true
[terrain]
rows = [
"...",
"...",
"...",
]
"""
)
temp_path = pathlib.Path(f.name)
try:
zone = load_zone(temp_path)
assert zone.safe is True
finally:
temp_path.unlink()
def test_load_zone_safe_default():
"""Zone without safe field defaults to False."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
f.write(
"""
name = "wilderness"
description = "dangerous lands"
width = 3
height = 3
[terrain]
rows = [
"...",
"...",
"...",
]
"""
)
temp_path = pathlib.Path(f.name)
try:
zone = load_zone(temp_path)
assert zone.safe is False
finally:
temp_path.unlink()
def test_tavern_is_safe():
"""The tavern zone file has safe = true."""
project_root = pathlib.Path(__file__).resolve().parents[1]
tavern_path = project_root / "content" / "zones" / "tavern.toml"
zone = load_zone(tavern_path)
assert zone.safe is True
# --- Combat prevention ---
@pytest.mark.asyncio
async def test_attack_blocked_in_safe_zone(safe_zone, mock_writer, mock_reader):
"""Attacking in a safe zone sends error message."""
from mudlib.combat.commands import do_attack
from mudlib.combat.moves import CombatMove
player = Player(
name="attacker",
x=5,
y=5,
writer=mock_writer,
reader=mock_reader,
location=safe_zone,
)
safe_zone._contents.append(player)
players["attacker"] = player
target = Mob(
name="goblin",
x=5,
y=5,
location=safe_zone,
pl=50,
stamina=40,
max_stamina=40,
)
safe_zone._contents.append(target)
move = CombatMove(
name="punch left",
command="punch",
variant="left",
move_type="attack",
stamina_cost=5,
hit_time_ms=850,
damage_pct=0.15,
)
await do_attack(player, "goblin", move)
# Should have sent the safe zone message
mock_writer.write.assert_called()
written = mock_writer.write.call_args_list[0][0][0]
assert "can't fight here" in written.lower()
# Should NOT have started combat
assert get_encounter(player) is None
@pytest.mark.asyncio
async def test_attack_allowed_in_unsafe_zone(unsafe_zone, mock_writer, mock_reader):
"""Attacking in an unsafe zone works normally."""
from mudlib.combat.commands import do_attack
from mudlib.combat.moves import CombatMove
player = Player(
name="fighter",
x=5,
y=5,
writer=mock_writer,
reader=mock_reader,
location=unsafe_zone,
)
unsafe_zone._contents.append(player)
players["fighter"] = player
target = Mob(
name="goblin",
x=5,
y=5,
location=unsafe_zone,
pl=50,
stamina=40,
max_stamina=40,
)
unsafe_zone._contents.append(target)
move = CombatMove(
name="punch left",
command="punch",
variant="left",
move_type="attack",
stamina_cost=5,
hit_time_ms=850,
damage_pct=0.15,
)
await do_attack(player, "goblin", move)
# Combat SHOULD have started
assert get_encounter(player) is not None