368 lines
17 KiB
Python
368 lines
17 KiB
Python
import os
|
||
import unittest
|
||
import pytest
|
||
from pathlib import Path
|
||
from mutagen.mp3 import MP3
|
||
from mutagen.flac import FLAC
|
||
from mutagen import MutagenError
|
||
|
||
from deezer_downloader.configuration import load_config
|
||
|
||
if "DEEZER_DOWNLOADER_CONFIG_FILE" in os.environ:
|
||
config_file = Path(os.environ["DEEZER_DOWNLOADER_CONFIG_FILE"])
|
||
else:
|
||
config_file = (Path(__file__).parents[1] / Path("deezer_downloader") / Path("cli") / Path("deezer-downloader.ini.template")).resolve()
|
||
|
||
load_config(config_file)
|
||
from deezer_downloader.configuration import config
|
||
|
||
from deezer_downloader.deezer import init_deezer_session, TYPE_TRACK, TYPE_ALBUM
|
||
from deezer_downloader.deezer import deezer_search, get_song_infos_from_deezer_website, parse_deezer_playlist, download_song, get_deezer_favorites
|
||
from deezer_downloader.deezer import Deezer404Exception, DeezerApiException
|
||
from deezer_downloader.spotify import get_songs_from_spotify_website, SpotifyWebsiteParserException, parse_uri, SpotifyInvalidUrlException
|
||
from deezer_downloader.youtubedl import youtubedl_download, YoutubeDLFailedException, DownloadedFileNotFoundException
|
||
|
||
known_song_keys = ["SNG_ID", "DURATION", "MD5_ORIGIN", "SNG_TITLE", "TRACK_NUMBER",
|
||
"ALB_PICTURE", "MEDIA_VERSION", "ART_NAME", "ALB_TITLE"]
|
||
|
||
init_deezer_session(config['proxy']['server'],
|
||
config["deezer"]["quality"])
|
||
|
||
|
||
class TestDeezerMethods(unittest.TestCase):
|
||
|
||
# BEGIN: TEST deezer_search
|
||
def test_deezer_search_song_valid(self):
|
||
songs = deezer_search("Großstadtgeflüster diadem", TYPE_TRACK)
|
||
self.assertIsInstance(songs, list)
|
||
s = songs[0]
|
||
self.assertSetEqual(set(s.keys()), {'preview_url', 'artist', 'id', 'id_type', 'album_id', 'title', 'img_url', 'album'})
|
||
self.assertTrue(s['id'], '730393272')
|
||
self.assertTrue(s['title'], 'Diadem')
|
||
self.assertTrue(s['artist'], 'Grossstadtgeflüster')
|
||
self.assertTrue(s['album'], 'Tips & Tricks')
|
||
self.assertTrue(s['preview_url'], 'https://cdns-preview-6.dzcdn.net/stream/c-6abdd540dd7e7f02d2c4d21537709c23-3.mp3')
|
||
self.assertTrue(s['album_id'], '107261872')
|
||
self.assertTrue(s['id_type'], 'track')
|
||
|
||
def test_deezer_search_album_valid(self):
|
||
albums = deezer_search("Coldplay", TYPE_ALBUM)
|
||
self.assertIsInstance(albums, list)
|
||
for album in albums:
|
||
self.assertSetEqual(set(album.keys()), {'id', 'id_type', 'album', 'album_id', 'img_url', 'artist', 'title', 'preview_url'})
|
||
|
||
found_album_names = [x['album'] for x in albums]
|
||
known_album_names = ['Parachutes', 'X&Y', 'A Head Full of Dreams']
|
||
for known_album_name in known_album_names:
|
||
self.assertIn(known_album_name, found_album_names)
|
||
|
||
def test_deezer_search_invalid_search_typ(self):
|
||
songs = deezer_search("Coldplay", "this is a wrong search type")
|
||
self.assertIsInstance(songs, list)
|
||
self.assertListEqual(songs, [])
|
||
|
||
def test_deezer_search_song_invalid_no_song_found(self):
|
||
songs = deezer_search("8f49834zf934fdshfkhejw", TYPE_TRACK)
|
||
self.assertIsInstance(songs, list)
|
||
self.assertListEqual(songs, [])
|
||
|
||
def test_deezer_search_album_invalid_no_song_found(self):
|
||
songs = deezer_search("8f49834zf934fdshfkhejw", TYPE_ALBUM)
|
||
self.assertIsInstance(songs, list)
|
||
self.assertListEqual(songs, [])
|
||
# END: TEST deezer_search
|
||
|
||
# BEGIN: TEST get_song_infos_from_deezer_website
|
||
def test_get_track_infos_from_website(self):
|
||
song = get_song_infos_from_deezer_website(TYPE_TRACK, "69962764")
|
||
self.assertIsInstance(song, dict)
|
||
song_keys = list(song.keys())
|
||
for key in known_song_keys:
|
||
self.assertIn(key, song_keys)
|
||
self.assertEqual(song["SNG_ID"], "69962764")
|
||
self.assertEqual(song["ART_NAME"], "The Clash")
|
||
self.assertEqual(song["SNG_TITLE"], "Should I Stay or Should I Go")
|
||
# MD5_ORIGIN changes over time
|
||
#self.assertEqual(song["MD5_ORIGIN"], "df51967a8b9b88d079fb0d9f4a0c1c38")
|
||
self.assertEqual(len(song["MD5_ORIGIN"]), 32)
|
||
|
||
def test_get_album_infos_from_website(self):
|
||
songs = get_song_infos_from_deezer_website(TYPE_ALBUM, "1434890")
|
||
self.assertIsInstance(songs, list)
|
||
self.assertEqual(len(songs), 15)
|
||
for song in songs:
|
||
song_keys = list(song.keys())
|
||
for key in known_song_keys:
|
||
self.assertIn(key, song_keys)
|
||
self.assertEqual(songs[0]["SNG_ID"], "15523769")
|
||
self.assertEqual(songs[0]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[0]["SNG_TITLE"], "Prison Song")
|
||
self.assertEqual(len(songs[14]["MD5_ORIGIN"]), 32)
|
||
self.assertEqual(songs[1]["SNG_ID"], "15523770")
|
||
self.assertEqual(songs[1]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[1]["SNG_TITLE"], "Needles")
|
||
self.assertEqual(songs[2]["SNG_ID"], "15523772")
|
||
self.assertEqual(songs[2]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[2]["SNG_TITLE"], "Deer Dance")
|
||
self.assertEqual(songs[3]["SNG_ID"], "15523775")
|
||
self.assertEqual(songs[3]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[3]["SNG_TITLE"], "Jet Pilot")
|
||
self.assertEqual(songs[4]["SNG_ID"], "15523778")
|
||
self.assertEqual(songs[4]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[4]["SNG_TITLE"], "X")
|
||
self.assertEqual(songs[5]["SNG_ID"], "15523781")
|
||
self.assertEqual(songs[5]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[5]["SNG_TITLE"], "Chop Suey!")
|
||
self.assertEqual(songs[6]["SNG_ID"], "15523784")
|
||
self.assertEqual(songs[6]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[6]["SNG_TITLE"], "Bounce")
|
||
self.assertEqual(songs[7]["SNG_ID"], "15523788")
|
||
self.assertEqual(songs[7]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[7]["SNG_TITLE"], "Forest")
|
||
self.assertEqual(songs[8]["SNG_ID"], "15523790")
|
||
self.assertEqual(songs[8]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[8]["SNG_TITLE"], "ATWA")
|
||
self.assertEqual(songs[9]["SNG_ID"], "15523791")
|
||
self.assertEqual(songs[9]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[9]["SNG_TITLE"], "Science")
|
||
self.assertEqual(songs[10]["SNG_ID"], "15523792")
|
||
self.assertEqual(songs[10]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[10]["SNG_TITLE"], "Shimmy")
|
||
self.assertEqual(songs[11]["SNG_ID"], "15523793")
|
||
self.assertEqual(songs[11]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[11]["SNG_TITLE"], "Toxicity")
|
||
self.assertEqual(songs[12]["SNG_ID"], "15523796")
|
||
self.assertEqual(songs[12]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[12]["SNG_TITLE"], "Psycho")
|
||
self.assertEqual(songs[13]["SNG_ID"], "15523799")
|
||
self.assertEqual(songs[13]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[13]["SNG_TITLE"], "Aerials")
|
||
self.assertEqual(songs[14]["SNG_ID"], "15523803")
|
||
self.assertEqual(songs[14]["ART_NAME"], "System of A Down")
|
||
self.assertEqual(songs[14]["SNG_TITLE"], "Arto")
|
||
|
||
def test_get_invalid_track_infos_from_website(self):
|
||
with self.assertRaises(Deezer404Exception):
|
||
get_song_infos_from_deezer_website(TYPE_TRACK, "thisdoesnotexist")
|
||
|
||
def test_get_invalid_album_infos_from_website(self):
|
||
with self.assertRaises(Deezer404Exception):
|
||
get_song_infos_from_deezer_website(TYPE_ALBUM, "thisdoesnotexist")
|
||
# END: TEST get_song_infos_from_deezer_website
|
||
|
||
# BEGIN: parse_deezer_playlist
|
||
def _call_parse_valid_deezer_playlist(self, playlist):
|
||
playlist_name, songs = parse_deezer_playlist(playlist)
|
||
self.assertEqual(playlist_name, "test-playlist")
|
||
self.assertIsInstance(songs, list)
|
||
self.assertEqual(len(songs), 2)
|
||
for song in songs:
|
||
song_keys = list(song.keys())
|
||
for key in known_song_keys:
|
||
self.assertIn(key, song_keys)
|
||
self.assertEqual(songs[0]["SNG_ID"], "113951680")
|
||
self.assertEqual(songs[0]["ART_NAME"], 'Fredrika Stahl')
|
||
self.assertEqual(songs[0]["SNG_TITLE"], 'Make a Change')
|
||
# MD5_ORIGIN is only there if we are logged in
|
||
self.assertEqual(songs[0]["MD5_ORIGIN"], '57250623592ef44c8caeead79917f7e5')
|
||
|
||
def test_parse_valid_deezer_playlist_with_url(self):
|
||
playlist_url = "https://www.deezer.com/de/playlist/7639370122"
|
||
self._call_parse_valid_deezer_playlist(playlist_url)
|
||
|
||
def test_parse_valid_deezer_playlist_with_id(self):
|
||
playlist_id = "7639370122"
|
||
self._call_parse_valid_deezer_playlist(playlist_id)
|
||
|
||
def test_parse_invalid_deezer_playlist_with_id(self):
|
||
invalid_playlist_id = "999999999999999999999999999"
|
||
with self.assertRaises(DeezerApiException):
|
||
playlist_name, songs = parse_deezer_playlist(invalid_playlist_id)
|
||
|
||
def test_parse_invalid_input_for_deezer_playlist_with_id(self):
|
||
invalid_playlist_id = "!\"§$%&/((;-';k(()=+ü\\?"
|
||
with self.assertRaises(DeezerApiException):
|
||
playlist_name, songs = parse_deezer_playlist(invalid_playlist_id)
|
||
|
||
def test_parse_invalid_deezer_playlist_with_url(self):
|
||
invalid_playlist_url = "https://www.heise.de"
|
||
with self.assertRaises(DeezerApiException):
|
||
playlist_name, songs = parse_deezer_playlist(invalid_playlist_url)
|
||
# END: parse_deezer_playlist
|
||
|
||
# BEGIN: get_deezer_favorites
|
||
def test_get_deezer_favorites_userid_not_numeric(self):
|
||
user_id = "123notnumeric"
|
||
with self.assertRaises(Exception):
|
||
get_deezer_favorites(user_id)
|
||
|
||
def test_get_deezer_favorites_userid_api_error(self):
|
||
user_id = "0"
|
||
with self.assertRaises(Exception):
|
||
get_deezer_favorites(user_id)
|
||
|
||
def test_get_deezer_favorites_userid_valid(self):
|
||
user_id = "2517244282" # own of test (works)
|
||
songs = get_deezer_favorites(user_id)
|
||
self.assertIsInstance(songs, list)
|
||
for song in songs:
|
||
self.assertIsInstance(song, int)
|
||
# END: get_deezer_favorites
|
||
|
||
# BEGIN: download_song
|
||
@pytest.mark.skipif(config["deezer"].get("quality", "mp3") == "flac", reason="skiping deezer mp3 download. Quality set to flac")
|
||
def test_download_song_valid_mp3(self):
|
||
def check_mp3_metadata_deezer_song(test_file: Path):
|
||
self.assertTrue(test_song.exists())
|
||
try:
|
||
audio = MP3(test_file)
|
||
except MutagenError as e:
|
||
pytest.fail(f"File is not in mp3 format: {e}")
|
||
self.assertEqual(audio['TPE1'].text[0], 'Faber')
|
||
self.assertEqual(audio['TALB'].text[0], 'Alles Gute')
|
||
self.assertEqual(audio['TIT2'].text[0], 'Tausendfrankenlang')
|
||
self.assertEqual(audio['TRCK'].text[0], '4')
|
||
self.assertEqual(audio['TDRC'].text[0].year, 2015)
|
||
self.assertEqual(audio['TPOS'].text[0], '1')
|
||
self.assertTrue('APIC:Cover' in audio)
|
||
|
||
test_song = Path("/tmp/song-548935.mp3")
|
||
test_song.unlink(missing_ok=True)
|
||
|
||
song_infos = deezer_search("faber tausendfrankenlang", TYPE_TRACK)[0]
|
||
song = get_song_infos_from_deezer_website(TYPE_TRACK, song_infos['id'])
|
||
download_song(song, test_song.as_posix())
|
||
check_mp3_metadata_deezer_song(test_song)
|
||
test_song.unlink()
|
||
|
||
@pytest.mark.skipif(config["deezer"].get("quality", "mp3") == "mp3", reason="skiping deezer flac download. Quality set to mp3")
|
||
def test_download_song_valid_flac(self):
|
||
def check_flac_metadata_deezer_song(test_file: Path):
|
||
try:
|
||
audio = FLAC(test_file)
|
||
except MutagenError as e:
|
||
pytest.fail(f"File is not in mp3 format: {e}")
|
||
|
||
assert audio["artist"] == ["Faber",]
|
||
assert audio["title"] == ['Tausendfrankenlang',]
|
||
assert audio["album"] == ['Alles Gute',]
|
||
assert audio["tracknumber"] == ['4',]
|
||
assert audio["discnumber"] == ["1",]
|
||
assert len(audio.pictures) == 1
|
||
assert audio.pictures[0].data[:5] == b'\xff\xd8\xff\xe0\x00'
|
||
|
||
test_song = Path("/tmp/song-548935.flac")
|
||
test_song.unlink(missing_ok=True)
|
||
|
||
song_infos = deezer_search("faber tausendfrankenlang", TYPE_TRACK)[0]
|
||
song = get_song_infos_from_deezer_website(TYPE_TRACK, song_infos['id'])
|
||
download_song(song, test_song.as_posix())
|
||
check_flac_metadata_deezer_song(test_song)
|
||
test_song.unlink()
|
||
|
||
def test_download_song_invalid_song_type(self):
|
||
with self.assertRaises(AssertionError):
|
||
download_song("this sould be a dict", "/not/relevant/outputfile.mp3")
|
||
# END: download_song
|
||
|
||
|
||
class TestSpotifyMethods(unittest.TestCase):
|
||
# BEGIN parse_uri
|
||
def test_parse_url_spotify(self):
|
||
res = parse_uri("spotify:album:Hksdhfaif23ffushef9823")
|
||
self.assertEqual(res['type'], "album")
|
||
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
|
||
|
||
res = parse_uri("spotify:playlist:Hksdhfaif23ffushef9823")
|
||
self.assertEqual(res['type'], "playlist")
|
||
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
|
||
|
||
res = parse_uri("spotify:track:Hksdhfaif23ffushef9823")
|
||
self.assertEqual(res['type'], "track")
|
||
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
|
||
|
||
def test_parse_url_open_domain(self):
|
||
res = parse_uri("https://open.spotify.com/track/Hksdhfaif23ffushef9823")
|
||
self.assertEqual(res['type'], "track")
|
||
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
|
||
|
||
def test_parse_url_play_domain(self):
|
||
res = parse_uri("https://play.spotify.com/track/Hksdhfaif23ffushef9823")
|
||
self.assertEqual(res['type'], "track")
|
||
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
|
||
|
||
def test_parse_url_embed_domain(self):
|
||
res = parse_uri("https://embed.spotify.com/?uri=spotify:track:Hksdhfaif23ffushef9823")
|
||
self.assertEqual(res['type'], "track")
|
||
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
|
||
# END parse_uri
|
||
|
||
# BEGIN test invalid
|
||
def test_spotify_parser_invalid_playlist_id(self):
|
||
playlist_id = "thisdoesnotexist"
|
||
with self.assertRaises(SpotifyWebsiteParserException):
|
||
get_songs_from_spotify_website(playlist_id, None)
|
||
|
||
def test_spotify_parser_invalid_playlist_url(self):
|
||
playlist_url = "https://www.heise.de"
|
||
with self.assertRaises(SpotifyInvalidUrlException):
|
||
get_songs_from_spotify_website(playlist_url, None)
|
||
# END test invalid
|
||
|
||
# BEGIN valid stuff
|
||
def check_parse_spotify_playlist_website(self, playlist):
|
||
songs = get_songs_from_spotify_website(playlist, None)
|
||
self.assertIn("Cyndi Lauper Time After Time", songs)
|
||
|
||
def test_spotify_parser_valid_playlist_embed_url(self):
|
||
playlist_url = "https://open.spotify.com/embed/playlist/0wl9Q3oedquNlBAJ4MGZtS"
|
||
self.check_parse_spotify_playlist_website(playlist_url)
|
||
|
||
def test_spotify_parser_valid_playlist_url(self):
|
||
playlist_url = "https://open.spotify.com/playlist/0wl9Q3oedquNlBAJ4MGZtS"
|
||
self.check_parse_spotify_playlist_website(playlist_url)
|
||
|
||
def test_spotify_parser_valid_playlist_id(self):
|
||
playlist_id = "0wl9Q3oedquNlBAJ4MGZtS"
|
||
self.check_parse_spotify_playlist_website(playlist_id)
|
||
# END valid stuff
|
||
|
||
|
||
class TestYoutubeMethods(unittest.TestCase):
|
||
is_github_ci = len(os.environ.get("GITHUB_ACTION", "")) > 0
|
||
|
||
@pytest.mark.xfail(is_github_ci, reason="Fails with 'Sign in to confirm you’re not a bot. This helps protect our community. Learn more'", raises=YoutubeDLFailedException)
|
||
def test_youtube_dl_valid_url(self):
|
||
out_file = Path("/tmp/Pharrell Williams - Happy (Video).mp3")
|
||
out_file.unlink(missing_ok=True)
|
||
url = "https://www.youtube.com/watch?v=ZbZSe6N_BXs"
|
||
destination_file = youtubedl_download(url, "/tmp")
|
||
self.assertEqual(out_file.as_posix(), destination_file)
|
||
self.assertTrue(out_file.exists())
|
||
try:
|
||
audio = MP3(out_file)
|
||
self.assertEqual(audio['TPE1'].text[0], 'Pharrell Williams')
|
||
self.assertEqual(audio['TIT2'].text[0], 'Pharrell Williams - Happy (Video)')
|
||
except MutagenError as e:
|
||
pytest.fail(f"File is not in mp3 format: {e}")
|
||
out_file.unlink()
|
||
|
||
def test_youtube_dl_invalid_url(self):
|
||
url = "https://www.heise.de"
|
||
with self.assertRaises(YoutubeDLFailedException):
|
||
youtubedl_download(url, "/tmp")
|
||
|
||
@pytest.mark.xfail(is_github_ci, reason="Fails with 'Sign in to confirm you’re not a bot. This helps protect our community. Learn more'", raises=YoutubeDLFailedException)
|
||
def test_youtube_dl_command_execution(self):
|
||
url = "https://www.youtube.com/watch?v=ZbZSe6N_BXs&$(touch /tmp/pwned.txt)"
|
||
try:
|
||
youtubedl_download(url, "/tmp")
|
||
except DownloadedFileNotFoundException:
|
||
pytest.xfail("Fails if the file already exists from the previous test. TODO")
|
||
pwn_succeeded = os.path.exists("/tmp/pwned.txt")
|
||
self.assertEqual(pwn_succeeded, False)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
unittest.main()
|