From 15dcbe69bb0b1cc5df668e3f8809088462c8d981 Mon Sep 17 00:00:00 2001 From: Bob Hancock Date: Wed, 14 Jan 2026 09:47:22 -0500 Subject: [PATCH] - Fixed minor bugs in api_examples tests and added test for conversion upload summary --- ChangeLog | 4 + .../remove_automatically_created_assets.py | 8 +- .../test_get_conversion_upload_summary.py | 107 ++++++++++++++++++ ...est_remove_automatically_created_assets.py | 13 ++- 4 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 api_examples/tests/test_get_conversion_upload_summary.py diff --git a/ChangeLog b/ChangeLog index 9b7e949..bf4d8f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +* 1.4.1 +- Fixed minor bugs in tests for api_examples. +- Added test api_examples/tests/test_get_conversion_upload_summary.py + * 1.4.0 - Modified ChangeLog so changes are listed in reverse chronological order. - Added step_by_step custom command. diff --git a/api_examples/remove_automatically_created_assets.py b/api_examples/remove_automatically_created_assets.py index 971f7d2..260bdbf 100644 --- a/api_examples/remove_automatically_created_assets.py +++ b/api_examples/remove_automatically_created_assets.py @@ -17,9 +17,7 @@ from google.ads.googleads.client import GoogleAdsClient from google.ads.googleads.errors import GoogleAdsException -from google.ads.googleads.v22.enums.asset_field_type_enum.asset_field_type import ( - AssetFieldTypeEnum, -) +from google.ads.googleads.v22.enums import AssetFieldTypeEnum def main( @@ -54,11 +52,11 @@ def main( # removing (e.g., TEXT, IMAGE, VIDEO). try: - field_type_enum = getattr(AssetFieldTypeEnum, field_type.upper()) + field_type_enum = getattr(AssetFieldTypeEnum.AssetFieldType, field_type.upper()) except AttributeError: print( f"Error: Invalid field type '{field_type}'. " - f"Please use one of: {[e.name for e in AssetFieldTypeEnum if e.name not in ('UNSPECIFIED', 'UNKNOWN')]}" + f"Please use one of: {[e.name for e in AssetFieldTypeEnum.AssetFieldType if e.name not in ('UNSPECIFIED', 'UNKNOWN')]}" ) sys.exit(1) diff --git a/api_examples/tests/test_get_conversion_upload_summary.py b/api_examples/tests/test_get_conversion_upload_summary.py new file mode 100644 index 0000000..ff3acd5 --- /dev/null +++ b/api_examples/tests/test_get_conversion_upload_summary.py @@ -0,0 +1,107 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import os +import unittest +from unittest.mock import MagicMock, call +from io import StringIO + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) + +from google.ads.googleads.errors import GoogleAdsException +from google.ads.googleads.client import GoogleAdsClient +from api_examples.get_conversion_upload_summary import main + + +class TestGetConversionUploadSummary(unittest.TestCase): + def setUp(self): + self.mock_client = MagicMock(spec=GoogleAdsClient) + self.mock_ga_service = MagicMock() + self.mock_client.get_service.return_value = self.mock_ga_service + self.customer_id = "1234567890" + + self.captured_output = StringIO() + sys.stdout = self.captured_output + + def tearDown(self): + sys.stdout = sys.__stdout__ + + def test_main_success(self): + # Mock responses for search_stream + mock_batch_1 = MagicMock() + mock_row_1 = MagicMock() + mock_summary_1 = MagicMock() + mock_summary_1.resource_name = "customers/123/offlineConversionUploadClientSummaries/1" + mock_summary_1.status.name = "SUCCESS" + mock_summary_1.total_event_count = 10 + mock_summary_1.successful_event_count = 10 + mock_summary_1.success_rate = 1.0 + mock_summary_1.last_upload_date_time = "2024-01-01 12:00:00" + mock_summary_1.alerts = [] + mock_summary_1.daily_summaries = [] + mock_summary_1.job_summaries = [] + mock_row_1.offline_conversion_upload_client_summary = mock_summary_1 + mock_batch_1.results = [mock_row_1] + + mock_batch_2 = MagicMock() + mock_row_2 = MagicMock() + mock_summary_2 = MagicMock() + mock_summary_2.resource_name = "customers/123/offlineConversionUploadConversionActionSummaries/1" + mock_summary_2.conversion_action_name = "My Conversion Action" + mock_summary_2.status.name = "SUCCESS" + mock_summary_2.total_event_count = 5 + mock_summary_2.successful_event_count = 5 + mock_summary_2.alerts = [] + mock_summary_2.daily_summaries = [] + mock_summary_2.job_summaries = [] + mock_row_2.offline_conversion_upload_conversion_action_summary = mock_summary_2 + mock_batch_2.results = [mock_row_2] + + # The first call returns client summary, second call returns conversion action summary + self.mock_ga_service.search_stream.side_effect = [[mock_batch_1], [mock_batch_2]] + + main(self.mock_client, self.customer_id) + + # Check output + output = self.captured_output.getvalue() + self.assertIn("Offline Conversion Upload Client Summary:", output) + self.assertIn("Resource Name: customers/123/offlineConversionUploadClientSummaries/1", output) + self.assertIn("Offline Conversion Upload Conversion Action Summary:", output) + self.assertIn("Conversion Action Name: My Conversion Action", output) + + self.assertEqual(self.mock_ga_service.search_stream.call_count, 2) + + def test_main_google_ads_exception(self): + mock_error = MagicMock() + mock_error.code.return_value.name = "INTERNAL_ERROR" + mock_failure = MagicMock() + mock_failure.errors = [MagicMock(message="Internal error")] + + self.mock_ga_service.search_stream.side_effect = GoogleAdsException( + error=mock_error, + call=MagicMock(), + failure=mock_failure, + request_id="test_request_id" + ) + + with self.assertRaises(SystemExit) as cm: + main(self.mock_client, self.customer_id) + + self.assertEqual(cm.exception.code, 1) + output = self.captured_output.getvalue() + self.assertIn('Request with ID "test_request_id" failed with status "INTERNAL_ERROR"', output) + +if __name__ == "__main__": + unittest.main() diff --git a/api_examples/tests/test_remove_automatically_created_assets.py b/api_examples/tests/test_remove_automatically_created_assets.py index 88dd56b..d7fa1fe 100644 --- a/api_examples/tests/test_remove_automatically_created_assets.py +++ b/api_examples/tests/test_remove_automatically_created_assets.py @@ -36,6 +36,11 @@ def setUp(self): self.mock_client.get_service.side_effect = self._get_mock_service + self.patcher = unittest.mock.patch( + "api_examples.remove_automatically_created_assets.AssetFieldTypeEnum" + ) + self.mock_asset_field_type_enum = self.patcher.start() + class MockAssetFieldType: UNSPECIFIED = MagicMock() UNSPECIFIED.name = "UNSPECIFIED" @@ -67,13 +72,8 @@ def __iter__(self): ] ) - self.mock_real_asset_field_type = MockAssetFieldType() + self.mock_asset_field_type_enum.AssetFieldType = MockAssetFieldType() - self.mock_client.enums = MagicMock() - self.mock_client.enums.AssetFieldTypeEnum = MagicMock() - self.mock_client.enums.AssetFieldTypeEnum.AssetFieldType = ( - self.mock_real_asset_field_type - ) self.customer_id = "1234567890" self.campaign_id = 12345 @@ -92,6 +92,7 @@ def _get_mock_service(self, service_name): def tearDown(self): sys.stdout = sys.__stdout__ + self.patcher.stop() def test_main_successful_removal(self): mock_response = MagicMock()