test: 81 unit tests for tickets (data, storage, transcript, ui)
All checks were successful
ci / ci (push) Successful in 1m3s
All checks were successful
ci / ci (push) Successful in 1m3s
- tests/test_data.py: models serialization, enums, roundtrip - tests/test_storage.py: CRUD JSON, cache, persistence - tests/test_transcript.py: HTML generation, escaping, attachments - tests/test_ui.py: button callbacks with mocked interactions - Fixed: transcript.py attachments access (dataclass not dict)
This commit is contained in:
parent
628e02aea9
commit
e771073909
5 changed files with 812 additions and 1 deletions
244
tests/test_data.py
Normal file
244
tests/test_data.py
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from tickets.data import (
|
||||
GuildTicketConfig,
|
||||
TicketCategory,
|
||||
TicketData,
|
||||
TicketMessage,
|
||||
TicketPriority,
|
||||
TicketStatus,
|
||||
)
|
||||
|
||||
|
||||
class TestTicketStatus:
|
||||
def test_open_value(self):
|
||||
assert TicketStatus.OPEN.value == "open"
|
||||
|
||||
def test_closed_value(self):
|
||||
assert TicketStatus.CLOSED.value == "closed"
|
||||
|
||||
def test_enum_inheritance(self):
|
||||
assert issubclass(TicketStatus, str)
|
||||
|
||||
|
||||
class TestTicketPriority:
|
||||
def test_all_values(self):
|
||||
assert TicketPriority.LOW.value == "low"
|
||||
assert TicketPriority.NORMAL.value == "normal"
|
||||
assert TicketPriority.HIGH.value == "high"
|
||||
assert TicketPriority.URGENT.value == "urgent"
|
||||
|
||||
|
||||
class TestTicketCategory:
|
||||
def test_create_default(self):
|
||||
cat = TicketCategory(id="test", name="Test")
|
||||
assert cat.id == "test"
|
||||
assert cat.name == "Test"
|
||||
assert cat.emoji == ""
|
||||
assert cat.description == ""
|
||||
assert cat.staff_role_id is None
|
||||
assert cat.discord_category_id is None
|
||||
|
||||
def test_to_dict(self):
|
||||
cat = TicketCategory(
|
||||
id="support",
|
||||
name="Support",
|
||||
emoji="🎫",
|
||||
description="Aide",
|
||||
staff_role_id=123,
|
||||
discord_category_id=456,
|
||||
)
|
||||
d = cat.to_dict()
|
||||
assert d["id"] == "support"
|
||||
assert d["name"] == "Support"
|
||||
assert d["emoji"] == "🎫"
|
||||
assert d["description"] == "Aide"
|
||||
assert d["staff_role_id"] == 123
|
||||
assert d["discord_category_id"] == 456
|
||||
|
||||
def test_from_dict(self):
|
||||
d = {
|
||||
"id": "support",
|
||||
"name": "Support",
|
||||
"emoji": "🎫",
|
||||
"description": "Aide",
|
||||
"staff_role_id": 123,
|
||||
"discord_category_id": 456,
|
||||
}
|
||||
cat = TicketCategory.from_dict(d)
|
||||
assert cat.id == "support"
|
||||
assert cat.name == "Support"
|
||||
assert cat.staff_role_id == 123
|
||||
|
||||
def test_from_dict_ignores_extra_keys(self):
|
||||
d = {"id": "x", "name": "X", "unknown_key": "should be ignored"}
|
||||
cat = TicketCategory.from_dict(d)
|
||||
assert cat.id == "x"
|
||||
|
||||
def test_roundtrip(self):
|
||||
cat = TicketCategory(id="a", name="A", emoji="🔧", description="desc")
|
||||
cat2 = TicketCategory.from_dict(cat.to_dict())
|
||||
assert cat2 == cat
|
||||
|
||||
|
||||
class TestTicketMessage:
|
||||
def test_create_minimal(self):
|
||||
msg = TicketMessage(author_id=1, author_name="user", content="hello", timestamp="2026-01-01")
|
||||
assert msg.attachments == []
|
||||
assert msg.is_staff is False
|
||||
|
||||
def test_create_full(self):
|
||||
msg = TicketMessage(
|
||||
author_id=1,
|
||||
author_name="user",
|
||||
content="hello",
|
||||
timestamp="2026-01-01",
|
||||
attachments=["a.png"],
|
||||
is_staff=True,
|
||||
)
|
||||
assert msg.attachments == ["a.png"]
|
||||
assert msg.is_staff is True
|
||||
|
||||
|
||||
class TestTicketData:
|
||||
def test_create_minimal(self):
|
||||
t = TicketData(channel_id=1, guild_id=2, user_id=3, category_id="cat")
|
||||
assert t.status == TicketStatus.OPEN
|
||||
assert t.priority == TicketPriority.NORMAL
|
||||
assert t.locked is False
|
||||
assert t.claimed_by is None
|
||||
assert t.messages == []
|
||||
assert t.reason == ""
|
||||
|
||||
def test_to_dict(self):
|
||||
t = TicketData(
|
||||
channel_id=100,
|
||||
guild_id=200,
|
||||
user_id=300,
|
||||
category_id="support",
|
||||
status=TicketStatus.CLOSED,
|
||||
priority=TicketPriority.HIGH,
|
||||
claimed_by=999,
|
||||
locked=True,
|
||||
reason="test",
|
||||
)
|
||||
d = t.to_dict()
|
||||
assert d["channel_id"] == 100
|
||||
assert d["status"] == "closed"
|
||||
assert d["priority"] == "high"
|
||||
assert d["claimed_by"] == 999
|
||||
assert d["locked"] is True
|
||||
assert d["reason"] == "test"
|
||||
|
||||
def test_to_dict_with_messages(self):
|
||||
t = TicketData(channel_id=1, guild_id=2, user_id=3, category_id="c")
|
||||
t.messages.append(TicketMessage(author_id=1, author_name="a", content="hi", timestamp="t"))
|
||||
d = t.to_dict()
|
||||
assert len(d["messages"]) == 1
|
||||
assert d["messages"][0]["content"] == "hi"
|
||||
|
||||
def test_from_dict(self):
|
||||
d = {
|
||||
"channel_id": 1,
|
||||
"guild_id": 2,
|
||||
"user_id": 3,
|
||||
"category_id": "c",
|
||||
"status": "closed",
|
||||
"priority": "high",
|
||||
"claimed_by": 99,
|
||||
"locked": True,
|
||||
"reason": "r",
|
||||
"messages": [{"author_id": 1, "author_name": "a", "content": "hi", "timestamp": "t"}],
|
||||
}
|
||||
t = TicketData.from_dict(d)
|
||||
assert t.status == TicketStatus.CLOSED
|
||||
assert t.priority == TicketPriority.HIGH
|
||||
assert t.claimed_by == 99
|
||||
assert t.locked is True
|
||||
assert len(t.messages) == 1
|
||||
|
||||
def test_from_dict_legacy_keys(self):
|
||||
d = {
|
||||
"channel_id": 1,
|
||||
"guild_id": 2,
|
||||
"user_id": 3,
|
||||
"category_id": "c",
|
||||
"transcript_path": "/old/path.html",
|
||||
"rating": 5,
|
||||
"feedback": "ok",
|
||||
}
|
||||
t = TicketData.from_dict(d)
|
||||
assert not hasattr(t, "transcript_path")
|
||||
assert not hasattr(t, "rating")
|
||||
assert not hasattr(t, "feedback")
|
||||
|
||||
def test_roundtrip(self):
|
||||
t = TicketData(
|
||||
channel_id=10,
|
||||
guild_id=20,
|
||||
user_id=30,
|
||||
category_id="cat",
|
||||
status=TicketStatus.CLOSED,
|
||||
priority=TicketPriority.URGENT,
|
||||
claimed_by=50,
|
||||
locked=True,
|
||||
reason="test reason",
|
||||
created_at="2026-01-01T00:00:00",
|
||||
closed_at="2026-01-02T00:00:00",
|
||||
)
|
||||
t.messages.append(TicketMessage(author_id=1, author_name="u", content="m", timestamp="t"))
|
||||
t2 = TicketData.from_dict(t.to_dict())
|
||||
assert t2.channel_id == t.channel_id
|
||||
assert t2.status == t.status
|
||||
assert t2.priority == t.priority
|
||||
assert t2.claimed_by == t.claimed_by
|
||||
assert t2.locked == t.locked
|
||||
assert len(t2.messages) == 1
|
||||
|
||||
|
||||
class TestGuildTicketConfig:
|
||||
def test_create_default(self):
|
||||
c = GuildTicketConfig(guild_id=1)
|
||||
assert c.enabled is False
|
||||
assert c.categories == []
|
||||
assert c.staff_role_id is None
|
||||
assert c.log_channel_id is None
|
||||
assert c.transcript_enabled is True
|
||||
|
||||
def test_to_dict(self):
|
||||
c = GuildTicketConfig(
|
||||
guild_id=1,
|
||||
enabled=True,
|
||||
staff_role_id=100,
|
||||
log_channel_id=200,
|
||||
panel_channel_id=300,
|
||||
panel_message_id=400,
|
||||
)
|
||||
c.categories.append(TicketCategory(id="a", name="A"))
|
||||
d = c.to_dict()
|
||||
assert d["guild_id"] == 1
|
||||
assert d["enabled"] is True
|
||||
assert d["staff_role_id"] == 100
|
||||
assert d["log_channel_id"] == 200
|
||||
assert len(d["categories"]) == 1
|
||||
|
||||
def test_from_dict(self):
|
||||
d = {
|
||||
"guild_id": 1,
|
||||
"enabled": True,
|
||||
"staff_role_id": 100,
|
||||
"log_channel_id": 200,
|
||||
"categories": [{"id": "a", "name": "A"}],
|
||||
}
|
||||
c = GuildTicketConfig.from_dict(d)
|
||||
assert c.enabled is True
|
||||
assert len(c.categories) == 1
|
||||
assert c.categories[0].id == "a"
|
||||
|
||||
def test_roundtrip(self):
|
||||
c = GuildTicketConfig(guild_id=42, enabled=True)
|
||||
c.categories.append(TicketCategory(id="x", name="X", emoji="🔧"))
|
||||
c2 = GuildTicketConfig.from_dict(c.to_dict())
|
||||
assert c2.guild_id == 42
|
||||
assert c2.enabled is True
|
||||
assert c2.categories[0].id == "x"
|
||||
130
tests/test_storage.py
Normal file
130
tests/test_storage.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from tickets import storage
|
||||
from tickets.data import GuildTicketConfig, TicketCategory, TicketData
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_storage(tmp_path):
|
||||
"""Isolate storage in a temp dir and reset caches."""
|
||||
storage.TICKETS_DIR = tmp_path / "tickets"
|
||||
storage._CONFIG_FILE = storage.TICKETS_DIR / "config.json"
|
||||
storage._config_cache.clear()
|
||||
storage._tickets_cache.clear()
|
||||
yield
|
||||
storage._config_cache.clear()
|
||||
storage._tickets_cache.clear()
|
||||
|
||||
|
||||
class TestGetConfig:
|
||||
def test_returns_default_when_missing(self):
|
||||
config = storage.get_config(guild_id=123)
|
||||
assert config.guild_id == 123
|
||||
assert config.enabled is False
|
||||
assert config.categories == []
|
||||
|
||||
def test_returns_same_object(self):
|
||||
c1 = storage.get_config(guild_id=1)
|
||||
c2 = storage.get_config(guild_id=1)
|
||||
assert c1 is c2
|
||||
|
||||
def test_different_guilds_different_configs(self):
|
||||
c1 = storage.get_config(guild_id=1)
|
||||
c2 = storage.get_config(guild_id=2)
|
||||
assert c1.guild_id != c2.guild_id
|
||||
|
||||
|
||||
class TestSaveConfig:
|
||||
def test_save_creates_file(self, tmp_path):
|
||||
config = storage.get_config(guild_id=1)
|
||||
config.enabled = True
|
||||
storage.save_config(config)
|
||||
assert storage._CONFIG_FILE.exists()
|
||||
|
||||
def test_save_persists(self, tmp_path):
|
||||
config = storage.get_config(guild_id=1)
|
||||
config.enabled = True
|
||||
config.categories.append(TicketCategory(id="a", name="A"))
|
||||
storage.save_config(config)
|
||||
|
||||
storage._config_cache.clear()
|
||||
loaded = storage.get_config(guild_id=1)
|
||||
assert loaded.enabled is True
|
||||
assert len(loaded.categories) == 1
|
||||
assert loaded.categories[0].id == "a"
|
||||
|
||||
def test_save_multiple_guilds(self):
|
||||
c1 = storage.get_config(guild_id=1)
|
||||
c1.enabled = True
|
||||
storage.save_config(c1)
|
||||
|
||||
c2 = storage.get_config(guild_id=2)
|
||||
c2.enabled = False
|
||||
storage.save_config(c2)
|
||||
|
||||
storage._config_cache.clear()
|
||||
assert storage.get_config(guild_id=1).enabled is True
|
||||
assert storage.get_config(guild_id=2).enabled is False
|
||||
|
||||
|
||||
class TestSaveAndGetTicket:
|
||||
def test_get_missing_returns_none(self):
|
||||
assert storage.get_ticket(999) is None
|
||||
|
||||
def test_save_and_get(self):
|
||||
ticket = TicketData(channel_id=100, guild_id=1, user_id=2, category_id="cat")
|
||||
storage.save_ticket(ticket)
|
||||
loaded = storage.get_ticket(100)
|
||||
assert loaded is not None
|
||||
assert loaded.channel_id == 100
|
||||
assert loaded.category_id == "cat"
|
||||
|
||||
def test_save_persists_to_disk(self):
|
||||
ticket = TicketData(channel_id=200, guild_id=1, user_id=2, category_id="c")
|
||||
storage.save_ticket(ticket)
|
||||
|
||||
storage._tickets_cache.clear()
|
||||
loaded = storage.get_ticket(200)
|
||||
assert loaded is not None
|
||||
assert loaded.user_id == 2
|
||||
|
||||
def test_save_overwrites(self):
|
||||
t1 = TicketData(channel_id=300, guild_id=1, user_id=2, category_id="old")
|
||||
storage.save_ticket(t1)
|
||||
|
||||
t2 = TicketData(channel_id=300, guild_id=1, user_id=2, category_id="new")
|
||||
storage.save_ticket(t2)
|
||||
|
||||
loaded = storage.get_ticket(300)
|
||||
assert loaded.category_id == "new"
|
||||
|
||||
def test_multiple_tickets(self):
|
||||
t1 = TicketData(channel_id=400, guild_id=1, user_id=2, category_id="a")
|
||||
t2 = TicketData(channel_id=401, guild_id=1, user_id=3, category_id="b")
|
||||
storage.save_ticket(t1)
|
||||
storage.save_ticket(t2)
|
||||
assert storage.get_ticket(400).category_id == "a"
|
||||
assert storage.get_ticket(401).category_id == "b"
|
||||
|
||||
|
||||
class TestDeleteTicket:
|
||||
def test_delete_removes_file(self):
|
||||
ticket = TicketData(channel_id=500, guild_id=1, user_id=2, category_id="c")
|
||||
storage.save_ticket(ticket)
|
||||
assert storage.get_ticket(500) is not None
|
||||
|
||||
storage.delete_ticket(500)
|
||||
assert storage.get_ticket(500) is None
|
||||
|
||||
def test_delete_nonexistent(self):
|
||||
storage.delete_ticket(9999)
|
||||
|
||||
def test_delete_clears_cache(self):
|
||||
ticket = TicketData(channel_id=600, guild_id=1, user_id=2, category_id="c")
|
||||
storage.save_ticket(ticket)
|
||||
storage.delete_ticket(600)
|
||||
assert 600 not in storage._tickets_cache
|
||||
133
tests/test_transcript.py
Normal file
133
tests/test_transcript.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from tickets.data import TicketData, TicketMessage
|
||||
from tickets.transcript import _format_ts, generate_transcript
|
||||
|
||||
|
||||
class TestFormatTs:
|
||||
def test_valid_iso(self):
|
||||
result = _format_ts("2026-01-15T10:30:00+00:00")
|
||||
assert result == "15/01/2026 10:30:00"
|
||||
|
||||
def test_valid_naive_iso(self):
|
||||
result = _format_ts("2026-06-01T12:00:00")
|
||||
assert result == "01/06/2026 12:00:00"
|
||||
|
||||
def test_invalid_string_returns_as_is(self):
|
||||
assert _format_ts("not-a-date") == "not-a-date"
|
||||
|
||||
def test_empty_string(self):
|
||||
assert _format_ts("") == ""
|
||||
|
||||
def test_none_returns_none(self):
|
||||
assert _format_ts(None) is None
|
||||
|
||||
|
||||
def _make_ticket(**kwargs) -> TicketData:
|
||||
defaults = dict(channel_id=123, guild_id=456, user_id=789, category_id="support")
|
||||
defaults.update(kwargs)
|
||||
return TicketData(**defaults)
|
||||
|
||||
|
||||
class TestGenerateTranscript:
|
||||
def test_returns_html(self):
|
||||
result = generate_transcript(_make_ticket())
|
||||
assert result.startswith("<!DOCTYPE html>")
|
||||
assert "</html>" in result
|
||||
|
||||
def test_contains_channel_id(self):
|
||||
result = generate_transcript(_make_ticket(channel_id=42))
|
||||
assert "Ticket #42" in result
|
||||
|
||||
def test_empty_messages(self):
|
||||
ticket = _make_ticket()
|
||||
result = generate_transcript(ticket)
|
||||
assert "Aucun message" in result
|
||||
|
||||
def test_single_user_message(self):
|
||||
ticket = _make_ticket()
|
||||
ticket.messages.append(
|
||||
TicketMessage(author_id=1, author_name="TestUser", content="Hello!", timestamp="2026-01-01T00:00:00")
|
||||
)
|
||||
result = generate_transcript(ticket)
|
||||
assert "TestUser" in result
|
||||
assert "Hello!" in result
|
||||
assert 'class="badge badge-staff"' not in result
|
||||
|
||||
def test_staff_message_has_badge(self):
|
||||
ticket = _make_ticket()
|
||||
ticket.messages.append(
|
||||
TicketMessage(
|
||||
author_id=2, author_name="StaffUser", content="Reply", timestamp="2026-01-01T00:00:00", is_staff=True
|
||||
)
|
||||
)
|
||||
result = generate_transcript(ticket)
|
||||
assert "badge-staff" in result
|
||||
assert "STAFF" in result
|
||||
|
||||
def test_message_content_escaped(self):
|
||||
ticket = _make_ticket()
|
||||
ticket.messages.append(
|
||||
TicketMessage(author_id=1, author_name="x", content="<script>alert('xss')</script>", timestamp="t")
|
||||
)
|
||||
result = generate_transcript(ticket)
|
||||
assert "<script>" not in result
|
||||
assert "<script>" in result
|
||||
|
||||
def test_image_attachment(self):
|
||||
ticket = _make_ticket()
|
||||
ticket.messages.append(
|
||||
TicketMessage(
|
||||
author_id=1,
|
||||
author_name="u",
|
||||
content="pic",
|
||||
timestamp="t",
|
||||
attachments=["https://example.com/image.png"],
|
||||
)
|
||||
)
|
||||
result = generate_transcript(ticket)
|
||||
assert "<img" in result
|
||||
assert "image.png" in result
|
||||
|
||||
def test_file_attachment(self):
|
||||
ticket = _make_ticket()
|
||||
ticket.messages.append(
|
||||
TicketMessage(
|
||||
author_id=1,
|
||||
author_name="u",
|
||||
content="file",
|
||||
timestamp="t",
|
||||
attachments=["https://example.com/doc.pdf"],
|
||||
)
|
||||
)
|
||||
result = generate_transcript(ticket)
|
||||
assert "<a href=" in result
|
||||
assert "doc.pdf" in result
|
||||
|
||||
def test_closed_ticket_shows_closed_at(self):
|
||||
ticket = _make_ticket(closed_at="2026-01-02T10:00:00+00:00")
|
||||
result = generate_transcript(ticket)
|
||||
assert "Ferme" in result or "Fermé" in result
|
||||
|
||||
def test_open_ticket_no_closed_at(self):
|
||||
ticket = _make_ticket()
|
||||
result = generate_transcript(ticket)
|
||||
assert "Ferme" not in result
|
||||
|
||||
def test_dark_theme_colors(self):
|
||||
result = generate_transcript(_make_ticket())
|
||||
assert "#0b0d11" in result
|
||||
|
||||
def test_multiple_messages(self):
|
||||
ticket = _make_ticket()
|
||||
for i in range(5):
|
||||
ticket.messages.append(
|
||||
TicketMessage(author_id=i, author_name=f"User{i}", content=f"Message {i}", timestamp=f"t{i}")
|
||||
)
|
||||
result = generate_transcript(ticket)
|
||||
for i in range(5):
|
||||
assert f"Message {i}" in result
|
||||
|
||||
def test_footer_text(self):
|
||||
result = generate_transcript(_make_ticket())
|
||||
assert "Vision Studio" in result
|
||||
304
tests/test_ui.py
Normal file
304
tests/test_ui.py
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue