Skip to content

Commit ac40c8d

Browse files
authored
Remove hidden bugs from unit tests by enforcing mocks (#1568)
Enforce check defined in: - databrickslabs/pylint-plugin#44
1 parent f68ee99 commit ac40c8d

28 files changed

+414
-196
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ dependencies = [
6767
"mypy~=1.9.0",
6868
"pylint~=3.1.0",
6969
"pylint-pytest==2.0.0a0",
70-
"databricks-labs-pylint~=0.3.0",
70+
"databricks-labs-pylint~=0.4.0",
7171
"pytest~=8.1.0",
7272
"pytest-cov~=4.1.0",
7373
"pytest-mock~=3.14.0",

src/databricks/labs/ucx/aws/credentials.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,11 @@ class IamRoleMigration:
113113
def __init__(
114114
self,
115115
installation: Installation,
116-
ws: WorkspaceClient,
117116
resource_permissions: AWSResourcePermissions,
118117
storage_credential_manager: CredentialManager,
119118
):
120119
self._output_file = "aws_iam_role_migration_result.csv"
121120
self._installation = installation
122-
self._ws = ws
123121
self._resource_permissions = resource_permissions
124122
self._storage_credential_manager = storage_credential_manager
125123

src/databricks/labs/ucx/contexts/cli_command.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ def aws_resource_permissions(self):
162162
def iam_role_migration(self):
163163
return IamRoleMigration(
164164
self.installation,
165-
self.workspace_client,
166165
self.aws_resource_permissions,
167166
self.iam_credential_manager,
168167
)

tests/integration/aws/test_credentials.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def inner(credentials: set[str], read_only=False) -> list[CredentialValidationRe
3232
location = ExternalLocations(ws, sql_backend, "inventory_schema")
3333
resource_permissions = AWSResourcePermissions(installation, ws, sql_backend, aws, location, "inventory_schema")
3434

35-
instance_profile_migration = IamRoleMigration(installation, ws, resource_permissions, CredentialManager(ws))
35+
instance_profile_migration = IamRoleMigration(installation, resource_permissions, CredentialManager(ws))
3636

3737
return instance_profile_migration.run(
3838
MockPrompts({"Above IAM roles will be migrated to UC storage credentials *": "Yes"}),

tests/unit/assessment/test_clusters.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def test_try_fetch():
142142

143143
def test_no_isolation_clusters():
144144
ws = workspace_client_mock(cluster_ids=['no-isolation'])
145-
sql_backend = create_autospec(SqlBackend)
145+
sql_backend = MockBackend()
146146
crawler = ClustersCrawler(ws, sql_backend, "ucx")
147147
result_set = list(crawler.snapshot())
148148
assert len(result_set) == 1
@@ -151,7 +151,7 @@ def test_no_isolation_clusters():
151151

152152
def test_unsupported_clusters():
153153
ws = workspace_client_mock(cluster_ids=['legacy-passthrough'])
154-
sql_backend = create_autospec(SqlBackend)
154+
sql_backend = MockBackend()
155155
crawler = ClustersCrawler(ws, sql_backend, "ucx")
156156
result_set = list(crawler.snapshot())
157157
assert len(result_set) == 1
@@ -163,7 +163,7 @@ def test_policy_crawler():
163163
policy_ids=['single-user-with-spn', 'single-user-with-spn-policyid', 'single-user-with-spn-no-sparkversion'],
164164
)
165165

166-
sql_backend = create_autospec(SqlBackend)
166+
sql_backend = MockBackend()
167167
crawler = PoliciesCrawler(ws, sql_backend, "ucx")
168168
result_set = list(crawler.snapshot())
169169
failures = json.loads(result_set[0].failures)

tests/unit/aws/test_access.py

Lines changed: 70 additions & 78 deletions
Large diffs are not rendered by default.

tests/unit/aws/test_credentials.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,15 @@ def installation():
2626
return MockInstallation(DEFAULT_CONFIG)
2727

2828

29-
@pytest.fixture
30-
def ws():
31-
return create_autospec(WorkspaceClient)
32-
33-
3429
def side_effect_create_aws_storage_credential(name, aws_iam_role, comment, read_only):
3530
return StorageCredentialInfo(
3631
name=name, aws_iam_role=AwsIamRoleResponse(role_arn=aws_iam_role.role_arn), comment=comment, read_only=read_only
3732
)
3833

3934

4035
@pytest.fixture
41-
def credential_manager(ws):
36+
def credential_manager():
37+
ws = create_autospec(WorkspaceClient)
4238
ws.storage_credentials.list.return_value = [
4339
StorageCredentialInfo(
4440
aws_iam_role=AwsIamRoleResponse(role_arn="arn:aws:iam::123456789012:role/example-role-name")
@@ -85,7 +81,7 @@ def test_create_storage_credentials(credential_manager):
8581

8682

8783
@pytest.fixture
88-
def instance_profile_migration(ws, installation, credential_manager):
84+
def instance_profile_migration(installation, credential_manager):
8985
def generate_instance_profiles(num_instance_profiles: int):
9086
arp = create_autospec(AWSResourcePermissions)
9187
arp.load_uc_compatible_roles.return_value = [
@@ -97,13 +93,12 @@ def generate_instance_profiles(num_instance_profiles: int):
9793
)
9894
for i in range(num_instance_profiles)
9995
]
100-
101-
return IamRoleMigration(installation, ws, arp, credential_manager)
96+
return IamRoleMigration(installation, arp, credential_manager)
10297

10398
return generate_instance_profiles
10499

105100

106-
def test_print_action_plan(caplog, ws, instance_profile_migration, credential_manager):
101+
def test_print_action_plan(caplog, instance_profile_migration, credential_manager):
107102
caplog.set_level(logging.INFO)
108103

109104
prompts = MockPrompts({"Above IAM roles will be migrated to UC storage credentials*": "Yes"})
@@ -118,20 +113,29 @@ def test_print_action_plan(caplog, ws, instance_profile_migration, credential_ma
118113
assert False, "Action plan is not logged"
119114

120115

121-
def test_migrate_credential_failed_creation(caplog, ws, instance_profile_migration):
116+
def test_migrate_credential_failed_creation(caplog, instance_profile_migration):
122117
caplog.set_level(logging.ERROR)
123118
prompts = MockPrompts(
124119
{
125120
"Above IAM roles will be migrated to UC storage credentials*": "Yes",
126121
}
127122
)
128-
ws.storage_credentials.create.return_value = StorageCredentialInfo(aws_iam_role=None)
129-
ws.storage_credentials.create.side_effect = None
130-
instance_profile_migration(1).run(prompts)
123+
migration = instance_profile_migration(1)
124+
# due to abuse of fixtures and the way fixtures are shared in PyTest,
125+
# we need to access the protected attribute to keep the test small.
126+
# this test also reveals a design flaw in test code and perhaps in
127+
# the code under test as well.
128+
# pylint: disable-next=protected-access
129+
migration._storage_credential_manager._ws.storage_credentials.create.return_value = StorageCredentialInfo(
130+
aws_iam_role=None
131+
)
132+
# pylint: disable-next=protected-access
133+
migration._storage_credential_manager._ws.storage_credentials.create.side_effect = None
134+
migration.run(prompts)
131135
assert "Failed to create storage credential for IAM role: arn:aws:iam::123456789012:role/prefix0" in caplog.messages
132136

133137

134-
def test_run_without_confirmation(ws, instance_profile_migration):
138+
def test_run_without_confirmation(instance_profile_migration):
135139
prompts = MockPrompts(
136140
{
137141
"Above IAM roles will be migrated to UC storage credentials*": "No",
@@ -142,18 +146,18 @@ def test_run_without_confirmation(ws, instance_profile_migration):
142146

143147

144148
@pytest.mark.parametrize("num_instance_profiles", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
145-
def test_run(ws, instance_profile_migration, num_instance_profiles: int):
149+
def test_run(instance_profile_migration, num_instance_profiles: int):
146150
prompts = MockPrompts({"Above IAM roles will be migrated to UC storage credentials*": "Yes"})
147151
migration = instance_profile_migration(num_instance_profiles)
148152
results = migration.run(prompts)
149153
assert len(results) == num_instance_profiles
150154

151155

152-
def test_run_no_credential_to_migrate(caplog, ws, installation, credential_manager):
156+
def test_run_no_credential_to_migrate(caplog, installation, credential_manager):
153157
caplog.set_level(logging.INFO)
154158
arp = create_autospec(AWSResourcePermissions)
155159
arp.load_uc_compatible_roles.return_value = []
156-
migration = IamRoleMigration(installation, ws, arp, credential_manager)
160+
migration = IamRoleMigration(installation, arp, credential_manager)
157161
migration.run(MockPrompts({}))
158162
assert "No IAM Role to migrate" in caplog.messages
159163

tests/unit/azure/test_access.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,17 @@ def test_save_spn_permissions_no_external_table(caplog):
3232
location = ExternalLocations(w, backend, "ucx")
3333
installation = MockInstallation()
3434
azure_resources = create_autospec(AzureResources)
35-
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
3635
azure_resources.storage_accounts.return_value = []
36+
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
3737
azure_resource_permission.save_spn_permissions()
3838
msg = "There are no external table present with azure storage account. Please check if assessment job is run"
3939
assert [rec.message for rec in caplog.records if msg in rec.message]
40+
w.cluster_policies.get.assert_not_called()
41+
w.secrets.get_secret.assert_not_called()
42+
w.secrets.create_scope.assert_not_called()
43+
w.secrets.put_secret.assert_not_called()
44+
w.cluster_policies.edit.assert_not_called()
45+
w.get_workspace_id.assert_not_called()
4046

4147

4248
def test_save_spn_permissions_no_external_tables():
@@ -49,6 +55,12 @@ def test_save_spn_permissions_no_external_tables():
4955
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
5056
azure_resources.storage_accounts.return_value = []
5157
assert not azure_resource_permission.save_spn_permissions()
58+
w.cluster_policies.get.assert_not_called()
59+
w.secrets.get_secret.assert_not_called()
60+
w.secrets.create_scope.assert_not_called()
61+
w.secrets.put_secret.assert_not_called()
62+
w.cluster_policies.edit.assert_not_called()
63+
w.get_workspace_id.assert_not_called()
5264

5365

5466
def test_save_spn_permissions_no_azure_storage_account():
@@ -63,6 +75,12 @@ def test_save_spn_permissions_no_azure_storage_account():
6375
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
6476
azure_resources.storage_accounts.return_value = []
6577
assert not azure_resource_permission.save_spn_permissions()
78+
w.cluster_policies.get.assert_not_called()
79+
w.secrets.get_secret.assert_not_called()
80+
w.secrets.create_scope.assert_not_called()
81+
w.secrets.put_secret.assert_not_called()
82+
w.cluster_policies.edit.assert_not_called()
83+
w.get_workspace_id.assert_not_called()
6684

6785

6886
def test_save_spn_permissions_valid_azure_storage_account():
@@ -111,6 +129,12 @@ def test_save_spn_permissions_valid_azure_storage_account():
111129
]
112130
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
113131
azure_resource_permission.save_spn_permissions()
132+
w.cluster_policies.get.assert_not_called()
133+
w.secrets.get_secret.assert_not_called()
134+
w.secrets.create_scope.assert_not_called()
135+
w.secrets.put_secret.assert_not_called()
136+
w.cluster_policies.edit.assert_not_called()
137+
w.get_workspace_id.assert_not_called()
114138
installation.assert_file_written(
115139
'azure_storage_account_info.csv',
116140
[
@@ -153,6 +177,16 @@ def test_create_global_spn_no_policy():
153177
prompts = MockPrompts({"Enter a name for the uber service principal to be created*": "UCXServicePrincipal"})
154178
with pytest.raises(ValueError):
155179
azure_resource_permission.create_uber_principal(prompts)
180+
azure_resources.storage_accounts.assert_not_called()
181+
azure_resources.create_or_update_access_connector.assert_not_called()
182+
azure_resources.role_assignments.assert_not_called()
183+
azure_resources.containers.assert_not_called()
184+
w.cluster_policies.get.assert_not_called()
185+
w.secrets.get_secret.assert_not_called()
186+
w.secrets.create_scope.assert_not_called()
187+
w.secrets.put_secret.assert_not_called()
188+
w.cluster_policies.edit.assert_not_called()
189+
w.get_workspace_id.assert_called_once()
156190

157191

158192
def test_create_global_spn_spn_present():
@@ -175,6 +209,16 @@ def test_create_global_spn_spn_present():
175209
prompts = MockPrompts({"Enter a name for the uber service principal to be created*": "UCXServicePrincipal"})
176210
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
177211
assert not azure_resource_permission.create_uber_principal(prompts)
212+
azure_resources.storage_accounts.assert_not_called()
213+
azure_resources.create_or_update_access_connector.assert_not_called()
214+
azure_resources.role_assignments.assert_not_called()
215+
azure_resources.containers.assert_not_called()
216+
w.cluster_policies.get.assert_not_called()
217+
w.secrets.get_secret.assert_not_called()
218+
w.secrets.create_scope.assert_not_called()
219+
w.secrets.put_secret.assert_not_called()
220+
w.cluster_policies.edit.assert_not_called()
221+
w.get_workspace_id.assert_called_once()
178222

179223

180224
def test_create_global_spn_no_storage():
@@ -198,6 +242,16 @@ def test_create_global_spn_no_storage():
198242
azure_resources = create_autospec(AzureResources)
199243
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
200244
assert not azure_resource_permission.create_uber_principal(prompts)
245+
azure_resources.storage_accounts.assert_not_called()
246+
azure_resources.create_or_update_access_connector.assert_not_called()
247+
azure_resources.role_assignments.assert_not_called()
248+
azure_resources.containers.assert_not_called()
249+
w.cluster_policies.get.assert_not_called()
250+
w.secrets.get_secret.assert_not_called()
251+
w.secrets.create_scope.assert_not_called()
252+
w.secrets.put_secret.assert_not_called()
253+
w.cluster_policies.edit.assert_not_called()
254+
w.get_workspace_id.assert_called_once()
201255

202256

203257
def test_create_global_spn_cluster_policy_not_found():
@@ -224,6 +278,12 @@ def test_create_global_spn_cluster_policy_not_found():
224278
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
225279
with pytest.raises(NotFound):
226280
azure_resource_permission.create_uber_principal(prompts)
281+
w.cluster_policies.get.assert_called_once()
282+
w.secrets.get_secret.assert_not_called()
283+
w.secrets.create_scope.assert_called_with("ucx")
284+
w.secrets.put_secret.assert_called_with('ucx', 'uber_principal_secret', string_value='mypwd')
285+
w.cluster_policies.edit.assert_not_called()
286+
w.get_workspace_id.assert_called_once()
227287

228288

229289
def test_create_global_spn():
@@ -302,6 +362,10 @@ def test_create_access_connectors_for_storage_accounts_logs_no_storage_accounts(
302362
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
303363

304364
azure_resource_permission.create_access_connectors_for_storage_accounts()
365+
366+
w.cluster_policies.get.assert_not_called()
367+
w.secrets.get_secret.assert_not_called()
368+
w.secrets.create_scope.assert_not_called()
305369
assert (
306370
"There are no external table present with azure storage account. Please check if assessment job is run"
307371
in caplog.messages
@@ -345,6 +409,11 @@ def test_create_access_connectors_for_storage_accounts_one_access_connector():
345409
azure_resource_permission = AzureResourcePermissions(installation, w, azure_resources, location)
346410

347411
access_connectors = azure_resource_permission.create_access_connectors_for_storage_accounts()
412+
413+
w.cluster_policies.get.assert_not_called()
414+
w.secrets.get_secret.assert_not_called()
415+
w.secrets.create_scope.assert_not_called()
416+
348417
assert len(access_connectors) == 1
349418
assert access_connectors[0].name == "ac-test"
350419

@@ -388,3 +457,7 @@ def test_create_access_connectors_for_storage_accounts_log_permission_applied(ca
388457
with caplog.at_level(logging.DEBUG, logger="databricks.labs.ucx"):
389458
azure_resource_permission.create_access_connectors_for_storage_accounts()
390459
assert any("STORAGE_BLOB_DATA_CONTRIBUTOR" in message for message in caplog.messages)
460+
461+
w.cluster_policies.get.assert_not_called()
462+
w.secrets.get_secret.assert_not_called()
463+
w.secrets.create_scope.assert_not_called()

0 commit comments

Comments
 (0)