# pylint: disable=unused-argument,too-many-arguments,too-many-locals,redefined-outer-name import json from datetime import datetime, timezone from unittest.mock import MagicMock import pytest from dapd_db_schema.schemas.etl import ( DimAlbum, DimArtist, DimDsp, DimMarket, DimPlaylist, DimPlaylistMeta, DimTrack, ) from dapd_transformation_service.entities.dimension_meta import DspEnum, TrackPositions from dapd_transformation_service.exceptions import ( IllegalCharError, InvalidSerializationError, SchemaValidationError, ) from dapd_transformation_service.services.upserter import UpserterService pytestmark = pytest.mark.integration FREEZED_DT = datetime(2015, 10, 15, 12, 23, 45, tzinfo=timezone.utc) def test_get_or_create_dim_dsp__ok(db, dim_dsp, logger): upserter_service = UpserterService(logger, db) assert db.query(DimDsp).count() == 1 result_dim_dsp = upserter_service.get_or_create_dim_dsp(DspEnum.APPLE_MUSIC) assert db.query(DimDsp).count() == 1 assert result_dim_dsp.dsp_id == dim_dsp.dsp_id assert DspEnum.APPLE_MUSIC.value == dim_dsp.dsp_name def test_get_or_create_dim_dsp__called_once(db, dim_dsp, mocker, logger): mocked_query = mocker.patch('sqlalchemy.orm.Session.query') upserter_service = UpserterService(logger, db) for _ in range(0, 5): upserter_service.get_or_create_dim_dsp(DspEnum.APPLE_MUSIC) assert mocked_query.call_count == 1 def test_get_or_create_dim_dsp__invalid_dsp(db, dim_dsp, logger): db.query(DimDsp).filter(DimDsp.dsp_name == DspEnum.SPOTIFY.value).delete() assert db.query(DimDsp).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.get_or_create_dim_dsp(DspEnum.SPOTIFY) assert db.query(DimDsp).count() == 2 def test_get_or_create_dim_market__ok(db, dim_market, logger): upserter_service = UpserterService(logger, db) result_dim_market = upserter_service.get_or_create_dim_market(dim_market.market_code) assert db.query(DimMarket).count() == 1 assert result_dim_market.market_id == dim_market.market_id def test_get_or_create_dim_market__invalid_code(db, dim_market, logger): assert db.query(DimMarket).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.get_or_create_dim_market('foobar') assert db.query(DimMarket).count() == 2 def test_get_or_create_dim_market__called_once(db, dim_dsp, mocker, logger): mocked_query = mocker.patch('sqlalchemy.orm.Session.query') upserter_service = UpserterService(logger, db) for _ in range(0, 5): upserter_service.get_or_create_dim_market('foo') assert mocked_query.call_count == 1 @pytest.mark.parametrize(['dsp', 'dim_type'], [ (DspEnum.SPOTIFY, DimPlaylist), (DspEnum.APPLE_MUSIC, DimPlaylist), (DspEnum.SPOTIFY, DimTrack), (DspEnum.APPLE_MUSIC, DimTrack), ]) def test_upsert_ok(db, mocker, dsp, dim_type, logger): dim = dim_type.__name__[3:].lower() mocked_dim = MagicMock() patched_processor: MagicMock = mocker.patch( f'dapd_transformation_service.services.upserter.UpserterService.process_{dsp.value}_{dim}', return_value=mocked_dim ) mocker.patch( 'dapd_transformation_service.services.upserter.UpserterService.process_playlist_metadata', ) mocker.patch( 'dapd_transformation_service.validators.json_schema_validator.JsonSchemaValidator.validate' ) mocker.patch( 'dapd_transformation_service.services.facts.fact_track_dynamics.' 'FactTrackDynamicsService.process_dimension' ) upserter_service = UpserterService(logger, db) upserter_service.upsert(b'{"foo": "bar"}', dim_type, dsp) patched_processor.assert_called_once_with({'foo': 'bar'}) @pytest.mark.parametrize(['dsp', 'dim_type'], [ (DspEnum.SPOTIFY, DimPlaylist), (DspEnum.APPLE_MUSIC, DimPlaylist), ]) def test_upsert__fact_track_dynamics_called(db, mocker, dsp, dim_type, logger): dim = dim_type.__name__[3:].lower() mocked_dim = MagicMock() mocker.patch( f'dapd_transformation_service.services.upserter.UpserterService.process_{dsp.value}_{dim}', return_value=mocked_dim ) mocker.patch( 'dapd_transformation_service.services.upserter.UpserterService.process_playlist_metadata' ) mocker.patch( 'dapd_transformation_service.validators.json_schema_validator.JsonSchemaValidator.validate' ) patched_fact_track_dynamics: MagicMock = mocker.patch( 'dapd_transformation_service.services.facts.fact_track_dynamics.' 'FactTrackDynamicsService.process_dimension' ) upserter_service = UpserterService(logger, db) upserter_service.upsert(b'{"foo": "bar"}', dim_type, dsp) patched_fact_track_dynamics.assert_called_once_with(mocked_dim, {'foo': 'bar'}) @pytest.mark.parametrize(['dsp', 'dim_type'], [ (DspEnum.SPOTIFY, DimTrack), (DspEnum.APPLE_MUSIC, DimTrack), ]) def test_upsert__fact_track_dynamics_not_called(db, mocker, dsp, dim_type, logger): dim = dim_type.__name__[3:].lower() mocked_dim = MagicMock() mocker.patch( f'dapd_transformation_service.services.upserter.UpserterService.process_{dsp.value}_{dim}', return_value=mocked_dim ) mocker.patch( 'dapd_transformation_service.services.upserter.UpserterService.process_playlist_metadata' ) mocker.patch( 'dapd_transformation_service.validators.json_schema_validator.JsonSchemaValidator.validate' ) patched_fact_track_dynamics: MagicMock = mocker.patch( 'dapd_transformation_service.services.facts.fact_track_dynamics.' 'FactTrackDynamicsService.process_dimension' ) upserter_service = UpserterService(logger, db) upserter_service.upsert(b'{"foo": "bar"}', dim_type, dsp) assert not patched_fact_track_dynamics.called @pytest.mark.parametrize(['dsp', 'dim_type'], [ (DspEnum.APPLE_MUSIC, DimArtist), (DspEnum.SPOTIFY, DimAlbum), (DspEnum.APPLE_MUSIC, DimAlbum), ]) def test_upsert_noop(db, mocker, dsp, dim_type, logger): patched_processor: MagicMock = mocker.patch( 'dapd_transformation_service.services.upserter.UpserterService.process_noop' ) mocker.patch( 'dapd_transformation_service.validators.json_schema_validator.JsonSchemaValidator.validate' ) patched_fact_track_dynamics: MagicMock = mocker.patch( 'dapd_transformation_service.services.facts.fact_track_dynamics.' 'FactTrackDynamicsService.process_dimension' ) mocker.patch( 'dapd_transformation_service.services.upserter.UpserterService.process_playlist_metadata', ) upserter_service = UpserterService(logger, db) upserter_service.upsert(b'{"foo": "bar"}', dim_type, dsp) patched_processor.assert_called_once_with({'foo': 'bar'}) assert not patched_fact_track_dynamics.called @pytest.mark.parametrize(['dsp', 'dim_type'], [ (DspEnum.SPOTIFY, DimPlaylist), (DspEnum.APPLE_MUSIC, DimPlaylist), (DspEnum.SPOTIFY, DimTrack), (DspEnum.APPLE_MUSIC, DimTrack), ]) def test_upsert_skipped_for_invalid_data(db, mocker, dsp, dim_type, logger): dim = dim_type.__name__[3:].lower() patched_processor: MagicMock = mocker.patch( f'dapd_transformation_service.services.upserter.UpserterService.process_{dsp.value}_{dim}' ) mocker.patch( 'dapd_transformation_service.services.upserter.UpserterService.deserialize_raw_data', side_effect=InvalidSerializationError, ) upserter_service = UpserterService(logger, db) upserter_service.upsert(b'{"foo": "bar"}', dim_type, dsp) assert not patched_processor.called @pytest.mark.parametrize(['dsp', 'dim_type'], [ (DspEnum.SPOTIFY, DimPlaylist), (DspEnum.APPLE_MUSIC, DimPlaylist), (DspEnum.SPOTIFY, DimTrack), (DspEnum.APPLE_MUSIC, DimTrack), ]) def test_upsert_skipped_for_invalid_schema(db, mocker, dsp, dim_type, logger): dim = dim_type.__name__[3:].lower() patched_processor: MagicMock = mocker.patch( f'dapd_transformation_service.services.upserter.UpserterService.process_{dsp.value}_{dim}' ) mocker.patch( 'dapd_transformation_service.validators.json_schema_validator.JsonSchemaValidator.validate', side_effect=SchemaValidationError, ) upserter_service = UpserterService(logger, db) upserter_service.upsert(b'{"foo": "bar"}', dim_type, dsp) assert not patched_processor.called @pytest.mark.parametrize(['dsp', 'dim_type'], [ (DspEnum.SPOTIFY, DimPlaylist), (DspEnum.APPLE_MUSIC, DimPlaylist), (DspEnum.SPOTIFY, DimTrack), (DspEnum.APPLE_MUSIC, DimTrack), ]) def test_upsert_skipped_for_illegal_char(db, mocker, dsp, dim_type, logger): dim = dim_type.__name__[3:].lower() patched_processor: MagicMock = mocker.patch( f'dapd_transformation_service.services.upserter.UpserterService.process_{dsp.value}_{dim}' ) mocker.patch( 'dapd_transformation_service.validators.illegal_chars_validator.' 'IllegalCharsValidator.validate', side_effect=IllegalCharError, ) upserter_service = UpserterService(logger, db) upserter_service.upsert(b'{"foo": "bar"}', dim_type, dsp) assert not patched_processor.called # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimPlaylist]]) def test_process_spotify_playlist__insert_new(db, dim_playlist, dim_market, raw_data, logger): data = json.loads(raw_data) expected_item = data['item'] expected_meta = data['meta'] dim_dsp = DimDsp(dsp_name=DspEnum.SPOTIFY) db.add(dim_dsp) db.flush() dim_playlist.dsp_id = dim_dsp.dsp_id dim_playlist.market_id = dim_market.market_id assert db.query(DimPlaylist).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.process_spotify_playlist(data) assert db.query(DimPlaylist).count() == 2 dim_instance: DimPlaylist = db.query(DimPlaylist) \ .filter(DimPlaylist.playlist_id != dim_playlist.playlist_id).one() assert dim_instance.playlist_id == expected_item['id'] assert dim_instance.dsp_id == \ db.query(DimDsp).filter(DimDsp.dsp_name == DspEnum.SPOTIFY).one().dsp_id assert dim_instance.market_id == dim_market.market_id assert dim_instance.uri == expected_item['uri'] # assert dim_instance.user_id == expected_item['owner']['id'] assert dim_instance.is_public == expected_item['public'] assert dim_instance.is_personalized == expected_meta['is_personalized'] assert dim_instance.playlist_name == expected_item['name'] assert dim_instance.created_at > dim_playlist.created_at assert dim_instance.updated_at == FREEZED_DT assert dim_instance.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimPlaylist]]) def test_process_spotify_playlist__update_existing(db, dim_market, dim_playlist, raw_data, logger): data = json.loads(raw_data) expected_item = data['item'] expected_meta = data['meta'] dim_dsp = DimDsp(dsp_name=DspEnum.SPOTIFY) db.add(dim_dsp) db.flush() dim_playlist.dsp_id = dim_dsp.dsp_id dim_playlist.market_id = dim_market.market_id dim_playlist.playlist_id = expected_item['id'] assert db.query(DimPlaylist).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.process_spotify_playlist(data) assert db.query(DimPlaylist).count() == 1 dim_instance: DimPlaylist = db.query(DimPlaylist).one() assert dim_instance.playlist_id == expected_item['id'] assert dim_instance.dsp_id == \ db.query(DimDsp).filter(DimDsp.dsp_name == DspEnum.SPOTIFY).one().dsp_id assert dim_instance.market_id == dim_market.market_id assert dim_instance.uri == expected_item['uri'] # assert dim_instance.user_id == expected_item['owner']['id'] assert dim_instance.is_public == expected_item['public'] assert dim_instance.is_personalized == expected_meta['is_personalized'] assert dim_instance.playlist_name == expected_item['name'] assert dim_instance.created_at == dim_playlist.created_at assert dim_instance.updated_at == FREEZED_DT assert dim_instance.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimPlaylist]]) def test_process_spotify_playlist__fill_track_positions( db, dim_playlist, dim_market, raw_data, logger ): data = json.loads(raw_data) expected_track_items = data['item']['tracks']['items'] dim_dsp = DimDsp(dsp_name=DspEnum.SPOTIFY) db.add(dim_dsp) db.flush() dim_playlist.dsp_id = dim_dsp.dsp_id dim_playlist.market_id = dim_market.market_id upserter_service = UpserterService(logger, db) dim_instance = upserter_service.process_spotify_playlist(data) fetched_dim_instance: DimPlaylist = db.query(DimPlaylist) \ .filter(DimPlaylist.playlist_id != dim_playlist.playlist_id).one() assert dim_instance.dsp_id == fetched_dim_instance.dsp_id assert dim_instance.market_id == fetched_dim_instance.market_id assert dim_instance.playlist_id == fetched_dim_instance.playlist_id assert hasattr(dim_instance.metadata, 'track_positions') assert len(dim_instance.metadata.track_positions) == len(expected_track_items) actual_track_items = dim_instance.metadata.track_positions.to_plain_list() for actual_track_item, expected_item in zip(actual_track_items, expected_track_items): assert actual_track_item['position'] == expected_item['track']['position'] assert actual_track_item['track_id'] == expected_item['track']['id'] # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimPlaylist]]) def test_process_spotify_playlist__related_tracks_processed( db, dim_playlist, dim_market, raw_data, logger ): data = json.loads(raw_data) expected_track_items = data['item']['tracks']['items'] dim_dsp = DimDsp(dsp_name=DspEnum.SPOTIFY) db.add(dim_dsp) db.flush() dim_playlist.dsp_id = dim_dsp.dsp_id dim_playlist.market_id = dim_market.market_id assert db.query(DimTrack).count() == 0 upserter_service = UpserterService(logger, db) upserter_service.process_spotify_playlist(data) assert db.query(DimTrack).count() == len(expected_track_items) for expected_item, actual_dimension in zip(expected_track_items, db.query(DimTrack).all()): actual_dimension: DimTrack assert expected_item['track']['id'] == actual_dimension.track_id assert expected_item['track']['name'] == actual_dimension.track_name assert expected_item['track']['duration'] == actual_dimension.track_duration assert expected_item['track']['external_ids']['isrc'] == actual_dimension.isrc assert dim_dsp == actual_dimension.dsp assert dim_market == actual_dimension.market assert actual_dimension.updated_at == FREEZED_DT assert not actual_dimension.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimPlaylist]]) def test_process_spotify_playlist__related_skip_isrc( db, dim_playlist, dim_market, raw_data, logger ): data = json.loads(raw_data) expected_track_items = data['item']['tracks']['items'] del expected_track_items[0]['track']['external_ids'] dim_dsp = DimDsp(dsp_name=DspEnum.SPOTIFY) db.add(dim_dsp) db.flush() dim_playlist.dsp_id = dim_dsp.dsp_id dim_playlist.market_id = dim_market.market_id upserter_service = UpserterService(logger, db) upserter_service.process_spotify_playlist(data) dim_track: DimTrack = db.query(DimTrack) \ .filter(DimTrack.track_id == expected_track_items[0]['track']['id']) \ .first() assert dim_track.isrc is None # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.APPLE_MUSIC, DimPlaylist]]) def test_process_apple_music_playlist__insert_new( db, dim_dsp, dim_playlist, dim_market, raw_data, logger ): data = json.loads(raw_data) expected_item = data['item']['data'][0] assert db.query(DimPlaylist).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.process_apple_music_playlist(data) assert db.query(DimPlaylist).count() == 2 expected_dim_market = db.query(DimMarket) \ .filter(DimMarket.market_code == data['meta']['item_storefront']).one() dim_instance: DimPlaylist = db.query(DimPlaylist) \ .filter(DimPlaylist.playlist_id != dim_playlist.playlist_id).one() assert dim_instance.playlist_id == expected_item['id'] assert dim_instance.dsp_id == dim_dsp.dsp_id assert dim_instance.market_id == expected_dim_market.market_id assert dim_instance.uri == expected_item['href'] # assert dim_instance.user_id == expected_item['relationships']['curator']['data'][0]['id'] # assert not dim_instance.is_public assert dim_instance.playlist_name == expected_item['attributes']['name'] assert dim_instance.created_at > dim_playlist.created_at assert dim_instance.updated_at == FREEZED_DT assert dim_instance.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.APPLE_MUSIC, DimPlaylist]]) def test_process_apple_music_playlist__update_existing( db, dim_dsp, dim_playlist, dim_market, raw_data, logger, ): data = json.loads(raw_data) expected_item = data['item']['data'][0] dim_market.market_code = 'gh' dim_playlist.dsp_id = dim_dsp.dsp_id dim_playlist.playlist_id = expected_item['id'] assert db.query(DimPlaylist).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.process_apple_music_playlist(data) assert db.query(DimPlaylist).count() == 1 expected_dim_market = db.query(DimMarket) \ .filter(DimMarket.market_code == data['meta']['item_storefront']).one() dim_instance: DimPlaylist = db.query(DimPlaylist).one() assert dim_instance.playlist_id == expected_item['id'] assert dim_instance.dsp_id == dim_dsp.dsp_id assert dim_instance.market_id == expected_dim_market.market_id assert dim_instance.uri == expected_item['href'] # assert dim_instance.user_id == expected_item['relationships']['curator']['data'][0]['id'] assert not dim_instance.is_public assert dim_instance.playlist_name == expected_item['attributes']['name'] assert dim_instance.created_at == dim_playlist.created_at assert dim_instance.updated_at == FREEZED_DT assert dim_instance.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.APPLE_MUSIC, DimPlaylist]]) def test_process_apple_music_playlist__fill_track_positions( db, dim_dsp, dim_playlist, dim_market, raw_data, logger, ): data = json.loads(raw_data) expected_track_items = data['item']['data'][0]['relationships']['tracks']['data'] upserter_service = UpserterService(logger, db) dim_instance = upserter_service.process_apple_music_playlist(data) fetched_dim_instance: DimPlaylist = db.query(DimPlaylist) \ .filter(DimPlaylist.playlist_id != dim_playlist.playlist_id).one() assert dim_instance.dsp_id == fetched_dim_instance.dsp_id assert dim_instance.market_id == fetched_dim_instance.market_id assert dim_instance.playlist_id == fetched_dim_instance.playlist_id assert hasattr(dim_instance.metadata, 'track_positions') assert len(dim_instance.metadata.track_positions) == len(expected_track_items) actual_track_items = dim_instance.metadata.track_positions.to_plain_list() for actual_track_item, expected_item in zip(actual_track_items, expected_track_items): assert actual_track_item['position'] == expected_item['position'] assert actual_track_item['track_id'] == expected_item['id'] # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.APPLE_MUSIC, DimPlaylist]]) def test_process_apple_music_playlist__related_tracks_processed( db, dim_dsp, dim_playlist, dim_market, raw_data, logger, ): data = json.loads(raw_data) expected_track_items = data['item']['data'][0]['relationships']['tracks']['data'] assert db.query(DimTrack).count() == 0 upserter_service = UpserterService(logger, db) upserter_service.process_apple_music_playlist(data) assert db.query(DimTrack).count() == len(expected_track_items) expected_dim_market = db.query(DimMarket) \ .filter(DimMarket.market_code == data['meta']['item_storefront']).one() for expected_item, actual_dimension in zip(expected_track_items, db.query(DimTrack).all()): actual_dimension: DimTrack assert expected_item['id'] == actual_dimension.track_id assert expected_item['attributes']['name'] == actual_dimension.track_name assert expected_item['attributes']['duration'] == actual_dimension.track_duration assert expected_item['attributes']['isrc'] == actual_dimension.isrc assert expected_dim_market == actual_dimension.market assert dim_dsp == actual_dimension.dsp assert actual_dimension.updated_at == FREEZED_DT assert not actual_dimension.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.APPLE_MUSIC, DimPlaylist]]) def test_process_apple_music_playlist__related_tracks_skip_isrc( db, dim_dsp, dim_playlist, dim_market, raw_data, logger, ): data = json.loads(raw_data) expected_track_items = data['item']['data'][0]['relationships']['tracks']['data'] del expected_track_items[0]['attributes']['isrc'] upserter_service = UpserterService(logger, db) upserter_service.process_apple_music_playlist(data) dim_track: DimTrack = db.query(DimTrack) \ .filter(DimTrack.track_id == expected_track_items[0]['id']) \ .first() assert dim_track.isrc is None # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimTrack]]) def test_process_spotify_track__insert_new(db, dim_track, dim_market, raw_data, logger): data = json.loads(raw_data) expected_item = data['item'] dim_dsp = DimDsp(dsp_name=DspEnum.SPOTIFY) db.add(dim_dsp) db.flush() dim_market.market_code = data['meta']['item_storefront'] dim_track.dsp_id = dim_dsp.dsp_id dim_track.market_id = dim_market.market_id assert db.query(DimTrack).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.process_spotify_track(data) assert db.query(DimTrack).count() == 2 dim_instance: DimTrack = db.query(DimTrack) \ .filter(DimTrack.track_id != dim_track.track_id).one() assert dim_instance.track_id == expected_item['id'] assert dim_instance.dsp_id == \ db.query(DimDsp).filter(DimDsp.dsp_name == DspEnum.SPOTIFY).one().dsp_id assert dim_instance.market_id == dim_market.market_id assert dim_instance.track_name == expected_item['name'] assert dim_instance.track_duration == expected_item['duration_ms'] assert dim_instance.isrc == expected_item['external_ids']['isrc'] assert dim_instance.created_at > dim_track.created_at assert dim_instance.updated_at == FREEZED_DT assert dim_instance.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimTrack]]) def test_process_spotify_track__update_existing(db, dim_market, dim_track, raw_data, logger): data = json.loads(raw_data) expected_item = data['item'] dim_dsp = DimDsp(dsp_name=DspEnum.SPOTIFY) db.add(dim_dsp) db.flush() dim_market.market_code = data['meta']['item_storefront'] dim_track.dsp_id = dim_dsp.dsp_id dim_track.market_id = dim_market.market_id dim_track.track_id = expected_item['id'] assert db.query(DimTrack).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.process_spotify_track(data) assert db.query(DimTrack).count() == 1 dim_instance: DimTrack = db.query(DimTrack).one() assert dim_instance.track_id == expected_item['id'] assert dim_instance.dsp_id == \ db.query(DimDsp).filter(DimDsp.dsp_name == DspEnum.SPOTIFY).one().dsp_id assert dim_instance.market_id == dim_market.market_id assert dim_instance.track_name == expected_item['name'] assert dim_instance.track_duration == expected_item['duration_ms'] assert dim_instance.isrc == expected_item['external_ids']['isrc'] assert dim_instance.created_at == dim_track.created_at assert dim_instance.updated_at == FREEZED_DT assert dim_instance.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimTrack]]) def test_process_spotify_track__related_artist_processed( db, dim_track, dim_market, raw_data, logger ): data = json.loads(raw_data) expected_artist_item = data['item']['artists'][0] dim_dsp = DimDsp(dsp_name=DspEnum.SPOTIFY) db.add(dim_dsp) db.flush() dim_market.market_code = data['meta']['item_storefront'] dim_track.dsp_id = dim_dsp.dsp_id dim_track.market_id = dim_market.market_id assert db.query(DimArtist).count() == 0 upserter_service = UpserterService(logger, db) upserter_service.process_spotify_track(data) assert db.query(DimArtist).count() == 1 dim_artist: DimArtist = db.query(DimArtist).one() assert dim_artist.artist_name == expected_artist_item['name'] assert dim_artist.artist_uri == expected_artist_item['uri'] assert dim_artist.dsp == dim_dsp assert dim_artist.market == dim_market assert dim_artist.created_at > FREEZED_DT assert dim_artist.updated_at == FREEZED_DT assert not dim_artist.is_stream_synced current_dim_track: DimTrack = db.query(DimTrack) \ .filter(DimTrack.track_id != dim_track.track_id).one() assert current_dim_track.artists[0].artist == dim_artist assert current_dim_track == dim_artist.tracks[0].track assert current_dim_track.artists[0].artist_number == 1 # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimTrack]]) def test_process_spotify_track__related_album_processed( db, dim_track, dim_market, raw_data, logger ): data = json.loads(raw_data) expected_album_item = data['item']['album'] dim_dsp = DimDsp(dsp_name=DspEnum.SPOTIFY) db.add(dim_dsp) db.flush() dim_market.market_code = data['meta']['item_storefront'] dim_track.dsp_id = dim_dsp.dsp_id dim_track.market_id = dim_market.market_id assert db.query(DimAlbum).count() == 0 upserter_service = UpserterService(logger, db) upserter_service.process_spotify_track(data) assert db.query(DimAlbum).count() == 1 dim_album: DimAlbum = db.query(DimAlbum).one() assert dim_album.album_name == expected_album_item['name'] assert dim_album.release_date == expected_album_item['release_date'] assert dim_album.release_date_precision == expected_album_item['release_date_precision'] assert dim_album.track_count == expected_album_item['total_tracks'] assert not dim_album.is_stream_synced assert dim_album.dsp == dim_dsp assert dim_album.market == dim_market current_dim_track: DimTrack = db.query(DimTrack) \ .filter(DimTrack.track_id != dim_track.track_id).one() assert dim_album.tracks[0].track == current_dim_track assert current_dim_track.albums[0].album == dim_album assert dim_album.tracks[0].track.albums[0].album == dim_album assert dim_album.tracks[0].track.artists[0].artist.albums[0].album == dim_album # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.APPLE_MUSIC, DimTrack]]) def test_process_apple_music_track__insert_new( db, dim_dsp, dim_track, dim_market, raw_data, logger ): data = json.loads(raw_data) expected_item = data['item']['data'][0] assert db.query(DimTrack).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.process_apple_music_track(data) assert db.query(DimTrack).count() == 2 expected_dim_market = db.query(DimMarket) \ .filter(DimMarket.market_code == data['meta']['item_storefront']).one() dim_instance: DimTrack = db.query(DimTrack) \ .filter(DimTrack.track_id != dim_track.track_id).one() assert dim_instance.track_id == expected_item['id'] assert dim_instance.dsp_id == dim_dsp.dsp_id assert dim_instance.market_id == expected_dim_market.market_id assert dim_instance.track_name == expected_item['attributes']['name'] assert dim_instance.track_duration == expected_item['attributes']['durationInMillis'] assert dim_instance.isrc == expected_item['attributes']['isrc'] assert dim_instance.created_at > dim_track.created_at assert dim_instance.updated_at == FREEZED_DT assert dim_instance.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.APPLE_MUSIC, DimTrack]]) def test_process_apple_music_track__update_existing( db, dim_dsp, dim_track, dim_market, raw_data, logger ): data = json.loads(raw_data) expected_item = data['item']['data'][0] dim_market.market_code = 'to' dim_track.dsp_id = dim_dsp.dsp_id dim_track.track_id = expected_item['id'] assert db.query(DimTrack).count() == 1 upserter_service = UpserterService(logger, db) upserter_service.process_apple_music_track(data) assert db.query(DimTrack).count() == 1 expected_dim_market = db.query(DimMarket) \ .filter(DimMarket.market_code == data['meta']['item_storefront']).one() dim_instance: DimTrack = db.query(DimTrack).one() assert dim_instance.track_id == expected_item['id'] assert dim_instance.dsp_id == dim_dsp.dsp_id assert dim_instance.market_id == expected_dim_market.market_id assert dim_instance.track_name == expected_item['attributes']['name'] assert dim_instance.track_duration == expected_item['attributes']['durationInMillis'] assert dim_instance.isrc == expected_item['attributes']['isrc'] assert dim_instance.created_at == dim_track.created_at assert dim_instance.updated_at == FREEZED_DT assert dim_instance.is_stream_synced # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.APPLE_MUSIC, DimTrack]]) def test_process_apple_music_track__related_artist_processed( db, dim_dsp, dim_track, dim_market, raw_data, logger, ): data = json.loads(raw_data) expected_artist_item = data['item']['data'][0]['relationships']['artists']['data'][0] assert db.query(DimArtist).count() == 0 upserter_service = UpserterService(logger, db) upserter_service.process_apple_music_track(data) assert db.query(DimArtist).count() == 1 dim_artist: DimArtist = db.query(DimArtist).one() assert dim_artist.artist_name == expected_artist_item['attributes']['name'] assert dim_artist.artist_uri == expected_artist_item['href'] assert dim_artist.dsp == dim_dsp assert dim_artist.created_at > FREEZED_DT assert dim_artist.updated_at == FREEZED_DT assert not dim_artist.is_stream_synced current_dim_market = db.query(DimMarket) \ .filter(DimMarket.market_code == data['meta']['item_storefront']).one() assert dim_artist.market == current_dim_market current_dim_track: DimTrack = db.query(DimTrack) \ .filter(DimTrack.track_id != dim_track.track_id).one() assert current_dim_track.artists[0].artist == dim_artist assert current_dim_track == dim_artist.tracks[0].track assert current_dim_track.artists[0].artist_number == 1 # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.APPLE_MUSIC, DimTrack]]) def test_process_apple_music_track__related_album_processed( db, dim_dsp, dim_track, dim_market, raw_data, logger, ): data = json.loads(raw_data) expected_album_item = data['item']['data'][0]['relationships']['albums']['data'][0] assert db.query(DimAlbum).count() == 0 upserter_service = UpserterService(logger, db) upserter_service.process_apple_music_track(data) assert db.query(DimAlbum).count() == 1 dim_album: DimAlbum = db.query(DimAlbum).one() assert dim_album.album_name == expected_album_item['attributes']['name'] assert dim_album.release_date == expected_album_item['attributes']['releaseDate'] assert dim_album.release_date_precision is None assert dim_album.track_count == expected_album_item['attributes']['trackCount'] assert dim_album.is_mastered_for_itunes \ == expected_album_item['attributes']['isMasteredForItunes'] assert dim_album.is_complete == expected_album_item['attributes']['isComplete'] assert dim_album.copyright == expected_album_item['attributes']['copyright'] assert dim_album.lbl == expected_album_item['attributes']['recordLabel'] assert not dim_album.is_stream_synced current_dim_market = db.query(DimMarket) \ .filter(DimMarket.market_code == data['meta']['item_storefront']).one() assert dim_album.dsp == dim_dsp assert dim_album.market == current_dim_market current_dim_track: DimTrack = db.query(DimTrack) \ .filter(DimTrack.track_id != dim_track.track_id).one() assert dim_album.tracks[0].track == current_dim_track assert current_dim_track.albums[0].album == dim_album assert dim_album.tracks[0].track.albums[0].album == dim_album assert dim_album.tracks[0].track.artists[0].artist.albums[0].album == dim_album def test_process_playlist_metadata__ok(db, dim_playlist, logger): track_positions = TrackPositions() for idx in range(1, 4): track_positions.add(position=idx, track_id=f'track_id_{idx}') dim_playlist.metadata.track_positions = track_positions assert db.query(DimPlaylistMeta).count() == 0 upserter_service = UpserterService(logger, db) upserter_service.process_playlist_metadata(dim_playlist) assert db.query(DimPlaylistMeta).count() == 1 dim_playlist_meta: DimPlaylistMeta = db.query(DimPlaylistMeta).one() assert dim_playlist_meta.playlist == dim_playlist assert dim_playlist_meta.dsp_id == dim_playlist.dsp_id assert dim_playlist_meta.market_id == dim_playlist.market_id assert dim_playlist_meta.playlist_id == dim_playlist.playlist_id assert dim_playlist_meta.created_at is not None assert dim_playlist_meta.track_positions == track_positions.to_plain_list() def test_process_playlist_metadata__inserted_twice(db, dim_playlist, logger): track_positions = TrackPositions() for idx in range(1, 4): track_positions.add(position=idx, track_id=f'track_id_{idx}') dim_playlist.metadata.track_positions = track_positions assert db.query(DimPlaylistMeta).count() == 0 upserter_service = UpserterService(logger, db) upserter_service.process_playlist_metadata(dim_playlist) upserter_service.process_playlist_metadata(dim_playlist) assert db.query(DimPlaylistMeta).count() == 2 # noinspection PyTestParametrized @pytest.mark.freeze_time(FREEZED_DT) @pytest.mark.parametrize(['dsp', 'dim_type'], [[DspEnum.SPOTIFY, DimArtist]]) def test_process_artist__add(db, raw_data, logger): data = json.loads(raw_data) expected_item = data['item'] assert db.query(DimArtist).count() == 0 upserter_service = UpserterService(logger, db) upserter_service.process_spotify_artist(data) assert db.query(DimArtist).count() == 1 expected_dim_market = db.query(DimMarket) \ .filter(DimMarket.market_code == data['meta']['item_storefront']).one() expected_dim_dsp = db.query(DimDsp).filter(DimDsp.dsp_name == 'spotify').one() dim_instance: DimArtist = db.query(DimArtist) \ .filter(DimArtist.artist_id == expected_item['id']).one() assert dim_instance.dsp_id == expected_dim_dsp.dsp_id assert dim_instance.artist_id == expected_item['id'] assert dim_instance.market_id == expected_dim_market.market_id assert dim_instance.artist_uri == expected_item['uri'] assert dim_instance.artist_name == expected_item['name'] assert dim_instance.genres == expected_item['genres'] assert dim_instance.updated_at == FREEZED_DT assert dim_instance.is_stream_synced