mud/tests/test_play_command.py
Jared Miller b6d933acc5
Add tests for embedded z-machine MUD integration
Unit tests for MUD UI components (screen, input stream, filesystem)
and integration tests with real zork1.z3 (session lifecycle, escape
commands, save/restore round-trip, state inspection).
2026-02-10 11:18:19 -05:00

207 lines
6.3 KiB
Python

"""Tests for the play command."""
from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
@pytest.fixture
def mock_writer():
writer = MagicMock()
writer.write = MagicMock()
writer.drain = AsyncMock()
return writer
@pytest.fixture
def player(mock_writer):
from mudlib.player import Player
return Player(name="tester", x=5, y=5, writer=mock_writer)
def test_play_command_registered():
"""Verify play command is registered."""
import mudlib.commands.play # noqa: F401
from mudlib import commands
assert "play" in commands._registry
cmd = commands._registry["play"]
assert cmd.name == "play"
assert cmd.mode == "normal"
@pytest.mark.asyncio
async def test_play_no_args(player):
"""Playing with no args sends usage message."""
from mudlib.commands.play import cmd_play
await cmd_play(player, "")
player.writer.write.assert_called()
output = player.writer.write.call_args[0][0]
assert "play what?" in output.lower()
@pytest.mark.asyncio
async def test_play_unknown_story(player):
"""Playing unknown story sends error message."""
from mudlib.commands.play import cmd_play
await cmd_play(player, "nosuchgame")
player.writer.write.assert_called()
output = player.writer.write.call_args[0][0]
assert "no story" in output.lower()
assert "nosuchgame" in output.lower()
@pytest.mark.asyncio
async def test_play_enters_if_mode(player):
"""Playing a valid story enters IF mode and creates session."""
from pathlib import Path
from mudlib.commands.play import cmd_play
# Mock IFSession
mock_session = Mock()
mock_session.start = AsyncMock(return_value="Welcome to Zork!")
mock_session.save_path = Mock(spec=Path)
mock_session.save_path.exists = Mock(return_value=False)
with patch("mudlib.commands.play.IFSession") as MockIFSession:
MockIFSession.return_value = mock_session
# Use .z5 to test dfrotz path
with patch("mudlib.commands.play._find_story") as mock_find:
mock_find.return_value = "/fake/path/zork1.z5"
await cmd_play(player, "zork1")
# Verify session was created and started
mock_session.start.assert_called_once()
# Verify mode was pushed
assert "if" in player.mode_stack
# Verify session was attached to player
assert player.if_session is mock_session
# Verify intro was sent
player.writer.write.assert_called()
output = player.writer.write.call_args[0][0]
assert "Welcome to Zork!" in output
@pytest.mark.asyncio
async def test_play_handles_dfrotz_missing(player):
"""Playing when dfrotz is missing sends error."""
from pathlib import Path
from mudlib.commands.play import cmd_play
# Mock IFSession to raise FileNotFoundError on start
mock_session = Mock()
mock_session.start = AsyncMock(side_effect=FileNotFoundError())
mock_session.stop = AsyncMock()
mock_session.save_path = Mock(spec=Path)
mock_session.save_path.exists = Mock(return_value=False)
with patch("mudlib.commands.play.IFSession") as MockIFSession:
MockIFSession.return_value = mock_session
# Use .z5 to test dfrotz path
with patch("mudlib.commands.play._find_story") as mock_find:
mock_find.return_value = "/fake/path/zork1.z5"
await cmd_play(player, "zork1")
# Verify error message was sent
player.writer.write.assert_called()
output = player.writer.write.call_args[0][0]
assert "dfrotz not found" in output.lower()
# Verify mode was NOT pushed
assert "if" not in player.mode_stack
# Verify session was NOT attached
assert player.if_session is None
# Verify session.stop() was called
mock_session.stop.assert_called_once()
@pytest.mark.asyncio
async def test_play_restores_save_if_exists(player):
"""Playing restores saved game if save file exists."""
from pathlib import Path
from mudlib.commands.play import cmd_play
# Mock IFSession
mock_session = Mock()
mock_session.start = AsyncMock(return_value="Welcome to Zork!")
mock_session._do_restore = AsyncMock(
return_value="West of House\nYou are standing in an open field."
)
mock_session.save_path = Mock(spec=Path)
mock_session.save_path.exists = Mock(return_value=True)
with patch("mudlib.commands.play.IFSession") as MockIFSession:
MockIFSession.return_value = mock_session
# Use .z5 to test dfrotz path
with patch("mudlib.commands.play._find_story") as mock_find:
mock_find.return_value = "/fake/path/zork1.z5"
await cmd_play(player, "zork1")
# Verify restore was called
mock_session._do_restore.assert_called_once()
# Verify session was created and started
mock_session.start.assert_called_once()
# Verify mode was pushed
assert "if" in player.mode_stack
# Verify restored text was sent
calls = [call[0][0] for call in player.writer.write.call_args_list]
full_output = "".join(calls)
assert "restoring" in full_output.lower()
assert "West of House" in full_output
assert "open field" in full_output
@pytest.mark.asyncio
async def test_play_no_restore_if_no_save(player):
"""Playing does not restore if no save file exists."""
from pathlib import Path
from mudlib.commands.play import cmd_play
# Mock IFSession
mock_session = Mock()
mock_session.start = AsyncMock(return_value="Welcome to Zork!")
mock_session._do_restore = AsyncMock(return_value="")
mock_session.save_path = Mock(spec=Path)
mock_session.save_path.exists = Mock(return_value=False)
with patch("mudlib.commands.play.IFSession") as MockIFSession:
MockIFSession.return_value = mock_session
# Use .z5 to test dfrotz path
with patch("mudlib.commands.play._find_story") as mock_find:
mock_find.return_value = "/fake/path/zork1.z5"
await cmd_play(player, "zork1")
# Verify restore was NOT called
mock_session._do_restore.assert_not_called()
# Verify session was created and started
mock_session.start.assert_called_once()
# Verify intro was sent but not restore message
calls = [call[0][0] for call in player.writer.write.call_args_list]
full_output = "".join(calls)
assert "Welcome to Zork!" in full_output
assert "restoring" not in full_output.lower()