diff --git a/main.py b/main.py index c390c85..5b16f8f 100644 --- a/main.py +++ b/main.py @@ -1,24 +1,53 @@ +import logging import os from dotenv import load_dotenv -from telegram.ext import ApplicationBuilder, CallbackQueryHandler, CommandHandler +from telegram import BotCommand +from telegram.ext import ( + ApplicationBuilder, + CallbackQueryHandler, + CommandHandler, + ContextTypes, +) from src.buttons import handle_buttons, handle_resolution -from src.handlers import ( - about, - beauty, - download, - service, - start, -) +from src.handlers import about, beauty, download, service, start from src.logo import print_logo +# Altro modo per ignorare gli argomenti quando eseguiamo test del codice. +async def error_handler(_: object, context: ContextTypes.DEFAULT_TYPE) -> None: + logging.error("Eccezione:", exc_info=context.error) + + +async def post_init(application): + await application.bot.set_my_commands( + [ + BotCommand("start", "Avvia il bot"), + BotCommand("download", "Scarica un media"), + BotCommand("service", "Servizi disponibili"), + BotCommand("beauty", "Bellezza del giorno"), + BotCommand("about", "Info sul progetto"), + ] + ) + + def main(): load_dotenv() - app = ApplicationBuilder().token(os.getenv("BOT_TOKEN")).build() + logging.basicConfig(level=logging.ERROR) + + app = ( + ApplicationBuilder() + .token(os.getenv("BOT_TOKEN")) + .concurrent_updates(256) + .post_init(post_init) + .build() + ) + + app.add_error_handler(error_handler) + app.add_handler(CommandHandler("start", start)) app.add_handler(CommandHandler("download", download)) app.add_handler(CommandHandler("about", about)) diff --git a/pyproject.toml b/pyproject.toml index 81f97fe..17e4e37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,4 +3,10 @@ profile = "black" line_length = 120 [tool.mypy] -ignore_missing_imports = true \ No newline at end of file +ignore_missing_imports = true + +[tool.coverage.run] +omit = ["src/logo.py", "src/messages.py", "__init__.py"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index 8dd6dd4..e4af0c5 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -7,4 +7,5 @@ flake8 black isort types-yt-dlp -types-requests \ No newline at end of file +types-requests +pytest-asyncio \ No newline at end of file diff --git a/src/buttons.py b/src/buttons.py index e51fc46..91a029c 100644 --- a/src/buttons.py +++ b/src/buttons.py @@ -5,7 +5,7 @@ from telegram.error import TelegramError from telegram.ext import CallbackContext, ContextTypes -from src.downloader import get_media +from src.downloader import get_media, get_media_size def get_main_menu() -> InlineKeyboardMarkup: @@ -101,49 +101,71 @@ async def handle_download(update: Update, context: CallbackContext) -> None: chat_id = update.callback_query.message.chat.id - await update.callback_query.edit_message_text("⏳ Download in corso...") - if context.user_data is not None: - # Se è impostato a true facciamo iniziare il download audio e passiamo come video resolution None. if context.user_data.get("download_audio"): - file_path = await asyncio.to_thread( - get_media, context.user_data["url"], None, "mp3" - ) - # Se è impostato a true facciamo iniziare il download video e passiamo come video resolution la risoluzione del video scelta. + media_type = "mp3" + resolution = None elif context.user_data.get("download_video"): - file_path = await asyncio.to_thread( - get_media, - context.user_data["url"], - context.user_data["video_resolution"], - "mp4", - ) + media_type = "mp4" + resolution = context.user_data["video_resolution"] else: return - if file_path and os.path.exists(file_path): - try: + telegram_max_upload_file = 50 * 1024 * 1024 - await context.bot.send_message( - chat_id=chat_id, - text="😎 Caricamento su Telegram in corso!", - ) - with open(file_path, "rb") as f: - await context.bot.send_document( - chat_id=chat_id, - document=f, - ) + size = await asyncio.to_thread( + get_media_size, context.user_data["url"], resolution, media_type + ) - os.remove(file_path) # eliminiamo il file dopo l'invio all'utente. + if size > telegram_max_upload_file: + await update.callback_query.edit_message_text( + "❌ File troppo grande per Telegram (max 50MB)." + ) + return + + await update.callback_query.edit_message_text("⏳ Download in corso...") - except TelegramError: + file_path = await asyncio.to_thread( + get_media, context.user_data["url"], resolution, media_type + ) + + if file_path and os.path.exists(file_path): + try: + ext = os.path.splitext(file_path) + + if ext[1] == ".mp3": + with open(file_path, "rb") as f: + await context.bot.send_audio( + chat_id=chat_id, + audio=f, + read_timeout=300, + write_timeout=300, + connect_timeout=30, + ) + else: + with open(file_path, "rb") as f: + await context.bot.send_video( + chat_id=chat_id, + video=f, + read_timeout=300, + write_timeout=300, + connect_timeout=30, + ) + + except TelegramError as e: + print(e) await context.bot.send_message( chat_id=chat_id, text="Errore invio file!", ) - except OSError: - await context.bot.send_message( - chat_id=chat_id, - text="Errore eliminazione file!", - ) + finally: + if os.path.exists(file_path): + try: + os.remove(file_path) + except OSError: + await context.bot.send_message( + chat_id=chat_id, + text="Errore eliminazione file!", + ) else: await context.bot.send_message(chat_id=chat_id, text="Download fallito.") diff --git a/src/downloader.py b/src/downloader.py index e1efb53..d687910 100644 --- a/src/downloader.py +++ b/src/downloader.py @@ -1,11 +1,12 @@ import os +import uuid import yt_dlp DOWNLOAD_DIR = "./tmp/downloads" -def build_ydl_opts(video_resolution: int | None, media_type: str) -> dict: +def build_ydl_opts(video_resolution: int | None, media_type: str, download_id) -> dict: if media_type == "mp3": ydl_format = "bestaudio/best" merge_format = "mp3" @@ -17,14 +18,15 @@ def build_ydl_opts(video_resolution: int | None, media_type: str) -> dict: "format": ydl_format, "merge_output_format": merge_format, "ffmpeg_location": "/usr/bin/ffmpeg", - "outtmpl": os.path.join(DOWNLOAD_DIR, "%(title)s.%(ext)s"), + "outtmpl": os.path.join(DOWNLOAD_DIR, f"{download_id}.%(title)s.%(ext)s"), "quiet": False, "noplaylist": True, } def get_media(video_url: str, video_resolution: int | None, media_type: str) -> str: - ydl_opts = build_ydl_opts(video_resolution, media_type) + download_id = str(uuid.uuid4()) + ydl_opts = build_ydl_opts(video_resolution, media_type, download_id) try: with yt_dlp.YoutubeDL(ydl_opts) as ydl: # type: ignore[arg-type] info = ydl.extract_info(video_url, download=True) @@ -32,3 +34,18 @@ def get_media(video_url: str, video_resolution: int | None, media_type: str) -> return file_path except (yt_dlp.utils.DownloadError, yt_dlp.utils.ExtractorError): return "error" + + +def get_media_size( + video_url: str, video_resolution: int | None, media_type: str +) -> int: + ydl_opts = build_ydl_opts(video_resolution, media_type, "size_check") + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: # type: ignore[arg-type] + info = ydl.extract_info(video_url, download=False) + if not info: + return 0 + filesize = info.get("filesize") or info.get("filesize_approx") + return filesize if isinstance(filesize, int) else 0 + except yt_dlp.utils.DownloadError: + return 0 diff --git a/src/handlers.py b/src/handlers.py index 5867115..3a7ecb6 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -17,12 +17,28 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def about(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if update.message: - await update.message.reply_text("Ciao informazioni sul progetto") + + user = update.effective_user + + if not user: + await update.message.reply_text("Ciao, utente informazioni sul progetto.") + return + + await update.message.reply_text( + f"Ciao, {user.first_name} informazioni sul progetto." + ) async def service(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if update.message: - await update.message.reply_text('Ciao, {"Stato dei servizi"}!') + + user = update.effective_user + + if not user: + await update.message.reply_text("Ciao, utente stato dei servizi.") + return + + await update.message.reply_text(f"Ciao, {user.first_name} stato dei servizi.") async def download(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: @@ -47,8 +63,14 @@ async def download(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: context.user_data["url"] = url_video + user = update.effective_user + + if not user: + return + await update.message.reply_text( - "Ciao, scarica il tuo contenuto!", reply_markup=get_main_menu() + f"{user.first_name}, scarica il tuo contenuto!", + reply_markup=get_main_menu(), ) diff --git a/tests/test_buttons.py b/tests/test_buttons.py new file mode 100644 index 0000000..95f3e90 --- /dev/null +++ b/tests/test_buttons.py @@ -0,0 +1,353 @@ +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from telegram import InlineKeyboardMarkup +from telegram.error import TelegramError + +from src.buttons import ( + get_main_menu, + get_resolution_video, + handle_buttons, + handle_download, + handle_resolution, +) + + +def test_get_main_menu(): + menu = get_main_menu() + + assert isinstance(menu, InlineKeyboardMarkup) + assert len(menu.inline_keyboard) == 2 + assert menu.inline_keyboard[0][0].text == "🎶 Audio" + assert menu.inline_keyboard[0][1].text == "📹 Video" + assert menu.inline_keyboard[1][0].text == "❌ Annulla" + + +def test_get_resolution_video(): + menu = get_resolution_video() + + assert isinstance(menu, InlineKeyboardMarkup) + assert len(menu.inline_keyboard) == 2 + assert menu.inline_keyboard[0][0].text == "360p" + assert menu.inline_keyboard[0][1].text == "480p" + assert menu.inline_keyboard[0][2].text == "720p" + assert menu.inline_keyboard[1][0].text == "❌ Annulla" + + +@pytest.mark.asyncio +async def test_handle_resolution_annulla(): + update = MagicMock() + context = MagicMock() + + update.callback_query.data = "annulla" + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = {"url": "https://youtube.com/watch?v=xxx"} + + await handle_resolution(update, context) + + assert context.user_data == {} + update.callback_query.edit_message_text.assert_called_once_with( + "Operazione annullata." + ) + + +@pytest.mark.asyncio +async def test_handle_resolution_360(): + update = MagicMock() + context = MagicMock() + + update.callback_query.data = "360" + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = {} + + with patch("src.buttons.handle_download", new_callable=AsyncMock): + await handle_resolution(update, context) + assert context.user_data["video_resolution"] == 360 + + +@pytest.mark.asyncio +async def test_handle_resolution_480(): + update = MagicMock() + context = MagicMock() + + update.callback_query.data = "480" + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = {} + + with patch("src.buttons.handle_download", new_callable=AsyncMock): + await handle_resolution(update, context) + assert context.user_data["video_resolution"] == 480 + + +@pytest.mark.asyncio +async def test_handle_resolution_720(): + update = MagicMock() + context = MagicMock() + + update.callback_query.data = "720" + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = {} + + with patch( + "src.buttons.handle_download", new_callable=AsyncMock + ) as mock_handle_download: + await handle_resolution(update, context) + assert context.user_data["video_resolution"] == 720 + mock_handle_download.assert_called_once_with(update, context) + + +@pytest.mark.asyncio +async def test_handle_resolution_no_query(): + update = MagicMock() + context = MagicMock() + update.callback_query = None + + await handle_resolution(update, context) + + +@pytest.mark.asyncio +async def test_handle_buttons_no_query(): + update = MagicMock() + context = MagicMock() + update.callback_query = None + + await handle_buttons(update, context) + + +@pytest.mark.asyncio +async def test_handle_buttons_annulla(): + update = MagicMock() + context = MagicMock() + + update.callback_query.data = "annulla" + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = {"url": "https://youtube.com/watch?v=xxx"} + + await handle_buttons(update, context) + + assert context.user_data == {} + update.callback_query.edit_message_text.assert_called_once_with( + "Operazione annullata." + ) + + +@pytest.mark.asyncio +async def test_handle_buttons_audio(): + update = MagicMock() + context = MagicMock() + + update.callback_query.data = "audio" + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = {} + + with patch( + "src.buttons.handle_download", new_callable=AsyncMock + ) as mock_handle_download: + await handle_buttons(update, context) + assert context.user_data["download_audio"] + assert not context.user_data["download_video"] + mock_handle_download.assert_called_once_with(update, context) + + +@pytest.mark.asyncio +async def test_handle_buttons_video(): + update = MagicMock() + context = MagicMock() + + update.callback_query.data = "video" + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = {} + + await handle_buttons(update, context) + + assert context.user_data["download_video"] + assert not context.user_data["download_audio"] + update.callback_query.edit_message_text.assert_called_once_with( + "Scaricherai un mp4", reply_markup=get_resolution_video() + ) + + +@pytest.mark.asyncio +async def test_handle_download_no_query(): + update = MagicMock() + context = MagicMock() + update.callback_query = None + + await handle_download(update, context) + + +@pytest.mark.asyncio +async def test_handle_download_no_message(): + update = MagicMock() + context = MagicMock() + update.callback_query.message = None + + await handle_download(update, context) + + +@pytest.mark.asyncio +async def test_handle_download_no_user_data(): + update = MagicMock() + context = MagicMock() + context.user_data = None + + await handle_download(update, context) + + +@pytest.mark.asyncio +async def test_handle_download_no_media_type(): + update = MagicMock() + context = MagicMock() + + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = {"url": "https://youtube.com/watch?v=xxx"} + + await handle_download(update, context) + + +@pytest.mark.asyncio +async def test_handle_download_file_too_large(): + update = MagicMock() + context = MagicMock() + + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = { + "url": "https://youtube.com/watch?v=xxx", + "download_audio": True, + } + + with patch("src.buttons.get_media_size", return_value=100 * 1024 * 1024): + await handle_download(update, context) + update.callback_query.edit_message_text.assert_called_once_with( + "❌ File troppo grande per Telegram (max 50MB)." + ) + + +@pytest.mark.asyncio +async def test_handle_download_audio_success(): + update = MagicMock() + context = MagicMock() + + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = { + "url": "https://youtube.com/watch?v=xxx", + "download_audio": True, + } + context.bot.send_audio = AsyncMock() + + with patch("src.buttons.get_media_size", return_value=10 * 1024 * 1024), patch( + "src.buttons.get_media", return_value="/tmp/audio.mp3" + ), patch("os.path.exists", return_value=True), patch("os.remove"), patch( + "builtins.open", MagicMock() + ): + await handle_download(update, context) + context.bot.send_audio.assert_called_once() + + +@pytest.mark.asyncio +async def test_handle_download_video_success(): + update = MagicMock() + context = MagicMock() + + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = { + "url": "https://youtube.com/watch?v=xxx", + "download_video": True, + "video_resolution": 720, + } + context.bot.send_video = AsyncMock() + + with patch("src.buttons.get_media_size", return_value=10 * 1024 * 1024), patch( + "src.buttons.get_media", return_value="/tmp/video.mp4" + ), patch("os.path.exists", return_value=True), patch("os.remove"), patch( + "builtins.open", MagicMock() + ): + await handle_download(update, context) + context.bot.send_video.assert_called_once() + + +@pytest.mark.asyncio +async def test_handle_download_failed(): + update = MagicMock() + context = MagicMock() + + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = { + "url": "https://youtube.com/watch?v=xxx", + "download_audio": True, + } + context.bot.send_message = AsyncMock() + + with patch("src.buttons.get_media_size", return_value=10 * 1024 * 1024), patch( + "src.buttons.get_media", return_value="error" + ), patch("os.path.exists", return_value=False): + await handle_download(update, context) + context.bot.send_message.assert_called_once_with( + chat_id=update.callback_query.message.chat.id, text="Download fallito." + ) + + +@pytest.mark.asyncio +async def test_handle_download_telegram_error(): + + update = MagicMock() + context = MagicMock() + + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = { + "url": "https://youtube.com/watch?v=xxx", + "download_audio": True, + } + context.bot.send_audio = AsyncMock(side_effect=TelegramError("errore")) + context.bot.send_message = AsyncMock() + + with patch("src.buttons.get_media_size", return_value=10 * 1024 * 1024), patch( + "src.buttons.get_media", return_value="/tmp/audio.mp3" + ), patch("os.path.exists", return_value=True), patch("os.remove"), patch( + "builtins.open", MagicMock() + ): + await handle_download(update, context) + context.bot.send_message.assert_called_once_with( + chat_id=update.callback_query.message.chat.id, text="Errore invio file!" + ) + + +@pytest.mark.asyncio +async def test_handle_download_os_error_on_remove(): + update = MagicMock() + context = MagicMock() + + update.callback_query.answer = AsyncMock() + update.callback_query.edit_message_text = AsyncMock() + context.user_data = { + "url": "https://youtube.com/watch?v=xxx", + "download_audio": True, + } + context.bot.send_audio = AsyncMock() + context.bot.send_message = AsyncMock() + + with patch("src.buttons.get_media_size", return_value=10 * 1024 * 1024), patch( + "src.buttons.get_media", return_value="/tmp/audio.mp3" + ), patch("os.path.exists", return_value=True), patch( + "os.remove", side_effect=OSError("errore") + ), patch( + "builtins.open", MagicMock() + ): + await handle_download(update, context) + context.bot.send_message.assert_called_once_with( + chat_id=update.callback_query.message.chat.id, + text="Errore eliminazione file!", + ) diff --git a/tests/test_downloader.py b/tests/test_downloader.py index 4a738fe..a4fe29f 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -1,58 +1,63 @@ +import os from unittest.mock import MagicMock, patch import yt_dlp -from src.downloader import build_ydl_opts, get_media +from src.downloader import DOWNLOAD_DIR, build_ydl_opts, get_media, get_media_size +# N.B Utilizzo il prefisso di un uid 05adfd95 ... perchè ogni download avrà come prefisso un identificativo univoco. def test_mp3_format(): - opts = build_ydl_opts(720, "mp3") + opts = build_ydl_opts(720, "mp3", "05adfd95 ...") assert opts["format"] == "bestaudio/best" def test_mp3_merge_format(): - opts = build_ydl_opts(720, "mp3") + opts = build_ydl_opts(720, "mp3", "05adfd95 ...") assert opts["merge_output_format"] == "mp3" -def test_mp4_format_contiene_risoluzione(): - opts = build_ydl_opts(1080, "mp4") +def test_mp4_format_contain_resolution(): + opts = build_ydl_opts(1080, "mp4", "05adfd95 ...") assert "1080" in opts["format"] def test_mp4_merge_format(): - opts = build_ydl_opts(1080, "mp4") + opts = build_ydl_opts(1080, "mp4", "05adfd95 ...") assert opts["merge_output_format"] == "mp4" -def test_mp4_risoluzione_720(): - opts = build_ydl_opts(720, "mp4") +def test_mp4_resolution_720(): + opts = build_ydl_opts(720, "mp4", "05adfd95 ...") assert "720" in opts["format"] -def test_mp4_risoluzione_480(): - opts = build_ydl_opts(480, "mp4") +def test_mp4_resolution_480(): + opts = build_ydl_opts(480, "mp4", "05adfd95 ...") assert "480" in opts["format"] -def test_noplaylist_sempre_attivo(): - opts = build_ydl_opts(720, "mp4") +def test_no_playlist_always_active(): + opts = build_ydl_opts(720, "mp4", "05adfd95 ...") assert opts["noplaylist"] is True def test_ffmpeg_path(): - opts = build_ydl_opts(720, "mp4") + opts = build_ydl_opts(720, "mp4", "05adfd95 ...") assert opts["ffmpeg_location"] == "/usr/bin/ffmpeg" -def test_outtmpl_contiene_download_dir(): - opts = build_ydl_opts(720, "mp4") +def test_outtmpl_contains_download_dir(): + opts = build_ydl_opts(720, "mp4", "05adfd95 ...") assert "downloads" in opts["outtmpl"] -def test_outtmpl_contiene_template_titolo(): - opts = build_ydl_opts(720, "mp4") - assert "%(title)s" in opts["outtmpl"] +def test_outtmpl_contains_template_titolo(): + opts = build_ydl_opts(720, "mp4", "05adfd95 ...") + assert ( + os.path.join(DOWNLOAD_DIR, f"{"05adfd95 ..."}.%(title)s.%(ext)s") + in opts["outtmpl"] + ) @patch("src.downloader.yt_dlp.YoutubeDL") @@ -81,9 +86,52 @@ def test_getMedia_extract_info(mock_ydl_class): @patch("src.downloader.yt_dlp.YoutubeDL") def test_getMedia_get_error_on_exception(mock_ydl_class): - mock_ydl_class.return_value.__enter__.side_effect = yt_dlp.utils.DownloadError( - "Network error" + mock_ydl_class.return_value.__enter__.return_value.extract_info.side_effect = ( + yt_dlp.utils.DownloadError("Network error") ) - result = get_media("https://url-non-valido.com", 720, "mp4") + result = get_media("https://luoutube.com/watch?v=xxx", 720, "mp4") assert result == "error" + + +@patch("src.downloader.yt_dlp.YoutubeDL") +def test_get_media_size_returns_zero_on_error(mock_ydl_class): + mock_ydl_class.return_value.__enter__.return_value.extract_info.side_effect = ( + yt_dlp.utils.DownloadError("errore") + ) + + result = get_media_size("https://youtube.com/watch?v=xxx", 720, "mp4") + assert result == 0 + + +@patch("src.downloader.yt_dlp.YoutubeDL") +def test_get_media_size_returns_correct_size(mock_ydl_class): + + fileSize = 1024 * 1024 * 10 + + mock_ydl_class.return_value.__enter__.return_value.extract_info.return_value = { + "filesize": fileSize, + "filesize_approx": None, + } + + result = get_media_size("https://youtube.com/watch?v=xxx", 720, "mp4") + assert result == fileSize + + +@patch("src.downloader.yt_dlp.YoutubeDL") +def test_get_media_size_returns_zero_when_filesize_not_int(mock_ydl_class): + mock_ydl_class.return_value.__enter__.return_value.extract_info.return_value = { + "filesize": None, + "filesize_approx": None, + } + + result = get_media_size("https://youtube.com/watch?v=xxx", 720, "mp4") + assert result == 0 + + +@patch("src.downloader.yt_dlp.YoutubeDL") +def test_get_media_size_returns_zero_when_info_is_none(mock_ydl_class): + mock_ydl_class.return_value.__enter__.return_value.extract_info.return_value = None + + result = get_media_size("https://youtube.com/watch?v=xxx", 720, "mp4") + assert result == 0 diff --git a/tests/test_handlers.py b/tests/test_handlers.py new file mode 100644 index 0000000..f852b42 --- /dev/null +++ b/tests/test_handlers.py @@ -0,0 +1,147 @@ +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from src.buttons import get_main_menu +from src.handlers import about, download, service, start + + +@pytest.mark.asyncio +async def test_start(): + update = MagicMock() + context = MagicMock() + + update.message.reply_text = AsyncMock() + update.effective_user.first_name = "Erika" + + await start(update, context) + + update.message.reply_text.assert_called_once_with( + "Ciao, Erika il tuo compare di fiducia ti aiuta a scaricare i tuoi media!" + ) + + +@pytest.mark.asyncio +async def test_about(): + update = MagicMock() + context = MagicMock() + + update.message.reply_text = AsyncMock() + update.effective_user.first_name = "Erika" + + await about(update, context) + + update.message.reply_text.assert_called_once_with( + "Ciao, Erika informazioni sul progetto." + ) + + +@pytest.mark.asyncio +async def test_service(): + update = MagicMock() + context = MagicMock() + + update.message.reply_text = AsyncMock() + update.effective_user.first_name = "Erika" + + await service(update, context) + + update.message.reply_text.assert_called_once_with("Ciao, Erika stato dei servizi.") + + +@pytest.mark.asyncio +async def test_download_no_message(): + update = MagicMock() + context = MagicMock() + update.message = None + + await download(update, context) + + +@pytest.mark.asyncio +async def test_download_no_url(): + update = MagicMock() + context = MagicMock() + + update.message.reply_text = AsyncMock() + update.message.text = "/download" + + await download(update, context) + + update.message.reply_text.assert_called_once_with( + "Devi fornire un messaggio così formato /download " + ) + + +@pytest.mark.asyncio +async def test_download_invalid_url(): + update = MagicMock() + context = MagicMock() + + update.message.reply_text = AsyncMock() + update.message.text = "/download url-non-valido" + + await download(update, context) + + update.message.reply_text.assert_not_called() + + +@pytest.mark.asyncio +async def test_download_valid_url(): + update = MagicMock() + context = MagicMock() + + update.message.reply_text = AsyncMock() + update.message.text = "/download https://www.youtube.com/watch?v=xxx" + update.effective_user.first_name = "Erika" + context.user_data = {} + + await download(update, context) + + assert context.user_data["url"] == "https://www.youtube.com/watch?v=xxx" + update.message.reply_text.assert_called_once_with( + "Erika, scarica il tuo contenuto!", reply_markup=get_main_menu() + ) + + +@pytest.mark.asyncio +async def test_about_no_user(): + update = MagicMock() + context = MagicMock() + + update.message.reply_text = AsyncMock() + update.effective_user = None + + await about(update, context) + + update.message.reply_text.assert_called_once_with( + "Ciao, utente informazioni sul progetto." + ) + + +@pytest.mark.asyncio +async def test_service_no_user(): + update = MagicMock() + context = MagicMock() + + update.message.reply_text = AsyncMock() + update.effective_user = None + + await service(update, context) + + update.message.reply_text.assert_called_once_with("Ciao, utente stato dei servizi.") + + +@pytest.mark.asyncio +async def test_download_no_user(): + update = MagicMock() + context = MagicMock() + + update.message.reply_text = AsyncMock() + update.message.text = "/download https://www.youtube.com/watch?v=xxx" + update.effective_user = None + context.user_data = {} + + await download(update, context) + + update.message.reply_text.assert_not_called()