Skip to content

Commit 55a6627

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 eb4a0a8 commit 55a6627

6 files changed

Lines changed: 2004 additions & 0 deletions

examples/travel/tests/__init__.py

Whitespace-only changes.
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
import unittest
2+
from unittest import mock
3+
import sys
4+
5+
# Adjust path to import the script under test.
6+
# This assumes the test is run from the root of the repository.
7+
# If examples/travel/ is not in PYTHONPATH, this might be an issue when running the test directly.
8+
# However, for the purpose of this environment, direct import should work if the structure is as expected.
9+
from examples.travel import add_hotel_ad
10+
# The script uses argparse, so we might need it.
11+
import argparse
12+
import runpy # For running the script's __main__ block
13+
14+
class TestAddHotelAd(unittest.TestCase):
15+
16+
def setUp(self):
17+
# Mock GoogleAdsClient instance
18+
# Use spec=True to ensure that the mock only allows attributes/methods that exist on the real class
19+
self.mock_google_ads_client = mock.Mock(spec=add_hotel_ad.GoogleAdsClient)
20+
21+
# Mock services
22+
self.mock_campaign_budget_service = mock.Mock()
23+
self.mock_campaign_service = mock.Mock()
24+
self.mock_ad_group_service = mock.Mock()
25+
self.mock_ad_group_ad_service = mock.Mock()
26+
27+
# Configure get_service on the client mock
28+
def get_service_side_effect(service_name, version=None): # Add version to match real signature if used
29+
if service_name == "CampaignBudgetService":
30+
return self.mock_campaign_budget_service
31+
elif service_name == "CampaignService":
32+
return self.mock_campaign_service
33+
elif service_name == "AdGroupService":
34+
return self.mock_ad_group_service
35+
elif service_name == "AdGroupAdService":
36+
return self.mock_ad_group_ad_service
37+
return mock.DEFAULT
38+
self.mock_google_ads_client.get_service.side_effect = get_service_side_effect
39+
40+
# Mock enums (values should match what the script expects)
41+
self.mock_google_ads_client.enums = mock.Mock()
42+
# Replicate enum structure more closely if needed, e.g. self.mock_google_ads_client.enums.BudgetDeliveryMethodEnum = mock.Mock()
43+
# Then self.mock_google_ads_client.enums.BudgetDeliveryMethodEnum.STANDARD = "STANDARD_ENUM_VALUE"
44+
# For simplicity, direct assignment if the script accesses them like client.enums.BudgetDeliveryMethodEnum.STANDARD
45+
self.mock_google_ads_client.enums.BudgetDeliveryMethodEnum = mock.Mock()
46+
self.mock_google_ads_client.enums.BudgetDeliveryMethodEnum.STANDARD = "STANDARD"
47+
48+
self.mock_google_ads_client.enums.AdvertisingChannelTypeEnum = mock.Mock()
49+
self.mock_google_ads_client.enums.AdvertisingChannelTypeEnum.HOTEL = "HOTEL"
50+
51+
self.mock_google_ads_client.enums.CampaignStatusEnum = mock.Mock()
52+
self.mock_google_ads_client.enums.CampaignStatusEnum.PAUSED = "PAUSED"
53+
54+
self.mock_google_ads_client.enums.AdGroupStatusEnum = mock.Mock()
55+
self.mock_google_ads_client.enums.AdGroupStatusEnum.ENABLED = "ENABLED"
56+
57+
self.mock_google_ads_client.enums.AdGroupTypeEnum = mock.Mock()
58+
self.mock_google_ads_client.enums.AdGroupTypeEnum.HOTEL_ADS = "HOTEL_ADS"
59+
60+
self.mock_google_ads_client.enums.AdGroupAdStatusEnum = mock.Mock()
61+
self.mock_google_ads_client.enums.AdGroupAdStatusEnum.ENABLED = "ENABLED"
62+
63+
self.mock_hotel_ad_info_type = mock.Mock(name="HotelAdInfoType")
64+
65+
# Mock get_type to return mock operation objects
66+
def get_type_side_effect(type_name, version=None): # Add version to match real signature if used
67+
mock_operation_container = mock.Mock(name=f"{type_name}_OperationContainer")
68+
# The actual object to be populated is typically operation.create or operation.update
69+
# We make the .create attribute a separate mock to inspect it.
70+
created_object_mock = mock.Mock(name=f"{type_name}_Create")
71+
72+
# If the type_name is for an entity that will be directly manipulated (e.g. campaign.hotel_setting)
73+
# we might need to pre-populate it if it's accessed before being set.
74+
# Example: campaign.hotel_setting.hotel_center_id = ...
75+
# So, campaign_operation.create.hotel_setting needs to be a mock that can have hotel_center_id set.
76+
if type_name == "CampaignOperation":
77+
created_object_mock.hotel_setting = mock.Mock()
78+
created_object_mock.percent_cpc = mock.Mock()
79+
created_object_mock.network_settings = mock.Mock()
80+
elif type_name == "AdGroupAdOperation":
81+
# ad_group_ad.ad.hotel_ad is used with copy_from
82+
created_object_mock.ad = mock.Mock()
83+
created_object_mock.ad.hotel_ad = mock.Mock(name="HotelAdOnAdGroupAd")
84+
85+
mock_operation_container.create = created_object_mock
86+
87+
if type_name == "HotelAdInfo":
88+
return self.mock_hotel_ad_info_type
89+
return mock_operation_container
90+
self.mock_google_ads_client.get_type.side_effect = get_type_side_effect
91+
92+
self.mock_google_ads_client.copy_from = mock.Mock()
93+
94+
self.expected_budget_resource_name = "customers/test_customer_id/campaignBudgets/budget123"
95+
self.expected_campaign_resource_name = "customers/test_customer_id/campaigns/campaign456"
96+
self.expected_ad_group_resource_name = "customers/test_customer_id/adGroups/adgroup789"
97+
self.expected_ad_group_ad_resource_name = "customers/test_customer_id/adGroupAds/adgroupad012"
98+
99+
mock_budget_response = mock.Mock()
100+
mock_budget_response.results = [mock.Mock()]
101+
mock_budget_response.results[0].resource_name = self.expected_budget_resource_name
102+
self.mock_campaign_budget_service.mutate_campaign_budgets.return_value = mock_budget_response
103+
104+
mock_campaign_response = mock.Mock()
105+
mock_campaign_response.results = [mock.Mock()]
106+
mock_campaign_response.results[0].resource_name = self.expected_campaign_resource_name
107+
self.mock_campaign_service.mutate_campaigns.return_value = mock_campaign_response
108+
109+
mock_ad_group_response = mock.Mock()
110+
mock_ad_group_response.results = [mock.Mock()]
111+
mock_ad_group_response.results[0].resource_name = self.expected_ad_group_resource_name
112+
self.mock_ad_group_service.mutate_ad_groups.return_value = mock_ad_group_response
113+
114+
mock_ad_group_ad_response = mock.Mock()
115+
mock_ad_group_ad_response.results = [mock.Mock()]
116+
mock_ad_group_ad_response.results[0].resource_name = self.expected_ad_group_ad_resource_name
117+
self.mock_ad_group_ad_service.mutate_ad_group_ads.return_value = mock_ad_group_ad_response
118+
119+
120+
@mock.patch('examples.travel.add_hotel_ad.main')
121+
@mock.patch('examples.travel.add_hotel_ad.GoogleAdsClient.load_from_storage')
122+
def test_google_ads_client_load(self, mock_load_from_storage, mock_main_function_in_script):
123+
"""Tests that GoogleAdsClient.load_from_storage is called correctly
124+
and the loaded client is passed to the script's main function.
125+
"""
126+
mock_google_ads_client_instance = mock.Mock()
127+
mock_load_from_storage.return_value = mock_google_ads_client_instance
128+
original_argv = sys.argv
129+
test_argv = [
130+
'add_hotel_ad.py',
131+
'--customer_id', 'test_customer_id_val',
132+
'--hotel_center_account_id', '12345',
133+
'--cpc_bid_ceiling_micro_amount', '1000000'
134+
]
135+
sys.argv = test_argv
136+
try:
137+
runpy.run_module('examples.travel.add_hotel_ad', run_name='__main__', alter_sys=True)
138+
finally:
139+
sys.argv = original_argv
140+
mock_load_from_storage.assert_called_once_with(version="v19")
141+
mock_main_function_in_script.assert_called_once_with(
142+
mock_google_ads_client_instance,
143+
'test_customer_id_val',
144+
12345,
145+
1000000
146+
)
147+
148+
@mock.patch('examples.travel.add_hotel_ad.main')
149+
@mock.patch('examples.travel.add_hotel_ad.GoogleAdsClient.load_from_storage')
150+
def test_argument_parsing(self, mock_load_from_storage, mock_main_function_in_script):
151+
"""Tests that command-line arguments are parsed and passed to main correctly."""
152+
mock_google_ads_client_instance = mock.Mock()
153+
mock_load_from_storage.return_value = mock_google_ads_client_instance
154+
expected_customer_id = "test_customer_999"
155+
expected_hotel_id = 777
156+
expected_cpc_bid = 200000
157+
original_argv = sys.argv
158+
test_argv = [
159+
'add_hotel_ad.py',
160+
'--customer_id', expected_customer_id,
161+
'--hotel_center_account_id', str(expected_hotel_id),
162+
'--cpc_bid_ceiling_micro_amount', str(expected_cpc_bid)
163+
]
164+
sys.argv = test_argv
165+
try:
166+
runpy.run_module('examples.travel.add_hotel_ad', run_name='__main__', alter_sys=True)
167+
finally:
168+
sys.argv = original_argv
169+
mock_load_from_storage.assert_called_once_with(version="v19")
170+
mock_main_function_in_script.assert_called_once_with(
171+
mock_google_ads_client_instance,
172+
expected_customer_id,
173+
expected_hotel_id,
174+
expected_cpc_bid
175+
)
176+
177+
@mock.patch('builtins.print')
178+
def test_main_function_logic_and_api_calls(self, mock_print):
179+
customer_id = "test_customer_id"
180+
hotel_center_account_id = 98765
181+
cpc_bid_ceiling_micro_amount = 2000000
182+
183+
add_hotel_ad.main(
184+
self.mock_google_ads_client,
185+
customer_id,
186+
hotel_center_account_id,
187+
cpc_bid_ceiling_micro_amount
188+
)
189+
190+
expected_get_service_calls = [
191+
mock.call("CampaignBudgetService"),
192+
mock.call("CampaignService"),
193+
mock.call("AdGroupService"),
194+
mock.call("AdGroupAdService"),
195+
]
196+
self.assertEqual(self.mock_google_ads_client.get_service.call_args_list, expected_get_service_calls)
197+
198+
self.mock_campaign_budget_service.mutate_campaign_budgets.assert_called_once()
199+
args, kwargs = self.mock_campaign_budget_service.mutate_campaign_budgets.call_args
200+
self.assertEqual(kwargs['customer_id'], customer_id)
201+
budget_operation = kwargs['operations'][0]
202+
self.assertTrue(hasattr(budget_operation.create, 'name'))
203+
self.assertEqual(budget_operation.create.delivery_method, self.mock_google_ads_client.enums.BudgetDeliveryMethodEnum.STANDARD)
204+
self.assertEqual(budget_operation.create.amount_micros, 500000)
205+
206+
self.mock_campaign_service.mutate_campaigns.assert_called_once()
207+
args, kwargs = self.mock_campaign_service.mutate_campaigns.call_args
208+
self.assertEqual(kwargs['customer_id'], customer_id)
209+
campaign_operation = kwargs['operations'][0]
210+
self.assertTrue(hasattr(campaign_operation.create, 'name'))
211+
self.assertEqual(campaign_operation.create.advertising_channel_type, self.mock_google_ads_client.enums.AdvertisingChannelTypeEnum.HOTEL)
212+
self.assertEqual(campaign_operation.create.hotel_setting.hotel_center_id, hotel_center_account_id)
213+
self.assertEqual(campaign_operation.create.status, self.mock_google_ads_client.enums.CampaignStatusEnum.PAUSED)
214+
self.assertEqual(campaign_operation.create.percent_cpc.cpc_bid_ceiling_micros, cpc_bid_ceiling_micro_amount)
215+
self.assertEqual(campaign_operation.create.campaign_budget, self.expected_budget_resource_name)
216+
self.assertTrue(campaign_operation.create.network_settings.target_google_search)
217+
218+
self.mock_ad_group_service.mutate_ad_groups.assert_called_once()
219+
args, kwargs = self.mock_ad_group_service.mutate_ad_groups.call_args
220+
self.assertEqual(kwargs['customer_id'], customer_id)
221+
ad_group_operation = kwargs['operations'][0]
222+
self.assertTrue(hasattr(ad_group_operation.create, 'name'))
223+
self.assertEqual(ad_group_operation.create.status, self.mock_google_ads_client.enums.AdGroupStatusEnum.ENABLED)
224+
self.assertEqual(ad_group_operation.create.campaign, self.expected_campaign_resource_name)
225+
self.assertEqual(ad_group_operation.create.type_, self.mock_google_ads_client.enums.AdGroupTypeEnum.HOTEL_ADS)
226+
self.assertEqual(ad_group_operation.create.cpc_bid_micros, 10000000)
227+
228+
self.mock_ad_group_ad_service.mutate_ad_group_ads.assert_called_once()
229+
args, kwargs = self.mock_ad_group_ad_service.mutate_ad_group_ads.call_args
230+
self.assertEqual(kwargs['customer_id'], customer_id)
231+
ad_group_ad_operation = kwargs['operations'][0]
232+
self.assertEqual(ad_group_ad_operation.create.ad_group, self.expected_ad_group_resource_name)
233+
self.assertEqual(ad_group_ad_operation.create.status, self.mock_google_ads_client.enums.AdGroupAdStatusEnum.ENABLED)
234+
235+
# Check that the 'create' object's ad.hotel_ad attribute was passed to copy_from
236+
self.mock_google_ads_client.copy_from.assert_called_once_with(
237+
ad_group_ad_operation.create.ad.hotel_ad,
238+
self.mock_hotel_ad_info_type
239+
)
240+
241+
expected_print_calls = [
242+
mock.call(f"Created budget with resource name '{self.expected_budget_resource_name}'."),
243+
mock.call(f"Added a hotel campaign with resource name '{self.expected_campaign_resource_name}'."),
244+
mock.call(f"Added a hotel ad group with resource name '{self.expected_ad_group_resource_name}'."),
245+
mock.call(f"Created hotel ad with resource name '{self.expected_ad_group_ad_resource_name}'."),
246+
]
247+
self.assertEqual(mock_print.call_args_list, expected_print_calls)
248+
249+
250+
if __name__ == "__main__":
251+
unittest.main()

0 commit comments

Comments
 (0)