Skip to content

Commit b6aa2c9

Browse files
Here's the commit message I've drafted:
Add test suite for Google Ads Account Analyzer Demo and ensure v19 API usage. This commit introduces a test suite for the `analyzer.py` script located in `examples/google-ads-account-analyzer-demo/`. Key changes include: - Created a new `tests` subdirectory within `examples/google-ads-account-analyzer-demo/`. - Added `test_analyzer.py` containing unit tests for the `account_hierarchy_module` and `get_users_module` functions. - Implemented comprehensive mocking for the Google Ads API client, services, and API responses to ensure tests are isolated and cover various scenarios: - `account_hierarchy_module`: Tested with and without a specific customer ID, simulating different account structures and API responses. - `get_users_module`: Tested by mocking API responses for user access information. - Ensured that the Google Ads API v19 is usable by confirming the current client library version (26.1.0) already includes v19 support, making library updates unnecessary for this specific requirement. The tests are written to be compatible with v19 API structures where applicable. - Added `__init__.py` to the new test directory. - All tests pass, verifying the core functionality of the analyzer script and its output formatting.
1 parent 4f350a8 commit b6aa2c9

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

examples/google-ads-account-analyzer-demo/tests/__init__.py

Whitespace-only changes.
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import unittest
2+
from unittest.mock import MagicMock, patch, call
3+
4+
# Assuming analyzer.py is in the parent directory
5+
from .. import analyzer
6+
7+
class TestAnalyzer(unittest.TestCase):
8+
9+
@patch('builtins.print')
10+
@patch('google.ads.googleads.client.GoogleAdsClient.load_from_storage')
11+
def test_account_hierarchy_module_with_customer_id(self, mock_load_from_storage, mock_print):
12+
mock_ads_client = MagicMock()
13+
mock_load_from_storage.return_value = mock_ads_client
14+
15+
mock_google_ads_service = MagicMock()
16+
mock_customer_service = MagicMock()
17+
18+
mock_ads_client.get_service.side_effect = lambda service_name, version=None: \
19+
mock_google_ads_service if service_name == "GoogleAdsService" else \
20+
mock_customer_service if service_name == "CustomerService" else MagicMock()
21+
22+
# Mock responses for GoogleAdsService search
23+
def mock_search_side_effect(customer_id, query):
24+
# Mock for initial call with customer_id='123'
25+
if customer_id == '123':
26+
# Row for root account '123'
27+
mock_row_root = MagicMock()
28+
mock_row_root.customer_client.id = '123'
29+
mock_row_root.customer_client.descriptive_name = 'Root Account'
30+
mock_row_root.customer_client.currency_code = 'USD'
31+
mock_row_root.customer_client.time_zone = 'America/New_York'
32+
mock_row_root.customer_client.manager = True
33+
mock_row_root.customer_client.level = 0
34+
35+
# Row for child account '456' (manager)
36+
mock_row_child1 = MagicMock()
37+
mock_row_child1.customer_client.id = '456'
38+
mock_row_child1.customer_client.descriptive_name = 'Child 1'
39+
mock_row_child1.customer_client.currency_code = 'USD'
40+
mock_row_child1.customer_client.time_zone = 'America/New_York'
41+
mock_row_child1.customer_client.manager = True
42+
mock_row_child1.customer_client.level = 1
43+
44+
# Row for child account '789' (not a manager)
45+
mock_row_child2 = MagicMock()
46+
mock_row_child2.customer_client.id = '789'
47+
mock_row_child2.customer_client.descriptive_name = 'Child 2'
48+
mock_row_child2.customer_client.currency_code = 'EUR'
49+
mock_row_child2.customer_client.time_zone = 'Europe/London'
50+
mock_row_child2.customer_client.manager = False
51+
mock_row_child2.customer_client.level = 1
52+
return iter([mock_row_root, mock_row_child1, mock_row_child2])
53+
# Mock for call with child manager customer_id='456'
54+
elif customer_id == '456':
55+
# Row for manager account '456' itself (as root of this query)
56+
mock_row_manager_self = MagicMock()
57+
mock_row_manager_self.customer_client.id = '456'
58+
mock_row_manager_self.customer_client.descriptive_name = 'Child 1' # Name consistency
59+
mock_row_manager_self.customer_client.currency_code = 'USD'
60+
mock_row_manager_self.customer_client.time_zone = 'America/New_York'
61+
mock_row_manager_self.customer_client.manager = True # It is a manager
62+
mock_row_manager_self.customer_client.level = 0 # Level 0 for this specific query
63+
64+
# Row for grandchild account '45601'
65+
mock_row_grandchild = MagicMock()
66+
mock_row_grandchild.customer_client.id = '45601'
67+
mock_row_grandchild.customer_client.descriptive_name = 'Grandchild 1.1'
68+
mock_row_grandchild.customer_client.currency_code = 'USD'
69+
mock_row_grandchild.customer_client.time_zone = 'America/New_York'
70+
mock_row_grandchild.customer_client.manager = False
71+
mock_row_grandchild.customer_client.level = 1 # Level 1 relative to '456'
72+
return iter([mock_row_manager_self, mock_row_grandchild])
73+
return iter([]) # Default empty response
74+
75+
mock_google_ads_service.search.side_effect = mock_search_side_effect
76+
77+
# Mock CustomerService (not directly used by account_hierarchy_module for hierarchy logic,
78+
# but good to have a basic mock if the client tries to get it)
79+
mock_customer_service.list_accessible_customers.return_value = MagicMock(resource_names=[])
80+
81+
82+
analyzer.account_hierarchy_module(mock_ads_client, '123')
83+
84+
# _DEFAULT_LOG_SPACE_LENGTH is 4
85+
# Depth 0: "-"
86+
# Depth 1: "-----"
87+
# Depth 2: "---------"
88+
expected_prints = [
89+
call("\nThe hierarchy of customer ID 123 is printed below:"),
90+
call("Customer ID (Descriptive Name, Currency Code, Time Zone)"),
91+
call("-", end=""),
92+
call("123 (Root Account, USD, America/New_York)"),
93+
call("-----", end=""),
94+
call("456 (Child 1, USD, America/New_York)"),
95+
call("---------", end=""),
96+
call("45601 (Grandchild 1.1, USD, America/New_York)"),
97+
call("-----", end=""),
98+
call("789 (Child 2, EUR, Europe/London)")
99+
]
100+
mock_print.assert_has_calls(expected_prints, any_order=False)
101+
102+
@patch('builtins.print')
103+
@patch('google.ads.googleads.client.GoogleAdsClient.load_from_storage')
104+
def test_account_hierarchy_module_without_customer_id(self, mock_load_from_storage, mock_print):
105+
mock_ads_client = MagicMock()
106+
mock_load_from_storage.return_value = mock_ads_client
107+
108+
mock_google_ads_service = MagicMock()
109+
mock_customer_service = MagicMock()
110+
111+
mock_ads_client.get_service.side_effect = lambda service_name, version=None: \
112+
mock_google_ads_service if service_name == "GoogleAdsService" else \
113+
mock_customer_service if service_name == "CustomerService" else MagicMock()
114+
115+
# Mock list_accessible_customers response
116+
mock_accessible_customers_response = MagicMock()
117+
mock_accessible_customers_response.resource_names = ["customers/111", "customers/222"]
118+
mock_customer_service.list_accessible_customers.return_value = mock_accessible_customers_response
119+
120+
# Mock responses for GoogleAdsService search
121+
def mock_search_side_effect(customer_id, query):
122+
if customer_id == '111':
123+
mock_row_root1 = MagicMock()
124+
mock_row_root1.customer_client.id = '111'
125+
mock_row_root1.customer_client.descriptive_name = 'Acc One'
126+
mock_row_root1.customer_client.currency_code = 'USD'
127+
mock_row_root1.customer_client.time_zone = 'America/New_York'
128+
mock_row_root1.customer_client.manager = True
129+
mock_row_root1.customer_client.level = 0
130+
131+
mock_row_child1_1 = MagicMock()
132+
mock_row_child1_1.customer_client.id = '11101'
133+
mock_row_child1_1.customer_client.descriptive_name = 'Child Acc One'
134+
mock_row_child1_1.customer_client.currency_code = 'USD'
135+
mock_row_child1_1.customer_client.time_zone = 'America/New_York'
136+
mock_row_child1_1.customer_client.manager = False
137+
mock_row_child1_1.customer_client.level = 1
138+
return iter([mock_row_root1, mock_row_child1_1])
139+
elif customer_id == '222':
140+
mock_row_root2 = MagicMock()
141+
mock_row_root2.customer_client.id = '222'
142+
mock_row_root2.customer_client.descriptive_name = 'Acc Two'
143+
mock_row_root2.customer_client.currency_code = 'EUR'
144+
mock_row_root2.customer_client.time_zone = 'Europe/Dublin'
145+
mock_row_root2.customer_client.manager = True # Making it a manager for potential deeper hierarchy
146+
mock_row_root2.customer_client.level = 0
147+
148+
mock_row_child2_1 = MagicMock()
149+
mock_row_child2_1.customer_client.id = '22201'
150+
mock_row_child2_1.customer_client.descriptive_name = 'Child Acc Two'
151+
mock_row_child2_1.customer_client.currency_code = 'EUR'
152+
mock_row_child2_1.customer_client.time_zone = 'Europe/Dublin'
153+
mock_row_child2_1.customer_client.manager = False
154+
mock_row_child2_1.customer_client.level = 1
155+
return iter([mock_row_root2, mock_row_child2_1])
156+
return iter([])
157+
158+
mock_google_ads_service.search.side_effect = mock_search_side_effect
159+
160+
analyzer.account_hierarchy_module(mock_ads_client, None)
161+
162+
expected_prints = [
163+
call("No manager ID is specified. The example will print the hierarchies of all accessible customer IDs."),
164+
call("Total results: 2"),
165+
call('Customer resource name: "customers/111"'),
166+
call('Customer resource name: "customers/222"'),
167+
call("\nThe hierarchy of customer ID 111 is printed below:"),
168+
call("Customer ID (Descriptive Name, Currency Code, Time Zone)"),
169+
call("-", end=""),
170+
call("111 (Acc One, USD, America/New_York)"),
171+
call("-----", end=""),
172+
call("11101 (Child Acc One, USD, America/New_York)"),
173+
call("\nThe hierarchy of customer ID 222 is printed below:"),
174+
call("Customer ID (Descriptive Name, Currency Code, Time Zone)"),
175+
call("-", end=""),
176+
call("222 (Acc Two, EUR, Europe/Dublin)"),
177+
call("-----", end=""),
178+
call("22201 (Child Acc Two, EUR, Europe/Dublin)"),
179+
]
180+
mock_print.assert_has_calls(expected_prints, any_order=False)
181+
182+
@patch('builtins.print')
183+
@patch('google.ads.googleads.client.GoogleAdsClient.load_from_storage')
184+
def test_get_users_module(self, mock_load_from_storage, mock_print):
185+
mock_ads_client = MagicMock()
186+
mock_load_from_storage.return_value = mock_ads_client
187+
188+
mock_google_ads_service = MagicMock()
189+
# For this test, only GoogleAdsService is expected.
190+
mock_ads_client.get_service.side_effect = lambda service_name, version=None: \
191+
mock_google_ads_service if service_name == "GoogleAdsService" else MagicMock()
192+
193+
# Prepare mock rows for the search response
194+
mock_row1 = MagicMock()
195+
mock_user_access1 = mock_row1.customer_user_access
196+
mock_user_access1.user_id = 'user001'
197+
mock_user_access1.email_address = '[email protected]'
198+
mock_user_access1.access_role.name = 'ADMIN'
199+
mock_user_access1.access_creation_date_time = '2023-01-15 09:30:00'
200+
# mock_user_access1.inviter_user_email_address = '[email protected]' # Not printed in current analyzer.py
201+
202+
mock_row2 = MagicMock()
203+
mock_user_access2 = mock_row2.customer_user_access
204+
mock_user_access2.user_id = 'user002'
205+
mock_user_access2.email_address = '[email protected]'
206+
mock_user_access2.access_role.name = 'STANDARD'
207+
mock_user_access2.access_creation_date_time = '2023-02-20 14:00:00'
208+
# mock_user_access2.inviter_user_email_address = '[email protected]' # Not printed
209+
210+
mock_google_ads_service.search.return_value = iter([mock_row1, mock_row2])
211+
212+
customer_id_to_test = '9876543210'
213+
analyzer.get_users_module(mock_ads_client, customer_id_to_test)
214+
215+
expected_prints = [
216+
call(f"The given customer ID has access to the client account with ID {mock_user_access1.user_id}, "
217+
f"with an access role of '{mock_user_access1.access_role.name}', "
218+
f"and creation time of '{mock_user_access1.access_creation_date_time}'."),
219+
call(f"The given customer ID has access to the client account with ID {mock_user_access2.user_id}, "
220+
f"with an access role of '{mock_user_access2.access_role.name}', "
221+
f"and creation time of '{mock_user_access2.access_creation_date_time}'.")
222+
]
223+
224+
# Check that search was called with the correct customer ID
225+
mock_google_ads_service.search.assert_called_once()
226+
call_args, call_kwargs = mock_google_ads_service.search.call_args
227+
self.assertEqual(call_kwargs['customer_id'], customer_id_to_test)
228+
# It's also good to check the query if it's stable, but for now, customer_id is the main variable part.
229+
230+
mock_print.assert_has_calls(expected_prints, any_order=False)
231+
232+
233+
if __name__ == '__main__':
234+
unittest.main()

0 commit comments

Comments
 (0)