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()