Kolejka jest nieusuwalna, a pozycję śledzi indeks, więc przeskoki, usuwanie i zmiana kolejności muszą ten indeks poprawnie przesuwać. To najbardziej podatna na błędy część przeniesionego kodu, więc dostaje testy przypinające zachowanie: - start odtwarzania przy dodaniu do pustej kolejki - limit odtworzeń usuwający utwór i cofający indeks - przechodzenie dalej oraz oba tryby zapętlania - koniec kolejki czyszczący aktualny utwór - przeskok, usunięcie utworu przed indeksem i usunięcie odtwarzanego - zmiana kolejności zachowująca indeks na odtwarzanym utworze - stosowanie głośności serwera i zatrzymanie przy niedziałających utworach 16 przypadków na atrapach Discorda, bez sieci i ffmpega. Uruchamiane przez python -m unittest discover -s tests -t . - bez nowych zależności. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
2.2 KiB
Python
82 lines
2.2 KiB
Python
"""Atrapy pozwalające testować ``MusicPlayer`` bez Discorda i ffmpeg."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from unittest.mock import MagicMock
|
|
|
|
from zyrarbot.music.music_player import MusicPlayer
|
|
|
|
|
|
class FakeTrack:
|
|
"""Odpowiednik ``YTDLSource`` o tym samym interfejsie, ale bez audio."""
|
|
|
|
def __init__(self, track_id: str):
|
|
self.id = track_id
|
|
self.title = track_id
|
|
self.data: dict = {}
|
|
self.url = None
|
|
self.duration = 10
|
|
self.elapsed = 0.0
|
|
self.plays_left: int | None = None
|
|
self.played = False
|
|
self.volume = 0.5
|
|
self.is_local = False
|
|
self.filepath = None
|
|
self.display_url = None
|
|
self.has_web_link = False
|
|
|
|
|
|
class FakeVoiceClient:
|
|
"""Klient głosowy, który zapamiętuje odtwarzane źródło zamiast je grać.
|
|
|
|
Nie wywołuje zwrotnie ``after`` - w testach kolejny utwór uruchamiamy ręcznie,
|
|
tak jak zrobiłaby to biblioteka discord.py po zakończeniu ścieżki.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
self.playing = None
|
|
self.after = None
|
|
self.source = None
|
|
|
|
def is_playing(self) -> bool:
|
|
return self.playing is not None
|
|
|
|
def is_paused(self) -> bool:
|
|
return False
|
|
|
|
def play(self, source, after=None) -> None:
|
|
self.playing = source
|
|
self.source = source
|
|
self.after = after
|
|
|
|
def stop(self) -> None:
|
|
self.playing = None
|
|
|
|
|
|
def build_player(default_plays: int = 5) -> tuple[MusicPlayer, FakeVoiceClient]:
|
|
bot = MagicMock()
|
|
bot.loop = asyncio.get_event_loop()
|
|
voice_client = FakeVoiceClient()
|
|
|
|
async def recreate(track, loop=None):
|
|
fresh = FakeTrack(track.id)
|
|
fresh.plays_left = track.plays_left
|
|
return fresh
|
|
|
|
tracks = MagicMock()
|
|
tracks.recreate = recreate
|
|
|
|
history = MagicMock()
|
|
history.list.return_value = []
|
|
|
|
config = MagicMock()
|
|
config.get_default_plays.return_value = default_plays
|
|
|
|
channels = MagicMock()
|
|
channels.resolve.return_value = None
|
|
|
|
player = MusicPlayer(bot, tracks, history, config, channels)
|
|
player.voice_client = lambda guild_id: voice_client # type: ignore[method-assign]
|
|
return player, voice_client
|