22from unittest .mock import patch , Mock , call , ANY , MagicMock
33import io
44import sys
5+ import os
56from types import SimpleNamespace
67
8+ # Add the project root to sys.path to allow for relative imports
9+ sys .path .insert (0 , os .path .abspath (os .path .join (os .path .dirname (__file__ ), '..' , '..' , '..' )))
10+
711# SUT (System Under Test)
812from examples .remarketing import update_audience_target_restriction
913
@@ -86,15 +90,8 @@ def setUp(self):
8690 )
8791 self .mock_client .enums = SimpleNamespace (TargetingDimensionEnum = self .mock_enums )
8892
89- self .patched_update_targeting_setting_was_invoked_flag = False
90- def log_call_and_proceed_side_effect (* args , ** kwargs ):
91- sys .__stderr__ .write ("DEBUG_PATCH_SIDE_EFFECT: Patched update_targeting_setting was CALLED.\n " )
92- self .patched_update_targeting_setting_was_invoked_flag = True
93- return None
94-
9593 self .update_setting_patcher = patch (
9694 'examples.remarketing.update_audience_target_restriction.update_targeting_setting' ,
97- side_effect = log_call_and_proceed_side_effect ,
9895 autospec = True
9996 )
10097 self .patched_update_targeting_setting = self .update_setting_patcher .start ()
@@ -104,24 +101,22 @@ def log_call_and_proceed_side_effect(*args, **kwargs):
104101 self ._list_of_restrictions_added_by_sut_add_method = []
105102
106103 def mock_add_to_restrictions_list_side_effect ():
107- sys .__stderr__ .write ("DEBUG_SIDE_EFFECT: mock_add_to_restrictions_list_side_effect CALLED\n " )
108104 new_mock_for_add = Mock (name = "NewRestrictionAddedBySUT_from_add" )
109105 self ._current_target_restrictions_list_for_sut .append (new_mock_for_add )
110106 self ._list_of_restrictions_added_by_sut_add_method .append (new_mock_for_add )
111- sys .__stderr__ .write (f"DEBUG_SIDE_EFFECT: Appended. List now: { self ._current_target_restrictions_list_for_sut } \n " )
112107 return new_mock_for_add
113108
114109 def mock_append_to_restrictions_list_side_effect (item ):
115- sys .__stderr__ .write (f"DEBUG_SIDE_EFFECT: mock_append_to_restrictions_list_side_effect CALLED with { item } \n " )
116110 self ._current_target_restrictions_list_for_sut .append (item )
117- sys .__stderr__ .write (f"DEBUG_SIDE_EFFECT: Appended. List now: { self ._current_target_restrictions_list_for_sut } \n " )
118111
119112
120113 self .ts_list_behavior_mock = MagicMock (spec = list , name = "TargetRestrictionsListBehavior" )
121114 self .ts_list_behavior_mock .add = Mock (side_effect = mock_add_to_restrictions_list_side_effect , name = "AddMethodMock" )
122115 self .ts_list_behavior_mock .append = Mock (side_effect = mock_append_to_restrictions_list_side_effect , name = "AppendMethodMock" )
116+
123117 self .ts_list_behavior_mock .__iter__ = lambda x = None : iter (self ._current_target_restrictions_list_for_sut )
124118 self .ts_list_behavior_mock .__len__ = lambda x = None : len (self ._current_target_restrictions_list_for_sut )
119+
125120 def get_item_side_effect (index_or_self , index_val = None ):
126121 if index_val is None :
127122 return self ._current_target_restrictions_list_for_sut [index_or_self ]
@@ -166,7 +161,7 @@ def _create_mock_search_response(self, target_restrictions_config):
166161
167162 @patch ('sys.stdout' , new_callable = io .StringIO )
168163 def test_main_update_needed_audience_is_false (self , mock_stdout ):
169- self .patched_update_targeting_setting_was_invoked_flag = False
164+ self .patched_update_targeting_setting . reset_mock ()
170165 self .mock_google_ads_service .search .return_value = self ._create_mock_search_response (
171166 [(self .mock_enums .AUDIENCE , False )]
172167 )
@@ -175,56 +170,54 @@ def test_main_update_needed_audience_is_false(self, mock_stdout):
175170 self .mock_google_ads_service .search .assert_called_once ()
176171 self .assertIn (f"ad_group.id = { self .test_ad_group_id } " , self .mock_google_ads_service .search .call_args [1 ]['query' ])
177172
178- self .assertTrue (self .patched_update_targeting_setting_was_invoked_flag , "Patched update_targeting_setting should have been called." )
179173 self .patched_update_targeting_setting .assert_called_once ()
180174
181175 called_args = self .patched_update_targeting_setting .call_args [0 ]
182176 self .assertEqual (called_args [1 ], self .test_customer_id )
183177 self .assertEqual (called_args [2 ], str (self .test_ad_group_id ))
184178
185- called_targeting_setting = called_args [3 ]
186- self .assertEqual (len (self ._list_of_restrictions_added_by_sut_add_method ), 1 )
187- added_restriction = self ._list_of_restrictions_added_by_sut_add_method [0 ]
188- self .assertEqual (added_restriction .targeting_dimension , self .mock_enums .AUDIENCE )
189- self .assertTrue (added_restriction .bid_only )
190- self .assertIn (added_restriction , self ._current_target_restrictions_list_for_sut )
179+ self .assertEqual (len (self ._current_target_restrictions_list_for_sut ), 1 )
180+ updated_restriction = self ._current_target_restrictions_list_for_sut [0 ]
181+ self .assertEqual (updated_restriction .targeting_dimension , self .mock_enums .AUDIENCE )
182+ self .assertTrue (updated_restriction .bid_only )
183+
191184
192185 captured_output = mock_stdout .getvalue ()
193186 self .assertIn (f"Ad group with ID { str (self .test_ad_group_id )} and name 'Mock Ad Group Name' was found" , captured_output )
194187 self .assertIn (f"\t Targeting restriction with targeting dimension '{ self .mock_enums .AUDIENCE .name } ' and bid only set to 'False'." , captured_output )
195- # The "Updating..." message is NOT printed by main directly. Call to helper is the indicator.
196188
197189 @patch ('sys.stdout' , new_callable = io .StringIO )
198190 def test_main_no_update_audience_is_true (self , mock_stdout ):
199- self .patched_update_targeting_setting_was_invoked_flag = False
191+ self .patched_update_targeting_setting . reset_mock ()
200192 self .mock_google_ads_service .search .return_value = self ._create_mock_search_response (
201193 [(self .mock_enums .AUDIENCE , True )]
202194 )
203195 update_audience_target_restriction .main (self .mock_client , self .test_customer_id , self .test_ad_group_id )
204- self . assertFalse ( self . patched_update_targeting_setting_was_invoked_flag )
196+
205197 self .patched_update_targeting_setting .assert_not_called ()
206198 captured_output = mock_stdout .getvalue ()
207- self .assertIn (f"Ad group with ID { str (self .test_ad_group_id )} " , captured_output )
199+ self .assertIn (f"Ad group with ID { str (self .test_ad_group_id )} and name 'Mock Ad Group Name' was found " , captured_output )
208200 self .assertIn (f"\t Targeting restriction with targeting dimension '{ self .mock_enums .AUDIENCE .name } ' and bid only set to 'True'." , captured_output )
209201 self .assertIn ("No target restrictions to update.\n " , captured_output )
210202
211203 @patch ('sys.stdout' , new_callable = io .StringIO )
212204 def test_main_no_update_no_audience_restrictions (self , mock_stdout ):
213- self .patched_update_targeting_setting_was_invoked_flag = False
205+ self .patched_update_targeting_setting . reset_mock ()
214206 self .mock_google_ads_service .search .return_value = self ._create_mock_search_response (
215207 [(self .mock_enums .KEYWORD , False )]
216208 )
217209 update_audience_target_restriction .main (self .mock_client , self .test_customer_id , self .test_ad_group_id )
218- self . assertFalse ( self . patched_update_targeting_setting_was_invoked_flag )
210+
219211 self .patched_update_targeting_setting .assert_not_called ()
220212 captured_output = mock_stdout .getvalue ()
221- self .assertIn (f"Ad group with ID { str (self .test_ad_group_id )} " , captured_output )
213+ self .assertIn (f"Ad group with ID { str (self .test_ad_group_id )} and name 'Mock Ad Group Name' was found " , captured_output )
222214 self .assertIn (f"\t Targeting restriction with targeting dimension '{ self .mock_enums .KEYWORD .name } ' and bid only set to 'False'." , captured_output )
223215 self .assertIn ("No target restrictions to update.\n " , captured_output )
224216
225217 @patch ('sys.stdout' , new_callable = io .StringIO )
226218 def test_main_update_needed_mixed_restrictions_audience_false (self , mock_stdout ):
227- self .patched_update_targeting_setting_was_invoked_flag = False
219+ self .patched_update_targeting_setting .reset_mock ()
220+
228221 keyword_restriction_from_search = Mock (name = "KeywordRestrictionFromSearch" )
229222 keyword_restriction_from_search .targeting_dimension = self .mock_enums .KEYWORD
230223 keyword_restriction_from_search .bid_only = False
@@ -233,67 +226,51 @@ def test_main_update_needed_mixed_restrictions_audience_false(self, mock_stdout)
233226 audience_restriction_from_search .targeting_dimension = self .mock_enums .AUDIENCE
234227 audience_restriction_from_search .bid_only = False
235228
236- search_response_mock = Mock (name = "SearchResponse" )
237- mock_ad_group_row = Mock (name = "GoogleAdsRow" )
238- mock_ad_group_row .ad_group .id = str (self .test_ad_group_id )
239- mock_ad_group_row .ad_group .name = "Mock Ad Group Name"
240- mock_ad_group_row .ad_group .targeting_setting .target_restrictions = [
241- keyword_restriction_from_search , audience_restriction_from_search
242- ]
243- search_response_mock .__iter__ = Mock (return_value = iter ([mock_ad_group_row ]))
244- self .mock_google_ads_service .search .return_value = search_response_mock
229+ self .mock_google_ads_service .search .return_value = self ._create_mock_search_response (
230+ [(self .mock_enums .KEYWORD , False ), (self .mock_enums .AUDIENCE , False )]
231+ )
245232
246233 update_audience_target_restriction .main (self .mock_client , self .test_customer_id , self .test_ad_group_id )
247234
248- self .assertTrue (self .patched_update_targeting_setting_was_invoked_flag )
249235 self .patched_update_targeting_setting .assert_called_once ()
250236
251- called_targeting_setting_arg = self .patched_update_targeting_setting .call_args [0 ][3 ]
237+ passed_targeting_setting = self .patched_update_targeting_setting .call_args [0 ][3 ]
252238
253- self .assertEqual (len (self . _current_target_restrictions_list_for_sut ), 2 )
239+ self .assertEqual (len (passed_targeting_setting . target_restrictions ), 2 )
254240
255241 found_keyword = None ; found_audience = None
256- for r in self . _current_target_restrictions_list_for_sut :
242+ for r in passed_targeting_setting . target_restrictions :
257243 if r .targeting_dimension == self .mock_enums .KEYWORD : found_keyword = r
258244 elif r .targeting_dimension == self .mock_enums .AUDIENCE : found_audience = r
259245
260246 self .assertIsNotNone (found_keyword , "Keyword restriction should be preserved." )
261247 self .assertEqual (found_keyword .targeting_dimension , self .mock_enums .KEYWORD )
262248 self .assertFalse (found_keyword .bid_only )
263- self .assertIn (keyword_restriction_from_search , self ._current_target_restrictions_list_for_sut )
264249
265250 self .assertIsNotNone (found_audience , "Audience restriction should exist and be updated." )
266251 self .assertTrue (found_audience .bid_only )
267- self .assertIn (found_audience , self ._list_of_restrictions_added_by_sut_add_method )
268252
269253 captured_output = mock_stdout .getvalue ()
270- self .assertIn (f"Ad group with ID { str (self .test_ad_group_id )} " , captured_output )
254+ self .assertIn (f"Ad group with ID { str (self .test_ad_group_id )} and name 'Mock Ad Group Name' was found " , captured_output )
271255 self .assertIn (f"\t Targeting restriction with targeting dimension '{ self .mock_enums .KEYWORD .name } ' and bid only set to 'False'." , captured_output )
272256 self .assertIn (f"\t Targeting restriction with targeting dimension '{ self .mock_enums .AUDIENCE .name } ' and bid only set to 'False'." , captured_output )
273- # The "Updating..." message is NOT printed by main. If the patched function was called, that's enough.
274257
275258 @patch ('sys.stdout' , new_callable = io .StringIO )
276259 def test_main_no_update_empty_initial_restrictions (self , mock_stdout ):
277- self .patched_update_targeting_setting_was_invoked_flag = False
260+ self .patched_update_targeting_setting . reset_mock ()
278261 self .mock_google_ads_service .search .return_value = self ._create_mock_search_response ([])
279262
280263 update_audience_target_restriction .main (self .mock_client , self .test_customer_id , self .test_ad_group_id )
281264
282- self .assertTrue ( self . patched_update_targeting_setting_was_invoked_flag , "Patched update_targeting_setting should have been called for empty initial restrictions." )
283- self .patched_update_targeting_setting . assert_called_once ( )
265+ self .mock_google_ads_service . search . assert_called_once ( )
266+ self .assertIn ( f"ad_group.id = { self . test_ad_group_id } " , self . mock_google_ads_service . search . call_args [ 1 ][ 'query' ] )
284267
285- called_args = self .patched_update_targeting_setting .call_args [0 ]
286- called_targeting_setting = called_args [3 ]
287- self .assertEqual (len (self ._list_of_restrictions_added_by_sut_add_method ), 1 )
288- added_restriction = self ._list_of_restrictions_added_by_sut_add_method [0 ]
289- self .assertEqual (added_restriction .targeting_dimension , self .mock_enums .AUDIENCE )
290- self .assertTrue (added_restriction .bid_only )
291- self .assertIn (added_restriction , self ._current_target_restrictions_list_for_sut )
268+ self .patched_update_targeting_setting .assert_not_called ()
292269
293270 captured_output = mock_stdout .getvalue ()
294- self .assertIn (f"Ad group with ID { str (self .test_ad_group_id )} " , captured_output )
295- self .assertIn ("No AUDIENCE targeting restriction found. A new one will be created with bid_only set to True. " , captured_output )
296- # The "Updating..." message is NOT printed by main. If the patched function was called, that's enough.
271+ self .assertIn (f"Ad group with ID { str (self .test_ad_group_id )} and name 'Mock Ad Group Name' was found " , captured_output )
272+ self .assertNotIn ("No AUDIENCE targeting restriction found" , captured_output )
273+ self . assertIn ( "No target restrictions to update." , captured_output )
297274
298275
299276if __name__ == "__main__" :
0 commit comments