regscale-cli 6.25.1.0__py3-none-any.whl → 6.26.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.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +18 -3
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/integrations/commercial/sicura/api.py +14 -13
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +49 -39
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/wizv2/click.py +26 -26
- regscale/integrations/commercial/wizv2/compliance_report.py +152 -157
- regscale/integrations/commercial/wizv2/scanner.py +3 -3
- regscale/integrations/compliance_integration.py +67 -2
- regscale/integrations/control_matcher.py +358 -0
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +1 -0
- regscale/integrations/public/cci_importer.py +37 -38
- regscale/integrations/public/fedramp/click.py +60 -2
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +150 -96
- regscale/models/integration_models/cisa_kev_data.json +154 -4
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +46 -21
- regscale/models/regscale_models/issue.py +256 -94
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/RECORD +80 -33
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3731 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +1814 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +350 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1469 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1351 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +750 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +264 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +624 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/test_control_matcher.py +1314 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_issue.py +378 -1
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Test for sonarcloud code scan integration in RegScale CLI
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from unittest.mock import MagicMock, patch
|
|
8
|
+
import pytest
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from tests import CLITestFixture
|
|
12
|
+
from regscale.integrations.commercial.sonarcloud import (
|
|
13
|
+
build_data,
|
|
14
|
+
create_alert_assessment,
|
|
15
|
+
create_alert_issues,
|
|
16
|
+
get_sonarcloud_results,
|
|
17
|
+
build_dataframes,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestSonarcloud(CLITestFixture):
|
|
22
|
+
"""
|
|
23
|
+
Test for sonarcloud integration
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def sample_vulnerability_data(self):
|
|
28
|
+
"""Fixture providing sample vulnerability data for multiple tests"""
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
"key": "test_issue_1",
|
|
32
|
+
"severity": "HIGH",
|
|
33
|
+
"component": "test_component_1",
|
|
34
|
+
"status": "OPEN",
|
|
35
|
+
"message": "Test message 1",
|
|
36
|
+
"creationDate": "2021-01-01T00:00:00",
|
|
37
|
+
"updateDate": "2021-01-01T00:00:00",
|
|
38
|
+
"type": "Vulnerability",
|
|
39
|
+
"days_elapsed": 5,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"key": "test_issue_2",
|
|
43
|
+
"severity": "LOW",
|
|
44
|
+
"component": "test_component_2",
|
|
45
|
+
"status": "OPEN",
|
|
46
|
+
"message": "Test message 2",
|
|
47
|
+
"creationDate": "2021-01-02T00:00:00",
|
|
48
|
+
"updateDate": "2021-01-02T00:00:00",
|
|
49
|
+
"type": "Vulnerability",
|
|
50
|
+
"days_elapsed": 3,
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
@pytest.fixture
|
|
55
|
+
def single_vulnerability_data(self):
|
|
56
|
+
"""Fixture providing single vulnerability data for specific tests"""
|
|
57
|
+
return [
|
|
58
|
+
{
|
|
59
|
+
"key": "test_issue_1",
|
|
60
|
+
"severity": "HIGH",
|
|
61
|
+
"component": "test_component_1",
|
|
62
|
+
"status": "OPEN",
|
|
63
|
+
"message": "Test message 1",
|
|
64
|
+
"creationDate": "2021-01-01T00:00:00",
|
|
65
|
+
"updateDate": "2021-01-01T00:00:00",
|
|
66
|
+
"type": "Vulnerability",
|
|
67
|
+
"days_elapsed": 5,
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
@pytest.fixture
|
|
72
|
+
def mock_api(self):
|
|
73
|
+
"""Fixture providing a mock API with common configuration"""
|
|
74
|
+
mock_api = MagicMock()
|
|
75
|
+
mock_api.config = {"sonarToken": "test_token_123", "sonarUrl": "https://sonarcloud.io", "userId": "1234"}
|
|
76
|
+
mock_api.logger = MagicMock()
|
|
77
|
+
mock_api.logger.info.return_value = None
|
|
78
|
+
return mock_api
|
|
79
|
+
|
|
80
|
+
@pytest.fixture
|
|
81
|
+
def mock_response_success(self):
|
|
82
|
+
"""Fixture providing a successful mock response"""
|
|
83
|
+
mock_response = MagicMock()
|
|
84
|
+
mock_response.status_code = 200
|
|
85
|
+
mock_response.ok = True
|
|
86
|
+
return mock_response
|
|
87
|
+
|
|
88
|
+
@pytest.fixture
|
|
89
|
+
def mock_response_error(self):
|
|
90
|
+
"""Fixture providing an error mock response"""
|
|
91
|
+
mock_response = MagicMock()
|
|
92
|
+
mock_response.status_code = 401
|
|
93
|
+
mock_response.ok = False
|
|
94
|
+
return mock_response
|
|
95
|
+
|
|
96
|
+
@pytest.fixture
|
|
97
|
+
def mock_sonarcloud_issue(self):
|
|
98
|
+
"""Fixture providing a mock sonarcloud issue"""
|
|
99
|
+
return {
|
|
100
|
+
"key": "test_issue",
|
|
101
|
+
"name": "Test Issue",
|
|
102
|
+
"description": "This is a test issue",
|
|
103
|
+
"type": "Vulnerability",
|
|
104
|
+
"status": "OPEN",
|
|
105
|
+
"severity": "HIGH",
|
|
106
|
+
"component": "test_component",
|
|
107
|
+
"message": "This is a test message",
|
|
108
|
+
"creationDate": "2021-01-01T00:00:00Z",
|
|
109
|
+
"updateDate": "2021-01-01T00:00:00Z",
|
|
110
|
+
"days_elapsed": 0,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@pytest.fixture
|
|
114
|
+
def mock_app(self):
|
|
115
|
+
"""Fixture providing a mock application"""
|
|
116
|
+
mock_app = MagicMock()
|
|
117
|
+
mock_app.config = {
|
|
118
|
+
"userId": "1234",
|
|
119
|
+
"domain": "https://test.regscale.com",
|
|
120
|
+
"sonarToken": "test_token_123",
|
|
121
|
+
"sonarUrl": "https://sonarcloud.io",
|
|
122
|
+
}
|
|
123
|
+
return mock_app
|
|
124
|
+
|
|
125
|
+
@pytest.fixture
|
|
126
|
+
def mock_issue_instance(self):
|
|
127
|
+
"""Fixture providing a mock issue instance"""
|
|
128
|
+
mock_issue = MagicMock()
|
|
129
|
+
mock_issue.dict.return_value = {"title": "Sonarcloud Code Scan", "description": "Test message"}
|
|
130
|
+
return mock_issue
|
|
131
|
+
|
|
132
|
+
def test_depend(self):
|
|
133
|
+
"""Make sure values are present"""
|
|
134
|
+
self.verify_config("sonarToken", compare_template=False)
|
|
135
|
+
|
|
136
|
+
def test_sonarcloud(self):
|
|
137
|
+
"""Get sonarcloud code scans"""
|
|
138
|
+
url = "https://sonarcloud.io/api/"
|
|
139
|
+
try:
|
|
140
|
+
response = requests.get(url, timeout=5)
|
|
141
|
+
if response.status_code != 200:
|
|
142
|
+
pytest.skip(f"Sonarcloud is down. {response.status_code}")
|
|
143
|
+
assert response.status_code == 200
|
|
144
|
+
except Exception:
|
|
145
|
+
pytest.skip("Sonarcloud is down.")
|
|
146
|
+
|
|
147
|
+
@patch("regscale.integrations.commercial.sonarcloud.requests.get")
|
|
148
|
+
def test_get_sonarcloud_results_success(self, mock_get, mock_sonarcloud_issue):
|
|
149
|
+
"""Test sonarcloud results pull with a success"""
|
|
150
|
+
mock_response1 = MagicMock()
|
|
151
|
+
mock_response1.status_code = 200
|
|
152
|
+
mock_response1.ok = True
|
|
153
|
+
mock_response1.json.return_value = {"paging": {"total": 2, "pageSize": 1}}
|
|
154
|
+
|
|
155
|
+
mock_response2 = MagicMock()
|
|
156
|
+
mock_response2.status_code = 200
|
|
157
|
+
mock_response2.ok = True
|
|
158
|
+
mock_response2.json.return_value = {"issues": [mock_sonarcloud_issue]}
|
|
159
|
+
|
|
160
|
+
mock_get.side_effect = [mock_response1, mock_response2, mock_response2]
|
|
161
|
+
results = get_sonarcloud_results(self.config)
|
|
162
|
+
test_results = mock_response2.json()["issues"] * 2 # Expecting two issues, one from each page
|
|
163
|
+
assert len(results) == 2
|
|
164
|
+
assert results == test_results
|
|
165
|
+
|
|
166
|
+
@patch("regscale.integrations.commercial.sonarcloud.requests.get")
|
|
167
|
+
def test_get_sonarcloud_results_error(self, mock_get, mock_response_error):
|
|
168
|
+
"""Test sonarcloud results pull with an error"""
|
|
169
|
+
mock_get.return_value = mock_response_error
|
|
170
|
+
|
|
171
|
+
with pytest.raises(SystemExit) as e:
|
|
172
|
+
get_sonarcloud_results(self.config)
|
|
173
|
+
assert e.type == SystemExit
|
|
174
|
+
|
|
175
|
+
@patch("regscale.integrations.commercial.sonarcloud.requests.get")
|
|
176
|
+
def test_get_sonarcloud_results_empty(self, mock_get):
|
|
177
|
+
"""Test sonarcloud results when no issues are found"""
|
|
178
|
+
mock_response = MagicMock()
|
|
179
|
+
mock_response.status_code = 200
|
|
180
|
+
mock_response.ok = True
|
|
181
|
+
mock_response.json.return_value = {
|
|
182
|
+
"paging": {"total": 0, "pageSize": 500},
|
|
183
|
+
"issues": [],
|
|
184
|
+
}
|
|
185
|
+
mock_get.return_value = mock_response
|
|
186
|
+
|
|
187
|
+
results = get_sonarcloud_results(self.config)
|
|
188
|
+
assert len(results) == 0
|
|
189
|
+
|
|
190
|
+
@patch("regscale.integrations.commercial.sonarcloud.get_sonarcloud_results")
|
|
191
|
+
def test_build_data(self, mock_get_sonarcloud_results, mock_api, mock_sonarcloud_issue):
|
|
192
|
+
"""Test the build_data function"""
|
|
193
|
+
mock_get_sonarcloud_results.return_value = [[mock_sonarcloud_issue]]
|
|
194
|
+
results = build_data(mock_api)
|
|
195
|
+
assert len(results) == 1
|
|
196
|
+
assert results[0]["key"] == "test_issue"
|
|
197
|
+
|
|
198
|
+
@patch("regscale.integrations.commercial.sonarcloud.get_sonarcloud_results")
|
|
199
|
+
def test_build_data_empty(self, mock_get_sonarcloud_results, mock_api):
|
|
200
|
+
"""Test the build_data function when no issues are found"""
|
|
201
|
+
mock_get_sonarcloud_results.return_value = [[]]
|
|
202
|
+
results = build_data(mock_api)
|
|
203
|
+
assert len(results) == 0
|
|
204
|
+
assert results == []
|
|
205
|
+
|
|
206
|
+
@patch("regscale.integrations.commercial.sonarcloud.build_data")
|
|
207
|
+
def test_build_dataframes(self, mock_build_data, sample_vulnerability_data):
|
|
208
|
+
"""Test the build_dataframes function"""
|
|
209
|
+
mock_build_data.return_value = sample_vulnerability_data
|
|
210
|
+
api = MagicMock()
|
|
211
|
+
result = build_dataframes(api)
|
|
212
|
+
|
|
213
|
+
assert "<table" in result
|
|
214
|
+
assert "test_issue_1" in result
|
|
215
|
+
assert "test_issue_2" in result
|
|
216
|
+
assert "HIGH" in result
|
|
217
|
+
assert "LOW" in result
|
|
218
|
+
|
|
219
|
+
# Should be sorted by severity (HIGH should come before LOW)
|
|
220
|
+
high_index = result.find("HIGH")
|
|
221
|
+
low_index = result.find("LOW")
|
|
222
|
+
assert high_index < low_index
|
|
223
|
+
|
|
224
|
+
@patch("regscale.integrations.commercial.sonarcloud.build_data")
|
|
225
|
+
@patch("regscale.integrations.commercial.sonarcloud.Assessment.create")
|
|
226
|
+
def test_create_alert_assessment(
|
|
227
|
+
self, mock_assessment_create, mock_build_data, mock_api, sample_vulnerability_data
|
|
228
|
+
):
|
|
229
|
+
"""Test the create_alert_assessment function"""
|
|
230
|
+
mock_build_data.return_value = sample_vulnerability_data
|
|
231
|
+
mock_assessment_create.return_value = MagicMock()
|
|
232
|
+
|
|
233
|
+
result = create_alert_assessment(mock_api, parent_id=123, parent_module="test_module")
|
|
234
|
+
|
|
235
|
+
assert result is not None
|
|
236
|
+
mock_assessment_create.assert_called_once()
|
|
237
|
+
|
|
238
|
+
@patch("regscale.integrations.commercial.sonarcloud.build_data")
|
|
239
|
+
@patch("regscale.integrations.commercial.sonarcloud.Assessment.create")
|
|
240
|
+
def test_create_alert_assessment_failure(
|
|
241
|
+
self, mock_assessment_create, mock_build_data, mock_api, sample_vulnerability_data
|
|
242
|
+
):
|
|
243
|
+
"""Test the create_alert_assessment function when assessment creation fails"""
|
|
244
|
+
mock_build_data.return_value = sample_vulnerability_data
|
|
245
|
+
mock_assessment_create.return_value = None
|
|
246
|
+
|
|
247
|
+
result = create_alert_assessment(mock_api)
|
|
248
|
+
|
|
249
|
+
assert result is None
|
|
250
|
+
mock_assessment_create.assert_called_once()
|
|
251
|
+
|
|
252
|
+
@patch("regscale.integrations.commercial.sonarcloud.build_data")
|
|
253
|
+
@patch("regscale.integrations.commercial.sonarcloud.Assessment")
|
|
254
|
+
def test_create_alert_assessment_critical_vulnerability(self, mock_assessment_class, mock_build_data, mock_api):
|
|
255
|
+
"""Test the create_alert_assessment function with CRITICAL vulnerability >= 10 days"""
|
|
256
|
+
# Create critical vulnerability data
|
|
257
|
+
critical_data = [
|
|
258
|
+
{
|
|
259
|
+
"key": "test_issue_1",
|
|
260
|
+
"severity": "CRITICAL",
|
|
261
|
+
"component": "test_component_1",
|
|
262
|
+
"status": "OPEN",
|
|
263
|
+
"message": "Test message 1",
|
|
264
|
+
"creationDate": "2021-01-01T00:00:00",
|
|
265
|
+
"updateDate": "2021-01-01T00:00:00",
|
|
266
|
+
"type": "Vulnerability",
|
|
267
|
+
"days_elapsed": 15, # >= 10 days
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
mock_build_data.return_value = critical_data
|
|
271
|
+
|
|
272
|
+
# Create a mock assessment instance
|
|
273
|
+
mock_assessment_instance = MagicMock()
|
|
274
|
+
mock_assessment_instance.id = 12345
|
|
275
|
+
mock_assessment_instance.create.return_value = mock_assessment_instance
|
|
276
|
+
mock_assessment_class.return_value = mock_assessment_instance
|
|
277
|
+
|
|
278
|
+
result = create_alert_assessment(mock_api)
|
|
279
|
+
|
|
280
|
+
assert result == 12345
|
|
281
|
+
# Verify that the assessment was created with the correct status and result
|
|
282
|
+
assert mock_assessment_instance.status == "Complete"
|
|
283
|
+
assert mock_assessment_instance.assessmentResult == "Fail"
|
|
284
|
+
assert mock_assessment_instance.actualFinish is not None
|
|
285
|
+
|
|
286
|
+
@patch("regscale.integrations.commercial.sonarcloud.logger")
|
|
287
|
+
@patch("regscale.integrations.commercial.sonarcloud.Issue")
|
|
288
|
+
@patch("regscale.integrations.commercial.sonarcloud.build_data")
|
|
289
|
+
@patch("regscale.integrations.commercial.sonarcloud.create_alert_assessment")
|
|
290
|
+
@patch("regscale.integrations.commercial.sonarcloud.Api")
|
|
291
|
+
@patch("regscale.integrations.commercial.sonarcloud.Application")
|
|
292
|
+
def test_create_alert_issues(
|
|
293
|
+
self,
|
|
294
|
+
mock_app_class,
|
|
295
|
+
mock_api_class,
|
|
296
|
+
mock_create_assessment,
|
|
297
|
+
mock_build_data,
|
|
298
|
+
mock_issue_class,
|
|
299
|
+
mock_logger,
|
|
300
|
+
mock_app,
|
|
301
|
+
mock_issue_instance,
|
|
302
|
+
sample_vulnerability_data,
|
|
303
|
+
mock_response_success,
|
|
304
|
+
):
|
|
305
|
+
"""Test the create_alert_issues function with mocked dependencies"""
|
|
306
|
+
mock_app_class.return_value = mock_app
|
|
307
|
+
|
|
308
|
+
mock_api = MagicMock()
|
|
309
|
+
mock_api_class.return_value = mock_api
|
|
310
|
+
|
|
311
|
+
mock_create_assessment.return_value = 12345 # assessment_id
|
|
312
|
+
mock_build_data.return_value = sample_vulnerability_data
|
|
313
|
+
|
|
314
|
+
mock_issue_class.return_value = mock_issue_instance
|
|
315
|
+
mock_api.post.return_value = mock_response_success
|
|
316
|
+
|
|
317
|
+
create_alert_issues(parent_id=123, parent_module="test_module")
|
|
318
|
+
|
|
319
|
+
mock_create_assessment.assert_called_once_with(api=mock_api, parent_id=123, parent_module="test_module")
|
|
320
|
+
mock_build_data.assert_called_once_with(mock_api, None)
|
|
321
|
+
|
|
322
|
+
# Verify Issue was created for each vulnerability
|
|
323
|
+
assert mock_issue_class.call_count == 2
|
|
324
|
+
|
|
325
|
+
# Verify API post was called for each issue
|
|
326
|
+
assert mock_api.post.call_count == 2
|
|
327
|
+
|
|
328
|
+
# Verify successful logging
|
|
329
|
+
assert mock_logger.info.call_count == 2
|
|
330
|
+
mock_logger.info.assert_any_call("Issue created successfully.")
|
|
331
|
+
|
|
332
|
+
@patch("regscale.integrations.commercial.sonarcloud.logger")
|
|
333
|
+
@patch("regscale.integrations.commercial.sonarcloud.Issue")
|
|
334
|
+
@patch("regscale.integrations.commercial.sonarcloud.build_data")
|
|
335
|
+
@patch("regscale.integrations.commercial.sonarcloud.create_alert_assessment")
|
|
336
|
+
@patch("regscale.integrations.commercial.sonarcloud.Api")
|
|
337
|
+
@patch("regscale.integrations.commercial.sonarcloud.Application")
|
|
338
|
+
def test_create_alert_issues_api_failure(
|
|
339
|
+
self,
|
|
340
|
+
mock_app_class,
|
|
341
|
+
mock_api_class,
|
|
342
|
+
mock_create_assessment,
|
|
343
|
+
mock_build_data,
|
|
344
|
+
mock_issue_class,
|
|
345
|
+
mock_logger,
|
|
346
|
+
mock_app,
|
|
347
|
+
mock_issue_instance,
|
|
348
|
+
single_vulnerability_data,
|
|
349
|
+
mock_response_error,
|
|
350
|
+
):
|
|
351
|
+
"""Test the create_alert_issues function when API calls fail"""
|
|
352
|
+
mock_app_class.return_value = mock_app
|
|
353
|
+
|
|
354
|
+
mock_api = MagicMock()
|
|
355
|
+
mock_api_class.return_value = mock_api
|
|
356
|
+
|
|
357
|
+
mock_create_assessment.return_value = 12345 # assessment_id
|
|
358
|
+
mock_build_data.return_value = single_vulnerability_data
|
|
359
|
+
|
|
360
|
+
mock_issue_class.return_value = mock_issue_instance
|
|
361
|
+
mock_api.post.return_value = mock_response_error
|
|
362
|
+
|
|
363
|
+
create_alert_issues()
|
|
364
|
+
|
|
365
|
+
# Verify API post was called
|
|
366
|
+
mock_api.post.assert_called_once()
|
|
367
|
+
|
|
368
|
+
# Verify failure logging
|
|
369
|
+
mock_logger.info.assert_called_once_with("Issue was not created.")
|
|
370
|
+
|
|
371
|
+
@patch("regscale.integrations.commercial.sonarcloud.logger")
|
|
372
|
+
@patch("regscale.integrations.commercial.sonarcloud.build_data")
|
|
373
|
+
@patch("regscale.integrations.commercial.sonarcloud.create_alert_assessment")
|
|
374
|
+
@patch("regscale.integrations.commercial.sonarcloud.Api")
|
|
375
|
+
@patch("regscale.integrations.commercial.sonarcloud.Application")
|
|
376
|
+
def test_create_alert_issues_no_vulnerabilities(
|
|
377
|
+
self, mock_app_class, mock_api_class, mock_create_assessment, mock_build_data, mock_logger, mock_app
|
|
378
|
+
):
|
|
379
|
+
"""Test the create_alert_issues function when no vulnerabilities are found"""
|
|
380
|
+
mock_app_class.return_value = mock_app
|
|
381
|
+
|
|
382
|
+
mock_api = MagicMock()
|
|
383
|
+
mock_api_class.return_value = mock_api
|
|
384
|
+
|
|
385
|
+
mock_create_assessment.return_value = 12345 # assessment_id
|
|
386
|
+
mock_build_data.return_value = []
|
|
387
|
+
|
|
388
|
+
create_alert_issues()
|
|
389
|
+
|
|
390
|
+
mock_create_assessment.assert_called_once_with(api=mock_api, parent_id=None, parent_module=None)
|
|
391
|
+
mock_build_data.assert_called_once_with(mock_api, None)
|
|
392
|
+
|
|
393
|
+
# Verify no API post calls were made (no vulnerabilities)
|
|
394
|
+
mock_api.post.assert_not_called()
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from unittest.mock import ANY, call, patch, MagicMock
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from regscale.integrations.commercial.sqlserver import (
|
|
8
|
+
build_connection_string,
|
|
9
|
+
create_and_save_assessment,
|
|
10
|
+
calculate_finish_date,
|
|
11
|
+
create_assessment_object,
|
|
12
|
+
upload_files_to_assessment,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.skip(reason="Manual test", allow_module_level=True)
|
|
17
|
+
class TestSqlServer(unittest.TestCase):
|
|
18
|
+
@patch("regscale.core.app.utils.app_utils.check_license")
|
|
19
|
+
@patch("regscale.core.app.utils.regscale_utils.verify_provided_module")
|
|
20
|
+
@patch("regscale.integrations.commercial.sqlserver.build_connection_string")
|
|
21
|
+
@patch("pyodbc.connect")
|
|
22
|
+
@patch("pandas.DataFrame.from_records")
|
|
23
|
+
@patch("os.makedirs")
|
|
24
|
+
@patch("regscale.integrations.commercial.sqlserver.create_and_save_assessment")
|
|
25
|
+
@patch("regscale.core.app.internal.workflow.create_regscale_workflow_from_template")
|
|
26
|
+
def test_generate_and_save_report(
|
|
27
|
+
self,
|
|
28
|
+
mock_check_license,
|
|
29
|
+
mock_verify_provided_module,
|
|
30
|
+
mock_build_connection_string,
|
|
31
|
+
mock_pyodbc_connect,
|
|
32
|
+
mock_df_from_records,
|
|
33
|
+
mock_os_makedirs,
|
|
34
|
+
mock_create_and_save_assessment,
|
|
35
|
+
mock_create_regscale_workflow_from_template,
|
|
36
|
+
):
|
|
37
|
+
# Add your assertions here
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
def test_build_connection_string_with_username_and_password(self):
|
|
41
|
+
result = build_connection_string("server", "database", 1433, "username", "password")
|
|
42
|
+
expected = (
|
|
43
|
+
r"DRIVER={ODBC Driver 17 for SQL Server};"
|
|
44
|
+
r"SERVER=server;"
|
|
45
|
+
r"DATABASE=database;"
|
|
46
|
+
r"PORT=port;"
|
|
47
|
+
r"UID=username;"
|
|
48
|
+
r"PWD=password;"
|
|
49
|
+
r"TrustServerCertificate=yes;"
|
|
50
|
+
r"TIMEOUT=30;"
|
|
51
|
+
)
|
|
52
|
+
self.assertEqual(result, expected)
|
|
53
|
+
|
|
54
|
+
def test_build_connection_string_with_trusted_connection(self):
|
|
55
|
+
result = build_connection_string("server", "database", 1433, None, None)
|
|
56
|
+
expected = (
|
|
57
|
+
r"DRIVER={ODBC Driver 17 for SQL Server};"
|
|
58
|
+
r"SERVER=server;"
|
|
59
|
+
r"DATABASE=database;"
|
|
60
|
+
r"PORT=port;"
|
|
61
|
+
r"Trusted_Connection=yes;"
|
|
62
|
+
)
|
|
63
|
+
self.assertEqual(result, expected)
|
|
64
|
+
|
|
65
|
+
@patch("regscale.integrations.commercial.sqlserver.calculate_finish_date")
|
|
66
|
+
@patch("regscale.integrations.commercial.sqlserver.get_current_datetime")
|
|
67
|
+
@patch("regscale.integrations.commercial.sqlserver.Assessment")
|
|
68
|
+
def test_create_assessment_object(self, mock_assessment, mock_get_current_datetime, mock_calculate_finish_date):
|
|
69
|
+
mock_app = MagicMock()
|
|
70
|
+
mock_app.config = {
|
|
71
|
+
"userId": "904efb0a-c3ee-4819-ba4e-df1f4554d843",
|
|
72
|
+
"assessmentDays": 7,
|
|
73
|
+
}
|
|
74
|
+
mock_get_current_datetime.return_value = "2023-07-23T12:00:00"
|
|
75
|
+
mock_calculate_finish_date.return_value = "2023-08-23T12:00:00"
|
|
76
|
+
|
|
77
|
+
create_assessment_object(
|
|
78
|
+
app=mock_app,
|
|
79
|
+
report="report",
|
|
80
|
+
regscale_id=1,
|
|
81
|
+
regscale_module="module",
|
|
82
|
+
title="title",
|
|
83
|
+
description="description",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
mock_assessment.assert_called_once_with(
|
|
87
|
+
leadAssessorId=mock_app.config["userId"],
|
|
88
|
+
title="title",
|
|
89
|
+
assessmentType="Control Testing",
|
|
90
|
+
plannedStart=mock_get_current_datetime.return_value,
|
|
91
|
+
plannedFinish=mock_calculate_finish_date.return_value,
|
|
92
|
+
assessmentReport="report",
|
|
93
|
+
assessmentPlan="description",
|
|
94
|
+
createdById=mock_app.config["userId"],
|
|
95
|
+
dateCreated=mock_get_current_datetime.return_value,
|
|
96
|
+
lastUpdatedById=mock_app.config["userId"],
|
|
97
|
+
dateLastUpdated=mock_get_current_datetime.return_value,
|
|
98
|
+
parentModule="module",
|
|
99
|
+
parentId=1,
|
|
100
|
+
status="Scheduled",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@patch("regscale.integrations.commercial.sqlserver.File.upload_file_to_regscale")
|
|
104
|
+
@patch("regscale.integrations.commercial.sqlserver.job_progress")
|
|
105
|
+
def test_upload_files_to_assessment(self, mock_job_progress, mock_upload_file_to_regscale):
|
|
106
|
+
mock_api = MagicMock()
|
|
107
|
+
mock_upload_file_to_regscale.return_value = True
|
|
108
|
+
mock_job_progress.add_task.return_value = "task_id"
|
|
109
|
+
|
|
110
|
+
upload_files_to_assessment(api=mock_api, assessment_id=1, files=["file1", "file2"])
|
|
111
|
+
|
|
112
|
+
mock_job_progress.add_task.assert_called_once_with(
|
|
113
|
+
"[#0866b4]Uploading files to the new RegScale Assessment...", total=2
|
|
114
|
+
)
|
|
115
|
+
mock_upload_file_to_regscale.assert_has_calls(
|
|
116
|
+
[
|
|
117
|
+
call(
|
|
118
|
+
file_name="file1",
|
|
119
|
+
parent_id=1,
|
|
120
|
+
parent_module="assessments",
|
|
121
|
+
api=mock_api,
|
|
122
|
+
),
|
|
123
|
+
call(
|
|
124
|
+
file_name="file2",
|
|
125
|
+
parent_id=1,
|
|
126
|
+
parent_module="assessments",
|
|
127
|
+
api=mock_api,
|
|
128
|
+
),
|
|
129
|
+
]
|
|
130
|
+
)
|
|
131
|
+
mock_job_progress.update.assert_has_calls(
|
|
132
|
+
[
|
|
133
|
+
call("task_id", advance=1),
|
|
134
|
+
call("task_id", advance=1),
|
|
135
|
+
]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@patch("regscale.integrations.commercial.sqlserver.upload_files_to_assessment")
|
|
139
|
+
@patch("regscale.integrations.commercial.sqlserver.create_assessment_object")
|
|
140
|
+
@patch("regscale.integrations.commercial.sqlserver.job_progress")
|
|
141
|
+
@patch("regscale.integrations.commercial.sqlserver.Api")
|
|
142
|
+
def test_create_and_save_assessment(
|
|
143
|
+
self,
|
|
144
|
+
mock_api_object,
|
|
145
|
+
mock_job_progress,
|
|
146
|
+
mock_create_assessment_object,
|
|
147
|
+
mock_upload_files_to_assessment,
|
|
148
|
+
):
|
|
149
|
+
mock_api = MagicMock()
|
|
150
|
+
mock_api_object.return_value = mock_api
|
|
151
|
+
mock_assessment = MagicMock()
|
|
152
|
+
mock_assessment.id = 1
|
|
153
|
+
mock_assessment.insert_assessment.return_value = mock_assessment
|
|
154
|
+
mock_create_assessment_object.return_value = mock_assessment
|
|
155
|
+
mock_job_progress.add_task.return_value = "task_id"
|
|
156
|
+
mock_app = MagicMock()
|
|
157
|
+
result = create_and_save_assessment(
|
|
158
|
+
report="report",
|
|
159
|
+
files=["file1", "file2"],
|
|
160
|
+
regscale_id=1,
|
|
161
|
+
regscale_module="module",
|
|
162
|
+
title="title",
|
|
163
|
+
description="description",
|
|
164
|
+
app=mock_app,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
self.assertIsNotNone(result)
|
|
168
|
+
mock_api_object.assert_called_once()
|
|
169
|
+
mock_create_assessment_object.assert_called_once_with(
|
|
170
|
+
ANY,
|
|
171
|
+
"report",
|
|
172
|
+
1,
|
|
173
|
+
"module",
|
|
174
|
+
"title",
|
|
175
|
+
"description",
|
|
176
|
+
)
|
|
177
|
+
mock_job_progress.add_task.assert_called_once_with("[#21a5bb]Creating assessment in RegScale...", total=1)
|
|
178
|
+
mock_assessment.insert_assessment.assert_called_once_with(app=ANY)
|
|
179
|
+
mock_upload_files_to_assessment.assert_called_once_with(ANY, 1, ["file1", "file2"])
|
|
180
|
+
|
|
181
|
+
def test_calculate_finish_date(self):
|
|
182
|
+
current_date = datetime(2023, 7, 23, 12, 0, 0)
|
|
183
|
+
days = 7
|
|
184
|
+
result = calculate_finish_date(current_date, days)
|
|
185
|
+
expected = "2023-07-30T12:00:00"
|
|
186
|
+
self.assertEqual(result, expected)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# #!/usr/bin/env python3
|
|
2
|
+
# # -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# from regscale.core.app.api import Api
|
|
5
|
+
# from regscale.core.app.application import Application
|
|
6
|
+
# from regscale.integrations.commercial.stig import STIG, cci_control_mapping
|
|
7
|
+
#
|
|
8
|
+
#
|
|
9
|
+
# def test_cci_control_mapping():
|
|
10
|
+
# """Can we build a cci control mapping"""
|
|
11
|
+
# assert 1 == 1
|
|
12
|
+
#
|
|
13
|
+
#
|
|
14
|
+
# def test_component_build():
|
|
15
|
+
# """Can we build a components from a folder of stigs"""
|
|
16
|
+
# assert 1 == 1
|
|
17
|
+
#
|
|
18
|
+
#
|
|
19
|
+
# def test_asset_build():
|
|
20
|
+
# """Can we build a asset from a folder of stigs"""
|
|
21
|
+
# assert 1 == 1
|
|
22
|
+
#
|
|
23
|
+
#
|
|
24
|
+
# def test_checks():
|
|
25
|
+
# """Make sure unique checks only"""
|
|
26
|
+
# # BEWARE of duplicate checks status for same check
|
|
27
|
+
# assert 1 == 1
|
|
28
|
+
#
|
|
29
|
+
#
|
|
30
|
+
# def test_checks_asset():
|
|
31
|
+
# """Make sure unique checks to correct asset"""
|
|
32
|
+
# # BEWARE OF DUPLICATES or mismatched checks to assets
|
|
33
|
+
# assert 1 == 1
|