Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 167 additions & 1 deletion tests/test_deezer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import pytest
from unittest.mock import Mock, patch
import deezer
from unittest.mock import Mock, AsyncMock, patch
from util import arun

from streamrip.client.downloadable import DeezerDownloadable
Expand Down Expand Up @@ -124,6 +125,171 @@ def test_deezer_no_fallback_when_disabled(mock_deezer_client):
with pytest.raises(NonStreamableError, match="The requested quality 2 is not available and fallback is disabled"):
arun(mock_deezer_client.get_downloadable("123", quality=2))

# ===== get_downloadable — geoblocking =====

def test_deezer_geoblocked_with_fallback(mock_deezer_client):
"""WrongGeolocation retries the download using the FALLBACK track ID."""
def gw_get_track_side_effect(track_id):
if track_id == "123":
return {
"FILESIZE_FLAC": 25_000_000,
"FILESIZE_MP3_320": 5_000_000,
"FILESIZE_MP3_128": 2_000_000,
"TRACK_TOKEN": "token_123",
"FALLBACK": {"SNG_ID": "456"},
}
return {
"FILESIZE_FLAC": 25_000_000,
"FILESIZE_MP3_320": 5_000_000,
"FILESIZE_MP3_128": 2_000_000,
"TRACK_TOKEN": "token_456",
}

mock_deezer_client.client.gw.get_track.side_effect = gw_get_track_side_effect

def url_side_effect(token, fmt):
if token == "token_123":
raise deezer.WrongGeolocation("FR")
return "https://test.flac"

mock_deezer_client.client.get_track_url.side_effect = url_side_effect

downloadable = arun(mock_deezer_client.get_downloadable("123", quality=2))
assert downloadable.quality == 2
assert mock_deezer_client.client.gw.get_track.call_count == 2


def test_deezer_geoblocked_no_fallback(mock_deezer_client):
"""WrongGeolocation raises NonStreamableError when no FALLBACK ID is available."""
mock_track_info = {
"FILESIZE_FLAC": 25_000_000,
"FILESIZE_MP3_320": 5_000_000,
"FILESIZE_MP3_128": 2_000_000,
"TRACK_TOKEN": "test_token",
# no FALLBACK key
}
mock_deezer_client.client.gw.get_track.return_value = mock_track_info
mock_deezer_client.client.get_track_url.side_effect = deezer.WrongGeolocation("FR")

with pytest.raises(NonStreamableError, match="country"):
arun(mock_deezer_client.get_downloadable("123", quality=2))


# ===== get_downloadable — encrypted URL fallback =====

def test_deezer_encrypted_url_fallback(mock_deezer_client):
"""When get_track_url returns None, falls back to the AES-encrypted CDN URL."""
mock_track_info = {
"FILESIZE_FLAC": 25_000_000,
"FILESIZE_MP3_320": 5_000_000,
"FILESIZE_MP3_128": 2_000_000,
"TRACK_TOKEN": "test_token",
"MD5_ORIGIN": "abc123def456abc123def456abc12345",
"MEDIA_VERSION": "1",
}
mock_deezer_client.client.gw.get_track.return_value = mock_track_info
mock_deezer_client.client.get_track_url.return_value = None

with patch.object(
mock_deezer_client,
"_get_encrypted_file_url",
return_value="https://e-cdns-proxy-a.dzcdn.net/mobile/1/deadbeef",
) as mock_encrypted:
downloadable = arun(mock_deezer_client.get_downloadable("123", quality=2))

mock_encrypted.assert_called_once_with(
"123", "abc123def456abc123def456abc12345", "1"
)
assert downloadable.url == "https://e-cdns-proxy-a.dzcdn.net/mobile/1/deadbeef"


# ===== get_track =====

def test_deezer_get_track(mock_deezer_client):
"""get_track returns a track dict with full album metadata embedded."""
mock_deezer_client.client.api.get_track.return_value = {
"id": "100",
"title": "Test Track",
"album": {"id": 200},
}
mock_deezer_client.client.api.get_album.return_value = {
"id": "200",
"title": "Test Album",
}
mock_deezer_client.client.api.get_album_tracks.return_value = {
"data": [{"id": "100"}]
}

track = arun(mock_deezer_client.get_track("100"))

assert track["title"] == "Test Track"
assert track["album"]["title"] == "Test Album"
assert track["album"]["track_total"] == 1


# ===== get_playlist =====

def test_deezer_get_playlist(mock_deezer_client):
"""get_playlist returns playlist metadata enriched with tracks and track_total."""
mock_deezer_client.client.api.get_playlist.return_value = {
"title": "My Playlist",
}
mock_deezer_client.client.api.get_playlist_tracks.return_value = {
"data": [{"id": "1"}, {"id": "2"}],
}

result = arun(mock_deezer_client.get_playlist("playlist_123"))

assert result["title"] == "My Playlist"
assert result["track_total"] == 2
assert len(result["tracks"]) == 2


# ===== get_metadata =====

def test_deezer_get_metadata_dispatch(mock_deezer_client):
"""get_metadata dispatches to the correct handler for each media type."""
with patch.object(
mock_deezer_client, "get_album", return_value={"id": "1"}
) as mock_get_album:
result = arun(mock_deezer_client.get_metadata("1", "album"))
mock_get_album.assert_called_once_with("1")
assert result == {"id": "1"}


def test_deezer_get_metadata_invalid_type(mock_deezer_client):
"""get_metadata raises for unsupported media types."""
with pytest.raises(Exception, match="not available on deezer"):
arun(mock_deezer_client.get_metadata("1", "label"))


# ===== search =====

def test_deezer_search_track(mock_deezer_client):
"""search returns a list containing the API response when results are found."""
mock_deezer_client.client.api.search_track.return_value = {
"total": 2,
"data": [{"id": "1"}, {"id": "2"}],
}

results = arun(mock_deezer_client.search("track", "test query"))

assert len(results) == 1
assert results[0]["total"] == 2
mock_deezer_client.client.api.search_track.assert_called_once_with(
"test query", limit=200
)


def test_deezer_search_no_results(mock_deezer_client):
"""search returns an empty list when the API reports zero results."""
mock_deezer_client.client.api.search_track.return_value = {"total": 0, "data": []}

results = arun(mock_deezer_client.search("track", "nonexistent"))

assert results == []


# ===== INTEGRATION TEST =====

@pytest.mark.skipif(
Expand Down