import base64 import copy import datetime from unittest import mock import faker import pytest from anydi import Container from dirty_equals import IsStr from fansifter_common.adapters.ows_account import Vendor from fansifter_common.auth.account import Account from fansifter_common.core.enums import Brand from freezegun import freeze_time from starlette.testclient import TestClient from email_campaigns.automated.handlers import ( SendAutomatedTestEmailHandler, ) from email_campaigns.automated.models import ( AutomatedEmail, AutomatedEmailTrigger, SourceCampaign, ) from email_campaigns.emails.models import ( EmailAnalyticsBenchmarkByAccountArtistV2Dbt, EmailAnalyticsBenchmarkByLinktV3Dbt, EmailAnalyticsV3Dbt, EmailDomain, ShopifyEmailOrdersDbt, ShopifyEmailProductsDbt, ShopifyProductTypesDbt, ) from tests.unit.equals import IsISODatetime from tests.unit.types import BuildModel, CreateModel def test_send_automated_test_email( client: TestClient, container: Container, faker: faker.Faker, ) -> None: handler_mock = mock.MagicMock(spec=SendAutomatedTestEmailHandler) handler_mock.handle.return_value = None automated_email_id = faker.uuid4() test_emails = [faker.email(), faker.email()] test_content = base64.b64encode(b"test content").decode() with container.override(SendAutomatedTestEmailHandler, handler_mock): response = client.post( f"/automated/{automated_email_id}/send-test-email", json={ "emails": test_emails, "content": test_content, }, ) assert response.status_code == 204 @pytest.mark.db def test_apply_automated_email_changes_returns_status( client: TestClient, create_model: CreateModel, account: Account, faker: faker.Faker, automated_stripo_client_v2_mock: mock.MagicMock, ) -> None: automated_email = create_model( AutomatedEmail, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, has_unapplied_changes=True, ) automated_stripo_client_v2_mock.get_html_css.return_value = mock.Mock( html=faker.pystr(), css=faker.pystr() ) response = client.post(f"/automated/{automated_email.id}/apply-changes") assert response.status_code == 200 assert response.json() == { "isAutomated": False, "hasUnappliedChanges": False, } @pytest.mark.db def test_apply_automated_email_changes_returns_status_when_nothing_to_apply( client: TestClient, create_model: CreateModel, account: Account, automated_stripo_client_v2_mock: mock.MagicMock, ) -> None: automated_email = create_model( AutomatedEmail, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, has_unapplied_changes=False, ) response = client.post(f"/automated/{automated_email.id}/apply-changes") assert response.status_code == 200 assert response.json() == { "isAutomated": False, "hasUnappliedChanges": False, } automated_stripo_client_v2_mock.get_html_css.assert_not_called() @pytest.mark.db def test_update_automated_email( client: TestClient, build_model: BuildModel, create_model: CreateModel, faker: faker.Faker, ows_account_client_mock: mock.MagicMock, account: Account, identity_id: str, ) -> None: vendor = build_model(Vendor, vendor_id=account.vendor_id, company_brand=Brand.SME) subject = faker.sentence() name = faker.word() sender_name = faker.first_name() email_username = faker.uuid4() global_participant_id = faker.uuid4() update_time = datetime.datetime.now(datetime.UTC) email_domain = create_model( EmailDomain, brand=vendor.brand, vendor_id=None, subaccount_id=None ) source_campaign = create_model(SourceCampaign) automated_email = create_model( AutomatedEmail, email_domain=email_domain, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, global_participant_id=global_participant_id, custom_list_id=None, source_campaign_connections=[ AutomatedEmailTrigger( source_campaign_id=source_campaign.id, subscription_only=False, ) ], ) automated_email = copy.deepcopy(automated_email) source_campaign = copy.deepcopy(source_campaign) ows_account_client_mock.get_vendor.return_value = vendor with freeze_time(update_time): response = client.put( f"/automated/{automated_email.id}", json={ "name": name, "vendorId": account.vendor_id, "subaccountId": account.subaccount_id, "fanDataListId": { "id": automated_email.global_participant_id, "type": "ARTIST", }, "senderName": sender_name, "type": automated_email.type, "emailDomainId": email_domain.id, "emailUsername": email_username, "subject": subject, "previewText": automated_email.preview_text, }, ) assert response.status_code == 200 response_data = response.json() assert response_data == { "id": automated_email.id, "name": name, "type": automated_email.type, "vendorId": automated_email.vendor_id, "subaccountId": automated_email.subaccount_id, "fanDataListId": { "id": automated_email.global_participant_id, "type": "ARTIST", }, "senderName": sender_name, "emailUsername": email_username, "emailDomain": { "id": email_domain.id, "domain": email_domain.domain, "brand": email_domain.brand, }, "subject": subject, "previewText": automated_email.preview_text, "isAutomated": True, "connectedTo": [ { "subscriptionOnly": False, "sourceCampaign": { "id": source_campaign.id, "campaignId": source_campaign.campaign_id, "source": source_campaign.source, "name": source_campaign.name, "url": source_campaign.url, "status": source_campaign.status, "fanDataListId": { "id": source_campaign.global_participant_id, "type": "ARTIST", }, "hasOtherConnections": None, }, } ], "lastSentAt": None, "analytics": None, "createdAt": IsISODatetime(automated_email.created_at), "createdBy": automated_email.created_by, "updatedAt": IsISODatetime(update_time), "hasUnappliedChanges": automated_email.has_unapplied_changes, "updatedBy": identity_id, } @pytest.mark.db def test_get_automated_email( client: TestClient, create_model: CreateModel, build_model: BuildModel, account: Account, faker: faker.Faker, ) -> None: vendor = build_model(Vendor, vendor_id=account.vendor_id, company_brand=Brand.SME) email_domain = create_model( EmailDomain, brand=vendor.brand, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) source_campaign1 = create_model(SourceCampaign) source_campaign2 = create_model(SourceCampaign) automated_email = create_model( AutomatedEmail, email_domain=email_domain, sender_name=faker.name(), subject=faker.sentence(), preview_text=faker.sentence(), email_username=faker.user_name(), subaccount_id=account.subaccount_id, vendor_id=account.vendor_id, custom_list_id=None, source_campaign_connections=[ AutomatedEmailTrigger( source_campaign_id=source_campaign1.id, subscription_only=False, ), AutomatedEmailTrigger( source_campaign_id=source_campaign2.id, subscription_only=False, ), ], ) response = client.get(f"/automated/{automated_email.id}") assert response.status_code == 200 assert response.json() == { "id": automated_email.id, "name": automated_email.name, "type": automated_email.type, "vendorId": automated_email.vendor_id, "subaccountId": automated_email.subaccount_id, "fanDataListId": { "id": automated_email.global_participant_id, "type": "ARTIST", } if automated_email.global_participant_id else None, "senderName": automated_email.sender_name, "emailUsername": automated_email.email_username, "emailDomain": { "id": email_domain.id, "domain": email_domain.domain, "brand": email_domain.brand, }, "subject": automated_email.subject, "previewText": automated_email.preview_text, "isAutomated": True, "connectedTo": [ { "subscriptionOnly": False, "sourceCampaign": { "id": source_campaign1.id, "campaignId": source_campaign1.campaign_id, "source": source_campaign1.source, "name": source_campaign1.name, "url": source_campaign1.url, "status": source_campaign1.status, "fanDataListId": { "id": source_campaign1.global_participant_id, "type": "ARTIST", }, "hasOtherConnections": None, }, }, { "subscriptionOnly": False, "sourceCampaign": { "id": source_campaign2.id, "campaignId": source_campaign2.campaign_id, "source": source_campaign2.source, "name": source_campaign2.name, "url": source_campaign2.url, "status": source_campaign2.status, "fanDataListId": { "id": source_campaign2.global_participant_id, "type": "ARTIST", }, "hasOtherConnections": None, }, }, ], "lastSentAt": None, "analytics": None, "createdAt": IsISODatetime(automated_email.created_at), "createdBy": automated_email.created_by, "updatedAt": IsISODatetime(automated_email.updated_at), "hasUnappliedChanges": automated_email.has_unapplied_changes, "updatedBy": automated_email.updated_by, } @pytest.mark.db def test_get_automated_email_status( client: TestClient, create_model: CreateModel, account: Account, ) -> None: source_campaign = create_model(SourceCampaign) automated_email = create_model( AutomatedEmail, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, has_unapplied_changes=True, source_campaign_connections=[ AutomatedEmailTrigger( source_campaign_id=source_campaign.id, subscription_only=False, ), ], ) response = client.get(f"/automated/{automated_email.id}/status") assert response.status_code == 200 assert response.json() == { "isAutomated": True, "hasUnappliedChanges": True, } @pytest.mark.db def test_get_automated_email_status_not_found( client: TestClient, ) -> None: response = client.get("/automated/does-not-exist/status") assert response.status_code == 404 @pytest.mark.db def test_get_automated_emails( client: TestClient, create_model: CreateModel, account: Account, build_model: BuildModel, ) -> None: vendor = build_model(Vendor, vendor_id=account.vendor_id, company_brand=Brand.SME) email_domain = create_model( EmailDomain, brand=vendor.brand, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) source_campaign = create_model(SourceCampaign) automated_email = create_model( AutomatedEmail, email_domain=email_domain, custom_list_id=None, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, deleted_at=None, deleted_by=None, source_campaign_connections=[ AutomatedEmailTrigger(source_campaign_id=source_campaign.id) ], ) trigger = automated_email.source_campaign_connections[0] response = client.post("/automated/list", json={}) assert response.status_code == 200 assert response.json() == { "total": 1, "items": [ { "id": automated_email.id, "name": automated_email.name, "type": automated_email.type, "vendorId": automated_email.vendor_id, "subaccountId": automated_email.subaccount_id, "fanDataListId": { "id": automated_email.global_participant_id, "type": "ARTIST", }, "senderName": automated_email.sender_name, "emailUsername": automated_email.email_username, "emailDomain": { "id": email_domain.id, "domain": email_domain.domain, "brand": email_domain.brand, }, "subject": automated_email.subject, "previewText": automated_email.preview_text, "isAutomated": True, "connectedTo": [ { "subscriptionOnly": trigger.subscription_only, "sourceCampaign": { "id": source_campaign.id, "campaignId": source_campaign.campaign_id, "source": source_campaign.source, "name": source_campaign.name, "url": source_campaign.url, "status": source_campaign.status, "fanDataListId": { "id": source_campaign.global_participant_id, "type": "ARTIST", }, "hasOtherConnections": None, }, } ], "lastSentAt": None, "analytics": None, "createdAt": IsISODatetime(automated_email.created_at), "createdBy": automated_email.created_by, "updatedAt": IsISODatetime(automated_email.updated_at), "hasUnappliedChanges": automated_email.has_unapplied_changes, "updatedBy": automated_email.updated_by, } ], "limit": 50, "offset": 0, } @pytest.mark.db def test_delete_automated_email(client: TestClient, create_model: CreateModel) -> None: automated_email = create_model(AutomatedEmail) response = client.delete( f"/automated/{automated_email.id}", ) assert response.status_code == 204 @pytest.mark.db def test_duplicate_automated_email( client: TestClient, identity_id: str, create_model: CreateModel, ) -> None: email_domain = create_model(EmailDomain) automated_email = create_model( AutomatedEmail, email_domain=email_domain, custom_list_id=None, html_content='
', ) new_name = f"{automated_email.name} (Copy)" response = client.post( f"/automated/{automated_email.id}/duplicate", json={"name": new_name}, ) assert response.status_code == 201 assert response.json() == { "id": IsStr(), "name": new_name, "type": automated_email.type, "vendorId": automated_email.vendor_id, "subaccountId": automated_email.subaccount_id, "fanDataListId": { "id": automated_email.global_participant_id, "type": "ARTIST", }, "emailDomain": { "id": email_domain.id, "domain": email_domain.domain, "brand": email_domain.brand, }, "emailUsername": automated_email.email_username, "previewText": automated_email.preview_text, "senderName": automated_email.sender_name, "subject": automated_email.subject, "lastSentAt": None, "analytics": None, "isAutomated": automated_email.is_automated, "connectedTo": [], "createdAt": IsStr(), "createdBy": identity_id, "updatedAt": IsStr(), "hasUnappliedChanges": False, "updatedBy": identity_id, } @pytest.mark.db def test_attach_source_campaigns( client: TestClient, create_model: CreateModel, ) -> None: email_domain = create_model(EmailDomain) source_campaign_1 = create_model(SourceCampaign, vendor_id=None) source_campaign_2 = create_model(SourceCampaign, vendor_id=None) automated_email = create_model( AutomatedEmail, email_domain=email_domain, html_content='', source_campaign_connections=[ AutomatedEmailTrigger(source_campaign_id=source_campaign_1.id) ], ) trigger_1 = automated_email.source_campaign_connections[0] response = client.put( f"/automated/{automated_email.id}/source-campaigns/attach", json={ "connectTo": [ {"sourceCampaignId": source_campaign_1.id}, {"sourceCampaignId": source_campaign_2.id}, ] }, ) assert response.status_code == 200 assert response.json() == { "id": automated_email.id, "name": automated_email.name, "type": automated_email.type, "vendorId": automated_email.vendor_id, "subaccountId": automated_email.subaccount_id, "fanDataListId": { "id": automated_email.global_participant_id, "type": "ARTIST", }, "senderName": automated_email.sender_name, "emailUsername": automated_email.email_username, "emailDomain": { "id": email_domain.id, "domain": email_domain.domain, "brand": email_domain.brand, }, "subject": automated_email.subject, "previewText": automated_email.preview_text, "isAutomated": True, "connectedTo": [ { "subscriptionOnly": trigger_1.subscription_only, "sourceCampaign": { "id": source_campaign_1.id, "campaignId": source_campaign_1.campaign_id, "source": source_campaign_1.source, "name": source_campaign_1.name, "url": source_campaign_1.url, "status": source_campaign_1.status, "fanDataListId": { "id": source_campaign_1.global_participant_id, "type": "ARTIST", }, "hasOtherConnections": None, }, }, { "subscriptionOnly": False, "sourceCampaign": { "id": source_campaign_2.id, "campaignId": source_campaign_2.campaign_id, "source": source_campaign_2.source, "name": source_campaign_2.name, "url": source_campaign_2.url, "status": source_campaign_2.status, "fanDataListId": { "id": source_campaign_2.global_participant_id, "type": "ARTIST", }, "hasOtherConnections": None, }, }, ], "lastSentAt": automated_email.last_sent_at, "analytics": None, "createdAt": IsISODatetime(automated_email.created_at), "createdBy": automated_email.created_by, "updatedAt": IsISODatetime(automated_email.updated_at), "hasUnappliedChanges": automated_email.has_unapplied_changes, "updatedBy": automated_email.updated_by, } @pytest.mark.db def test_detach_source_campaigns( client: TestClient, create_model: CreateModel, ) -> None: email_domain = create_model(EmailDomain) source_campaign_1 = create_model(SourceCampaign) source_campaign_2 = create_model(SourceCampaign) source_campaign_3 = create_model(SourceCampaign) automated_email = create_model( AutomatedEmail, email_domain=email_domain, source_campaign_connections=[ AutomatedEmailTrigger(source_campaign_id=source_campaign_1.id), AutomatedEmailTrigger(source_campaign_id=source_campaign_2.id), AutomatedEmailTrigger(source_campaign_id=source_campaign_3.id), ], ) trigger_3 = automated_email.source_campaign_connections[2] response = client.post( f"/automated/{automated_email.id}/source-campaigns/detach", json={ "disconnectFrom": [source_campaign_1.id, source_campaign_2.id], }, ) assert response.status_code == 200 assert response.json() == { "id": automated_email.id, "name": automated_email.name, "type": automated_email.type, "vendorId": automated_email.vendor_id, "subaccountId": automated_email.subaccount_id, "fanDataListId": { "id": automated_email.global_participant_id, "type": "ARTIST", }, "senderName": automated_email.sender_name, "emailUsername": automated_email.email_username, "emailDomain": { "id": email_domain.id, "domain": email_domain.domain, "brand": email_domain.brand, }, "subject": automated_email.subject, "previewText": automated_email.preview_text, "isAutomated": True, "connectedTo": [ { "subscriptionOnly": trigger_3.subscription_only, "sourceCampaign": { "id": source_campaign_3.id, "campaignId": source_campaign_3.campaign_id, "source": source_campaign_3.source, "name": source_campaign_3.name, "url": source_campaign_3.url, "status": source_campaign_3.status, "fanDataListId": { "id": source_campaign_3.global_participant_id, "type": "ARTIST", }, "hasOtherConnections": None, }, }, ], "lastSentAt": automated_email.last_sent_at, "analytics": None, "createdAt": IsISODatetime(automated_email.created_at), "createdBy": automated_email.created_by, "updatedAt": IsISODatetime(automated_email.updated_at), "hasUnappliedChanges": automated_email.has_unapplied_changes, "updatedBy": automated_email.updated_by, } @pytest.mark.db def test_detach_source_campaigns_with_invalid_id( client: TestClient, create_model: CreateModel, faker: faker.Faker, ) -> None: automated_email = create_model(AutomatedEmail) invalid_id = faker.uuid4() response = client.post( f"/automated/{automated_email.id}/source-campaigns/detach", json={"disconnectFrom": [invalid_id]}, ) assert response.status_code == 400 assert response.json() == { "code": "invalid_source_campaign_id", "message": "Invalid Source Campaign ID", } @pytest.mark.db def test_get_automated_email_analytics( client: TestClient, create_model: CreateModel ) -> None: automated_email = create_model(AutomatedEmail, name="Test Automated Email") create_model( EmailAnalyticsV3Dbt, email_id=automated_email.id, email_type=automated_email.email_type, sends=10, drops=0, delivered=9, opens=6, clicks=5, unique_opens=6, unique_clicks=5, unique_bounces=0, unique_spam_complaints=1, unsubscribes=0, open_rate=0.6, delivered_rate=0.9, click_through_rate=0.3, click_to_open_rate=0.24, bounce_rate=0.0, spam_complaints_rate=0.1, unsubscribe_rate=0.0, unique_opens_dropoff=0.67, unique_clicks_dropoff=0.83, ) create_model( EmailAnalyticsBenchmarkByAccountArtistV2Dbt, global_participant_id=automated_email.global_participant_id, custom_list_id=automated_email.custom_list_id, vendor_id=automated_email.vendor_id, subaccount_id=automated_email.subaccount_id, email_type=automated_email.email_type, avg_open_rate=0.5, avg_click_through_rate=0.4, avg_bounce_rate=0.1, avg_delivered_rate=0.9, avg_unique_clicks=7, avg_click_to_open_rate=0.6, avg_spam_complaints_rate=0.55, avg_unsubscribe_rate=0.1, ) response = client.get( f"/automated/{automated_email.id}/analytics", ) assert response.status_code == 200 @pytest.mark.db def test_get_automated_email_link_analytics( client: TestClient, create_model: CreateModel ) -> None: automated_email = create_model(AutomatedEmail, name="Test Automated Email") create_model( EmailAnalyticsV3Dbt, email_id=automated_email.id, email_type=automated_email.email_type, sends=10, drops=0, delivered=9, opens=6, clicks=5, unique_opens=6, unique_clicks=5, unique_bounces=0, unique_spam_complaints=1, unsubscribes=0, open_rate=0.6, delivered_rate=0.9, click_through_rate=0.3, click_to_open_rate=0.24, bounce_rate=0.0, spam_complaints_rate=0.1, unsubscribe_rate=0.0, unique_opens_dropoff=0.67, unique_clicks_dropoff=0.83, ) create_model( EmailAnalyticsBenchmarkByAccountArtistV2Dbt, global_participant_id=automated_email.global_participant_id, custom_list_id=automated_email.custom_list_id, vendor_id=automated_email.vendor_id, subaccount_id=automated_email.subaccount_id, email_type=automated_email.email_type, avg_open_rate=0.5, avg_click_through_rate=0.4, avg_bounce_rate=0.1, avg_delivered_rate=0.9, avg_unique_clicks=7, avg_click_to_open_rate=0.6, avg_spam_complaints_rate=0.55, avg_unsubscribe_rate=0.1, ) create_model( EmailAnalyticsBenchmarkByLinktV3Dbt, email_id=automated_email.id, email_type=automated_email.email_type, url="http://www.awesome-artist.com", total_clicks=10, unique_clicks=10, percentage_of_total_clicks=1.0, ) response = client.get( f"/automated/{automated_email.id}/analytics/links", ) assert response.status_code == 200 @pytest.mark.db def test_get_automated_email_sales_analytics( client: TestClient, create_model: CreateModel ) -> None: automated_email = create_model(AutomatedEmail, name="Test Automated Email") create_model( ShopifyEmailOrdersDbt, email_id=automated_email.id, shop_id="shop1", currency="USD", country_code="US", shop_name="Shop One", total_orders=5, items_sold=10, total_sales_usd=120.0, total_discounts_usd=10.0, total_taxes_usd=8.0, total_delivery_usd=5.0, total_net_sales_usd=100.0, total_refunds_usd=10.0, first_time_buyer_rate=0.4, ) response = client.get(f"/automated/{automated_email.id}/analytics/sales") assert response.status_code == 200 assert response.json() == { "data": { "ordersCount": 5, "itemsSold": 10, "netSalesUsd": 100.0, "grossSalesUsd": 120.0, "discountsUsd": 10.0, "taxesUsd": 8.0, "shippingUsd": 5.0, "refundsUsd": 10.0, "averageUnitsPerOrder": 2.0, "averageOrderAmount": 20.0, "firstTimeBuyerRate": 0.4, } } @pytest.mark.db def test_get_automated_email_sales_products_analytics( client: TestClient, create_model: CreateModel ) -> None: automated_email = create_model(AutomatedEmail, name="Test Automated Email") create_model( ShopifyEmailProductsDbt, email_id=automated_email.id, product_title="Vinyl Record", product_type="Music", currency="USD", total_quantity_sold=5, total_net_sales_usd=200.0, ) create_model( ShopifyEmailProductsDbt, email_id=automated_email.id, product_title="T-Shirt", product_type="Merch", currency="USD", total_quantity_sold=10, total_net_sales_usd=100.0, ) create_model(ShopifyProductTypesDbt, product_type="Music", chart_eligible=True) create_model(ShopifyProductTypesDbt, product_type="Merch", chart_eligible=False) response = client.get(f"/automated/{automated_email.id}/analytics/sales/products") assert response.status_code == 200 assert response.json() == { "data": [ { "productTitle": "Vinyl Record", "productType": "Music", "itemsSold": 5, "netSalesUsd": 200.0, "chartEligible": True, }, { "productTitle": "T-Shirt", "productType": "Merch", "itemsSold": 10, "netSalesUsd": 100.0, "chartEligible": False, }, ] } @pytest.mark.db def test_get_automated_email_sales_locations_analytics( client: TestClient, create_model: CreateModel ) -> None: automated_email = create_model(AutomatedEmail, name="Test Automated Email") create_model( ShopifyEmailOrdersDbt, email_id=automated_email.id, shop_id="shop1", currency="USD", country_code="US", shop_name="Shop One", total_orders=5, items_sold=10, total_sales_usd=220.0, total_discounts_usd=10.0, total_taxes_usd=8.0, total_delivery_usd=5.0, total_net_sales_usd=200.0, ) create_model( ShopifyEmailOrdersDbt, email_id=automated_email.id, shop_id="shop2", currency="USD", country_code="CA", shop_name="Shop Two", total_orders=3, items_sold=6, total_sales_usd=115.0, total_discounts_usd=5.0, total_taxes_usd=4.0, total_delivery_usd=3.0, total_net_sales_usd=100.0, ) response = client.get(f"/automated/{automated_email.id}/analytics/sales/locations") assert response.status_code == 200 assert response.json() == { "data": [ { "countryCode": "US", "itemsSold": 10, "netSalesUsd": 200.0, "salesRate": pytest.approx(200 / 300), }, { "countryCode": "CA", "itemsSold": 6, "netSalesUsd": 100.0, "salesRate": pytest.approx(100 / 300), }, ] } @pytest.mark.db def test_get_automated_email_sales_shops_analytics( client: TestClient, create_model: CreateModel ) -> None: automated_email = create_model(AutomatedEmail, name="Test Automated Email") create_model( ShopifyEmailOrdersDbt, email_id=automated_email.id, shop_id="shop1", currency="USD", country_code="US", shop_name="Big Shop", total_orders=5, items_sold=10, total_sales_usd=320.0, total_discounts_usd=10.0, total_taxes_usd=8.0, total_delivery_usd=5.0, total_net_sales_usd=300.0, ) create_model( ShopifyEmailOrdersDbt, email_id=automated_email.id, shop_id="shop2", currency="USD", country_code="CA", shop_name="Small Shop", total_orders=2, items_sold=4, total_sales_usd=115.0, total_discounts_usd=5.0, total_taxes_usd=4.0, total_delivery_usd=3.0, total_net_sales_usd=100.0, ) response = client.get(f"/automated/{automated_email.id}/analytics/sales/shops") assert response.status_code == 200 assert response.json() == { "data": [ {"shopName": "Big Shop", "itemsSold": 10, "netSalesUsd": 300.0}, {"shopName": "Small Shop", "itemsSold": 4, "netSalesUsd": 100.0}, ] } @pytest.mark.db def test_get_automated_email_sales_products_analytics_no_data( client: TestClient, create_model: CreateModel ) -> None: automated_email = create_model(AutomatedEmail, name="Test Automated Email") response = client.get(f"/automated/{automated_email.id}/analytics/sales/products") assert response.status_code == 200 assert response.json() == {"data": []} @pytest.mark.db def test_get_automated_email_sales_locations_analytics_no_data( client: TestClient, create_model: CreateModel ) -> None: automated_email = create_model(AutomatedEmail, name="Test Automated Email") response = client.get(f"/automated/{automated_email.id}/analytics/sales/locations") assert response.status_code == 200 assert response.json() == {"data": []} @pytest.mark.db def test_get_automated_email_sales_shops_analytics_no_data( client: TestClient, create_model: CreateModel ) -> None: automated_email = create_model(AutomatedEmail, name="Test Automated Email") response = client.get(f"/automated/{automated_email.id}/analytics/sales/shops") assert response.status_code == 200 assert response.json() == {"data": []}