mirror of https://github.com/Nezreka/SoulSync.git
Merge pull request #492 from Nezreka/refactor/typed-album-consumers
Migrate discography + quality scanner to typed Album pathpull/496/head
commit
ea741df286
@ -0,0 +1,87 @@
|
||||
"""Pin the typed-path migration of `_normalize_track_album` in
|
||||
`core/discovery/quality_scanner.py`.
|
||||
|
||||
Quality scanner result normalization now routes the embedded
|
||||
`track.album` blob through `Album.from_<source>_dict()` when provider
|
||||
is known. Falls back to legacy duck-typed extraction below.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from core.discovery import quality_scanner
|
||||
|
||||
|
||||
SPOTIFY_TRACK = {
|
||||
'id': 'tr1',
|
||||
'name': 'HUMBLE.',
|
||||
'artists': [{'id': 'kdot', 'name': 'Kendrick Lamar'}],
|
||||
'album': {
|
||||
'id': 'sp_album',
|
||||
'name': 'DAMN.',
|
||||
'artists': [{'id': 'kdot', 'name': 'Kendrick Lamar'}],
|
||||
'release_date': '2017-04-14',
|
||||
'total_tracks': 14,
|
||||
'album_type': 'album',
|
||||
'images': [{'url': 'https://i.scdn.co/640.jpg'}],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_typed_path_seeds_album_fields_from_known_provider():
|
||||
out = quality_scanner._normalize_track_album(SPOTIFY_TRACK, provider='spotify')
|
||||
assert out['name'] == 'DAMN.'
|
||||
assert out['album_type'] == 'album'
|
||||
assert out['total_tracks'] == 14
|
||||
assert out['release_date'] == '2017-04-14'
|
||||
assert out['id'] == 'sp_album'
|
||||
|
||||
|
||||
def test_legacy_path_used_when_no_provider():
|
||||
out = quality_scanner._normalize_track_album(SPOTIFY_TRACK)
|
||||
# Legacy path still resolves album from raw `album` dict.
|
||||
assert out['name'] == 'DAMN.'
|
||||
assert out['album_type'] == 'album'
|
||||
assert out['total_tracks'] == 14
|
||||
|
||||
|
||||
def test_legacy_path_used_for_unknown_provider():
|
||||
out = quality_scanner._normalize_track_album(SPOTIFY_TRACK, provider='made_up')
|
||||
assert out['name'] == 'DAMN.'
|
||||
|
||||
|
||||
def test_legacy_path_used_when_typed_converter_raises():
|
||||
def _explode(_):
|
||||
raise RuntimeError('simulated converter bug')
|
||||
|
||||
with patch.dict(quality_scanner._TYPED_ALBUM_CONVERTERS,
|
||||
{'spotify': _explode}):
|
||||
out = quality_scanner._normalize_track_album(SPOTIFY_TRACK, provider='spotify')
|
||||
# Fell back to legacy — still has the album fields.
|
||||
assert out['name'] == 'DAMN.'
|
||||
assert out['total_tracks'] == 14
|
||||
|
||||
|
||||
@pytest.mark.parametrize('provider,track', [
|
||||
('itunes', {
|
||||
'id': 1, 'name': 'X',
|
||||
'album': {'collectionId': 99, 'collectionName': 'iTunes Album',
|
||||
'artistName': 'Artist', 'trackCount': 10},
|
||||
}),
|
||||
('deezer', {
|
||||
'id': 2, 'name': 'X',
|
||||
'album': {'id': 99, 'title': 'Deezer Album',
|
||||
'artist': {'name': 'Artist'}, 'nb_tracks': 8},
|
||||
}),
|
||||
('discogs', {
|
||||
'id': 3, 'name': 'X',
|
||||
'album': {'id': 99, 'title': 'Discogs Album',
|
||||
'artists': [{'name': 'Artist'}], 'year': 2020},
|
||||
}),
|
||||
])
|
||||
def test_typed_path_works_for_other_providers(provider, track):
|
||||
out = quality_scanner._normalize_track_album(track, provider=provider)
|
||||
assert out['name'] # populated
|
||||
@ -0,0 +1,160 @@
|
||||
"""Pin the typed-path migration in `core/metadata/discography.py`.
|
||||
|
||||
`_build_discography_release_dict` and `_build_artist_detail_release_card`
|
||||
historically did duck-typed extraction with fallback chains. This pr
|
||||
routes them through `Album.from_<source>_dict()` when caller supplies
|
||||
a known source. Legacy duck-typing kicks in as fallback.
|
||||
|
||||
These tests pin:
|
||||
- Typed path used when source is recognized.
|
||||
- Typed path output matches expected fields the legacy path produced.
|
||||
- Legacy path runs unchanged when source is empty/unknown OR when
|
||||
the typed converter raises.
|
||||
- Cross-provider parametrized smoke for every registered source.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from core.metadata import discography
|
||||
|
||||
|
||||
SAMPLE_SPOTIFY_RELEASE = {
|
||||
'id': 'sp123',
|
||||
'name': 'GNX',
|
||||
'artists': [{'id': 'kdot', 'name': 'Kendrick Lamar'}],
|
||||
'release_date': '2024-11-22',
|
||||
'total_tracks': 12,
|
||||
'album_type': 'album',
|
||||
'images': [{'url': 'https://i.scdn.co/640.jpg', 'height': 640}],
|
||||
'external_urls': {'spotify': 'https://open.spotify.com/album/sp123'},
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _build_discography_release_dict
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_typed_path_used_for_known_source():
|
||||
out = discography._build_discography_release_dict(
|
||||
SAMPLE_SPOTIFY_RELEASE, artist_id='kdot', source='spotify',
|
||||
)
|
||||
assert out['id'] == 'sp123'
|
||||
assert out['name'] == 'GNX'
|
||||
assert out['artist_name'] == 'Kendrick Lamar'
|
||||
assert out['release_date'] == '2024-11-22'
|
||||
assert out['album_type'] == 'album'
|
||||
assert out['total_tracks'] == 12
|
||||
assert out['image_url'] == 'https://i.scdn.co/640.jpg'
|
||||
assert out['external_urls'] == {'spotify': 'https://open.spotify.com/album/sp123'}
|
||||
|
||||
|
||||
def test_legacy_path_used_when_no_source():
|
||||
out = discography._build_discography_release_dict(
|
||||
SAMPLE_SPOTIFY_RELEASE, artist_id='kdot',
|
||||
)
|
||||
assert out['id'] == 'sp123'
|
||||
assert out['name'] == 'GNX'
|
||||
assert out['artist_name'] == 'Kendrick Lamar'
|
||||
|
||||
|
||||
def test_legacy_path_used_for_unknown_source():
|
||||
out = discography._build_discography_release_dict(
|
||||
SAMPLE_SPOTIFY_RELEASE, artist_id='kdot', source='made_up',
|
||||
)
|
||||
assert out['id'] == 'sp123'
|
||||
|
||||
|
||||
def test_legacy_path_used_when_typed_converter_raises():
|
||||
def _explode(_):
|
||||
raise RuntimeError('simulated converter bug')
|
||||
|
||||
with patch.dict(discography._TYPED_ALBUM_CONVERTERS,
|
||||
{'spotify': _explode}):
|
||||
out = discography._build_discography_release_dict(
|
||||
SAMPLE_SPOTIFY_RELEASE, artist_id='kdot', source='spotify',
|
||||
)
|
||||
# Legacy path still produced a result.
|
||||
assert out['id'] == 'sp123'
|
||||
assert out['name'] == 'GNX'
|
||||
|
||||
|
||||
def test_release_with_no_id_returns_none():
|
||||
raw = dict(SAMPLE_SPOTIFY_RELEASE)
|
||||
raw.pop('id')
|
||||
out = discography._build_discography_release_dict(
|
||||
raw, artist_id='kdot', source='spotify',
|
||||
)
|
||||
assert out is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('source,raw', [
|
||||
('itunes', {
|
||||
'collectionId': 1, 'collectionName': 'GNX',
|
||||
'artistName': 'Kendrick Lamar', 'trackCount': 12,
|
||||
}),
|
||||
('deezer', {
|
||||
'id': 1, 'title': 'GNX',
|
||||
'artist': {'name': 'Kendrick Lamar'}, 'nb_tracks': 12,
|
||||
}),
|
||||
('discogs', {
|
||||
'id': 1, 'title': 'GNX',
|
||||
'artists': [{'name': 'Kendrick Lamar'}], 'year': 2024,
|
||||
}),
|
||||
('musicbrainz', {
|
||||
'id': 'mbid', 'title': 'GNX',
|
||||
'artist-credit': [{'artist': {'name': 'Kendrick Lamar'}}],
|
||||
}),
|
||||
('hydrabase', {
|
||||
'id': 'hb', 'name': 'GNX',
|
||||
'artists': [{'name': 'Kendrick Lamar'}],
|
||||
}),
|
||||
('qobuz', {
|
||||
'id': 1, 'title': 'GNX',
|
||||
'artist': {'name': 'Kendrick Lamar'}, 'tracks_count': 12,
|
||||
}),
|
||||
])
|
||||
def test_typed_path_works_for_every_registered_source(source, raw):
|
||||
out = discography._build_discography_release_dict(
|
||||
raw, artist_id='whatever', source=source,
|
||||
)
|
||||
assert out is not None
|
||||
assert out['name'] == 'GNX'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _build_artist_detail_release_card — typed dispatch on raw input
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_artist_detail_card_typed_path():
|
||||
card = discography._build_artist_detail_release_card(
|
||||
SAMPLE_SPOTIFY_RELEASE, source='spotify',
|
||||
)
|
||||
assert card['id'] == 'sp123'
|
||||
assert card['name'] == 'GNX'
|
||||
assert card['album_type'] == 'album'
|
||||
assert card['year'] == '2024'
|
||||
assert card['release_date'] == '2024-11-22'
|
||||
assert card['image_url'] == 'https://i.scdn.co/640.jpg'
|
||||
assert card['track_count'] == 12
|
||||
|
||||
|
||||
def test_artist_detail_card_legacy_path_no_source():
|
||||
"""Existing canonical-dict input (no source) takes legacy path."""
|
||||
canonical = {
|
||||
'id': 'sp123',
|
||||
'name': 'GNX',
|
||||
'album_type': 'album',
|
||||
'release_date': '2024-11-22',
|
||||
'image_url': 'https://i.scdn.co/640.jpg',
|
||||
'total_tracks': 12,
|
||||
}
|
||||
card = discography._build_artist_detail_release_card(canonical)
|
||||
assert card['id'] == 'sp123'
|
||||
assert card['name'] == 'GNX'
|
||||
assert card['year'] == '2024'
|
||||
Loading…
Reference in new issue