- _find_story() now compares path.stem.lower() so "lostpig" matches "LostPig.z8" - Server no longer writes its own prompt in IF mode (game handles prompting) - Suppress phantom game output on restore (saved PC past sread causes garbage) - Route .z5/.z8 files to EmbeddedIFSession now that V5+ is supported
190 lines
5.8 KiB
Python
190 lines
5.8 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.EmbeddedIFSession") as MockSession:
|
|
MockSession.return_value = mock_session
|
|
|
|
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 .zblorb to test dfrotz path (z3/z5/z8 go to embedded)
|
|
with patch("mudlib.commands.play._find_story") as mock_find:
|
|
mock_find.return_value = "/fake/path/game.zblorb"
|
|
|
|
await cmd_play(player, "game")
|
|
|
|
# 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 (via start())."""
|
|
from mudlib.commands.play import cmd_play
|
|
|
|
# Mock IFSession - restore now happens in start() before thread launches
|
|
mock_session = Mock()
|
|
restored_output = (
|
|
"restoring saved game...\r\nrestored.\r\n\r\n"
|
|
"West of House\nYou are standing in an open field."
|
|
)
|
|
mock_session.start = AsyncMock(return_value=restored_output)
|
|
|
|
with patch("mudlib.commands.play.EmbeddedIFSession") as MockSession:
|
|
MockSession.return_value = mock_session
|
|
|
|
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 restored text was sent (start() returns full output with restore)
|
|
calls = [call[0][0] for call in player.writer.write.call_args_list]
|
|
full_output = "".join(calls)
|
|
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 mudlib.commands.play import cmd_play
|
|
|
|
# Mock EmbeddedIFSession
|
|
mock_session = Mock()
|
|
mock_session.start = AsyncMock(return_value="Welcome to Zork!")
|
|
|
|
with patch("mudlib.commands.play.EmbeddedIFSession") as MockSession:
|
|
MockSession.return_value = mock_session
|
|
|
|
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 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()
|