mud/tests/test_stamina_cues.py

237 lines
6.4 KiB
Python

"""Tests for stamina cue broadcasts."""
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from mudlib.player import Player
from mudlib.zone import Zone
@pytest.fixture
def mock_writer():
writer = MagicMock()
writer.write = MagicMock()
writer.drain = AsyncMock()
return writer
@pytest.fixture
def test_zone():
return Zone(name="test", width=10, height=10)
@pytest.fixture
def player(mock_writer, test_zone):
p = Player(
name="Goku",
x=5,
y=5,
pl=100.0,
stamina=100.0,
max_stamina=100.0,
writer=mock_writer,
)
p.move_to(test_zone, x=5, y=5)
return p
@pytest.fixture
def nearby_player(test_zone):
"""A nearby player to receive broadcasts."""
w = MagicMock()
w.write = MagicMock()
w.drain = AsyncMock()
p = Player(
name="Vegeta",
x=5,
y=5,
pl=100.0,
stamina=100.0,
max_stamina=100.0,
writer=w,
)
p.move_to(test_zone, x=5, y=5)
return p
@pytest.mark.asyncio
async def test_no_cue_above_75_percent(player):
"""No cue when stamina is above 75%."""
from mudlib.combat.stamina import check_stamina_cues
player.stamina = 80.0
player.max_stamina = 100.0
await check_stamina_cues(player)
# No message sent to player
player.writer.write.assert_not_called()
@pytest.mark.asyncio
async def test_breathing_heavily_below_75_percent(player):
"""'Breathing heavily' cue when stamina drops below 75%."""
from mudlib.combat.stamina import check_stamina_cues
player.stamina = 70.0
player.max_stamina = 100.0
await check_stamina_cues(player)
# Check self-directed message
calls = [call[0][0] for call in player.writer.write.call_args_list]
assert any("You're breathing heavily." in msg for msg in calls)
@pytest.mark.asyncio
async def test_drenched_in_sweat_below_50_percent(player):
"""'Drenched in sweat' cue when below 50%."""
from mudlib.combat.stamina import check_stamina_cues
player.stamina = 45.0
player.max_stamina = 100.0
await check_stamina_cues(player)
calls = [call[0][0] for call in player.writer.write.call_args_list]
assert any("You're drenched in sweat." in msg for msg in calls)
@pytest.mark.asyncio
async def test_visibly_shaking_below_25_percent(player):
"""'Visibly shaking' cue when below 25%."""
from mudlib.combat.stamina import check_stamina_cues
player.stamina = 20.0
player.max_stamina = 100.0
await check_stamina_cues(player)
calls = [call[0][0] for call in player.writer.write.call_args_list]
assert any("You're visibly shaking from exhaustion." in msg for msg in calls)
@pytest.mark.asyncio
async def test_can_barely_stand_below_10_percent(player):
"""'Can barely stand' cue when below 10%."""
from mudlib.combat.stamina import check_stamina_cues
player.stamina = 8.0
player.max_stamina = 100.0
await check_stamina_cues(player)
calls = [call[0][0] for call in player.writer.write.call_args_list]
assert any("You can barely stand." in msg for msg in calls)
@pytest.mark.asyncio
async def test_same_threshold_not_triggered_twice(player):
"""Same threshold doesn't trigger twice (spam prevention)."""
from mudlib.combat.stamina import check_stamina_cues
# First trigger at 70%
player.stamina = 70.0
player.max_stamina = 100.0
await check_stamina_cues(player)
first_call_count = player.writer.write.call_count
# Second trigger at same threshold (still 70%)
player.stamina = 70.0
await check_stamina_cues(player)
second_call_count = player.writer.write.call_count
# No new messages should have been sent
assert second_call_count == first_call_count
@pytest.mark.asyncio
async def test_higher_threshold_doesnt_retrigger(player):
"""Higher threshold doesn't trigger if already at lower."""
from mudlib.combat.stamina import check_stamina_cues
# Drop to 20% first
player.stamina = 20.0
player.max_stamina = 100.0
await check_stamina_cues(player)
player.writer.write.reset_mock()
# Recover to 30% (back into 25-50% range)
player.stamina = 30.0
await check_stamina_cues(player)
# No new cue should trigger
player.writer.write.assert_not_called()
@pytest.mark.asyncio
async def test_self_directed_message_sent(player):
"""Self-directed message sent to the entity."""
from mudlib.combat.stamina import check_stamina_cues
player.stamina = 70.0
player.max_stamina = 100.0
await check_stamina_cues(player)
# Verify self message was sent
calls = [call[0][0] for call in player.writer.write.call_args_list]
assert any("You're breathing heavily." in msg for msg in calls)
@pytest.mark.asyncio
async def test_nearby_broadcast_sent(player, nearby_player):
"""Nearby broadcast sent to other players."""
from mudlib.combat.stamina import check_stamina_cues
player.stamina = 70.0
player.max_stamina = 100.0
with patch("mudlib.combat.stamina.send_nearby_message") as mock_nearby:
await check_stamina_cues(player)
# Verify send_nearby_message was called
mock_nearby.assert_called_once()
args = mock_nearby.call_args[0]
assert args[0] is player
assert args[1] == 5 # x coordinate
assert args[2] == 5 # y coordinate
assert "Goku is breathing heavily." in args[3]
@pytest.mark.asyncio
async def test_descending_thresholds_trigger_each_once(player):
"""Descending through thresholds triggers each once."""
from mudlib.combat.stamina import check_stamina_cues
player.max_stamina = 100.0
# Start at 80%, no cue
player.stamina = 80.0
await check_stamina_cues(player)
count_80 = player.writer.write.call_count
# Drop to 70%, should trigger 75% threshold
player.stamina = 70.0
await check_stamina_cues(player)
count_70 = player.writer.write.call_count
assert count_70 > count_80
# Drop to 40%, should trigger 50% threshold
player.stamina = 40.0
await check_stamina_cues(player)
count_40 = player.writer.write.call_count
assert count_40 > count_70
# Drop to 20%, should trigger 25% threshold
player.stamina = 20.0
await check_stamina_cues(player)
count_20 = player.writer.write.call_count
assert count_20 > count_40
# Drop to 5%, should trigger 10% threshold
player.stamina = 5.0
await check_stamina_cues(player)
count_5 = player.writer.write.call_count
assert count_5 > count_20