regscale-cli 6.23.0.1__py3-none-any.whl → 6.24.0.1__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/application.py +2 -0
- regscale/integrations/commercial/__init__.py +1 -0
- regscale/integrations/commercial/jira.py +95 -22
- regscale/integrations/commercial/sarif/sarif_converter.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +132 -2
- regscale/integrations/commercial/wizv2/compliance_report.py +1574 -0
- regscale/integrations/commercial/wizv2/constants.py +72 -2
- regscale/integrations/commercial/wizv2/data_fetcher.py +61 -0
- regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
- regscale/integrations/commercial/wizv2/issue.py +775 -27
- regscale/integrations/commercial/wizv2/policy_compliance.py +599 -181
- regscale/integrations/commercial/wizv2/reports.py +243 -0
- regscale/integrations/commercial/wizv2/scanner.py +668 -245
- regscale/integrations/compliance_integration.py +534 -56
- regscale/integrations/due_date_handler.py +210 -0
- regscale/integrations/public/cci_importer.py +444 -0
- regscale/integrations/scanner_integration.py +718 -153
- regscale/models/integration_models/CCI_List.xml +1 -0
- regscale/models/integration_models/cisa_kev_data.json +18 -3
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/control_implementation.py +13 -3
- regscale/models/regscale_models/form_field_value.py +1 -1
- regscale/models/regscale_models/milestone.py +1 -0
- regscale/models/regscale_models/regscale_model.py +225 -60
- regscale/models/regscale_models/security_plan.py +3 -2
- regscale/regscale.py +7 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/METADATA +17 -17
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/RECORD +45 -28
- tests/fixtures/test_fixture.py +13 -8
- tests/regscale/integrations/public/__init__.py +0 -0
- tests/regscale/integrations/public/test_alienvault.py +220 -0
- tests/regscale/integrations/public/test_cci.py +458 -0
- tests/regscale/integrations/public/test_cisa.py +1021 -0
- tests/regscale/integrations/public/test_emass.py +518 -0
- tests/regscale/integrations/public/test_fedramp.py +851 -0
- tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
- tests/regscale/integrations/public/test_file_uploads.py +506 -0
- tests/regscale/integrations/public/test_oscal.py +453 -0
- tests/regscale/models/test_form_field_value_integration.py +304 -0
- tests/regscale/models/test_module_integration.py +582 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Test OSCAL Integrations"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from regscale.core.app.utils.app_utils import check_file_path
|
|
14
|
+
from regscale.integrations.public.oscal import (
|
|
15
|
+
process_component,
|
|
16
|
+
process_fedramp_objectives,
|
|
17
|
+
upload_catalog,
|
|
18
|
+
upload_profile,
|
|
19
|
+
)
|
|
20
|
+
from regscale.utils.threading.threadhandler import create_threads, thread_assignment
|
|
21
|
+
from tests import CLITestFixture
|
|
22
|
+
|
|
23
|
+
sys.path.append("..") # Adds higher directory to python modules path.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestOscal(CLITestFixture):
|
|
27
|
+
"""Oscal Test Class"""
|
|
28
|
+
|
|
29
|
+
@pytest.fixture(autouse=True)
|
|
30
|
+
def oscal_catalog(self):
|
|
31
|
+
"""Test OSCAL Catalog"""
|
|
32
|
+
catalog_path = self.get_tests_dir("tests/test_data/NIST-800-53r4_catalog_MIN.json")
|
|
33
|
+
with open(catalog_path.absolute(), "r", encoding="utf-8") as infile:
|
|
34
|
+
data = json.load(infile)
|
|
35
|
+
return data
|
|
36
|
+
|
|
37
|
+
@pytest.fixture(autouse=True)
|
|
38
|
+
def oscal_control(self, oscal_catalog):
|
|
39
|
+
"""Test OSCAL Control"""
|
|
40
|
+
control = oscal_catalog["catalog"]["groups"][0]["controls"][0]
|
|
41
|
+
return control
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
@pytest.fixture(autouse=True)
|
|
45
|
+
def sample_control():
|
|
46
|
+
"""
|
|
47
|
+
Provides a sample security control for tests.
|
|
48
|
+
|
|
49
|
+
:return: A sample security control
|
|
50
|
+
:rtype: dict
|
|
51
|
+
"""
|
|
52
|
+
return {"id": "ctrl-1"}
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
@pytest.fixture(autouse=True)
|
|
56
|
+
def sample_part_with_prose():
|
|
57
|
+
"""
|
|
58
|
+
Provides a sample part with prose for tests.
|
|
59
|
+
|
|
60
|
+
:return: A sample part with prose
|
|
61
|
+
:rtype: dict
|
|
62
|
+
"""
|
|
63
|
+
return {"id": "part-1", "prose": "Sample prose.", "name": "statement"}
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
@pytest.fixture(autouse=True)
|
|
67
|
+
def sample_part_with_nested_parts():
|
|
68
|
+
"""
|
|
69
|
+
Provides a sample part with nested parts for tests.
|
|
70
|
+
|
|
71
|
+
:return: A sample part with nested parts
|
|
72
|
+
:rtype: dict
|
|
73
|
+
"""
|
|
74
|
+
return {
|
|
75
|
+
"id": "part-2",
|
|
76
|
+
"name": "objective",
|
|
77
|
+
"parts": [
|
|
78
|
+
{"id": "part-2-1", "name": "item", "props": [{"prose": "nested-prop-value"}], "prose": "Nested prose."}
|
|
79
|
+
],
|
|
80
|
+
"prose": "First prose.",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
@pytest.fixture(autouse=True)
|
|
85
|
+
def sample_part_deeply_nested():
|
|
86
|
+
"""
|
|
87
|
+
Provides a sample part with deeply nested parts for tests.
|
|
88
|
+
|
|
89
|
+
:return: A sample part with deeply nested parts
|
|
90
|
+
:rtype: dict
|
|
91
|
+
"""
|
|
92
|
+
return {
|
|
93
|
+
"id": "part-3",
|
|
94
|
+
"name": "objective",
|
|
95
|
+
"prose": "First prose.",
|
|
96
|
+
"parts": [
|
|
97
|
+
{
|
|
98
|
+
"id": "part-3-1",
|
|
99
|
+
"name": "item",
|
|
100
|
+
"prose": "Second prose.",
|
|
101
|
+
"parts": [{"id": "part-3-1-1", "name": "objective", "prose": "Deeply nested prose."}],
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
@pytest.fixture(autouse=True)
|
|
108
|
+
def sample_part_with_super_deep_nested_parts():
|
|
109
|
+
"""
|
|
110
|
+
Provides a sample part with super deep nested parts for tests.
|
|
111
|
+
|
|
112
|
+
:return: A sample part with super deep nested parts
|
|
113
|
+
:rtype: dict
|
|
114
|
+
"""
|
|
115
|
+
return {
|
|
116
|
+
"id": "part-4",
|
|
117
|
+
"name": "objective",
|
|
118
|
+
"prose": "First prose.",
|
|
119
|
+
"parts": [
|
|
120
|
+
{
|
|
121
|
+
"id": "part-2-1",
|
|
122
|
+
"name": "item",
|
|
123
|
+
"props": [{"value": "nested-prop-value"}],
|
|
124
|
+
"prose": "Second prose.",
|
|
125
|
+
"parts": [
|
|
126
|
+
{
|
|
127
|
+
"id": "part-3-1",
|
|
128
|
+
"name": "objective",
|
|
129
|
+
"prose": "Third prose.",
|
|
130
|
+
"parts": [
|
|
131
|
+
{
|
|
132
|
+
"id": "part-3-1-1",
|
|
133
|
+
"name": "item",
|
|
134
|
+
"prose": "Fourth prose.",
|
|
135
|
+
"parts": [
|
|
136
|
+
{
|
|
137
|
+
"id": "part-3-1-1-1",
|
|
138
|
+
"name": "objective",
|
|
139
|
+
"prose": "Fifth prose.",
|
|
140
|
+
"parts": [
|
|
141
|
+
{
|
|
142
|
+
"id": "part-3-1-1-1-1",
|
|
143
|
+
"name": "item",
|
|
144
|
+
"prose": "Sixth prose.",
|
|
145
|
+
"parts": [
|
|
146
|
+
{
|
|
147
|
+
"id": "part-3-1-1-1-1-1",
|
|
148
|
+
"name": "objective",
|
|
149
|
+
"prose": "Seventh prose.",
|
|
150
|
+
"parts": [
|
|
151
|
+
{
|
|
152
|
+
"id": "part-3-1-1-1-1-1-1",
|
|
153
|
+
"name": "item",
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def test_no_prose_or_name(sample_control):
|
|
172
|
+
part = {"id": "part-0"}
|
|
173
|
+
objectives = []
|
|
174
|
+
result = process_fedramp_objectives(part, "", objectives, sample_control)
|
|
175
|
+
assert result == ""
|
|
176
|
+
assert len(objectives) == 0
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def test_with_prose_only(sample_part_with_prose, sample_control):
|
|
180
|
+
part = sample_part_with_prose
|
|
181
|
+
objectives = []
|
|
182
|
+
result = process_fedramp_objectives(part, "", objectives, sample_control)
|
|
183
|
+
assert "Sample prose." in result
|
|
184
|
+
assert len(objectives) == 1
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def test_with_name_item(sample_control):
|
|
188
|
+
part = {"id": "part-1", "name": "item", "prose": "Item prose."}
|
|
189
|
+
objectives = []
|
|
190
|
+
result = process_fedramp_objectives(part, "", objectives, sample_control)
|
|
191
|
+
assert "Item prose." in result
|
|
192
|
+
assert len(objectives) == 1
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def test_with_nested_parts(sample_part_with_nested_parts, sample_control):
|
|
196
|
+
part = sample_part_with_nested_parts
|
|
197
|
+
objectives = []
|
|
198
|
+
result = process_fedramp_objectives(part, "", objectives, sample_control)
|
|
199
|
+
assert "First prose." in result
|
|
200
|
+
assert len(objectives) == 1
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
def test_with_deeply_nested_parts(sample_part_deeply_nested, sample_control):
|
|
204
|
+
part = sample_part_deeply_nested
|
|
205
|
+
objectives = []
|
|
206
|
+
result = process_fedramp_objectives(part, "", objectives, sample_control)
|
|
207
|
+
assert "First prose." in result
|
|
208
|
+
assert len(objectives) == 1
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def test_with_multiple_nested_parts(
|
|
212
|
+
sample_part_with_nested_parts,
|
|
213
|
+
sample_part_deeply_nested,
|
|
214
|
+
sample_control,
|
|
215
|
+
sample_part_with_super_deep_nested_parts,
|
|
216
|
+
):
|
|
217
|
+
part = {
|
|
218
|
+
"id": "part-4",
|
|
219
|
+
"name": "objective",
|
|
220
|
+
"parts": [
|
|
221
|
+
sample_part_with_nested_parts,
|
|
222
|
+
sample_part_deeply_nested,
|
|
223
|
+
sample_part_with_super_deep_nested_parts,
|
|
224
|
+
],
|
|
225
|
+
}
|
|
226
|
+
objectives = []
|
|
227
|
+
result = process_fedramp_objectives(part, "", objectives, sample_control)
|
|
228
|
+
assert len(objectives) == 3
|
|
229
|
+
assert result == ""
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def test_no_parts(sample_control):
|
|
233
|
+
part = {"id": "part-5", "name": "objective", "prose": "No parts prose."}
|
|
234
|
+
objectives = []
|
|
235
|
+
result = process_fedramp_objectives(part, "", objectives, sample_control)
|
|
236
|
+
assert "No parts prose." in result
|
|
237
|
+
assert len(objectives) == 1
|
|
238
|
+
|
|
239
|
+
def test_create_catalog(self, oscal_catalog):
|
|
240
|
+
"""Test Catalog Code"""
|
|
241
|
+
check_file_path("processing")
|
|
242
|
+
tmp_file = tempfile.NamedTemporaryFile(delete=False)
|
|
243
|
+
# change the catalog name to the name of the test run id
|
|
244
|
+
data = oscal_catalog
|
|
245
|
+
with open(tmp_file.name, "w", encoding="utf-8") as outfile:
|
|
246
|
+
title = data["catalog"]["metadata"]["title"]
|
|
247
|
+
data["catalog"]["metadata"]["title"] = title.replace("(TEST)", self.title_prefix)
|
|
248
|
+
cat_name = data["catalog"]["metadata"]["title"]
|
|
249
|
+
json.dump(data, outfile, indent=4)
|
|
250
|
+
# Pass default argument to click function
|
|
251
|
+
self.logger.debug(tmp_file.name)
|
|
252
|
+
|
|
253
|
+
self.upload_catalog(tmp_file.name)
|
|
254
|
+
# delete extra data after we are finished
|
|
255
|
+
self.delete_catalog_items()
|
|
256
|
+
# delete the catalog
|
|
257
|
+
self.delete_inserted_catalog(cat_name)
|
|
258
|
+
self.logger.debug(cat_name)
|
|
259
|
+
tmp_file.close()
|
|
260
|
+
os.remove(tmp_file.name)
|
|
261
|
+
|
|
262
|
+
def test_create_profile(self):
|
|
263
|
+
"""Test Profile Code"""
|
|
264
|
+
if not os.path.exists("processing"):
|
|
265
|
+
os.mkdir("processing")
|
|
266
|
+
# Need a runner to allow click to work with pytest
|
|
267
|
+
test_file_path = self.get_tests_dir("tests") / "test_data/fedramp_high_profile.json"
|
|
268
|
+
with open(test_file_path, "r", encoding="utf-8") as infile:
|
|
269
|
+
data = json.load(infile)
|
|
270
|
+
tmp_file = tempfile.NamedTemporaryFile(delete=False)
|
|
271
|
+
with open(tmp_file.name, "w", encoding="utf-8") as outfile:
|
|
272
|
+
json.dump(data, outfile, indent=4)
|
|
273
|
+
self.logger.debug(outfile.name)
|
|
274
|
+
# change the profile title to the name of the test run id
|
|
275
|
+
with open(tmp_file.name, "r", encoding="utf-8") as infile:
|
|
276
|
+
data = json.load(infile)
|
|
277
|
+
with open(tmp_file.name, "w", encoding="utf-8") as outfile:
|
|
278
|
+
title = data["profile"]["metadata"]["title"]
|
|
279
|
+
data["profile"]["metadata"]["title"] = self.title_prefix + title
|
|
280
|
+
prof_name = data["profile"]["metadata"]["title"]
|
|
281
|
+
json.dump(data, outfile, indent=4)
|
|
282
|
+
self.logger.debug(prof_name)
|
|
283
|
+
# Pass default argument to click function
|
|
284
|
+
|
|
285
|
+
self.upload_profile(file_name=test_file_path, title=prof_name)
|
|
286
|
+
# delete extra data after we are finished
|
|
287
|
+
self.delete_inserted_profile(prof_name)
|
|
288
|
+
self.logger.debug(prof_name)
|
|
289
|
+
tmp_file.close()
|
|
290
|
+
os.remove(tmp_file.name)
|
|
291
|
+
|
|
292
|
+
def test_create_component(self):
|
|
293
|
+
"""Test Component Code"""
|
|
294
|
+
if not os.path.exists("processing"):
|
|
295
|
+
os.mkdir("processing")
|
|
296
|
+
component_file_path = self.get_tests_dir("tests") / "test_data/oscal_component.yaml"
|
|
297
|
+
with open(component_file_path, "r", encoding="utf-8") as infile:
|
|
298
|
+
data = infile.read()
|
|
299
|
+
self.logger.debug(data)
|
|
300
|
+
assert data
|
|
301
|
+
tmp_file = tempfile.NamedTemporaryFile(delete=False)
|
|
302
|
+
tmp_file.write(bytes(data, "utf-8"))
|
|
303
|
+
tmp_file.close()
|
|
304
|
+
os.rename(tmp_file.name, tmp_file.name + ".yaml")
|
|
305
|
+
filename = tmp_file.name + ".yaml"
|
|
306
|
+
process_component(filename)
|
|
307
|
+
os.remove(filename)
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def upload_profile(file_name, title) -> None:
|
|
311
|
+
"""
|
|
312
|
+
Upload the catalog
|
|
313
|
+
|
|
314
|
+
:param str file_name: file path to the catalog to upload to RegScale
|
|
315
|
+
:param str title: title of the catalog
|
|
316
|
+
:rtype: None
|
|
317
|
+
"""
|
|
318
|
+
from pathlib import Path
|
|
319
|
+
|
|
320
|
+
upload_profile(title=title, catalog=84, categorization="Moderate", file_name=Path(file_name))
|
|
321
|
+
|
|
322
|
+
@staticmethod
|
|
323
|
+
def upload_catalog(file_name) -> None:
|
|
324
|
+
"""Upload the catalog"""
|
|
325
|
+
upload_catalog(file_name=file_name)
|
|
326
|
+
|
|
327
|
+
def delete_catalog_items(self):
|
|
328
|
+
"""testing out deleting items for a catalog for debugging"""
|
|
329
|
+
# update api pool limits to max_thread count from init.yaml
|
|
330
|
+
self.api.pool_connections = (
|
|
331
|
+
self.config["maxThreads"]
|
|
332
|
+
if self.config["maxThreads"] > self.api.pool_connections
|
|
333
|
+
else self.api.pool_connections
|
|
334
|
+
)
|
|
335
|
+
self.api.pool_maxsize = (
|
|
336
|
+
self.config["maxThreads"] if self.config["maxThreads"] > self.api.pool_maxsize else self.api.pool_maxsize
|
|
337
|
+
)
|
|
338
|
+
inserted_items: list[dict] = [
|
|
339
|
+
{"file_name": "newParameters.json", "regscale_module": "controlParameters"},
|
|
340
|
+
{
|
|
341
|
+
"file_name": "newTests.json",
|
|
342
|
+
"regscale_module": "controlTestPlans",
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"file_name": "newObjectives.json",
|
|
346
|
+
"regscale_module": "controlObjectives",
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
"file_name": "newControls.json",
|
|
350
|
+
"regscale_module": "securitycontrols",
|
|
351
|
+
},
|
|
352
|
+
]
|
|
353
|
+
for file in inserted_items:
|
|
354
|
+
with open(f".{os.sep}processing{os.sep}{file['file_name']}", "r", encoding="utf-8") as infile:
|
|
355
|
+
data = json.load(infile)
|
|
356
|
+
create_threads(
|
|
357
|
+
process=self.delete_inserted_items,
|
|
358
|
+
args=(
|
|
359
|
+
data,
|
|
360
|
+
file["regscale_module"],
|
|
361
|
+
self.app.config,
|
|
362
|
+
self.api,
|
|
363
|
+
self.logger,
|
|
364
|
+
),
|
|
365
|
+
thread_count=len(data),
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
def delete_inserted_catalog(self, cat_name):
|
|
369
|
+
"""delete catalog"""
|
|
370
|
+
from regscale.models.regscale_models import Catalog
|
|
371
|
+
|
|
372
|
+
cat_found = False
|
|
373
|
+
cats = Catalog.get_list()
|
|
374
|
+
for cat in cats:
|
|
375
|
+
if cat.title == cat_name:
|
|
376
|
+
delete_this_cat = cat
|
|
377
|
+
cat_found = True
|
|
378
|
+
break
|
|
379
|
+
assert cat_found is True
|
|
380
|
+
self.logger.info(delete_this_cat.model_dump()) # noqa
|
|
381
|
+
response = delete_this_cat.delete()
|
|
382
|
+
assert response is True
|
|
383
|
+
|
|
384
|
+
def delete_inserted_profile(self, prof_name):
|
|
385
|
+
"""delete profile"""
|
|
386
|
+
from regscale.models.regscale_models import Profile
|
|
387
|
+
|
|
388
|
+
profs = Profile.get_list()
|
|
389
|
+
delete_this_prof = [prof for prof in profs if prof.name == prof_name][0]
|
|
390
|
+
self.logger.info(delete_this_prof.model_dump())
|
|
391
|
+
assert delete_this_prof is not None
|
|
392
|
+
response = delete_this_prof.delete()
|
|
393
|
+
assert response is True
|
|
394
|
+
|
|
395
|
+
@staticmethod
|
|
396
|
+
def delete_inserted_items(args: Tuple, thread: int) -> None:
|
|
397
|
+
"""
|
|
398
|
+
Delete items that were added to the catalog
|
|
399
|
+
:rtype: None
|
|
400
|
+
"""
|
|
401
|
+
inserted_items, regscale_module, config, api, logger = args
|
|
402
|
+
headers = {
|
|
403
|
+
"accept": "*/*",
|
|
404
|
+
"Authorization": config["token"],
|
|
405
|
+
}
|
|
406
|
+
# find which records should be executed by the current thread
|
|
407
|
+
threads = thread_assignment(thread=thread, total_items=len(inserted_items))
|
|
408
|
+
|
|
409
|
+
# iterate through the thread assignment items and process them
|
|
410
|
+
for i in range(len(threads)):
|
|
411
|
+
# set the recommendation for the thread for later use in the function
|
|
412
|
+
item = inserted_items[threads[i]]
|
|
413
|
+
|
|
414
|
+
url = f'{config["domain"]}/api/{regscale_module}/{item["id"]}'
|
|
415
|
+
response = api.delete(url=url, headers=headers)
|
|
416
|
+
if response.status_code == 200:
|
|
417
|
+
logger.info("Deleted #%s from %s\n%s", item["id"], regscale_module, item)
|
|
418
|
+
else:
|
|
419
|
+
logger.error(
|
|
420
|
+
"Unable to delete #%s from %s\n%s",
|
|
421
|
+
item["id"],
|
|
422
|
+
regscale_module,
|
|
423
|
+
item,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
@staticmethod
|
|
427
|
+
def test_empty_part():
|
|
428
|
+
part = {}
|
|
429
|
+
str_obj = ""
|
|
430
|
+
objectives = []
|
|
431
|
+
ctrl = {}
|
|
432
|
+
result = process_fedramp_objectives(part, str_obj, objectives, ctrl)
|
|
433
|
+
assert result == ""
|
|
434
|
+
|
|
435
|
+
@staticmethod
|
|
436
|
+
def test_create_new_objective():
|
|
437
|
+
part = {"name": "item", "prose": "This is a test objective", "id": "test_id"}
|
|
438
|
+
str_obj = ""
|
|
439
|
+
objectives = []
|
|
440
|
+
ctrl = {"id": "test_ctrl_id"}
|
|
441
|
+
_ = process_fedramp_objectives(part, str_obj, objectives, ctrl)
|
|
442
|
+
assert len(objectives) == 1
|
|
443
|
+
assert objectives[0]["name"] == "test_id"
|
|
444
|
+
assert objectives[0]["part_id"] == "test_ctrl_id"
|
|
445
|
+
|
|
446
|
+
@staticmethod
|
|
447
|
+
def test_nested_control_parts(oscal_control):
|
|
448
|
+
part = oscal_control["parts"][0]
|
|
449
|
+
str_obj = ""
|
|
450
|
+
objectives = []
|
|
451
|
+
result = process_fedramp_objectives(part, str_obj, objectives, oscal_control)
|
|
452
|
+
assert result == "The organization: "
|
|
453
|
+
assert len(objectives) == 4
|