Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions tests/admin_panel/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import pytest

from typer_bot.commands.admin_commands import AdminCommands


@pytest.fixture
def admin_cog(mock_bot, database):
mock_bot.db = database
return AdminCommands(mock_bot)
47 changes: 47 additions & 0 deletions tests/admin_panel/test_fixture_discord_cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from unittest.mock import AsyncMock, MagicMock

import discord
import pytest

from typer_bot.commands.admin_panel.fixtures import _cleanup_discord_announcement


class TestDiscordCleanup:
"""_cleanup_discord_announcement should delete thread+message, tolerating Discord errors."""

@pytest.mark.asyncio
async def test_cleanup_deletes_thread_and_message(self):
bot = MagicMock(spec=discord.Client)
mock_thread = AsyncMock()
mock_message = AsyncMock()
mock_message.thread = mock_thread
channel = AsyncMock()
channel.fetch_message = AsyncMock(return_value=mock_message)
bot.get_channel.return_value = channel

await _cleanup_discord_announcement(bot, "111", "222", week_number=5)

mock_thread.delete.assert_called_once()
mock_message.delete.assert_called_once()

@pytest.mark.asyncio
async def test_cleanup_no_thread_deletes_message_only(self):
bot = MagicMock(spec=discord.Client)
mock_message = AsyncMock()
mock_message.thread = None
channel = AsyncMock()
channel.fetch_message = AsyncMock(return_value=mock_message)
bot.get_channel.return_value = channel

await _cleanup_discord_announcement(bot, "111", "222", week_number=5)

mock_message.delete.assert_called_once()

@pytest.mark.asyncio
async def test_cleanup_swallows_discord_errors(self):
bot = MagicMock(spec=discord.Client)
channel = AsyncMock()
channel.fetch_message.side_effect = Exception("Discord unavailable")
bot.get_channel.return_value = channel

await _cleanup_discord_announcement(bot, "111", "222", week_number=5)
81 changes: 81 additions & 0 deletions tests/admin_panel/test_fixture_panel_creation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from unittest.mock import MagicMock

import discord
import pytest

from tests.admin_panel_helpers import get_button as _get_button
from typer_bot.commands.admin_panel import (
CreateFixtureModal,
UnifiedAdminPanelView,
)


class TestFixturePanelCreation:
@pytest.mark.asyncio
async def test_unified_panel_create_fixture_button_opens_modal(
self,
admin_cog,
mock_interaction_admin,
):
channel = MagicMock(spec=discord.TextChannel)
channel.id = mock_interaction_admin.channel.id
mock_interaction_admin.channel = channel
view = UnifiedAdminPanelView(
admin_cog.db,
admin_cog.service,
str(mock_interaction_admin.user.id),
"111111",
admin_commands=admin_cog,
bot=admin_cog.bot,
)
create_button = _get_button(view, "Create Fixture")

await create_button.callback(mock_interaction_admin)

assert isinstance(mock_interaction_admin.modal_sent["modal"], CreateFixtureModal)

@pytest.mark.asyncio
async def test_unified_panel_create_fixture_button_uses_parent_channel_from_thread(
self,
admin_cog,
mock_interaction_admin,
):
parent_channel = MagicMock(spec=discord.TextChannel)
parent_channel.id = 123456
thread = MagicMock(spec=discord.Thread)
thread.parent = parent_channel
mock_interaction_admin.channel = thread

view = UnifiedAdminPanelView(
admin_cog.db,
admin_cog.service,
str(mock_interaction_admin.user.id),
"111111",
admin_commands=admin_cog,
bot=admin_cog.bot,
)
create_button = _get_button(view, "Create Fixture")
await create_button.callback(mock_interaction_admin)

assert mock_interaction_admin.modal_sent["modal"].channel is parent_channel

@pytest.mark.asyncio
async def test_unified_panel_create_fixture_button_rejects_invalid_context(
self,
admin_cog,
mock_interaction_admin,
):
mock_interaction_admin.channel = MagicMock()

view = UnifiedAdminPanelView(
admin_cog.db,
admin_cog.service,
str(mock_interaction_admin.user.id),
"111111",
admin_commands=admin_cog,
bot=admin_cog.bot,
)
create_button = _get_button(view, "Create Fixture")
await create_button.callback(mock_interaction_admin)

assert "text channel" in mock_interaction_admin.response_sent[-1]["content"].lower()
105 changes: 105 additions & 0 deletions tests/admin_panel/test_fixture_panel_jump_to_week.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from datetime import UTC, datetime, timedelta

import pytest

from tests.admin_panel_helpers import get_button as _get_button
from tests.admin_panel_helpers import has_button as _has_button
from typer_bot.commands.admin_panel import (
UnifiedAdminPanelView,
)


class TestFixturePanelJumpToWeek:
@pytest.mark.asyncio
async def test_unified_panel_jump_to_week_reaches_older_open_fixture(
self,
admin_cog,
mock_interaction_admin,
sample_games,
):
deadline = datetime.now(UTC) + timedelta(days=1)
first_fixture_id = None
for week in range(1, 28):
fixture_id = await admin_cog.db.create_fixture("111111", week, sample_games, deadline)
if week == 1:
first_fixture_id = fixture_id
assert first_fixture_id is not None
await admin_cog.db.save_results(first_fixture_id, ["1-0", "1-1", "0-0"])

view = UnifiedAdminPanelView(
admin_cog.db,
admin_cog.service,
str(mock_interaction_admin.user.id),
"111111",
admin_commands=admin_cog,
bot=admin_cog.bot,
)
await view.load_fixture_options()

assert all(option.label != "Week 1 [OPEN]" for option in view.fixture_select.options)

jump_button = _get_button(view, "Jump To Week")
await jump_button.callback(mock_interaction_admin)
modal = mock_interaction_admin.modal_sent["modal"]
modal.week_input._value = "1"

await modal.on_submit(mock_interaction_admin)

assert view.selection.fixture_label == "Week 1 [OPEN]"
assert "Fixture: Week 1 [OPEN]" in mock_interaction_admin.response_sent[-1]["content"]
assert _has_button(view, "Enter Results") is False
assert _has_button(view, "Calculate Scores") is True
assert _has_button(view, "Correct Results") is True

@pytest.mark.asyncio
async def test_unified_panel_jump_to_week_rejects_invalid_input(
self,
admin_cog,
mock_interaction_admin,
):
view = UnifiedAdminPanelView(
admin_cog.db,
admin_cog.service,
str(mock_interaction_admin.user.id),
"111111",
admin_commands=admin_cog,
bot=admin_cog.bot,
)
jump_button = _get_button(view, "Jump To Week")
await jump_button.callback(mock_interaction_admin)
modal = mock_interaction_admin.modal_sent["modal"]
modal.week_input._value = "abc"

await modal.on_submit(mock_interaction_admin)

assert "whole number" in mock_interaction_admin.response_sent[-1]["content"]
assert view.selection.fixture_id is None

@pytest.mark.asyncio
async def test_unified_panel_jump_to_week_rejects_duplicate_open_weeks(
self,
admin_cog,
mock_interaction_admin,
sample_games,
):
deadline = datetime.now(UTC) + timedelta(days=1)
await admin_cog.db.create_fixture("111111", 5, sample_games, deadline)
await admin_cog.db.create_fixture("111111", 5, sample_games, deadline)

view = UnifiedAdminPanelView(
admin_cog.db,
admin_cog.service,
str(mock_interaction_admin.user.id),
"111111",
admin_commands=admin_cog,
bot=admin_cog.bot,
)
jump_button = _get_button(view, "Jump To Week")
await jump_button.callback(mock_interaction_admin)
modal = mock_interaction_admin.modal_sent["modal"]
modal.week_input._value = "5"

await modal.on_submit(mock_interaction_admin)

assert "More than one open fixture" in mock_interaction_admin.response_sent[-1]["content"]
assert view.selection.fixture_id is None
Loading
Loading