import datetime from unittest import mock import pytest from audience_common.auth.account import AccountAccess from campaigns.auth.dtos import User, UserId from campaigns.campaigns.enums import CampaignStatus from campaigns.connectors.ows_product.dtos import Product from campaigns.core.exceptions import FieldError from campaigns.meta.dtos import CampaignValidationSummary from campaigns.meta.enums import ( CampaignContentType, CampaignLinkType, CampaignSummaryId, ) from campaigns.meta.errors import MetaErrors from campaigns.meta.exceptions import ( CampaignPostDoesNotExist, CampaignValidationError, FacebookPageDoesNotExist, FacebookPageInvalidToken, ) from campaigns.meta.models import ( Campaign, CampaignImage, CampaignVideo, TargetingAudience, ) from campaigns.meta.services import CampaignPostService, FacebookPageService from campaigns.meta.validators import CampaignValidator from tests.unit.faker import FakerTyped from tests.unit.types import BuildModel, CreateModel @pytest.fixture def facebook_page_service_mock() -> mock.MagicMock: return mock.MagicMock(spec=FacebookPageService) @pytest.fixture def campaign_post_service_mock() -> mock.MagicMock: return mock.MagicMock(spec=CampaignPostService) @pytest.fixture def campaign_validator( ows_account_mock: mock.MagicMock, ows_product_mock: mock.MagicMock, facebook_page_service_mock: mock.MagicMock, campaign_post_service_mock: mock.MagicMock, ) -> CampaignValidator: return CampaignValidator( ows_account=ows_account_mock, ows_product=ows_product_mock, facebook_page_service=facebook_page_service_mock, campaign_post_service=campaign_post_service_mock, ) async def test_validate_campaign_label_id( ows_account_mock: mock.MagicMock, campaign_validator: CampaignValidator, build_model: BuildModel, user: User, ) -> None: campaign = build_model(Campaign) ows_account_mock.validate_subaccount.return_value = True await campaign_validator.validate_account(campaign, user=user) async def test_validate_campaign_label_id_not_allowed( campaign_validator: CampaignValidator, ows_account_mock: mock.MagicMock, build_model: BuildModel, user: User, ) -> None: campaign = build_model(Campaign) ows_account_mock.validate_subaccount.return_value = False with pytest.raises(CampaignValidationError) as exc_info: await campaign_validator.validate_account(campaign, user=user) assert exc_info.value.errors[0].code == MetaErrors.campaign_not_allowed_label.code async def test_validate_campaign_product_id( campaign_validator: CampaignValidator, ows_product_mock: mock.MagicMock, build_model: BuildModel, fake: FakerTyped, user: User, ) -> None: product_id = fake.integer() product_upc = fake.numeric_string() campaign = build_model(Campaign, product_id=product_id) ows_product_mock.get_product_by_id.return_value = Product( product_id=product_id, upc=product_upc, vendor_id=campaign.vendor_id, subaccount_id=campaign.subaccount_id, status="in_content", ) await campaign_validator.validate_product_id(campaign, user=user) async def test_validate_campaign_product_id_not_set( campaign_validator: CampaignValidator, build_model: BuildModel, user: User, ) -> None: campaign = build_model(Campaign, product_id=None) await campaign_validator.validate_product_id(campaign, user=user) async def test_validate_campaign_product_id_not_found( campaign_validator: CampaignValidator, ows_product_mock: mock.MagicMock, build_model: BuildModel, fake: FakerTyped, user: User, ) -> None: campaign = build_model(Campaign, product_id=fake.integer()) ows_product_mock.get_product_by_id.return_value = None with pytest.raises(CampaignValidationError) as exc_info: await campaign_validator.validate_product_id(campaign, user=user) assert exc_info.value.errors[0].code == MetaErrors.campaign_not_allowed_product.code async def test_validate_campaign_product_id_not_allowed( campaign_validator: CampaignValidator, ows_product_mock: mock.MagicMock, build_model: BuildModel, fake: FakerTyped, user_id: UserId, ) -> None: product_id = fake.integer() product_upc = fake.numeric_string() campaign = build_model(Campaign, product_id=product_id) ows_product_mock.get_product_by_id.return_value = Product( product_id=product_id, upc=product_upc, vendor_id=fake.integer(), subaccount_id=fake.integer(), status="in_content", ) with pytest.raises(CampaignValidationError) as exc_info: await campaign_validator.validate_product_id( campaign, user=User( id=user_id, account_access=AccountAccess(accounts=[campaign.account]), ), ) assert exc_info.value.errors[0].code == MetaErrors.campaign_not_allowed_product.code async def test_validate_campaign_facebook_page_id_valid( campaign_validator: CampaignValidator, facebook_page_service_mock: mock.MagicMock, create_model: CreateModel, user: User, ) -> None: campaign = await create_model(Campaign) facebook_page_service_mock.get_user_valid_page_for_account.return_value = None await campaign_validator.validate_facebook_page_id(campaign, user=user) async def test_validate_campaign_facebook_page_id_invalid_id( campaign_validator: CampaignValidator, facebook_page_service_mock: mock.MagicMock, create_model: CreateModel, user: User, ) -> None: campaign = await create_model(Campaign) facebook_page_service_mock.get_user_valid_page_for_account.side_effect = ( FacebookPageDoesNotExist ) with pytest.raises(CampaignValidationError) as exc_info: await campaign_validator.validate_facebook_page_id(campaign, user=user) assert exc_info.value.errors[0].code == MetaErrors.facebook_page_invalid_id.code async def test_validate_campaign_facebook_page_id_invalid_token( campaign_validator: CampaignValidator, facebook_page_service_mock: mock.MagicMock, create_model: CreateModel, user: User, ) -> None: campaign = await create_model(Campaign) facebook_page_service_mock.get_user_valid_page_for_account.side_effect = ( FacebookPageInvalidToken ) with pytest.raises(CampaignValidationError) as exc_info: await campaign_validator.validate_facebook_page_id(campaign, user=user) assert exc_info.value.errors[0].code == MetaErrors.facebook_page_invalid_token.code def test_validate_campaign_schedule_valid( campaign_validator: CampaignValidator, build_model: BuildModel, fake: FakerTyped ) -> None: start_time = fake.date_time_utc() campaign = build_model( Campaign, start_time=start_time, end_time=start_time + datetime.timedelta(days=10), ) campaign_validator.validate_schedule(campaign) def test_validate_campaign_schedule_invalid( campaign_validator: CampaignValidator, build_model: BuildModel, fake: FakerTyped ) -> None: start_time = fake.date_time_utc() end_time = start_time campaign = build_model(Campaign, start_time=start_time, end_time=end_time) with pytest.raises(CampaignValidationError) as exc_info: campaign_validator.validate_schedule(campaign) assert exc_info.value.errors[0].code == MetaErrors.campaign_invalid_schedule.code async def test_validate_campaign_post_external_id( campaign_validator: CampaignValidator, build_model: BuildModel, facebook_client_mock: mock.MagicMock, fake: FakerTyped, ) -> None: post_external_id = fake.pystr() campaign = build_model(Campaign, facebook_post_external_id=post_external_id) await campaign_validator.validate_post_external_id(campaign) async def test_validate_post_campaign_external_id_required( campaign_validator: CampaignValidator, build_model: BuildModel, ) -> None: campaign = build_model( Campaign, facebook_post_external_id=None, instagram_post_external_id=None ) with pytest.raises(CampaignValidationError) as exc_info: await campaign_validator.validate_post_external_id(campaign) exc = exc_info.value assert exc.errors[0].code == MetaErrors.campaign_post_external_id_required.code async def test_validate_campaign_post_external_id_invalid( campaign_validator: CampaignValidator, build_model: BuildModel, campaign_post_service_mock: mock.MagicMock, ) -> None: post_external_id = "111_222" campaign = build_model(Campaign, facebook_post_external_id=post_external_id) campaign_post_service_mock.get_campaign_post.side_effect = ( CampaignPostDoesNotExist() ) with pytest.raises(CampaignValidationError) as exc_info: await campaign_validator.validate_post_external_id(campaign) exc = exc_info.value assert exc.errors[0].code == MetaErrors.campaign_invalid_post_external_id.code async def test_validate_campaign_images( campaign_validator: CampaignValidator, create_model: CreateModel ) -> None: image = await create_model(CampaignImage, is_valid=True) campaign_validator.validate_images(image.campaign) async def test_validate_campaign_no_images( campaign_validator: CampaignValidator, create_model: CreateModel ) -> None: image = await create_model(CampaignImage, is_valid=False) with pytest.raises(CampaignValidationError) as exc_info: campaign_validator.validate_images(image.campaign) assert exc_info.value.errors[0].code == MetaErrors.campaign_no_valid_images.code async def test_validate_campaign_videos( campaign_validator: CampaignValidator, create_model: CreateModel ) -> None: image = await create_model(CampaignVideo, is_valid=True) campaign_validator.validate_videos(image.campaign) async def test_validate_campaign_no_videos( campaign_validator: CampaignValidator, create_model: CreateModel ) -> None: image = await create_model(CampaignVideo, is_valid=False) with pytest.raises(CampaignValidationError) as exc_info: campaign_validator.validate_videos(image.campaign) assert exc_info.value.errors[0].code == MetaErrors.campaign_no_valid_videos.code async def test_validate_campaign_audiences( campaign_validator: CampaignValidator, create_model: CreateModel ) -> None: targeting_audience = await create_model(TargetingAudience) campaign = await create_model(Campaign, targeting_audiences=[targeting_audience]) campaign_validator.validate_audiences(campaign) async def test_validate_campaign_audiences_empty( campaign_validator: CampaignValidator, create_model: CreateModel ) -> None: campaign = await create_model(Campaign, targeting_audience=None) with pytest.raises(CampaignValidationError) as exc_info: campaign_validator.validate_audiences(campaign) assert exc_info.value.errors[0].code == MetaErrors.campaign_no_audiences.code def test_validate_campaign_required(campaign_validator: CampaignValidator) -> None: campaign = Campaign() with pytest.raises(CampaignValidationError) as exc_info: campaign_validator.validate_required(campaign) assert FieldError.required("start_time") in exc_info.value.errors assert FieldError.required("end_time") in exc_info.value.errors def test_validate_campaign_link_type_website_url( campaign_validator: CampaignValidator, ) -> None: campaign = Campaign(link_type=CampaignLinkType.WEBSITE, website_url=None) with pytest.raises(CampaignValidationError) as exc_info: campaign_validator.validate_link_type(campaign) assert FieldError.required("website_url") in exc_info.value.errors def test_validate_campaign_link_type_landing_page_path( campaign_validator: CampaignValidator, ) -> None: campaign = Campaign(link_type=CampaignLinkType.LANDING_PAGE, landing_page_path=None) with pytest.raises(CampaignValidationError) as exc_info: campaign_validator.validate_link_type(campaign) assert FieldError.required("landing_page_path") in exc_info.value.errors async def test_get_validation_summary( campaign_validator: CampaignValidator, create_model: CreateModel, user: User, ) -> None: campaign = await create_model( Campaign, status=CampaignStatus.DRAFT, start_time=None, end_time=None, lifetime_budget=None, content_type=CampaignContentType.IMAGE, targeting_audience=None, ) summary = await campaign_validator.get_summary( campaign, user=user, camelize=False, ) assert summary == [ CampaignValidationSummary( id=CampaignSummaryId.SCHEDULE_AND_BUDGET, errors=[ FieldError.required("start_time"), FieldError.required("end_time"), FieldError.required("lifetime_budget"), ], ), CampaignValidationSummary( id=CampaignSummaryId.CREATIVE, errors=[ FieldError.required("primary_text"), FieldError.required("images"), ], ), CampaignValidationSummary( id=CampaignSummaryId.AUDIENCE, errors=[ FieldError.required("targeting_audience"), ], ), ] async def test_get_validation_summary_non_draft( campaign_validator: CampaignValidator, create_model: CreateModel, user: User, ) -> None: campaign = await create_model(Campaign, status=CampaignStatus.SUBMITTED) summary = await campaign_validator.get_summary(campaign, user=user) assert summary == []