256 lines
8.5 KiB
Python
256 lines
8.5 KiB
Python
"""Unit tests for MpvMediaPlayer."""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def make_player(device=None):
|
|
"""Return an MpvMediaPlayer with the internal LibMpvPlayer mocked out."""
|
|
from linux_voice_assistant.player.state import PlayerState
|
|
|
|
mock_lib_player = MagicMock()
|
|
mock_lib_player.state.return_value = PlayerState.IDLE
|
|
|
|
with patch("linux_voice_assistant.mpv_player.LibMpvPlayer", return_value=mock_lib_player):
|
|
from linux_voice_assistant.mpv_player import MpvMediaPlayer
|
|
|
|
player = MpvMediaPlayer(device=device)
|
|
player._mock = mock_lib_player
|
|
return player
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Initialization
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestInit:
|
|
def test_player_initialized_with_device(self):
|
|
with patch("linux_voice_assistant.mpv_player.LibMpvPlayer") as mock_cls:
|
|
mock_cls.return_value = MagicMock()
|
|
from linux_voice_assistant.mpv_player import MpvMediaPlayer
|
|
|
|
MpvMediaPlayer(device="hw:1,0")
|
|
mock_cls.assert_called_once_with(device="hw:1,0")
|
|
|
|
def test_player_initialized_with_none_device(self):
|
|
with patch("linux_voice_assistant.mpv_player.LibMpvPlayer") as mock_cls:
|
|
mock_cls.return_value = MagicMock()
|
|
from linux_voice_assistant.mpv_player import MpvMediaPlayer
|
|
|
|
MpvMediaPlayer(device=None)
|
|
mock_cls.assert_called_once_with(device=None)
|
|
|
|
def test_done_callback_starts_none(self):
|
|
player = make_player()
|
|
assert player._done_callback is None
|
|
|
|
def test_playlist_starts_empty(self):
|
|
player = make_player()
|
|
assert player._playlist == []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# play()
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestPlay:
|
|
def test_play_single_url_calls_lib_player(self):
|
|
player = make_player()
|
|
player.play("http://example.com/audio.mp3")
|
|
player._mock.play.assert_called_once()
|
|
|
|
def test_play_single_url_passes_correct_url(self):
|
|
player = make_player()
|
|
player.play("http://example.com/audio.mp3")
|
|
args, kwargs = player._mock.play.call_args
|
|
assert args[0] == "http://example.com/audio.mp3"
|
|
|
|
def test_play_list_plays_first_url(self):
|
|
player = make_player()
|
|
player.play(["http://a.com/1.mp3", "http://a.com/2.mp3"])
|
|
args, kwargs = player._mock.play.call_args
|
|
assert args[0] == "http://a.com/1.mp3"
|
|
|
|
def test_play_list_stores_remaining_in_playlist(self):
|
|
player = make_player()
|
|
player.play(["http://a.com/1.mp3", "http://a.com/2.mp3", "http://a.com/3.mp3"])
|
|
assert player._playlist == ["http://a.com/2.mp3", "http://a.com/3.mp3"]
|
|
|
|
def test_play_single_url_playlist_is_empty(self):
|
|
player = make_player()
|
|
player.play("http://example.com/audio.mp3")
|
|
assert player._playlist == []
|
|
|
|
def test_play_stores_done_callback(self):
|
|
player = make_player()
|
|
cb = MagicMock()
|
|
player.play("http://example.com/audio.mp3", done_callback=cb)
|
|
assert player._done_callback is cb
|
|
|
|
def test_play_empty_list_does_not_call_lib_player(self):
|
|
player = make_player()
|
|
player.play([])
|
|
player._mock.play.assert_not_called()
|
|
|
|
def test_play_while_active_stops_previous(self):
|
|
from linux_voice_assistant.player.state import PlayerState
|
|
|
|
player = make_player()
|
|
player._done_callback = MagicMock() # simulate active playback
|
|
player._mock.state.return_value = PlayerState.PLAYING
|
|
|
|
player.play("http://example.com/new.mp3")
|
|
player._mock.stop.assert_called_once()
|
|
|
|
def test_play_while_idle_does_not_stop(self):
|
|
from linux_voice_assistant.player.state import PlayerState
|
|
|
|
player = make_player()
|
|
player._mock.state.return_value = PlayerState.IDLE
|
|
player._done_callback = None
|
|
|
|
player.play("http://example.com/new.mp3")
|
|
player._mock.stop.assert_not_called()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _on_track_finished()
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOnTrackFinished:
|
|
def test_plays_next_url_when_playlist_has_items(self):
|
|
player = make_player()
|
|
player._playlist = ["http://a.com/2.mp3"]
|
|
player._on_track_finished()
|
|
args, _ = player._mock.play.call_args
|
|
assert args[0] == "http://a.com/2.mp3"
|
|
|
|
def test_invokes_done_callback_when_playlist_empty(self):
|
|
player = make_player()
|
|
cb = MagicMock()
|
|
player._done_callback = cb
|
|
player._playlist = []
|
|
player._on_track_finished()
|
|
cb.assert_called_once()
|
|
|
|
def test_clears_done_callback_after_invoking(self):
|
|
player = make_player()
|
|
player._done_callback = MagicMock()
|
|
player._playlist = []
|
|
player._on_track_finished()
|
|
assert player._done_callback is None
|
|
|
|
def test_no_error_when_done_callback_is_none(self):
|
|
player = make_player()
|
|
player._done_callback = None
|
|
player._playlist = []
|
|
player._on_track_finished() # should not raise
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# pause() / resume() / stop()
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestPauseResumeStop:
|
|
def test_pause_delegates_to_lib_player(self):
|
|
player = make_player()
|
|
player.pause()
|
|
player._mock.pause.assert_called_once()
|
|
|
|
def test_resume_delegates_to_lib_player(self):
|
|
player = make_player()
|
|
player.resume()
|
|
player._mock.resume.assert_called_once()
|
|
|
|
def test_stop_delegates_to_lib_player(self):
|
|
player = make_player()
|
|
player.stop()
|
|
player._mock.stop.assert_called_once()
|
|
|
|
def test_stop_invokes_done_callback(self):
|
|
player = make_player()
|
|
cb = MagicMock()
|
|
player._done_callback = cb
|
|
player.stop()
|
|
cb.assert_called_once()
|
|
|
|
def test_stop_clears_done_callback(self):
|
|
player = make_player()
|
|
player._done_callback = MagicMock()
|
|
player.stop()
|
|
assert player._done_callback is None
|
|
|
|
def test_stop_no_error_when_no_callback(self):
|
|
player = make_player()
|
|
player._done_callback = None
|
|
player.stop() # should not raise
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# is_playing
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestIsPlaying:
|
|
def test_true_when_playing(self):
|
|
from linux_voice_assistant.player.state import PlayerState
|
|
|
|
player = make_player()
|
|
player._mock.state.return_value = PlayerState.PLAYING
|
|
assert player.is_playing is True
|
|
|
|
def test_true_when_paused(self):
|
|
from linux_voice_assistant.player.state import PlayerState
|
|
|
|
player = make_player()
|
|
player._mock.state.return_value = PlayerState.PAUSED
|
|
assert player.is_playing is True
|
|
|
|
def test_true_when_loading(self):
|
|
from linux_voice_assistant.player.state import PlayerState
|
|
|
|
player = make_player()
|
|
player._mock.state.return_value = PlayerState.LOADING
|
|
assert player.is_playing is True
|
|
|
|
def test_false_when_idle(self):
|
|
from linux_voice_assistant.player.state import PlayerState
|
|
|
|
player = make_player()
|
|
player._mock.state.return_value = PlayerState.IDLE
|
|
assert player.is_playing is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# set_volume() / duck() / unduck()
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestVolume:
|
|
def test_set_volume_delegates_to_lib_player(self):
|
|
player = make_player()
|
|
player.set_volume(75.0)
|
|
player._mock.set_volume.assert_called_once_with(75.0)
|
|
|
|
def test_duck_delegates_to_lib_player(self):
|
|
player = make_player()
|
|
player.duck(factor=0.3)
|
|
player._mock.duck.assert_called_once_with(0.3)
|
|
|
|
def test_duck_default_factor(self):
|
|
player = make_player()
|
|
player.duck()
|
|
player._mock.duck.assert_called_once_with(0.5)
|
|
|
|
def test_unduck_delegates_to_lib_player(self):
|
|
player = make_player()
|
|
player.unduck()
|
|
player._mock.unduck.assert_called_once()
|