Add MusicWorkshop application
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import json
|
||||
import time
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from urllib import error
|
||||
|
||||
from backend.app.matcher import MatchHttpClient, MusicBrainzProvider, SpotifyProvider
|
||||
|
||||
|
||||
class MatchProviderTests(unittest.TestCase):
|
||||
def test_match_http_client_retries_url_errors(self):
|
||||
client = MatchHttpClient()
|
||||
|
||||
with patch(
|
||||
'backend.app.matcher.request.urlopen',
|
||||
side_effect=[
|
||||
error.URLError('temporary dns error'),
|
||||
FakeResponse({'ok': True})
|
||||
]
|
||||
) as mock_urlopen:
|
||||
payload = client.request_json('test', 'https://example.com', retries=1)
|
||||
|
||||
self.assertEqual(payload['ok'], True)
|
||||
self.assertEqual(mock_urlopen.call_count, 2)
|
||||
|
||||
def test_match_http_client_retries_timeout(self):
|
||||
client = MatchHttpClient()
|
||||
|
||||
with patch(
|
||||
'backend.app.matcher.request.urlopen',
|
||||
side_effect=[
|
||||
TimeoutError('timeout'),
|
||||
FakeResponse({'ok': True})
|
||||
]
|
||||
) as mock_urlopen:
|
||||
payload = client.request_json('test', 'https://example.com', retries=1)
|
||||
|
||||
self.assertEqual(payload['ok'], True)
|
||||
self.assertEqual(mock_urlopen.call_count, 2)
|
||||
|
||||
def test_musicbrainz_requests_use_user_agent_and_throttle(self):
|
||||
client = MatchHttpClient()
|
||||
provider = MusicBrainzProvider(client)
|
||||
observed_headers = []
|
||||
|
||||
def fake_urlopen(req, timeout):
|
||||
observed_headers.append(dict(req.header_items()))
|
||||
return FakeResponse({'recordings': []})
|
||||
|
||||
with patch('backend.app.matcher.request.urlopen', side_effect=fake_urlopen) as mock_urlopen:
|
||||
with patch('backend.app.matcher.time.sleep') as mock_sleep:
|
||||
with patch(
|
||||
'backend.app.matcher.time.monotonic',
|
||||
side_effect=[0.0, 0.0, 0.1, 0.1]
|
||||
):
|
||||
provider._request_json(
|
||||
'musicbrainz',
|
||||
'https://musicbrainz.org/ws/2/recording',
|
||||
params={'fmt': 'json'}
|
||||
)
|
||||
provider._request_json(
|
||||
'musicbrainz',
|
||||
'https://musicbrainz.org/ws/2/recording',
|
||||
params={'fmt': 'json'}
|
||||
)
|
||||
|
||||
self.assertEqual(mock_urlopen.call_count, 2)
|
||||
self.assertTrue(any('User-agent' in headers or 'User-Agent' in headers for headers in observed_headers))
|
||||
self.assertTrue(mock_sleep.called)
|
||||
|
||||
def test_spotify_provider_refreshes_expired_token(self):
|
||||
provider = SpotifyProvider(MatchHttpClient())
|
||||
config = {
|
||||
'metadata': {
|
||||
'spotifyUrl': 'https://api.spotify.com/v1',
|
||||
'spotifyClientId': 'spotify-id',
|
||||
'spotifySecret': 'spotify-secret'
|
||||
}
|
||||
}
|
||||
observed_authorization = []
|
||||
token_counter = {'value': 0}
|
||||
|
||||
def fake_urlopen(req, timeout):
|
||||
url = req.full_url
|
||||
if 'api/token' in url:
|
||||
token_counter['value'] += 1
|
||||
return FakeResponse(
|
||||
{
|
||||
'access_token': f'token-{token_counter["value"]}',
|
||||
'expires_in': 3600
|
||||
}
|
||||
)
|
||||
|
||||
observed_authorization.append(req.headers.get('Authorization'))
|
||||
return FakeResponse(
|
||||
{
|
||||
'tracks': {
|
||||
'items': [
|
||||
{
|
||||
'id': 'track-1',
|
||||
'name': 'Song Title',
|
||||
'artists': [{'name': 'Song Artist'}],
|
||||
'album': {
|
||||
'id': 'album-1',
|
||||
'name': 'Album Name',
|
||||
'release_date': '2024-01-01',
|
||||
'images': []
|
||||
},
|
||||
'track_number': 1,
|
||||
'disc_number': 1,
|
||||
'duration_ms': 201000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
with patch('backend.app.matcher.request.urlopen', side_effect=fake_urlopen):
|
||||
provider.search(
|
||||
{
|
||||
'title': 'Song Title',
|
||||
'artist': 'Song Artist',
|
||||
'album': 'Album Name'
|
||||
},
|
||||
config
|
||||
)
|
||||
cache_key = 'spotify-id:spotify-secret'
|
||||
provider._token_cache[cache_key]['expires_at'] = time.time() - 1
|
||||
provider.search(
|
||||
{
|
||||
'title': 'Song Title',
|
||||
'artist': 'Song Artist',
|
||||
'album': 'Album Name'
|
||||
},
|
||||
config
|
||||
)
|
||||
|
||||
self.assertEqual(token_counter['value'], 2)
|
||||
self.assertEqual(observed_authorization, ['Bearer token-1', 'Bearer token-2'])
|
||||
|
||||
def test_spotify_provider_skips_when_credentials_are_missing(self):
|
||||
provider = SpotifyProvider(MatchHttpClient())
|
||||
config = {
|
||||
'metadata': {
|
||||
'spotifyUrl': 'https://api.spotify.com/v1',
|
||||
'spotifyClientId': '',
|
||||
'spotifySecret': ''
|
||||
}
|
||||
}
|
||||
|
||||
with patch('backend.app.matcher.request.urlopen') as mock_urlopen:
|
||||
candidates = provider.search(
|
||||
{
|
||||
'title': 'Song Title',
|
||||
'artist': 'Song Artist',
|
||||
'album': 'Album Name'
|
||||
},
|
||||
config
|
||||
)
|
||||
|
||||
self.assertEqual(candidates, [])
|
||||
self.assertEqual(mock_urlopen.call_count, 0)
|
||||
|
||||
|
||||
class FakeHeaders:
|
||||
def get_content_charset(self):
|
||||
return 'utf-8'
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, payload):
|
||||
self._payload = json.dumps(payload).encode('utf-8')
|
||||
self.headers = FakeHeaders()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
|
||||
def read(self):
|
||||
return self._payload
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user