Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion streamrip/rip/parse_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

logger = logging.getLogger("streamrip")
URL_REGEX = re.compile(
r"https?://(?:www|open|play|listen)?\.?(qobuz|tidal|deezer|dzr)\.?(com|page.link)(?:(?:/(album|artist|track|playlist|video|label))|(?:\/[-\w]+?))+\/([-\w]+)",
r"https?://(?:www|open|play|listen)?\.?(qobuz|tidal|deezer)\.com?(?:(?:/(album|artist|track|playlist|video|label))|(?:\/[-\w]+?))+\/([-\w]+)",
)
SOUNDCLOUD_URL_REGEX = re.compile(r"https://soundcloud.com/[-\w:/]+")
LASTFM_URL_REGEX = re.compile(r"https://www.last.fm/user/\w+/playlists/\w+")
Expand Down
155 changes: 155 additions & 0 deletions tests/test_parse_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import unittest
from unittest.mock import AsyncMock, patch

from streamrip.rip.parse_url import (
DeezerDynamicURL,
GenericURL,
SoundcloudURL,
parse_url,
)


class TestParseURL(unittest.TestCase):
def test_deezer_dynamic_url(self):
"""Test that Deezer dynamic URLs are matched correctly."""
url = "https://dzr.page.link/SnV6hCyHihkmCCwUA"
result = parse_url(url)

self.assertIsNotNone(result)
self.assertIsInstance(result, DeezerDynamicURL)
self.assertEqual(result.source, "deezer")

def test_qobuz_album_url(self):
"""Test that Qobuz album URLs are matched correctly."""
url = "https://www.qobuz.com/fr-fr/album/bizarre-ride-ii-the-pharcyde-the-pharcyde/0066991040005"
result = parse_url(url)

self.assertIsNotNone(result)
self.assertIsInstance(result, GenericURL)
self.assertEqual(result.source, "qobuz")

# Verify the regex match groups
groups = result.match.groups()
self.assertEqual(len(groups), 3)
self.assertEqual(groups[0], "qobuz") # source
self.assertEqual(groups[1], "album") # media_type
self.assertEqual(groups[2], "0066991040005") # item_id

def test_tidal_track_url(self):
"""Test that Tidal track URLs are matched correctly."""
url = "https://tidal.com/browse/track/3083287"
result = parse_url(url)

self.assertIsNotNone(result)
self.assertIsInstance(result, GenericURL)
self.assertEqual(result.source, "tidal")

# Verify the regex match groups
groups = result.match.groups()
self.assertEqual(len(groups), 3)
self.assertEqual(groups[0], "tidal") # source
self.assertEqual(groups[1], "track") # media_type
self.assertEqual(groups[2], "3083287") # item_id

def test_deezer_track_url(self):
"""Test that Deezer track URLs are matched correctly."""
url = "https://www.deezer.com/track/4195713"
result = parse_url(url)

self.assertIsNotNone(result)
self.assertIsInstance(result, GenericURL)
self.assertEqual(result.source, "deezer")

# Verify the regex match groups
groups = result.match.groups()
self.assertEqual(len(groups), 3)
self.assertEqual(groups[0], "deezer") # source
self.assertEqual(groups[1], "track") # media_type
self.assertEqual(groups[2], "4195713") # item_id

def test_invalid_url(self):
"""Test that invalid URLs return None."""
urls = [
"https://example.com",
"not a url",
"https://spotify.com/track/123456", # Unsupported source
"https://tidal.com/invalid/3083287", # Invalid media type
]

for url in urls:
result = parse_url(url)
self.assertIsNone(result, f"URL should not parse: {url}")

def test_alternate_url_formats(self):
"""Test various URL formats that should be valid."""
# Test with different domain prefixes
url1 = "https://open.tidal.com/track/3083287"
url2 = "https://play.qobuz.com/album/0066991040005"
url3 = "https://listen.tidal.com/track/3083287"

for url in [url1, url2, url3]:
result = parse_url(url)
self.assertIsNotNone(result, f"Should parse URL: {url}")
self.assertIsInstance(result, GenericURL)

def test_url_with_language_code(self):
"""Test URLs with different language codes."""
urls = [
"https://www.qobuz.com/us-en/album/name/id123456",
"https://www.qobuz.com/gb-en/album/name/id123456",
"https://www.deezer.com/en/track/4195713",
"https://www.deezer.com/fr/track/4195713",
]

for url in urls:
result = parse_url(url)
self.assertIsNotNone(result, f"Should parse URL: {url}")
self.assertIsInstance(result, GenericURL)

def test_soundcloud_url(self):
"""Test that Soundcloud URLs are matched correctly."""
urls = [
"https://soundcloud.com/artist-name/track-name",
"https://soundcloud.com/artist-name/sets/playlist-name",
]

for url in urls:
result = parse_url(url)
self.assertIsNotNone(result, f"Should parse URL: {url}")
self.assertIsInstance(result, SoundcloudURL)
self.assertEqual(result.source, "soundcloud")


class TestDeezerDynamicURL(unittest.TestCase):
@patch("streamrip.rip.parse_url.DeezerDynamicURL._extract_info_from_dynamic_link")
def test_into_pending_album(self, mock_extract):
"""Test conversion of Deezer dynamic URL to a PendingAlbum."""
import asyncio

async def run_test():
url = "https://dzr.page.link/SnV6hCyHihkmCCwUA"
result = parse_url(url)

# Mock the extract method to return album type and ID
mock_extract.return_value = ("album", "12345")

# Mock the client, config, db
mock_client = AsyncMock()
mock_client.source = "deezer"
mock_config = AsyncMock()
mock_db = AsyncMock()

# Call into_pending
pending = await result.into_pending(mock_client, mock_config, mock_db)

# Verify the correct pending type was created
self.assertEqual(pending.__class__.__name__, "PendingAlbum")
self.assertEqual(pending.id, "12345")

# Run the coroutine
asyncio.run(run_test())


if __name__ == "__main__":
unittest.main()

Loading