From 4d295fb94b89599154c205e834ed82ad9604672c Mon Sep 17 00:00:00 2001 From: Kutu Date: Sat, 13 Jan 2024 02:10:56 +0100 Subject: [PATCH] Add add and remove method with property syncthing --- src/knuckles/models/playlist.py | 55 +++++++++++++++++++++++++++++++++ tests/conftest.py | 43 +++++++++++++++++++++++++- tests/mocks/playlists.py | 27 ++++++++++++++++ tests/models/test_playlist.py | 42 +++++++++++++++++++++++++ tests/subsonic.py | 43 -------------------------- 5 files changed, 166 insertions(+), 44 deletions(-) delete mode 100644 tests/subsonic.py diff --git a/src/knuckles/models/playlist.py b/src/knuckles/models/playlist.py index e309dc4..05fa1bb 100644 --- a/src/knuckles/models/playlist.py +++ b/src/knuckles/models/playlist.py @@ -120,6 +120,9 @@ def update(self) -> Self: Updates the name, comment and public state of the playlist with the ones in the parameters of the object. + NOT the playlist list, please use TODO (add_songs) + and TODO (remove_songs) to reflect it in the model. + :return: The object itself to allow method chaining. :rtype: Self """ @@ -140,3 +143,55 @@ def delete(self) -> Self: self.__subsonic.playlists.delete_playlist(self.id) return self + + def add_songs(self, song_ids: list[str]) -> Self: + """Add any number of new songs to the playlist + It's reflected in the songs list in the model. + + :param song_ids: A list with the IDs of the songs to add. + + :return: The object itself to allow method chaining. + :rtype: Self + """ + + self.__subsonic.playlists.update_playlist(self.id, song_ids_to_add=song_ids) + + if not self.songs: + self.songs = [] + + for id_ in song_ids: + self.songs.append(Song(self.__subsonic, id_)) + + if not self.song_count: + self.song_count = 0 + + self.song_count += len(song_ids) + + return self + + def remove_songs(self, songs_indexes: list[int]) -> Self: + """Remove any number of new songs to the playlist + It's reflected in the songs list in the model. + + :param song_indexes: A list with the indexes of the songs to remove. + + :return: The object itself to allow method chaining. + :rtype: Self + """ + + self.__subsonic.playlists.update_playlist( + self.id, song_indexes_to_remove=songs_indexes + ) + + if not self.songs: + self.songs = [] + + for index in songs_indexes: + del self.songs[index] + + if not self.song_count: + self.song_count = 0 + + self.song_count -= len(songs_indexes) + + return self diff --git a/tests/conftest.py b/tests/conftest.py index 7e17146..7c5c1b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,11 @@ from typing import Any, Protocol +import knuckles import pytest +from knuckles.subsonic import Subsonic from responses import GET, Response, matchers pytest_plugins = [ - "tests.subsonic", "tests.mocks.system", "tests.mocks.chat", "tests.mocks.jukebox_control", @@ -22,6 +23,46 @@ ] +@pytest.fixture +def username() -> str: + return "user" + + +@pytest.fixture +def password() -> str: + return "password" + + +@pytest.fixture +def client() -> str: + return "client" + + +@pytest.fixture +def base_url() -> str: + return "https://example.com" + + +@pytest.fixture +def subsonic(base_url: str, username: str, password: str, client: str) -> Subsonic: + return knuckles.Subsonic( + url=base_url, + user=username, + password=password, + client=client, + ) + + +@pytest.fixture +def params(username: str, client: str) -> dict[str, str]: + return { + "u": username, + "v": "1.16.1", + "c": client, + "f": "json", + } + + @pytest.fixture def subsonic_response() -> dict[str, Any]: return { diff --git a/tests/mocks/playlists.py b/tests/mocks/playlists.py index 19dc785..9ebd372 100644 --- a/tests/mocks/playlists.py +++ b/tests/mocks/playlists.py @@ -99,3 +99,30 @@ def mock_delete_playlist( mock_generator: MockGenerator, playlist: dict[str, Any] ) -> Response: return mock_generator("deletePlaylist", {"id": playlist["id"]}) + + +@pytest.fixture +def mock_add_song_to_playlist( + mock_generator: MockGenerator, + playlist: dict[str, Any], + song: dict[str, Any], +) -> Response: + return mock_generator( + "updatePlaylist", + {"playlistId": playlist["id"], "songIdToAdd": song["id"]}, + ) + + +@pytest.fixture +def mock_remove_song_to_playlist( + mock_generator: MockGenerator, + playlist: dict[str, Any], + song: dict[str, Any], +) -> Response: + return mock_generator( + "updatePlaylist", + { + "playlistId": playlist["id"], + "songIndexToRemove": 0, + }, + ) diff --git a/tests/models/test_playlist.py b/tests/models/test_playlist.py index 7f453d7..16a4e8c 100644 --- a/tests/models/test_playlist.py +++ b/tests/models/test_playlist.py @@ -70,3 +70,45 @@ def test_delete( response = response.delete() assert type(response) is Playlist + + +@responses.activate +def test_add( + subsonic: Subsonic, + mock_get_playlist: Response, + mock_add_song_to_playlist: Response, + playlist: dict[str, Any], + song: dict[str, Any], +) -> None: + responses.add(mock_get_playlist) + responses.add(mock_add_song_to_playlist) + + response = subsonic.playlists.get_playlist(playlist["id"]) + # Remove the default songs from the mock_get_playlist mock + response.songs = [] + response.song_count = 0 + + response = response.add_songs([song["id"]]) + + assert response.songs is not None + assert response.songs[0].id == song["id"] + assert response.song_count == 1 + + +@responses.activate +def test_remove( + subsonic: Subsonic, + mock_get_playlist: Response, + mock_remove_song_to_playlist: Response, + playlist: dict[str, Any], +) -> None: + responses.add(mock_get_playlist) + responses.add(mock_remove_song_to_playlist) + + response = subsonic.playlists.get_playlist(playlist["id"]) + + response = response.remove_songs([0]) + + assert response.songs is not None + assert response.songs == [] + assert response.song_count == 0 diff --git a/tests/subsonic.py b/tests/subsonic.py deleted file mode 100644 index 233ce2b..0000000 --- a/tests/subsonic.py +++ /dev/null @@ -1,43 +0,0 @@ -import knuckles -import pytest -from knuckles.subsonic import Subsonic - - -@pytest.fixture -def username() -> str: - return "user" - - -@pytest.fixture -def password() -> str: - return "password" - - -@pytest.fixture -def client() -> str: - return "client" - - -@pytest.fixture -def base_url() -> str: - return "https://example.com" - - -@pytest.fixture -def subsonic(base_url: str, username: str, password: str, client: str) -> Subsonic: - return knuckles.Subsonic( - url=base_url, - user=username, - password=password, - client=client, - ) - - -@pytest.fixture -def params(username: str, client: str) -> dict[str, str]: - return { - "u": username, - "v": "1.16.1", - "c": client, - "f": "json", - }