Skip to content

Commit b1bb6b1

Browse files
bobhancockggoogle-labs-jules[bot]BenRKarl
authored
Gold tests assets (#987)
* 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. * Update github/feat/asset-examples-tests-v19 branch to reorganize tests and get them to work. Change-Id: Ie06bd9c015987ce27c0b8c9aafddd695c3a57b11 --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Ben Karl <[email protected]>
1 parent 86908ac commit b1bb6b1

File tree

11 files changed

+1044
-7
lines changed

11 files changed

+1044
-7
lines changed

examples/assets/__init__.py

Whitespace-only changes.

noxfile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
@nox.session(python=PYTHON_VERSIONS)
5151
@nox.parametrize("protobuf_implementation", PROTOBUF_IMPLEMENTATIONS)
5252
def tests(session, protobuf_implementation):
53-
session.install(".")
53+
session.install("-e", ".")
5454
# modules for testing
5555
session.install(*TEST_DEPENDENCIES)
5656
session.run(*FREEZE_COMMAND)
@@ -79,7 +79,7 @@ def tests_minimum_dependency_versions(session, protobuf_implementation):
7979

8080
constraints_file = os.path.join(CONSTRAINTS_DIR, "minimums", filename)
8181

82-
session.install(".")
82+
session.install("-e", ".")
8383
session.install(*TEST_DEPENDENCIES, "-c", constraints_file)
8484
session.run(*FREEZE_COMMAND)
8585
session.run(

pyproject.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,14 @@ version = "27.0.0"
2222
description = "Client library for the Google Ads API"
2323
readme = "./README.rst"
2424
requires-python = ">=3.9, <3.14"
25-
license = {text = "Apache 2.0"}
25+
license = "Apache-2.0"
2626
authors = [
2727
{name = "Google LLC", email = "[email protected]"}
2828
]
2929
classifiers = [
3030
"Intended Audience :: Developers",
3131
"Development Status :: 5 - Production/Stable",
3232
"Intended Audience :: Developers",
33-
"License :: OSI Approved :: Apache Software License",
3433
"Programming Language :: Python",
3534
"Programming Language :: Python :: 3",
3635
"Programming Language :: Python :: 3.9",
@@ -66,6 +65,5 @@ tests = ["nox >= 2020.12.31, < 2022.6"]
6665
Repository = "https://github.com/googleads/google-ads-python"
6766

6867
[tool.setuptools.packages.find]
69-
include=["google.ads.*"]
70-
exclude=["examples", "examples.*", "tests", "tests.*"]
71-
68+
include=["google.ads.*", "examples.*"]
69+
exclude=["tests", "tests.*"]

tests/examples/__init__.py

Whitespace-only changes.

tests/examples/assets/__init__.py

Whitespace-only changes.
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import unittest
15+
from unittest.mock import MagicMock, patch
16+
17+
import examples.assets.add_call as add_call
18+
19+
20+
class TestAddCall(unittest.TestCase):
21+
@patch("examples.assets.add_call.GoogleAdsClient")
22+
def test_add_call_asset(self, mock_google_ads_client_constructor):
23+
mock_client_instance = mock_google_ads_client_constructor.return_value
24+
mock_asset_service = MagicMock()
25+
mock_google_ads_service = MagicMock()
26+
27+
def get_service_side_effect(service_name):
28+
if service_name == "AssetService":
29+
return mock_asset_service
30+
elif service_name == "GoogleAdsService":
31+
return mock_google_ads_service
32+
return MagicMock()
33+
34+
mock_client_instance.get_service.side_effect = get_service_side_effect
35+
mock_asset_operation = MagicMock(spec=["create"])
36+
mock_call_asset = mock_asset_operation.create.call_asset
37+
mock_ad_schedule_targets_list = MagicMock(spec=["append"])
38+
mock_call_asset.ad_schedule_targets = mock_ad_schedule_targets_list
39+
mock_ad_schedule_info = MagicMock()
40+
41+
def get_type_side_effect(type_name):
42+
if type_name == "AssetOperation":
43+
return mock_asset_operation
44+
elif type_name == "AdScheduleInfo":
45+
return mock_ad_schedule_info
46+
return MagicMock()
47+
48+
mock_client_instance.get_type.side_effect = get_type_side_effect
49+
mock_client_instance.enums.DayOfWeekEnum.MONDAY = "MONDAY_ENUM"
50+
mock_client_instance.enums.MinuteOfHourEnum.ZERO = "ZERO_ENUM"
51+
mock_client_instance.enums.CallConversionReportingStateEnum.USE_RESOURCE_LEVEL_CALL_CONVERSION_ACTION = (
52+
"REPORTING_STATE_ENUM"
53+
)
54+
customer_id = "1234567890"
55+
phone_number = "(800) 555-0100"
56+
phone_country = "US"
57+
conversion_action_id = "987654321"
58+
mock_asset_service.mutate_assets.return_value.results = [
59+
MagicMock(resource_name="asset_resource_name")
60+
]
61+
mock_google_ads_service.conversion_action_path.return_value = (
62+
"conversion_action_path"
63+
)
64+
returned_resource_name = add_call.add_call_asset(
65+
mock_client_instance,
66+
customer_id,
67+
phone_number,
68+
phone_country,
69+
conversion_action_id,
70+
)
71+
self.assertEqual(returned_resource_name, "asset_resource_name")
72+
mock_client_instance.get_service.assert_any_call("AssetService")
73+
mock_client_instance.get_service.assert_any_call("GoogleAdsService")
74+
mock_client_instance.get_type.assert_any_call("AssetOperation")
75+
mock_client_instance.get_type.assert_any_call("AdScheduleInfo")
76+
self.assertEqual(mock_call_asset.country_code, phone_country)
77+
self.assertEqual(mock_call_asset.phone_number, phone_number)
78+
self.assertEqual(
79+
mock_call_asset.call_conversion_action, "conversion_action_path"
80+
)
81+
self.assertEqual(
82+
mock_call_asset.call_conversion_reporting_state,
83+
"REPORTING_STATE_ENUM",
84+
)
85+
mock_ad_schedule_targets_list.append.assert_called_once_with(
86+
mock_ad_schedule_info
87+
)
88+
self.assertEqual(mock_ad_schedule_info.day_of_week, "MONDAY_ENUM")
89+
self.assertEqual(mock_ad_schedule_info.start_hour, 9)
90+
self.assertEqual(mock_ad_schedule_info.end_hour, 17)
91+
self.assertEqual(mock_ad_schedule_info.start_minute, "ZERO_ENUM")
92+
self.assertEqual(mock_ad_schedule_info.end_minute, "ZERO_ENUM")
93+
mock_asset_service.mutate_assets.assert_called_once_with(
94+
customer_id=customer_id, operations=[mock_asset_operation]
95+
)
96+
self.assertIs(mock_asset_operation.create.call_asset, mock_call_asset)
97+
98+
@patch("examples.assets.add_call.GoogleAdsClient")
99+
def test_link_asset_to_account(self, mock_google_ads_client_constructor):
100+
mock_client_instance = mock_google_ads_client_constructor.return_value
101+
mock_customer_asset_service = MagicMock()
102+
mock_client_instance.get_service.return_value = (
103+
mock_customer_asset_service
104+
)
105+
mock_customer_asset_operation = MagicMock(spec=["create"])
106+
mock_client_instance.get_type.return_value = (
107+
mock_customer_asset_operation
108+
)
109+
mock_customer_asset = mock_customer_asset_operation.create
110+
mock_client_instance.enums.AssetFieldTypeEnum.CALL = "CALL_ENUM"
111+
customer_id = "1234567890"
112+
asset_resource_name = "asset_resource_name"
113+
mock_customer_asset_service.mutate_customer_assets.return_value.results = [
114+
MagicMock(resource_name="customer_asset_resource_name")
115+
]
116+
add_call.link_asset_to_account(
117+
mock_client_instance, customer_id, asset_resource_name
118+
)
119+
mock_client_instance.get_service.assert_called_once_with(
120+
"CustomerAssetService"
121+
)
122+
mock_client_instance.get_type.assert_called_once_with(
123+
"CustomerAssetOperation"
124+
)
125+
self.assertEqual(mock_customer_asset.asset, asset_resource_name)
126+
self.assertEqual(mock_customer_asset.field_type, "CALL_ENUM")
127+
mock_customer_asset_service.mutate_customer_assets.assert_called_once_with(
128+
customer_id=customer_id, operations=[mock_customer_asset_operation]
129+
)
130+
131+
# This test directly invokes the 'main' function from add_call.py,
132+
# ensuring its internal logic (calls to add_call_asset and link_asset_to_account) is tested.
133+
# It relies on those functions being correctly patched if they interact with external services.
134+
@patch(
135+
"examples.assets.add_call.link_asset_to_account"
136+
) # Mock the function that links asset to account
137+
@patch(
138+
"examples.assets.add_call.add_call_asset"
139+
) # Mock the function that creates the asset
140+
def test_main_function_logic(
141+
self, mock_add_call_asset, mock_link_asset_to_account
142+
):
143+
mock_client_instance = MagicMock() # Mock the GoogleAdsClient instance
144+
mock_add_call_asset.return_value = (
145+
"test_asset_resource_name" # Mock return value for asset creation
146+
)
147+
148+
# Define test parameters
149+
customer_id = "cust123"
150+
phone_number = "123-456-7890"
151+
phone_country = "CA"
152+
conversion_action_id = "conv123"
153+
154+
# Call the main function from the add_call module
155+
add_call.main(
156+
mock_client_instance,
157+
customer_id,
158+
phone_number,
159+
phone_country,
160+
conversion_action_id,
161+
)
162+
163+
# Assert that add_call_asset was called correctly
164+
mock_add_call_asset.assert_called_once_with(
165+
mock_client_instance,
166+
customer_id,
167+
phone_number,
168+
phone_country,
169+
conversion_action_id,
170+
)
171+
# Assert that link_asset_to_account was called correctly with the mocked asset name
172+
mock_link_asset_to_account.assert_called_once_with(
173+
mock_client_instance, customer_id, "test_asset_resource_name"
174+
)
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import unittest
2+
from unittest.mock import MagicMock, patch
3+
4+
from examples.assets import add_hotel_callout
5+
6+
7+
class TestAddHotelCallout(unittest.TestCase):
8+
@patch("examples.assets.add_hotel_callout.GoogleAdsClient")
9+
def test_add_hotel_callout_assets(self, mock_google_ads_client_constructor):
10+
mock_client_instance = mock_google_ads_client_constructor.return_value
11+
mock_asset_service = MagicMock()
12+
mock_client_instance.get_service.return_value = mock_asset_service
13+
14+
mock_operations = [MagicMock(), MagicMock()]
15+
mock_client_instance.get_type.side_effect = mock_operations
16+
17+
customer_id = "test_customer_123"
18+
language_code = "en"
19+
expected_texts = ["Activities", "Facilities"]
20+
21+
mock_results = [
22+
MagicMock(resource_name=f"asset_rn_{i}")
23+
for i in range(len(expected_texts))
24+
]
25+
mock_asset_service.mutate_assets.return_value.results = mock_results
26+
27+
returned_resource_names = add_hotel_callout.add_hotel_callout_assets(
28+
mock_client_instance, customer_id, language_code
29+
)
30+
31+
self.assertEqual(
32+
mock_client_instance.get_type.call_count, len(expected_texts)
33+
)
34+
mock_client_instance.get_type.assert_called_with("AssetOperation")
35+
36+
for i, text in enumerate(expected_texts):
37+
asset_mock = mock_operations[i].create
38+
self.assertEqual(asset_mock.hotel_callout_asset.text, text)
39+
self.assertEqual(
40+
asset_mock.hotel_callout_asset.language_code, language_code
41+
)
42+
43+
mock_asset_service.mutate_assets.assert_called_once_with(
44+
customer_id=customer_id, operations=mock_operations
45+
)
46+
self.assertEqual(
47+
returned_resource_names, [res.resource_name for res in mock_results]
48+
)
49+
50+
@patch("examples.assets.add_hotel_callout.GoogleAdsClient")
51+
def test_link_asset_to_account(self, mock_google_ads_client_constructor):
52+
mock_client_instance = mock_google_ads_client_constructor.return_value
53+
mock_customer_asset_service = MagicMock()
54+
mock_client_instance.get_service.return_value = (
55+
mock_customer_asset_service
56+
)
57+
58+
mock_operations = [MagicMock(), MagicMock()]
59+
mock_client_instance.get_type.side_effect = mock_operations
60+
mock_client_instance.enums.AssetFieldTypeEnum.HOTEL_CALLOUT = (
61+
"HOTEL_CALLOUT_ENUM"
62+
)
63+
64+
customer_id = "test_customer_123"
65+
resource_names = ["asset_rn_0", "asset_rn_1"]
66+
67+
mock_results = [
68+
MagicMock(resource_name=f"ca_asset_rn_{i}")
69+
for i in range(len(resource_names))
70+
]
71+
mock_customer_asset_service.mutate_customer_assets.return_value.results = (
72+
mock_results
73+
)
74+
75+
add_hotel_callout.link_asset_to_account(
76+
mock_client_instance, customer_id, resource_names
77+
)
78+
79+
self.assertEqual(
80+
mock_client_instance.get_type.call_count, len(resource_names)
81+
)
82+
mock_client_instance.get_type.assert_called_with(
83+
"CustomerAssetOperation"
84+
)
85+
86+
for i, rn in enumerate(resource_names):
87+
customer_asset_mock = mock_operations[i].create
88+
self.assertEqual(customer_asset_mock.asset, rn)
89+
self.assertEqual(
90+
customer_asset_mock.field_type, "HOTEL_CALLOUT_ENUM"
91+
)
92+
93+
mock_customer_asset_service.mutate_customer_assets.assert_called_once_with(
94+
customer_id=customer_id, operations=mock_operations
95+
)
96+
97+
@patch("examples.assets.add_hotel_callout.link_asset_to_account")
98+
@patch("examples.assets.add_hotel_callout.add_hotel_callout_assets")
99+
def test_main_function_logic(self, mock_add_assets, mock_link_assets):
100+
mock_client_instance = MagicMock()
101+
customer_id = "test_customer_123"
102+
language_code = "fr"
103+
104+
mock_asset_resource_names = ["asset1", "asset2"]
105+
mock_add_assets.return_value = mock_asset_resource_names
106+
107+
add_hotel_callout.main(mock_client_instance, customer_id, language_code)
108+
109+
mock_add_assets.assert_called_once_with(
110+
mock_client_instance, customer_id, language_code
111+
)
112+
mock_link_assets.assert_called_once_with(
113+
mock_client_instance, customer_id, mock_asset_resource_names
114+
)

0 commit comments

Comments
 (0)