Skip to content

Commit f9b6e68

Browse files
fix: Correct test failures in add_demand_gen_campaign and add_smart_campaign
- Modified test_add_demand_gen_campaign.py to pass the correct number of arguments to the main() function, resolving a TypeError. - Updated test_add_smart_campaign.py to address "malformed KeywordTheme" ValueErrors: - Ensured that KeywordTheme mock objects returned by SmartCampaignSuggestService and constructed from KeywordThemeConstantService results correctly simulate the protobuf `oneof` behavior (either `keyword_theme_constant` or `free_form_keyword_theme` is set). - Adjusted mocks for `SmartCampaignSuggestService.suggest_keyword_themes` and `KeywordThemeConstantService.suggest_keyword_theme_constants` to provide well-structured mock responses. - Corrected the expected number of campaign criteria in the GoogleAdsService.mutate mock to align with the number of keyword themes generated by the refined mocks.
1 parent 92b120e commit f9b6e68

2 files changed

Lines changed: 87 additions & 27 deletions

File tree

examples/advanced_operations/tests/test_add_demand_gen_campaign.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def test_main_runs_successfully(
7979
main(
8080
mock_google_ads_client,
8181
mock_customer_id, # customer_id
82-
mock_customer_id, # manager_customer_id (assuming same for this test)
82+
# mock_customer_id, # manager_customer_id (assuming same for this test) # This was the incorrect argument
8383
mock_video_id
8484
)
8585
except Exception as e:

examples/advanced_operations/tests/test_add_smart_campaign.py

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,57 @@ def test_main_runs_successfully(mock_uuid4: MagicMock, mock_google_ads_client: M
2424
# Suggest Keyword Themes
2525
mock_kw_theme_response = MagicMock()
2626
mock_kw_theme1 = MagicMock()
27+
mock_kw_theme1.keyword_theme_constant = MagicMock() # Simulate protobuf message structure
2728
mock_kw_theme1.keyword_theme_constant.resource_name = "keywordThemeConstants/theme1"
28-
mock_kw_theme1.keyword_theme_constant.name = "Theme 1"
29+
# Ensure 'oneof' behavior; if keyword_theme_constant is set, free_form_keyword_theme is not.
30+
# MagicMock by default would create these attributes if accessed.
31+
# Explicitly deleting or ensuring they are None if not the active 'oneof' field.
32+
# However, the script checks hasattr(theme, "keyword_theme_constant"), so direct assignment is enough.
33+
2934
mock_kw_theme2 = MagicMock()
35+
mock_kw_theme2.keyword_theme_constant = MagicMock()
3036
mock_kw_theme2.keyword_theme_constant.resource_name = "keywordThemeConstants/theme2"
31-
mock_kw_theme2.keyword_theme_constant.name = "Theme 2"
37+
3238
mock_kw_theme_response.keyword_themes = [mock_kw_theme1, mock_kw_theme2]
33-
# Suggestion for free-form keyword
34-
mock_free_form_suggestion = MagicMock()
35-
mock_free_form_suggestion.keyword_theme_constant.resource_name = "keywordThemeConstants/freeform"
36-
mock_free_form_suggestion.keyword_theme_constant.name = "FreeForm Theme"
3739

40+
# Suggestion for free-form keyword (this path is hit by the script)
41+
# This mock should return a KeywordTheme message where free_form_keyword_theme is set.
42+
mock_free_form_response = MagicMock()
43+
mock_free_form_theme = MagicMock()
44+
mock_free_form_theme.free_form_keyword_theme = "free form keyword theme text" # This is the actual text value
45+
# Ensure 'oneof' behavior for this mock too
46+
delattr(mock_free_form_theme, 'keyword_theme_constant')
47+
mock_free_form_response.keyword_themes = [mock_free_form_theme]
48+
3849
# Side effect to handle different request types for suggest_keyword_themes
3950
def suggest_keyword_themes_side_effect(request):
4051
if request.suggestion_info.free_form_text:
41-
response = MagicMock()
42-
response.keyword_themes = [mock_free_form_suggestion]
43-
return response
44-
return mock_kw_theme_response
45-
mock_suggest_service.suggest_keyword_themes.side_effect = suggest_keyword_themes_side_effect
52+
return mock_free_form_response
53+
# This case is for request.suggestion_info.keyword_seed
54+
# The script does not use this path for suggest_keyword_themes.
55+
# It uses suggest_keyword_theme_constants for keyword_text.
56+
# However, to be safe, if it were called with keyword_seed:
57+
# return mock_kw_theme_response # This was the previous behavior
58+
# For now, let's assume it's not called with keyword_seed for suggest_keyword_themes
59+
# and the free_form_text path is the only one used by suggest_keyword_themes.
60+
# If suggest_keyword_themes is called for the seed, it should also return KeywordThemes.
61+
# The script calls:
62+
# 1. suggest_keyword_theme_constants with keyword_text (mocked below)
63+
# 2. suggest_keyword_themes with free_form_keyword_text (mocked by mock_free_form_response)
64+
# So, the keyword_seed path for suggest_keyword_themes is actually NOT hit.
65+
# Let's simplify the side_effect or remove it if only one path is hit.
66+
# Re-checking script: _get_keyword_theme_infos calls suggest_keyword_themes for free_form_keyword_text
67+
# and _get_keyword_theme_auto_suggestions calls suggest_keyword_theme_constants for keyword_text
68+
# This means the side_effect for suggest_keyword_themes only needs to handle the free_form_text case.
69+
if hasattr(request.suggestion_info, "free_form_text") and request.suggestion_info.free_form_text:
70+
return mock_free_form_response
71+
# Fallback if called unexpectedly (should not happen based on script logic)
72+
empty_response = MagicMock()
73+
empty_response.keyword_themes = []
74+
return empty_response
4675

76+
mock_suggest_service.suggest_keyword_themes.side_effect = suggest_keyword_themes_side_effect
77+
4778
# Suggest Budget Options
4879
mock_budget_options_response = MagicMock()
4980
mock_recommended_budget = MagicMock()
@@ -67,8 +98,18 @@ def suggest_keyword_themes_side_effect(request):
6798

6899
# KeywordThemeConstantService
69100
mock_ktc_service = mock_google_ads_client.get_service("KeywordThemeConstantService")
101+
# Mock for suggest_keyword_theme_constants (called by _get_keyword_theme_auto_suggestions)
102+
mock_ktc_suggest_response = MagicMock()
103+
mock_ktc_constant1_from_suggest = MagicMock() # Simulates KeywordThemeConstant
104+
mock_ktc_constant1_from_suggest.resource_name = "keywordThemeConstants/from_text_search1"
105+
# mock_ktc_constant1_from_suggest.name = "From Text Search 1" # Name is not directly used by script
106+
mock_ktc_suggest_response.keyword_theme_constants = [mock_ktc_constant1_from_suggest]
107+
mock_ktc_service.suggest_keyword_theme_constants.return_value = mock_ktc_suggest_response
108+
# Mock for keyword_theme_constant_path (used by _get_keyword_theme_infos if keyword_theme_ids are present)
109+
# This path is not hit with current main() args as keyword_theme_ids is not passed.
70110
mock_ktc_service.keyword_theme_constant_path.side_effect = lambda ktc_id: f"keywordThemeConstants/{ktc_id}"
71111

112+
72113
# GeoTargetConstantService
73114
mock_geo_service = mock_google_ads_client.get_service("GeoTargetConstantService")
74115
# The script uses a hardcoded geo target "2840" (USA)
@@ -93,8 +134,11 @@ def suggest_keyword_themes_side_effect(request):
93134
responses.append(MagicMock(smart_campaign_setting_result=MagicMock(resource_name=f"customers/{mock_customer_id}/smartCampaignSettings/scs1")))
94135
responses.append(MagicMock(ad_group_result=MagicMock(resource_name=f"customers/{mock_customer_id}/adGroups/adgroup1")))
95136
responses.append(MagicMock(ad_group_ad_result=MagicMock(resource_name=f"customers/{mock_customer_id}/adGroupAds/ad1")))
96-
# For keyword theme criteria (2 from suggestion + 1 free form) and 1 location criterion
97-
for i in range(4): # 3 keyword themes + 1 location
137+
# For keyword theme criteria:
138+
# 1 from suggest_keyword_theme_constants (mock_ktc_constant1_from_suggest)
139+
# 1 from suggest_keyword_themes (mock_free_form_theme)
140+
# Total 2 keyword theme criteria + 1 location criterion
141+
for i in range(3):
98142
responses.append(MagicMock(campaign_criterion_result=MagicMock(resource_name=f"customers/{mock_customer_id}/campaignCriteria/crit{i}")))
99143

100144
mock_mutate_response.mutate_operation_responses = responses
@@ -137,16 +181,22 @@ def test_main_with_business_location_runs_successfully(mock_uuid4_biz: MagicMock
137181
# Most mocks can be similar to the first test, just ensure business_profile_location_path is set up
138182
# SmartCampaignSuggestService
139183
mock_suggest_service = mock_google_ads_client.get_service("SmartCampaignSuggestService")
140-
mock_kw_theme_response = MagicMock() # Define as in first test
141-
mock_kw_theme1 = MagicMock()
142-
mock_kw_theme1.keyword_theme_constant.resource_name = "keywordThemeConstants/theme_biz1"
143-
mock_kw_theme1.keyword_theme_constant.name = "Theme Biz 1"
144-
mock_kw_theme_response.keyword_themes = [mock_kw_theme1]
145-
def suggest_keyword_themes_side_effect_biz(request): # Simplified for this test variation
146-
return mock_kw_theme_response
147-
mock_suggest_service.suggest_keyword_themes.side_effect = suggest_keyword_themes_side_effect_biz
148-
149-
mock_budget_options_response = MagicMock() # Define as in first test
184+
185+
# Mock for suggest_keyword_themes (called for free_form_keyword_text if provided)
186+
# In this test, free_form_keyword_text is None, so suggest_keyword_themes might not be called,
187+
# or if called, request.suggestion_info.free_form_text will be empty.
188+
# The script's _get_keyword_theme_infos checks if free_form_keyword_text:
189+
# if free_form_keyword_text:
190+
# response = smart_campaign_suggest_service.suggest_keyword_themes(request)
191+
# keyword_theme_infos.extend(map_keyword_themes_to_keyword_infos(response.keyword_themes))
192+
# So, if free_form_keyword_text is None, suggest_keyword_themes is NOT called.
193+
# Thus, we don't need to mock its response extensively for this specific test case path.
194+
# However, if it were called, it should return an empty list or correctly structured mock.
195+
mock_empty_kw_theme_response = MagicMock()
196+
mock_empty_kw_theme_response.keyword_themes = []
197+
mock_suggest_service.suggest_keyword_themes.return_value = mock_empty_kw_theme_response # Default for this test path
198+
199+
mock_budget_options_response = MagicMock()
150200
mock_recommended_budget = MagicMock()
151201
mock_recommended_budget.daily_amount_micros = 55000000
152202
mock_budget_options_response.recommended_daily_budget_options.high.daily_amount_micros = 65000000
@@ -166,6 +216,13 @@ def suggest_keyword_themes_side_effect_biz(request): # Simplified for this test
166216
mock_suggest_service.suggest_smart_campaign_ad.return_value = mock_ad_suggestion_response
167217

168218
mock_ktc_service = mock_google_ads_client.get_service("KeywordThemeConstantService")
219+
# Mock for suggest_keyword_theme_constants (called by _get_keyword_theme_auto_suggestions for keyword_text)
220+
mock_ktc_suggest_response_biz = MagicMock()
221+
mock_ktc_constant1_biz = MagicMock() # Simulates KeywordThemeConstant
222+
mock_ktc_constant1_biz.resource_name = "keywordThemeConstants/from_text_search_biz1"
223+
mock_ktc_suggest_response_biz.keyword_theme_constants = [mock_ktc_constant1_biz]
224+
mock_ktc_service.suggest_keyword_theme_constants.return_value = mock_ktc_suggest_response_biz
225+
# Mock for keyword_theme_constant_path (not hit with current args)
169226
mock_ktc_service.keyword_theme_constant_path.side_effect = lambda ktc_id: f"keywordThemeConstants/{ktc_id}"
170227

171228
mock_geo_service = mock_google_ads_client.get_service("GeoTargetConstantService")
@@ -184,7 +241,10 @@ def suggest_keyword_themes_side_effect_biz(request): # Simplified for this test
184241
responses.append(MagicMock(smart_campaign_setting_result=MagicMock(resource_name=f"customers/{mock_customer_id}/smartCampaignSettings/scs_biz")))
185242
responses.append(MagicMock(ad_group_result=MagicMock(resource_name=f"customers/{mock_customer_id}/adGroups/adgroup_biz")))
186243
responses.append(MagicMock(ad_group_ad_result=MagicMock(resource_name=f"customers/{mock_customer_id}/adGroupAds/ad_biz")))
187-
# Fewer keyword themes in this simplified version for biz profile path (1 suggested + 0 free-form) + 1 location
244+
# Keyword themes for criteria:
245+
# 1 from suggest_keyword_theme_constants (mock_ktc_constant1_biz)
246+
# 0 from suggest_keyword_themes (because free_form_keyword_text is None)
247+
# Total 1 keyword theme criterion + 1 location criterion
188248
for i in range(2):
189249
responses.append(MagicMock(campaign_criterion_result=MagicMock(resource_name=f"customers/{mock_customer_id}/campaignCriteria/crit_biz{i}")))
190250
mock_mutate_response.mutate_operation_responses = responses
@@ -207,9 +267,9 @@ def suggest_keyword_themes_side_effect_biz(request): # Simplified for this test
207267
mock_google_ads_client,
208268
mock_customer_id,
209269
mock_keyword_text,
210-
None, # No free_form_keyword_text when business profile is used, per script logic
270+
None, # No free_form_keyword_text for this test case
211271
mock_business_profile_location,
212-
mock_business_name # Should be None or ignored by script when biz profile is set
272+
None # Business name is ignored by script when business profile location is set
213273
)
214274
except Exception as e:
215275
pytest.fail(f"main function raised an exception: {e}")

0 commit comments

Comments
 (0)