from __future__ import annotations from unittest.mock import AsyncMock, MagicMock import discord import pytest from tickets import storage from tickets.cog import ConfigToggleButton from tickets.data import GuildTicketConfig, TicketCategory, TicketData, TicketStatus from tickets.ui import ( AddMemberButton, ClaimButton, CloseButton, LockButton, PanelCreateButton, TransferButton, _is_staff, _now_iso, _send_log, ) STAFF_ROLE_ID = 999999 @pytest.fixture(autouse=True) def clean_storage(): storage._config_cache.clear() storage._tickets_cache.clear() config = GuildTicketConfig(guild_id=999) config.staff_role_id = STAFF_ROLE_ID storage._config_cache[999] = config yield storage._config_cache.clear() storage._tickets_cache.clear() def _make_member(is_admin=False, has_staff_role=True): member = MagicMock(spec=discord.Member) member.guild_permissions.administrator = is_admin if has_staff_role: member.roles = [MagicMock(id=STAFF_ROLE_ID)] else: member.roles = [] return member def _mock_interaction(guild_id=999, user_id=1, channel_id=123, member=None): if member is None: member = _make_member() interaction = MagicMock(spec=discord.Interaction) interaction.guild_id = guild_id interaction.channel_id = channel_id interaction.channel = MagicMock() interaction.channel.id = channel_id interaction.user = MagicMock() interaction.user.id = user_id interaction.user.mention = f"<@{user_id}>" interaction.response = AsyncMock() interaction.followup = AsyncMock() interaction.guild = MagicMock(spec=discord.Guild) interaction.guild.id = guild_id interaction.guild.get_member.return_value = member interaction.channel.overwrites_for = MagicMock() interaction.channel.set_permissions = AsyncMock() interaction.channel.delete = AsyncMock() interaction.channel.send = AsyncMock() return interaction def _save_ticket(channel_id=123, **kwargs): defaults = dict(channel_id=channel_id, guild_id=999, user_id=1, category_id="cat") defaults.update(kwargs) ticket = TicketData(**defaults) storage.save_ticket(ticket) return ticket class TestIsStaff: def test_admin(self): member = _make_member(is_admin=True) config = storage.get_config(999) assert _is_staff(member, config) is True def test_staff_role_match(self): member = _make_member(has_staff_role=True) config = storage.get_config(999) assert _is_staff(member, config) is True def test_not_staff(self): member = _make_member(has_staff_role=False) config = storage.get_config(999) assert _is_staff(member, config) is False def test_category_staff_role(self): member = _make_member(has_staff_role=False) member.roles = [MagicMock(id=77777)] config = storage.get_config(999) config.categories = [TicketCategory(id="c", name="C", staff_role_id=77777)] assert _is_staff(member, config) is True class TestNowIso: def test_returns_iso_string(self): result = _now_iso() assert "T" in result class TestSendLog: @pytest.mark.asyncio async def test_no_log_channel(self): config = storage.get_config(999) guild = MagicMock() guild.get_channel.return_value = None await _send_log(guild, config, MagicMock()) guild.get_channel.assert_not_called() @pytest.mark.asyncio async def test_sends_to_log_channel(self): config = storage.get_config(999) config.log_channel_id = 555 channel = AsyncMock() guild = MagicMock() guild.get_channel.return_value = channel embed = MagicMock() await _send_log(guild, config, embed) channel.send.assert_called_once_with(embed=embed) class TestPanelCreateButton: @pytest.mark.asyncio async def test_disabled_system(self): config = storage.get_config(999) config.enabled = False interaction = _mock_interaction() await PanelCreateButton().callback(interaction) interaction.response.send_message.assert_called_once() @pytest.mark.asyncio async def test_no_categories(self): config = storage.get_config(999) config.enabled = True config.categories = [] interaction = _mock_interaction() await PanelCreateButton().callback(interaction) interaction.response.send_message.assert_called_once() @pytest.mark.asyncio async def test_single_category_opens_modal(self): config = storage.get_config(999) config.enabled = True config.categories = [TicketCategory(id="c1", name="Cat1")] interaction = _mock_interaction() await PanelCreateButton().callback(interaction) interaction.response.send_modal.assert_called_once() @pytest.mark.asyncio async def test_multiple_categories_sends_select(self): config = storage.get_config(999) config.enabled = True config.categories = [ TicketCategory(id="c1", name="Cat1"), TicketCategory(id="c2", name="Cat2"), ] interaction = _mock_interaction() await PanelCreateButton().callback(interaction) call_kwargs = interaction.response.send_message.call_args.kwargs assert "view" in call_kwargs class TestClaimButton: @pytest.mark.asyncio async def test_not_a_ticket(self): interaction = _mock_interaction() await ClaimButton().callback(interaction) assert "pas un ticket" in interaction.response.send_message.call_args[0][0] @pytest.mark.asyncio async def test_not_staff(self): _save_ticket() member = _make_member(is_admin=False, has_staff_role=False) interaction = _mock_interaction(member=member) await ClaimButton().callback(interaction) assert "staff" in interaction.response.send_message.call_args[0][0].lower() @pytest.mark.asyncio async def test_claim_success(self): _save_ticket() interaction = _mock_interaction() await ClaimButton().callback(interaction) assert storage.get_ticket(123).claimed_by == 1 @pytest.mark.asyncio async def test_unclaim_own(self): _save_ticket(claimed_by=1) interaction = _mock_interaction() await ClaimButton().callback(interaction) assert storage.get_ticket(123).claimed_by is None @pytest.mark.asyncio async def test_already_claimed_by_other(self): _save_ticket(claimed_by=42) interaction = _mock_interaction() await ClaimButton().callback(interaction) assert storage.get_ticket(123).claimed_by == 42 class TestLockButton: @pytest.mark.asyncio async def test_not_a_ticket(self): interaction = _mock_interaction(channel_id=99999) await LockButton().callback(interaction) assert "pas un ticket" in interaction.response.send_message.call_args[0][0] @pytest.mark.asyncio async def test_lock_toggle(self): _save_ticket(locked=False) interaction = _mock_interaction() interaction.channel.overwrites_for.return_value = MagicMock() await LockButton().callback(interaction) assert storage.get_ticket(123).locked is True interaction.channel.set_permissions.assert_called_once() @pytest.mark.asyncio async def test_unlock_toggle(self): _save_ticket(locked=True) interaction = _mock_interaction() interaction.channel.overwrites_for.return_value = MagicMock() await LockButton().callback(interaction) assert storage.get_ticket(123).locked is False class TestCloseButton: @pytest.mark.asyncio async def test_not_a_ticket(self): interaction = _mock_interaction(channel_id=99999) await CloseButton().callback(interaction) assert "pas un ticket" in interaction.response.send_message.call_args[0][0] @pytest.mark.asyncio async def test_not_staff(self): _save_ticket() member = _make_member(is_admin=False, has_staff_role=False) interaction = _mock_interaction(member=member) await CloseButton().callback(interaction) assert "staff" in interaction.response.send_message.call_args[0][0].lower() @pytest.mark.asyncio async def test_close_success(self): _save_ticket() interaction = _mock_interaction() await CloseButton().callback(interaction) loaded = storage.get_ticket(123) assert loaded.status == TicketStatus.CLOSED assert loaded.closed_by == 1 @pytest.mark.asyncio async def test_close_without_transcript(self): config = storage.get_config(999) config.transcript_enabled = False _save_ticket() interaction = _mock_interaction() await CloseButton().callback(interaction) assert storage.get_ticket(123).status == TicketStatus.CLOSED class TestTransferButton: @pytest.mark.asyncio async def test_not_a_ticket(self): interaction = _mock_interaction(channel_id=99999) await TransferButton().callback(interaction) assert "pas un ticket" in interaction.response.send_message.call_args[0][0] @pytest.mark.asyncio async def test_sends_user_select(self): _save_ticket() interaction = _mock_interaction() await TransferButton().callback(interaction) assert "view" in interaction.response.send_message.call_args.kwargs class TestAddMemberButton: @pytest.mark.asyncio async def test_not_a_ticket(self): interaction = _mock_interaction(channel_id=99999) await AddMemberButton().callback(interaction) assert "pas un ticket" in interaction.response.send_message.call_args[0][0] @pytest.mark.asyncio async def test_sends_user_select(self): _save_ticket() interaction = _mock_interaction() await AddMemberButton().callback(interaction) assert "view" in interaction.response.send_message.call_args.kwargs class TestConfigToggleButton: @pytest.mark.asyncio async def test_toggle(self): config = storage.get_config(999) initial = config.enabled interaction = _mock_interaction() await ConfigToggleButton(enabled=initial).callback(interaction) assert config.enabled != initial