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,386 @@
1
+ """Unit tests for AWS Evidence Generator."""
2
+
3
+ import json
4
+ import unittest
5
+ from datetime import datetime, timedelta
6
+ from unittest.mock import MagicMock, call, patch
7
+
8
+ import pytest
9
+
10
+ from regscale.integrations.commercial.aws.evidence_generator import AWSEvidenceGenerator
11
+
12
+
13
+ class TestAWSEvidenceGenerator(unittest.TestCase):
14
+ """Test cases for AWSEvidenceGenerator."""
15
+
16
+ def setUp(self):
17
+ """Set up test fixtures."""
18
+ self.mock_api = MagicMock()
19
+ self.ssp_id = 456
20
+ self.generator = AWSEvidenceGenerator(api=self.mock_api, ssp_id=self.ssp_id)
21
+
22
+ def test_init(self):
23
+ """Test AWSEvidenceGenerator initialization."""
24
+ assert self.generator.api == self.mock_api
25
+ assert self.generator.ssp_id == self.ssp_id
26
+
27
+ def test_init_without_ssp_id(self):
28
+ """Test AWSEvidenceGenerator initialization without SSP ID."""
29
+ generator = AWSEvidenceGenerator(api=self.mock_api)
30
+ assert generator.api == self.mock_api
31
+ assert generator.ssp_id is None
32
+
33
+ @patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
34
+ @patch("regscale.integrations.commercial.aws.evidence_generator.Evidence")
35
+ @patch("regscale.integrations.commercial.aws.evidence_generator.File")
36
+ @patch("regscale.integrations.commercial.aws.evidence_generator.datetime")
37
+ def test_create_evidence_from_scan_success(self, mock_datetime, mock_file, mock_evidence, mock_evidence_mapping):
38
+ """Test successful evidence creation from scan."""
39
+ # Setup mocks
40
+ mock_now = datetime(2025, 10, 13, 12, 0, 0)
41
+ mock_datetime.now.return_value = mock_now
42
+
43
+ findings = [
44
+ {
45
+ "Severity": {"Label": "HIGH"},
46
+ "Title": "Test Finding 1",
47
+ },
48
+ {
49
+ "Severity": {"Label": "MEDIUM"},
50
+ "Title": "Test Finding 2",
51
+ },
52
+ ]
53
+
54
+ mock_evidence_instance = MagicMock()
55
+ mock_evidence_instance.id = 12345
56
+ mock_evidence_instance.title = "SecurityHub Findings Scan - 2025-10-13"
57
+ mock_evidence.return_value = mock_evidence_instance
58
+ mock_evidence_instance.create.return_value = mock_evidence_instance
59
+
60
+ mock_file.upload_file_to_regscale.return_value = True
61
+
62
+ # Mock EvidenceMapping
63
+ mock_mapping_instance = MagicMock()
64
+ mock_evidence_mapping.return_value = mock_mapping_instance
65
+
66
+ # Execute
67
+ result = self.generator.create_evidence_from_scan(
68
+ service_name="SecurityHub",
69
+ findings=findings,
70
+ ocsf_data=None,
71
+ update_frequency=30,
72
+ control_ids=None,
73
+ )
74
+
75
+ # Verify evidence created
76
+ assert result == mock_evidence_instance
77
+ mock_evidence_instance.create.assert_called_once()
78
+
79
+ # Verify file uploads called
80
+ assert mock_file.upload_file_to_regscale.call_count == 1
81
+
82
+ # Verify SSP linking called (since self.ssp_id = 456 in setUp)
83
+ mock_evidence_mapping.assert_called_once()
84
+
85
+ @patch("regscale.integrations.commercial.aws.evidence_generator.logger")
86
+ def test_create_evidence_from_scan_no_findings(self, mock_logger):
87
+ """Test evidence creation with no findings."""
88
+ result = self.generator.create_evidence_from_scan(
89
+ service_name="SecurityHub",
90
+ findings=[],
91
+ ocsf_data=None,
92
+ )
93
+
94
+ assert result is None
95
+ mock_logger.warning.assert_called_once()
96
+
97
+ @patch("regscale.integrations.commercial.aws.evidence_generator.Evidence")
98
+ @patch("regscale.integrations.commercial.aws.evidence_generator.datetime")
99
+ def test_create_evidence_from_scan_creation_failure(self, mock_datetime, mock_evidence):
100
+ """Test evidence creation failure."""
101
+ mock_now = datetime(2025, 10, 13, 12, 0, 0)
102
+ mock_datetime.now.return_value = mock_now
103
+
104
+ findings = [{"Severity": {"Label": "HIGH"}}]
105
+
106
+ mock_evidence_instance = MagicMock()
107
+ mock_evidence_instance.create.return_value = None
108
+ mock_evidence.return_value = mock_evidence_instance
109
+
110
+ result = self.generator.create_evidence_from_scan(
111
+ service_name="SecurityHub",
112
+ findings=findings,
113
+ )
114
+
115
+ assert result is None
116
+
117
+ @patch("regscale.integrations.commercial.aws.evidence_generator.Evidence")
118
+ @patch("regscale.integrations.commercial.aws.evidence_generator.datetime")
119
+ def test_create_evidence_from_scan_exception(self, mock_datetime, mock_evidence):
120
+ """Test evidence creation with exception."""
121
+ mock_now = datetime(2025, 10, 13, 12, 0, 0)
122
+ mock_datetime.now.return_value = mock_now
123
+
124
+ findings = [{"Severity": {"Label": "HIGH"}}]
125
+
126
+ mock_evidence.side_effect = Exception("Test error")
127
+
128
+ result = self.generator.create_evidence_from_scan(
129
+ service_name="SecurityHub",
130
+ findings=findings,
131
+ )
132
+
133
+ assert result is None
134
+
135
+ def test_count_severities_guardduty(self):
136
+ """Test severity counting for GuardDuty findings."""
137
+ findings = [
138
+ {"Severity": 8.0}, # HIGH
139
+ {"Severity": 7.5}, # HIGH
140
+ {"Severity": 5.0}, # MEDIUM
141
+ {"Severity": 4.0}, # MEDIUM
142
+ {"Severity": 2.0}, # LOW
143
+ ]
144
+
145
+ result = self.generator._count_severities(findings, "GuardDuty")
146
+
147
+ assert result["HIGH"] == 2
148
+ assert result["MEDIUM"] == 2
149
+ assert result["LOW"] == 1
150
+ assert result["CRITICAL"] == 0
151
+
152
+ def test_count_severities_securityhub(self):
153
+ """Test severity counting for Security Hub findings."""
154
+ findings = [
155
+ {"Severity": {"Label": "CRITICAL"}},
156
+ {"Severity": {"Label": "HIGH"}},
157
+ {"Severity": {"Label": "MEDIUM"}},
158
+ {"Severity": {"Label": "LOW"}},
159
+ {"Severity": {"Label": "INFORMATIONAL"}},
160
+ ]
161
+
162
+ result = self.generator._count_severities(findings, "SecurityHub")
163
+
164
+ assert result["CRITICAL"] == 1
165
+ assert result["HIGH"] == 1
166
+ assert result["MEDIUM"] == 1
167
+ assert result["LOW"] == 1
168
+ assert result["INFO"] == 0
169
+
170
+ def test_count_severities_cloudtrail(self):
171
+ """Test severity counting for CloudTrail events."""
172
+ findings = [
173
+ {"EventName": "DescribeInstances"},
174
+ {"EventName": "CreateBucket"},
175
+ ]
176
+
177
+ result = self.generator._count_severities(findings, "CloudTrail")
178
+
179
+ assert result["INFO"] == 2
180
+ assert result["HIGH"] == 0
181
+
182
+ def test_build_evidence_description(self):
183
+ """Test building evidence description."""
184
+ severity_counts = {
185
+ "CRITICAL": 2,
186
+ "HIGH": 5,
187
+ "MEDIUM": 10,
188
+ "LOW": 3,
189
+ "INFO": 0,
190
+ }
191
+
192
+ result = self.generator._build_evidence_description(
193
+ service_name="SecurityHub",
194
+ total_findings=20,
195
+ severity_counts=severity_counts,
196
+ ocsf_data=None,
197
+ )
198
+
199
+ assert "SecurityHub" in result
200
+ assert "Total findings: 20" in result
201
+ assert "CRITICAL: 2" in result
202
+ assert "HIGH: 5" in result
203
+ assert "MEDIUM: 10" in result
204
+ assert "LOW: 3" in result
205
+ assert "INFO: 0" not in result # Should not show zero counts
206
+ assert "securityhub_findings_native.jsonl" in result
207
+ assert "securityhub_findings_ocsf.jsonl" not in result
208
+
209
+ def test_build_evidence_description_with_ocsf(self):
210
+ """Test building evidence description with OCSF data."""
211
+ severity_counts = {"HIGH": 5}
212
+ ocsf_data = [{"class_uid": 2004}]
213
+
214
+ result = self.generator._build_evidence_description(
215
+ service_name="SecurityHub",
216
+ total_findings=5,
217
+ severity_counts=severity_counts,
218
+ ocsf_data=ocsf_data,
219
+ )
220
+
221
+ assert "securityhub_findings_ocsf.jsonl" in result
222
+
223
+ @patch("regscale.integrations.commercial.aws.evidence_generator.File")
224
+ def test_attach_findings_files_native_only(self, mock_file):
225
+ """Test attaching findings files (native only)."""
226
+ findings = [
227
+ {"Id": "finding-1", "Title": "Test 1"},
228
+ {"Id": "finding-2", "Title": "Test 2"},
229
+ ]
230
+
231
+ mock_file.upload_file_to_regscale.return_value = True
232
+
233
+ self.generator._attach_findings_files(
234
+ evidence_id=123,
235
+ findings=findings,
236
+ ocsf_data=None,
237
+ service_name="SecurityHub",
238
+ )
239
+
240
+ # Verify native file uploaded
241
+ assert mock_file.upload_file_to_regscale.call_count == 1
242
+ call_args = mock_file.upload_file_to_regscale.call_args
243
+ assert call_args[1]["parent_id"] == 123
244
+ assert call_args[1]["parent_module"] == "evidence"
245
+ assert "securityhub_findings_native.jsonl" in call_args[1]["file_name"]
246
+ assert "aws,securityhub,native" in call_args[1]["tags"]
247
+
248
+ @patch("regscale.integrations.commercial.aws.evidence_generator.File")
249
+ def test_attach_findings_files_with_ocsf(self, mock_file):
250
+ """Test attaching findings files with OCSF."""
251
+ findings = [{"Id": "finding-1"}]
252
+ ocsf_data = [{"class_uid": 2004}]
253
+
254
+ mock_file.upload_file_to_regscale.return_value = True
255
+
256
+ self.generator._attach_findings_files(
257
+ evidence_id=123,
258
+ findings=findings,
259
+ ocsf_data=ocsf_data,
260
+ service_name="SecurityHub",
261
+ )
262
+
263
+ # Verify both files uploaded
264
+ assert mock_file.upload_file_to_regscale.call_count == 2
265
+
266
+ @patch("regscale.integrations.commercial.aws.evidence_generator.File")
267
+ @patch("regscale.integrations.commercial.aws.evidence_generator.logger")
268
+ def test_attach_findings_files_upload_failure(self, mock_logger, mock_file):
269
+ """Test file attachment with upload failure."""
270
+ findings = [{"Id": "finding-1"}]
271
+ mock_file.upload_file_to_regscale.return_value = False
272
+
273
+ self.generator._attach_findings_files(
274
+ evidence_id=123,
275
+ findings=findings,
276
+ ocsf_data=None,
277
+ service_name="SecurityHub",
278
+ )
279
+
280
+ # Verify warning logged
281
+ assert mock_logger.warning.called
282
+
283
+ @patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
284
+ def test_link_to_ssp_success(self, mock_mapping):
285
+ """Test linking evidence to SSP."""
286
+ mock_mapping_instance = MagicMock()
287
+ mock_mapping.return_value = mock_mapping_instance
288
+
289
+ self.generator._link_to_ssp(evidence_id=123)
290
+
291
+ # Verify mapping created
292
+ mock_mapping.assert_called_once_with(
293
+ evidenceID=123,
294
+ mappedID=self.ssp_id,
295
+ mappingType="securityplans",
296
+ )
297
+ mock_mapping_instance.create.assert_called_once()
298
+
299
+ @patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
300
+ @patch("regscale.integrations.commercial.aws.evidence_generator.logger")
301
+ def test_link_to_ssp_failure(self, mock_logger, mock_mapping):
302
+ """Test linking evidence to SSP with failure."""
303
+ mock_mapping_instance = MagicMock()
304
+ mock_mapping_instance.create.side_effect = Exception("Test error")
305
+ mock_mapping.return_value = mock_mapping_instance
306
+
307
+ self.generator._link_to_ssp(evidence_id=123)
308
+
309
+ # Verify warning logged
310
+ mock_logger.warning.assert_called_once()
311
+
312
+ def test_link_to_ssp_no_ssp_id(self):
313
+ """Test linking to SSP when no SSP ID configured."""
314
+ generator = AWSEvidenceGenerator(api=self.mock_api)
315
+ # Should not raise exception
316
+ generator._link_to_ssp(evidence_id=123)
317
+
318
+ @patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
319
+ def test_link_to_controls_success(self, mock_mapping):
320
+ """Test linking evidence to controls."""
321
+ control_ids = [789, 790, 791]
322
+ mock_mapping_instance = MagicMock()
323
+ mock_mapping.return_value = mock_mapping_instance
324
+
325
+ self.generator._link_to_controls(evidence_id=123, control_ids=control_ids)
326
+
327
+ # Verify all mappings created
328
+ assert mock_mapping.call_count == 3
329
+ assert mock_mapping_instance.create.call_count == 3
330
+
331
+ @patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
332
+ @patch("regscale.integrations.commercial.aws.evidence_generator.logger")
333
+ def test_link_to_controls_partial_failure(self, mock_logger, mock_mapping):
334
+ """Test linking to controls with partial failure."""
335
+ control_ids = [789, 790]
336
+ mock_mapping_instance = MagicMock()
337
+ mock_mapping_instance.create.side_effect = [None, Exception("Test error")]
338
+ mock_mapping.return_value = mock_mapping_instance
339
+
340
+ self.generator._link_to_controls(evidence_id=123, control_ids=control_ids)
341
+
342
+ # Verify warning logged for failure
343
+ mock_logger.warning.assert_called_once()
344
+ assert mock_mapping.call_count == 2
345
+
346
+ @patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
347
+ @patch("regscale.integrations.commercial.aws.evidence_generator.Evidence")
348
+ @patch("regscale.integrations.commercial.aws.evidence_generator.File")
349
+ @patch("regscale.integrations.commercial.aws.evidence_generator.datetime")
350
+ def test_create_evidence_with_all_options(self, mock_datetime, mock_file, mock_evidence, mock_evidence_mapping):
351
+ """Test evidence creation with all options enabled."""
352
+ mock_now = datetime(2025, 10, 13, 12, 0, 0)
353
+ mock_datetime.now.return_value = mock_now
354
+
355
+ findings = [{"Severity": {"Label": "HIGH"}}]
356
+ ocsf_data = [{"class_uid": 2004}]
357
+ control_ids = [789, 790]
358
+
359
+ mock_evidence_instance = MagicMock()
360
+ mock_evidence_instance.id = 12345
361
+ mock_evidence.return_value = mock_evidence_instance
362
+ mock_evidence_instance.create.return_value = mock_evidence_instance
363
+
364
+ mock_file.upload_file_to_regscale.return_value = True
365
+
366
+ # Mock EvidenceMapping
367
+ mock_mapping_instance = MagicMock()
368
+ mock_evidence_mapping.return_value = mock_mapping_instance
369
+
370
+ result = self.generator.create_evidence_from_scan(
371
+ service_name="SecurityHub",
372
+ findings=findings,
373
+ ocsf_data=ocsf_data,
374
+ update_frequency=90,
375
+ control_ids=control_ids,
376
+ )
377
+
378
+ assert result is not None
379
+ # Verify both file uploads called
380
+ assert mock_file.upload_file_to_regscale.call_count == 2
381
+ # Verify SSP and control mappings called
382
+ assert mock_evidence_mapping.call_count == 3 # 1 SSP + 2 controls
383
+
384
+
385
+ if __name__ == "__main__":
386
+ pytest.main([__file__, "-v"])