187 lines
5.1 KiB
Python
187 lines
5.1 KiB
Python
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()
|