Prevents users from managing injuries for players not on their team. Admins bypass the check; org affiliates (MiL/IL) are recognized. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
170 lines
6.2 KiB
Python
170 lines
6.2 KiB
Python
"""Tests for injury command team ownership verification (issue #18).
|
|
|
|
Ensures /injury set-new and /injury clear only allow users to manage
|
|
injuries for players on their own team (or organizational affiliates).
|
|
Admins bypass the check.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from commands.injuries.management import InjuryGroup
|
|
from models.player import Player
|
|
from models.team import Team
|
|
|
|
|
|
def _make_team(team_id: int, abbrev: str, sname: str | None = None) -> Team:
|
|
"""Create a Team via model_construct to skip validation.
|
|
|
|
For MiL teams (e.g. PORMIL), pass sname explicitly to avoid the IL
|
|
disambiguation logic in _get_base_abbrev treating them as IL teams.
|
|
"""
|
|
return Team.model_construct(
|
|
id=team_id,
|
|
abbrev=abbrev,
|
|
sname=sname or abbrev,
|
|
lname=f"Team {abbrev}",
|
|
season=13,
|
|
)
|
|
|
|
|
|
def _make_player(player_id: int, name: str, team: Team) -> Player:
|
|
"""Create a Player via model_construct to skip validation."""
|
|
return Player.model_construct(
|
|
id=player_id,
|
|
name=name,
|
|
wara=2.0,
|
|
season=13,
|
|
team_id=team.id,
|
|
team=team,
|
|
)
|
|
|
|
|
|
def _make_interaction(is_admin: bool = False) -> MagicMock:
|
|
"""Create a mock Discord interaction with configurable admin status."""
|
|
interaction = MagicMock()
|
|
interaction.user = MagicMock()
|
|
interaction.user.id = 12345
|
|
interaction.user.guild_permissions = MagicMock()
|
|
interaction.user.guild_permissions.administrator = is_admin
|
|
|
|
# Make isinstance(interaction.user, discord.Member) return True
|
|
import discord
|
|
|
|
interaction.user.__class__ = discord.Member
|
|
|
|
interaction.followup = MagicMock()
|
|
interaction.followup.send = AsyncMock()
|
|
return interaction
|
|
|
|
|
|
@pytest.fixture
|
|
def injury_group():
|
|
return InjuryGroup()
|
|
|
|
|
|
class TestVerifyTeamOwnership:
|
|
"""Tests for InjuryGroup._verify_team_ownership (issue #18)."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_admin_bypasses_check(self, injury_group):
|
|
"""Admins should always pass the ownership check."""
|
|
interaction = _make_interaction(is_admin=True)
|
|
por_team = _make_team(1, "POR")
|
|
player = _make_player(100, "Mike Trout", por_team)
|
|
|
|
result = await injury_group._verify_team_ownership(interaction, player)
|
|
assert result is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_owner_passes_check(self, injury_group):
|
|
"""User who owns the player's team should pass."""
|
|
interaction = _make_interaction(is_admin=False)
|
|
por_team = _make_team(1, "POR")
|
|
player = _make_player(100, "Mike Trout", por_team)
|
|
|
|
with patch("services.team_service.team_service") as mock_ts, patch(
|
|
"commands.injuries.management.get_config"
|
|
) as mock_config:
|
|
mock_config.return_value.sba_season = 13
|
|
mock_ts.get_team_by_owner = AsyncMock(return_value=por_team)
|
|
result = await injury_group._verify_team_ownership(interaction, player)
|
|
|
|
assert result is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_org_affiliate_passes_check(self, injury_group):
|
|
"""User who owns the ML team should pass for MiL/IL affiliate players."""
|
|
interaction = _make_interaction(is_admin=False)
|
|
por_ml = _make_team(1, "POR")
|
|
por_mil = _make_team(2, "PORMIL", sname="POR MiL")
|
|
player = _make_player(100, "Minor Leaguer", por_mil)
|
|
|
|
with patch("services.team_service.team_service") as mock_ts, patch(
|
|
"commands.injuries.management.get_config"
|
|
) as mock_config:
|
|
mock_config.return_value.sba_season = 13
|
|
mock_ts.get_team_by_owner = AsyncMock(return_value=por_ml)
|
|
mock_ts.get_team = AsyncMock(return_value=por_mil)
|
|
result = await injury_group._verify_team_ownership(interaction, player)
|
|
|
|
assert result is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_different_team_fails(self, injury_group):
|
|
"""User who owns a different team should be denied."""
|
|
interaction = _make_interaction(is_admin=False)
|
|
por_team = _make_team(1, "POR")
|
|
nyy_team = _make_team(2, "NYY")
|
|
player = _make_player(100, "Mike Trout", nyy_team)
|
|
|
|
with patch("services.team_service.team_service") as mock_ts, patch(
|
|
"commands.injuries.management.get_config"
|
|
) as mock_config:
|
|
mock_config.return_value.sba_season = 13
|
|
mock_ts.get_team_by_owner = AsyncMock(return_value=por_team)
|
|
mock_ts.get_team = AsyncMock(return_value=nyy_team)
|
|
result = await injury_group._verify_team_ownership(interaction, player)
|
|
|
|
assert result is False
|
|
interaction.followup.send.assert_called_once()
|
|
call_kwargs = interaction.followup.send.call_args
|
|
embed = call_kwargs.kwargs.get("embed") or call_kwargs.args[0]
|
|
assert "Not Your Player" in embed.title
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_no_team_owned_fails(self, injury_group):
|
|
"""User who owns no team should be denied."""
|
|
interaction = _make_interaction(is_admin=False)
|
|
nyy_team = _make_team(2, "NYY")
|
|
player = _make_player(100, "Mike Trout", nyy_team)
|
|
|
|
with patch("services.team_service.team_service") as mock_ts, patch(
|
|
"commands.injuries.management.get_config"
|
|
) as mock_config:
|
|
mock_config.return_value.sba_season = 13
|
|
mock_ts.get_team_by_owner = AsyncMock(return_value=None)
|
|
result = await injury_group._verify_team_ownership(interaction, player)
|
|
|
|
assert result is False
|
|
interaction.followup.send.assert_called_once()
|
|
call_kwargs = interaction.followup.send.call_args
|
|
embed = call_kwargs.kwargs.get("embed") or call_kwargs.args[0]
|
|
assert "No Team Found" in embed.title
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_player_without_team_id_passes(self, injury_group):
|
|
"""Players with no team_id should pass (can't verify, allow through)."""
|
|
interaction = _make_interaction(is_admin=False)
|
|
player = Player.model_construct(
|
|
id=100,
|
|
name="Free Agent",
|
|
wara=0.0,
|
|
season=13,
|
|
team_id=None,
|
|
team=None,
|
|
)
|
|
|
|
result = await injury_group._verify_team_ownership(interaction, player)
|
|
assert result is True
|