beets/test/plugins/test_beatport.py
Lars Kruse 508d28f66b tests: move reusable test-related modules into the beets package
External Python packages interfacing beets may want to use an in-memory
beets library instance for testing beets-related code.
The `TestHelper` class is very helpful for this purpose.
Previously `TestHelper` was located in the `test/` directory.
Now it is part of `beets` itself (`beets.test.helper.TestHelper`) and
can be easily imported.
2024-01-15 19:40:57 +01:00

679 lines
23 KiB
Python

# This file is part of beets.
# Copyright 2016, Adrian Sampson.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
"""Tests for the 'beatport' plugin.
"""
import unittest
from datetime import timedelta
from beets import library
from beets.test import _common
from beets.test.helper import TestHelper
from beetsplug import beatport
class BeatportTest(_common.TestCase, TestHelper):
def _make_release_response(self):
"""Returns a dict that mimics a response from the beatport API.
The results were retrieved from:
https://oauth-api.beatport.com/catalog/3/releases?id=1742984
The list of elements on the returned dict is incomplete, including just
those required for the tests on this class.
"""
results = {
"id": 1742984,
"type": "release",
"name": "Charade",
"slug": "charade",
"releaseDate": "2016-04-11",
"publishDate": "2016-04-11",
"audioFormat": "",
"category": "Release",
"currentStatus": "General Content",
"catalogNumber": "GR089",
"description": "",
"label": {
"id": 24539,
"name": "Gravitas Recordings",
"type": "label",
"slug": "gravitas-recordings",
},
"artists": [
{
"id": 326158,
"name": "Supersillyus",
"slug": "supersillyus",
"type": "artist",
}
],
"genres": [
{"id": 9, "name": "Breaks", "slug": "breaks", "type": "genre"}
],
}
return results
def _make_tracks_response(self):
"""Return a list that mimics a response from the beatport API.
The results were retrieved from:
https://oauth-api.beatport.com/catalog/3/tracks?releaseId=1742984
The list of elements on the returned list is incomplete, including just
those required for the tests on this class.
"""
results = [
{
"id": 7817567,
"type": "track",
"sku": "track-7817567",
"name": "Mirage a Trois",
"trackNumber": 1,
"mixName": "Original Mix",
"title": "Mirage a Trois (Original Mix)",
"slug": "mirage-a-trois-original-mix",
"releaseDate": "2016-04-11",
"publishDate": "2016-04-11",
"currentStatus": "General Content",
"length": "7:05",
"lengthMs": 425421,
"bpm": 90,
"key": {
"standard": {
"letter": "G",
"sharp": False,
"flat": False,
"chord": "minor",
},
"shortName": "Gmin",
},
"artists": [
{
"id": 326158,
"name": "Supersillyus",
"slug": "supersillyus",
"type": "artist",
}
],
"genres": [
{
"id": 9,
"name": "Breaks",
"slug": "breaks",
"type": "genre",
}
],
"subGenres": [
{
"id": 209,
"name": "Glitch Hop",
"slug": "glitch-hop",
"type": "subgenre",
}
],
"release": {
"id": 1742984,
"name": "Charade",
"type": "release",
"slug": "charade",
},
"label": {
"id": 24539,
"name": "Gravitas Recordings",
"type": "label",
"slug": "gravitas-recordings",
"status": True,
},
},
{
"id": 7817568,
"type": "track",
"sku": "track-7817568",
"name": "Aeon Bahamut",
"trackNumber": 2,
"mixName": "Original Mix",
"title": "Aeon Bahamut (Original Mix)",
"slug": "aeon-bahamut-original-mix",
"releaseDate": "2016-04-11",
"publishDate": "2016-04-11",
"currentStatus": "General Content",
"length": "7:38",
"lengthMs": 458000,
"bpm": 100,
"key": {
"standard": {
"letter": "G",
"sharp": False,
"flat": False,
"chord": "major",
},
"shortName": "Gmaj",
},
"artists": [
{
"id": 326158,
"name": "Supersillyus",
"slug": "supersillyus",
"type": "artist",
}
],
"genres": [
{
"id": 9,
"name": "Breaks",
"slug": "breaks",
"type": "genre",
}
],
"subGenres": [
{
"id": 209,
"name": "Glitch Hop",
"slug": "glitch-hop",
"type": "subgenre",
}
],
"release": {
"id": 1742984,
"name": "Charade",
"type": "release",
"slug": "charade",
},
"label": {
"id": 24539,
"name": "Gravitas Recordings",
"type": "label",
"slug": "gravitas-recordings",
"status": True,
},
},
{
"id": 7817569,
"type": "track",
"sku": "track-7817569",
"name": "Trancendental Medication",
"trackNumber": 3,
"mixName": "Original Mix",
"title": "Trancendental Medication (Original Mix)",
"slug": "trancendental-medication-original-mix",
"releaseDate": "2016-04-11",
"publishDate": "2016-04-11",
"currentStatus": "General Content",
"length": "1:08",
"lengthMs": 68571,
"bpm": 141,
"key": {
"standard": {
"letter": "F",
"sharp": False,
"flat": False,
"chord": "major",
},
"shortName": "Fmaj",
},
"artists": [
{
"id": 326158,
"name": "Supersillyus",
"slug": "supersillyus",
"type": "artist",
}
],
"genres": [
{
"id": 9,
"name": "Breaks",
"slug": "breaks",
"type": "genre",
}
],
"subGenres": [
{
"id": 209,
"name": "Glitch Hop",
"slug": "glitch-hop",
"type": "subgenre",
}
],
"release": {
"id": 1742984,
"name": "Charade",
"type": "release",
"slug": "charade",
},
"label": {
"id": 24539,
"name": "Gravitas Recordings",
"type": "label",
"slug": "gravitas-recordings",
"status": True,
},
},
{
"id": 7817570,
"type": "track",
"sku": "track-7817570",
"name": "A List of Instructions for When I'm Human",
"trackNumber": 4,
"mixName": "Original Mix",
"title": "A List of Instructions for When I'm Human (Original Mix)",
"slug": "a-list-of-instructions-for-when-im-human-original-mix",
"releaseDate": "2016-04-11",
"publishDate": "2016-04-11",
"currentStatus": "General Content",
"length": "6:57",
"lengthMs": 417913,
"bpm": 88,
"key": {
"standard": {
"letter": "A",
"sharp": False,
"flat": False,
"chord": "minor",
},
"shortName": "Amin",
},
"artists": [
{
"id": 326158,
"name": "Supersillyus",
"slug": "supersillyus",
"type": "artist",
}
],
"genres": [
{
"id": 9,
"name": "Breaks",
"slug": "breaks",
"type": "genre",
}
],
"subGenres": [
{
"id": 209,
"name": "Glitch Hop",
"slug": "glitch-hop",
"type": "subgenre",
}
],
"release": {
"id": 1742984,
"name": "Charade",
"type": "release",
"slug": "charade",
},
"label": {
"id": 24539,
"name": "Gravitas Recordings",
"type": "label",
"slug": "gravitas-recordings",
"status": True,
},
},
{
"id": 7817571,
"type": "track",
"sku": "track-7817571",
"name": "The Great Shenanigan",
"trackNumber": 5,
"mixName": "Original Mix",
"title": "The Great Shenanigan (Original Mix)",
"slug": "the-great-shenanigan-original-mix",
"releaseDate": "2016-04-11",
"publishDate": "2016-04-11",
"currentStatus": "General Content",
"length": "9:49",
"lengthMs": 589875,
"bpm": 123,
"key": {
"standard": {
"letter": "E",
"sharp": False,
"flat": True,
"chord": "major",
},
"shortName": "E♭maj",
},
"artists": [
{
"id": 326158,
"name": "Supersillyus",
"slug": "supersillyus",
"type": "artist",
}
],
"genres": [
{
"id": 9,
"name": "Breaks",
"slug": "breaks",
"type": "genre",
}
],
"subGenres": [
{
"id": 209,
"name": "Glitch Hop",
"slug": "glitch-hop",
"type": "subgenre",
}
],
"release": {
"id": 1742984,
"name": "Charade",
"type": "release",
"slug": "charade",
},
"label": {
"id": 24539,
"name": "Gravitas Recordings",
"type": "label",
"slug": "gravitas-recordings",
"status": True,
},
},
{
"id": 7817572,
"type": "track",
"sku": "track-7817572",
"name": "Charade",
"trackNumber": 6,
"mixName": "Original Mix",
"title": "Charade (Original Mix)",
"slug": "charade-original-mix",
"releaseDate": "2016-04-11",
"publishDate": "2016-04-11",
"currentStatus": "General Content",
"length": "7:05",
"lengthMs": 425423,
"bpm": 123,
"key": {
"standard": {
"letter": "A",
"sharp": False,
"flat": False,
"chord": "major",
},
"shortName": "Amaj",
},
"artists": [
{
"id": 326158,
"name": "Supersillyus",
"slug": "supersillyus",
"type": "artist",
}
],
"genres": [
{
"id": 9,
"name": "Breaks",
"slug": "breaks",
"type": "genre",
}
],
"subGenres": [
{
"id": 209,
"name": "Glitch Hop",
"slug": "glitch-hop",
"type": "subgenre",
}
],
"release": {
"id": 1742984,
"name": "Charade",
"type": "release",
"slug": "charade",
},
"label": {
"id": 24539,
"name": "Gravitas Recordings",
"type": "label",
"slug": "gravitas-recordings",
"status": True,
},
},
]
return results
def setUp(self):
self.setup_beets()
self.load_plugins("beatport")
self.lib = library.Library(":memory:")
# Set up 'album'.
response_release = self._make_release_response()
self.album = beatport.BeatportRelease(response_release)
# Set up 'tracks'.
response_tracks = self._make_tracks_response()
self.tracks = [beatport.BeatportTrack(t) for t in response_tracks]
# Set up 'test_album'.
self.test_album = self.mk_test_album()
# Set up 'test_tracks'
self.test_tracks = self.test_album.items()
def tearDown(self):
self.unload_plugins()
self.teardown_beets()
def mk_test_album(self):
items = [_common.item() for _ in range(6)]
for item in items:
item.album = "Charade"
item.catalognum = "GR089"
item.label = "Gravitas Recordings"
item.artist = "Supersillyus"
item.year = 2016
item.comp = False
item.label_name = "Gravitas Recordings"
item.genre = "Glitch Hop"
item.year = 2016
item.month = 4
item.day = 11
item.mix_name = "Original Mix"
items[0].title = "Mirage a Trois"
items[1].title = "Aeon Bahamut"
items[2].title = "Trancendental Medication"
items[3].title = "A List of Instructions for When I'm Human"
items[4].title = "The Great Shenanigan"
items[5].title = "Charade"
items[0].length = timedelta(minutes=7, seconds=5).total_seconds()
items[1].length = timedelta(minutes=7, seconds=38).total_seconds()
items[2].length = timedelta(minutes=1, seconds=8).total_seconds()
items[3].length = timedelta(minutes=6, seconds=57).total_seconds()
items[4].length = timedelta(minutes=9, seconds=49).total_seconds()
items[5].length = timedelta(minutes=7, seconds=5).total_seconds()
items[0].url = "mirage-a-trois-original-mix"
items[1].url = "aeon-bahamut-original-mix"
items[2].url = "trancendental-medication-original-mix"
items[3].url = "a-list-of-instructions-for-when-im-human-original-mix"
items[4].url = "the-great-shenanigan-original-mix"
items[5].url = "charade-original-mix"
counter = 0
for item in items:
counter += 1
item.track_number = counter
items[0].bpm = 90
items[1].bpm = 100
items[2].bpm = 141
items[3].bpm = 88
items[4].bpm = 123
items[5].bpm = 123
items[0].initial_key = "Gmin"
items[1].initial_key = "Gmaj"
items[2].initial_key = "Fmaj"
items[3].initial_key = "Amin"
items[4].initial_key = "E♭maj"
items[5].initial_key = "Amaj"
for item in items:
self.lib.add(item)
album = self.lib.add_album(items)
album.store()
return album
# Test BeatportRelease.
def test_album_name_applied(self):
self.assertEqual(self.album.name, self.test_album["album"])
def test_catalog_number_applied(self):
self.assertEqual(
self.album.catalog_number, self.test_album["catalognum"]
)
def test_label_applied(self):
self.assertEqual(self.album.label_name, self.test_album["label"])
def test_category_applied(self):
self.assertEqual(self.album.category, "Release")
def test_album_url_applied(self):
self.assertEqual(
self.album.url, "https://beatport.com/release/charade/1742984"
)
# Test BeatportTrack.
def test_title_applied(self):
for track, test_track in zip(self.tracks, self.test_tracks):
self.assertEqual(track.name, test_track.title)
def test_mix_name_applied(self):
for track, test_track in zip(self.tracks, self.test_tracks):
self.assertEqual(track.mix_name, test_track.mix_name)
def test_length_applied(self):
for track, test_track in zip(self.tracks, self.test_tracks):
self.assertEqual(
int(track.length.total_seconds()), int(test_track.length)
)
def test_track_url_applied(self):
# Specify beatport ids here because an 'item.id' is beets-internal.
ids = [
7817567,
7817568,
7817569,
7817570,
7817571,
7817572,
]
# Concatenate with 'id' to pass strict equality test.
for track, test_track, id in zip(self.tracks, self.test_tracks, ids):
self.assertEqual(
track.url,
"https://beatport.com/track/" + test_track.url + "/" + str(id),
)
def test_bpm_applied(self):
for track, test_track in zip(self.tracks, self.test_tracks):
self.assertEqual(track.bpm, test_track.bpm)
def test_initial_key_applied(self):
for track, test_track in zip(self.tracks, self.test_tracks):
self.assertEqual(track.initial_key, test_track.initial_key)
def test_genre_applied(self):
for track, test_track in zip(self.tracks, self.test_tracks):
self.assertEqual(track.genre, test_track.genre)
class BeatportResponseEmptyTest(_common.TestCase, TestHelper):
def _make_tracks_response(self):
results = [
{
"id": 7817567,
"name": "Mirage a Trois",
"genres": [
{
"id": 9,
"name": "Breaks",
"slug": "breaks",
"type": "genre",
}
],
"subGenres": [
{
"id": 209,
"name": "Glitch Hop",
"slug": "glitch-hop",
"type": "subgenre",
}
],
}
]
return results
def setUp(self):
self.setup_beets()
self.load_plugins("beatport")
self.lib = library.Library(":memory:")
# Set up 'tracks'.
self.response_tracks = self._make_tracks_response()
self.tracks = [beatport.BeatportTrack(t) for t in self.response_tracks]
# Make alias to be congruent with class `BeatportTest`.
self.test_tracks = self.response_tracks
def tearDown(self):
self.unload_plugins()
self.teardown_beets()
def test_response_tracks_empty(self):
response_tracks = []
tracks = [beatport.BeatportTrack(t) for t in response_tracks]
self.assertEqual(tracks, [])
def test_sub_genre_empty_fallback(self):
"""No 'sub_genre' is provided. Test if fallback to 'genre' works."""
self.response_tracks[0]["subGenres"] = []
tracks = [beatport.BeatportTrack(t) for t in self.response_tracks]
self.test_tracks[0]["subGenres"] = []
self.assertEqual(
tracks[0].genre, self.test_tracks[0]["genres"][0]["name"]
)
def test_genre_empty(self):
"""No 'genre' is provided. Test if 'sub_genre' is applied."""
self.response_tracks[0]["genres"] = []
tracks = [beatport.BeatportTrack(t) for t in self.response_tracks]
self.test_tracks[0]["genres"] = []
self.assertEqual(
tracks[0].genre, self.test_tracks[0]["subGenres"][0]["name"]
)
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
if __name__ == "__main__":
unittest.main(defaultTest="suite")