From 5675dfd20b24f1e0eef06b11d9ec41a894d3a5bf Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 28 Apr 2026 20:05:36 +0200 Subject: multitude of changes. but primarly, use db for all data. Signed-off-by: salmonik --- tests/cogs/test_cron.py | 88 +++++++---------- tests/cogs/test_uploader.py | 226 ++++++++++++++++++++++++++++++++++++++++++++ tests/cogs/test_vclogs.py | 31 +++--- 3 files changed, 277 insertions(+), 68 deletions(-) create mode 100644 tests/cogs/test_uploader.py (limited to 'tests/cogs') diff --git a/tests/cogs/test_cron.py b/tests/cogs/test_cron.py index 93031f8..91fcb82 100644 --- a/tests/cogs/test_cron.py +++ b/tests/cogs/test_cron.py @@ -1,5 +1,3 @@ -import json -import os import pytest from datetime import datetime from unittest.mock import MagicMock, patch @@ -19,68 +17,48 @@ def mock_bot(): def create_cog(mock_bot, tmp_path): with patch.object(Cron, "__init__", lambda self, bot: None): - cog = Cron.__new__(Cron) - cog.bot = mock_bot - cog.scheduled_tasks = [] - cog.cron_file = str(tmp_path / "cron_jobs.json") - cog.task = MagicMock() - return cog + with patch("cogs.cron.init_database"): + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.fetchall.return_value = [] + mock_conn.cursor.return_value = mock_cursor + mock_conn.close.return_value = None + + with patch("cogs.cron.get_connection", return_value=mock_conn): + with patch("cogs.utils.database.get_db_path", return_value=":memory:"): + cog = Cron.__new__(Cron) + cog.bot = mock_bot + cog.scheduled_tasks = [] + cog.task = MagicMock() + return cog class TestLoadTasks: - def test_load_tasks_file_not_exists(self, mock_bot, tmp_path): - cog = create_cog(mock_bot, tmp_path) - cog.cron_file = str(tmp_path / "nonexistent.json") - cog.load_tasks() - assert cog.scheduled_tasks == [] - - def test_load_tasks_empty_file(self, mock_bot, tmp_path): - cron_file = tmp_path / "empty.json" - cron_file.write_text("") - cog = create_cog(mock_bot, tmp_path) - cog.cron_file = str(cron_file) - cog.load_tasks() - assert cog.scheduled_tasks == [] - - def test_load_tasks_valid_file(self, mock_bot, tmp_path): - cron_file = tmp_path / "valid.json" - tasks = [{"id": 1, "cron_expression": "* * * * *", "type": "audio", "channel_id": 123, "active": True}] - cron_file.write_text(json.dumps(tasks)) - cog = create_cog(mock_bot, tmp_path) - cog.cron_file = str(cron_file) - cog.load_tasks() - assert len(cog.scheduled_tasks) == 1 - assert cog.scheduled_tasks[0]["id"] == 1 + def test_load_tasks_with_data(self, mock_bot, tmp_path): + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.fetchall.return_value = [ + (1, "* * * * *", "audio", 123, 1, None, "test message") + ] + mock_conn.cursor.return_value = mock_cursor + mock_conn.close.return_value = None - def test_load_tasks_io_error(self, mock_bot, tmp_path): - cog = create_cog(mock_bot, tmp_path) - with patch("builtins.open", side_effect=IOError("test error")): + with patch("cogs.cron.get_connection", return_value=mock_conn): + cog = Cron.__new__(Cron) + cog.bot = mock_bot + cog.scheduled_tasks = [] + cog.task = MagicMock() cog.load_tasks() - assert cog.scheduled_tasks == [] + assert len(cog.scheduled_tasks) == 1 + assert cog.scheduled_tasks[0]["id"] == 1 class TestSaveTasks: - def test_save_tasks_with_real_file(self, mock_bot, tmp_path): - cron_file = tmp_path / "cron_jobs.json" - cog = create_cog(mock_bot, tmp_path) - cog.cron_file = str(cron_file) - cog.scheduled_tasks = [{"id": 1, "cron_expression": "* * * * *"}] - cog.save_tasks() - assert os.path.exists(cron_file) - with open(cron_file) as f: - loaded = json.load(f) - assert len(loaded) == 1 - - def test_save_tasks_creates_directory_structure(self, mock_bot, tmp_path): + def test_save_tasks(self, mock_bot, tmp_path): cog = create_cog(mock_bot, tmp_path) - new_dir = tmp_path / "new_dir" - cron_file = new_dir / "cron_jobs.json" - cog.cron_file = str(cron_file) - cog.scheduled_tasks = [{"id": 1}] - + cog.scheduled_tasks = [{"id": 1, "cron_expression": "* * * * *", "type": "audio", "channel_id": 123, "active": True}] cog.save_tasks() - - assert os.path.exists(new_dir) + assert len(cog.scheduled_tasks) == 1 class TestShouldRunTask: @@ -163,4 +141,4 @@ class TestTaskTypes: def test_task_type_message_aliases(self, mock_bot, tmp_path): message_types = ("wyślij_wiadomość", "send_message") for task_type in message_types: - assert task_type in ("wyślij_wiadomość", "send_message") + assert task_type in ("wyślij_wiadomość", "send_message") \ No newline at end of file diff --git a/tests/cogs/test_uploader.py b/tests/cogs/test_uploader.py new file mode 100644 index 0000000..addf87e --- /dev/null +++ b/tests/cogs/test_uploader.py @@ -0,0 +1,226 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock +import os +import discord +from tests.conftest import MockApplicationContext, MockMessage, MockUser + + +@pytest.fixture +def uploader_cog(bot): + with patch('cogs.utils.temp.get_temp_dir', return_value='/tmp/test'): + from cogs.uploader import Uploader + cog = Uploader(bot) + return cog + + +@pytest.mark.asyncio +async def test_file_autocomplete(uploader_cog): + mock_autocomplete_ctx = MagicMock() + mock_autocomplete_ctx.value = "test" + mock_interaction = MagicMock() + mock_user = MockUser(id=12345) + mock_interaction.user = mock_user + mock_autocomplete_ctx.interaction = mock_interaction + + with patch('cogs.utils.file_search.UniversalSearcher.search_copyparty_autocomplete', new_callable=AsyncMock) as mock_search: + mock_search.return_value = [ + "https://copyparty.local/testfile1.mp3", + "https://copyparty.local/testfile2.mp3" + ] + result = await uploader_cog._file_autocomplete(mock_autocomplete_ctx) + assert len(result) == 2 + assert result[0].name == "testfile1.mp3" + + +@pytest.mark.asyncio +async def test_file_autocomplete_empty_query(uploader_cog): + mock_autocomplete_ctx = MagicMock() + mock_autocomplete_ctx.value = "" + mock_interaction = MagicMock() + mock_interaction.user = MockUser(id=12345) + mock_autocomplete_ctx.interaction = mock_interaction + + with patch('cogs.utils.file_search.UniversalSearcher.search_copyparty_autocomplete', new_callable=AsyncMock) as mock_search: + result = await uploader_cog._file_autocomplete(mock_autocomplete_ctx) + mock_search.assert_called_once_with("", max_results=25) + + +@pytest.mark.asyncio +async def test_download_from_url_success(uploader_cog): + with patch('requests.get') as mock_get: + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.content = b"test file content" + mock_get.return_value = mock_response + + mock_file = MagicMock() + mock_file.write = AsyncMock() + + mock_cm = AsyncMock() + mock_cm.__aenter__ = AsyncMock(return_value=mock_file) + mock_cm.__aexit__ = AsyncMock(return_value=None) + + with patch('cogs.uploader.aiofiles.open', return_value=mock_cm): + result = await uploader_cog._download_from_url("https://copyparty.local/file.txt") + + assert result is not None + assert "file.txt" in result + mock_file.write.assert_called_once_with(b"test file content") + + +@pytest.mark.asyncio +async def test_download_from_url_failure(uploader_cog): + with patch('requests.get') as mock_get: + mock_response = MagicMock() + mock_response.status_code = 404 + mock_get.return_value = mock_response + + result = await uploader_cog._download_from_url("https://copyparty.local/notfound.txt") + + assert result is None + + +@pytest.mark.asyncio +async def test_upload_to_copyparty_success(uploader_cog): + mock_file = MagicMock() + mock_file.read = AsyncMock(return_value=b"test content") + + mock_cm = AsyncMock() + mock_cm.__aenter__ = AsyncMock(return_value=mock_file) + mock_cm.__aexit__ = AsyncMock(return_value=None) + + with patch('cogs.uploader.aiofiles.open', return_value=mock_cm): + with patch('requests.post') as mock_post: + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"fileurl": "https://copyparty.local/uploaded.txt"} + mock_post.return_value = mock_response + + with patch.dict(os.environ, {"COPYPARTY_PASSWORD": "testpass"}): + result = await uploader_cog._upload_to_copyparty("/tmp/testfile.txt") + + assert result == "https://copyparty.local/uploaded.txt" + + +@pytest.mark.asyncio +async def test_upload_to_copyparty_failure(uploader_cog): + mock_file = MagicMock() + mock_file.read = AsyncMock(return_value=b"test content") + + mock_cm = AsyncMock() + mock_cm.__aenter__ = AsyncMock(return_value=mock_file) + mock_cm.__aexit__ = AsyncMock(return_value=None) + + with patch('cogs.uploader.aiofiles.open', return_value=mock_cm): + with patch('requests.post') as mock_post: + mock_response = MagicMock() + mock_response.status_code = 500 + mock_post.return_value = mock_response + + result = await uploader_cog._upload_to_copyparty("/tmp/testfile.txt") + + assert result is None + + +@pytest.mark.asyncio +async def test_upload_file_no_url(uploader_cog): + uploader_cog._uploadurl = None + + result = await uploader_cog._upload_file("/tmp/test.txt") + + assert result is None + + +@pytest.mark.asyncio +async def test_message_upload_no_attachments(uploader_cog, mock_channel, mock_guild, bot): + ctx = MockApplicationContext(bot=bot, channel=mock_channel, guild=mock_guild) + + mock_message = MagicMock() + mock_message.attachments = [] + + with patch.object(ctx, 'respond', new_callable=AsyncMock) as mock_respond: + await uploader_cog.message_upload.callback(uploader_cog, ctx, message=mock_message) + mock_respond.assert_called_once_with("Brak załączników w wiadomości", ephemeral=True) + + +@pytest.mark.asyncio +async def test_message_upload_success(uploader_cog, mock_channel, mock_guild, bot): + ctx = MockApplicationContext(bot=bot, channel=mock_channel, guild=mock_guild) + + mock_attachment = MagicMock() + mock_attachment.filename = "test.txt" + mock_attachment.save = AsyncMock() + + mock_message = MagicMock() + mock_message.attachments = [mock_attachment] + + with patch.object(uploader_cog, '_upload_file', new_callable=AsyncMock) as mock_upload: + mock_upload.return_value = "https://copyparty.local/uploaded.txt" + + with patch('os.path.exists', return_value=True): + with patch('os.remove'): + with patch.object(ctx, 'defer', new_callable=AsyncMock): + with patch.object(ctx, 'respond', new_callable=AsyncMock) as mock_respond: + await uploader_cog.message_upload.callback(uploader_cog, ctx, message=mock_message) + mock_respond.assert_called_once() + assert "Wysłano" in str(mock_respond.call_args) + + +@pytest.mark.asyncio +async def test_message_upload_all_failed(uploader_cog, mock_channel, mock_guild, bot): + ctx = MockApplicationContext(bot=bot, channel=mock_channel, guild=mock_guild) + + mock_attachment = MagicMock() + mock_attachment.filename = "test.txt" + mock_attachment.save = AsyncMock() + + mock_message = MagicMock() + mock_message.attachments = [mock_attachment] + + with patch.object(uploader_cog, '_upload_file', new_callable=AsyncMock) as mock_upload: + mock_upload.return_value = None + + with patch('os.path.exists', return_value=True): + with patch('os.remove'): + with patch.object(ctx, 'defer', new_callable=AsyncMock): + with patch.object(ctx, 'respond', new_callable=AsyncMock) as mock_respond: + await uploader_cog.message_upload.callback(uploader_cog, ctx, message=mock_message) + mock_respond.assert_called_once_with("Nie udało się wysłać żadnych plików", ephemeral=True) + + +@pytest.mark.asyncio +async def test_upload_slash_command_success(uploader_cog, mock_channel, mock_guild, bot): + ctx = MockApplicationContext(bot=bot, channel=mock_channel, guild=mock_guild) + + mock_attachment = MagicMock() + mock_attachment.filename = "test.txt" + mock_attachment.save = AsyncMock() + + with patch.object(uploader_cog, '_upload_file', new_callable=AsyncMock) as mock_upload: + mock_upload.return_value = "https://copyparty.local/uploaded.txt" + + with patch('os.path.exists', return_value=True): + with patch('os.remove'): + with patch.object(ctx, 'defer', new_callable=AsyncMock): + with patch.object(ctx, 'respond', new_callable=AsyncMock) as mock_respond: + await uploader_cog.upload.callback(uploader_cog, ctx, zalacznik=mock_attachment) + mock_respond.assert_called_once_with("https://copyparty.local/uploaded.txt") + + +@pytest.mark.asyncio +async def test_upload_slash_command_failure(uploader_cog, mock_channel, mock_guild, bot): + ctx = MockApplicationContext(bot=bot, channel=mock_channel, guild=mock_guild) + + mock_attachment = MagicMock() + mock_attachment.filename = "test.txt" + mock_attachment.save = AsyncMock() + + with patch.object(uploader_cog, '_upload_file', new_callable=AsyncMock) as mock_upload: + mock_upload.return_value = None + + with patch('os.path.exists', return_value=True): + with patch('os.remove'): + with patch.object(ctx, 'defer', new_callable=AsyncMock): + with patch.object(ctx, 'respond', new_callable=AsyncMock) as mock_respond: + await uploader_cog.upload.callback(uploader_cog, ctx, zalacznik=mock_attachment) + mock_respond.assert_called_once_with("Nie udało się wysłać pliku", ephemeral=True) \ No newline at end of file diff --git a/tests/cogs/test_vclogs.py b/tests/cogs/test_vclogs.py index 2003478..da41482 100644 --- a/tests/cogs/test_vclogs.py +++ b/tests/cogs/test_vclogs.py @@ -1,6 +1,5 @@ import pytest from unittest.mock import AsyncMock, patch, MagicMock -import json from tests.conftest import MockApplicationContext @@ -13,20 +12,26 @@ def test_format_duration(): def test_load_activity_data(): - with patch('builtins.open', MagicMock()): - with patch('os.path.exists', return_value=True): - with patch('json.load', return_value={"123": {"active": True}}): - from cogs.vclogs import load_activity_data - data = load_activity_data() - assert isinstance(data, dict) + with patch("cogs.vclogs.get_connection") as mock_conn: + mock_cursor = MagicMock() + mock_conn.return_value.cursor.return_value = mock_cursor + mock_conn.return_value.close.return_value = None + mock_cursor.fetchall.return_value = [] + + from cogs.vclogs import load_activity_data + data = load_activity_data() + assert isinstance(data, dict) def test_save_activity_data(): - with patch('builtins.open', MagicMock()) as mock_open: - with patch('json.dump'): - from cogs.vclogs import save_activity_data - save_activity_data({"123": {"active": True}}) - mock_open.assert_called_once() + with patch("cogs.vclogs.get_connection") as mock_conn: + mock_cursor = MagicMock() + mock_conn.return_value.cursor.return_value = mock_cursor + mock_conn.return_value.commit.return_value = None + mock_conn.return_value.close.return_value = None + + from cogs.vclogs import save_activity_data + save_activity_data({"123": {"active": True}}) @pytest.fixture @@ -80,4 +85,4 @@ async def test_handle_state_change_mute_false(vclogs_cog): tts_text, channel = await vclogs_cog.handle_state_change(mock_member, mock_channel, 'mute', False, None) - assert "zrobił jajecznice" in tts_text + assert "zrobił jajecznice" in tts_text \ No newline at end of file -- cgit v1.2.3