[WIP] Monkeypatch tests (#448)

* Parameterize test_internals.py

* Create test_spotify_tools.py

* Monkeypatch pafy.download

* Monkeypatch YouTube search page

* Replace globals with fixtures

* Add missing urllib import, re-ordering and rename test_with_metadata.py

* Avoid creating temp directory in current working directory during test

* Update CHANGES.md
This commit is contained in:
Ritiek Malhotra
2018-12-26 17:15:56 +05:30
committed by GitHub
parent bfe958dadc
commit 51b01fc448
16 changed files with 523 additions and 377 deletions

View File

@@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Ability to pass multiple tracks with `-s` option ([@ritiek](https://github.com/ritiek)) (#442)
### Changed
- Change FFmpeg to use the built-in encoder `aac` instead of 3rd party `libfdk-aac` which does not
ship with the apt package ([@ritiek](https://github.com/ritiek)) (#448)
- Monkeypatch ever-changing network-relying tests ([@ritiek](https://github.com/ritiek)) (#448)
- Correct `.m4a` container before writing metadata so metadata fields shows up properly in
media players (especially iTunes) ([@ritiek](https://github.com/ritiek) with thanks to [@Amit-L](https://github.com/Amit-L)!) (#453)
- Refactored core downloading module ([@ritiek](https://github.com/ritiek)) (#410)

View File

@@ -3,7 +3,8 @@ import os
from logzero import logger as log
"""What are the differences and similarities between ffmpeg, libav, and avconv?
"""
What are the differences and similarities between ffmpeg, libav, and avconv?
https://stackoverflow.com/questions/9477115
ffmeg encoders high to lower quality
@@ -25,10 +26,10 @@ def song(input_song, output_song, folder, avconv=False, trim_silence=False):
else:
return 0
if avconv:
exit_code = convert.with_avconv()
exit_code, command = convert.with_avconv()
else:
exit_code = convert.with_ffmpeg()
return exit_code
exit_code, command = convert.with_ffmpeg()
return exit_code, command
class Converter:
@@ -59,7 +60,7 @@ class Converter:
log.warning("--trim-silence not supported with avconv")
log.debug(command)
return subprocess.call(command)
return subprocess.call(command), command
def with_ffmpeg(self):
ffmpeg_pre = "ffmpeg -y "
@@ -84,7 +85,7 @@ class Converter:
if output_ext == ".mp3":
ffmpeg_params = "-codec:a libmp3lame -ar 44100 "
elif output_ext == ".m4a":
ffmpeg_params = "-cutoff 20000 -codec:a libfdk_aac -ar 44100 "
ffmpeg_params = "-cutoff 20000 -codec:a aac -ar 44100 "
if output_ext == ".flac":
ffmpeg_params = "-codec:a flac -ar 44100 "
@@ -104,4 +105,4 @@ class Converter:
)
log.debug(command)
return subprocess.call(command)
return subprocess.call(command), command

View File

@@ -1,3 +1,8 @@
import spotipy
import urllib
import os
from logzero import logger as log
from spotdl import const
from spotdl import metadata
from spotdl import convert
@@ -5,10 +10,6 @@ from spotdl import internals
from spotdl import spotify_tools
from spotdl import youtube_tools
import spotipy
from logzero import logger as log
import os
class CheckExists:
def __init__(self, music_file, meta_tags=None):

View File

@@ -1,14 +1,14 @@
import appdirs
from spotdl import internals
from logzero import logger as log
import appdirs
import logging
import yaml
import argparse
import mimetypes
import os
from spotdl import internals
_LOG_LEVELS_STR = ["INFO", "WARNING", "ERROR", "DEBUG"]

View File

@@ -1,6 +1,6 @@
from logzero import logger as log
import os
import sys
from logzero import logger as log
from spotdl import const

View File

@@ -2,10 +2,11 @@ from mutagen.easyid3 import EasyID3
from mutagen.id3 import ID3, TORY, TYER, TPUB, APIC, USLT, COMM
from mutagen.mp4 import MP4, MP4Cover
from mutagen.flac import Picture, FLAC
from logzero import logger as log
from spotdl.const import TAG_PRESET, M4A_TAG_PRESET
import urllib.request
from logzero import logger as log
from spotdl.const import TAG_PRESET, M4A_TAG_PRESET
def compare(music_file, metadata):

View File

@@ -1,5 +1,11 @@
#!/usr/bin/env python3
import sys
import platform
import pprint
import logzero
from logzero import logger as log
from spotdl import __version__
from spotdl import const
from spotdl import handle
@@ -7,11 +13,6 @@ from spotdl import internals
from spotdl import spotify_tools
from spotdl import youtube_tools
from spotdl import downloader
from logzero import logger as log
import logzero
import sys
import platform
import pprint
def debug_sys_info():

View File

@@ -1,15 +1,15 @@
import spotipy
import spotipy.oauth2 as oauth2
import lyricwikia
from logzero import logger as log
from spotdl import internals
from slugify import slugify
from titlecase import titlecase
from logzero import logger as log
import pprint
import sys
from spotdl import internals
def generate_token():
""" Generate the token. Please respect these credentials :) """
@@ -29,8 +29,8 @@ def refresh_token():
# token is mandatory when using Spotify's API
# https://developer.spotify.com/news-stories/2017/01/27/removing-unauthenticated-calls-to-the-web-api/
token = generate_token()
spotify = spotipy.Spotify(auth=token)
_token = generate_token()
spotify = spotipy.Spotify(auth=_token)
def generate_metadata(raw_song):
@@ -87,12 +87,6 @@ def generate_metadata(raw_song):
return meta_tags
def write_user_playlist(username, text_file=None):
links = get_playlists(username=username)
playlist = internals.input_link(links)
return write_playlist(playlist, text_file)
def get_playlists(username):
""" Fetch user playlists when using the -u option. """
playlists = spotify.user_playlists(username)
@@ -121,6 +115,12 @@ def get_playlists(username):
return links
def write_user_playlist(username, text_file=None):
links = get_playlists(username=username)
playlist = internals.input_link(links)
return write_playlist(playlist, text_file)
def fetch_playlist(playlist):
try:
playlist_id = internals.extract_spotify_id(playlist)
@@ -154,7 +154,7 @@ def fetch_album(album):
return album
def fetch_album_from_artist(artist_url, album_type="album"):
def fetch_albums_from_artist(artist_url, album_type="album"):
"""
This funcction returns all the albums from a give artist_url using the US
market
@@ -191,7 +191,7 @@ def write_all_albums_from_artist(artist_url, text_file=None):
album_base_url = "https://open.spotify.com/album/"
# fetching all default albums
albums = fetch_album_from_artist(artist_url)
albums = fetch_albums_from_artist(artist_url)
# if no file if given, the default save file is in the current working
# directory with the name of the artist
@@ -204,7 +204,7 @@ def write_all_albums_from_artist(artist_url, text_file=None):
write_album(album_base_url + album["id"], text_file=text_file)
# fetching all single albums
singles = fetch_album_from_artist(artist_url, album_type="single")
singles = fetch_albums_from_artist(artist_url, album_type="single")
for single in singles:
log.info("Fetching single: " + single["name"])

View File

@@ -1,15 +1,15 @@
from bs4 import BeautifulSoup
import urllib
import pafy
from slugify import slugify
from logzero import logger as log
import os
from spotdl import spotify_tools
from spotdl import internals
from spotdl import const
import os
# Fix download speed throttle on short duration tracks
# Read more on mps-youtube/pafy#199
pafy.g.opener.addheaders.append(("Range", "bytes=0-"))
@@ -75,6 +75,8 @@ def generate_m3u(track_file):
log.info("Generating {0} from {1} YouTube URLs".format(target_file, total_tracks))
with open(target_file, "w") as output_file:
output_file.write("#EXTM3U\n\n")
videos = []
for n, track in enumerate(tracks, 1):
content, _ = match_video_and_metadata(track)
if content is None:
@@ -94,6 +96,9 @@ def generate_m3u(track_file):
log.debug(m3u_key)
with open(target_file, "a") as output_file:
output_file.write(m3u_key)
videos.append(content.watchv_url)
return videos
def download_song(file_name, content):
@@ -240,7 +245,7 @@ class GenerateYouTubeURL:
search_url = generate_search_url(self.search_query)
log.debug("Opening URL: {0}".format(search_url))
item = urllib.request.urlopen(search_url).read()
item = self._fetch_response(search_url).read()
items_parse = BeautifulSoup(item, "html.parser")
videos = []
@@ -319,3 +324,11 @@ class GenerateYouTubeURL:
return self._best_match(videos)
return videos
@staticmethod
def _fetch_response(url):
# XXX: This method exists only because it helps us indirectly
# monkey patch `urllib.request.open`, directly monkey patching
# `urllib.request.open` causes us to end up in an infinite recursion
# during the test since `urllib.request.open` would monkeypatch itself.
return urllib.request.urlopen(url)

View File

@@ -0,0 +1,195 @@
import urllib
import subprocess
import os
from spotdl import const
from spotdl import internals
from spotdl import spotify_tools
from spotdl import youtube_tools
from spotdl import convert
from spotdl import metadata
from spotdl import downloader
import pytest
import loader
loader.load_defaults()
SPOTIFY_TRACK_URL = "https://open.spotify.com/track/3SipFlNddvL0XNZRLXvdZD"
EXPECTED_YOUTUBE_TITLE = "Janji - Heroes Tonight (feat. Johnning) [NCS Release]"
EXPECTED_SPOTIFY_TITLE = "Janji - Heroes Tonight"
EXPECTED_YOUTUBE_URL = "http://youtube.com/watch?v=3nQNiWdeH2Q"
# GIST_URL is the monkeypatched version of: https://www.youtube.com/results?search_query=janji+-+heroes
# so that we get same results even if YouTube changes the list/order of videos on their page.
GIST_URL = "https://gist.githubusercontent.com/ritiek/e731338e9810e31c2f00f13c249a45f5/raw/c11a27f3b5d11a8d082976f1cdd237bd605ec2c2/search_results.html"
def pytest_namespace():
# XXX: We override the value of `content_fixture` later in the tests.
# We do not use an acutal @pytest.fixture because it does not accept
# the monkeypatch parameter and we need to monkeypatch the network
# request before creating the Pafy object.
return {"content_fixture": None}
@pytest.fixture(scope="module")
def metadata_fixture():
meta_tags = spotify_tools.generate_metadata(SPOTIFY_TRACK_URL)
return meta_tags
def test_metadata(metadata_fixture):
expect_number = 23
assert len(metadata_fixture) == expect_number
class TestFileFormat:
def test_with_spaces(self, metadata_fixture):
title = internals.format_string(const.args.file_format, metadata_fixture)
assert title == EXPECTED_SPOTIFY_TITLE
def test_without_spaces(self, metadata_fixture):
const.args.no_spaces = True
title = internals.format_string(const.args.file_format, metadata_fixture)
assert title == EXPECTED_SPOTIFY_TITLE.replace(" ", "_")
def monkeypatch_youtube_search_page(*args, **kwargs):
fake_urlopen = urllib.request.urlopen(GIST_URL)
return fake_urlopen
def test_youtube_url(metadata_fixture, monkeypatch):
monkeypatch.setattr(youtube_tools.GenerateYouTubeURL, "_fetch_response", monkeypatch_youtube_search_page)
url = youtube_tools.generate_youtube_url(SPOTIFY_TRACK_URL, metadata_fixture)
assert url == EXPECTED_YOUTUBE_URL
def test_youtube_title(metadata_fixture, monkeypatch):
monkeypatch.setattr(youtube_tools.GenerateYouTubeURL, "_fetch_response", monkeypatch_youtube_search_page)
content = youtube_tools.go_pafy(SPOTIFY_TRACK_URL, metadata_fixture)
pytest.content_fixture = content
title = youtube_tools.get_youtube_title(content)
assert title == EXPECTED_YOUTUBE_TITLE
@pytest.fixture(scope="module")
def filename_fixture(metadata_fixture):
songname = internals.format_string(const.args.file_format, metadata_fixture)
filename = internals.sanitize_title(songname)
return filename
def test_check_track_exists_before_download(tmpdir, metadata_fixture, filename_fixture):
expect_check = False
const.args.folder = str(tmpdir)
# prerequisites for determining filename
track_existence = downloader.CheckExists(filename_fixture, metadata_fixture)
check = track_existence.already_exists(SPOTIFY_TRACK_URL)
assert check == expect_check
class TestDownload:
def blank_audio_generator(self, filepath):
if filepath.endswith(".m4a"):
cmd = "ffmpeg -f lavfi -i anullsrc -t 1 -c:a aac {}".format(filepath)
elif filepath.endswith(".webm"):
cmd = "ffmpeg -f lavfi -i anullsrc -t 1 -c:a libopus {}".format(filepath)
subprocess.call(cmd.split(" "))
def test_m4a(self, monkeypatch, filename_fixture):
expect_download = True
monkeypatch.setattr("pafy.backend_shared.BaseStream.download", self.blank_audio_generator)
download = youtube_tools.download_song(filename_fixture + ".m4a", pytest.content_fixture)
assert download == expect_download
def test_webm(self, monkeypatch, filename_fixture):
expect_download = True
monkeypatch.setattr("pafy.backend_shared.BaseStream.download", self.blank_audio_generator)
download = youtube_tools.download_song(filename_fixture + ".webm", pytest.content_fixture)
assert download == expect_download
class TestFFmpeg:
def test_convert_from_webm_to_mp3(self, filename_fixture, monkeypatch):
expect_command = "ffmpeg -y -i {0}.webm -codec:a libmp3lame -ar 44100 -b:a 192k -vn {0}.mp3".format(os.path.join(const.args.folder, filename_fixture))
_, command = convert.song(
filename_fixture + ".webm", filename_fixture + ".mp3", const.args.folder
)
assert ' '.join(command) == expect_command
def test_convert_from_webm_to_m4a(self, filename_fixture, monkeypatch):
expect_command = "ffmpeg -y -i {0}.webm -cutoff 20000 -codec:a aac -ar 44100 -b:a 192k -vn {0}.m4a".format(os.path.join(const.args.folder, filename_fixture))
_, command = convert.song(
filename_fixture + ".webm", filename_fixture + ".m4a", const.args.folder
)
assert ' '.join(command) == expect_command
def test_convert_from_m4a_to_mp3(self, filename_fixture, monkeypatch):
expect_command = "ffmpeg -y -i {0}.m4a -codec:v copy -codec:a libmp3lame -ar 44100 -b:a 192k -vn {0}.mp3".format(os.path.join(const.args.folder, filename_fixture))
_, command = convert.song(
filename_fixture + ".m4a", filename_fixture + ".mp3", const.args.folder
)
assert ' '.join(command) == expect_command
def test_convert_from_m4a_to_webm(self, filename_fixture, monkeypatch):
expect_command = "ffmpeg -y -i {0}.m4a -codec:a libopus -vbr on -b:a 192k -vn {0}.webm".format(os.path.join(const.args.folder, filename_fixture))
_, command = convert.song(
filename_fixture + ".m4a", filename_fixture + ".webm", const.args.folder
)
assert ' '.join(command) == expect_command
def test_convert_from_m4a_to_flac(self, filename_fixture, monkeypatch):
expect_command = "ffmpeg -y -i {0}.m4a -codec:a flac -ar 44100 -b:a 192k -vn {0}.flac".format(os.path.join(const.args.folder, filename_fixture))
_, command = convert.song(
filename_fixture + ".m4a", filename_fixture + ".flac", const.args.folder
)
assert ' '.join(command) == expect_command
class TestAvconv:
def test_convert_from_m4a_to_mp3(self, filename_fixture):
expect_command = "avconv -loglevel debug -i {0}.m4a -ab 192k {0}.mp3 -y".format(os.path.join(const.args.folder, filename_fixture))
_, command = convert.song(
filename_fixture + ".m4a", filename_fixture + ".mp3", const.args.folder, avconv=True
)
assert ' '.join(command) == expect_command
@pytest.fixture(scope="module")
def trackpath_fixture(filename_fixture):
trackpath = os.path.join(const.args.folder, filename_fixture)
return trackpath
class TestEmbedMetadata:
def test_embed_in_mp3(self, metadata_fixture, trackpath_fixture):
expect_embed = True
embed = metadata.embed(trackpath_fixture + ".mp3", metadata_fixture)
assert embed == expect_embed
def test_embed_in_m4a(self, metadata_fixture, trackpath_fixture):
expect_embed = True
embed = metadata.embed(trackpath_fixture + ".m4a", metadata_fixture)
os.remove(trackpath_fixture + ".m4a")
assert embed == expect_embed
def test_embed_in_webm(self, metadata_fixture, trackpath_fixture):
expect_embed = False
embed = metadata.embed(trackpath_fixture + ".webm", metadata_fixture)
os.remove(trackpath_fixture + ".webm")
assert embed == expect_embed
def test_embed_in_flac(self, metadata_fixture, trackpath_fixture):
expect_embed = True
embed = metadata.embed(trackpath_fixture + ".flac", metadata_fixture)
os.remove(trackpath_fixture + ".flac")
assert embed == expect_embed
def test_check_track_exists_after_download(metadata_fixture, filename_fixture, trackpath_fixture):
expect_check = True
track_existence = downloader.CheckExists(filename_fixture, metadata_fixture)
check = track_existence.already_exists(SPOTIFY_TRACK_URL)
os.remove(trackpath_fixture + ".mp3")
assert check == expect_check

View File

@@ -25,28 +25,37 @@ def test_log_str_to_int():
assert levels == expect_levels
@pytest.fixture(scope="module")
def config_path_fixture(tmpdir_factory):
config_path = os.path.join(str(tmpdir_factory.mktemp("config")),
"config.yml")
return config_path
@pytest.fixture(scope="module")
def modified_config_fixture():
modified_config = dict(handle.default_conf)
return modified_config
class TestConfig:
def test_default_config(self, tmpdir):
def test_default_config(self, config_path_fixture):
expect_config = handle.default_conf["spotify-downloader"]
global config_path
config_path = os.path.join(str(tmpdir), "config.yml")
config = handle.get_config(config_path)
config = handle.get_config(config_path_fixture)
assert config == expect_config
def test_modified_config(self):
global modified_config
modified_config = dict(handle.default_conf)
modified_config["spotify-downloader"]["file-format"] = "just_a_test"
merged_config = handle.merge(handle.default_conf, modified_config)
assert merged_config == modified_config
def test_modified_config(self, modified_config_fixture):
modified_config_fixture["spotify-downloader"]["file-format"] = "just_a_test"
merged_config = handle.merge(handle.default_conf, modified_config_fixture)
assert merged_config == modified_config_fixture
def test_custom_config_path(self, tmpdir):
def test_custom_config_path(self, config_path_fixture, modified_config_fixture):
parser = argparse.ArgumentParser()
with open(config_path, "w") as config_file:
yaml.dump(modified_config, config_file, default_flow_style=False)
overridden_config = handle.override_config(config_path, parser, raw_args="")
with open(config_path_fixture, "w") as config_file:
yaml.dump(modified_config_fixture, config_file, default_flow_style=False)
overridden_config = handle.override_config(config_path_fixture, parser, raw_args="")
modified_values = [
str(value) for value in modified_config["spotify-downloader"].values()
str(value) for value in modified_config_fixture["spotify-downloader"].values()
]
overridden_config.folder = os.path.realpath(overridden_config.folder)
overridden_values = [

View File

@@ -80,6 +80,26 @@ STRING_IDS_TEST_TABLE = [
]
FROM_SECONDS_TEST_TABLE = [
(35, "35"),
(23, "23"),
(158, "2:38"),
(263, "4:23"),
(4562, "1:16:02"),
(26762, "7:26:02")
]
TO_SECONDS_TEST_TABLE = [
("0:23", 23),
("0:45", 45),
("2:19", 139),
("3:33", 213),
("7:38", 458),
("1:30:05", 5405),
]
def test_default_music_directory():
if sys.platform.startswith("linux"):
output = subprocess.check_output(["xdg-user-dir", "MUSIC"])
@@ -92,68 +112,39 @@ def test_default_music_directory():
assert directory == expect_directory
@pytest.fixture(scope="module")
def directory_fixture(tmpdir_factory):
dir_path = os.path.join(str(tmpdir_factory.mktemp("tmpdir")),
"filter_this_folder")
return dir_path
class TestPathFilterer:
def test_create_directory(self, tmpdir):
def test_create_directory(self, directory_fixture):
expect_path = True
global folder_path
folder_path = os.path.join(str(tmpdir), "filter_this_folder")
internals.filter_path(folder_path)
is_path = os.path.isdir(folder_path)
internals.filter_path(directory_fixture)
is_path = os.path.isdir(directory_fixture)
assert is_path == expect_path
def test_remove_temp_files(self, tmpdir):
def test_remove_temp_files(self, directory_fixture):
expect_file = False
file_path = os.path.join(folder_path, "pesky_file.temp")
file_path = os.path.join(directory_fixture, "pesky_file.temp")
open(file_path, "a")
internals.filter_path(folder_path)
internals.filter_path(directory_fixture)
is_file = os.path.isfile(file_path)
assert is_file == expect_file
class TestVideoTimeFromSeconds:
def test_from_seconds(self):
expect_duration = "35"
duration = internals.videotime_from_seconds(35)
assert duration == expect_duration
def test_from_minutes(self):
expect_duration = "2:38"
duration = internals.videotime_from_seconds(158)
assert duration == expect_duration
def test_from_hours(self):
expect_duration = "1:16:02"
duration = internals.videotime_from_seconds(4562)
assert duration == expect_duration
@pytest.mark.parametrize("sec_duration, str_duration", FROM_SECONDS_TEST_TABLE)
def test_video_time_from_seconds(sec_duration, str_duration):
duration = internals.videotime_from_seconds(sec_duration)
assert duration == str_duration
class TestGetSeconds:
def test_from_seconds(self):
expect_secs = 45
secs = internals.get_sec("0:45")
assert secs == expect_secs
secs = internals.get_sec("0.45")
assert secs == expect_secs
def test_from_minutes(self):
expect_secs = 213
secs = internals.get_sec("3.33")
assert secs == expect_secs
secs = internals.get_sec("3:33")
assert secs == expect_secs
def test_from_hours(self):
expect_secs = 5405
secs = internals.get_sec("1.30.05")
assert secs == expect_secs
secs = internals.get_sec("1:30:05")
assert secs == expect_secs
def test_raise_error(self):
with pytest.raises(ValueError):
internals.get_sec("10*05")
with pytest.raises(ValueError):
internals.get_sec("02,28,46")
@pytest.mark.parametrize("str_duration, sec_duration", TO_SECONDS_TEST_TABLE)
def test_get_seconds_from_video_time(str_duration, sec_duration):
secs = internals.get_sec(str_duration)
assert secs == sec_duration
@pytest.mark.parametrize("duplicates, expected", DUPLICATE_TRACKS_TEST_TABLE)
@@ -170,3 +161,21 @@ def test_get_unique_tracks(tmpdir, duplicates, expected):
def test_extract_spotify_id(input_str, expected_spotify_id):
spotify_id = internals.extract_spotify_id(input_str)
assert spotify_id == expected_spotify_id
def test_trim(tmpdir):
text_file = os.path.join(str(tmpdir), "test_trim.txt")
with open(text_file, "w") as track_file:
track_file.write("ncs - spectre\nncs - heroes\nncs - hope")
with open(text_file, "r") as track_file:
tracks = track_file.readlines()
expect_number = len(tracks) - 1
expect_track = tracks[0]
track = internals.trim_song(text_file)
with open(text_file, "r") as track_file:
number = len(track_file.readlines())
assert expect_number == number and expect_track == track

View File

@@ -1,90 +0,0 @@
import builtins
import os
from spotdl import spotify_tools
from spotdl import youtube_tools
from spotdl import const
from spotdl import spotdl
import loader
USERNAME = "uqlakumu7wslkoen46s5bulq0"
PLAYLIST_URL = "https://open.spotify.com/playlist/0fWBMhGh38y0wsYWwmM9Kt"
ALBUM_URL = "https://open.spotify.com/album/499J8bIsEnU7DSrosFDJJg"
ARTIST_URL = "https://open.spotify.com/artist/4dpARuHxo51G3z768sgnrY"
loader.load_defaults()
def test_user_playlists(tmpdir, monkeypatch):
expect_tracks = 17
text_file = os.path.join(str(tmpdir), "test_us.txt")
monkeypatch.setattr("builtins.input", lambda x: 1)
spotify_tools.write_user_playlist(USERNAME, text_file)
with open(text_file, "r") as f:
tracks = len(f.readlines())
assert tracks == expect_tracks
def test_playlist(tmpdir):
expect_tracks = 14
text_file = os.path.join(str(tmpdir), "test_pl.txt")
spotify_tools.write_playlist(PLAYLIST_URL, text_file)
with open(text_file, "r") as f:
tracks = len(f.readlines())
assert tracks == expect_tracks
def test_album(tmpdir):
expect_tracks = 15
text_file = os.path.join(str(tmpdir), "test_al.txt")
spotify_tools.write_album(ALBUM_URL, text_file)
with open(text_file, "r") as f:
tracks = len(f.readlines())
assert tracks == expect_tracks
def test_m3u(tmpdir):
expect_m3u = (
"#EXTM3U\n\n"
"#EXTINF:31,Eminem - 01 - Eminem - Curtains Up (Skit)\n"
"http://www.youtube.com/watch?v=qk13SFlwG9A\n"
"#EXTINF:226,Alan Walker - Spectre [NCS Release]\n"
"http://www.youtube.com/watch?v=AOeY-nDp7hI\n"
)
m3u_track_file = os.path.join(str(tmpdir), "m3u_test.txt")
with open(m3u_track_file, "w") as track_file:
track_file.write("\nhttps://open.spotify.com/track/2nT5m433s95hvYJH4S7ont")
track_file.write("\nhttp://www.youtube.com/watch?v=AOeY-nDp7hI")
youtube_tools.generate_m3u(m3u_track_file)
m3u_file = "{}.m3u".format(m3u_track_file.split(".")[0])
with open(m3u_file, "r") as m3u_in:
m3u = m3u_in.readlines()
assert "".join(m3u) == expect_m3u
def test_all_albums(tmpdir):
# current number of tracks on spotify since as of 10/10/2018
# in US market only
expect_tracks = 49
global text_file
text_file = os.path.join(str(tmpdir), "test_ab.txt")
spotify_tools.write_all_albums_from_artist(ARTIST_URL, text_file)
with open(text_file, "r") as f:
tracks = len(f.readlines())
assert tracks == expect_tracks
def test_trim():
with open(text_file, "r") as track_file:
tracks = track_file.readlines()
expect_number = len(tracks) - 1
expect_track = tracks[0]
track = spotdl.internals.trim_song(text_file)
with open(text_file, "r") as track_file:
number = len(track_file.readlines())
assert expect_number == number and expect_track == track

137
test/test_spotify_tools.py Normal file
View File

@@ -0,0 +1,137 @@
from spotdl import spotify_tools
import os
import pytest
def test_generate_token():
token = spotify_tools.generate_token()
assert len(token) == 83
def test_refresh_token():
old_instance = spotify_tools.spotify
spotify_tools.refresh_token()
new_instance = spotify_tools.spotify
assert not old_instance == new_instance
class TestGenerateMetadata:
@pytest.fixture(scope="module")
def metadata_fixture(self):
metadata = spotify_tools.generate_metadata("ncs - spectre")
return metadata
def test_len(self, metadata_fixture):
assert len(metadata_fixture) == 23
def test_trackname(self, metadata_fixture):
assert metadata_fixture["name"] == "Spectre"
def test_artist(self, metadata_fixture):
assert metadata_fixture["artists"][0]["name"] == "Alan Walker"
def test_duration(self, metadata_fixture):
assert metadata_fixture["duration"] == 230.634
def test_get_playlists():
expect_playlist_ids = [ "34gWCK8gVeYDPKcctB6BQJ",
"04wTU2c2WNQG9XE5oSLYfj",
"0fWBMhGh38y0wsYWwmM9Kt" ]
expect_playlists = [ "https://open.spotify.com/playlist/" + playlist_id
for playlist_id in expect_playlist_ids ]
playlists = spotify_tools.get_playlists("uqlakumu7wslkoen46s5bulq0")
assert playlists == expect_playlists
def test_write_user_playlist(tmpdir, monkeypatch):
expect_tracks = 17
text_file = os.path.join(str(tmpdir), "test_us.txt")
monkeypatch.setattr("builtins.input", lambda x: 1)
spotify_tools.write_user_playlist("uqlakumu7wslkoen46s5bulq0", text_file)
with open(text_file, "r") as f:
tracks = len(f.readlines())
assert tracks == expect_tracks
class TestFetchPlaylist:
@pytest.fixture(scope="module")
def playlist_fixture(self):
playlist = spotify_tools.fetch_playlist("https://open.spotify.com/playlist/0fWBMhGh38y0wsYWwmM9Kt")
return playlist
def test_name(self, playlist_fixture):
assert playlist_fixture["name"] == "special_test_playlist"
def test_tracks(self, playlist_fixture):
assert playlist_fixture["tracks"]["total"] == 14
def test_write_playlist(tmpdir):
expect_tracks = 14
text_file = os.path.join(str(tmpdir), "test_pl.txt")
spotify_tools.write_playlist("https://open.spotify.com/playlist/0fWBMhGh38y0wsYWwmM9Kt", text_file)
with open(text_file, "r") as f:
tracks = len(f.readlines())
assert tracks == expect_tracks
# XXX: Mock this test off if it fails in future
class TestFetchAlbum:
@pytest.fixture(scope="module")
def album_fixture(self):
album = spotify_tools.fetch_album("https://open.spotify.com/album/499J8bIsEnU7DSrosFDJJg")
return album
def test_name(self, album_fixture):
assert album_fixture["name"] == "NCS: Infinity"
def test_tracks(self, album_fixture):
assert album_fixture["tracks"]["total"] == 15
# XXX: Mock this test off if it fails in future
class TestFetchAlbumsFromArtist:
@pytest.fixture(scope="module")
def albums_from_artist_fixture(self):
albums = spotify_tools.fetch_albums_from_artist("https://open.spotify.com/artist/7oPftvlwr6VrsViSDV7fJY")
return albums
def test_len(self, albums_from_artist_fixture):
assert len(albums_from_artist_fixture) == 18
def test_zeroth_album_name(self, albums_from_artist_fixture):
assert albums_from_artist_fixture[0]["name"] == "Revolution Radio"
def test_zeroth_album_tracks(self, albums_from_artist_fixture):
assert albums_from_artist_fixture[0]["total_tracks"] == 12
def test_fist_album_name(self, albums_from_artist_fixture):
assert albums_from_artist_fixture[1]["name"] == "Demolicious"
def test_first_album_tracks(self, albums_from_artist_fixture):
assert albums_from_artist_fixture[0]["total_tracks"] == 12
# XXX: Mock this test off if it fails in future
def test_write_all_albums_from_artist(tmpdir):
# current number of tracks on spotify since as of 10/10/2018
# in US market only
expect_tracks = 49
text_file = os.path.join(str(tmpdir), "test_ab.txt")
spotify_tools.write_all_albums_from_artist("https://open.spotify.com/artist/4dpARuHxo51G3z768sgnrY", text_file)
with open(text_file, "r") as f:
tracks = len(f.readlines())
assert tracks == expect_tracks
def test_write_album(tmpdir):
expect_tracks = 15
text_file = os.path.join(str(tmpdir), "test_al.txt")
spotify_tools.write_album("https://open.spotify.com/album/499J8bIsEnU7DSrosFDJJg", text_file)
with open(text_file, "r") as f:
tracks = len(f.readlines())
assert tracks == expect_tracks

View File

@@ -1,153 +0,0 @@
import os
from spotdl import const
from spotdl import internals
from spotdl import spotify_tools
from spotdl import youtube_tools
from spotdl import convert
from spotdl import metadata
from spotdl import downloader
import loader
loader.load_defaults()
TRACK_URL = "https://open.spotify.com/track/2nT5m433s95hvYJH4S7ont"
EXPECTED_TITLE = "Eminem - Curtains Up"
EXPECTED_YT_TITLE = "Eminem - 01 - Eminem - Curtains Up (Skit)"
EXPECTED_YT_URL = "http://youtube.com/watch?v=qk13SFlwG9A"
def test_metadata():
expect_number = 23
global meta_tags
meta_tags = spotify_tools.generate_metadata(TRACK_URL)
assert len(meta_tags) == expect_number
class TestFileFormat:
def test_with_spaces(self):
title = internals.format_string(const.args.file_format, meta_tags)
assert title == EXPECTED_TITLE
def test_without_spaces(self):
const.args.no_spaces = True
title = internals.format_string(const.args.file_format, meta_tags)
assert title == EXPECTED_TITLE.replace(" ", "_")
def test_youtube_url():
url = youtube_tools.generate_youtube_url(TRACK_URL, meta_tags)
assert url == EXPECTED_YT_URL
def test_youtube_title():
global content
content = youtube_tools.go_pafy(TRACK_URL, meta_tags)
title = youtube_tools.get_youtube_title(content)
assert title == EXPECTED_YT_TITLE
def test_check_track_exists_before_download(tmpdir):
expect_check = False
const.args.folder = str(tmpdir)
# prerequisites for determining filename
songname = internals.format_string(const.args.file_format, meta_tags)
global file_name
file_name = internals.sanitize_title(songname)
track_existence = downloader.CheckExists(file_name, meta_tags)
check = track_existence.already_exists(TRACK_URL)
assert check == expect_check
class TestDownload:
def test_m4a(self):
expect_download = True
download = youtube_tools.download_song(file_name + ".m4a", content)
assert download == expect_download
def test_webm(self):
expect_download = True
download = youtube_tools.download_song(file_name + ".webm", content)
assert download == expect_download
class TestFFmpeg:
def test_convert_from_webm_to_mp3(self):
expect_return_code = 0
return_code = convert.song(
file_name + ".webm", file_name + ".mp3", const.args.folder
)
assert return_code == expect_return_code
def test_convert_from_webm_to_m4a(self):
expect_return_code = 0
return_code = convert.song(
file_name + ".webm", file_name + ".m4a", const.args.folder
)
assert return_code == expect_return_code
def test_convert_from_m4a_to_mp3(self):
expect_return_code = 0
return_code = convert.song(
file_name + ".m4a", file_name + ".mp3", const.args.folder
)
assert return_code == expect_return_code
def test_convert_from_m4a_to_webm(self):
expect_return_code = 0
return_code = convert.song(
file_name + ".m4a", file_name + ".webm", const.args.folder
)
assert return_code == expect_return_code
def test_convert_from_m4a_to_flac(self):
expect_return_code = 0
return_code = convert.song(
file_name + ".m4a", file_name + ".flac", const.args.folder
)
assert return_code == expect_return_code
class TestAvconv:
def test_convert_from_m4a_to_mp3(self):
expect_return_code = 0
return_code = convert.song(
file_name + ".m4a", file_name + ".mp3", const.args.folder, avconv=True
)
assert return_code == expect_return_code
class TestEmbedMetadata:
def test_embed_in_mp3(self):
expect_embed = True
global track_path
track_path = os.path.join(const.args.folder, file_name)
embed = metadata.embed(track_path + ".mp3", meta_tags)
assert embed == expect_embed
def test_embed_in_m4a(self):
expect_embed = True
embed = metadata.embed(track_path + ".m4a", meta_tags)
os.remove(track_path + ".m4a")
assert embed == expect_embed
def test_embed_in_webm(self):
expect_embed = False
embed = metadata.embed(track_path + ".webm", meta_tags)
os.remove(track_path + ".webm")
assert embed == expect_embed
def test_embed_in_flac(self):
expect_embed = True
embed = metadata.embed(track_path + ".flac", meta_tags)
os.remove(track_path + ".flac")
assert embed == expect_embed
def test_check_track_exists_after_download():
expect_check = True
track_existence = downloader.CheckExists(file_name, meta_tags)
check = track_existence.already_exists(TRACK_URL)
os.remove(track_path + ".mp3")
assert check == expect_check

View File

@@ -8,6 +8,7 @@ from spotdl import youtube_tools
from spotdl import downloader
import loader
import pytest
loader.load_defaults()
@@ -38,11 +39,15 @@ class TestYouTubeAPIKeys:
assert key == EXPECTED_YT_API_KEY
def test_metadata():
expect_metadata = None
global metadata
@pytest.fixture(scope="module")
def metadata_fixture():
metadata = spotify_tools.generate_metadata(TRACK_SEARCH)
assert metadata == expect_metadata
return metadata
def test_metadata(metadata_fixture):
expect_metadata = None
assert metadata_fixture == expect_metadata
class TestArgsManualResultCount:
@@ -63,68 +68,82 @@ class TestArgsManualResultCount:
class TestYouTubeURL:
def test_only_music_category(self):
def test_only_music_category(self, metadata_fixture):
const.args.music_videos_only = True
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata)
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata_fixture)
# YouTube keeps changing its results
assert url in EXPECTED_YT_URLS
def test_all_categories(self):
def test_all_categories(self, metadata_fixture):
const.args.music_videos_only = False
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata)
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata_fixture)
assert url == EXPECTED_YT_URL
def test_args_manual(self, monkeypatch):
def test_args_manual(self, metadata_fixture, monkeypatch):
const.args.manual = True
monkeypatch.setattr("builtins.input", lambda x: "1")
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata)
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata_fixture)
assert url == EXPECTED_YT_URL
def test_args_manual_none(self, monkeypatch):
def test_args_manual_none(self, metadata_fixture, monkeypatch):
expect_url = None
monkeypatch.setattr("builtins.input", lambda x: "0")
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata)
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata_fixture)
const.args.manual = False
assert url == expect_url
@pytest.fixture(scope="module")
def content_fixture(metadata_fixture):
content = youtube_tools.go_pafy(TRACK_SEARCH, metadata_fixture)
return content
@pytest.fixture(scope="module")
def title_fixture(content_fixture):
title = youtube_tools.get_youtube_title(content_fixture)
return title
class TestYouTubeTitle:
def test_single_download_with_youtube_api(self):
global content
global title
def test_single_download_with_youtube_api(self, title_fixture):
const.args.youtube_api_key = YT_API_KEY
youtube_tools.set_api_key()
content = youtube_tools.go_pafy(TRACK_SEARCH, metadata)
title = youtube_tools.get_youtube_title(content)
assert title == EXPECTED_TITLE
assert title_fixture == EXPECTED_TITLE
def test_download_from_list_without_youtube_api(self):
def test_download_from_list_without_youtube_api(self, metadata_fixture, content_fixture):
const.args.youtube_api_key = None
youtube_tools.set_api_key()
content = youtube_tools.go_pafy(TRACK_SEARCH, metadata)
title = youtube_tools.get_youtube_title(content, 1)
content_fixture = youtube_tools.go_pafy(TRACK_SEARCH, metadata_fixture)
title = youtube_tools.get_youtube_title(content_fixture, 1)
assert title == "1. {0}".format(EXPECTED_TITLE)
def test_check_exists(tmpdir):
@pytest.fixture(scope="module")
def filename_fixture(title_fixture):
filename = internals.sanitize_title(title_fixture)
return filename
def test_check_exists(metadata_fixture, filename_fixture, tmpdir):
expect_check = False
const.args.folder = str(tmpdir)
# prerequisites for determining filename
global file_name
file_name = internals.sanitize_title(title)
track_existence = downloader.CheckExists(file_name, metadata)
track_existence = downloader.CheckExists(filename_fixture, metadata_fixture)
check = track_existence.already_exists(TRACK_SEARCH)
assert check == expect_check
class TestDownload:
def test_webm(self):
# content does not have any .webm audiostream
def test_webm(self, content_fixture, filename_fixture, monkeypatch):
# content_fixture does not have any .webm audiostream
expect_download = False
download = youtube_tools.download_song(file_name + ".webm", content)
monkeypatch.setattr("pafy.backend_shared.BaseStream.download", lambda x: None)
download = youtube_tools.download_song(filename_fixture + ".webm", content_fixture)
assert download == expect_download
def test_other(self):
def test_other(self, content_fixture, filename_fixture, monkeypatch):
expect_download = False
download = youtube_tools.download_song(file_name + ".fake_extension", content)
monkeypatch.setattr("pafy.backend_shared.BaseStream.download", lambda x: None)
download = youtube_tools.download_song(filename_fixture + ".fake_extension", content_fixture)
assert download == expect_download