diff --git a/examples/asyncio/async_add_campaigns.py b/examples/asyncio/async_add_campaigns.py index 887eec872..69d8d9455 100644 --- a/examples/asyncio/async_add_campaigns.py +++ b/examples/asyncio/async_add_campaigns.py @@ -24,58 +24,53 @@ from google.ads.googleads.client import GoogleAdsClient from google.ads.googleads.errors import GoogleAdsException -from google.ads.googleads.v22.services.services.campaign_budget_service import ( - CampaignBudgetServiceClient, +from google.ads.googleads.v22.resources.types.campaign import Campaign +from google.ads.googleads.v22.resources.types.campaign_budget import ( + CampaignBudget, ) from google.ads.googleads.v22.services.types.campaign_budget_service import ( CampaignBudgetOperation, - MutateCampaignBudgetsResponse, -) -from google.ads.googleads.v22.services.services.campaign_service import ( - CampaignServiceClient, ) from google.ads.googleads.v22.services.types.campaign_service import ( CampaignOperation, - MutateCampaignsResponse, ) -from google.ads.googleads.v22.resources.types.campaign_budget import ( - CampaignBudget, +from google.ads.googleads.v22.services.types.google_ads_service import ( + MutateGoogleAdsResponse, + MutateOperation, ) -from google.ads.googleads.v22.resources.types.campaign import Campaign _DATE_FORMAT: str = "%Y%m%d" async def main(client: GoogleAdsClient, customer_id: str) -> None: - campaign_budget_service: CampaignBudgetServiceClient = client.get_service( - "CampaignBudgetService", is_async=True - ) - campaign_service: CampaignServiceClient = client.get_service( - "CampaignService", is_async=True - ) + # Gets the GoogleAdsService client. + googleads_service = client.get_service("GoogleAdsService", is_async=True) + + # We are creating both the budget and the campaign in the same request, so + # we need to use a temporary resource name for the budget to reference it + # in the campaign. + # Temporary resource names must be negative integers formatted as strings. + # https://developers.google.com/google-ads/api/docs/batch-processing/temporary-ids + budget_resource_name: str = f"customers/{customer_id}/campaignBudgets/-1" + + mutate_operations: List[MutateOperation] = [] # Create a budget, which can be shared by multiple campaigns. campaign_budget_operation: CampaignBudgetOperation = client.get_type( "CampaignBudgetOperation" ) campaign_budget: CampaignBudget = campaign_budget_operation.create + campaign_budget.resource_name = budget_resource_name campaign_budget.name = f"Interplanetary Budget {uuid.uuid4()}" campaign_budget.delivery_method = ( client.enums.BudgetDeliveryMethodEnum.STANDARD ) campaign_budget.amount_micros = 500000 - # Add budget. - budget_operations: List[CampaignBudgetOperation] = [ - campaign_budget_operation - ] - campaign_budget_response: MutateCampaignBudgetsResponse = ( - await campaign_budget_service.mutate_campaign_budgets( - customer_id=customer_id, - operations=budget_operations, - ) - ) + mutate_operation_budget: MutateOperation = client.get_type("MutateOperation") + mutate_operation_budget.campaign_budget_operation = campaign_budget_operation + mutate_operations.append(mutate_operation_budget) # Create campaign. campaign_operation: CampaignOperation = client.get_type("CampaignOperation") @@ -92,7 +87,8 @@ async def main(client: GoogleAdsClient, customer_id: str) -> None: # Set the bidding strategy and budget. campaign.manual_cpc = client.get_type("ManualCpc") - campaign.campaign_budget = campaign_budget_response.results[0].resource_name + # Reference the budget created in the same request. + campaign.campaign_budget = budget_resource_name # Set the campaign network options. campaign.network_settings.target_google_search = True @@ -121,12 +117,21 @@ async def main(client: GoogleAdsClient, customer_id: str) -> None: campaign.end_date = datetime.date.strftime(end_time, _DATE_FORMAT) # [END add_campaigns_1] - # Add the campaign. - campaign_operations: List[CampaignOperation] = [campaign_operation] - campaign_response: MutateCampaignsResponse = await campaign_service.mutate_campaigns( - customer_id=customer_id, operations=campaign_operations + mutate_operation_campaign: MutateOperation = client.get_type( + "MutateOperation" + ) + mutate_operation_campaign.campaign_operation = campaign_operation + mutate_operations.append(mutate_operation_campaign) + + # Issue a mutate request to add the budget and campaign. + response: MutateGoogleAdsResponse = await googleads_service.mutate( + customer_id=customer_id, mutate_operations=mutate_operations ) - print(f"Created campaign {campaign_response.results[0].resource_name}.") + + # Check the result for the campaign (index 1 in the operations list). + # The response returns results in the same order as operations. + campaign_result = response.mutate_operation_responses[1].campaign_result + print(f"Created campaign {campaign_result.resource_name}.") if __name__ == "__main__": diff --git a/examples/asyncio/async_search.py b/examples/asyncio/async_search.py index 6d57417cc..c0524b023 100755 --- a/examples/asyncio/async_search.py +++ b/examples/asyncio/async_search.py @@ -40,8 +40,7 @@ async def main(client: GoogleAdsClient, customer_id: str) -> None: campaign.id, campaign.name FROM campaign - ORDER BY campaign.id - LIMIT 10""" + ORDER BY campaign.id""" # Issues a search request using streaming. stream = await ga_service.search( diff --git a/examples/asyncio/async_search_stream.py b/examples/asyncio/async_search_stream.py index 6008b74c5..913e0730a 100755 --- a/examples/asyncio/async_search_stream.py +++ b/examples/asyncio/async_search_stream.py @@ -40,8 +40,7 @@ async def main(client: GoogleAdsClient, customer_id: str) -> None: campaign.id, campaign.name FROM campaign - ORDER BY campaign.id - LIMIT 10""" + ORDER BY campaign.id""" # Issues a search request using streaming. stream = await ga_service.search_stream( diff --git a/tests/examples/asyncio/async_add_campaigns_test.py b/tests/examples/asyncio/async_add_campaigns_test.py new file mode 100644 index 000000000..d1db73926 --- /dev/null +++ b/tests/examples/asyncio/async_add_campaigns_test.py @@ -0,0 +1,101 @@ +import unittest +import sys +from unittest.mock import MagicMock, AsyncMock, patch + +# Mocking modules before import because the environment seems to lack dependencies +mock_google = MagicMock() +sys.modules["google"] = mock_google +sys.modules["google.ads"] = mock_google +sys.modules["google.ads.googleads"] = mock_google +sys.modules["google.ads.googleads.client"] = mock_google +sys.modules["google.ads.googleads.errors"] = mock_google +sys.modules["google.ads.googleads.v22"] = mock_google +sys.modules["google.ads.googleads.v22.resources"] = mock_google +sys.modules["google.ads.googleads.v22.resources.types"] = mock_google +sys.modules["google.ads.googleads.v22.resources.types.campaign"] = mock_google +sys.modules["google.ads.googleads.v22.resources.types.campaign_budget"] = mock_google +sys.modules["google.ads.googleads.v22.services"] = mock_google +sys.modules["google.ads.googleads.v22.services.services"] = mock_google +sys.modules["google.ads.googleads.v22.services.services.campaign_budget_service"] = mock_google +sys.modules["google.ads.googleads.v22.services.services.campaign_service"] = mock_google +sys.modules["google.ads.googleads.v22.services.types"] = mock_google +sys.modules["google.ads.googleads.v22.services.types.campaign_budget_service"] = mock_google +sys.modules["google.ads.googleads.v22.services.types.campaign_service"] = mock_google +sys.modules["google.ads.googleads.v22.services.types.google_ads_service"] = mock_google + +from examples.asyncio import async_add_campaigns + + +class TestAsyncAddCampaigns(unittest.IsolatedAsyncioTestCase): + async def test_main(self): + # Setup Mocks + mock_client_instance = MagicMock() # Mock the client instance directly + mock_googleads_service = AsyncMock() + mock_client_instance.get_service.return_value = mock_googleads_service + + mock_budget_op = MagicMock(name="BudgetOp") + mock_campaign_op = MagicMock(name="CampaignOp") + mock_mutate_op_budget = MagicMock(name="MutateOpBudget") + mock_mutate_op_campaign = MagicMock(name="MutateOpCampaign") + mock_manual_cpc = MagicMock(name="ManualCpc") + + # side_effect for get_type calls: + # 1. CampaignBudgetOperation + # 2. MutateOperation (for budget) + # 3. CampaignOperation + # 4. ManualCpc + # 5. MutateOperation (for campaign) + mock_client_instance.get_type.side_effect = [ + mock_budget_op, + mock_mutate_op_budget, + mock_campaign_op, + mock_manual_cpc, + mock_mutate_op_campaign, + ] + + # Setup inner objects + mock_budget = mock_budget_op.create + mock_campaign = mock_campaign_op.create + + # Setup response + mock_response = MagicMock() + mock_campaign_result = MagicMock() + mock_campaign_result.resource_name = "customers/123/campaigns/456" + + # response.mutate_operation_responses[1].campaign_result + mock_response.mutate_operation_responses = [ + MagicMock(), # budget result + MagicMock(campaign_result=mock_campaign_result) # campaign result + ] + + mock_googleads_service.mutate.return_value = mock_response + + customer_id = "1234567890" + + await async_add_campaigns.main(mock_client_instance, customer_id) + + # Verification + + # Check if service was retrieved correctly + mock_client_instance.get_service.assert_called_with("GoogleAdsService", is_async=True) + + # Verify Budget Resource Name + expected_budget_resource_name = f"customers/{customer_id}/campaignBudgets/-1" + self.assertEqual(mock_budget.resource_name, expected_budget_resource_name) + + # Verify Campaign references Budget + self.assertEqual(mock_campaign.campaign_budget, expected_budget_resource_name) + + # Verify MutateOperations were constructed + self.assertEqual(mock_mutate_op_budget.campaign_budget_operation, mock_budget_op) + self.assertEqual(mock_mutate_op_campaign.campaign_operation, mock_campaign_op) + + # Verify GoogleAdsService.mutate called with correct operations + mock_googleads_service.mutate.assert_called_once() + call_args = mock_googleads_service.mutate.call_args + self.assertEqual(call_args.kwargs["customer_id"], customer_id) + + operations = call_args.kwargs["mutate_operations"] + self.assertEqual(len(operations), 2) + self.assertEqual(operations[0], mock_mutate_op_budget) + self.assertEqual(operations[1], mock_mutate_op_campaign) diff --git a/tests/examples/asyncio/async_search_stream_test.py b/tests/examples/asyncio/async_search_stream_test.py new file mode 100644 index 000000000..28cddd24c --- /dev/null +++ b/tests/examples/asyncio/async_search_stream_test.py @@ -0,0 +1,66 @@ +import unittest +import sys +from unittest.mock import MagicMock, AsyncMock, patch + +# Mocking modules before import because the environment seems to lack dependencies +mock_google = MagicMock() +sys.modules["google"] = mock_google +sys.modules["google.ads"] = mock_google +sys.modules["google.ads.googleads"] = mock_google +sys.modules["google.ads.googleads.client"] = mock_google +sys.modules["google.ads.googleads.errors"] = mock_google +sys.modules["google.ads.googleads.v22"] = mock_google +sys.modules["google.ads.googleads.v22.services"] = mock_google +sys.modules["google.ads.googleads.v22.services.services"] = mock_google +sys.modules["google.ads.googleads.v22.services.services.google_ads_service"] = mock_google +sys.modules["google.ads.googleads.v22.services.types"] = mock_google +sys.modules["google.ads.googleads.v22.services.types.google_ads_service"] = mock_google + +# Import module under test AFTER mocking +from examples.asyncio import async_search_stream + + +class TestAsyncSearchStream(unittest.IsolatedAsyncioTestCase): + async def test_main(self): + # Setup Mocks + mock_client_instance = MagicMock() + mock_googleads_service = AsyncMock() + mock_client_instance.get_service.return_value = mock_googleads_service + + # Mock the stream response + # search_stream returns an async iterator of batches + + # Create a mock batch + mock_row = MagicMock() + mock_row.campaign.id = 123 + mock_row.campaign.name = "Test Campaign" + + mock_batch = MagicMock() + mock_batch.results = [mock_row] + + # Async iterator setup + async def async_gen(): + yield mock_batch + + mock_googleads_service.search_stream.return_value = async_gen() + + customer_id = "1234567890" + + # Execute + await async_search_stream.main(mock_client_instance, customer_id) + + # Verification + + # Check if service was retrieved correctly + mock_client_instance.get_service.assert_called_with("GoogleAdsService", is_async=True) + + # Verify search_stream called + mock_googleads_service.search_stream.assert_called_once() + call_args = mock_googleads_service.search_stream.call_args + self.assertEqual(call_args.kwargs["customer_id"], customer_id) + + # Verify Query does NOT contain LIMIT 10 + query = call_args.kwargs["query"] + self.assertNotIn("LIMIT 10", query) + self.assertIn("SELECT", query) + self.assertIn("FROM campaign", query) diff --git a/tests/examples/asyncio/async_search_test.py b/tests/examples/asyncio/async_search_test.py new file mode 100644 index 000000000..95ca6da11 --- /dev/null +++ b/tests/examples/asyncio/async_search_test.py @@ -0,0 +1,63 @@ +import unittest +import sys +from unittest.mock import MagicMock, AsyncMock, patch + +# Mocking modules before import because the environment seems to lack dependencies +mock_google = MagicMock() +sys.modules["google"] = mock_google +sys.modules["google.ads"] = mock_google +sys.modules["google.ads.googleads"] = mock_google +sys.modules["google.ads.googleads.client"] = mock_google +sys.modules["google.ads.googleads.errors"] = mock_google +sys.modules["google.ads.googleads.v22"] = mock_google +sys.modules["google.ads.googleads.v22.services"] = mock_google +sys.modules["google.ads.googleads.v22.services.services"] = mock_google +sys.modules["google.ads.googleads.v22.services.services.google_ads_service"] = mock_google +sys.modules["google.ads.googleads.v22.services.types"] = mock_google +sys.modules["google.ads.googleads.v22.services.types.google_ads_service"] = mock_google + +# Import module under test AFTER mocking +from examples.asyncio import async_search + + +class TestAsyncSearch(unittest.IsolatedAsyncioTestCase): + async def test_main(self): + # Setup Mocks + mock_client_instance = MagicMock() + mock_googleads_service = AsyncMock() + mock_client_instance.get_service.return_value = mock_googleads_service + + # Mock the search response (AsyncPager) + # search returns an object that is an async iterator + + # Create a mock row + mock_row = MagicMock() + mock_row.campaign.id = 123 + mock_row.campaign.name = "Test Campaign" + + # Async iterator setup for the pager + async def async_gen(): + yield mock_row + + mock_googleads_service.search.return_value = async_gen() + + customer_id = "1234567890" + + # Execute + await async_search.main(mock_client_instance, customer_id) + + # Verification + + # Check if service was retrieved correctly + mock_client_instance.get_service.assert_called_with("GoogleAdsService", is_async=True) + + # Verify search called + mock_googleads_service.search.assert_called_once() + call_args = mock_googleads_service.search.call_args + self.assertEqual(call_args.kwargs["customer_id"], customer_id) + + # Verify Query does NOT contain LIMIT 10 + query = call_args.kwargs["query"] + self.assertNotIn("LIMIT 10", query) + self.assertIn("SELECT", query) + self.assertIn("FROM campaign", query)