import datetime import decimal import pytest from audience_common.formatters import format_datetime from starlette.testclient import TestClient from campaigns.assets.dtos import AssetUploadCredentials from campaigns.connectors.facebook.enums import ( AdSetFacebookPosition, AudienceEventSourceType, EngagementAudienceEventValue, ) from campaigns.connectors.facebook.models import ( AudienceSummary, TargetingGeolocation, TargetingInterest, ) from campaigns.core.exceptions import FieldError from campaigns.meta.dtos import ( AdFormat, CampaignPost, CampaignValidationSummary, EngagementAudienceEvent, ) from campaigns.meta.enums import ( AdFormatGroup, AssetPositionGroup, CampaignCallToAction, CampaignContentType, CampaignLinkType, CampaignPlatform, CampaignPlatformSelector, CampaignSummaryId, ) from campaigns.meta.handlers import ( CreateCampaignAdImageHandler, CreateCampaignAdVideoHandler, CreateCampaignEngagementAudienceHandler, CreateCampaignHandler, CreateCampaignImageHandler, CreateCampaignVideoHandler, CreateOrUpdateCampaignTargetingAudienceHandler, DeleteCampaignImageHandler, DeleteCampaignVideoHandler, GetCampaignAdFormatsHandler, GetCampaignAdPreviewHandler, GetCampaignAudienceSummaryHandler, GetCampaignImageHandler, GetCampaignImagesHandler, GetCampaignOverviewHandler, GetCampaignOverviewResponse, GetCampaignPostHandler, GetCampaignPostsHandler, GetCampaignVideoHandler, GetCampaignVideosHandler, GetEngagementAudienceEventsHandler, RefreshCampaignAdImageHandler, RefreshCampaignAdVideoHandler, UpdateCampaignCreativeHandler, UpdateCampaignHandler, UpdateCampaignScheduleAndBudgetHandler, ) from campaigns.meta.handlers.create_campaign_engagement_audience import ( CreateCampaignEngagementAudienceResponse, ) from campaigns.meta.handlers.get_campaign_ad_preview import GetCampaignAdPreviewResponse from campaigns.meta.handlers.get_campaign_audiences import ( GetCampaignAudiencesHandler, GetCampaignAudiencesResponse, ) from campaigns.meta.models import ( Campaign, CampaignImage, CampaignVideo, TargetingAudience, ) from campaigns.utils.formatters import float_or_none, format_datetime_or_none from tests.unit.faker import FakerTyped from tests.unit.types import BuildModel, MockDependency pytestmark = pytest.mark.usefixtures("mock_auth") def test_create_campaign( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) handler_mock = mock_dependency(CreateCampaignHandler) handler_mock.handle.return_value = campaign response = test_client.post( "/meta/campaigns", json={ "name": campaign.name, "vendorId": campaign.vendor_id, "subaccountId": campaign.subaccount_id, "globalParticipantId": campaign.global_participant_id, "productId": campaign.product_id, "facebookPageId": campaign.facebook_page_id, "instagramAccountId": campaign.instagram_account_external_id, "objective": campaign.objective, }, ) assert response.status_code == 201 assert response.json() == { "id": campaign.id, "name": campaign.name, "vendorId": campaign.vendor_id, "subaccountId": campaign.subaccount_id, "globalParticipantId": campaign.global_participant_id, "productId": campaign.product_id, "productUpc": campaign.product_upc, "status": campaign.status, "facebookPageId": campaign.facebook_page_id, "instagramAccountId": campaign.instagram_account_external_id, "postExternalId": campaign.post_external_id, "startTime": format_datetime_or_none(campaign.start_time), "endTime": format_datetime_or_none(campaign.end_time), "lifetimeBudget": float_or_none(campaign.lifetime_budget), "objective": campaign.objective, "platforms": campaign.platforms, "contentType": campaign.content_type, "primaryText": campaign.primary_text, "headline": campaign.headline, "description": campaign.description, "linkType": campaign.link_type, "websiteUrl": campaign.website_url, "landingPagePath": campaign.landing_page_path, "landingPageProductId": campaign.landing_page_product_id, "callToAction": campaign.call_to_action, "createdAt": format_datetime(campaign.created_at), "createdBy": campaign.created_by, "updatedAt": format_datetime(campaign.updated_at), "updatedBy": campaign.updated_by, "images": [], "videos": [], } def test_get_campaign_overview( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) handler_mock = mock_dependency(GetCampaignOverviewHandler) handler_mock.handle.return_value = GetCampaignOverviewResponse( campaign=campaign, summary=[ CampaignValidationSummary( id=CampaignSummaryId.SCHEDULE_AND_BUDGET, errors=[FieldError.required("lifetime_budget")], ) ], ) response = test_client.get(f"/meta/campaigns/{campaign.id}") assert response.status_code == 200 assert response.json() == { "campaign": { "id": campaign.id, "name": campaign.name, "vendorId": campaign.vendor_id, "subaccountId": campaign.subaccount_id, "globalParticipantId": campaign.global_participant_id, "productId": campaign.product_id, "productUpc": campaign.product_upc, "status": campaign.status, "facebookPageId": campaign.facebook_page_id, "instagramAccountId": campaign.instagram_account_external_id, "postExternalId": campaign.post_external_id, "startTime": format_datetime_or_none(campaign.start_time), "endTime": format_datetime_or_none(campaign.end_time), "lifetimeBudget": float_or_none(campaign.lifetime_budget), "objective": campaign.objective, "platforms": campaign.platforms, "contentType": campaign.content_type, "primaryText": campaign.primary_text, "headline": campaign.headline, "description": campaign.description, "linkType": campaign.link_type, "websiteUrl": campaign.website_url, "landingPagePath": campaign.landing_page_path, "landingPageProductId": campaign.landing_page_product_id, "callToAction": campaign.call_to_action, "createdAt": format_datetime(campaign.created_at), "createdBy": campaign.created_by, "updatedAt": format_datetime(campaign.updated_at), "updatedBy": campaign.updated_by, "images": [], "videos": [], }, "summary": [ { "id": CampaignSummaryId.SCHEDULE_AND_BUDGET, "errors": [ { "field": "lifetime_budget", "code": "value_error.missing", "message": "Field required", } ], } ], } def test_update_campaign( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) handler_mock = mock_dependency(UpdateCampaignHandler) handler_mock.handle.return_value = campaign response = test_client.put( f"/meta/campaigns/{campaign.id}", json={ "name": campaign.name, "vendorId": campaign.vendor_id, "subaccountId": campaign.subaccount_id, "globalParticipantId": campaign.global_participant_id, "productId": campaign.product_id, "facebookPageId": campaign.facebook_page_id, "instagramAccountId": campaign.instagram_account_external_id, "objective": campaign.objective, }, ) assert response.status_code == 200 assert response.json() == { "id": campaign.id, "name": campaign.name, "vendorId": campaign.vendor_id, "subaccountId": campaign.subaccount_id, "globalParticipantId": campaign.global_participant_id, "productId": campaign.product_id, "productUpc": campaign.product_upc, "status": campaign.status, "facebookPageId": campaign.facebook_page_id, "instagramAccountId": campaign.instagram_account_external_id, "postExternalId": campaign.post_external_id, "startTime": format_datetime_or_none(campaign.start_time), "endTime": format_datetime_or_none(campaign.end_time), "lifetimeBudget": float_or_none(campaign.lifetime_budget), "objective": campaign.objective, "platforms": campaign.platforms, "contentType": campaign.content_type, "primaryText": campaign.primary_text, "headline": campaign.headline, "description": campaign.description, "linkType": campaign.link_type, "websiteUrl": campaign.website_url, "landingPagePath": campaign.landing_page_path, "landingPageProductId": campaign.landing_page_product_id, "callToAction": campaign.call_to_action, "createdAt": format_datetime(campaign.created_at), "createdBy": campaign.created_by, "updatedAt": format_datetime(campaign.updated_at), "updatedBy": campaign.updated_by, "images": [], "videos": [], } def test_update_campaign_schedule_and_budget( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: start_time = fake.date_time_utc() end_time = fake.date_time_utc() + datetime.timedelta(days=10) lifetime_budget = decimal.Decimal("1000.00") campaign = build_model( Campaign, start_time=start_time, end_time=end_time, lifetime_budget=lifetime_budget, ) handler_mock = mock_dependency(UpdateCampaignScheduleAndBudgetHandler) handler_mock.handle.return_value = campaign response = test_client.put( f"/meta/campaigns/{campaign.id}/schedule-and-budget", json={ "startTime": start_time.isoformat(), "endTime": end_time.isoformat(), "lifetimeBudget": str(lifetime_budget), }, ) assert response.status_code == 200 assert response.json() == { "id": campaign.id, "name": campaign.name, "vendorId": campaign.vendor_id, "subaccountId": campaign.subaccount_id, "globalParticipantId": campaign.global_participant_id, "productId": campaign.product_id, "productUpc": campaign.product_upc, "status": campaign.status, "facebookPageId": campaign.facebook_page_id, "instagramAccountId": campaign.instagram_account_external_id, "postExternalId": campaign.post_external_id, "startTime": format_datetime_or_none(campaign.start_time), "endTime": format_datetime_or_none(campaign.end_time), "lifetimeBudget": float_or_none(campaign.lifetime_budget), "objective": campaign.objective, "platforms": campaign.platforms, "contentType": campaign.content_type, "primaryText": campaign.primary_text, "headline": campaign.headline, "description": campaign.description, "linkType": campaign.link_type, "websiteUrl": campaign.website_url, "landingPagePath": campaign.landing_page_path, "landingPageProductId": campaign.landing_page_product_id, "callToAction": campaign.call_to_action, "createdAt": format_datetime(campaign.created_at), "createdBy": campaign.created_by, "updatedAt": format_datetime(campaign.updated_at), "updatedBy": campaign.updated_by, "images": [], "videos": [], } def test_update_campaign_creative( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: platform = CampaignPlatformSelector.BOTH primary_text = fake.pystr() link_type = CampaignLinkType.WEBSITE website_url = fake.uri() call_to_action = CampaignCallToAction.LEARN_MORE campaign = build_model( Campaign, platforms=platform.to_list(), primary_text=primary_text, link_type=link_type, website_url=website_url, call_to_action=call_to_action, ) handler_mock = mock_dependency(UpdateCampaignCreativeHandler) handler_mock.handle.return_value = campaign response = test_client.put( f"/meta/campaigns/{campaign.id}/creative", json={ "contentType": campaign.content_type, "platform": campaign.platform, "primaryText": campaign.primary_text, "linkType": campaign.link_type, "websiteUrl": campaign.website_url, "callToAction": campaign.call_to_action, }, ) assert response.status_code == 200 assert response.json() == { "id": campaign.id, "name": campaign.name, "vendorId": campaign.vendor_id, "subaccountId": campaign.subaccount_id, "globalParticipantId": campaign.global_participant_id, "productId": campaign.product_id, "productUpc": campaign.product_upc, "status": campaign.status, "facebookPageId": campaign.facebook_page_id, "instagramAccountId": campaign.instagram_account_external_id, "postExternalId": campaign.post_external_id, "startTime": format_datetime_or_none(campaign.start_time), "endTime": format_datetime_or_none(campaign.end_time), "lifetimeBudget": float_or_none(campaign.lifetime_budget), "objective": campaign.objective, "platforms": campaign.platforms, "contentType": campaign.content_type, "primaryText": campaign.primary_text, "headline": campaign.headline, "description": campaign.description, "linkType": campaign.link_type, "websiteUrl": campaign.website_url, "landingPagePath": campaign.landing_page_path, "landingPageProductId": campaign.landing_page_product_id, "callToAction": campaign.call_to_action, "createdAt": format_datetime(campaign.created_at), "createdBy": campaign.created_by, "updatedAt": format_datetime(campaign.updated_at), "updatedBy": campaign.updated_by, "images": [], "videos": [], } def test_get_campaign_audiences( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: location = TargetingGeolocation(key="EE", name="Estonia", type="country") interest = TargetingInterest(id="100", name="Music", path=[]) targeting_audience = build_model( TargetingAudience, locations=[location.dict()], interests=[interest.dict()] ) campaign = build_model(Campaign, audiences=[targeting_audience]) handler_mock = mock_dependency(GetCampaignAudiencesHandler) handler_mock.handle.return_value = GetCampaignAudiencesResponse( targeting_audience=targeting_audience ) response = test_client.get(f"/meta/campaigns/{campaign.id}/audiences") assert response.status_code == 200 assert response.json() == { "targetingAudience": { "id": targeting_audience.id, "locations": [ { "key": location.key, "name": location.name, "type": location.type, "countryName": location.country_name, "countryCode": location.country_code, "region": location.region, } ], "ageMin": targeting_audience.age_min, "ageMax": targeting_audience.age_max, "gender": targeting_audience.gender, "interests": [ { "id": interest.id, "name": interest.name, "path": interest.path, "topic": interest.topic, "disambiguationCategory": interest.disambiguation_category, } ], "createdAt": format_datetime(targeting_audience.created_at), "updatedAt": format_datetime(targeting_audience.updated_at), } } def test_create_or_update_campaign_targeting_audience( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: location = TargetingGeolocation(key="EE", name="Estonia", type="country") audience = build_model(TargetingAudience, locations=[location.dict()]) campaign = build_model(Campaign, audiences=[audience]) handler_mock = mock_dependency(CreateOrUpdateCampaignTargetingAudienceHandler) handler_mock.handle.return_value = audience response = test_client.post( f"/meta/campaigns/{campaign.id}/targeting-audience", json={ "locations": [ { "key": location.key, "name": location.name, "type": location.type, } ], "ageMin": audience.age_min, "ageMax": audience.age_max, "gender": audience.gender, "interests": [], }, ) assert response.status_code == 200 assert response.json() == { "id": audience.id, "locations": [ { "key": location.key, "name": location.name, "type": location.type, "countryName": location.country_name, "countryCode": location.country_code, "region": location.region, } ], "ageMin": audience.age_min, "ageMax": audience.age_max, "gender": audience.gender, "interests": [], "createdAt": format_datetime(audience.created_at), "updatedAt": format_datetime(audience.updated_at), } def test_get_campaign_images( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: image = build_model(CampaignImage) campaign = image.campaign handler_mock = mock_dependency(GetCampaignImagesHandler) handler_mock.handle.return_value = [image] response = test_client.get(f"/meta/campaigns/{campaign.id}/images") assert response.status_code == 200 assert response.json() == [ { "id": image.id, "assetId": image.asset_id, "positionGroup": image.position_group, "filename": image.filename, "url": image.url, "thumbnailUrl": image.thumbnail_url, "size": image.size, "status": image.status, "errors": image.errors, "createdAt": format_datetime(image.created_at), "updatedAt": format_datetime(image.updated_at), "uploadedAt": format_datetime_or_none(image.uploaded_at), } ] def test_create_campaign_image( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: image = build_model(CampaignImage) campaign = image.campaign filename = "asset.jpg" asset_id = fake.uuid4_string() credentials = AssetUploadCredentials( asset_id=image.asset_id, upload_bucket="qa-audience-campaign-assets", upload_key=f"raw/{asset_id}/{filename}", upload_region="us-east-1", access_key_id="ABC123", secret_access_key="def456", session_token="ghi789", expiration=datetime.datetime.now() + datetime.timedelta(minutes=30), upload_metadata={ "metaCampaignId": image.campaign_id, "metaCampaignImageId": image.id, "metaCampaignImagePositionGroup": image.position_group, }, ) handler_mock = mock_dependency(CreateCampaignImageHandler) handler_mock.handle.return_value = image, credentials response = test_client.post( f"/meta/campaigns/{campaign.id}/images", json={ "filename": filename, "positionGroup": image.position_group.value, }, ) assert response.status_code == 201 assert response.json() == { "imageId": image.id, "credentials": { "assetId": credentials.asset_id, "uploadBucket": credentials.upload_bucket, "uploadKey": credentials.upload_key, "uploadRegion": credentials.upload_region, "accessKeyId": credentials.access_key_id, "secretAccessKey": credentials.secret_access_key, "sessionToken": credentials.session_token, "expiration": credentials.expiration.isoformat(), "uploadMetadata": { "metaCampaignId": image.campaign_id, "metaCampaignImageId": image.id, "metaCampaignImagePositionGroup": image.position_group, }, }, } def test_get_campaign_image( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: image = build_model(CampaignImage) handler_mock = mock_dependency(GetCampaignImageHandler) handler_mock.handle.return_value = image response = test_client.get(f"/meta/campaigns/{image.campaign_id}/images/{image.id}") assert response.status_code == 200 assert response.json() == { "id": image.id, "assetId": image.asset_id, "positionGroup": image.position_group, "filename": image.filename, "url": image.url, "thumbnailUrl": image.thumbnail_url, "size": image.size, "status": image.status, "errors": image.errors, "createdAt": format_datetime(image.created_at), "updatedAt": format_datetime(image.updated_at), "uploadedAt": format_datetime_or_none(image.uploaded_at), } def test_delete_campaign_image( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: image = build_model(CampaignImage) handler_mock = mock_dependency(DeleteCampaignImageHandler) handler_mock.handle.return_value = None response = test_client.delete( f"/meta/campaigns/{image.campaign_id}/images/{image.id}" ) assert response.status_code == 204 def test_get_campaign_videos( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: video = build_model(CampaignVideo) campaign = video.campaign handler_mock = mock_dependency(GetCampaignVideosHandler) handler_mock.handle.return_value = [video] response = test_client.get(f"/meta/campaigns/{campaign.id}/videos") assert response.status_code == 200 assert response.json() == [ { "id": video.id, "assetId": video.asset_id, "positionGroup": video.position_group, "filename": video.filename, "url": video.url, "thumbnailUrl": video.thumbnail_url, "size": video.size, "status": video.status, "errors": video.errors, "createdAt": format_datetime(video.created_at), "updatedAt": format_datetime(video.updated_at), "uploadedAt": format_datetime_or_none(video.uploaded_at), } ] def test_create_campaign_video( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: video = build_model(CampaignVideo) campaign = video.campaign filename = "asset.mov" asset_id = fake.uuid4_string() credentials = AssetUploadCredentials( asset_id=video.asset_id, upload_bucket="qa-audience-campaign-assets", upload_key=f"raw/{asset_id}/{filename}", upload_region="us-east-1", access_key_id="ABC123", secret_access_key="def456", session_token="ghi789", expiration=datetime.datetime.now() + datetime.timedelta(minutes=30), upload_metadata={ "meta_campaign_id": video.campaign_id, "meta_campaign_video_id": video.id, "meta_campaign_video_position_group": video.position_group, }, ) handler_mock = mock_dependency(CreateCampaignVideoHandler) handler_mock.handle.return_value = video, credentials response = test_client.post( f"/meta/campaigns/{campaign.id}/videos", json={ "filename": filename, "positionGroup": video.position_group.value, }, ) assert response.status_code == 201 assert response.json() == { "videoId": video.id, "credentials": { "assetId": credentials.asset_id, "uploadBucket": credentials.upload_bucket, "uploadKey": credentials.upload_key, "uploadRegion": credentials.upload_region, "accessKeyId": credentials.access_key_id, "secretAccessKey": credentials.secret_access_key, "sessionToken": credentials.session_token, "expiration": credentials.expiration.isoformat(), "uploadMetadata": { "meta_campaign_id": video.campaign_id, "meta_campaign_video_id": video.id, "meta_campaign_video_position_group": video.position_group, }, }, } def test_create_campaign_ad_image( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: image = build_model(CampaignImage) handler_mock = mock_dependency(CreateCampaignAdImageHandler) handler_mock.handle.return_value = image response = test_client.post( f"/meta/campaigns/images/{image.id}/adimage", json={"url": fake.image_url()}, ) assert response.status_code == 201 assert response.json() == { "id": image.id, "assetId": image.asset_id, "positionGroup": image.position_group, "filename": image.filename, "url": image.url, "thumbnailUrl": image.thumbnail_url, "size": image.size, "status": image.status, "errors": image.errors, "createdAt": format_datetime(image.created_at), "updatedAt": format_datetime(image.updated_at), "uploadedAt": format_datetime_or_none(image.uploaded_at), } def test_refresh_campaign_ad_image( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: image = build_model(CampaignImage) handler_mock = mock_dependency(RefreshCampaignAdImageHandler) handler_mock.handle.return_value = image response = test_client.put(f"/meta/campaigns/images/{image.id}/adimage") assert response.status_code == 200 assert response.json() == { "id": image.id, "assetId": image.asset_id, "positionGroup": image.position_group, "filename": image.filename, "url": image.url, "thumbnailUrl": image.thumbnail_url, "size": image.size, "status": image.status, "errors": image.errors, "createdAt": format_datetime(image.created_at), "updatedAt": format_datetime(image.updated_at), "uploadedAt": format_datetime_or_none(image.uploaded_at), } def test_get_campaign_video( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: video = build_model(CampaignVideo) handler_mock = mock_dependency(GetCampaignVideoHandler) handler_mock.handle.return_value = video response = test_client.get(f"/meta/campaigns/{video.campaign_id}/videos/{video.id}") assert response.status_code == 200 assert response.json() == { "id": video.id, "assetId": video.asset_id, "positionGroup": video.position_group, "filename": video.filename, "url": video.url, "thumbnailUrl": video.thumbnail_url, "size": video.size, "status": video.status, "errors": video.errors, "createdAt": format_datetime(video.created_at), "updatedAt": format_datetime(video.updated_at), "uploadedAt": format_datetime_or_none(video.uploaded_at), } def test_delete_campaign_video( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: video = build_model(CampaignVideo) handler_mock = mock_dependency(DeleteCampaignVideoHandler) handler_mock.handle.return_value = None response = test_client.delete( f"/meta/campaigns/{video.campaign_id}/videos/{video.id}" ) assert response.status_code == 204 def test_create_campaign_ad_video( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: video = build_model(CampaignVideo) handler_mock = mock_dependency(CreateCampaignAdVideoHandler) handler_mock.handle.return_value = video response = test_client.post( f"/meta/campaigns/videos/{video.id}/advideo", json={"url": fake.video_url()}, ) assert response.status_code == 201 assert response.json() == { "id": video.id, "assetId": video.asset_id, "positionGroup": video.position_group, "filename": video.filename, "url": video.url, "thumbnailUrl": video.thumbnail_url, "size": video.size, "status": video.status, "errors": video.errors, "createdAt": format_datetime(video.created_at), "updatedAt": format_datetime(video.updated_at), "uploadedAt": format_datetime_or_none(video.uploaded_at), } def test_refresh_campaign_ad_video( test_client: TestClient, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: video = build_model(CampaignVideo) handler_mock = mock_dependency(RefreshCampaignAdVideoHandler) handler_mock.handle.return_value = video response = test_client.put(f"/meta/campaigns/videos/{video.id}/advideo") assert response.status_code == 200 assert response.json() == { "id": video.id, "assetId": video.asset_id, "positionGroup": video.position_group, "filename": video.filename, "url": video.url, "thumbnailUrl": video.thumbnail_url, "size": video.size, "status": video.status, "errors": video.errors, "createdAt": format_datetime(video.created_at), "updatedAt": format_datetime(video.updated_at), "uploadedAt": format_datetime_or_none(video.uploaded_at), } def test_get_campaign_ad_formats( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) ad_format = AdFormat( id=fake.pystr(), name=fake.pystr(), position=AdSetFacebookPosition.FEED, group=AdFormatGroup.FEED, position_group=AssetPositionGroup.RATIO_1_1, platform=CampaignPlatform.FACEBOOK, height=100, width=100, ) handler_mock = mock_dependency(GetCampaignAdFormatsHandler) handler_mock.handle.return_value = [ad_format] response = test_client.get( f"/meta/campaigns/{campaign.id}/adformats", params={ "contentType": CampaignContentType.IMAGE, "platform": CampaignPlatform.FACEBOOK, }, ) assert response.status_code == 200 assert response.json() == [ { "id": ad_format.id, "name": ad_format.name, "group": ad_format.group.value, "platform": ad_format.platform.value, } ] def test_get_campaign_ad_preview( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) ad_preview_response = GetCampaignAdPreviewResponse( body=fake.pystr(), src=fake.pystr(), height=fake.integer(), width=fake.integer() ) handler_mock = mock_dependency(GetCampaignAdPreviewHandler) handler_mock.handle.return_value = ad_preview_response response = test_client.get( f"/meta/campaigns/{campaign.id}/adpreview", params={ "contentType": CampaignContentType.IMAGE, "platform": "facebook", "adFormatId": fake.pystr(), "primaryText": fake.pystr(), }, ) assert response.status_code == 200 assert response.json() == { "body": ad_preview_response.body, "src": ad_preview_response.src, "height": ad_preview_response.height, "width": ad_preview_response.width, } def test_get_campaign_audience_summary( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) audience_summary = AudienceSummary( users_lower_bound=fake.integer(6), users_upper_bound=fake.integer(7), estimate_ready=True, ) handler_mock = mock_dependency(GetCampaignAudienceSummaryHandler) handler_mock.handle.return_value = audience_summary response = test_client.post( f"/meta/campaigns/{campaign.id}/audience-summary", json={ "ageMin": 19, "ageMax": 45, }, ) assert response.status_code == 200 assert response.json() == { "usersLowerBound": audience_summary.users_lower_bound, "usersUpperBound": audience_summary.users_upper_bound, "estimateReady": True, } def test_get_campaign_posts( test_client: TestClient, fake: FakerTyped, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) post = CampaignPost( id=fake.numeric_string(), platform=CampaignPlatform.FACEBOOK, type="Post", title="Test", permalink_url=fake.uri(), picture_url=fake.image_url(), thumbnail_url=fake.image_url(), created_at=fake.date_time_utc(), ) handler_mock = mock_dependency(GetCampaignPostsHandler) handler_mock.handle.return_value = [post] response = test_client.get(f"/meta/campaigns/{campaign.id}/posts") assert response.status_code == 200 assert response.json() == [ { "id": post.id, "platform": post.platform, "type": post.type, "title": post.title, "permalinkUrl": post.permalink_url, "pictureUrl": post.picture_url, "thumbnailUrl": post.thumbnail_url, "createdAt": format_datetime(post.created_at), } ] def test_get_campaign_post( test_client: TestClient, fake: FakerTyped, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) post = CampaignPost( id=fake.numeric_string(), platform=CampaignPlatform.FACEBOOK, type="Post", title="Test", permalink_url=fake.uri(), picture_url=fake.image_url(), thumbnail_url=fake.image_url(), created_at=fake.date_time_utc(), ) handler_mock = mock_dependency(GetCampaignPostHandler) handler_mock.handle.return_value = post response = test_client.get(f"/meta/campaigns/{campaign.id}/post") assert response.status_code == 200 assert response.json() == { "post": { "id": post.id, "platform": post.platform, "type": post.type, "title": post.title, "permalinkUrl": post.permalink_url, "pictureUrl": post.picture_url, "thumbnailUrl": post.thumbnail_url, "createdAt": format_datetime(post.created_at), } } def test_get_campaign_post_does_not_exist( test_client: TestClient, fake: FakerTyped, build_model: BuildModel, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) handler_mock = mock_dependency(GetCampaignPostHandler) handler_mock.handle.return_value = None response = test_client.get(f"/meta/campaigns/{campaign.id}/post") assert response.status_code == 200 assert response.json() == {"post": None} def test_get_campaign_engagement_audience_events( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) event = EngagementAudienceEvent( id=fake.pystr(), name=fake.pystr(), platform=fake.choice([CampaignPlatform.FACEBOOK, CampaignPlatform.INSTAGRAM]), source_type=AudienceEventSourceType.PAGE, value=EngagementAudienceEventValue.FB_PAGE_ENGAGED, ) handler_mock = mock_dependency(GetEngagementAudienceEventsHandler) handler_mock.handle.return_value = [event] response = test_client.get( f"/meta/campaigns/{campaign.id}/engagement-audience-events", ) assert response.status_code == 200 assert response.json() == [ {"id": event.id, "name": event.name, "platform": event.platform} ] def test_create_campaign_engagement_audience( test_client: TestClient, build_model: BuildModel, fake: FakerTyped, mock_dependency: MockDependency, ) -> None: campaign = build_model(Campaign) handler_response = CreateCampaignEngagementAudienceResponse( id=fake.pystr(), lower_bound=fake.integer(), upper_bound=fake.integer(), event_ids=[fake.pystr()], ) handler_mock = mock_dependency(CreateCampaignEngagementAudienceHandler) handler_mock.handle.return_value = handler_response response = test_client.post( f"/meta/campaigns/{campaign.id}/engagement-audience", json={"eventIds": ["event_id"]}, ) assert response.status_code == 200 assert response.json() == { "id": handler_response.id, "lowerBound": handler_response.lower_bound, "upperBound": handler_response.upper_bound, "eventIds": handler_response.event_ids, }