regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.1.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.
- regscale/_version.py +1 -1
- regscale/core/app/utils/app_utils.py +11 -2
- regscale/dev/cli.py +26 -0
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +15 -1
- regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/amazon/common.py +204 -0
- regscale/integrations/commercial/amazon/common.py +48 -58
- regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
- regscale/integrations/commercial/aws/cli.py +3093 -55
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +851 -206
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/commercial/synqly/ticketing.py +27 -0
- regscale/integrations/compliance_integration.py +308 -38
- regscale/integrations/due_date_handler.py +3 -0
- regscale/integrations/scanner_integration.py +399 -84
- regscale/models/integration_models/cisa_kev_data.json +65 -5
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/issue.py +2 -5
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/RECORD +113 -34
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/test_aws.py +55 -56
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"""Unit tests for AWS Inspector 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.inspector import InspectorCollector
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestInspectorCollector(unittest.TestCase):
|
|
13
|
+
"""Test cases for InspectorCollector."""
|
|
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 = InspectorCollector(self.mock_session, self.region, self.account_id)
|
|
21
|
+
|
|
22
|
+
def test_init(self):
|
|
23
|
+
"""Test InspectorCollector 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 InspectorCollector initialization without account ID."""
|
|
30
|
+
collector = InspectorCollector(self.mock_session, self.region)
|
|
31
|
+
assert collector.account_id is None
|
|
32
|
+
|
|
33
|
+
@patch("regscale.integrations.commercial.aws.inventory.resources.inspector.logger")
|
|
34
|
+
def test_collect_success(self, mock_logger):
|
|
35
|
+
"""Test successful collection of Inspector resources."""
|
|
36
|
+
mock_client = MagicMock()
|
|
37
|
+
self.mock_session.client.return_value = mock_client
|
|
38
|
+
|
|
39
|
+
mock_client.batch_get_account_status.return_value = {
|
|
40
|
+
"accounts": [{"accountId": self.account_id, "state": {"status": "ENABLED"}}]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
mock_client.list_coverage.return_value = {
|
|
44
|
+
"coveredResources": [
|
|
45
|
+
{"resourceId": "i-1234567890abcdef0", "resourceType": "AWS_EC2_INSTANCE", "accountId": self.account_id}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
mock_client.list_coverage_statistics.return_value = {
|
|
50
|
+
"countsByGroup": [{"count": 10, "groupKey": "SCAN_STATUS"}]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
mock_client.list_findings.return_value = {
|
|
54
|
+
"findings": [
|
|
55
|
+
{
|
|
56
|
+
"findingArn": "arn:aws:inspector2:us-east-1:123456789012:finding/abc123",
|
|
57
|
+
"awsAccountId": self.account_id,
|
|
58
|
+
"severity": "HIGH",
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
mock_client.list_members.return_value = {
|
|
64
|
+
"members": [{"accountId": self.account_id, "relationshipStatus": "ENABLED"}]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
result = self.collector.collect()
|
|
68
|
+
|
|
69
|
+
assert "Findings" in result
|
|
70
|
+
assert "Coverage" in result
|
|
71
|
+
assert "AccountStatus" in result
|
|
72
|
+
assert "Members" in result
|
|
73
|
+
assert "CoverageStatistics" in result
|
|
74
|
+
assert len(result["Findings"]) == 1
|
|
75
|
+
assert len(result["Coverage"]) == 1
|
|
76
|
+
assert result["Findings"][0]["Region"] == self.region
|
|
77
|
+
|
|
78
|
+
@patch("regscale.integrations.commercial.aws.inventory.resources.inspector.logger")
|
|
79
|
+
def test_collect_handles_client_error(self, mock_logger):
|
|
80
|
+
"""Test collection handles ClientError."""
|
|
81
|
+
mock_client = MagicMock()
|
|
82
|
+
self.mock_session.client.return_value = mock_client
|
|
83
|
+
|
|
84
|
+
error_response = {"Error": {"Code": "InternalError", "Message": "Internal error"}}
|
|
85
|
+
mock_client.batch_get_account_status.side_effect = ClientError(error_response, "batch_get_account_status")
|
|
86
|
+
|
|
87
|
+
# Mock the other methods to return empty results
|
|
88
|
+
mock_client.list_coverage.return_value = {"coveredResources": []}
|
|
89
|
+
mock_client.list_coverage_statistics.return_value = {"countsByGroup": []}
|
|
90
|
+
mock_client.list_findings.return_value = {"findings": []}
|
|
91
|
+
mock_client.list_members.return_value = {"members": []}
|
|
92
|
+
|
|
93
|
+
result = self.collector.collect()
|
|
94
|
+
|
|
95
|
+
assert result["AccountStatus"] == {}
|
|
96
|
+
|
|
97
|
+
@patch("regscale.integrations.commercial.aws.inventory.resources.inspector.logger")
|
|
98
|
+
def test_collect_handles_unexpected_error(self, mock_logger):
|
|
99
|
+
"""Test collection handles unexpected errors."""
|
|
100
|
+
mock_client = MagicMock()
|
|
101
|
+
self.mock_session.client.return_value = mock_client
|
|
102
|
+
|
|
103
|
+
mock_client.batch_get_account_status.side_effect = Exception("Unexpected error")
|
|
104
|
+
|
|
105
|
+
# Mock the other methods to return empty results
|
|
106
|
+
mock_client.list_coverage.return_value = {"coveredResources": []}
|
|
107
|
+
mock_client.list_coverage_statistics.return_value = {"countsByGroup": []}
|
|
108
|
+
mock_client.list_findings.return_value = {"findings": []}
|
|
109
|
+
mock_client.list_members.return_value = {"members": []}
|
|
110
|
+
|
|
111
|
+
result = self.collector.collect()
|
|
112
|
+
|
|
113
|
+
assert result["AccountStatus"] == {}
|
|
114
|
+
mock_logger.error.assert_called()
|
|
115
|
+
|
|
116
|
+
def test_get_account_status_success(self):
|
|
117
|
+
"""Test successful account status retrieval."""
|
|
118
|
+
mock_client = MagicMock()
|
|
119
|
+
mock_client.batch_get_account_status.return_value = {
|
|
120
|
+
"accounts": [{"accountId": self.account_id, "state": {"status": "ENABLED"}}]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
result = self.collector._get_account_status(mock_client)
|
|
124
|
+
|
|
125
|
+
assert result["accountId"] == self.account_id
|
|
126
|
+
assert result["Region"] == self.region
|
|
127
|
+
|
|
128
|
+
def test_get_account_status_without_account_id(self):
|
|
129
|
+
"""Test account status without account ID filter."""
|
|
130
|
+
collector = InspectorCollector(self.mock_session, self.region)
|
|
131
|
+
mock_client = MagicMock()
|
|
132
|
+
mock_client.batch_get_account_status.return_value = {
|
|
133
|
+
"accounts": [{"accountId": "111111111111", "state": {"status": "ENABLED"}}]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
result = collector._get_account_status(mock_client)
|
|
137
|
+
|
|
138
|
+
assert result["accountId"] == "111111111111"
|
|
139
|
+
|
|
140
|
+
def test_get_account_status_access_denied(self):
|
|
141
|
+
"""Test account status with access denied."""
|
|
142
|
+
mock_client = MagicMock()
|
|
143
|
+
error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
|
|
144
|
+
mock_client.batch_get_account_status.side_effect = ClientError(error_response, "batch_get_account_status")
|
|
145
|
+
|
|
146
|
+
result = self.collector._get_account_status(mock_client)
|
|
147
|
+
|
|
148
|
+
assert result == {}
|
|
149
|
+
|
|
150
|
+
def test_get_account_status_empty_response(self):
|
|
151
|
+
"""Test account status with empty response."""
|
|
152
|
+
mock_client = MagicMock()
|
|
153
|
+
mock_client.batch_get_account_status.return_value = {"accounts": []}
|
|
154
|
+
|
|
155
|
+
result = self.collector._get_account_status(mock_client)
|
|
156
|
+
|
|
157
|
+
assert result == {}
|
|
158
|
+
|
|
159
|
+
def test_list_coverage_success(self):
|
|
160
|
+
"""Test successful coverage listing."""
|
|
161
|
+
mock_client = MagicMock()
|
|
162
|
+
mock_client.list_coverage.return_value = {
|
|
163
|
+
"coveredResources": [
|
|
164
|
+
{"resourceId": "i-1234567890abcdef0", "resourceType": "AWS_EC2_INSTANCE", "accountId": self.account_id}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
result = self.collector._list_coverage(mock_client)
|
|
169
|
+
|
|
170
|
+
assert len(result) == 1
|
|
171
|
+
assert result[0]["Region"] == self.region
|
|
172
|
+
|
|
173
|
+
def test_list_coverage_with_pagination(self):
|
|
174
|
+
"""Test coverage listing with pagination."""
|
|
175
|
+
mock_client = MagicMock()
|
|
176
|
+
mock_client.list_coverage.side_effect = [
|
|
177
|
+
{"coveredResources": [{"resourceId": "i-111"}], "nextToken": "token-1"},
|
|
178
|
+
{"coveredResources": [{"resourceId": "i-222"}]},
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
result = self.collector._list_coverage(mock_client)
|
|
182
|
+
|
|
183
|
+
assert len(result) == 2
|
|
184
|
+
assert result[0]["resourceId"] == "i-111"
|
|
185
|
+
assert result[1]["resourceId"] == "i-222"
|
|
186
|
+
|
|
187
|
+
def test_list_coverage_filters_by_account_id(self):
|
|
188
|
+
"""Test coverage listing filters by account ID."""
|
|
189
|
+
mock_client = MagicMock()
|
|
190
|
+
mock_client.list_coverage.return_value = {"coveredResources": []}
|
|
191
|
+
|
|
192
|
+
self.collector._list_coverage(mock_client)
|
|
193
|
+
|
|
194
|
+
call_args = mock_client.list_coverage.call_args[1]
|
|
195
|
+
assert "filterCriteria" in call_args
|
|
196
|
+
assert call_args["filterCriteria"]["accountId"][0]["value"] == self.account_id
|
|
197
|
+
|
|
198
|
+
def test_list_coverage_access_denied(self):
|
|
199
|
+
"""Test coverage listing with access denied."""
|
|
200
|
+
mock_client = MagicMock()
|
|
201
|
+
error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
|
|
202
|
+
mock_client.list_coverage.side_effect = ClientError(error_response, "list_coverage")
|
|
203
|
+
|
|
204
|
+
result = self.collector._list_coverage(mock_client)
|
|
205
|
+
|
|
206
|
+
assert result == []
|
|
207
|
+
|
|
208
|
+
def test_list_coverage_statistics_success(self):
|
|
209
|
+
"""Test successful coverage statistics retrieval."""
|
|
210
|
+
mock_client = MagicMock()
|
|
211
|
+
mock_client.list_coverage_statistics.return_value = {
|
|
212
|
+
"countsByGroup": [{"count": 10, "groupKey": "SCAN_STATUS"}]
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
result = self.collector._list_coverage_statistics(mock_client)
|
|
216
|
+
|
|
217
|
+
assert "Region" in result
|
|
218
|
+
assert "CountsByGroup" in result
|
|
219
|
+
assert len(result["CountsByGroup"]) == 1
|
|
220
|
+
|
|
221
|
+
def test_list_coverage_statistics_with_account_filter(self):
|
|
222
|
+
"""Test coverage statistics with account filter."""
|
|
223
|
+
mock_client = MagicMock()
|
|
224
|
+
mock_client.list_coverage_statistics.return_value = {"countsByGroup": []}
|
|
225
|
+
|
|
226
|
+
self.collector._list_coverage_statistics(mock_client)
|
|
227
|
+
|
|
228
|
+
call_args = mock_client.list_coverage_statistics.call_args[1]
|
|
229
|
+
assert "filterCriteria" in call_args
|
|
230
|
+
assert call_args["filterCriteria"]["accountId"][0]["value"] == self.account_id
|
|
231
|
+
|
|
232
|
+
def test_list_coverage_statistics_access_denied(self):
|
|
233
|
+
"""Test coverage statistics with access denied."""
|
|
234
|
+
mock_client = MagicMock()
|
|
235
|
+
error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
|
|
236
|
+
mock_client.list_coverage_statistics.side_effect = ClientError(error_response, "list_coverage_statistics")
|
|
237
|
+
|
|
238
|
+
result = self.collector._list_coverage_statistics(mock_client)
|
|
239
|
+
|
|
240
|
+
assert result == {}
|
|
241
|
+
|
|
242
|
+
def test_list_findings_success(self):
|
|
243
|
+
"""Test successful findings listing."""
|
|
244
|
+
mock_client = MagicMock()
|
|
245
|
+
mock_client.list_findings.return_value = {
|
|
246
|
+
"findings": [
|
|
247
|
+
{
|
|
248
|
+
"findingArn": "arn:aws:inspector2:us-east-1:123456789012:finding/abc123",
|
|
249
|
+
"awsAccountId": self.account_id,
|
|
250
|
+
"severity": "HIGH",
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
result = self.collector._list_findings(mock_client)
|
|
256
|
+
|
|
257
|
+
assert len(result) == 1
|
|
258
|
+
assert result[0]["Region"] == self.region
|
|
259
|
+
|
|
260
|
+
def test_list_findings_with_pagination(self):
|
|
261
|
+
"""Test findings listing with pagination."""
|
|
262
|
+
mock_client = MagicMock()
|
|
263
|
+
mock_client.list_findings.side_effect = [
|
|
264
|
+
{"findings": [{"findingArn": "arn-1"}], "nextToken": "token-1"},
|
|
265
|
+
{"findings": [{"findingArn": "arn-2"}]},
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
result = self.collector._list_findings(mock_client)
|
|
269
|
+
|
|
270
|
+
assert len(result) == 2
|
|
271
|
+
assert result[0]["findingArn"] == "arn-1"
|
|
272
|
+
assert result[1]["findingArn"] == "arn-2"
|
|
273
|
+
|
|
274
|
+
def test_list_findings_filters_by_account_id(self):
|
|
275
|
+
"""Test findings listing filters by account ID."""
|
|
276
|
+
mock_client = MagicMock()
|
|
277
|
+
mock_client.list_findings.return_value = {"findings": []}
|
|
278
|
+
|
|
279
|
+
self.collector._list_findings(mock_client)
|
|
280
|
+
|
|
281
|
+
call_args = mock_client.list_findings.call_args[1]
|
|
282
|
+
assert "filterCriteria" in call_args
|
|
283
|
+
assert call_args["filterCriteria"]["awsAccountId"][0]["value"] == self.account_id
|
|
284
|
+
|
|
285
|
+
def test_list_findings_access_denied(self):
|
|
286
|
+
"""Test findings listing with access denied."""
|
|
287
|
+
mock_client = MagicMock()
|
|
288
|
+
error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
|
|
289
|
+
mock_client.list_findings.side_effect = ClientError(error_response, "list_findings")
|
|
290
|
+
|
|
291
|
+
result = self.collector._list_findings(mock_client)
|
|
292
|
+
|
|
293
|
+
assert result == []
|
|
294
|
+
|
|
295
|
+
def test_list_members_success(self):
|
|
296
|
+
"""Test successful members listing."""
|
|
297
|
+
mock_client = MagicMock()
|
|
298
|
+
mock_client.list_members.return_value = {
|
|
299
|
+
"members": [{"accountId": self.account_id, "relationshipStatus": "ENABLED"}]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
result = self.collector._list_members(mock_client)
|
|
303
|
+
|
|
304
|
+
assert len(result) == 1
|
|
305
|
+
assert result[0]["Region"] == self.region
|
|
306
|
+
|
|
307
|
+
def test_list_members_with_pagination(self):
|
|
308
|
+
"""Test members listing with pagination."""
|
|
309
|
+
mock_client = MagicMock()
|
|
310
|
+
mock_client.list_members.side_effect = [
|
|
311
|
+
{"members": [{"accountId": "111111111111"}], "nextToken": "token-1"},
|
|
312
|
+
{"members": [{"accountId": "222222222222"}]},
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
result = self.collector._list_members(mock_client)
|
|
316
|
+
|
|
317
|
+
assert len(result) == 2
|
|
318
|
+
|
|
319
|
+
def test_list_members_filters_by_account_id(self):
|
|
320
|
+
"""Test members listing filters by account ID."""
|
|
321
|
+
mock_client = MagicMock()
|
|
322
|
+
mock_client.list_members.return_value = {
|
|
323
|
+
"members": [{"accountId": self.account_id}, {"accountId": "999999999999"}]
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
result = self.collector._list_members(mock_client)
|
|
327
|
+
|
|
328
|
+
# Should only include the matching account
|
|
329
|
+
assert len(result) == 2 # Both are returned because filtering happens at list level
|
|
330
|
+
|
|
331
|
+
def test_list_members_access_denied(self):
|
|
332
|
+
"""Test members listing with access denied."""
|
|
333
|
+
mock_client = MagicMock()
|
|
334
|
+
error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
|
|
335
|
+
mock_client.list_members.side_effect = ClientError(error_response, "list_members")
|
|
336
|
+
|
|
337
|
+
result = self.collector._list_members(mock_client)
|
|
338
|
+
|
|
339
|
+
assert result == []
|
|
340
|
+
|
|
341
|
+
def test_list_members_other_error(self):
|
|
342
|
+
"""Test members listing with other error."""
|
|
343
|
+
mock_client = MagicMock()
|
|
344
|
+
error_response = {"Error": {"Code": "InternalError", "Message": "Internal error"}}
|
|
345
|
+
mock_client.list_members.side_effect = ClientError(error_response, "list_members")
|
|
346
|
+
|
|
347
|
+
result = self.collector._list_members(mock_client)
|
|
348
|
+
|
|
349
|
+
assert result == []
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
if __name__ == "__main__":
|
|
353
|
+
pytest.main([__file__, "-v"])
|