305 lines
10 KiB
Python
305 lines
10 KiB
Python
|
|
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
|