from unittest import mock from unittest.mock import MagicMock import faker import pytest from anydi import Container from dirty_equals import IsDatetime from faker import Faker from fansifter_common.adapters.graphql_router import GlobalParticipant from fansifter_common.adapters.twilio.models import MessagingService, PhoneNumber from fansifter_common.utils import timezone from fastapi.testclient import TestClient from pytest_mock import MockerFixture from ows_text_campaigns.adapters.aws.sts import AssumedRoleCredentials from ows_text_campaigns.adapters.scanner import ContentRecommendations, ContentScanner from ows_text_campaigns.artist.models import ArtistPhoneNumber, ArtistSettings from ows_text_campaigns.assets.models import Asset from ows_text_campaigns.assets.services import AssetService from ows_text_campaigns.assets.types import BinaryAsset from ows_text_campaigns.config import Settings from ows_text_campaigns.rosters.models import ArtistRosterMainRep from ows_text_campaigns.twilio.services import TwilioAccountService from tests.unit.types import BuildModel, CreateModel @pytest.mark.db def test_get_artist_settings( client: TestClient, create_model: CreateModel, settings: Settings ) -> None: artist_settings = create_model( ArtistSettings, opt_in_message_scan_result=ContentRecommendations( includedArtistName=True, sensitiveWords=[] ), welcome_message_scan_result=ContentRecommendations( includedArtistName=True, sensitiveWords=[] ), ) create_model( ArtistRosterMainRep, global_participant_id=artist_settings.global_participant_id, vendor_id=1, subaccount_id=0, ) response = client.get(f"/artist-settings/{artist_settings.global_participant_id}") assert response.status_code == 200 assert response.json() == { "welcomeMessage": artist_settings.welcome_message, "welcomeMessageInstructions": settings.artist_welcome_message_instructions, "optInMessage": artist_settings.opt_in_message, "optInMessageTerms": settings.artist_opt_in_message_terms, "imageUrl": artist_settings.image_url, "phoneNumbers": [], "welcomeMessageScanResult": {"sensitiveWords": [], "includedArtistName": True}, "optInMessageScanResult": {"sensitiveWords": [], "includedArtistName": True}, } @pytest.mark.db def test_create_or_update_artist_settings( container: Container, client: TestClient, settings: Settings, build_model: BuildModel, create_model: CreateModel, graphql_router_client_mock: mock.MagicMock, twilio_client_mock: mock.MagicMock, faker: faker.Faker, ) -> None: global_participant = build_model(GlobalParticipant, imageUrl=None) create_model( ArtistRosterMainRep, global_participant_id=global_participant.id, vendor_id=1, subaccount_id=0, ) graphql_router_client_mock.get_global_participant_by_gp_id.return_value = ( global_participant ) phone_number = "+12345678901" country_code = "US" twilio_account_sid = faker.pystr() twilio_messaging_service_sid = faker.pystr() twilio_client_mock.get_messaging_service.return_value = MessagingService( sid=twilio_messaging_service_sid, friendly_name="messaging", account_sid=twilio_account_sid, ) twilio_client_mock.get_messaging_phone_numbers.return_value = [ PhoneNumber( sid=faker.pystr(), phone_number=phone_number, capabilities=[], ) ] twilio_account_service_mock = mock.MagicMock(spec=TwilioAccountService) with container.override(TwilioAccountService, twilio_account_service_mock): response = client.post( f"/artist-settings/{global_participant.id}", json={ "phoneNumbers": [phone_number], "twilioAccountSid": twilio_account_sid, "twilioAuthToken": faker.pystr(), "twilioMessagingServiceSid": twilio_messaging_service_sid, }, ) assert response.status_code == 200 assert response.json() == { "welcomeMessage": "", "welcomeMessageInstructions": settings.artist_welcome_message_instructions, "optInMessage": "", "optInMessageTerms": settings.artist_opt_in_message_terms, "imageUrl": None, "phoneNumbers": [ { "countryCode": country_code, "phoneNumber": phone_number, } ], "welcomeMessageScanResult": None, "optInMessageScanResult": None, } @pytest.mark.db def test_update_artist_settings( client: TestClient, create_model: CreateModel, faker: Faker, container: Container, settings: Settings, ) -> None: artist_settings = create_model( ArtistSettings, opt_in_message_scan_result=ContentRecommendations( includedArtistName=True, sensitiveWords=[] ), welcome_message_scan_result=ContentRecommendations( includedArtistName=True, sensitiveWords=[] ), ) create_model( ArtistRosterMainRep, global_participant_id=artist_settings.global_participant_id, vendor_id=1, subaccount_id=0, ) new_welcome_message = faker.sentence() new_opt_in_message = faker.sentence() content_scanner_mock = MagicMock(spec=ContentScanner) content_scanner_mock.scan.return_value = ContentRecommendations( sensitiveWords=[], includedArtistName=True ) with container.override( ContentScanner, content_scanner_mock, ): response = client.patch( f"/artist-settings/{artist_settings.global_participant_id}", json={ "welcomeMessage": new_welcome_message, "optInMessage": new_opt_in_message, }, ) assert response.status_code == 200 assert response.json() == { "welcomeMessage": new_welcome_message, "welcomeMessageInstructions": settings.artist_welcome_message_instructions, "optInMessage": new_opt_in_message, "optInMessageTerms": settings.artist_opt_in_message_terms, "imageUrl": artist_settings.image_url, "phoneNumbers": [], "welcomeMessageScanResult": { "includedArtistName": True, "sensitiveWords": [], }, "optInMessageScanResult": {"includedArtistName": True, "sensitiveWords": []}, } @pytest.mark.db def test_create_artist_image_upload_credentials( client: TestClient, sts_client_mock: mock.Mock, build_model: BuildModel, create_model: CreateModel, settings: Settings, mocker: MockerFixture, ) -> None: artist_settings = create_model(ArtistSettings) create_model( ArtistRosterMainRep, global_participant_id=artist_settings.global_participant_id, vendor_id=1, subaccount_id=0, ) filename = "test.jpg" credentials = build_model(AssumedRoleCredentials, expiration=timezone.now()) sts_client_mock.assume_role.return_value = credentials mocker.patch( "ows_text_campaigns.assets.utils.unique_hash", return_value="123", ) response = client.post( f"/artist-settings/{artist_settings.global_participant_id}" "/image/upload/credentials", json={ "filename": filename, }, ) assert response.status_code == 201 assert response.json() == { "accessKeyId": credentials.access_key_id, "secretAccessKey": credentials.secret_access_key, "sessionToken": credentials.session_token, "expiresAt": IsDatetime(approx=credentials.expiration, iso_string=True), "key": f"assets/artist/{artist_settings.global_participant_id}/test__123.jpg", "bucket": settings.assets_bucket_name, "region": settings.aws_region_name, } @pytest.mark.db def test_create_artist_image( container: Container, client: TestClient, create_model: CreateModel, build_model: BuildModel, faker: Faker, graphql_router_client_mock: mock.MagicMock, ) -> None: global_participant = build_model(GlobalParticipant) graphql_router_client_mock.get_global_participant_by_gp_id.return_value = ( global_participant ) artist_settings = create_model( ArtistSettings, global_participant_id=global_participant.id ) create_model( ArtistRosterMainRep, global_participant_id=global_participant.id, vendor_id=1, subaccount_id=0, ) image_key = "/path/to/image.jpg" asset = create_model(Asset, key=image_key) asset_service_mock = mock.MagicMock(spec=AssetService) asset_service_mock.get_asset_from_s3.return_value = BinaryAsset.from_data( faker.image((500, 500), image_format="JPEG"), ) asset_service_mock.create_and_upload_image_to_s3.return_value = asset with container.override(AssetService, asset_service_mock): response = client.post( f"/artist-settings/{artist_settings.global_participant_id}/image", json={ "imageKey": image_key, }, ) assert response.status_code == 201 assert response.json() == { "id": asset.id, "key": asset.key, "url": asset.url, "contentType": asset.content_type, "extension": asset.extension, "originalFilename": asset.original_filename, "fileSize": asset.file_size, "createdBy": asset.created_by, "createdAt": IsDatetime(approx=asset.created_at, iso_string=True), } @pytest.mark.db def test_delete_artist_image( container: Container, client: TestClient, create_model: CreateModel, ) -> None: asset = create_model(Asset) artist_settings = create_model( ArtistSettings, asset_id=asset.id, opt_in_message_scan_result=ContentRecommendations( includedArtistName=True, sensitiveWords=[] ), welcome_message_scan_result=ContentRecommendations( includedArtistName=True, sensitiveWords=[] ), ) create_model( ArtistRosterMainRep, global_participant_id=artist_settings.global_participant_id, vendor_id=1, subaccount_id=0, ) asset_service_mock = mock.Mock(spec=AssetService) with container.override(AssetService, asset_service_mock): response = client.delete( f"/artist-settings/{artist_settings.global_participant_id}/image", ) assert response.status_code == 200 assert response.json() == { "welcomeMessage": artist_settings.welcome_message, "welcomeMessageInstructions": None, "optInMessage": artist_settings.opt_in_message, "optInMessageTerms": None, "imageUrl": None, "phoneNumbers": [], "welcomeMessageScanResult": { "includedArtistName": True, "sensitiveWords": [], }, "optInMessageScanResult": {"includedArtistName": True, "sensitiveWords": []}, } @pytest.mark.db def test_get_artist_settings_by_phone_number( client: TestClient, create_model: CreateModel, faker: faker.Faker, settings: Settings, ) -> None: global_participant_id = faker.uuid4() phone_number = "+12345678901" artist_phone_number = ArtistPhoneNumber( global_participant_id=global_participant_id, phone_number=phone_number, ) artist_settings = create_model( ArtistSettings, phone_numbers=[artist_phone_number], global_participant_id=global_participant_id, ) response = client.get(f"/artist-settings/by-phone-number/{phone_number}") assert response.status_code == 200 assert response.json() == { "artistName": artist_settings.artist_name, "globalParticipantId": artist_settings.global_participant_id, "optInMessage": artist_settings.opt_in_message, "optInMessageTerms": settings.artist_opt_in_message_terms, "reOptInMessage": settings.artist_reopt_in_message, "welcomeMessage": artist_settings.welcome_message, "welcomeMessageInstructions": settings.artist_welcome_message_instructions, "infoMessage": settings.artist_info_message, "vCardUrl": artist_phone_number.vcard_url, }