diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f59566e..5fb9e09 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,3 +22,5 @@ jobs: run: pip install -r requirements.txt - name: check-format run: python ./check_format.py + - name: run-tests + run: pytest diff --git a/requirements.txt b/requirements.txt index 10b1df8..de1048a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,9 @@ autopep8 +python-dbusmock jeepney lyriq pycodestyle pylint +pytest setuptools twine diff --git a/spotifycli/spotifycli.py b/spotifycli/spotifycli.py index b427121..1df15d3 100755 --- a/spotifycli/spotifycli.py +++ b/spotifycli/spotifycli.py @@ -135,27 +135,28 @@ def add_arguments(): def get_arguments(): return [ - ("--version", "shows version number"), - ("--status", "shows song name and artist"), + ("--version", "shows version number", False), + ("--status", "shows song name and artist", False), ( "--statusposition", - "shows song name and artist, with current playback position" + "shows song name and artist, with current playback position", + False ), - ("--statusshort", "shows status in a short way"), - ("--song", "shows the song name"), - ("--songshort", "shows the song name in a short way"), - ("--artist", "shows artist name"), - ("--artistshort", "shows artist name in a short way"), - ("--album", "shows album name"), - ("--position", "shows song position"), - ("--arturl", "shows album image url"), - ("--playbackstatus", "shows playback status"), - ("--play", "plays the song"), - ("--pause", "pauses the song"), - ("--playpause", "plays or pauses the song (toggles a state)"), - ("--lyrics", "shows the lyrics for the song"), - ("--next", "plays the next song"), - ("--prev", "plays the previous song"), + ("--statusshort", "shows status in a short way", False), + ("--song", "shows the song name", False), + ("--songshort", "shows the song name in a short way", False), + ("--artist", "shows artist name", False), + ("--artistshort", "shows artist name in a short way", False), + ("--album", "shows album name", False), + ("--position", "shows song position", False), + ("--arturl", "shows album image url", False), + ("--playbackstatus", "shows playback status", False), + ("--play", "plays the song", False), + ("--pause", "pauses the song", False), + ("--playpause", "plays or pauses the song (toggles a state)", False), + ("--lyrics", "shows the lyrics for the song", False), + ("--next", "plays the next song", False), + ("--prev", "plays the previous song", False), ("--songuri", "plays the track at the provided Uri", True), ("--listuri", "plays the playlist at the provided Uri", True), ] @@ -167,7 +168,9 @@ def show_version(): def get_song(): metadata = get_spotify_property("Metadata") - artist = ", ".join(metadata['xesam:artist'][1]) + artist = metadata['xesam:artist'][1] + if isinstance(artist, list): + artist = ", ".join(artist) title = metadata['xesam:title'][1] return artist, title @@ -192,9 +195,9 @@ def show_status_position(): artist, title = get_song() # Both values are in microseconds - position = datetime.timedelta(milliseconds=position_raw / 1000) + position = datetime.timedelta(milliseconds=int(position_raw) / 1000) length = datetime.timedelta( - milliseconds=metadata['mpris:length'][1] / 1000 + milliseconds=int(metadata['mpris:length'][1]) / 1000 ) p_hours, p_minutes, p_seconds = convert_timedelta(position) @@ -350,9 +353,9 @@ def show_position(): metadata = get_spotify_property("Metadata") position_raw = get_spotify_property("Position") # Both values are in microseconds - position = datetime.timedelta(milliseconds=position_raw / 1000) + position = datetime.timedelta(milliseconds=int(position_raw) / 1000) length = datetime.timedelta( - milliseconds=metadata['mpris:length'][1] / 1000 + milliseconds=int(metadata['mpris:length'][1]) / 1000 ) p_hours, p_minutes, p_seconds = convert_timedelta(position) diff --git a/spotifycli/test/__init__.py b/spotifycli/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/spotifycli/test/dbus_metadata_response.py b/spotifycli/test/dbus_metadata_response.py new file mode 100644 index 0000000..25ac971 --- /dev/null +++ b/spotifycli/test/dbus_metadata_response.py @@ -0,0 +1,14 @@ +if args[1] == 'Metadata': + ret = { + 'xesam:artist': ['', 'The Beatles'], + 'xesam:title': ['', 'Yesterday'], + 'xesam:album': ['', 'Help!'], + 'mpris:length': ['', '10000000'], + 'mpris:artUrl': ['', 'https://github.com/pwittchen/spotify-cli-linux'], + } +elif args[1] == 'PlaybackStatus': + ret = 'Playing' +elif args[1] == 'Position': + ret = 1000000 +else: + ret = args diff --git a/spotifycli/test/test_spotifycli.py b/spotifycli/test/test_spotifycli.py new file mode 100644 index 0000000..d84d0a0 --- /dev/null +++ b/spotifycli/test/test_spotifycli.py @@ -0,0 +1,163 @@ +from io import StringIO +from pathlib import Path +from subprocess import PIPE +from unittest import skip +from unittest.mock import patch + +import dbus +import dbusmock + +from spotifycli.spotifycli import main +from spotifycli.version import __version__ + + +class TestSpotifyCLI(dbusmock.DBusTestCase): + @classmethod + def setUpClass(cls): + cls.start_session_bus() + cls.dbus_con = cls.get_dbus(system_bus=False) + + def setUp(self): + self.dbus_spotify_server_mock = self.spawn_server( + "org.mpris.MediaPlayer2.spotify", + "/org/mpris/MediaPlayer2", + "org.freedesktop.DBus.Properties", + system_bus=False, + stdout=PIPE + ) + self.dbus_spotify_interface_mock = dbus.Interface( + self.dbus_con.get_object( + 'org.mpris.MediaPlayer2.spotify', '/org/mpris/MediaPlayer2' + ), + dbusmock.MOCK_IFACE + ) + metadata_file = Path(__file__).resolve().parent / 'dbus_metadata_response.py' + self.dbus_spotify_interface_mock.AddMethod( + '', 'Get', 'ss', 'v', + metadata_file.read_text(), + ) + # TODO: Mock up the controls bus/interface, since I think it is slightly different + # self.dbus_spotify_interface_mock.AddMethod( + # '', 'Play', '', '', '' + # ) + + def tearDown(self): + self.dbus_spotify_server_mock.stdout.close() + self.dbus_spotify_server_mock.terminate() + self.dbus_spotify_server_mock.wait() + + def test_cli_usage(self): + with patch('sys.argv', ["spotifycli", "--help"]), patch("sys.exit") as mock_exit, patch('sys.stdout', new_callable=StringIO) as buffer: + main() + self.assertIn("usage", buffer.getvalue()) + mock_exit.assert_called_with(0) + + def test_cli_version(self): + with patch('sys.argv', ["spotifycli", "--version"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + self.assertIn(__version__, buffer.getvalue()) + + def test_cli_status(self): + with patch('sys.argv', ["spotifycli", "--status"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + self.assertIn("The Beatles - Yesterday", buffer.getvalue()) + + def test_cli_status_position(self): + with patch('sys.argv', ["spotifycli", "--statusposition"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + output = buffer.getvalue() + self.assertIn('00:01', output) + self.assertIn('00:10', output) + + def test_cli_status_short(self): + with patch('sys.argv', ["spotifycli", "--statusshort"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + output = buffer.getvalue() + self.assertIn('Beatles', output) + self.assertIn('Yesterday', output) + + def test_cli_song(self): + with patch('sys.argv', ["spotifycli", "--song"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + self.assertIn("Yesterday", buffer.getvalue()) + + def test_cli_song_short(self): + # TODO: Change to a long song title to check the trimming + with patch('sys.argv', ["spotifycli", "--songshort"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + self.assertIn("Yesterday", buffer.getvalue()) + + def test_cli_artist(self): + with patch('sys.argv', ["spotifycli", "--artist"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + self.assertIn("Beatles", buffer.getvalue()) + + def test_cli_artist_short(self): + # TODO: Change to a long artist name to check the trimming + with patch('sys.argv', ["spotifycli", "--artistshort"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + self.assertIn("Beatles", buffer.getvalue()) + + def test_cli_album(self): + with patch('sys.argv', ["spotifycli", "--album"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + self.assertIn("Help!", buffer.getvalue()) + + def test_cli_position(self): + with patch('sys.argv', ["spotifycli", "--position"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + output = buffer.getvalue() + self.assertIn('00:01', output) + self.assertIn('00:10', output) + + def test_cli_art_url(self): + with patch('sys.argv', ["spotifycli", "--arturl"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + self.assertIn('http', buffer.getvalue()) + + def test_cli_playback_status(self): + with patch('sys.argv', ["spotifycli", "--playbackstatus"]), patch('sys.stdout', new_callable=StringIO) as buffer: + main() + playback_symbols = ['▶', '▮▮', '■'] + output = buffer.getvalue() + self.assertTrue(any([x in output for x in playback_symbols])) + + @skip('Lyrics dont seem to be working right now...is it still valid?') + def test_cli_lyrics(self): + with patch('sys.argv', ["spotifycli", "--lyrics"]): + main() + + @skip('TODO: Need to mock up the spotify_action dbus interface') + def test_cli_play(self): + with patch('sys.argv', ["spotifycli", "--play"]): + main() + + @skip('TODO: Need to mock up the spotify_action dbus interface') + def test_cli_pause(self): + with patch('sys.argv', ["spotifycli", "--pause"]): + main() + + @skip('TODO: Need to mock up the spotify_action dbus interface') + def test_cli_play_pause(self): + with patch('sys.argv', ["spotifycli", "--playpause"]): + main() + + @skip('TODO: Need to mock up the spotify_action dbus interface') + def test_cli_next(self): + with patch('sys.argv', ["spotifycli", "--next"]): + main() + + @skip('TODO: Need to mock up the spotify_action dbus interface') + def test_cli_prev(self): + with patch('sys.argv', ["spotifycli", "--prev"]): + main() + + @skip('TODO: Need to mock up the spotify_action dbus interface') + def test_cli_songuri(self): + with patch('sys.argv', ["spotifycli", "--songuri"]): + main() + + @skip('TODO: Need to mock up the spotify_action dbus interface') + def test_cli_listuri(self): + with patch('sys.argv', ["spotifycli", "--listuri"]): + main()