regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (112) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/utils/app_utils.py +11 -2
  3. regscale/dev/cli.py +26 -0
  4. regscale/dev/version.py +72 -0
  5. regscale/integrations/commercial/__init__.py +15 -1
  6. regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
  7. regscale/integrations/commercial/amazon/amazon/common.py +204 -0
  8. regscale/integrations/commercial/amazon/common.py +48 -58
  9. regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
  10. regscale/integrations/commercial/aws/cli.py +3093 -55
  11. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  12. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  13. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  14. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  15. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  16. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  17. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  18. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  19. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  20. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  21. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  22. regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
  23. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  24. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  25. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  26. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  27. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  28. regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
  29. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  30. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  31. regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
  32. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  33. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  34. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  35. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  36. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  37. regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
  38. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  39. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  40. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  41. regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
  42. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  43. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  44. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  45. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  46. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  47. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  48. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  49. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  50. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  51. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  52. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  53. regscale/integrations/commercial/aws/scanner.py +851 -206
  54. regscale/integrations/commercial/aws/security_hub.py +319 -0
  55. regscale/integrations/commercial/aws/session_manager.py +282 -0
  56. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  57. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  58. regscale/integrations/compliance_integration.py +308 -38
  59. regscale/integrations/due_date_handler.py +3 -0
  60. regscale/integrations/scanner_integration.py +399 -84
  61. regscale/models/integration_models/cisa_kev_data.json +34 -4
  62. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  63. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
  64. regscale/models/regscale_models/assessment.py +2 -1
  65. regscale/models/regscale_models/control_objective.py +74 -5
  66. regscale/models/regscale_models/file.py +2 -0
  67. regscale/models/regscale_models/issue.py +2 -5
  68. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
  69. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +112 -33
  70. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  71. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  72. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  73. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  74. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  75. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  76. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  77. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  78. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  79. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  80. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  81. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  82. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  83. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  84. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  85. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  86. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  87. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  88. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  89. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  90. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  91. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  92. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  93. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  94. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  95. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  96. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  97. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  98. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  99. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  100. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  101. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  102. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  103. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  104. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  105. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  106. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  107. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  108. tests/regscale/integrations/commercial/test_aws.py +55 -56
  109. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
  110. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
  111. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
  112. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,458 @@
1
+ """Unit tests for AWS IAM collector."""
2
+
3
+ import unittest
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import pytest
7
+ from botocore.exceptions import ClientError
8
+
9
+ from regscale.integrations.commercial.aws.inventory.resources.iam import IAMCollector
10
+
11
+
12
+ class TestIAMCollector(unittest.TestCase):
13
+ """Test cases for IAMCollector."""
14
+
15
+ def setUp(self):
16
+ """Set up test fixtures."""
17
+ self.mock_session = MagicMock()
18
+ self.region = "us-east-1"
19
+ self.account_id = "123456789012"
20
+ self.collector = IAMCollector(self.mock_session, self.region, self.account_id)
21
+
22
+ def test_init(self):
23
+ """Test IAMCollector initialization."""
24
+ assert self.collector.session == self.mock_session
25
+ assert self.collector.region == self.region
26
+ assert self.collector.account_id == self.account_id
27
+
28
+ def test_init_without_account_id(self):
29
+ """Test IAMCollector initialization without account ID."""
30
+ collector = IAMCollector(self.mock_session, self.region)
31
+ assert collector.account_id is None
32
+
33
+ @patch("regscale.integrations.commercial.aws.inventory.resources.iam.logger")
34
+ def test_collect_success(self, mock_logger):
35
+ """Test successful collection of IAM resources."""
36
+ mock_client = MagicMock()
37
+ self.mock_session.client.return_value = mock_client
38
+
39
+ # Mock account summary
40
+ mock_client.get_account_summary.return_value = {"SummaryMap": {"Users": 5, "Roles": 10}}
41
+
42
+ # Mock password policy
43
+ mock_client.get_account_password_policy.return_value = {
44
+ "PasswordPolicy": {"MinimumPasswordLength": 14, "RequireSymbols": True}
45
+ }
46
+
47
+ # Mock users
48
+ user_arn = f"arn:aws:iam::{self.account_id}:user/test-user"
49
+ mock_client.get_paginator.return_value.paginate.return_value = [
50
+ {"Users": [{"UserName": "test-user", "UserId": "AIDAI123", "Arn": user_arn, "CreateDate": "2024-01-01"}]}
51
+ ]
52
+
53
+ # Mock access keys and MFA devices
54
+ mock_client.list_access_keys.return_value = {
55
+ "AccessKeyMetadata": [{"AccessKeyId": "AKIAI123", "Status": "Active", "CreateDate": "2024-01-01"}]
56
+ }
57
+ mock_client.list_mfa_devices.return_value = {
58
+ "MFADevices": [{"SerialNumber": "arn:aws:iam::123:mfa/device", "EnableDate": "2024-01-01"}]
59
+ }
60
+
61
+ result = self.collector.collect()
62
+
63
+ assert "Users" in result
64
+ assert "Roles" in result
65
+ assert "Groups" in result
66
+ assert "Policies" in result
67
+ assert "AccessKeys" in result
68
+ assert "MFADevices" in result
69
+ assert "AccountSummary" in result
70
+ assert "PasswordPolicy" in result
71
+
72
+ @patch("regscale.integrations.commercial.aws.inventory.resources.iam.logger")
73
+ def test_collect_handles_client_error(self, mock_logger):
74
+ """Test collection handles ClientError."""
75
+ mock_client = MagicMock()
76
+ self.mock_session.client.return_value = mock_client
77
+
78
+ error_response = {"Error": {"Code": "InternalError", "Message": "Internal error"}}
79
+ mock_client.get_account_summary.side_effect = ClientError(error_response, "get_account_summary")
80
+
81
+ result = self.collector.collect()
82
+
83
+ assert result["AccountSummary"] == {}
84
+
85
+ @patch("regscale.integrations.commercial.aws.inventory.resources.iam.logger")
86
+ def test_collect_handles_unexpected_error(self, mock_logger):
87
+ """Test collection handles unexpected errors."""
88
+ mock_client = MagicMock()
89
+ self.mock_session.client.return_value = mock_client
90
+
91
+ mock_client.get_account_summary.side_effect = Exception("Unexpected error")
92
+
93
+ self.collector.collect()
94
+
95
+ mock_logger.error.assert_called()
96
+
97
+ def test_get_account_summary_success(self):
98
+ """Test successful account summary retrieval."""
99
+ mock_client = MagicMock()
100
+ mock_client.get_account_summary.return_value = {"SummaryMap": {"Users": 5, "Roles": 10}}
101
+
102
+ result = self.collector._get_account_summary(mock_client)
103
+
104
+ assert result["Users"] == 5
105
+ assert result["Roles"] == 10
106
+ assert result["Region"] == self.region
107
+
108
+ def test_get_account_summary_access_denied(self):
109
+ """Test account summary with access denied."""
110
+ mock_client = MagicMock()
111
+ error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
112
+ mock_client.get_account_summary.side_effect = ClientError(error_response, "get_account_summary")
113
+
114
+ result = self.collector._get_account_summary(mock_client)
115
+
116
+ assert result == {}
117
+
118
+ def test_get_password_policy_success(self):
119
+ """Test successful password policy retrieval."""
120
+ mock_client = MagicMock()
121
+ mock_client.get_account_password_policy.return_value = {
122
+ "PasswordPolicy": {"MinimumPasswordLength": 14, "RequireSymbols": True}
123
+ }
124
+
125
+ result = self.collector._get_password_policy(mock_client)
126
+
127
+ assert result["MinimumPasswordLength"] == 14
128
+ assert result["RequireSymbols"] is True
129
+ assert result["Region"] == self.region
130
+
131
+ def test_get_password_policy_no_policy(self):
132
+ """Test password policy when no policy exists."""
133
+ mock_client = MagicMock()
134
+ error_response = {"Error": {"Code": "NoSuchEntity", "Message": "No policy"}}
135
+ mock_client.get_account_password_policy.side_effect = ClientError(error_response, "get_account_password_policy")
136
+
137
+ result = self.collector._get_password_policy(mock_client)
138
+
139
+ assert result == {}
140
+
141
+ def test_list_users_success(self):
142
+ """Test successful users listing."""
143
+ mock_client = MagicMock()
144
+ user_arn = f"arn:aws:iam::{self.account_id}:user/test-user"
145
+ mock_paginator = MagicMock()
146
+ mock_paginator.paginate.return_value = [
147
+ {"Users": [{"UserName": "test-user", "UserId": "AIDAI123", "Arn": user_arn, "CreateDate": "2024-01-01"}]}
148
+ ]
149
+ mock_client.get_paginator.return_value = mock_paginator
150
+
151
+ result = self.collector._list_users(mock_client)
152
+
153
+ assert len(result) == 1
154
+ assert result[0]["UserName"] == "test-user"
155
+ assert result[0]["Region"] == self.region
156
+
157
+ def test_list_users_filters_by_account_id(self):
158
+ """Test users listing filters by account ID."""
159
+ mock_client = MagicMock()
160
+ user_arn_match = f"arn:aws:iam::{self.account_id}:user/test-user"
161
+ user_arn_no_match = "arn:aws:iam::999999999999:user/other-user"
162
+
163
+ mock_paginator = MagicMock()
164
+ mock_paginator.paginate.return_value = [
165
+ {
166
+ "Users": [
167
+ {"UserName": "test-user", "UserId": "AIDAI123", "Arn": user_arn_match, "CreateDate": "2024-01-01"},
168
+ {
169
+ "UserName": "other-user",
170
+ "UserId": "AIDAI456",
171
+ "Arn": user_arn_no_match,
172
+ "CreateDate": "2024-01-01",
173
+ },
174
+ ]
175
+ }
176
+ ]
177
+ mock_client.get_paginator.return_value = mock_paginator
178
+
179
+ result = self.collector._list_users(mock_client)
180
+
181
+ assert len(result) == 1
182
+ assert result[0]["UserName"] == "test-user"
183
+
184
+ def test_list_users_access_denied(self):
185
+ """Test users listing with access denied."""
186
+ mock_client = MagicMock()
187
+ mock_paginator = MagicMock()
188
+ error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
189
+ mock_paginator.paginate.side_effect = ClientError(error_response, "list_users")
190
+ mock_client.get_paginator.return_value = mock_paginator
191
+
192
+ result = self.collector._list_users(mock_client)
193
+
194
+ assert result == []
195
+
196
+ def test_list_roles_success(self):
197
+ """Test successful roles listing."""
198
+ mock_client = MagicMock()
199
+ role_arn = f"arn:aws:iam::{self.account_id}:role/test-role"
200
+ mock_paginator = MagicMock()
201
+ mock_paginator.paginate.return_value = [
202
+ {"Roles": [{"RoleName": "test-role", "RoleId": "AIDAI123", "Arn": role_arn, "CreateDate": "2024-01-01"}]}
203
+ ]
204
+ mock_client.get_paginator.return_value = mock_paginator
205
+
206
+ result = self.collector._list_roles(mock_client)
207
+
208
+ assert len(result) == 1
209
+ assert result[0]["RoleName"] == "test-role"
210
+ assert result[0]["Region"] == self.region
211
+
212
+ def test_list_roles_filters_by_account_id(self):
213
+ """Test roles listing filters by account ID."""
214
+ mock_client = MagicMock()
215
+ role_arn_match = f"arn:aws:iam::{self.account_id}:role/test-role"
216
+ role_arn_no_match = "arn:aws:iam::999999999999:role/other-role"
217
+
218
+ mock_paginator = MagicMock()
219
+ mock_paginator.paginate.return_value = [
220
+ {
221
+ "Roles": [
222
+ {"RoleName": "test-role", "RoleId": "AIDAI123", "Arn": role_arn_match, "CreateDate": "2024-01-01"},
223
+ {
224
+ "RoleName": "other-role",
225
+ "RoleId": "AIDAI456",
226
+ "Arn": role_arn_no_match,
227
+ "CreateDate": "2024-01-01",
228
+ },
229
+ ]
230
+ }
231
+ ]
232
+ mock_client.get_paginator.return_value = mock_paginator
233
+
234
+ result = self.collector._list_roles(mock_client)
235
+
236
+ assert len(result) == 1
237
+ assert result[0]["RoleName"] == "test-role"
238
+
239
+ def test_list_roles_access_denied(self):
240
+ """Test roles listing with access denied."""
241
+ mock_client = MagicMock()
242
+ mock_paginator = MagicMock()
243
+ error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
244
+ mock_paginator.paginate.side_effect = ClientError(error_response, "list_roles")
245
+ mock_client.get_paginator.return_value = mock_paginator
246
+
247
+ result = self.collector._list_roles(mock_client)
248
+
249
+ assert result == []
250
+
251
+ def test_list_groups_success(self):
252
+ """Test successful groups listing."""
253
+ mock_client = MagicMock()
254
+ group_arn = f"arn:aws:iam::{self.account_id}:group/test-group"
255
+ mock_paginator = MagicMock()
256
+ mock_paginator.paginate.return_value = [
257
+ {
258
+ "Groups": [
259
+ {"GroupName": "test-group", "GroupId": "AIDAI123", "Arn": group_arn, "CreateDate": "2024-01-01"}
260
+ ]
261
+ }
262
+ ]
263
+ mock_client.get_paginator.return_value = mock_paginator
264
+
265
+ result = self.collector._list_groups(mock_client)
266
+
267
+ assert len(result) == 1
268
+ assert result[0]["GroupName"] == "test-group"
269
+ assert result[0]["Region"] == self.region
270
+
271
+ def test_list_groups_filters_by_account_id(self):
272
+ """Test groups listing filters by account ID."""
273
+ mock_client = MagicMock()
274
+ group_arn_match = f"arn:aws:iam::{self.account_id}:group/test-group"
275
+ group_arn_no_match = "arn:aws:iam::999999999999:group/other-group"
276
+
277
+ mock_paginator = MagicMock()
278
+ mock_paginator.paginate.return_value = [
279
+ {
280
+ "Groups": [
281
+ {
282
+ "GroupName": "test-group",
283
+ "GroupId": "AIDAI123",
284
+ "Arn": group_arn_match,
285
+ "CreateDate": "2024-01-01",
286
+ },
287
+ {
288
+ "GroupName": "other-group",
289
+ "GroupId": "AIDAI456",
290
+ "Arn": group_arn_no_match,
291
+ "CreateDate": "2024-01-01",
292
+ },
293
+ ]
294
+ }
295
+ ]
296
+ mock_client.get_paginator.return_value = mock_paginator
297
+
298
+ result = self.collector._list_groups(mock_client)
299
+
300
+ assert len(result) == 1
301
+ assert result[0]["GroupName"] == "test-group"
302
+
303
+ def test_list_groups_access_denied(self):
304
+ """Test groups listing with access denied."""
305
+ mock_client = MagicMock()
306
+ mock_paginator = MagicMock()
307
+ error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
308
+ mock_paginator.paginate.side_effect = ClientError(error_response, "list_groups")
309
+ mock_client.get_paginator.return_value = mock_paginator
310
+
311
+ result = self.collector._list_groups(mock_client)
312
+
313
+ assert result == []
314
+
315
+ def test_list_policies_success(self):
316
+ """Test successful policies listing."""
317
+ mock_client = MagicMock()
318
+ policy_arn = f"arn:aws:iam::{self.account_id}:policy/test-policy"
319
+ mock_paginator = MagicMock()
320
+ mock_paginator.paginate.return_value = [
321
+ {
322
+ "Policies": [
323
+ {
324
+ "PolicyName": "test-policy",
325
+ "PolicyId": "ANPAI123",
326
+ "Arn": policy_arn,
327
+ "CreateDate": "2024-01-01",
328
+ "UpdateDate": "2024-01-01",
329
+ }
330
+ ]
331
+ }
332
+ ]
333
+ mock_client.get_paginator.return_value = mock_paginator
334
+
335
+ result = self.collector._list_policies(mock_client)
336
+
337
+ assert len(result) == 1
338
+ assert result[0]["PolicyName"] == "test-policy"
339
+ assert result[0]["Region"] == self.region
340
+
341
+ def test_list_policies_filters_by_account_id(self):
342
+ """Test policies listing filters by account ID."""
343
+ mock_client = MagicMock()
344
+ policy_arn_match = f"arn:aws:iam::{self.account_id}:policy/test-policy"
345
+ policy_arn_no_match = "arn:aws:iam::999999999999:policy/other-policy"
346
+
347
+ mock_paginator = MagicMock()
348
+ mock_paginator.paginate.return_value = [
349
+ {
350
+ "Policies": [
351
+ {
352
+ "PolicyName": "test-policy",
353
+ "PolicyId": "ANPAI123",
354
+ "Arn": policy_arn_match,
355
+ "CreateDate": "2024-01-01",
356
+ "UpdateDate": "2024-01-01",
357
+ },
358
+ {
359
+ "PolicyName": "other-policy",
360
+ "PolicyId": "ANPAI456",
361
+ "Arn": policy_arn_no_match,
362
+ "CreateDate": "2024-01-01",
363
+ "UpdateDate": "2024-01-01",
364
+ },
365
+ ]
366
+ }
367
+ ]
368
+ mock_client.get_paginator.return_value = mock_paginator
369
+
370
+ result = self.collector._list_policies(mock_client)
371
+
372
+ assert len(result) == 1
373
+ assert result[0]["PolicyName"] == "test-policy"
374
+
375
+ def test_list_policies_access_denied(self):
376
+ """Test policies listing with access denied."""
377
+ mock_client = MagicMock()
378
+ mock_paginator = MagicMock()
379
+ error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
380
+ mock_paginator.paginate.side_effect = ClientError(error_response, "list_policies")
381
+ mock_client.get_paginator.return_value = mock_paginator
382
+
383
+ result = self.collector._list_policies(mock_client)
384
+
385
+ assert result == []
386
+
387
+ def test_list_access_keys_success(self):
388
+ """Test successful access keys listing."""
389
+ mock_client = MagicMock()
390
+ mock_client.list_access_keys.return_value = {
391
+ "AccessKeyMetadata": [{"AccessKeyId": "AKIAI123", "Status": "Active", "CreateDate": "2024-01-01"}]
392
+ }
393
+
394
+ result = self.collector._list_access_keys(mock_client, "test-user")
395
+
396
+ assert len(result) == 1
397
+ assert result[0]["AccessKeyId"] == "AKIAI123"
398
+ assert result[0]["UserName"] == "test-user"
399
+ assert result[0]["Region"] == self.region
400
+
401
+ def test_list_access_keys_access_denied(self):
402
+ """Test access keys listing with access denied."""
403
+ mock_client = MagicMock()
404
+ error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
405
+ mock_client.list_access_keys.side_effect = ClientError(error_response, "list_access_keys")
406
+
407
+ result = self.collector._list_access_keys(mock_client, "test-user")
408
+
409
+ assert result == []
410
+
411
+ def test_list_mfa_devices_success(self):
412
+ """Test successful MFA devices listing."""
413
+ mock_client = MagicMock()
414
+ mock_client.list_mfa_devices.return_value = {
415
+ "MFADevices": [{"SerialNumber": "arn:aws:iam::123:mfa/device", "EnableDate": "2024-01-01"}]
416
+ }
417
+
418
+ result = self.collector._list_mfa_devices(mock_client, "test-user")
419
+
420
+ assert len(result) == 1
421
+ assert result[0]["SerialNumber"] == "arn:aws:iam::123:mfa/device"
422
+ assert result[0]["UserName"] == "test-user"
423
+ assert result[0]["Region"] == self.region
424
+
425
+ def test_list_mfa_devices_access_denied(self):
426
+ """Test MFA devices listing with access denied."""
427
+ mock_client = MagicMock()
428
+ error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
429
+ mock_client.list_mfa_devices.side_effect = ClientError(error_response, "list_mfa_devices")
430
+
431
+ result = self.collector._list_mfa_devices(mock_client, "test-user")
432
+
433
+ assert result == []
434
+
435
+ def test_matches_account_id_with_matching_arn(self):
436
+ """Test account ID matching with matching ARN."""
437
+ arn = f"arn:aws:iam::{self.account_id}:user/test-user"
438
+ assert self.collector._matches_account_id(arn) is True
439
+
440
+ def test_matches_account_id_with_non_matching_arn(self):
441
+ """Test account ID matching with non-matching ARN."""
442
+ arn = "arn:aws:iam::999999999999:user/test-user"
443
+ assert self.collector._matches_account_id(arn) is False
444
+
445
+ def test_matches_account_id_with_invalid_arn(self):
446
+ """Test account ID matching with invalid ARN."""
447
+ arn = "invalid-arn"
448
+ assert self.collector._matches_account_id(arn) is False
449
+
450
+ def test_matches_account_id_without_filter(self):
451
+ """Test account ID matching without account filter."""
452
+ collector = IAMCollector(self.mock_session, self.region)
453
+ arn = "arn:aws:iam::999999999999:user/test-user"
454
+ assert collector._matches_account_id(arn) is True
455
+
456
+
457
+ if __name__ == "__main__":
458
+ pytest.main([__file__, "-v"])