from collections.abc import Iterator import httpx import pytest import respx from faker import Faker from app.adapters.tiktok import TikTokClient from app.adapters.tiktok.constants import AUDIENCE_CALCULATE_TYPE from app.adapters.tiktok.exceptions import TikTokClientError from tests.unit.adapters.tiktok.factories import AudienceFactory, AudienceFileFactory class TestTikTokClient: @pytest.fixture(scope="module") def client(self) -> Iterator[TikTokClient]: with TikTokClient() as client: yield client def test_upload_audience_file( self, client: TikTokClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: ad_account_external_id = faker.pystr() access_token = faker.pystr() file = faker.binary(length=10) file_signature = faker.pystr() audience_file = AudienceFileFactory.build() respx_mock.post( "https://business-api.tiktok.com/open_api/v1.3/dmp/custom_audience/file/upload/", data={ "advertiser_id": ad_account_external_id, "calculate_type": AUDIENCE_CALCULATE_TYPE, "file_signature": file_signature, }, headers={"Access-Token": access_token}, files={"file": ("file.csv", file)}, ).mock( return_value=httpx.Response( status_code=200, json={ "code": 0, "data": audience_file.model_dump(), "request_id": faker.pystr(), }, ) ) result = client.upload_audience_file( access_token=access_token, ad_account_external_id=ad_account_external_id, file=file, file_signature=file_signature, ) assert result == audience_file def test_create_custom_audience( self, client: TikTokClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: ad_account_external_id = faker.pystr() access_token = faker.pystr() audience_name = faker.pystr() file_paths = [faker.pystr(), faker.pystr()] audience = AudienceFactory.build() respx_mock.post( "https://business-api.tiktok.com/open_api/v1.3/dmp/custom_audience/create/", json={ "custom_audience_name": audience_name, "advertiser_id": ad_account_external_id, "calculate_type": AUDIENCE_CALCULATE_TYPE, "file_paths": file_paths, }, headers={"Access-Token": access_token}, ).mock( return_value=httpx.Response( status_code=200, json={ "code": 0, "data": audience.model_dump(), "request_id": faker.pystr(), }, ) ) result = client.create_custom_audience( access_token=access_token, audience_name=audience_name, ad_account_external_id=ad_account_external_id, file_paths=file_paths, ) assert result == audience def test_error_response_code( self, client: TikTokClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: ad_account_external_id = faker.pystr() access_token = faker.pystr() audience_name = faker.pystr() file_paths = [faker.pystr(), faker.pystr()] respx_mock.post( "https://business-api.tiktok.com/open_api/v1.3/dmp/custom_audience/create/", json={ "custom_audience_name": audience_name, "advertiser_id": ad_account_external_id, "calculate_type": AUDIENCE_CALCULATE_TYPE, "file_paths": file_paths, }, headers={"Access-Token": access_token}, ).mock( return_value=httpx.Response( status_code=200, json={ "code": 1, "message": "Invalid request", "request_id": faker.pystr(), }, ) ) with pytest.raises(TikTokClientError) as exc_info: client.create_custom_audience( access_token=access_token, audience_name=audience_name, ad_account_external_id=ad_account_external_id, file_paths=file_paths, ) assert exc_info.value.tiktok_error assert exc_info.value.tiktok_error.code == 1 assert exc_info.value.tiktok_error.message == "Invalid request" assert exc_info.value.log_msg == "Invalid request" assert exc_info.value.log_extra == { "tiktok_client_request_method": "POST", "tiktok_client_request_path": "/open_api/v1.3/dmp/custom_audience/create/", "tiktok_client_response_status_code": 200, "tiktok_client_response_error_code": 1, "tiktok_client_response_error_message": "Invalid request", } def test_error_response_missing_data( self, client: TikTokClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: ad_account_external_id = faker.pystr() access_token = faker.pystr() audience_name = faker.pystr() file_paths = [faker.pystr(), faker.pystr()] respx_mock.post( "https://business-api.tiktok.com/open_api/v1.3/dmp/custom_audience/create/", json={ "custom_audience_name": audience_name, "advertiser_id": ad_account_external_id, "calculate_type": AUDIENCE_CALCULATE_TYPE, "file_paths": file_paths, }, headers={"Access-Token": access_token}, ).mock( return_value=httpx.Response( status_code=200, json={ "code": 1, "message": "Invalid request", "request_id": faker.pystr(), }, ) ) with pytest.raises(TikTokClientError) as exc_info: client.create_custom_audience( access_token=access_token, audience_name=audience_name, ad_account_external_id=ad_account_external_id, file_paths=file_paths, ) assert exc_info.value.tiktok_error assert exc_info.value.tiktok_error.code == 1 assert exc_info.value.tiktok_error.message == "Invalid request" assert exc_info.value.log_msg == "Invalid request" assert exc_info.value.log_extra == { "tiktok_client_request_method": "POST", "tiktok_client_request_path": "/open_api/v1.3/dmp/custom_audience/create/", "tiktok_client_response_status_code": 200, "tiktok_client_response_error_code": 1, "tiktok_client_response_error_message": "Invalid request", }