from unittest import mock import pytest from faker import Faker from pytest_mock import MockerFixture from app.config import settings from app.handlers import ProcessSongwhipEventRequest, process_songwhip_event from app.types import EventRecord from tests.unit.factories import EmailPresaveEventFactory, NativePresaveEventFactory from tests.unit.helpers import override_settings class TestProcessSongwhipEventHandler: @pytest.fixture(autouse=True) def producer_mock(self, mocker: MockerFixture) -> mock.MagicMock: return mocker.patch("app.handlers.kafka_producer", autospec=True) @pytest.fixture(autouse=True) def kms_decrypt_mock(self, mocker: MockerFixture) -> mock.MagicMock: return mocker.patch( "app.handlers.aws_kms.decrypt", return_value="decrypted-token" ) @pytest.fixture def create_fan_connection_mock(self, mocker: MockerFixture) -> mock.MagicMock: return mocker.patch("app.handlers.ows_resonance_engine.create_fan_connection") def test_handle_no_events(self, producer_mock: mock.MagicMock) -> None: process_songwhip_event(ProcessSongwhipEventRequest(events=[])) producer_mock.publish.assert_not_called() producer_mock.flush.assert_not_called() def test_handle_presave_email_event( self, producer_mock: mock.MagicMock, faker: Faker ) -> None: product_upc = faker.pystr() email_presave_event = EmailPresaveEventFactory.build(productUpc=product_upc) process_songwhip_event( ProcessSongwhipEventRequest(events=[email_presave_event]) ) record = EventRecord.model_construct( task_id=email_presave_event.task_id, group_id=email_presave_event.group_id, album_id=email_presave_event.album_id, country=email_presave_event.country, product_upc=product_upc, store_name=email_presave_event.store_name, store_id=email_presave_event.store_id, client_id=email_presave_event.client_id, client_ip=email_presave_event.client_ip, email=email_presave_event.email, token=None, full_name=None, city=email_presave_event.city, region=email_presave_event.region, region_code=email_presave_event.region_code, latitude=email_presave_event.latitude, longitude=email_presave_event.longitude, global_participant_id=None, vendor_id=None, subaccount_id=None, created_at=email_presave_event.created_at, ) producer_mock.publish.assert_called_once_with( topic=settings.kafka_producer_topic, value=record.model_dump_json(by_alias=True), key=record.key, ) producer_mock.flush.assert_called_once() def test_handle_presave_email_event_empty_product_upc( self, producer_mock: mock.MagicMock ) -> None: email_presave_event = EmailPresaveEventFactory.build(productUpc=None) process_songwhip_event( ProcessSongwhipEventRequest(events=[email_presave_event]) ) record = EventRecord.model_construct( task_id=email_presave_event.task_id, group_id=email_presave_event.group_id, album_id=email_presave_event.album_id, country=email_presave_event.country, product_upc=email_presave_event.product_upc, store_name=email_presave_event.store_name, store_id=email_presave_event.store_id, client_id=email_presave_event.client_id, client_ip=email_presave_event.client_ip, email=email_presave_event.email, token=None, full_name=None, city=email_presave_event.city, region=email_presave_event.region, region_code=email_presave_event.region_code, latitude=email_presave_event.latitude, longitude=email_presave_event.longitude, global_participant_id=None, vendor_id=None, subaccount_id=None, created_at=email_presave_event.created_at, ) producer_mock.publish.assert_called_once_with( topic=settings.kafka_producer_topic, value=record.model_dump_json(by_alias=True), key=record.key, ) producer_mock.flush.assert_called_once() def test_handle_presave_native_event( self, producer_mock: mock.MagicMock, faker: Faker, ) -> None: product_upc = faker.pystr() native_presave_event = NativePresaveEventFactory.build(productUpc=product_upc) process_songwhip_event( ProcessSongwhipEventRequest(events=[native_presave_event]) ) record = EventRecord.model_construct( task_id=native_presave_event.task_id, group_id=native_presave_event.group_id, album_id=native_presave_event.album_id, country=native_presave_event.country, product_upc=product_upc, store_name=native_presave_event.store_name, store_id=native_presave_event.store_id, client_id=native_presave_event.client_id, client_ip=native_presave_event.client_ip, email=native_presave_event.email, token=native_presave_event.token, full_name=native_presave_event.full_name, city=native_presave_event.city, region=native_presave_event.region, region_code=native_presave_event.region_code, latitude=native_presave_event.latitude, longitude=native_presave_event.longitude, global_participant_id=None, vendor_id=None, subaccount_id=None, created_at=native_presave_event.created_at, ) producer_mock.publish.assert_called_once_with( topic=settings.kafka_producer_topic, value=record.model_dump_json(by_alias=True), key=record.key, ) producer_mock.flush.assert_called_once() def test_handle_presave_native_event_empty_product_upc( self, producer_mock: mock.MagicMock, faker: Faker ) -> None: native_presave_event = NativePresaveEventFactory.build(productUpc=None) process_songwhip_event( ProcessSongwhipEventRequest(events=[native_presave_event]) ) record = EventRecord.model_construct( task_id=native_presave_event.task_id, group_id=native_presave_event.group_id, album_id=native_presave_event.album_id, country=native_presave_event.country, product_upc=native_presave_event.product_upc, store_name=native_presave_event.store_name, store_id=native_presave_event.store_id, client_id=native_presave_event.client_id, client_ip=native_presave_event.client_ip, email=native_presave_event.email, token=native_presave_event.token, full_name=native_presave_event.full_name, city=native_presave_event.city, region=native_presave_event.region, region_code=native_presave_event.region_code, latitude=native_presave_event.latitude, longitude=native_presave_event.longitude, global_participant_id=None, vendor_id=None, subaccount_id=None, created_at=native_presave_event.created_at, ) producer_mock.publish.assert_called_once_with( topic=settings.kafka_producer_topic, value=record.model_dump_json(by_alias=True), key=record.key, ) producer_mock.flush.assert_called_once() def test_handle_presave_email_event_no_label_participants( self, producer_mock: mock.MagicMock, faker: Faker ) -> None: product_upc = faker.pystr() email_presave_event = EmailPresaveEventFactory.build(product_upc=product_upc) process_songwhip_event( ProcessSongwhipEventRequest(events=[email_presave_event]) ) record = EventRecord.model_construct( task_id=email_presave_event.task_id, group_id=email_presave_event.group_id, album_id=email_presave_event.album_id, country=email_presave_event.country, product_upc=email_presave_event.product_upc, store_name=email_presave_event.store_name, store_id=email_presave_event.store_id, client_id=email_presave_event.client_id, client_ip=email_presave_event.client_ip, email=email_presave_event.email, token=None, full_name=None, city=email_presave_event.city, region=email_presave_event.region, region_code=email_presave_event.region_code, latitude=email_presave_event.latitude, longitude=email_presave_event.longitude, global_participant_id=None, vendor_id=None, subaccount_id=None, created_at=email_presave_event.created_at, ) producer_mock.publish.assert_called_once_with( topic=settings.kafka_producer_topic, value=record.model_dump_json(by_alias=True), key=record.key, ) producer_mock.flush.assert_called_once() def test_sends_event_when_enabled( self, kms_decrypt_mock: mock.MagicMock, create_fan_connection_mock: mock.MagicMock, ) -> None: event = NativePresaveEventFactory.build( taskId="spotify-presave-user123", email="user@mail.com", ) with override_settings(send_events_to_resonance_engine=True): process_songwhip_event(ProcessSongwhipEventRequest(events=[event])) kms_decrypt_mock.assert_called_once_with(event.token) create_fan_connection_mock.assert_called_once_with( dsp_client_name="spotify_songwhip", email="user@mail.com", token="decrypted-token", ) def test_skips_when_disabled( self, kms_decrypt_mock: mock.MagicMock, create_fan_connection_mock: mock.MagicMock, ) -> None: event = NativePresaveEventFactory.build( taskId="spotify-presave-user123", ) with override_settings(send_events_to_resonance_engine=False): process_songwhip_event(ProcessSongwhipEventRequest(events=[event])) kms_decrypt_mock.assert_not_called() create_fan_connection_mock.assert_not_called() def test_skips_email_presave_event( self, create_fan_connection_mock: mock.MagicMock, ) -> None: event = EmailPresaveEventFactory.build( taskId="spotify-presave-user123", ) with override_settings(send_events_to_resonance_engine=True): process_songwhip_event(ProcessSongwhipEventRequest(events=[event])) create_fan_connection_mock.assert_not_called() def test_skips_unsupported_dsp_event( self, create_fan_connection_mock: mock.MagicMock ) -> None: event = NativePresaveEventFactory.build( taskId="tidal-presave-user123", token="token", email="user@mail.com", ) with override_settings(send_events_to_resonance_engine=True): process_songwhip_event(ProcessSongwhipEventRequest(events=[event])) create_fan_connection_mock.assert_not_called() def test_error_does_not_break_kafka_flow( self, producer_mock: mock.MagicMock, create_fan_connection_mock: mock.MagicMock, ) -> None: create_fan_connection_mock.side_effect = RuntimeError("connection failed") event = NativePresaveEventFactory.build( taskId="spotify-presave-user123", ) with override_settings(send_events_to_resonance_engine=True): process_songwhip_event(ProcessSongwhipEventRequest(events=[event])) producer_mock.publish.assert_called_once() producer_mock.flush.assert_called_once()