[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) - Ability to pass multiple tracks with `-s` option ([@ritiek](https://github.com/ritiek)) (#442)
### Changed ### 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 - 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) 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) - Refactored core downloading module ([@ritiek](https://github.com/ritiek)) (#410)

View File

@@ -3,7 +3,8 @@ import os
from logzero import logger as log 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 https://stackoverflow.com/questions/9477115
ffmeg encoders high to lower quality ffmeg encoders high to lower quality
@@ -25,10 +26,10 @@ def song(input_song, output_song, folder, avconv=False, trim_silence=False):
else: else:
return 0 return 0
if avconv: if avconv:
exit_code = convert.with_avconv() exit_code, command = convert.with_avconv()
else: else:
exit_code = convert.with_ffmpeg() exit_code, command = convert.with_ffmpeg()
return exit_code return exit_code, command
class Converter: class Converter:
@@ -59,7 +60,7 @@ class Converter:
log.warning("--trim-silence not supported with avconv") log.warning("--trim-silence not supported with avconv")
log.debug(command) log.debug(command)
return subprocess.call(command) return subprocess.call(command), command
def with_ffmpeg(self): def with_ffmpeg(self):
ffmpeg_pre = "ffmpeg -y " ffmpeg_pre = "ffmpeg -y "
@@ -84,7 +85,7 @@ class Converter:
if output_ext == ".mp3": if output_ext == ".mp3":
ffmpeg_params = "-codec:a libmp3lame -ar 44100 " ffmpeg_params = "-codec:a libmp3lame -ar 44100 "
elif output_ext == ".m4a": 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": if output_ext == ".flac":
ffmpeg_params = "-codec:a flac -ar 44100 " ffmpeg_params = "-codec:a flac -ar 44100 "
@@ -104,4 +105,4 @@ class Converter:
) )
log.debug(command) 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 const
from spotdl import metadata from spotdl import metadata
from spotdl import convert from spotdl import convert
@@ -5,10 +10,6 @@ from spotdl import internals
from spotdl import spotify_tools from spotdl import spotify_tools
from spotdl import youtube_tools from spotdl import youtube_tools
import spotipy
from logzero import logger as log
import os
class CheckExists: class CheckExists:
def __init__(self, music_file, meta_tags=None): 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 from logzero import logger as log
import appdirs
import logging import logging
import yaml import yaml
import argparse import argparse
import mimetypes import mimetypes
import os import os
from spotdl import internals
_LOG_LEVELS_STR = ["INFO", "WARNING", "ERROR", "DEBUG"] _LOG_LEVELS_STR = ["INFO", "WARNING", "ERROR", "DEBUG"]

View File

@@ -1,6 +1,6 @@
from logzero import logger as log
import os import os
import sys import sys
from logzero import logger as log
from spotdl import const 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.id3 import ID3, TORY, TYER, TPUB, APIC, USLT, COMM
from mutagen.mp4 import MP4, MP4Cover from mutagen.mp4 import MP4, MP4Cover
from mutagen.flac import Picture, FLAC from mutagen.flac import Picture, FLAC
from logzero import logger as log
from spotdl.const import TAG_PRESET, M4A_TAG_PRESET
import urllib.request import urllib.request
from logzero import logger as log
from spotdl.const import TAG_PRESET, M4A_TAG_PRESET
def compare(music_file, metadata): def compare(music_file, metadata):

View File

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

View File

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

View File

@@ -1,15 +1,15 @@
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import urllib import urllib
import pafy import pafy
from slugify import slugify from slugify import slugify
from logzero import logger as log from logzero import logger as log
import os
from spotdl import spotify_tools from spotdl import spotify_tools
from spotdl import internals from spotdl import internals
from spotdl import const from spotdl import const
import os
# Fix download speed throttle on short duration tracks # Fix download speed throttle on short duration tracks
# Read more on mps-youtube/pafy#199 # Read more on mps-youtube/pafy#199
pafy.g.opener.addheaders.append(("Range", "bytes=0-")) 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)) log.info("Generating {0} from {1} YouTube URLs".format(target_file, total_tracks))
with open(target_file, "w") as output_file: with open(target_file, "w") as output_file:
output_file.write("#EXTM3U\n\n") output_file.write("#EXTM3U\n\n")
videos = []
for n, track in enumerate(tracks, 1): for n, track in enumerate(tracks, 1):
content, _ = match_video_and_metadata(track) content, _ = match_video_and_metadata(track)
if content is None: if content is None:
@@ -94,6 +96,9 @@ def generate_m3u(track_file):
log.debug(m3u_key) log.debug(m3u_key)
with open(target_file, "a") as output_file: with open(target_file, "a") as output_file:
output_file.write(m3u_key) output_file.write(m3u_key)
videos.append(content.watchv_url)
return videos
def download_song(file_name, content): def download_song(file_name, content):
@@ -240,7 +245,7 @@ class GenerateYouTubeURL:
search_url = generate_search_url(self.search_query) search_url = generate_search_url(self.search_query)
log.debug("Opening URL: {0}".format(search_url)) 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") items_parse = BeautifulSoup(item, "html.parser")
videos = [] videos = []
@@ -319,3 +324,11 @@ class GenerateYouTubeURL:
return self._best_match(videos) return self._best_match(videos)
return 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 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: class TestConfig:
def test_default_config(self, tmpdir): def test_default_config(self, config_path_fixture):
expect_config = handle.default_conf["spotify-downloader"] expect_config = handle.default_conf["spotify-downloader"]
global config_path config = handle.get_config(config_path_fixture)
config_path = os.path.join(str(tmpdir), "config.yml")
config = handle.get_config(config_path)
assert config == expect_config assert config == expect_config
def test_modified_config(self): def test_modified_config(self, modified_config_fixture):
global modified_config modified_config_fixture["spotify-downloader"]["file-format"] = "just_a_test"
modified_config = dict(handle.default_conf) merged_config = handle.merge(handle.default_conf, modified_config_fixture)
modified_config["spotify-downloader"]["file-format"] = "just_a_test" assert merged_config == modified_config_fixture
merged_config = handle.merge(handle.default_conf, modified_config)
assert merged_config == modified_config
def test_custom_config_path(self, tmpdir): def test_custom_config_path(self, config_path_fixture, modified_config_fixture):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
with open(config_path, "w") as config_file: with open(config_path_fixture, "w") as config_file:
yaml.dump(modified_config, config_file, default_flow_style=False) yaml.dump(modified_config_fixture, config_file, default_flow_style=False)
overridden_config = handle.override_config(config_path, parser, raw_args="") overridden_config = handle.override_config(config_path_fixture, parser, raw_args="")
modified_values = [ 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_config.folder = os.path.realpath(overridden_config.folder)
overridden_values = [ 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(): def test_default_music_directory():
if sys.platform.startswith("linux"): if sys.platform.startswith("linux"):
output = subprocess.check_output(["xdg-user-dir", "MUSIC"]) output = subprocess.check_output(["xdg-user-dir", "MUSIC"])
@@ -92,68 +112,39 @@ def test_default_music_directory():
assert directory == expect_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: class TestPathFilterer:
def test_create_directory(self, tmpdir): def test_create_directory(self, directory_fixture):
expect_path = True expect_path = True
global folder_path internals.filter_path(directory_fixture)
folder_path = os.path.join(str(tmpdir), "filter_this_folder") is_path = os.path.isdir(directory_fixture)
internals.filter_path(folder_path)
is_path = os.path.isdir(folder_path)
assert is_path == expect_path assert is_path == expect_path
def test_remove_temp_files(self, tmpdir): def test_remove_temp_files(self, directory_fixture):
expect_file = False 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") open(file_path, "a")
internals.filter_path(folder_path) internals.filter_path(directory_fixture)
is_file = os.path.isfile(file_path) is_file = os.path.isfile(file_path)
assert is_file == expect_file assert is_file == expect_file
class TestVideoTimeFromSeconds: @pytest.mark.parametrize("sec_duration, str_duration", FROM_SECONDS_TEST_TABLE)
def test_from_seconds(self): def test_video_time_from_seconds(sec_duration, str_duration):
expect_duration = "35" duration = internals.videotime_from_seconds(sec_duration)
duration = internals.videotime_from_seconds(35) assert duration == str_duration
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
class TestGetSeconds: @pytest.mark.parametrize("str_duration, sec_duration", TO_SECONDS_TEST_TABLE)
def test_from_seconds(self): def test_get_seconds_from_video_time(str_duration, sec_duration):
expect_secs = 45 secs = internals.get_sec(str_duration)
secs = internals.get_sec("0:45") assert secs == sec_duration
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("duplicates, expected", DUPLICATE_TRACKS_TEST_TABLE) @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): def test_extract_spotify_id(input_str, expected_spotify_id):
spotify_id = internals.extract_spotify_id(input_str) spotify_id = internals.extract_spotify_id(input_str)
assert spotify_id == expected_spotify_id 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 from spotdl import downloader
import loader import loader
import pytest
loader.load_defaults() loader.load_defaults()
@@ -38,11 +39,15 @@ class TestYouTubeAPIKeys:
assert key == EXPECTED_YT_API_KEY assert key == EXPECTED_YT_API_KEY
def test_metadata(): @pytest.fixture(scope="module")
expect_metadata = None def metadata_fixture():
global metadata
metadata = spotify_tools.generate_metadata(TRACK_SEARCH) 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: class TestArgsManualResultCount:
@@ -63,68 +68,82 @@ class TestArgsManualResultCount:
class TestYouTubeURL: class TestYouTubeURL:
def test_only_music_category(self): def test_only_music_category(self, metadata_fixture):
const.args.music_videos_only = True 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 # YouTube keeps changing its results
assert url in EXPECTED_YT_URLS assert url in EXPECTED_YT_URLS
def test_all_categories(self): def test_all_categories(self, metadata_fixture):
const.args.music_videos_only = False 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 assert url == EXPECTED_YT_URL
def test_args_manual(self, monkeypatch): def test_args_manual(self, metadata_fixture, monkeypatch):
const.args.manual = True const.args.manual = True
monkeypatch.setattr("builtins.input", lambda x: "1") 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 assert url == EXPECTED_YT_URL
def test_args_manual_none(self, monkeypatch): def test_args_manual_none(self, metadata_fixture, monkeypatch):
expect_url = None expect_url = None
monkeypatch.setattr("builtins.input", lambda x: "0") 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 const.args.manual = False
assert url == expect_url 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: class TestYouTubeTitle:
def test_single_download_with_youtube_api(self): def test_single_download_with_youtube_api(self, title_fixture):
global content
global title
const.args.youtube_api_key = YT_API_KEY const.args.youtube_api_key = YT_API_KEY
youtube_tools.set_api_key() youtube_tools.set_api_key()
content = youtube_tools.go_pafy(TRACK_SEARCH, metadata) assert title_fixture == EXPECTED_TITLE
title = youtube_tools.get_youtube_title(content)
assert title == 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 const.args.youtube_api_key = None
youtube_tools.set_api_key() youtube_tools.set_api_key()
content = youtube_tools.go_pafy(TRACK_SEARCH, metadata) content_fixture = youtube_tools.go_pafy(TRACK_SEARCH, metadata_fixture)
title = youtube_tools.get_youtube_title(content, 1) title = youtube_tools.get_youtube_title(content_fixture, 1)
assert title == "1. {0}".format(EXPECTED_TITLE) 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 expect_check = False
const.args.folder = str(tmpdir) const.args.folder = str(tmpdir)
# prerequisites for determining filename # prerequisites for determining filename
global file_name track_existence = downloader.CheckExists(filename_fixture, metadata_fixture)
file_name = internals.sanitize_title(title)
track_existence = downloader.CheckExists(file_name, metadata)
check = track_existence.already_exists(TRACK_SEARCH) check = track_existence.already_exists(TRACK_SEARCH)
assert check == expect_check assert check == expect_check
class TestDownload: class TestDownload:
def test_webm(self): def test_webm(self, content_fixture, filename_fixture, monkeypatch):
# content does not have any .webm audiostream # content_fixture does not have any .webm audiostream
expect_download = False 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 assert download == expect_download
def test_other(self): def test_other(self, content_fixture, filename_fixture, monkeypatch):
expect_download = False 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 assert download == expect_download