Merge branch '9-skip' into 'develop'

Resolve "Implement !skip command"

Closes #9

See merge request ekzyis/musicube!8
This commit is contained in:
Ramdip Gill 2021-09-25 19:10:30 +00:00
commit 9c32b3d8a0
4 changed files with 80 additions and 7 deletions

7
.vscode/launch.json vendored
View File

@ -4,6 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "bot",
"type": "python",
"request": "launch",
"program": "src/bot.py",
"console": "integratedTerminal"
},
{ {
"name": "pytest", "name": "pytest",
"type": "python", "type": "python",

View File

@ -76,6 +76,13 @@ class Music(commands.Cog):
if ctx.voice_client.is_playing(): if ctx.voice_client.is_playing():
await ctx.send(f"Queued: {title}") await ctx.send(f"Queued: {title}")
@commands.command()
async def skip(self, ctx):
async with ctx.typing():
if ctx.voice_client is None or not ctx.voice_client.is_playing():
raise commands.CommandError("Cannot skip: No song playing")
ctx.voice_client.stop()
@commands.command() @commands.command()
async def stop(self, ctx): async def stop(self, ctx):
await ctx.voice_client.disconnect() await ctx.voice_client.disconnect()

View File

@ -29,7 +29,9 @@ def mbot(bot):
@pytest.fixture @pytest.fixture
def ctx(mocker: MockerFixture): def ctx(mocker: MockerFixture):
yield mocker.patch('discord.ext.commands.Context', autospec=True) ctx_mock = mocker.patch('discord.ext.commands.Context', autospec=True)
ctx_mock.voice_client.stop = lambda: ctx_mock.voice_client.play.call_args.kwargs["after"](None)
return ctx_mock
@pytest.fixture @pytest.fixture

View File

@ -7,14 +7,14 @@ from discord.ext import commands
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_bot_ensure_voice(mbot, ctx): async def test_bot_ensure_voice(mbot, ctx):
# Connects to voice channel of author if possible # TEST: Connects to voice channel of author if possible
ctx.voice_client = None ctx.voice_client = None
ctx.author.voice = AsyncMock() ctx.author.voice = AsyncMock()
await mbot.ensure_voice(ctx) await mbot.ensure_voice(ctx)
assert ctx.author.voice.channel.connect.call_count == 1, "Did not connect to voice channel of author" assert ctx.author.voice.channel.connect.call_count == 1, "Did not connect to voice channel of author"
ctx.reset_mock(return_value=True) ctx.reset_mock(return_value=True)
# Error if author not inside a channel # TEST: Error if author not inside a channel
ctx.voice_client = None ctx.voice_client = None
ctx.author.voice = None ctx.author.voice = None
with pytest.raises(commands.CommandError): with pytest.raises(commands.CommandError):
@ -35,6 +35,8 @@ def mock_ffmpeg_pcm_audio(ffmpeg_pcm_audio):
async def test_bot_playback(mbot, ctx): async def test_bot_playback(mbot, ctx):
with patch.object(mbot, '_ytdl') as ytdl: with patch.object(mbot, '_ytdl') as ytdl:
with patch('discord.FFmpegPCMAudio') as ffmpeg_pcm_audio: with patch('discord.FFmpegPCMAudio') as ffmpeg_pcm_audio:
# TEST: First song queued is immediately played
ctx.voice_client.is_playing.return_value = False ctx.voice_client.is_playing.return_value = False
url = "https://www.youtube.com/watch?v=Wr9LZ1hAFpQ" url = "https://www.youtube.com/watch?v=Wr9LZ1hAFpQ"
title = "In Flames - Deliver Us (Official Video)" title = "In Flames - Deliver Us (Official Video)"
@ -66,6 +68,7 @@ async def test_bot_playback(mbot, ctx):
ctx.send.call_args.args == (f"Now playing: {title}",), \ ctx.send.call_args.args == (f"Now playing: {title}",), \
"Did not send 'Now playing:' message" "Did not send 'Now playing:' message"
# TEST: Following songs are put inside a queue
ctx.voice_client.is_playing.return_value = True ctx.voice_client.is_playing.return_value = True
url = "https://www.youtube.com/watch?v=pMDcYX2wRSg" url = "https://www.youtube.com/watch?v=pMDcYX2wRSg"
title = "Three Days Grace - Time of Dying (lyrics)" title = "Three Days Grace - Time of Dying (lyrics)"
@ -87,12 +90,66 @@ async def test_bot_playback(mbot, ctx):
ctx.send.call_args.args == (f"Queued: {title}",), \ ctx.send.call_args.args == (f"Queued: {title}",), \
"Did not send 'Queued:' message" "Did not send 'Queued:' message"
await asyncio.sleep(0) await asyncio.sleep(0)
# Still no playback because previous song not finished # Assert that there is still no playback because previous song is not finished yet
assert \ assert \
not ffmpeg_pcm_audio.call_args.args == (url,), \ not ffmpeg_pcm_audio.call_args.args == (url,), \
f"FFmpegPCMAudio was called with {url} before previous song finished" f"FFmpegPCMAudio was called with {url} before previous song finished"
ctx.voice_client.play.call_args.kwargs["after"](None) # Execute callback for song finish event # Execute callback for song finish event
ctx.voice_client.play.call_args.kwargs["after"](None)
await asyncio.sleep(0) await asyncio.sleep(0)
assert \ assert \
ffmpeg_pcm_audio.call_args.args == (url,), \ ctx.voice_client.play.call_args.args == (time_of_dying_audio,), \
f"FFmpegPCMAudio was not called with {url}" "Did not queue next song"
@pytest.mark.asyncio
async def test_bot_skip(mbot, ctx):
with patch.object(mbot, '_ytdl') as ytdl:
with patch('discord.FFmpegPCMAudio') as ffmpeg_pcm_audio:
# TEST: One can skip songs to immediately play the next song in queue
# Queue first song
ctx.voice_client.is_playing.return_value = False
url = "https://www.youtube.com/watch?v=Wr9LZ1hAFpQ"
title = "In Flames - Deliver Us (Official Video)"
mock_ytdl_extract_info(ytdl, url, title)
deliver_us_audio = mock_ffmpeg_pcm_audio(ffmpeg_pcm_audio)
query = 'in flames deliver us'
# pylint: disable=too-many-function-args
await mbot.play(mbot, ctx, query=query)
await asyncio.sleep(0)
assert \
ctx.voice_client.play.call_args.args == (deliver_us_audio,), \
"Did not playback correct audio"
# Queue second song
ctx.voice_client.is_playing.return_value = True
url = "https://www.youtube.com/watch?v=pMDcYX2wRSg"
title = "Three Days Grace - Time of Dying (lyrics)"
mock_ytdl_extract_info(ytdl, url, title)
time_of_dying_audio = mock_ffmpeg_pcm_audio(ffmpeg_pcm_audio)
# pylint: disable=too-many-function-args
query = "three days grace time of dying"
await mbot.play(mbot, ctx, query=query)
await asyncio.sleep(0)
assert \
not ctx.voice_client.play.call_args.args == (time_of_dying_audio,), \
"Did immediately playback audio instead of being queued"
# Now skip first song
await mbot.skip(mbot, ctx)
await asyncio.sleep(0)
assert \
ctx.voice_client.play.call_args.args == (time_of_dying_audio,), \
"Did not skip song"
# TEST: Error if no song playing
ctx.voice_client.is_playing.return_value = False
with pytest.raises(commands.CommandError):
await mbot.skip(mbot, ctx)
# TEST: Error if no voice client
ctx.voice_client = None
with pytest.raises(commands.CommandError):
await mbot.skip(mbot, ctx)