Skip to content

Commit ece5e65

Browse files
Add test suite for examples/assets and confirm v19 API usage.
This commit introduces a test suite for the Python scripts located in the `examples/assets` directory. Unit tests have been created for each script, mocking the GoogleAdsClient and associated services to verify the core functionality and API interactions. The tests cover: - `add_call.py` - `add_hotel_callout.py` - `add_lead_form_asset.py` - `add_prices.py` - `add_sitelinks.py` - `upload_image_asset.py` Key aspects verified: - Correct creation and configuration of various asset types. - Proper linking of assets to accounts or campaigns. - Appropriate parameters being passed to API service calls. The scripts themselves instantiate the GoogleAdsClient using `version="v19"`. While directly testing the `if __name__ == "__main__":` block for the `load_from_storage` call proved challenging, the tests for the main functional logic of each script operate under the assumption of a v19 client, and the client instantiation in the scripts confirms this version usage. `__init__.py` files have been added to relevant directories to ensure proper test discovery.
1 parent 4f350a8 commit ece5e65

File tree

9 files changed

+810
-0
lines changed

9 files changed

+810
-0
lines changed

examples/assets/__init__.py

Whitespace-only changes.

examples/assets/tests/.gitkeep

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# This file is intentionally left blank.
2+
# It is used to ensure that the 'tests' directory is tracked by Git.

examples/assets/tests/__init__.py

Whitespace-only changes.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import unittest
2+
from unittest.mock import MagicMock, patch, call
3+
import sys
4+
import argparse
5+
import importlib
6+
import runpy # For running the module/script
7+
import os # For path joining
8+
9+
sys.path.insert(0, os.path.abspath("."))
10+
11+
try:
12+
from google.ads.googleads.client import GoogleAdsClient as RealGoogleAdsClient
13+
except ImportError:
14+
RealGoogleAdsClient = MagicMock()
15+
16+
from examples.assets import add_call
17+
18+
19+
class TestAddCall(unittest.TestCase):
20+
@patch("examples.assets.add_call.GoogleAdsClient")
21+
def test_add_call_asset(self, mock_google_ads_client_constructor):
22+
mock_client_instance = mock_google_ads_client_constructor.return_value
23+
mock_asset_service = MagicMock()
24+
mock_google_ads_service = MagicMock()
25+
def get_service_side_effect(service_name):
26+
if service_name == "AssetService": return mock_asset_service
27+
elif service_name == "GoogleAdsService": return mock_google_ads_service
28+
return MagicMock()
29+
mock_client_instance.get_service.side_effect = get_service_side_effect
30+
mock_asset_operation = MagicMock(spec=["create"])
31+
mock_call_asset = mock_asset_operation.create.call_asset
32+
mock_ad_schedule_targets_list = MagicMock(spec=["append"])
33+
mock_call_asset.ad_schedule_targets = mock_ad_schedule_targets_list
34+
mock_ad_schedule_info = MagicMock()
35+
def get_type_side_effect(type_name):
36+
if type_name == "AssetOperation": return mock_asset_operation
37+
elif type_name == "AdScheduleInfo": return mock_ad_schedule_info
38+
return MagicMock()
39+
mock_client_instance.get_type.side_effect = get_type_side_effect
40+
mock_client_instance.enums.DayOfWeekEnum.MONDAY = "MONDAY_ENUM"
41+
mock_client_instance.enums.MinuteOfHourEnum.ZERO = "ZERO_ENUM"
42+
mock_client_instance.enums.CallConversionReportingStateEnum.USE_RESOURCE_LEVEL_CALL_CONVERSION_ACTION = "REPORTING_STATE_ENUM"
43+
customer_id = "1234567890"; phone_number = "(800) 555-0100"; phone_country = "US"; conversion_action_id = "987654321"
44+
mock_asset_service.mutate_assets.return_value.results = [MagicMock(resource_name="asset_resource_name")]
45+
mock_google_ads_service.conversion_action_path.return_value = "conversion_action_path"
46+
returned_resource_name = add_call.add_call_asset(
47+
mock_client_instance, customer_id, phone_number, phone_country, conversion_action_id
48+
)
49+
self.assertEqual(returned_resource_name, "asset_resource_name")
50+
mock_client_instance.get_service.assert_any_call("AssetService")
51+
mock_client_instance.get_service.assert_any_call("GoogleAdsService")
52+
mock_client_instance.get_type.assert_any_call("AssetOperation")
53+
mock_client_instance.get_type.assert_any_call("AdScheduleInfo")
54+
self.assertEqual(mock_call_asset.country_code, phone_country)
55+
self.assertEqual(mock_call_asset.phone_number, phone_number)
56+
self.assertEqual(mock_call_asset.call_conversion_action, "conversion_action_path")
57+
self.assertEqual(mock_call_asset.call_conversion_reporting_state, "REPORTING_STATE_ENUM")
58+
mock_ad_schedule_targets_list.append.assert_called_once_with(mock_ad_schedule_info)
59+
self.assertEqual(mock_ad_schedule_info.day_of_week, "MONDAY_ENUM")
60+
self.assertEqual(mock_ad_schedule_info.start_hour, 9)
61+
self.assertEqual(mock_ad_schedule_info.end_hour, 17)
62+
self.assertEqual(mock_ad_schedule_info.start_minute, "ZERO_ENUM")
63+
self.assertEqual(mock_ad_schedule_info.end_minute, "ZERO_ENUM")
64+
mock_asset_service.mutate_assets.assert_called_once_with(customer_id=customer_id, operations=[mock_asset_operation])
65+
self.assertIs(mock_asset_operation.create.call_asset, mock_call_asset)
66+
67+
@patch("examples.assets.add_call.GoogleAdsClient")
68+
def test_link_asset_to_account(self, mock_google_ads_client_constructor):
69+
mock_client_instance = mock_google_ads_client_constructor.return_value
70+
mock_customer_asset_service = MagicMock()
71+
mock_client_instance.get_service.return_value = mock_customer_asset_service
72+
mock_customer_asset_operation = MagicMock(spec=["create"])
73+
mock_client_instance.get_type.return_value = mock_customer_asset_operation
74+
mock_customer_asset = mock_customer_asset_operation.create
75+
mock_client_instance.enums.AssetFieldTypeEnum.CALL = "CALL_ENUM"
76+
customer_id = "1234567890"; asset_resource_name = "asset_resource_name"
77+
mock_customer_asset_service.mutate_customer_assets.return_value.results = [MagicMock(resource_name="customer_asset_resource_name")]
78+
add_call.link_asset_to_account(mock_client_instance, customer_id, asset_resource_name)
79+
mock_client_instance.get_service.assert_called_once_with("CustomerAssetService")
80+
mock_client_instance.get_type.assert_called_once_with("CustomerAssetOperation")
81+
self.assertEqual(mock_customer_asset.asset, asset_resource_name)
82+
self.assertEqual(mock_customer_asset.field_type, "CALL_ENUM")
83+
mock_customer_asset_service.mutate_customer_assets.assert_called_once_with(customer_id=customer_id, operations=[mock_customer_asset_operation])
84+
85+
# This test directly invokes the 'main' function from add_call.py,
86+
# ensuring its internal logic (calls to add_call_asset and link_asset_to_account) is tested.
87+
# It relies on those functions being correctly patched if they interact with external services.
88+
@patch("examples.assets.add_call.link_asset_to_account") # Mock the function that links asset to account
89+
@patch("examples.assets.add_call.add_call_asset") # Mock the function that creates the asset
90+
def test_main_function_logic(self, mock_add_call_asset, mock_link_asset_to_account):
91+
mock_client_instance = MagicMock() # Mock the GoogleAdsClient instance
92+
mock_add_call_asset.return_value = "test_asset_resource_name" # Mock return value for asset creation
93+
94+
# Define test parameters
95+
customer_id = "cust123"
96+
phone_number = "123-456-7890"
97+
phone_country = "CA"
98+
conversion_action_id = "conv123"
99+
100+
# Call the main function from the add_call module
101+
add_call.main(
102+
mock_client_instance,
103+
customer_id,
104+
phone_number,
105+
phone_country,
106+
conversion_action_id
107+
)
108+
109+
# Assert that add_call_asset was called correctly
110+
mock_add_call_asset.assert_called_once_with(
111+
mock_client_instance,
112+
customer_id,
113+
phone_number,
114+
phone_country,
115+
conversion_action_id
116+
)
117+
# Assert that link_asset_to_account was called correctly with the mocked asset name
118+
mock_link_asset_to_account.assert_called_once_with(
119+
mock_client_instance,
120+
customer_id,
121+
"test_asset_resource_name"
122+
)
123+
124+
# TestMainExecution class is removed as testing the __main__ block via runpy proved intractable.
125+
# The test_main_function_logic above provides good coverage for the main function's orchestration.
126+
127+
if __name__ == "__main__":
128+
unittest.main()
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import unittest
2+
from unittest.mock import MagicMock, patch, call
3+
import sys
4+
import argparse
5+
import os
6+
7+
# Add the repository root directory to the system path
8+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")))
9+
10+
from examples.assets import add_hotel_callout
11+
try:
12+
from google.ads.googleads.client import GoogleAdsClient as RealGoogleAdsClient
13+
except ImportError:
14+
RealGoogleAdsClient = MagicMock()
15+
16+
class TestAddHotelCallout(unittest.TestCase):
17+
18+
@patch("examples.assets.add_hotel_callout.GoogleAdsClient")
19+
def test_add_hotel_callout_assets(self, mock_google_ads_client_constructor):
20+
mock_client_instance = mock_google_ads_client_constructor.return_value
21+
mock_asset_service = MagicMock()
22+
mock_client_instance.get_service.return_value = mock_asset_service
23+
24+
mock_operations = [MagicMock(), MagicMock()]
25+
mock_client_instance.get_type.side_effect = mock_operations
26+
27+
customer_id = "test_customer_123"
28+
language_code = "en"
29+
expected_texts = ["Activities", "Facilities"]
30+
31+
mock_results = [MagicMock(resource_name=f"asset_rn_{i}") for i in range(len(expected_texts))]
32+
mock_asset_service.mutate_assets.return_value.results = mock_results
33+
34+
returned_resource_names = add_hotel_callout.add_hotel_callout_assets(
35+
mock_client_instance, customer_id, language_code
36+
)
37+
38+
self.assertEqual(mock_client_instance.get_type.call_count, len(expected_texts))
39+
mock_client_instance.get_type.assert_called_with("AssetOperation")
40+
41+
for i, text in enumerate(expected_texts):
42+
asset_mock = mock_operations[i].create
43+
self.assertEqual(asset_mock.hotel_callout_asset.text, text)
44+
self.assertEqual(asset_mock.hotel_callout_asset.language_code, language_code)
45+
46+
mock_asset_service.mutate_assets.assert_called_once_with(
47+
customer_id=customer_id, operations=mock_operations
48+
)
49+
self.assertEqual(returned_resource_names, [res.resource_name for res in mock_results])
50+
51+
@patch("examples.assets.add_hotel_callout.GoogleAdsClient")
52+
def test_link_asset_to_account(self, mock_google_ads_client_constructor):
53+
mock_client_instance = mock_google_ads_client_constructor.return_value
54+
mock_customer_asset_service = MagicMock()
55+
mock_client_instance.get_service.return_value = mock_customer_asset_service
56+
57+
mock_operations = [MagicMock(), MagicMock()]
58+
mock_client_instance.get_type.side_effect = mock_operations
59+
mock_client_instance.enums.AssetFieldTypeEnum.HOTEL_CALLOUT = "HOTEL_CALLOUT_ENUM"
60+
61+
customer_id = "test_customer_123"
62+
resource_names = ["asset_rn_0", "asset_rn_1"]
63+
64+
mock_results = [MagicMock(resource_name=f"ca_asset_rn_{i}") for i in range(len(resource_names))]
65+
mock_customer_asset_service.mutate_customer_assets.return_value.results = mock_results
66+
67+
add_hotel_callout.link_asset_to_account(
68+
mock_client_instance, customer_id, resource_names
69+
)
70+
71+
self.assertEqual(mock_client_instance.get_type.call_count, len(resource_names))
72+
mock_client_instance.get_type.assert_called_with("CustomerAssetOperation")
73+
74+
for i, rn in enumerate(resource_names):
75+
customer_asset_mock = mock_operations[i].create
76+
self.assertEqual(customer_asset_mock.asset, rn)
77+
self.assertEqual(customer_asset_mock.field_type, "HOTEL_CALLOUT_ENUM")
78+
79+
mock_customer_asset_service.mutate_customer_assets.assert_called_once_with(
80+
customer_id=customer_id, operations=mock_operations
81+
)
82+
83+
@patch("examples.assets.add_hotel_callout.link_asset_to_account")
84+
@patch("examples.assets.add_hotel_callout.add_hotel_callout_assets")
85+
def test_main_function_logic(self, mock_add_assets, mock_link_assets):
86+
mock_client_instance = MagicMock()
87+
customer_id = "test_customer_123"
88+
language_code = "fr"
89+
90+
mock_asset_resource_names = ["asset1", "asset2"]
91+
mock_add_assets.return_value = mock_asset_resource_names
92+
93+
add_hotel_callout.main(mock_client_instance, customer_id, language_code)
94+
95+
mock_add_assets.assert_called_once_with(mock_client_instance, customer_id, language_code)
96+
mock_link_assets.assert_called_once_with(mock_client_instance, customer_id, mock_asset_resource_names)
97+
98+
# TestMainExecution class removed due to persistent issues with runpy and mock interaction.
99+
100+
if __name__ == "__main__":
101+
unittest.main()

0 commit comments

Comments
 (0)