Skip to content

Commit 15643ea

Browse files
Jules was unable to complete the task in time. Please review the work done so far and provide feedback for Jules to continue.
1 parent 1fd89af commit 15643ea

7 files changed

+756
-0
lines changed

examples/advanced_operations/add_app_campaign.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ def create_app_ad(client, customer_id, ad_group_resource_name):
284284
"Ad Group App Ad created with resource name:"
285285
f'"{ad_group_ad_resource_name}".'
286286
)
287+
return ad_group_ad_resource_name
287288

288289

289290
def create_ad_text_asset(client, text):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This file intentionally left blank.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# Copyright 2020 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+
"""Tests for add_ad_customizer.py."""
15+
16+
from unittest import mock
17+
from unittest import TestCase
18+
19+
from google.ads.googleads.client import GoogleAdsClient
20+
from google.ads.googleads.errors import GoogleAdsException
21+
22+
from examples.advanced_operations.add_ad_customizer import main
23+
from examples.advanced_operations.add_ad_customizer import create_text_customizer_attribute
24+
from examples.advanced_operations.add_ad_customizer import create_price_customizer_attribute
25+
from examples.advanced_operations.add_ad_customizer import link_customizer_attributes
26+
from examples.advanced_operations.add_ad_customizer import create_ad_with_customizations
27+
28+
29+
_CUSTOMER_ID = "1234567890"
30+
_AD_GROUP_ID = "9876543210"
31+
_TEXT_CUSTOMIZER_NAME = "Planet_test"
32+
_PRICE_CUSTOMIZER_NAME = "Price_test"
33+
_TEXT_CUSTOMIZER_RESOURCE_NAME = "customers/1234567890/customizerAttributes/1"
34+
_PRICE_CUSTOMIZER_RESOURCE_NAME = "customers/1234567890/customizerAttributes/2"
35+
36+
37+
@mock.patch("examples.advanced_operations.add_ad_customizer.GoogleAdsClient.load_from_storage")
38+
class AddAdCustomizerTest(TestCase):
39+
def setUp(self):
40+
self.client_mock = mock.MagicMock(spec=GoogleAdsClient)
41+
# Mock the enums attribute
42+
self.client_mock.enums = mock.MagicMock()
43+
self.client_mock.enums.CustomizerAttributeTypeEnum = mock.MagicMock()
44+
self.client_mock.enums.ServedAssetFieldTypeEnum = mock.MagicMock()
45+
# It's also good practice to assign specific mock objects or values
46+
# if the code relies on specific enum members, for example:
47+
# self.client_mock.enums.CustomizerAttributeTypeEnum.TEXT = "TEXT_ENUM_VALUE"
48+
# self.client_mock.enums.CustomizerAttributeTypeEnum.PRICE = "PRICE_ENUM_VALUE"
49+
# self.client_mock.enums.ServedAssetFieldTypeEnum.HEADLINE_1 = "HEADLINE_1_ENUM_VALUE"
50+
# However, for now, MagicMock should suffice as the code seems to only access them.
51+
52+
self.googleads_service_mock = self.client_mock.get_service("GoogleAdsService")
53+
self.customizer_attribute_service_mock = self.client_mock.get_service("CustomizerAttributeService")
54+
self.ad_group_customizer_service_mock = self.client_mock.get_service("AdGroupCustomizerService")
55+
self.ad_group_ad_service_mock = self.client_mock.get_service("AdGroupAdService")
56+
57+
# Mock successful responses for service calls
58+
# This will be the default return value for mutate_customizer_attributes.
59+
# Individual tests that call it once will need to adjust this.
60+
self.customizer_attribute_service_mock.mutate_customizer_attributes.return_value.results = [
61+
mock.Mock(resource_name=_TEXT_CUSTOMIZER_RESOURCE_NAME),
62+
mock.Mock(resource_name=_PRICE_CUSTOMIZER_RESOURCE_NAME)
63+
]
64+
self.ad_group_customizer_service_mock.mutate_ad_group_customizers.return_value.results = [
65+
mock.Mock(resource_name="adGroupCustomizer1"),
66+
mock.Mock(resource_name="adGroupCustomizer2")
67+
]
68+
self.ad_group_ad_service_mock.mutate_ad_group_ads.return_value.results = [
69+
mock.Mock(resource_name="adGroupAd1")
70+
]
71+
72+
self.googleads_service_mock.ad_group_path.return_value = f"customers/{_CUSTOMER_ID}/adGroups/{_AD_GROUP_ID}"
73+
74+
75+
def test_create_text_customizer_attribute(self, mock_load_client):
76+
mock_load_client.return_value = self.client_mock
77+
# Adjust return_value for a single call to mutate_customizer_attributes
78+
self.customizer_attribute_service_mock.mutate_customizer_attributes.return_value.results = [
79+
mock.Mock(resource_name=_TEXT_CUSTOMIZER_RESOURCE_NAME)
80+
]
81+
82+
resource_name = create_text_customizer_attribute(
83+
self.client_mock, _CUSTOMER_ID, _TEXT_CUSTOMIZER_NAME
84+
)
85+
self.assertEqual(resource_name, _TEXT_CUSTOMIZER_RESOURCE_NAME)
86+
self.customizer_attribute_service_mock.mutate_customizer_attributes.assert_called_once()
87+
88+
89+
def test_create_price_customizer_attribute(self, mock_load_client):
90+
mock_load_client.return_value = self.client_mock
91+
# Adjust return_value for a single call to mutate_customizer_attributes
92+
self.customizer_attribute_service_mock.mutate_customizer_attributes.return_value.results = [
93+
mock.Mock(resource_name=_PRICE_CUSTOMIZER_RESOURCE_NAME)
94+
]
95+
96+
resource_name = create_price_customizer_attribute(
97+
self.client_mock, _CUSTOMER_ID, _PRICE_CUSTOMIZER_NAME
98+
)
99+
self.assertEqual(resource_name, _PRICE_CUSTOMIZER_RESOURCE_NAME)
100+
self.customizer_attribute_service_mock.mutate_customizer_attributes.assert_called_once()
101+
102+
103+
def test_link_customizer_attributes(self, mock_load_client):
104+
mock_load_client.return_value = self.client_mock
105+
link_customizer_attributes(
106+
self.client_mock,
107+
_CUSTOMER_ID,
108+
_AD_GROUP_ID,
109+
_TEXT_CUSTOMIZER_RESOURCE_NAME,
110+
_PRICE_CUSTOMIZER_RESOURCE_NAME,
111+
)
112+
self.ad_group_customizer_service_mock.mutate_ad_group_customizers.assert_called_once()
113+
114+
115+
def test_create_ad_with_customizations(self, mock_load_client):
116+
mock_load_client.return_value = self.client_mock
117+
create_ad_with_customizations(
118+
self.client_mock,
119+
_CUSTOMER_ID,
120+
_AD_GROUP_ID,
121+
_TEXT_CUSTOMIZER_NAME,
122+
_PRICE_CUSTOMIZER_NAME,
123+
)
124+
self.ad_group_ad_service_mock.mutate_ad_group_ads.assert_called_once()
125+
126+
127+
@mock.patch("examples.advanced_operations.add_ad_customizer.create_text_customizer_attribute")
128+
@mock.patch("examples.advanced_operations.add_ad_customizer.create_price_customizer_attribute")
129+
@mock.patch("examples.advanced_operations.add_ad_customizer.link_customizer_attributes")
130+
@mock.patch("examples.advanced_operations.add_ad_customizer.create_ad_with_customizations")
131+
def test_main_success(
132+
self,
133+
mock_create_ad,
134+
mock_link_attributes,
135+
mock_create_price_attr,
136+
mock_create_text_attr,
137+
mock_load_client
138+
):
139+
mock_load_client.return_value = self.client_mock
140+
mock_create_text_attr.return_value = _TEXT_CUSTOMIZER_RESOURCE_NAME
141+
mock_create_price_attr.return_value = _PRICE_CUSTOMIZER_RESOURCE_NAME
142+
143+
main(self.client_mock, _CUSTOMER_ID, _AD_GROUP_ID)
144+
145+
mock_create_text_attr.assert_called_once_with(
146+
self.client_mock, _CUSTOMER_ID, mock.ANY
147+
)
148+
mock_create_price_attr.assert_called_once_with(
149+
self.client_mock, _CUSTOMER_ID, mock.ANY
150+
)
151+
mock_link_attributes.assert_called_once_with(
152+
self.client_mock,
153+
_CUSTOMER_ID,
154+
_AD_GROUP_ID,
155+
_TEXT_CUSTOMIZER_RESOURCE_NAME,
156+
_PRICE_CUSTOMIZER_RESOURCE_NAME,
157+
)
158+
mock_create_ad.assert_called_once_with(
159+
self.client_mock,
160+
_CUSTOMER_ID,
161+
_AD_GROUP_ID,
162+
mock.ANY,
163+
mock.ANY,
164+
)
165+
166+
@mock.patch("examples.advanced_operations.add_ad_customizer.create_text_customizer_attribute")
167+
def test_main_google_ads_exception(
168+
self, mock_create_text_attr, mock_load_client
169+
):
170+
# We don't need mock_load_client here as main directly uses the passed client
171+
# However, it's included by the class-level decorator, so we accept it.
172+
mock_failure = mock.Mock()
173+
mock_failure.errors = [mock.Mock(message="Test Error")]
174+
# Add a mock for the 'call' attribute if it's accessed by GoogleAdsException
175+
mock_call = mock.Mock()
176+
177+
mock_create_text_attr.side_effect = GoogleAdsException(
178+
error=mock.Mock(),
179+
failure=mock_failure,
180+
call=mock_call, # Add the missing 'call' argument
181+
request_id="test_request_id"
182+
)
183+
184+
# The main function in the script catches the exception and calls sys.exit(1)
185+
# and prints to stdout. For this test, we'll assert that the exception is raised
186+
# when calling main with our mock client.
187+
with self.assertRaises(GoogleAdsException):
188+
main(self.client_mock, _CUSTOMER_ID, _AD_GROUP_ID)
189+
190+
# It's good practice to also create an __init__.py in the tests directory
191+
# if it doesn't exist, to make it a Python package.
192+
# This subtask will also create that file.
193+
# Create the file examples/advanced_operations/tests/__init__.py if it doesn't exist.
194+
# If it exists, leave it as is.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright 2020 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+
"""Tests for add_ad_group_bid_modifier.py."""
15+
16+
from unittest import mock
17+
from unittest import TestCase
18+
19+
from google.ads.googleads.client import GoogleAdsClient
20+
from google.ads.googleads.errors import GoogleAdsException
21+
# Attempt to import DeviceEnum from the versioned enum path
22+
from google.ads.googleads.v20.enums import DeviceEnum
23+
24+
from examples.advanced_operations.add_ad_group_bid_modifier import main
25+
26+
27+
_CUSTOMER_ID = "1234567890"
28+
_AD_GROUP_ID = "9876543210"
29+
_BID_MODIFIER_VALUE = 1.5
30+
# Using the known value for DeviceEnum.MOBILE (which is 3) directly
31+
# to avoid issues with enum access at module load time.
32+
# DeviceEnum.MOBILE.value is 3.
33+
_MODIFIER_RESOURCE_NAME = (
34+
f"customers/{_CUSTOMER_ID}/adGroupBidModifiers/"
35+
f"{_AD_GROUP_ID}~3"
36+
)
37+
38+
39+
@mock.patch("examples.advanced_operations.add_ad_group_bid_modifier.GoogleAdsClient.load_from_storage")
40+
class AddAdGroupBidModifierTest(TestCase):
41+
def setUp(self):
42+
self.client_mock = mock.MagicMock(spec=GoogleAdsClient)
43+
self.ad_group_service_mock = self.client_mock.get_service("AdGroupService")
44+
self.ad_group_bm_service_mock = self.client_mock.get_service("AdGroupBidModifierService")
45+
46+
# Mock the ad group path
47+
self.ad_group_service_mock.ad_group_path.return_value = (
48+
f"customers/{_CUSTOMER_ID}/adGroups/{_AD_GROUP_ID}"
49+
)
50+
51+
# Mock the response from mutate_ad_group_bid_modifiers
52+
mock_result = mock.Mock()
53+
mock_result.resource_name = _MODIFIER_RESOURCE_NAME
54+
self.ad_group_bm_service_mock.mutate_ad_group_bid_modifiers.return_value.results = [
55+
mock_result
56+
]
57+
58+
# Mock enums
59+
self.client_mock.enums = mock.MagicMock() # Ensure enums attribute exists
60+
# Create a mock for the DeviceEnum type itself
61+
mock_device_enum = mock.MagicMock()
62+
# Set the MOBILE attribute on this mock to return the integer value 3
63+
mock_device_enum.MOBILE = 3
64+
self.client_mock.enums.DeviceEnum = mock_device_enum
65+
66+
67+
def test_main_success(self, mock_load_client):
68+
mock_load_client.return_value = self.client_mock
69+
70+
main(self.client_mock, _CUSTOMER_ID, _AD_GROUP_ID, _BID_MODIFIER_VALUE)
71+
72+
self.ad_group_service_mock.ad_group_path.assert_called_once_with(
73+
_CUSTOMER_ID, _AD_GROUP_ID
74+
)
75+
76+
# Check that mutate_ad_group_bid_modifiers was called
77+
self.ad_group_bm_service_mock.mutate_ad_group_bid_modifiers.assert_called_once()
78+
79+
# Get the actual operation passed to the mock
80+
call_args = self.ad_group_bm_service_mock.mutate_ad_group_bid_modifiers.call_args
81+
operation = call_args[1]["operations"][0].create # Accessing create from AdGroupBidModifierOperation
82+
83+
self.assertEqual(
84+
operation.ad_group,
85+
f"customers/{_CUSTOMER_ID}/adGroups/{_AD_GROUP_ID}",
86+
)
87+
self.assertEqual(operation.bid_modifier, _BID_MODIFIER_VALUE)
88+
# Assert against the integer value since DeviceEnum.MOBILE might be problematic
89+
self.assertEqual(operation.device.type_, 3)
90+
91+
92+
def test_main_google_ads_exception(self, mock_load_client):
93+
mock_load_client.return_value = self.client_mock
94+
95+
# Configure the service mock to raise GoogleAdsException
96+
self.ad_group_bm_service_mock.mutate_ad_group_bid_modifiers.side_effect = GoogleAdsException(
97+
error=mock.Mock(),
98+
failure=mock.Mock(errors=[mock.Mock(message="Test Error")]),
99+
request_id="test_request_id",
100+
call=mock.Mock() # Added missing call argument
101+
)
102+
103+
with self.assertRaises(GoogleAdsException):
104+
main(self.client_mock, _CUSTOMER_ID, _AD_GROUP_ID, _BID_MODIFIER_VALUE)
105+
106+
if __name__ == "__main__":
107+
TestCase.main()

0 commit comments

Comments
 (0)