From d238ca66321bc3b20963c39b76b56c6ee2cc9d44 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 21:32:33 +0200 Subject: [PATCH 01/17] Fix imports in yt.py --- src/yt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yt.py b/src/yt.py index 69d9592..8148bba 100644 --- a/src/yt.py +++ b/src/yt.py @@ -1,3 +1,6 @@ +import asyncio + +import discord import youtube_dl # Suppress noise about console usage from errors From 602a53dde474554b053fdbe3af1908c89d2cd76c Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 21:32:58 +0200 Subject: [PATCH 02/17] Add __pycache__ to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0330071..2fb9ba9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ venv +__pycache__ .env From 085e4459b9405742047db2aecaa91fd0d3afa841 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 21:36:53 +0200 Subject: [PATCH 03/17] Fix pylint issues --- .pylintrc | 22 ++++++++++++++++++++++ src/bot.py | 23 +++++++++++------------ 2 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..6df1145 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,22 @@ +[MASTER] +init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc()) + '/src'); sys.path.append(os.path.dirname(find_pylintrc()) + '/test')" + +[MESSAGES CONTROL] +disable= + missing-module-docstring, + missing-class-docstring, + missing-function-docstring, + global-statement, + too-many-arguments, + too-few-public-methods, + wrong-import-position, + redefined-outer-name, + invalid-name, + no-self-use + +[FORMAT] +indent-string=' ' +max-line-length=160 + +[SIMILARITIES] +ignore-comments = no \ No newline at end of file diff --git a/src/bot.py b/src/bot.py index eddb57c..3108022 100644 --- a/src/bot.py +++ b/src/bot.py @@ -1,5 +1,5 @@ -import asyncio import os +import sys import discord from discord.ext import commands @@ -28,9 +28,9 @@ class Music(commands.Cog): """Plays a file from the local filesystem""" source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(query)) - ctx.voice_client.play(source, after=lambda e: print('Player error: %s' % e) if e else None) + ctx.voice_client.play(source, after=lambda e: print(f"Player error: {e}") if e else None) - await ctx.send('Now playing: {}'.format(query)) + await ctx.send(f"Now playing: {query}") @commands.command() async def yt(self, ctx, *, url): @@ -38,9 +38,9 @@ class Music(commands.Cog): async with ctx.typing(): player = await YTDLSource.from_url(url, loop=self.bot.loop) - ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None) + ctx.voice_client.play(player, after=lambda e: print(f"Player error: {e}") if e else None) - await ctx.send('Now playing: {}'.format(player.title)) + await ctx.send(f"Now playing: {player.title}") @commands.command() async def stream(self, ctx, *, url): @@ -48,9 +48,9 @@ class Music(commands.Cog): async with ctx.typing(): player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) - ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None) + ctx.voice_client.play(player, after=lambda e: print(f"Player error: {e}") if e else None) - await ctx.send('Now playing: {}'.format(player.title)) + await ctx.send(f"Now playing: {player.title}") @commands.command() async def volume(self, ctx, volume: int): @@ -60,7 +60,7 @@ class Music(commands.Cog): return await ctx.send("Not connected to a voice channel.") ctx.voice_client.source.volume = volume / 100 - await ctx.send("Changed volume to {}%".format(volume)) + await ctx.send(f"Changed volume to {volume}%") @commands.command() async def stop(self, ctx): @@ -82,13 +82,12 @@ class Music(commands.Cog): ctx.voice_client.stop() -bot = commands.Bot(command_prefix=commands.when_mentioned_or("!"), - description='Relatively simple music bot example') +bot = commands.Bot(command_prefix=commands.when_mentioned_or("!"), description='Relatively simple music bot example') @bot.event async def on_ready(): - print('Logged in as {0} ({0.id})'.format(bot.user)) + print(f"Logged in as {bot.user} ({bot.user.id})") print('------') bot.add_cog(Music(bot)) @@ -96,6 +95,6 @@ bot.add_cog(Music(bot)) token = os.environ.get("BOT_TOKEN", None) if not token: print("No token fouund in BOT_TOKEN") - exit(1) + sys.exit(1) bot.run(token) From 31b9b1b71773712d83bdc5f00376e40d043bf67a Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 21:37:00 +0200 Subject: [PATCH 04/17] Add .vscode --- .vscode/extensions.json | 3 +++ .vscode/launch.json | 14 ++++++++++++++ .vscode/settings.json | 5 +++++ 3 files changed, 22 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..eea6cb0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["littlefoxteam.vscode-python-test-adapter"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b6a65fa --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "pytest", + "type": "python", + "request": "launch", + "module": "pytest" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e52f158 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.pythonPath": "venv/bin/python3.9", + "python.testing.autoTestDiscoverOnSaveEnabled": true, + "pythonTestExplorer.testFramework": "pytest" +} From 6c37d49bbff7a930d1abe17a226bf632d9a2fcef Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 21:38:48 +0200 Subject: [PATCH 05/17] pip install pytest --- requirements.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/requirements.txt b/requirements.txt index db3ff52..190674b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ aiohttp==3.7.4.post0 +astroid==2.8.0 async-timeout==3.0.1 attrs==21.2.0 autopep8==1.5.7 @@ -7,13 +8,25 @@ chardet==4.0.0 discord==1.7.3 discord.py==1.7.3 idna==3.2 +iniconfig==1.1.1 +isort==5.9.3 +lazy-object-proxy==1.6.0 +mccabe==0.6.1 multidict==5.1.0 +packaging==21.0 +platformdirs==2.3.0 +pluggy==1.0.0 +py==1.10.0 pycodestyle==2.7.0 pycparser==2.20 +pylint==2.11.1 PyNaCl==1.4.0 +pyparsing==2.4.7 +pytest==6.2.5 python-dotenv==0.19.0 six==1.16.0 toml==0.10.2 typing-extensions==3.10.0.2 +wrapt==1.12.1 yarl==1.6.3 youtube-dl==2021.6.6 From c2fefc4362f14d547845d317828085e441c1462e Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 21:38:54 +0200 Subject: [PATCH 06/17] Add .gitlab-ci.yml --- .gitlab-ci.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e86df46 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,32 @@ +stages: + - test-lint-format + +.job_setup: + image: python:3.9 + before_script: + - pip install -r requirements.txt + +pylint: + extends: + - .job_setup + stage: test-lint-format + script: + - pylint src/ test/ + +autopep8: + extends: + - .job_setup + stage: test-lint-format + script: + - autopep8 --recursive --diff src/ test/ + +pytest: + extends: + - .job_setup + stage: test-lint-format + script: + - pytest --cov=src/ + - coverage xml + artifacts: + reports: + cobertura: coverage.xml From a3d440ad75f0c7d5010950196eab8e638f27cc57 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 21:47:33 +0200 Subject: [PATCH 07/17] pip install pytest-cov --- .gitignore | 1 + requirements.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2fb9ba9..f106780 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ venv __pycache__ .env +.coverage diff --git a/requirements.txt b/requirements.txt index 190674b..c93160a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ attrs==21.2.0 autopep8==1.5.7 cffi==1.14.6 chardet==4.0.0 +coverage==5.5 discord==1.7.3 discord.py==1.7.3 idna==3.2 @@ -23,6 +24,7 @@ pylint==2.11.1 PyNaCl==1.4.0 pyparsing==2.4.7 pytest==6.2.5 +pytest-cov==2.12.1 python-dotenv==0.19.0 six==1.16.0 toml==0.10.2 From bf82306cf0b79383146480100d9916a4bbe98f3e Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 21:57:25 +0200 Subject: [PATCH 08/17] Add test folder --- test/conftest.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 test/conftest.py diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..bdfc4e1 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,6 @@ +import sys +from pathlib import Path + +# Add source to PATH such that imports within src modules are properly resolved. +SRC_PATH = str(Path(__file__).parent / '..' / 'src') +sys.path.insert(0, SRC_PATH) From 406556e8cf18f542c2278c5e106c005990a444a9 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 22:03:03 +0200 Subject: [PATCH 09/17] Add if __name__ == "__main__" --- src/bot.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/bot.py b/src/bot.py index 3108022..2959859 100644 --- a/src/bot.py +++ b/src/bot.py @@ -82,19 +82,19 @@ class Music(commands.Cog): ctx.voice_client.stop() -bot = commands.Bot(command_prefix=commands.when_mentioned_or("!"), description='Relatively simple music bot example') +if __name__ == "__main__": + bot = commands.Bot(command_prefix=commands.when_mentioned_or("!"), description='Relatively simple music bot example') + @bot.event + async def on_ready(): + print(f"Logged in as {bot.user} ({bot.user.id})") + print('------') -@bot.event -async def on_ready(): - print(f"Logged in as {bot.user} ({bot.user.id})") - print('------') + bot.add_cog(Music(bot)) -bot.add_cog(Music(bot)) + token = os.environ.get("BOT_TOKEN", None) + if not token: + print("No token fouund in BOT_TOKEN") + sys.exit(1) -token = os.environ.get("BOT_TOKEN", None) -if not token: - print("No token fouund in BOT_TOKEN") - sys.exit(1) - -bot.run(token) + bot.run(token) From 737919557a38e42d632e5b9895338e5404100af2 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 22:03:28 +0200 Subject: [PATCH 10/17] Fix typo --- src/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot.py b/src/bot.py index 2959859..d1b16d0 100644 --- a/src/bot.py +++ b/src/bot.py @@ -94,7 +94,7 @@ if __name__ == "__main__": token = os.environ.get("BOT_TOKEN", None) if not token: - print("No token fouund in BOT_TOKEN") + print("No token found in BOT_TOKEN") sys.exit(1) bot.run(token) From d88ab63c2bc4755a9ce137d9d8ab6dda5f876e4a Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 22:15:45 +0200 Subject: [PATCH 11/17] Remove unnecessary code --- src/bot.py | 44 +------------------------------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/src/bot.py b/src/bot.py index d1b16d0..826ab6b 100644 --- a/src/bot.py +++ b/src/bot.py @@ -1,7 +1,6 @@ import os import sys -import discord from discord.ext import commands from dotenv import load_dotenv @@ -14,37 +13,9 @@ class Music(commands.Cog): def __init__(self, bot): self.bot = bot - @commands.command() - async def join(self, ctx, *, channel: discord.VoiceChannel): - """Joins a voice channel""" - - if ctx.voice_client is not None: - return await ctx.voice_client.move_to(channel) - - await channel.connect() - - @commands.command() - async def play(self, ctx, *, query): - """Plays a file from the local filesystem""" - - source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(query)) - ctx.voice_client.play(source, after=lambda e: print(f"Player error: {e}") if e else None) - - await ctx.send(f"Now playing: {query}") - - @commands.command() - async def yt(self, ctx, *, url): - """Plays from a url (almost anything youtube_dl supports)""" - - async with ctx.typing(): - player = await YTDLSource.from_url(url, loop=self.bot.loop) - ctx.voice_client.play(player, after=lambda e: print(f"Player error: {e}") if e else None) - - await ctx.send(f"Now playing: {player.title}") - @commands.command() async def stream(self, ctx, *, url): - """Streams from a url (same as yt, but doesn't predownload)""" + """Streams from a url""" async with ctx.typing(): player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) @@ -52,24 +23,11 @@ class Music(commands.Cog): await ctx.send(f"Now playing: {player.title}") - @commands.command() - async def volume(self, ctx, volume: int): - """Changes the player's volume""" - - if ctx.voice_client is None: - return await ctx.send("Not connected to a voice channel.") - - ctx.voice_client.source.volume = volume / 100 - await ctx.send(f"Changed volume to {volume}%") - @commands.command() async def stop(self, ctx): """Stops and disconnects the bot from voice""" - await ctx.voice_client.disconnect() - @play.before_invoke - @yt.before_invoke @stream.before_invoke async def ensure_voice(self, ctx): if ctx.voice_client is None: From 65ddea7f2769081d9bc6bd5d0575dc964b451a6f Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 22:26:49 +0200 Subject: [PATCH 12/17] Add ErrorHandler cog --- src/bot.py | 2 ++ src/error.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/error.py diff --git a/src/bot.py b/src/bot.py index 826ab6b..927ac97 100644 --- a/src/bot.py +++ b/src/bot.py @@ -5,6 +5,7 @@ from discord.ext import commands from dotenv import load_dotenv from yt import YTDLSource +from error import ErrorHandler load_dotenv() @@ -49,6 +50,7 @@ if __name__ == "__main__": print('------') bot.add_cog(Music(bot)) + bot.add_cog(ErrorHandler(bot)) token = os.environ.get("BOT_TOKEN", None) if not token: diff --git a/src/error.py b/src/error.py new file mode 100644 index 0000000..553dfae --- /dev/null +++ b/src/error.py @@ -0,0 +1,25 @@ +from discord.ext import commands + + +class ErrorHandler(commands.Cog): + """A cog for global error handling.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.Cog.listener() + async def on_command_error(self, ctx: commands.Context, error: commands.CommandError): + if isinstance(error, commands.CommandNotFound): + return + elif isinstance(error, commands.MissingPermissions): + message = "You are missing the required permissions to run this command!" + elif isinstance(error, commands.UserInputError): + message = "Something about your input was wrong, please check your input and try again!" + else: + message = "Oh no! Something went wrong while running the command!" + + await ctx.send(message) + + +def setup(bot: commands.Bot): + bot.add_cog(ErrorHandler(bot)) From 273eb21d2c8710ffa9d8b5749329a0479c2c3ca8 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 22:33:27 +0200 Subject: [PATCH 13/17] Fix unnecesssary elif after return --- src/error.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.py b/src/error.py index 553dfae..2b4b457 100644 --- a/src/error.py +++ b/src/error.py @@ -11,7 +11,7 @@ class ErrorHandler(commands.Cog): async def on_command_error(self, ctx: commands.Context, error: commands.CommandError): if isinstance(error, commands.CommandNotFound): return - elif isinstance(error, commands.MissingPermissions): + if isinstance(error, commands.MissingPermissions): message = "You are missing the required permissions to run this command!" elif isinstance(error, commands.UserInputError): message = "Something about your input was wrong, please check your input and try again!" From 98ecfaa875bea5b4389a62875bf8f4f678b12f6b Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 22:34:44 +0200 Subject: [PATCH 14/17] Add placeholder test to make CI happy --- test/test_bot.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 test/test_bot.py diff --git a/test/test_bot.py b/test/test_bot.py new file mode 100644 index 0000000..8433e13 --- /dev/null +++ b/test/test_bot.py @@ -0,0 +1,3 @@ +def test_bot(): + # placeholder test + assert 1 + 1 == 2 From e036835bf4e7ac495f5b84ac6c07fc5942a21216 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 22:36:25 +0200 Subject: [PATCH 15/17] Remove unnecessary comments --- src/bot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/bot.py b/src/bot.py index 927ac97..0a52a8e 100644 --- a/src/bot.py +++ b/src/bot.py @@ -16,8 +16,6 @@ class Music(commands.Cog): @commands.command() async def stream(self, ctx, *, url): - """Streams from a url""" - async with ctx.typing(): player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) ctx.voice_client.play(player, after=lambda e: print(f"Player error: {e}") if e else None) @@ -26,7 +24,6 @@ class Music(commands.Cog): @commands.command() async def stop(self, ctx): - """Stops and disconnects the bot from voice""" await ctx.voice_client.disconnect() @stream.before_invoke From d8dfc7119ee3a9475a18fa297ddf86d02ce784b2 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 23:07:32 +0200 Subject: [PATCH 16/17] Add test for ensure_voice --- requirements.txt | 1 + src/bot.py | 1 - test/conftest.py | 13 +++++++++++++ test/test_bot.py | 41 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index c93160a..04e10c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,6 +25,7 @@ PyNaCl==1.4.0 pyparsing==2.4.7 pytest==6.2.5 pytest-cov==2.12.1 +pytest-mock==3.6.1 python-dotenv==0.19.0 six==1.16.0 toml==0.10.2 diff --git a/src/bot.py b/src/bot.py index 0a52a8e..9964019 100644 --- a/src/bot.py +++ b/src/bot.py @@ -32,7 +32,6 @@ class Music(commands.Cog): if ctx.author.voice: await ctx.author.voice.channel.connect() else: - await ctx.send("You are not connected to a voice channel.") raise commands.CommandError("Author not connected to a voice channel.") elif ctx.voice_client.is_playing(): ctx.voice_client.stop() diff --git a/test/conftest.py b/test/conftest.py index bdfc4e1..0a6029a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,6 +1,19 @@ import sys from pathlib import Path +import pytest +from pytest_mock import MockerFixture + # Add source to PATH such that imports within src modules are properly resolved. SRC_PATH = str(Path(__file__).parent / '..' / 'src') sys.path.insert(0, SRC_PATH) + + +@pytest.fixture +def bot(mocker: MockerFixture): + yield mocker.patch('discord.ext.commands.Bot', autospec=True) + + +@pytest.fixture +def ctx(mocker: MockerFixture): + yield mocker.patch('discord.ext.commands.Context', autospec=True) diff --git a/test/test_bot.py b/test/test_bot.py index 8433e13..8c0ca09 100644 --- a/test/test_bot.py +++ b/test/test_bot.py @@ -1,3 +1,38 @@ -def test_bot(): - # placeholder test - assert 1 + 1 == 2 +from unittest.mock import AsyncMock + +import pytest +from discord.ext import commands + +from bot import Music + + +@pytest.mark.asyncio +async def test_bot_ensure_voice(bot, ctx): + mbot = Music(bot) + + # 1. Inside a voice channel + # 1.1 Does not call stop if no sound is playing + ctx.voice_client.is_playing.return_value = False + await mbot.ensure_voice(ctx) + assert ctx.voice_client.stop.call_count == 0 + ctx.reset_mock(return_value=True) + + # 1.2 Does call stop if sound is playing + ctx.voice_client.is_playing.return_value = True + await mbot.ensure_voice(ctx) + assert ctx.voice_client.stop.call_count == 1 + ctx.reset_mock(return_value=True) + + # 2. Not inside a voice channel + # 2.1 Connects to voice channel of author if possible + ctx.voice_client = None + ctx.author.voice = AsyncMock() + await mbot.ensure_voice(ctx) + assert ctx.author.voice.channel.connect.call_count == 1 + ctx.reset_mock(return_value=True) + + # 2.2 Error if author not inside a channel + ctx.voice_client = None + ctx.author.voice = None + with pytest.raises(commands.CommandError): + await mbot.ensure_voice(ctx) From ecdbc2fa4582bc26bcfb9da4db1f1d2a7c4fc3f2 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 24 Sep 2021 23:37:46 +0200 Subject: [PATCH 17/17] Add test for stream --- test/conftest.py | 5 ++++- test/test_bot.py | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 0a6029a..1fa415c 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,5 +1,6 @@ import sys from pathlib import Path +from unittest.mock import Mock import pytest from pytest_mock import MockerFixture @@ -11,7 +12,9 @@ sys.path.insert(0, SRC_PATH) @pytest.fixture def bot(mocker: MockerFixture): - yield mocker.patch('discord.ext.commands.Bot', autospec=True) + bot_mock = mocker.patch('discord.ext.commands.Bot', autospec=True) + bot_mock.loop = Mock() + yield bot_mock @pytest.fixture diff --git a/test/test_bot.py b/test/test_bot.py index 8c0ca09..1bbe70d 100644 --- a/test/test_bot.py +++ b/test/test_bot.py @@ -1,4 +1,4 @@ -from unittest.mock import AsyncMock +from unittest.mock import Mock, AsyncMock, patch import pytest from discord.ext import commands @@ -36,3 +36,19 @@ async def test_bot_ensure_voice(bot, ctx): ctx.author.voice = None with pytest.raises(commands.CommandError): await mbot.ensure_voice(ctx) + + +@pytest.mark.asyncio +async def test_bot_stream(bot, ctx): + mbot = Music(bot) + + with patch('bot.YTDLSource', new_callable=AsyncMock) as ytdl_source: + player = Mock() + ytdl_source.from_url.return_value = player + url = 'A Day To Remember - All I Want' + # pylint: disable=too-many-function-args + await mbot.stream(mbot, ctx, url=url) + assert ytdl_source.from_url.await_args.args == (url,) + assert ytdl_source.from_url.await_args.kwargs == {"loop": bot.loop, "stream": True} + assert ctx.voice_client.play.call_args.args == (player,) + assert ctx.send.call_count == 1