regscale-cli 6.27.2.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 (140) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +1 -0
  3. regscale/core/app/internal/control_editor.py +73 -21
  4. regscale/core/app/internal/login.py +4 -1
  5. regscale/core/app/internal/model_editor.py +219 -64
  6. regscale/core/app/utils/app_utils.py +11 -2
  7. regscale/core/login.py +21 -4
  8. regscale/core/utils/date.py +77 -1
  9. regscale/dev/cli.py +26 -0
  10. regscale/dev/version.py +72 -0
  11. regscale/integrations/commercial/__init__.py +15 -1
  12. regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
  13. regscale/integrations/commercial/amazon/amazon/common.py +204 -0
  14. regscale/integrations/commercial/amazon/common.py +48 -58
  15. regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
  16. regscale/integrations/commercial/aws/cli.py +3093 -55
  17. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  18. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  19. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  20. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  21. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  22. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  23. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  24. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  25. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  26. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  27. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  28. regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
  29. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  30. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  31. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  32. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  33. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  34. regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
  35. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  36. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  37. regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
  38. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  39. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  40. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  41. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  42. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  43. regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
  44. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  45. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  46. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  47. regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
  48. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  49. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  50. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  51. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  52. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  53. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  54. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  55. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  56. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  57. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  58. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  59. regscale/integrations/commercial/aws/scanner.py +853 -205
  60. regscale/integrations/commercial/aws/security_hub.py +319 -0
  61. regscale/integrations/commercial/aws/session_manager.py +282 -0
  62. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  63. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  64. regscale/integrations/commercial/synqly/query_builder.py +4 -1
  65. regscale/integrations/compliance_integration.py +308 -38
  66. regscale/integrations/control_matcher.py +78 -23
  67. regscale/integrations/due_date_handler.py +3 -0
  68. regscale/integrations/public/csam/csam.py +572 -763
  69. regscale/integrations/public/csam/csam_agency_defined.py +179 -0
  70. regscale/integrations/public/csam/csam_common.py +154 -0
  71. regscale/integrations/public/csam/csam_controls.py +432 -0
  72. regscale/integrations/public/csam/csam_poam.py +124 -0
  73. regscale/integrations/public/fedramp/click.py +17 -4
  74. regscale/integrations/public/fedramp/fedramp_cis_crm.py +271 -62
  75. regscale/integrations/public/fedramp/poam/scanner.py +74 -7
  76. regscale/integrations/scanner_integration.py +415 -85
  77. regscale/models/integration_models/cisa_kev_data.json +80 -20
  78. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  79. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +44 -3
  80. regscale/models/integration_models/synqly_models/ocsf_mapper.py +41 -12
  81. regscale/models/platform.py +3 -0
  82. regscale/models/regscale_models/__init__.py +5 -0
  83. regscale/models/regscale_models/assessment.py +2 -1
  84. regscale/models/regscale_models/component.py +1 -1
  85. regscale/models/regscale_models/control_implementation.py +55 -24
  86. regscale/models/regscale_models/control_objective.py +74 -5
  87. regscale/models/regscale_models/file.py +2 -0
  88. regscale/models/regscale_models/issue.py +2 -5
  89. regscale/models/regscale_models/organization.py +3 -0
  90. regscale/models/regscale_models/regscale_model.py +17 -5
  91. regscale/models/regscale_models/security_plan.py +1 -0
  92. regscale/regscale.py +11 -1
  93. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
  94. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +140 -57
  95. tests/regscale/core/test_login.py +171 -4
  96. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  97. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  98. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  99. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  100. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  101. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  102. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  103. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  104. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  105. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  106. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  107. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  108. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  109. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  110. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  111. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  112. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  113. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  114. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  115. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  116. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  117. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  118. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  119. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  120. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  121. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  122. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  123. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  124. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  125. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  126. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  127. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  128. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  129. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  130. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  131. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  132. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  133. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  134. tests/regscale/integrations/commercial/test_aws.py +55 -56
  135. tests/regscale/integrations/test_control_matcher.py +24 -0
  136. tests/regscale/models/test_control_implementation.py +118 -3
  137. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
  138. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
  139. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
  140. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,373 @@
1
+ """Unit tests for AWS Scanner evidence integration."""
2
+
3
+ import unittest
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import pytest
7
+
8
+ from regscale.integrations.commercial.aws.scanner import AWSInventoryIntegration
9
+
10
+
11
+ class TestScannerEvidenceIntegration(unittest.TestCase):
12
+ """Test cases for scanner evidence integration."""
13
+
14
+ def setUp(self):
15
+ """Set up test fixtures."""
16
+ self.plan_id = 123
17
+ self.scanner = AWSInventoryIntegration(plan_id=self.plan_id)
18
+
19
+ @patch("regscale.core.app.api.Api")
20
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
21
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
22
+ def test_process_findings_with_evidence_native_only(self, mock_evidence_gen, mock_mapper, mock_api):
23
+ """Test processing findings with native format only."""
24
+ findings = [
25
+ {
26
+ "Id": "finding-1",
27
+ "Title": "Test Finding 1",
28
+ "Severity": {"Label": "HIGH"},
29
+ "Resources": [{"Type": "AwsEc2Instance", "Id": "i-123"}],
30
+ }
31
+ ]
32
+
33
+ # Mock parse_finding to return IntegrationFinding objects
34
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
35
+ mock_integration_finding = MagicMock()
36
+ mock_parse.return_value = [mock_integration_finding]
37
+
38
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
39
+ findings=findings,
40
+ service_name="SecurityHub",
41
+ generate_evidence=False,
42
+ ssp_id=None,
43
+ control_ids=None,
44
+ ocsf_format=False,
45
+ )
46
+
47
+ # Verify
48
+ assert len(result_findings) == 1
49
+ assert result_evidence is None
50
+ mock_parse.assert_called_once_with(findings[0])
51
+ mock_mapper.assert_not_called()
52
+ mock_evidence_gen.assert_not_called()
53
+
54
+ @patch("regscale.core.app.api.Api")
55
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
56
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
57
+ def test_process_findings_with_ocsf_only(self, mock_evidence_gen, mock_mapper, mock_api):
58
+ """Test processing findings with OCSF format."""
59
+ findings = [
60
+ {
61
+ "Id": "finding-1",
62
+ "Severity": {"Label": "HIGH"},
63
+ "Resources": [{"Type": "AwsEc2Instance", "Id": "i-123"}],
64
+ }
65
+ ]
66
+
67
+ # Setup mocks
68
+ mock_mapper_instance = MagicMock()
69
+ mock_mapper.return_value = mock_mapper_instance
70
+ mock_mapper_instance.securityhub_to_ocsf.return_value = {"class_uid": 2001}
71
+
72
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
73
+ mock_integration_finding = MagicMock()
74
+ mock_parse.return_value = [mock_integration_finding]
75
+
76
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
77
+ findings=findings,
78
+ service_name="SecurityHub",
79
+ generate_evidence=False,
80
+ ocsf_format=True,
81
+ )
82
+
83
+ # Verify OCSF mapper called
84
+ mock_mapper_instance.securityhub_to_ocsf.assert_called_once_with(findings[0])
85
+ assert result_evidence is None
86
+
87
+ @patch("regscale.core.app.api.Api")
88
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
89
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
90
+ def test_process_findings_with_evidence_generation(self, mock_evidence_gen, mock_mapper, mock_api):
91
+ """Test processing findings with evidence generation."""
92
+ findings = [
93
+ {
94
+ "Id": "finding-1",
95
+ "Severity": {"Label": "HIGH"},
96
+ "Resources": [{"Type": "AwsEc2Instance", "Id": "i-123"}],
97
+ }
98
+ ]
99
+
100
+ # Setup mocks
101
+ mock_evidence_instance = MagicMock()
102
+ mock_evidence_instance.id = 12345
103
+ mock_evidence_instance.title = "Test Evidence"
104
+
105
+ mock_gen_instance = MagicMock()
106
+ mock_gen_instance.create_evidence_from_scan.return_value = mock_evidence_instance
107
+ mock_evidence_gen.return_value = mock_gen_instance
108
+
109
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
110
+ mock_integration_finding = MagicMock()
111
+ mock_parse.return_value = [mock_integration_finding]
112
+
113
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
114
+ findings=findings,
115
+ service_name="SecurityHub",
116
+ generate_evidence=True,
117
+ ssp_id=456,
118
+ control_ids=[789, 790],
119
+ ocsf_format=False,
120
+ )
121
+
122
+ # Verify evidence generator called with Api instance
123
+ mock_api.assert_called_once()
124
+ mock_evidence_gen.assert_called_once_with(api=mock_api.return_value, ssp_id=456)
125
+ mock_gen_instance.create_evidence_from_scan.assert_called_once_with(
126
+ service_name="SecurityHub",
127
+ findings=findings,
128
+ ocsf_data=None,
129
+ control_ids=[789, 790],
130
+ )
131
+ assert result_evidence == mock_evidence_instance
132
+
133
+ @patch("regscale.core.app.api.Api")
134
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
135
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
136
+ def test_process_findings_guardduty_with_ocsf(self, mock_evidence_gen, mock_mapper, mock_api):
137
+ """Test processing GuardDuty findings with OCSF."""
138
+ findings = [
139
+ {
140
+ "Id": "guardduty-1",
141
+ "Severity": 8.0,
142
+ "Type": "UnauthorizedAccess:IAMUser/MaliciousIPCaller.Custom",
143
+ }
144
+ ]
145
+
146
+ # Setup mocks
147
+ mock_mapper_instance = MagicMock()
148
+ mock_mapper.return_value = mock_mapper_instance
149
+ mock_mapper_instance.guardduty_to_ocsf.return_value = {"class_uid": 2004}
150
+
151
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
152
+ mock_integration_finding = MagicMock()
153
+ mock_parse.return_value = [mock_integration_finding]
154
+
155
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
156
+ findings=findings,
157
+ service_name="GuardDuty",
158
+ ocsf_format=True,
159
+ )
160
+
161
+ # Verify GuardDuty mapper called
162
+ mock_mapper_instance.guardduty_to_ocsf.assert_called_once_with(findings[0])
163
+
164
+ @patch("regscale.core.app.api.Api")
165
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
166
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
167
+ def test_process_findings_cloudtrail_with_ocsf(self, mock_evidence_gen, mock_mapper, mock_api):
168
+ """Test processing CloudTrail events with OCSF."""
169
+ findings = [
170
+ {
171
+ "EventName": "DescribeInstances",
172
+ "EventSource": "ec2.amazonaws.com",
173
+ }
174
+ ]
175
+
176
+ # Setup mocks
177
+ mock_mapper_instance = MagicMock()
178
+ mock_mapper.return_value = mock_mapper_instance
179
+ mock_mapper_instance.cloudtrail_event_to_ocsf.return_value = {"class_uid": 3005}
180
+
181
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
182
+ mock_integration_finding = MagicMock()
183
+ mock_parse.return_value = [mock_integration_finding]
184
+
185
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
186
+ findings=findings,
187
+ service_name="CloudTrail",
188
+ ocsf_format=True,
189
+ )
190
+
191
+ # Verify CloudTrail mapper called
192
+ mock_mapper_instance.cloudtrail_event_to_ocsf.assert_called_once_with(findings[0])
193
+
194
+ @patch("regscale.core.app.api.Api")
195
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
196
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
197
+ def test_process_findings_with_both_formats(self, mock_evidence_gen, mock_mapper, mock_api):
198
+ """Test processing findings with both native and OCSF formats."""
199
+ findings = [
200
+ {
201
+ "Id": "finding-1",
202
+ "Severity": {"Label": "HIGH"},
203
+ "Resources": [{"Type": "AwsEc2Instance", "Id": "i-123"}],
204
+ }
205
+ ]
206
+
207
+ # Setup mocks
208
+ mock_mapper_instance = MagicMock()
209
+ mock_mapper.return_value = mock_mapper_instance
210
+ mock_mapper_instance.securityhub_to_ocsf.return_value = {"class_uid": 2001}
211
+
212
+ mock_evidence_instance = MagicMock()
213
+ mock_gen_instance = MagicMock()
214
+ mock_gen_instance.create_evidence_from_scan.return_value = mock_evidence_instance
215
+ mock_evidence_gen.return_value = mock_gen_instance
216
+
217
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
218
+ mock_integration_finding = MagicMock()
219
+ mock_parse.return_value = [mock_integration_finding]
220
+
221
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
222
+ findings=findings,
223
+ service_name="SecurityHub",
224
+ generate_evidence=True,
225
+ ssp_id=456,
226
+ ocsf_format=True,
227
+ )
228
+
229
+ # Verify both OCSF mapper and evidence generator called
230
+ mock_mapper_instance.securityhub_to_ocsf.assert_called_once()
231
+ mock_gen_instance.create_evidence_from_scan.assert_called_once()
232
+
233
+ # Verify OCSF data passed to evidence generator
234
+ call_kwargs = mock_gen_instance.create_evidence_from_scan.call_args[1]
235
+ assert call_kwargs["ocsf_data"] is not None
236
+ assert len(call_kwargs["ocsf_data"]) == 1
237
+
238
+ @patch("regscale.core.app.api.Api")
239
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
240
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
241
+ def test_process_findings_multiple_findings(self, mock_evidence_gen, mock_mapper, mock_api):
242
+ """Test processing multiple findings."""
243
+ findings = [
244
+ {
245
+ "Id": "finding-1",
246
+ "Severity": {"Label": "HIGH"},
247
+ "Resources": [{"Type": "AwsEc2Instance", "Id": "i-123"}],
248
+ },
249
+ {
250
+ "Id": "finding-2",
251
+ "Severity": {"Label": "MEDIUM"},
252
+ "Resources": [{"Type": "AwsS3Bucket", "Id": "bucket-1"}],
253
+ },
254
+ {
255
+ "Id": "finding-3",
256
+ "Severity": {"Label": "LOW"},
257
+ "Resources": [{"Type": "AwsIamRole", "Id": "role-1"}],
258
+ },
259
+ ]
260
+
261
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
262
+ mock_integration_finding = MagicMock()
263
+ # Each parse_finding returns a list with one finding
264
+ mock_parse.return_value = [mock_integration_finding]
265
+
266
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
267
+ findings=findings,
268
+ service_name="SecurityHub",
269
+ )
270
+
271
+ # Verify all findings parsed
272
+ assert mock_parse.call_count == 3
273
+ assert len(result_findings) == 3
274
+
275
+ @patch("regscale.core.app.api.Api")
276
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
277
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
278
+ def test_process_findings_with_multi_resource_finding(self, mock_evidence_gen, mock_mapper, mock_api):
279
+ """Test processing finding with multiple resources (yields multiple IntegrationFindings)."""
280
+ findings = [
281
+ {
282
+ "Id": "finding-1",
283
+ "Severity": {"Label": "HIGH"},
284
+ "Resources": [
285
+ {"Type": "AwsEc2Instance", "Id": "i-123"},
286
+ {"Type": "AwsEc2Instance", "Id": "i-456"},
287
+ ],
288
+ }
289
+ ]
290
+
291
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
292
+ # parse_finding returns multiple IntegrationFindings for multi-resource finding
293
+ mock_finding_1 = MagicMock()
294
+ mock_finding_2 = MagicMock()
295
+ mock_parse.return_value = [mock_finding_1, mock_finding_2]
296
+
297
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
298
+ findings=findings,
299
+ service_name="SecurityHub",
300
+ )
301
+
302
+ # Verify multiple IntegrationFindings returned
303
+ assert len(result_findings) == 2
304
+
305
+ @patch("regscale.core.app.api.Api")
306
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
307
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
308
+ def test_process_findings_empty_list(self, mock_evidence_gen, mock_mapper, mock_api):
309
+ """Test processing empty findings list."""
310
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
311
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
312
+ findings=[],
313
+ service_name="SecurityHub",
314
+ )
315
+
316
+ # Verify no findings processed
317
+ assert len(result_findings) == 0
318
+ assert result_evidence is None
319
+ mock_parse.assert_not_called()
320
+
321
+ @patch("regscale.core.app.api.Api")
322
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
323
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
324
+ def test_process_findings_no_control_ids(self, mock_evidence_gen, mock_mapper, mock_api):
325
+ """Test processing findings without control IDs."""
326
+ findings = [{"Id": "finding-1", "Severity": {"Label": "HIGH"}, "Resources": [{"Type": "AwsEc2Instance"}]}]
327
+
328
+ mock_evidence_instance = MagicMock()
329
+ mock_gen_instance = MagicMock()
330
+ mock_gen_instance.create_evidence_from_scan.return_value = mock_evidence_instance
331
+ mock_evidence_gen.return_value = mock_gen_instance
332
+
333
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
334
+ mock_parse.return_value = [MagicMock()]
335
+
336
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
337
+ findings=findings,
338
+ service_name="SecurityHub",
339
+ generate_evidence=True,
340
+ control_ids=None,
341
+ )
342
+
343
+ # Verify control_ids passed as None
344
+ call_kwargs = mock_gen_instance.create_evidence_from_scan.call_args[1]
345
+ assert call_kwargs["control_ids"] is None
346
+
347
+ @patch("regscale.core.app.api.Api")
348
+ @patch("regscale.integrations.commercial.aws.ocsf.mapper.AWSOCSFMapper")
349
+ @patch("regscale.integrations.commercial.aws.evidence_generator.AWSEvidenceGenerator")
350
+ def test_process_findings_no_ssp_id(self, mock_evidence_gen, mock_mapper, mock_api):
351
+ """Test processing findings without SSP ID."""
352
+ findings = [{"Id": "finding-1", "Severity": {"Label": "HIGH"}, "Resources": [{"Type": "AwsEc2Instance"}]}]
353
+
354
+ mock_gen_instance = MagicMock()
355
+ mock_evidence_gen.return_value = mock_gen_instance
356
+
357
+ with patch.object(self.scanner, "parse_finding") as mock_parse:
358
+ mock_parse.return_value = [MagicMock()]
359
+
360
+ result_findings, result_evidence = self.scanner.process_findings_with_evidence(
361
+ findings=findings,
362
+ service_name="SecurityHub",
363
+ generate_evidence=True,
364
+ ssp_id=None,
365
+ )
366
+
367
+ # Verify AWSEvidenceGenerator initialized with None SSP ID
368
+ mock_api.assert_called_once()
369
+ mock_evidence_gen.assert_called_once_with(api=mock_api.return_value, ssp_id=None)
370
+
371
+
372
+ if __name__ == "__main__":
373
+ pytest.main([__file__, "-v"])