Adds date_added field to Tidal provider (#2969)
authorFL550 <36160004+FL550@users.noreply.github.com>
Thu, 29 Jan 2026 06:48:33 +0000 (07:48 +0100)
committerGitHub <noreply@github.com>
Thu, 29 Jan 2026 06:48:33 +0000 (07:48 +0100)
* feat: adds date_added to tidal provider

* applied copilot suggestions

* Update snapshot

---------

Co-authored-by: FL550 <glacial0124@posteo.com>
music_assistant/providers/tidal/api_client.py
music_assistant/providers/tidal/library.py
music_assistant/providers/tidal/parsers.py
tests/providers/tidal/__snapshots__/test_parsers.ambr
tests/providers/tidal/fixtures/albums/album.json
tests/providers/tidal/fixtures/artists/artist.json
tests/providers/tidal/fixtures/playlists/playlist.json
tests/providers/tidal/fixtures/tracks/track.json
tests/providers/tidal/test_library.py

index 259e66e3892c77f793f930c17c70140bf29d29bb..41f62de8e7441a49dea660c08f09879f16ea33c2 100644 (file)
@@ -164,7 +164,6 @@ class TidalAPIClient:
         self,
         endpoint: str,
         item_key: str = "items",
-        nested_key: str | None = None,
         limit: int = 50,
         cursor_based: bool = False,
         **kwargs: Any,
@@ -192,10 +191,7 @@ class TidalAPIClient:
                 break
 
             for item in items:
-                if nested_key and nested_key in item and item[nested_key]:
-                    yield item[nested_key]
-                else:
-                    yield item
+                yield item
 
             if cursor_based:
                 cursor = response.get("cursor")
index 4e0ea2944dd7d24eb2c47bfbe08b89483b32a673..00cb67afa91585ebe631c353d0a0aacb3899144b 100644 (file)
@@ -31,22 +31,22 @@ class TidalLibraryManager:
     async def get_artists(self) -> AsyncGenerator[Artist, None]:
         """Retrieve library artists."""
         path = f"users/{self.auth.user_id}/favorites/artists"
-        async for item in self.api.paginate(path, nested_key="item"):
-            if item and item.get("id"):
+        async for item in self.api.paginate(path):
+            if item and item.get("item") and item["item"].get("id"):
                 yield parse_artist(self.provider, item)
 
     async def get_albums(self) -> AsyncGenerator[Album, None]:
         """Retrieve library albums."""
         path = f"users/{self.auth.user_id}/favorites/albums"
-        async for item in self.api.paginate(path, nested_key="item"):
-            if item and item.get("id"):
+        async for item in self.api.paginate(path):
+            if item and item.get("item") and item["item"].get("id"):
                 yield parse_album(self.provider, item)
 
     async def get_tracks(self) -> AsyncGenerator[Track, None]:
         """Retrieve library tracks."""
         path = f"users/{self.auth.user_id}/favorites/tracks"
-        async for item in self.api.paginate(path, nested_key="item"):
-            if item and item.get("id"):
+        async for item in self.api.paginate(path):
+            if item and item.get("item") and item["item"].get("id"):
                 yield parse_track(self.provider, item)
 
     async def get_playlists(self) -> AsyncGenerator[Playlist, None]:
@@ -60,8 +60,8 @@ class TidalLibraryManager:
 
         # 2. Get user playlists
         path = f"users/{self.auth.user_id}/playlistsAndFavoritePlaylists"
-        async for item in self.api.paginate(path, nested_key="playlist"):
-            if item and item.get("uuid"):
+        async for item in self.api.paginate(path):
+            if item and item.get("playlist") and item["playlist"].get("uuid"):
                 yield parse_playlist(self.provider, item)
 
     async def add_item(self, item: MediaItemType) -> bool:
index 30ea0847b07df5e5a4e9412dc6895a151e6aece6..3ced097c3f2b1aff56830b0f1b5307d144963af4 100644 (file)
@@ -34,11 +34,13 @@ if TYPE_CHECKING:
 
 def parse_artist(provider: TidalProvider, artist_obj: dict[str, Any]) -> Artist:
     """Parse tidal artist object to generic layout."""
-    artist_id = str(artist_obj["id"])
+    # Handle both full artist objects and nested ones coming from albums/tracks
+    artist_obj_data = artist_obj.get("item", artist_obj)
+    artist_id = str(artist_obj_data["id"])
     artist = Artist(
         item_id=artist_id,
         provider=provider.instance_id,
-        name=artist_obj["name"],
+        name=artist_obj_data["name"],
         provider_mappings={
             ProviderMapping(
                 item_id=artist_id,
@@ -51,8 +53,11 @@ def parse_artist(provider: TidalProvider, artist_obj: dict[str, Any]) -> Artist:
         },
     )
     # metadata
-    if artist_obj["picture"]:
-        picture_id = artist_obj["picture"].replace("-", "/")
+    if "created" in artist_obj:
+        with suppress(ValueError):
+            artist.date_added = datetime.fromisoformat(artist_obj["created"])
+    if artist_obj_data["picture"]:
+        picture_id = artist_obj_data["picture"].replace("-", "/")
         image_url = f"{RESOURCES_URL}/{picture_id}/750x750.jpg"
         artist.metadata.images = UniqueList(
             [
@@ -70,11 +75,12 @@ def parse_artist(provider: TidalProvider, artist_obj: dict[str, Any]) -> Artist:
 
 def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album:
     """Parse tidal album object to generic layout."""
+    album_obj_data = album_obj.get("item", album_obj)
     name, version = parse_title_and_version(
-        album_obj.get("title", "Unknown Album"),
-        album_obj.get("version") or None,
+        album_obj_data.get("title", "Unknown Album"),
+        album_obj_data.get("version") or None,
     )
-    album_id = str(album_obj.get("id", ""))
+    album_id = str(album_obj_data.get("id", ""))
 
     album = Album(
         item_id=album_id,
@@ -97,7 +103,7 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album:
 
     # Safely handle artists array
     various_artist_album: bool = False
-    for artist_obj in album_obj.get("artists", []):
+    for artist_obj in album_obj_data.get("artists", []):
         try:
             if artist_obj.get("name") == "Various Artists":
                 various_artist_album = True
@@ -106,7 +112,7 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album:
             provider.logger.warning("Error parsing artist in album %s: %s", name, err)
 
     # Safely determine album type
-    album_type = album_obj.get("type", "ALBUM")
+    album_type = album_obj_data.get("type", "ALBUM")
     if album_type == "COMPILATION" or various_artist_album:
         album.album_type = AlbumType.COMPILATION
     elif album_type == "ALBUM":
@@ -122,7 +128,7 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album:
         album.album_type = inferred_type
 
     # Safely parse year
-    if release_date := album_obj.get("releaseDate", ""):
+    if release_date := album_obj_data.get("releaseDate", ""):
         try:
             album.year = int(release_date.split("-")[0])
         except (ValueError, IndexError):
@@ -131,16 +137,19 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album:
             album.metadata.release_date = datetime.fromisoformat(release_date)
 
     # Safely set metadata
-    upc = album_obj.get("upc")
+    if "created" in album_obj:
+        with suppress(ValueError):
+            album.date_added = datetime.fromisoformat(album_obj["created"])
+    upc = album_obj_data.get("upc")
     if upc:
         album.external_ids.add((ExternalID.BARCODE, upc))
 
-    album.metadata.copyright = album_obj.get("copyright", "")
-    album.metadata.explicit = album_obj.get("explicit", False)
-    album.metadata.popularity = album_obj.get("popularity", 0)
+    album.metadata.copyright = album_obj_data.get("copyright", "")
+    album.metadata.explicit = album_obj_data.get("explicit", False)
+    album.metadata.popularity = album_obj_data.get("popularity", 0)
 
     # Safely handle cover image
-    cover = album_obj.get("cover")
+    cover = album_obj_data.get("cover")
     if cover:
         picture_id = cover.replace("-", "/")
         image_url = f"{RESOURCES_URL}/{picture_id}/750x750.jpg"
@@ -164,12 +173,13 @@ def parse_track(
     lyrics: dict[str, str] | None = None,
 ) -> Track:
     """Parse tidal track object to generic layout."""
+    track_obj_data = track_obj.get("item", track_obj)
     name, version = parse_title_and_version(
-        track_obj.get("title", "Unknown"),
-        track_obj.get("version") or None,
+        track_obj_data.get("title", "Unknown"),
+        track_obj_data.get("version") or None,
     )
-    track_id = str(track_obj.get("id", 0))
-    media_metadata = track_obj.get("mediaMetadata") or {}
+    track_id = str(track_obj_data.get("id", 0))
+    media_metadata = track_obj_data.get("mediaMetadata") or {}
     tags = media_metadata.get("tags", [])
     hi_res_lossless = any(tag in tags for tag in ["HIRES_LOSSLESS", "HI_RES_LOSSLESS"])
     track = Track(
@@ -177,7 +187,7 @@ def parse_track(
         provider=provider.instance_id,
         name=name,
         version=version,
-        duration=track_obj.get("duration", 0),
+        duration=track_obj_data.get("duration", 0),
         provider_mappings={
             ProviderMapping(
                 item_id=str(track_id),
@@ -188,37 +198,40 @@ def parse_track(
                     bit_depth=24 if hi_res_lossless else 16,
                 ),
                 url=f"https://tidal.com/track/{track_id}",
-                available=track_obj["streamReady"],
+                available=track_obj_data["streamReady"],
             )
         },
-        disc_number=track_obj.get("volumeNumber", 0) or 0,
-        track_number=track_obj.get("trackNumber", 0) or 0,
+        disc_number=track_obj_data.get("volumeNumber", 0) or 0,
+        track_number=track_obj_data.get("trackNumber", 0) or 0,
     )
-    if "isrc" in track_obj:
-        track.external_ids.add((ExternalID.ISRC, track_obj["isrc"]))
+    if "isrc" in track_obj_data:
+        track.external_ids.add((ExternalID.ISRC, track_obj_data["isrc"]))
     track.artists = UniqueList()
-    for track_artist in track_obj["artists"]:
+    for track_artist in track_obj_data["artists"]:
         artist = parse_artist(provider, track_artist)
         track.artists.append(artist)
     # metadata
-    track.metadata.explicit = track_obj["explicit"]
-    track.metadata.popularity = track_obj["popularity"]
-    if "copyright" in track_obj:
-        track.metadata.copyright = track_obj["copyright"]
+    if "created" in track_obj:
+        with suppress(ValueError):
+            track.date_added = datetime.fromisoformat(track_obj["created"])
+    track.metadata.explicit = track_obj_data["explicit"]
+    track.metadata.popularity = track_obj_data["popularity"]
+    if "copyright" in track_obj_data:
+        track.metadata.copyright = track_obj_data["copyright"]
     if lyrics and "lyrics" in lyrics:
         track.metadata.lyrics = lyrics["lyrics"]
     if lyrics and "subtitles" in lyrics:
         track.metadata.lrc_lyrics = lyrics["subtitles"]
-    if track_obj["album"]:
+    if track_obj_data["album"]:
         # Here we use an ItemMapping as Tidal returns
         # minimal data when getting an Album from a Track
         track.album = provider.get_item_mapping(
             media_type=MediaType.ALBUM,
-            key=str(track_obj["album"]["id"]),
-            name=track_obj["album"]["title"],
+            key=str(track_obj_data["album"]["id"]),
+            name=track_obj_data["album"]["title"],
         )
-        if track_obj["album"]["cover"]:
-            picture_id = track_obj["album"]["cover"].replace("-", "/")
+        if track_obj_data["album"]["cover"]:
+            picture_id = track_obj_data["album"]["cover"].replace("-", "/")
             image_url = f"{RESOURCES_URL}/{picture_id}/750x750.jpg"
             track.metadata.images = UniqueList(
                 [
@@ -237,8 +250,9 @@ def parse_playlist(
     provider: TidalProvider, playlist_obj: dict[str, Any], is_mix: bool = False
 ) -> Playlist:
     """Parse tidal playlist object to generic layout."""
+    playlist_obj_data = playlist_obj.get("playlist", playlist_obj)
     # Get ID based on playlist type
-    raw_id = str(playlist_obj.get("id" if is_mix else "uuid", ""))
+    raw_id = str(playlist_obj_data.get("id" if is_mix else "uuid", ""))
 
     # Add prefix for mixes to distinguish them
     playlist_id = f"mix_{raw_id}" if is_mix else raw_id
@@ -249,7 +263,7 @@ def parse_playlist(
         is_editable = False
     else:
         creator_id = None
-        creator = playlist_obj.get("creator", {})
+        creator = playlist_obj_data.get("creator", {})
         if creator:
             creator_id = creator.get("id")
         is_editable = bool(creator_id and str(creator_id) == str(provider.auth.user_id))
@@ -269,7 +283,7 @@ def parse_playlist(
     playlist = Playlist(
         item_id=playlist_id,
         provider=provider.instance_id,
-        name=playlist_obj.get("title", "Unknown"),
+        name=playlist_obj_data.get("title", "Unknown"),
         owner=owner_name,
         provider_mappings={
             ProviderMapping(
@@ -284,16 +298,18 @@ def parse_playlist(
     )
 
     # Metadata - different fields based on type
-
+    if "created" in playlist_obj:
+        with suppress(ValueError):
+            playlist.date_added = datetime.fromisoformat(playlist_obj["created"])
     # Add the description from the subtitle for mixes
     if is_mix:
-        subtitle = playlist_obj.get("subTitle")
+        subtitle = playlist_obj_data.get("subTitle")
         if subtitle:
             playlist.metadata.description = subtitle
 
     # Handle images differently based on type
     if is_mix:
-        if pictures := playlist_obj.get("images", {}).get("MEDIUM"):
+        if pictures := playlist_obj_data.get("images", {}).get("MEDIUM"):
             image_url = pictures.get("url", "")
             if image_url:
                 playlist.metadata.images = UniqueList(
@@ -306,7 +322,7 @@ def parse_playlist(
                         )
                     ]
                 )
-    elif picture := (playlist_obj.get("squareImage") or playlist_obj.get("image")):
+    elif picture := (playlist_obj_data.get("squareImage") or playlist_obj_data.get("image")):
         picture_id = picture.replace("-", "/")
         image_url = f"{RESOURCES_URL}/{picture_id}/750x750.jpg"
         playlist.metadata.images = UniqueList(
index 541491bdcb58794c86d38b1ee93b28029dd30bcc..2cc814c1c2db615bceae76df01d808dd9383dafa 100644 (file)
@@ -70,7 +70,7 @@
         'version': '',
       }),
     ]),
-    'date_added': None,
+    'date_added': '2024-06-10T12:00:00+00:00',
     'external_ids': list([
       list([
         'barcode',
 # ---
 # name: test_parse_artist[artist]
   dict({
-    'date_added': None,
+    'date_added': '2024-06-10T12:00:00+00:00',
     'external_ids': list([
     ]),
     'favorite': False,
 # ---
 # name: test_parse_playlist[playlist]
   dict({
-    'date_added': None,
+    'date_added': '2024-06-10T12:00:00+00:00',
     'external_ids': list([
     ]),
     'favorite': False,
         'version': '',
       }),
     ]),
-    'date_added': None,
+    'date_added': '2024-06-10T12:00:00+00:00',
     'disc_number': 1,
     'duration': 180,
     'external_ids': list([
index 7e27d6832b60897fd48db6467ca7131dabbaac24..f449f258def09fd70c2f2c68e127b1c7afac686a 100644 (file)
@@ -1,41 +1,44 @@
 {
-    "id": 67890,
-    "title": "Test Album",
-    "version": "Deluxe Edition",
-    "artists": [
-        {
-            "id": 12345,
-            "name": "Test Artist",
-            "picture": "1234-5678-90ab-cdef",
-            "type": "MAIN"
-        }
-    ],
-    "type": "ALBUM",
-    "releaseDate": "2023-01-01",
-    "availabilityDate": "2023-01-01",
-    "duration": 2400,
-    "numberOfTracks": 12,
-    "numberOfVolumes": 1,
-    "upc": "123456789012",
-    "copyright": "℗ 2023 Test Label",
-    "explicit": false,
-    "popularity": 50,
-    "cover": "abcd-ef01-2345-6789",
-    "videoCover": null,
-    "streamReady": true,
-    "streamStartDate": "2023-01-01T00:00:00.000+0000",
-    "allowStreaming": true,
-    "premiumStreamingOnly": false,
-    "numberOfVideos": 0,
-    "audioQuality": "LOSSLESS",
-    "audioModes": [
-        "STEREO"
-    ],
-    "mediaMetadata": {
-        "tags": [
-            "LOSSLESS",
-            "HIRES_LOSSLESS"
-        ]
-    },
-    "url": "http://www.tidal.com/album/67890"
+    "created": "2024-06-10T12:00:00Z",
+    "item": {
+        "id": 67890,
+        "title": "Test Album",
+        "version": "Deluxe Edition",
+        "artists": [
+            {
+                "id": 12345,
+                "name": "Test Artist",
+                "picture": "1234-5678-90ab-cdef",
+                "type": "MAIN"
+            }
+        ],
+        "type": "ALBUM",
+        "releaseDate": "2023-01-01",
+        "availabilityDate": "2023-01-01",
+        "duration": 2400,
+        "numberOfTracks": 12,
+        "numberOfVolumes": 1,
+        "upc": "123456789012",
+        "copyright": "℗ 2023 Test Label",
+        "explicit": false,
+        "popularity": 50,
+        "cover": "abcd-ef01-2345-6789",
+        "videoCover": null,
+        "streamReady": true,
+        "streamStartDate": "2023-01-01T00:00:00.000+0000",
+        "allowStreaming": true,
+        "premiumStreamingOnly": false,
+        "numberOfVideos": 0,
+        "audioQuality": "LOSSLESS",
+        "audioModes": [
+            "STEREO"
+        ],
+        "mediaMetadata": {
+            "tags": [
+                "LOSSLESS",
+                "HIRES_LOSSLESS"
+            ]
+        },
+        "url": "http://www.tidal.com/album/67890"
+    }
 }
index a6d286f69ee2bfd49e1791584cf0b894d00a1f68..e8e85ed125c5ee95d2e33674a4e2dc2982b92813 100644 (file)
@@ -1,15 +1,18 @@
 {
-    "id": 12345,
-    "name": "Test Artist",
-    "picture": "1234-5678-90ab-cdef",
-    "url": "http://www.tidal.com/artist/12345",
-    "artistTypes": [
-        "ARTIST"
-    ],
-    "popularity": 75,
-    "banner": "banner-1234-5678-90ab",
-    "releaseDateOriginal": "2010-01-01",
-    "mixes": {
-        "ARTIST_MIX": "artist_mix_id_123"
+    "created": "2024-06-10T12:00:00Z",
+    "item": {
+        "id": 12345,
+        "name": "Test Artist",
+        "picture": "1234-5678-90ab-cdef",
+        "url": "http://www.tidal.com/artist/12345",
+        "artistTypes": [
+            "ARTIST"
+        ],
+        "popularity": 75,
+        "banner": "banner-1234-5678-90ab",
+        "releaseDateOriginal": "2010-01-01",
+        "mixes": {
+            "ARTIST_MIX": "artist_mix_id_123"
+        }
     }
 }
index 2d2e6fd152c5c9c991f984f47a1a274444164fee..034a695b66771f947b04cf56b68e351f0f5a7359 100644 (file)
@@ -1,23 +1,26 @@
 {
-    "uuid": "aabbcc-1122-3344-5566",
-    "title": "Test Playlist",
-    "description": "A test playlist for testing",
-    "creator": {
-        "id": 99999,
-        "name": "Test User",
-        "picture": null
-    },
-    "type": "USER",
-    "publicPlaylist": true,
-    "created": "2023-01-01T00:00:00.000+0000",
-    "lastUpdated": "2023-06-15T12:00:00.000+0000",
-    "numberOfTracks": 25,
-    "numberOfVideos": 0,
-    "duration": 5400,
-    "popularity": 45,
-    "image": "playlist-image-id",
-    "squareImage": "playlist-square-image-id",
-    "url": "http://www.tidal.com/playlist/aabbcc-1122-3344-5566",
-    "promotedArtists": [],
-    "lastItemAddedAt": "2023-06-15T12:00:00.000+0000"
+    "created": "2024-06-10T12:00:00Z",
+    "playlist": {
+        "uuid": "aabbcc-1122-3344-5566",
+        "title": "Test Playlist",
+        "description": "A test playlist for testing",
+        "creator": {
+            "id": 99999,
+            "name": "Test User",
+            "picture": null
+        },
+        "type": "USER",
+        "publicPlaylist": true,
+        "created": "2023-01-01T00:00:00.000+0000",
+        "lastUpdated": "2023-06-15T12:00:00.000+0000",
+        "numberOfTracks": 25,
+        "numberOfVideos": 0,
+        "duration": 5400,
+        "popularity": 45,
+        "image": "playlist-image-id",
+        "squareImage": "playlist-square-image-id",
+        "url": "http://www.tidal.com/playlist/aabbcc-1122-3344-5566",
+        "promotedArtists": [],
+        "lastItemAddedAt": "2023-06-15T12:00:00.000+0000"
+    }
 }
index 04917ae4d1c9a0726744a38d57140c04174cdad3..9504ba3d622fdf691fac0541b2d78ba086806c8b 100644 (file)
@@ -1,49 +1,52 @@
 {
-    "id": 112233,
-    "title": "Test Track",
-    "version": "Remastered",
-    "duration": 180,
-    "replayGain": -8.5,
-    "peak": 0.95,
-    "allowStreaming": true,
-    "streamReady": true,
-    "streamStartDate": "2023-01-01T00:00:00.000+0000",
-    "premiumStreamingOnly": false,
-    "trackNumber": 1,
-    "volumeNumber": 1,
-    "isrc": "US1234567890",
-    "copyright": "℗ 2023 Test Label",
-    "artists": [
-        {
-            "id": 12345,
-            "name": "Test Artist",
-            "picture": "1234-5678-90ab-cdef",
-            "type": "MAIN"
-        }
-    ],
-    "album": {
-        "id": 67890,
-        "title": "Test Album",
-        "cover": "abcd-ef01-2345-6789",
-        "videoCover": null,
-        "releaseDate": "2023-01-01"
-    },
-    "explicit": false,
-    "audioQuality": "LOSSLESS",
-    "audioModes": [
-        "STEREO"
-    ],
-    "mediaMetadata": {
-        "tags": [
-            "LOSSLESS",
-            "HIRES_LOSSLESS"
-        ]
-    },
-    "popularity": 60,
-    "mixes": {
-        "TRACK_MIX": "track_mix_id_456"
-    },
-    "url": "http://www.tidal.com/track/112233",
-    "djReady": true,
-    "stemReady": false
+    "created": "2024-06-10T12:00:00Z",
+    "item": {
+        "id": 112233,
+        "title": "Test Track",
+        "version": "Remastered",
+        "duration": 180,
+        "replayGain": -8.5,
+        "peak": 0.95,
+        "allowStreaming": true,
+        "streamReady": true,
+        "streamStartDate": "2023-01-01T00:00:00.000+0000",
+        "premiumStreamingOnly": false,
+        "trackNumber": 1,
+        "volumeNumber": 1,
+        "isrc": "US1234567890",
+        "copyright": "℗ 2023 Test Label",
+        "artists": [
+            {
+                "id": 12345,
+                "name": "Test Artist",
+                "picture": "1234-5678-90ab-cdef",
+                "type": "MAIN"
+            }
+        ],
+        "album": {
+            "id": 67890,
+            "title": "Test Album",
+            "cover": "abcd-ef01-2345-6789",
+            "videoCover": null,
+            "releaseDate": "2023-01-01"
+        },
+        "explicit": false,
+        "audioQuality": "LOSSLESS",
+        "audioModes": [
+            "STEREO"
+        ],
+        "mediaMetadata": {
+            "tags": [
+                "LOSSLESS",
+                "HIRES_LOSSLESS"
+            ]
+        },
+        "popularity": 60,
+        "mixes": {
+            "TRACK_MIX": "track_mix_id_456"
+        },
+        "url": "http://www.tidal.com/track/112233",
+        "djReady": true,
+        "stemReady": false
+    }
 }
index 3efb038a616b351b1e497a415abcef64d7666ade..09f58ae4102da28fda696c8ddacb1da556f3100e 100644 (file)
@@ -56,7 +56,9 @@ async def test_get_artists(
     mock_parse_artist: Mock, library_manager: TidalLibraryManager, provider_mock: Mock
 ) -> None:
     """Test get_artists."""
-    provider_mock.api.paginate.return_value = [{"id": 1, "name": "Test Artist"}]
+    provider_mock.api.paginate.return_value = [
+        {"created": "2024-01-01T00:00:00", "item": {"id": 1, "name": "Test Artist"}}
+    ]
     mock_parse_artist.return_value = Mock(item_id="1")
 
     artists = [a async for a in library_manager.get_artists()]
@@ -65,7 +67,6 @@ async def test_get_artists(
     assert artists[0].item_id == "1"
     provider_mock.api.paginate.assert_called_with(
         "users/12345/favorites/artists",
-        nested_key="item",
     )
     mock_parse_artist.assert_called_once()
 
@@ -75,7 +76,9 @@ async def test_get_albums(
     mock_parse_album: Mock, library_manager: TidalLibraryManager, provider_mock: Mock
 ) -> None:
     """Test get_albums."""
-    provider_mock.api.paginate.return_value = [{"id": 1, "title": "Test Album"}]
+    provider_mock.api.paginate.return_value = [
+        {"created": "2024-01-01T00:00:00", "item": {"id": 1, "title": "Test Album"}}
+    ]
     mock_parse_album.return_value = Mock(item_id="1")
 
     albums = [a async for a in library_manager.get_albums()]
@@ -84,7 +87,6 @@ async def test_get_albums(
     assert albums[0].item_id == "1"
     provider_mock.api.paginate.assert_called_with(
         "users/12345/favorites/albums",
-        nested_key="item",
     )
     mock_parse_album.assert_called_once()
 
@@ -94,7 +96,9 @@ async def test_get_tracks(
     mock_parse_track: Mock, library_manager: TidalLibraryManager, provider_mock: Mock
 ) -> None:
     """Test get_tracks."""
-    provider_mock.api.paginate.return_value = [{"id": 1, "title": "Test Track"}]
+    provider_mock.api.paginate.return_value = [
+        {"created": "2024-01-01T00:00:00", "item": {"id": 1, "title": "Test Track"}}
+    ]
     mock_parse_track.return_value = Mock(item_id="1")
 
     tracks = [t async for t in library_manager.get_tracks()]
@@ -103,7 +107,6 @@ async def test_get_tracks(
     assert tracks[0].item_id == "1"
     provider_mock.api.paginate.assert_called_with(
         "users/12345/favorites/tracks",
-        nested_key="item",
     )
     mock_parse_track.assert_called_once()
 
@@ -116,7 +119,12 @@ async def test_get_playlists(
     # Mock mixes response
     mixes_response = [{"id": "mix_1", "title": "Mix 1"}]
     # Mock playlists response
-    playlists_response = [{"uuid": "pl_1", "title": "Playlist 1"}]
+    playlists_response = [
+        {
+            "created": "2024-01-01T00:00:00",
+            "playlist": {"uuid": "pl_1", "title": "Playlist 1"},
+        }
+    ]
 
     # Configure paginate side effect
     async def paginate_side_effect(
@@ -126,7 +134,8 @@ async def test_get_playlists(
             for item in mixes_response:
                 yield item
         else:
-            for item in playlists_response:
+            # The ignore[assignment] is needed because of the different return types
+            for item in playlists_response:  # type: ignore[assignment]
                 yield item
 
     provider_mock.api.paginate.side_effect = paginate_side_effect