mud/tests/test_if_spectator.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

199 lines
6.4 KiB
Python

"""Tests for spectator broadcasting in IF mode."""
from unittest.mock import AsyncMock, MagicMock
import pytest
from mudlib.if_session import IFResponse
from mudlib.player import Player, players
from mudlib.zone import Zone
@pytest.fixture(autouse=True)
def clear_players():
"""Clear players registry before and after each test."""
players.clear()
yield
players.clear()
@pytest.fixture
def test_zone():
"""Create a test zone for spatial queries."""
# Create a small test zone with passable terrain
terrain = [["."] * 20 for _ in range(20)]
return Zone(name="test", width=20, height=20, terrain=terrain, toroidal=False)
@pytest.fixture
def player_a(test_zone):
"""Player A at (5, 5) who will be playing IF."""
writer = MagicMock()
writer.write = MagicMock()
writer.drain = AsyncMock()
reader = MagicMock()
return Player(
name="PlayerA", location=test_zone, x=5, y=5, reader=reader, writer=writer
)
@pytest.fixture
def player_b(test_zone):
"""Player B at (5, 5) who will be spectating."""
writer = MagicMock()
writer.write = MagicMock()
writer.drain = AsyncMock()
reader = MagicMock()
return Player(
name="PlayerB", location=test_zone, x=5, y=5, reader=reader, writer=writer
)
@pytest.fixture
def player_c(test_zone):
"""Player C at different coords (10, 10)."""
writer = MagicMock()
writer.write = MagicMock()
writer.drain = AsyncMock()
reader = MagicMock()
return Player(
name="PlayerC", location=test_zone, x=10, y=10, reader=reader, writer=writer
)
@pytest.mark.asyncio
async def test_spectator_sees_if_output(player_a, player_b):
"""Spectator at same location sees IF output with header and input."""
# Register both players
players[player_a.name] = player_a
players[player_b.name] = player_b
# Create mock IF session for player A
mock_session = MagicMock()
mock_session.handle_input = AsyncMock(
return_value=IFResponse(
output="Opening the small mailbox reveals a leaflet.", done=False
)
)
player_a.if_session = mock_session
player_a.mode_stack.append("if")
# Import the broadcast function (will be created in implementation)
from mudlib.if_session import broadcast_to_spectators
# Player A sends input
command = "open mailbox"
response = await player_a.if_session.handle_input(command)
await player_a.send(response.output)
# Broadcast to spectators
spectator_msg = f"[{player_a.name}'s terminal]\r\n> {command}\r\n{response.output}"
await broadcast_to_spectators(player_a, spectator_msg)
# Player B should have received the message
assert player_b.writer.write.called
calls = player_b.writer.write.call_args_list
sent_text = "".join(call[0][0] for call in calls)
assert "[PlayerA's terminal]" in sent_text
assert "> open mailbox" in sent_text
assert "Opening the small mailbox reveals a leaflet." in sent_text
@pytest.mark.asyncio
async def test_spectator_not_on_same_tile_sees_nothing(player_a, player_c):
"""Spectator at different location does not see IF output."""
# Register both players (player_c is at different coords)
players[player_a.name] = player_a
players[player_c.name] = player_c
# Create mock IF session for player A
mock_session = MagicMock()
mock_session.handle_input = AsyncMock(
return_value=IFResponse(output="You open the door.", done=False)
)
player_a.if_session = mock_session
player_a.mode_stack.append("if")
from mudlib.if_session import broadcast_to_spectators
# Player A sends input
command = "open door"
response = await player_a.if_session.handle_input(command)
await player_a.send(response.output)
# Broadcast to spectators
spectator_msg = f"[{player_a.name}'s terminal]\r\n> {command}\r\n{response.output}"
await broadcast_to_spectators(player_a, spectator_msg)
# Player C should NOT have received anything
assert not player_c.writer.write.called
@pytest.mark.asyncio
async def test_spectator_sees_game_start(player_a, player_b):
"""Spectator sees formatted intro text when player starts game."""
# Register both players
players[player_a.name] = player_a
players[player_b.name] = player_b
from mudlib.if_session import broadcast_to_spectators
# Simulate game start with intro text
intro = "ZORK I: The Great Underground Empire\nCopyright (c) 1981"
spectator_msg = f"[{player_a.name}'s terminal]\r\n{intro}\r\n"
await broadcast_to_spectators(player_a, spectator_msg)
# Player B should see the intro
assert player_b.writer.write.called
calls = player_b.writer.write.call_args_list
sent_text = "".join(call[0][0] for call in calls)
assert "[PlayerA's terminal]" in sent_text
assert "ZORK I: The Great Underground Empire" in sent_text
@pytest.mark.asyncio
async def test_broadcast_to_spectators_skips_self(player_a, player_b):
"""broadcast_to_spectators does not send to the playing player."""
# Register both players
players[player_a.name] = player_a
players[player_b.name] = player_b
from mudlib.if_session import broadcast_to_spectators
message = "[PlayerA's terminal]\r\n> look\r\nYou see a room.\r\n"
await broadcast_to_spectators(player_a, message)
# Player A should NOT have received the message
assert not player_a.writer.write.called
# Player B should have received it
assert player_b.writer.write.called
@pytest.mark.asyncio
async def test_multiple_spectators(player_a, player_b, test_zone):
"""Multiple spectators at same location all see IF output."""
# Create a third player at same location
writer_d = MagicMock()
writer_d.write = MagicMock()
writer_d.drain = AsyncMock()
reader_d = MagicMock()
player_d = Player(
name="PlayerD", location=test_zone, x=5, y=5, reader=reader_d, writer=writer_d
)
# Register all players
players[player_a.name] = player_a
players[player_b.name] = player_b
players[player_d.name] = player_d
from mudlib.if_session import broadcast_to_spectators
message = "[PlayerA's terminal]\r\n> inventory\r\nYou are empty-handed.\r\n"
await broadcast_to_spectators(player_a, message)
# Both spectators should see the message
assert player_b.writer.write.called
assert player_d.writer.write.called
# Player A should not
assert not player_a.writer.write.called