Add is_past_trade_deadline property to Current model and guard /trade initiate, submit, and finalize flows. All checks fail-closed (block if API unreachable). 981 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
144 lines
5.6 KiB
Python
144 lines
5.6 KiB
Python
"""
|
|
Tests for trade deadline enforcement in /trade commands.
|
|
|
|
Validates that trades are blocked after the trade deadline and allowed during/before it.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from tests.factories import CurrentFactory, TeamFactory
|
|
|
|
|
|
class TestTradeInitiateDeadlineGuard:
|
|
"""Test trade deadline enforcement in /trade initiate command."""
|
|
|
|
@pytest.fixture
|
|
def mock_interaction(self):
|
|
"""Create mock Discord interaction with deferred response."""
|
|
interaction = AsyncMock()
|
|
interaction.user = MagicMock()
|
|
interaction.user.id = 258104532423147520
|
|
interaction.response = AsyncMock()
|
|
interaction.followup = AsyncMock()
|
|
interaction.guild = MagicMock()
|
|
interaction.guild.id = 669356687294988350
|
|
return interaction
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_trade_initiate_blocked_past_deadline(self, mock_interaction):
|
|
"""After the trade deadline, /trade initiate should return a friendly error."""
|
|
user_team = TeamFactory.west_virginia()
|
|
other_team = TeamFactory.new_york()
|
|
past_deadline = CurrentFactory.create(week=15, trade_deadline=14)
|
|
|
|
with (
|
|
patch(
|
|
"commands.transactions.trade.validate_user_has_team",
|
|
new_callable=AsyncMock,
|
|
return_value=user_team,
|
|
),
|
|
patch(
|
|
"commands.transactions.trade.get_team_by_abbrev_with_validation",
|
|
new_callable=AsyncMock,
|
|
return_value=other_team,
|
|
),
|
|
patch("commands.transactions.trade.league_service") as mock_league,
|
|
):
|
|
mock_league.get_current_state = AsyncMock(return_value=past_deadline)
|
|
|
|
from commands.transactions.trade import TradeCommands
|
|
|
|
bot = MagicMock()
|
|
cog = TradeCommands(bot)
|
|
await cog.trade_initiate.callback(cog, mock_interaction, "NY")
|
|
|
|
mock_interaction.followup.send.assert_called_once()
|
|
call_kwargs = mock_interaction.followup.send.call_args
|
|
msg = (
|
|
call_kwargs[0][0]
|
|
if call_kwargs[0]
|
|
else call_kwargs[1].get("content", "")
|
|
)
|
|
assert "trade deadline has passed" in msg.lower()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_trade_initiate_allowed_at_deadline_week(self, mock_interaction):
|
|
"""During the deadline week itself, /trade initiate should proceed."""
|
|
user_team = TeamFactory.west_virginia()
|
|
other_team = TeamFactory.new_york()
|
|
at_deadline = CurrentFactory.create(week=14, trade_deadline=14)
|
|
|
|
with (
|
|
patch(
|
|
"commands.transactions.trade.validate_user_has_team",
|
|
new_callable=AsyncMock,
|
|
return_value=user_team,
|
|
),
|
|
patch(
|
|
"commands.transactions.trade.get_team_by_abbrev_with_validation",
|
|
new_callable=AsyncMock,
|
|
return_value=other_team,
|
|
),
|
|
patch("commands.transactions.trade.league_service") as mock_league,
|
|
patch("commands.transactions.trade.clear_trade_builder") as mock_clear,
|
|
patch("commands.transactions.trade.get_trade_builder") as mock_get_builder,
|
|
patch(
|
|
"commands.transactions.trade.create_trade_embed",
|
|
new_callable=AsyncMock,
|
|
return_value=MagicMock(),
|
|
),
|
|
):
|
|
mock_league.get_current_state = AsyncMock(return_value=at_deadline)
|
|
mock_builder = MagicMock()
|
|
mock_builder.add_team = AsyncMock(return_value=(True, None))
|
|
mock_builder.trade_id = "test-123"
|
|
mock_get_builder.return_value = mock_builder
|
|
|
|
from commands.transactions.trade import TradeCommands
|
|
|
|
bot = MagicMock()
|
|
cog = TradeCommands(bot)
|
|
cog.channel_manager = MagicMock()
|
|
cog.channel_manager.create_trade_channel = AsyncMock(return_value=None)
|
|
await cog.trade_initiate.callback(cog, mock_interaction, "NY")
|
|
|
|
# Should have proceeded past deadline check to clear/create trade
|
|
mock_clear.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_trade_initiate_blocked_when_current_none(self, mock_interaction):
|
|
"""When league state can't be fetched, /trade initiate should fail closed."""
|
|
user_team = TeamFactory.west_virginia()
|
|
other_team = TeamFactory.new_york()
|
|
|
|
with (
|
|
patch(
|
|
"commands.transactions.trade.validate_user_has_team",
|
|
new_callable=AsyncMock,
|
|
return_value=user_team,
|
|
),
|
|
patch(
|
|
"commands.transactions.trade.get_team_by_abbrev_with_validation",
|
|
new_callable=AsyncMock,
|
|
return_value=other_team,
|
|
),
|
|
patch("commands.transactions.trade.league_service") as mock_league,
|
|
):
|
|
mock_league.get_current_state = AsyncMock(return_value=None)
|
|
|
|
from commands.transactions.trade import TradeCommands
|
|
|
|
bot = MagicMock()
|
|
cog = TradeCommands(bot)
|
|
await cog.trade_initiate.callback(cog, mock_interaction, "NY")
|
|
|
|
mock_interaction.followup.send.assert_called_once()
|
|
call_kwargs = mock_interaction.followup.send.call_args
|
|
msg = (
|
|
call_kwargs[0][0]
|
|
if call_kwargs[0]
|
|
else call_kwargs[1].get("content", "")
|
|
)
|
|
assert "could not retrieve league state" in msg.lower()
|