regscale-cli 6.23.0.0__py3-none-any.whl → 6.24.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/core/app/application.py +2 -0
- regscale/integrations/commercial/__init__.py +1 -0
- regscale/integrations/commercial/sarif/sarif_converter.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +109 -2
- regscale/integrations/commercial/wizv2/compliance_report.py +1485 -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 +304 -51
- 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 +61 -3
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +3 -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.0.dist-info → regscale_cli-6.24.0.0.dist-info}/METADATA +9 -9
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/RECORD +44 -27
- 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.0.dist-info → regscale_cli-6.24.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.23.0.0.dist-info → regscale_cli-6.24.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,3661 @@
|
|
|
1
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
2
|
+
from io import StringIO
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from unittest.mock import MagicMock, Mock, mock_open, patch
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import (
|
|
11
|
+
ALT_IMPLEMENTATION,
|
|
12
|
+
CAN_BE_INHERITED_CSP,
|
|
13
|
+
CONFIGURED_BY_CUSTOMER,
|
|
14
|
+
CONTROL_ID,
|
|
15
|
+
INHERITED,
|
|
16
|
+
PROVIDED_BY_CUSTOMER,
|
|
17
|
+
SERVICE_PROVIDER_CORPORATE,
|
|
18
|
+
SERVICE_PROVIDER_HYBRID,
|
|
19
|
+
SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
20
|
+
SHARED,
|
|
21
|
+
map_implementation_status,
|
|
22
|
+
map_origination,
|
|
23
|
+
parse_cis_worksheet,
|
|
24
|
+
parse_crm_worksheet,
|
|
25
|
+
transform_control,
|
|
26
|
+
)
|
|
27
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementation, ControlImplementationStatus
|
|
28
|
+
from regscale.models.regscale_models.security_control import SecurityControl
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture()
|
|
32
|
+
def mock_cis_dataframe():
|
|
33
|
+
"""Create a mock DataFrame that simulates the CIS worksheet structure"""
|
|
34
|
+
# Define the column structure
|
|
35
|
+
columns = [
|
|
36
|
+
CONTROL_ID,
|
|
37
|
+
"Implemented",
|
|
38
|
+
ControlImplementationStatus.PartiallyImplemented,
|
|
39
|
+
"Planned",
|
|
40
|
+
ALT_IMPLEMENTATION,
|
|
41
|
+
ControlImplementationStatus.NA,
|
|
42
|
+
SERVICE_PROVIDER_CORPORATE,
|
|
43
|
+
SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
44
|
+
SERVICE_PROVIDER_HYBRID,
|
|
45
|
+
CONFIGURED_BY_CUSTOMER,
|
|
46
|
+
PROVIDED_BY_CUSTOMER,
|
|
47
|
+
SHARED,
|
|
48
|
+
INHERITED,
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# Create sample data rows
|
|
52
|
+
data = [
|
|
53
|
+
# Row 1: AC-1 (a) - Fully Implemented by Service Provider Corporate
|
|
54
|
+
["AC-1 (a)", "X", "", "", "", "", "X", "", "", "", "", "", ""],
|
|
55
|
+
# Row 2: AC-2 - Partially Implemented by Service Provider System Specific
|
|
56
|
+
["AC-2", "", "X", "", "", "", "", "X", "", "", "", "", ""],
|
|
57
|
+
# Row 3: AC-3 - Planned by Customer Configured
|
|
58
|
+
["AC-3", "", "", "X", "", "", "", "", "", "X", "", "", ""],
|
|
59
|
+
# Row 4: AC-4 - Not Applicable
|
|
60
|
+
["AC-4", "", "", "", "", "X", "", "", "", "", "", "", ""],
|
|
61
|
+
# Row 5: AC-5 - Alternative Implementation by Hybrid
|
|
62
|
+
["AC-5", "", "", "", "X", "", "", "", "X", "", "", "", ""],
|
|
63
|
+
# Row 6: AC-6 (1) - Implemented by Customer Provided
|
|
64
|
+
["AC-6 (1)", "X", "", "", "", "", "", "", "", "", "X", "", ""],
|
|
65
|
+
# Row 7: AC-7 - Shared responsibility
|
|
66
|
+
["AC-7", "X", "", "", "", "", "", "", "", "", "", "X", ""],
|
|
67
|
+
# Row 8: AC-8 - Inherited
|
|
68
|
+
["AC-8", "X", "", "", "", "", "", "", "", "", "", "", "X"],
|
|
69
|
+
# Row 9: AC-9 - Not Implemented (empty row)
|
|
70
|
+
["AC-9", "", "", "", "", "", "", "", "", "", "", "", ""],
|
|
71
|
+
# Row 10: AC-10 - Multiple statuses
|
|
72
|
+
["AC-10", "X", "X", "", "", "", "X", "", "", "", "", "", ""],
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
return pd.DataFrame(data, columns=columns)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.fixture()
|
|
79
|
+
def mock_crm_validator_data():
|
|
80
|
+
"""Create a mock validator.data that includes header rows for CRM worksheet"""
|
|
81
|
+
csv_string = """
|
|
82
|
+
'FedRAMP High Customer Responsibility Matrix (CRM) Worksheet,Unnamed: 1,Unnamed: 2\n"GUIDANCE: \n\n• Refer to CSP responses in the completed CIS Worksheet, “Control Origination” section. \n\n• For Control IDs identified in the CIS Worksheet as Service Provider Corporate, Service Provider System Specific, or Service Provider Hybrid (Corporate and System Specific), enter ""Yes"" in the ""Can Be Inherited from CSP"" column below, and leave the ""Specific Inheritance and Customer Agency/CSP Responsibilities"" column blank. \n\n• For Control IDs identified in the CIS Worksheet as Shared (Service Provider and Customer Responsibility), enter ""Partial"" in the ""Can Be Inherited from CSP"" column (below). In the ""Specific Inheritance and Customer Agency/CSP Responsibilities"" column, describe which elements are inherited from the CSP and describe the customer responsibilities. \n\n• For Control IDs identified in the CIS Worksheet as Configured by Customer (Customer System Specific) or Provided by Customer (Customer System Specific), enter ""No"" in the ""Can Be Inherited from CSP"" column (below). In the ""Specific Inheritance and Customer Agency/CSP Responsibilities"" column, explain why the Control ID cannot be inherited, and describe the customer responsibilities. \n\n• For CSPs that offer a variety of services or features, the CSP must clearly describe any customer responsibilities associated with each service or feature. In the ""Specific Inheritance and Customer Agency/CSP Responsibilities"" column, for each affected control, the CSP must clearly link the responsibilities to the service or feature. CSPs, with multiple services or features, may wish to add a key to the CRM Worksheet. See the examples below: \n\n- Customer responsibilities noted with ""<ServiceName A>:"" are added if <ServiceName A> is an optional service that can be used by the customer. \n- Customer responsibilities noted with ""<ServiceName B>:"" are added if <ServiceName B> is an optional service that can be used by the customer. \n\n• Example CRM responses, for sample Control IDs, are provided in the Example CRM Worksheet Responses sheet of this workbook.\n",,\nControl ID,Can Be Inherited from CSP,Specific Inheritance and Customer Agency/CSP Responsibilities\nAC-1(a),Yes,\nAC-1(b),Yes,\nAC-1(c),Yes,\nAC-2(a),Partial,Netskope creates the initial customer administrator account for the federal customer in the application. Customer Responsibility: The federal customer administrator is responsible for creating the NGC accounts for the individual users within their organization and identify the types of accounts to support their mission or business functions.\nAC-2(b),Partial,Netskope creates the initial customer administrator account for the federal customer in the application. Customer Responsibility: The federal customer administrator is responsible for creating the NGC accounts for the individual users within their organization and identify the types of accounts to support their mission or business functions.\nAC-2(c),Partial,Federal customers are responsible for establishing conditions for role membership for their NGC accounts.\nAC-2(d),Partial,Customers are responsible for specifying which users within their organization can have NGC accounts.\nAC-2(e),Partial,Customers are responsible for approving access to their NGC accounts.\n'
|
|
83
|
+
"""
|
|
84
|
+
df = pd.read_csv(StringIO(csv_string))
|
|
85
|
+
# remove the last row
|
|
86
|
+
df = df.iloc[:-1]
|
|
87
|
+
return df
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.fixture()
|
|
91
|
+
def mock_validator_data(mock_cis_dataframe):
|
|
92
|
+
"""Create a mock validator.data that includes header rows"""
|
|
93
|
+
# Add header rows that would be skipped
|
|
94
|
+
header_rows = pd.DataFrame(
|
|
95
|
+
[
|
|
96
|
+
["", "", "", "", "", "", "", "", "", "", "", "", ""], # Empty row
|
|
97
|
+
["CIS Controls", "", "", "", "", "", "", "", "", "", "", "", ""], # Title row
|
|
98
|
+
list(mock_cis_dataframe.columns), # Column headers
|
|
99
|
+
],
|
|
100
|
+
columns=mock_cis_dataframe.columns,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Combine header rows with data
|
|
104
|
+
full_df = pd.concat([header_rows, mock_cis_dataframe], ignore_index=True)
|
|
105
|
+
return full_df
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
109
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
110
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.determine_skip_row")
|
|
111
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ThreadPoolExecutor")
|
|
112
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.clean_key")
|
|
113
|
+
def test_parse_cis_worksheet_success(
|
|
114
|
+
mock_clean_key,
|
|
115
|
+
mock_thread_pool,
|
|
116
|
+
mock_determine_skip_row,
|
|
117
|
+
mock_import_validator,
|
|
118
|
+
mock_get_pandas,
|
|
119
|
+
mock_validator_data,
|
|
120
|
+
):
|
|
121
|
+
"""Test successful parsing of CIS worksheet"""
|
|
122
|
+
# Setup mocks
|
|
123
|
+
mock_pandas = Mock()
|
|
124
|
+
mock_get_pandas.return_value = mock_pandas
|
|
125
|
+
|
|
126
|
+
# Mock the validator
|
|
127
|
+
mock_validator_instance = Mock()
|
|
128
|
+
mock_validator_instance.data = mock_validator_data
|
|
129
|
+
mock_import_validator.return_value = mock_validator_instance
|
|
130
|
+
|
|
131
|
+
# Mock determine_skip_row to return 3 (skipping header rows)
|
|
132
|
+
mock_determine_skip_row.return_value = 3
|
|
133
|
+
|
|
134
|
+
# Mock ThreadPoolExecutor
|
|
135
|
+
mock_executor = Mock()
|
|
136
|
+
mock_thread_pool.return_value.__enter__.return_value = mock_executor
|
|
137
|
+
|
|
138
|
+
# Mock the processed results
|
|
139
|
+
expected_results = [
|
|
140
|
+
{
|
|
141
|
+
"control_id": "AC-1 (a)",
|
|
142
|
+
"regscale_control_id": "ac-1.a",
|
|
143
|
+
"implementation_status": "Implemented",
|
|
144
|
+
"control_origination": "Service Provider Corporate",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"control_id": "AC-2",
|
|
148
|
+
"regscale_control_id": "ac-2",
|
|
149
|
+
"implementation_status": "Partially Implemented",
|
|
150
|
+
"control_origination": "Service Provider System Specific",
|
|
151
|
+
},
|
|
152
|
+
]
|
|
153
|
+
mock_executor.map.return_value = expected_results
|
|
154
|
+
|
|
155
|
+
# Mock clean_key to return the control_id as-is
|
|
156
|
+
mock_clean_key.side_effect = lambda x: x
|
|
157
|
+
|
|
158
|
+
# Call the function
|
|
159
|
+
result = parse_cis_worksheet("test_file.xlsx", "CIS Sheet")
|
|
160
|
+
|
|
161
|
+
# Assertions
|
|
162
|
+
assert result == {"AC-1 (a)": expected_results[0], "AC-2": expected_results[1]}
|
|
163
|
+
|
|
164
|
+
# Verify mocks were called correctly
|
|
165
|
+
mock_import_validator.assert_called_once()
|
|
166
|
+
mock_determine_skip_row.assert_called_once_with(
|
|
167
|
+
original_df=mock_validator_data, text_to_find=CONTROL_ID, original_skip=2
|
|
168
|
+
)
|
|
169
|
+
mock_thread_pool.assert_called_once()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
173
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
174
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.determine_skip_row")
|
|
175
|
+
def test_parse_cis_worksheet_dataframe_processing(
|
|
176
|
+
mock_determine_skip_row, mock_import_validator, mock_get_pandas, mock_cis_dataframe
|
|
177
|
+
):
|
|
178
|
+
"""Test the DataFrame processing logic in parse_cis_worksheet"""
|
|
179
|
+
# Setup mocks
|
|
180
|
+
mock_pandas = Mock()
|
|
181
|
+
mock_get_pandas.return_value = mock_pandas
|
|
182
|
+
|
|
183
|
+
# Create a mock validator with our test data
|
|
184
|
+
mock_validator_instance = Mock()
|
|
185
|
+
mock_validator_instance.data = mock_cis_dataframe
|
|
186
|
+
mock_import_validator.return_value = mock_validator_instance
|
|
187
|
+
|
|
188
|
+
# Mock determine_skip_row to return 0 (no header rows to skip)
|
|
189
|
+
mock_determine_skip_row.return_value = 0
|
|
190
|
+
|
|
191
|
+
# Mock ThreadPoolExecutor to return processed results
|
|
192
|
+
with patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ThreadPoolExecutor") as mock_thread_pool:
|
|
193
|
+
mock_executor = Mock()
|
|
194
|
+
mock_thread_pool.return_value.__enter__.return_value = mock_executor
|
|
195
|
+
|
|
196
|
+
# Mock the processed results based on our test data
|
|
197
|
+
expected_results = [
|
|
198
|
+
{
|
|
199
|
+
"control_id": "AC-1 (a)",
|
|
200
|
+
"regscale_control_id": "ac-1.a",
|
|
201
|
+
"implementation_status": "Implemented",
|
|
202
|
+
"control_origination": "Service Provider Corporate",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"control_id": "AC-2",
|
|
206
|
+
"regscale_control_id": "ac-2",
|
|
207
|
+
"implementation_status": "Partially Implemented",
|
|
208
|
+
"control_origination": "Service Provider System Specific",
|
|
209
|
+
},
|
|
210
|
+
]
|
|
211
|
+
mock_executor.map.return_value = expected_results
|
|
212
|
+
|
|
213
|
+
# Mock clean_key
|
|
214
|
+
with patch("regscale.integrations.public.fedramp.fedramp_cis_crm.clean_key") as mock_clean_key:
|
|
215
|
+
mock_clean_key.side_effect = lambda x: x
|
|
216
|
+
|
|
217
|
+
# Call the function
|
|
218
|
+
result = parse_cis_worksheet("test_file.xlsx", "CIS Sheet")
|
|
219
|
+
|
|
220
|
+
# Verify the DataFrame processing
|
|
221
|
+
# The function should have called iloc, reset_index, columns assignment, etc.
|
|
222
|
+
assert result == {"AC-1 (a)": expected_results[0], "AC-2": expected_results[1]}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
226
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
227
|
+
def test_parse_cis_worksheet_empty_data(mock_import_validator, mock_get_pandas):
|
|
228
|
+
"""Test parsing with empty data"""
|
|
229
|
+
# Setup mocks
|
|
230
|
+
mock_pandas = Mock()
|
|
231
|
+
mock_get_pandas.return_value = mock_pandas
|
|
232
|
+
|
|
233
|
+
# Create empty DataFrame
|
|
234
|
+
empty_df = pd.DataFrame()
|
|
235
|
+
mock_validator_instance = Mock()
|
|
236
|
+
mock_validator_instance.data = empty_df
|
|
237
|
+
mock_import_validator.return_value = mock_validator_instance
|
|
238
|
+
|
|
239
|
+
# Mock determine_skip_row
|
|
240
|
+
with patch("regscale.integrations.public.fedramp.fedramp_cis_crm.determine_skip_row") as mock_determine_skip_row:
|
|
241
|
+
mock_determine_skip_row.return_value = 0
|
|
242
|
+
|
|
243
|
+
# Mock ThreadPoolExecutor
|
|
244
|
+
with patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ThreadPoolExecutor") as mock_thread_pool:
|
|
245
|
+
mock_executor = Mock()
|
|
246
|
+
mock_thread_pool.return_value.__enter__.return_value = mock_executor
|
|
247
|
+
mock_executor.map.return_value = []
|
|
248
|
+
|
|
249
|
+
# Mock clean_key
|
|
250
|
+
with patch("regscale.integrations.public.fedramp.fedramp_cis_crm.clean_key") as mock_clean_key:
|
|
251
|
+
mock_clean_key.side_effect = lambda x: x
|
|
252
|
+
|
|
253
|
+
# Call the function
|
|
254
|
+
result = parse_cis_worksheet("test_file.xlsx", "CIS Sheet")
|
|
255
|
+
|
|
256
|
+
# Should return empty dict
|
|
257
|
+
assert result == {}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
261
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
262
|
+
def test_parse_cis_worksheet_with_nan_values(mock_import_validator, mock_get_pandas):
|
|
263
|
+
"""Test parsing with NaN values in the data"""
|
|
264
|
+
# Setup mocks
|
|
265
|
+
mock_pandas = Mock()
|
|
266
|
+
mock_get_pandas.return_value = mock_pandas
|
|
267
|
+
|
|
268
|
+
# Create DataFrame with NaN values
|
|
269
|
+
data_with_nan = pd.DataFrame(
|
|
270
|
+
[
|
|
271
|
+
["AC-1", "X", "", "", "", "", "X", "", "", "", "", "", ""],
|
|
272
|
+
["AC-2", "", "", "", "", "", "", "", "", "", "", "", ""], # Empty row
|
|
273
|
+
["AC-3", "X", "X", "", "", "", "", "", "", "", "", "", ""],
|
|
274
|
+
],
|
|
275
|
+
columns=[
|
|
276
|
+
CONTROL_ID,
|
|
277
|
+
"Implemented",
|
|
278
|
+
ControlImplementationStatus.PartiallyImplemented,
|
|
279
|
+
"Planned",
|
|
280
|
+
ALT_IMPLEMENTATION,
|
|
281
|
+
ControlImplementationStatus.NA,
|
|
282
|
+
SERVICE_PROVIDER_CORPORATE,
|
|
283
|
+
SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
284
|
+
SERVICE_PROVIDER_HYBRID,
|
|
285
|
+
CONFIGURED_BY_CUSTOMER,
|
|
286
|
+
PROVIDED_BY_CUSTOMER,
|
|
287
|
+
SHARED,
|
|
288
|
+
INHERITED,
|
|
289
|
+
],
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
mock_validator_instance = Mock()
|
|
293
|
+
mock_validator_instance.data = data_with_nan
|
|
294
|
+
mock_import_validator.return_value = mock_validator_instance
|
|
295
|
+
|
|
296
|
+
# Mock determine_skip_row
|
|
297
|
+
with patch("regscale.integrations.public.fedramp.fedramp_cis_crm.determine_skip_row") as mock_determine_skip_row:
|
|
298
|
+
mock_determine_skip_row.return_value = 0
|
|
299
|
+
|
|
300
|
+
# Mock ThreadPoolExecutor
|
|
301
|
+
with patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ThreadPoolExecutor") as mock_thread_pool:
|
|
302
|
+
mock_executor = Mock()
|
|
303
|
+
mock_thread_pool.return_value.__enter__.return_value = mock_executor
|
|
304
|
+
|
|
305
|
+
# Mock the processed results
|
|
306
|
+
expected_results = [
|
|
307
|
+
{
|
|
308
|
+
"control_id": "AC-1",
|
|
309
|
+
"regscale_control_id": "ac-1",
|
|
310
|
+
"implementation_status": "Implemented",
|
|
311
|
+
"control_origination": "Service Provider Corporate",
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"control_id": "AC-2",
|
|
315
|
+
"regscale_control_id": "ac-2",
|
|
316
|
+
"implementation_status": "",
|
|
317
|
+
"control_origination": "",
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"control_id": "AC-3",
|
|
321
|
+
"regscale_control_id": "ac-3",
|
|
322
|
+
"implementation_status": "Implemented, Partially Implemented",
|
|
323
|
+
"control_origination": "",
|
|
324
|
+
},
|
|
325
|
+
]
|
|
326
|
+
mock_executor.map.return_value = expected_results
|
|
327
|
+
|
|
328
|
+
# Mock clean_key
|
|
329
|
+
with patch("regscale.integrations.public.fedramp.fedramp_cis_crm.clean_key") as mock_clean_key:
|
|
330
|
+
mock_clean_key.side_effect = lambda x: x
|
|
331
|
+
|
|
332
|
+
# Call the function
|
|
333
|
+
result = parse_cis_worksheet("test_file.xlsx", "CIS Sheet")
|
|
334
|
+
|
|
335
|
+
# Verify results
|
|
336
|
+
assert len(result) == 3
|
|
337
|
+
assert "AC-1" in result
|
|
338
|
+
assert "AC-2" in result
|
|
339
|
+
assert "AC-3" in result
|
|
340
|
+
|
|
341
|
+
# Verify the status extraction logic worked correctly
|
|
342
|
+
assert result["AC-1"]["implementation_status"] == expected_results[0]["implementation_status"]
|
|
343
|
+
assert result["AC-2"]["implementation_status"] == expected_results[1]["implementation_status"]
|
|
344
|
+
assert result["AC-3"]["implementation_status"] == expected_results[2]["implementation_status"]
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
348
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.determine_skip_row")
|
|
349
|
+
def test_parse_crm_worksheet_success(mock_determine_skip_row, mock_import_validator, mock_crm_validator_data):
|
|
350
|
+
"""Test successful parsing of CRM worksheet"""
|
|
351
|
+
# Setup mocks
|
|
352
|
+
mock_validator_instance = Mock()
|
|
353
|
+
mock_validator_instance.data = mock_crm_validator_data
|
|
354
|
+
mock_import_validator.return_value = mock_validator_instance
|
|
355
|
+
|
|
356
|
+
# Mock determine_skip_row to return 2 (skipping header rows)
|
|
357
|
+
mock_determine_skip_row.return_value = 2
|
|
358
|
+
|
|
359
|
+
# Call the function
|
|
360
|
+
result = parse_crm_worksheet("test_file.xlsx", "CRM Sheet", "rev5")
|
|
361
|
+
|
|
362
|
+
# Assertions - should exclude rows where "Can Be Inherited from CSP" == "No"
|
|
363
|
+
assert len(result) == 8 # Should exclude AC-3, AC-9, AC-12 (which have "No")
|
|
364
|
+
|
|
365
|
+
# Check specific entries
|
|
366
|
+
assert "AC-1(a)" in result
|
|
367
|
+
assert result["AC-1(a)"]["control_id"] == "AC-1(a)"
|
|
368
|
+
assert result["AC-1(a)"]["can_be_inherited_from_csp"] == "Yes"
|
|
369
|
+
assert (
|
|
370
|
+
result["AC-2(b)"]["specific_inheritance_and_customer_agency_csp_responsibilities"]
|
|
371
|
+
== "Netskope creates the initial customer administrator account for the federal customer in the application. Customer Responsibility: The federal customer administrator is responsible for creating the NGC accounts for the individual users within their organization and identify the types of accounts to support their mission or business functions."
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Verify excluded entries are not present
|
|
375
|
+
assert "AC-3" not in result
|
|
376
|
+
assert "AC-9" not in result
|
|
377
|
+
assert "AC-12" not in result
|
|
378
|
+
|
|
379
|
+
# Verify mocks were called correctly
|
|
380
|
+
mock_import_validator.assert_called_once()
|
|
381
|
+
mock_determine_skip_row.assert_called_once_with(
|
|
382
|
+
original_df=mock_crm_validator_data, text_to_find=CONTROL_ID, original_skip=3
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
387
|
+
def test_parse_crm_worksheet_empty_data(mock_import_validator):
|
|
388
|
+
"""Test parsing CRM worksheet with empty data"""
|
|
389
|
+
# Setup mocks
|
|
390
|
+
empty_df = pd.DataFrame()
|
|
391
|
+
mock_validator_instance = Mock()
|
|
392
|
+
mock_validator_instance.data = empty_df
|
|
393
|
+
mock_import_validator.return_value = mock_validator_instance
|
|
394
|
+
|
|
395
|
+
# Call the function
|
|
396
|
+
result = parse_crm_worksheet("test_file.xlsx", "CRM Sheet", "rev5")
|
|
397
|
+
|
|
398
|
+
# Should return empty dict
|
|
399
|
+
assert result == {}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
403
|
+
def test_parse_crm_worksheet_no_crm_sheet_name(mock_import_validator):
|
|
404
|
+
"""Test parsing CRM worksheet with no CRM sheet name"""
|
|
405
|
+
# Call the function with empty CRM sheet name
|
|
406
|
+
result = parse_crm_worksheet("test_file.xlsx", "", "rev5")
|
|
407
|
+
|
|
408
|
+
# Should return empty dict
|
|
409
|
+
assert result == {}
|
|
410
|
+
|
|
411
|
+
# ImportValidater should not be called
|
|
412
|
+
mock_import_validator.assert_not_called()
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
416
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.determine_skip_row")
|
|
417
|
+
def test_parse_crm_worksheet_filtering_logic(mock_determine_skip_row, mock_import_validator, mock_crm_validator_data):
|
|
418
|
+
"""Test the filtering logic that excludes rows where 'Can Be Inherited from CSP' == 'No'"""
|
|
419
|
+
# Setup mocks
|
|
420
|
+
mock_validator_instance = Mock()
|
|
421
|
+
mock_validator_instance.data = mock_crm_validator_data
|
|
422
|
+
mock_import_validator.return_value = mock_validator_instance
|
|
423
|
+
mock_determine_skip_row.return_value = 2 # No header rows to skip
|
|
424
|
+
|
|
425
|
+
# Call the function
|
|
426
|
+
result = parse_crm_worksheet("test_file.xlsx", "CRM Sheet", "rev5")
|
|
427
|
+
|
|
428
|
+
# Should exclude rows where "Can Be Inherited from CSP" == "No"
|
|
429
|
+
# From our test data: AC-3, AC-9, AC-12 have "No"
|
|
430
|
+
excluded_controls = ["AC-3", "AC-9", "AC-12"]
|
|
431
|
+
included_controls = ["AC-1(a)", "AC-1(b)", "AC-1(c)", "AC-2(a)", "AC-2(b)", "AC-2(c)", "AC-2(d)", "AC-2(e)"]
|
|
432
|
+
|
|
433
|
+
# Verify excluded controls are not in result
|
|
434
|
+
for control in excluded_controls:
|
|
435
|
+
assert control not in result
|
|
436
|
+
|
|
437
|
+
# Verify included controls are in result
|
|
438
|
+
for control in included_controls:
|
|
439
|
+
assert control in result
|
|
440
|
+
|
|
441
|
+
# Verify total count
|
|
442
|
+
assert len(result) == len(included_controls)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
446
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.determine_skip_row")
|
|
447
|
+
def test_parse_crm_worksheet_data_structure(mock_determine_skip_row, mock_import_validator, mock_crm_validator_data):
|
|
448
|
+
"""Test the structure of the returned CRM data"""
|
|
449
|
+
# Setup mocks
|
|
450
|
+
mock_validator_instance = Mock()
|
|
451
|
+
mock_validator_instance.data = mock_crm_validator_data
|
|
452
|
+
mock_import_validator.return_value = mock_validator_instance
|
|
453
|
+
mock_determine_skip_row.return_value = 2
|
|
454
|
+
|
|
455
|
+
# Call the function
|
|
456
|
+
result = parse_crm_worksheet("test_file.xlsx", "CRM Sheet", "rev5")
|
|
457
|
+
|
|
458
|
+
# Check structure of returned data
|
|
459
|
+
for control_id, data in result.items():
|
|
460
|
+
# Verify required keys exist
|
|
461
|
+
assert "control_id" in data
|
|
462
|
+
assert "clean_control_id" in data
|
|
463
|
+
assert "regscale_control_id" in data
|
|
464
|
+
assert "can_be_inherited_from_csp" in data
|
|
465
|
+
assert "specific_inheritance_and_customer_agency_csp_responsibilities" in data
|
|
466
|
+
|
|
467
|
+
# Verify data types
|
|
468
|
+
assert isinstance(data["control_id"], str)
|
|
469
|
+
assert isinstance(data["clean_control_id"], str)
|
|
470
|
+
assert isinstance(data["regscale_control_id"], str)
|
|
471
|
+
assert isinstance(data["can_be_inherited_from_csp"], str)
|
|
472
|
+
assert isinstance(data["specific_inheritance_and_customer_agency_csp_responsibilities"], str)
|
|
473
|
+
|
|
474
|
+
# Verify clean_control_id is lowercase and processed
|
|
475
|
+
assert data["clean_control_id"] == data["clean_control_id"].lower()
|
|
476
|
+
assert " " not in data["clean_control_id"] # No spaces in clean_control_id
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.ImportValidater")
|
|
480
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.determine_skip_row")
|
|
481
|
+
def test_parse_crm_worksheet_column_validation(mock_determine_skip_row, mock_import_validator):
|
|
482
|
+
"""Test column validation in CRM worksheet parsing"""
|
|
483
|
+
# Create DataFrame with missing columns
|
|
484
|
+
invalid_df = pd.DataFrame(
|
|
485
|
+
[
|
|
486
|
+
["", "", ""], # Empty row
|
|
487
|
+
["CRM Controls", "", ""], # Title row
|
|
488
|
+
["Control ID", "Wrong Column", "Another Wrong Column"], # Wrong headers
|
|
489
|
+
]
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
mock_validator_instance = Mock()
|
|
493
|
+
mock_validator_instance.data = invalid_df
|
|
494
|
+
mock_import_validator.return_value = mock_validator_instance
|
|
495
|
+
mock_determine_skip_row.return_value = 3
|
|
496
|
+
|
|
497
|
+
# This should raise an error due to missing required columns
|
|
498
|
+
with pytest.raises(SystemExit):
|
|
499
|
+
parse_crm_worksheet("test_file.xlsx", "CRM Sheet", "rev5")
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@staticmethod
|
|
503
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.SecurityPlan")
|
|
504
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
505
|
+
def test_create_backup_file_success(mock_logger, mock_security_plan):
|
|
506
|
+
"""Test successful backup file creation"""
|
|
507
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import create_backup_file
|
|
508
|
+
|
|
509
|
+
# Mock the SecurityPlan.export_cis_crm method
|
|
510
|
+
mock_security_plan.export_cis_crm.return_value = {
|
|
511
|
+
"status": "complete",
|
|
512
|
+
"trustedDisplayName": "test_backup_file.docx",
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
# Call the function
|
|
516
|
+
create_backup_file(123)
|
|
517
|
+
|
|
518
|
+
# Verify SecurityPlan.export_cis_crm was called with correct parameter
|
|
519
|
+
mock_security_plan.export_cis_crm.assert_called_once_with(123)
|
|
520
|
+
|
|
521
|
+
# Verify logger calls
|
|
522
|
+
mock_logger.info.assert_any_call("Creating a CIS/CRM Backup file of the current SSP state ..")
|
|
523
|
+
mock_logger.info.assert_any_call("A CIS/CRM Backup file saved to SSP# 123 file subsystem as test_backup_file.docx!")
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@staticmethod
|
|
527
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.SecurityPlan")
|
|
528
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.click")
|
|
529
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
530
|
+
def test_create_backup_file_failure_user_continues(mock_logger, mock_click, mock_security_plan):
|
|
531
|
+
"""Test backup file creation failure with user choosing to continue"""
|
|
532
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import create_backup_file
|
|
533
|
+
|
|
534
|
+
# Mock the SecurityPlan.export_cis_crm method to return failed status
|
|
535
|
+
mock_security_plan.export_cis_crm.return_value = {"status": "failed", "error": "Backup failed"}
|
|
536
|
+
|
|
537
|
+
# Mock click.prompt to return True (user chooses to continue)
|
|
538
|
+
mock_click.prompt.return_value = True
|
|
539
|
+
|
|
540
|
+
# Call the function
|
|
541
|
+
create_backup_file(456)
|
|
542
|
+
|
|
543
|
+
# Verify SecurityPlan.export_cis_crm was called with correct parameter
|
|
544
|
+
mock_security_plan.export_cis_crm.assert_called_once_with(456)
|
|
545
|
+
|
|
546
|
+
# Verify logger calls
|
|
547
|
+
mock_logger.info.assert_called_once_with("Creating a CIS/CRM Backup file of the current SSP state ..")
|
|
548
|
+
|
|
549
|
+
# Verify click.prompt was called
|
|
550
|
+
mock_click.prompt.assert_called_once_with("Unable to create a backup file. Would you like to continue?", type=bool)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
@staticmethod
|
|
554
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.SecurityPlan")
|
|
555
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.click")
|
|
556
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.error_and_exit")
|
|
557
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
558
|
+
def test_create_backup_file_failure_user_exits(mock_logger, mock_error_and_exit, mock_click, mock_security_plan):
|
|
559
|
+
"""Test backup file creation failure with user choosing not to continue"""
|
|
560
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import create_backup_file
|
|
561
|
+
|
|
562
|
+
# Mock the SecurityPlan.export_cis_crm method to return failed status
|
|
563
|
+
mock_security_plan.export_cis_crm.return_value = {"status": "failed", "error": "Backup failed"}
|
|
564
|
+
|
|
565
|
+
# Mock click.prompt to return False (user chooses not to continue)
|
|
566
|
+
mock_click.prompt.return_value = False
|
|
567
|
+
|
|
568
|
+
# Call the function
|
|
569
|
+
create_backup_file(789)
|
|
570
|
+
|
|
571
|
+
# Verify SecurityPlan.export_cis_crm was called with correct parameter
|
|
572
|
+
mock_security_plan.export_cis_crm.assert_called_once_with(789)
|
|
573
|
+
|
|
574
|
+
# Verify logger calls
|
|
575
|
+
mock_logger.info.assert_called_once_with("Creating a CIS/CRM Backup file of the current SSP state ..")
|
|
576
|
+
|
|
577
|
+
# Verify click.prompt was called
|
|
578
|
+
mock_click.prompt.assert_called_once_with("Unable to create a backup file. Would you like to continue?", type=bool)
|
|
579
|
+
|
|
580
|
+
# Verify error_and_exit was called
|
|
581
|
+
mock_error_and_exit.assert_called_once()
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@staticmethod
|
|
585
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.SecurityPlan")
|
|
586
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.click")
|
|
587
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.error_and_exit")
|
|
588
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
589
|
+
def test_create_backup_file_no_status(mock_logger, mock_error_and_exit, mock_click, mock_security_plan):
|
|
590
|
+
"""Test backup file creation when response has no status field"""
|
|
591
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import create_backup_file
|
|
592
|
+
|
|
593
|
+
# Mock the SecurityPlan.export_cis_crm method to return response without status
|
|
594
|
+
mock_security_plan.export_cis_crm.return_value = {"trustedDisplayName": "test_backup_file.docx"}
|
|
595
|
+
|
|
596
|
+
# Mock click.prompt to return False (user chooses not to continue)
|
|
597
|
+
mock_click.prompt.return_value = False
|
|
598
|
+
|
|
599
|
+
# Call the function
|
|
600
|
+
create_backup_file(999)
|
|
601
|
+
|
|
602
|
+
# Verify SecurityPlan.export_cis_crm was called with correct parameter
|
|
603
|
+
mock_security_plan.export_cis_crm.assert_called_once_with(999)
|
|
604
|
+
|
|
605
|
+
# Verify logger calls
|
|
606
|
+
mock_logger.info.assert_called_once_with("Creating a CIS/CRM Backup file of the current SSP state ..")
|
|
607
|
+
|
|
608
|
+
# Verify click.prompt was called
|
|
609
|
+
mock_click.prompt.assert_called_once_with("Unable to create a backup file. Would you like to continue?", type=bool)
|
|
610
|
+
|
|
611
|
+
# Verify error_and_exit was called
|
|
612
|
+
mock_error_and_exit.assert_called_once()
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
@staticmethod
|
|
616
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.SecurityPlan")
|
|
617
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.click")
|
|
618
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
619
|
+
def test_create_backup_file_empty_response(mock_logger, mock_click, mock_security_plan):
|
|
620
|
+
"""Test backup file creation when response is empty"""
|
|
621
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import create_backup_file
|
|
622
|
+
|
|
623
|
+
# Mock the SecurityPlan.export_cis_crm method to return empty response
|
|
624
|
+
mock_security_plan.export_cis_crm.return_value = {}
|
|
625
|
+
|
|
626
|
+
# Mock click.prompt to return True (user chooses to continue)
|
|
627
|
+
mock_click.prompt.return_value = True
|
|
628
|
+
|
|
629
|
+
# Call the function
|
|
630
|
+
create_backup_file(111)
|
|
631
|
+
|
|
632
|
+
# Verify SecurityPlan.export_cis_crm was called with correct parameter
|
|
633
|
+
mock_security_plan.export_cis_crm.assert_called_once_with(111)
|
|
634
|
+
|
|
635
|
+
# Verify logger calls
|
|
636
|
+
mock_logger.info.assert_called_once_with("Creating a CIS/CRM Backup file of the current SSP state ..")
|
|
637
|
+
|
|
638
|
+
# Verify click.prompt was called
|
|
639
|
+
mock_click.prompt.assert_called_once_with("Unable to create a backup file. Would you like to continue?", type=bool)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
@staticmethod
|
|
643
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.SecurityPlan")
|
|
644
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
645
|
+
def test_create_backup_file_success_no_filename(mock_logger, mock_security_plan):
|
|
646
|
+
"""Test successful backup file creation without filename"""
|
|
647
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import create_backup_file
|
|
648
|
+
|
|
649
|
+
# Mock the SecurityPlan.export_cis_crm method
|
|
650
|
+
mock_security_plan.export_cis_crm.return_value = {
|
|
651
|
+
"status": "complete"
|
|
652
|
+
# No trustedDisplayName field
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
# Call the function
|
|
656
|
+
create_backup_file(222)
|
|
657
|
+
|
|
658
|
+
# Verify SecurityPlan.export_cis_crm was called with correct parameter
|
|
659
|
+
mock_security_plan.export_cis_crm.assert_called_once_with(222)
|
|
660
|
+
|
|
661
|
+
# Verify logger calls
|
|
662
|
+
mock_logger.info.assert_any_call("Creating a CIS/CRM Backup file of the current SSP state ..")
|
|
663
|
+
mock_logger.info.assert_any_call("A CIS/CRM Backup file saved to SSP# 222 file subsystem as None!")
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
@staticmethod
|
|
667
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.SecurityPlan")
|
|
668
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.click")
|
|
669
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
670
|
+
def test_create_backup_file_partial_status(mock_logger, mock_click, mock_security_plan):
|
|
671
|
+
"""Test backup file creation with partial status (not 'complete')"""
|
|
672
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import create_backup_file
|
|
673
|
+
|
|
674
|
+
# Mock the SecurityPlan.export_cis_crm method to return partial status
|
|
675
|
+
mock_security_plan.export_cis_crm.return_value = {
|
|
676
|
+
"status": "in_progress",
|
|
677
|
+
"trustedDisplayName": "test_backup_file.docx",
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
# Mock click.prompt to return True (user chooses to continue)
|
|
681
|
+
mock_click.prompt.return_value = True
|
|
682
|
+
|
|
683
|
+
# Call the function
|
|
684
|
+
create_backup_file(333)
|
|
685
|
+
|
|
686
|
+
# Verify SecurityPlan.export_cis_crm was called with correct parameter
|
|
687
|
+
mock_security_plan.export_cis_crm.assert_called_once_with(333)
|
|
688
|
+
|
|
689
|
+
# Verify logger calls
|
|
690
|
+
mock_logger.info.assert_called_once_with("Creating a CIS/CRM Backup file of the current SSP state ..")
|
|
691
|
+
|
|
692
|
+
# Verify click.prompt was called
|
|
693
|
+
mock_click.prompt.assert_called_once_with("Unable to create a backup file. Would you like to continue?", type=bool)
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
@staticmethod
|
|
697
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.File")
|
|
698
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.compute_hash")
|
|
699
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
700
|
+
def test_upload_file_new_file_success(mock_logger, mock_compute_hash, mock_file):
|
|
701
|
+
"""Test successful upload of a new file."""
|
|
702
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import upload_file
|
|
703
|
+
|
|
704
|
+
# Mock file path
|
|
705
|
+
file_path = Path("/test/path/test_file.docx")
|
|
706
|
+
|
|
707
|
+
# Mock API object
|
|
708
|
+
mock_api = MagicMock()
|
|
709
|
+
|
|
710
|
+
# Mock compute_hash to return a test hash
|
|
711
|
+
mock_compute_hash.return_value = "test_hash_123"
|
|
712
|
+
|
|
713
|
+
# Mock existing files (empty list - no identical files)
|
|
714
|
+
mock_file.get_files_for_parent_from_regscale.return_value = []
|
|
715
|
+
|
|
716
|
+
# Mock file content
|
|
717
|
+
mock_file_content = b"test file content"
|
|
718
|
+
|
|
719
|
+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
|
|
720
|
+
# Call the function
|
|
721
|
+
upload_file(file_path, 123, "securityplans", mock_api)
|
|
722
|
+
|
|
723
|
+
# Verify compute_hash was called
|
|
724
|
+
mock_compute_hash.assert_called_once()
|
|
725
|
+
|
|
726
|
+
# Verify get_files_for_parent_from_regscale was called
|
|
727
|
+
mock_file.get_files_for_parent_from_regscale.assert_called_once_with(123, "securityplans")
|
|
728
|
+
|
|
729
|
+
# Verify upload_file_to_regscale was called with correct parameters
|
|
730
|
+
mock_file.upload_file_to_regscale.assert_called_once_with(
|
|
731
|
+
file_name=file_path.absolute(), parent_id=123, parent_module="securityplans", api=mock_api, tags="cis-crm"
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
@staticmethod
|
|
736
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.File")
|
|
737
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.compute_hash")
|
|
738
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
739
|
+
def test_upload_file_identical_file_exists(mock_logger, mock_compute_hash, mock_file):
|
|
740
|
+
"""Test upload when identical file already exists."""
|
|
741
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import upload_file
|
|
742
|
+
|
|
743
|
+
# Mock file path
|
|
744
|
+
file_path = Path("/test/path/test_file.docx")
|
|
745
|
+
|
|
746
|
+
# Mock API object
|
|
747
|
+
mock_api = MagicMock()
|
|
748
|
+
|
|
749
|
+
# Mock compute_hash to return a test hash
|
|
750
|
+
mock_compute_hash.return_value = "test_hash_123"
|
|
751
|
+
|
|
752
|
+
# Mock existing file with same hash
|
|
753
|
+
mock_existing_file = MagicMock()
|
|
754
|
+
mock_existing_file.shaHash = "test_hash_123"
|
|
755
|
+
mock_existing_file.trustedDisplayName = "existing_file.docx"
|
|
756
|
+
|
|
757
|
+
# Mock existing files list with identical file
|
|
758
|
+
mock_file.get_files_for_parent_from_regscale.return_value = [mock_existing_file]
|
|
759
|
+
|
|
760
|
+
# Mock file content
|
|
761
|
+
mock_file_content = b"test file content"
|
|
762
|
+
|
|
763
|
+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
|
|
764
|
+
# Call the function
|
|
765
|
+
upload_file(file_path, 456, "securityplans", mock_api)
|
|
766
|
+
|
|
767
|
+
# Verify compute_hash was called
|
|
768
|
+
mock_compute_hash.assert_called_once()
|
|
769
|
+
|
|
770
|
+
# Verify get_files_for_parent_from_regscale was called
|
|
771
|
+
mock_file.get_files_for_parent_from_regscale.assert_called_once_with(456, "securityplans")
|
|
772
|
+
|
|
773
|
+
# Verify upload_file_to_regscale was NOT called (file already exists)
|
|
774
|
+
mock_file.upload_file_to_regscale.assert_not_called()
|
|
775
|
+
|
|
776
|
+
# Verify logger message was called
|
|
777
|
+
mock_logger.info.assert_called_once_with(
|
|
778
|
+
"An identical file existing_file.docx already exists in RegScale, skipping upload."
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
@staticmethod
|
|
783
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.File")
|
|
784
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.compute_hash")
|
|
785
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
786
|
+
def test_upload_file_no_hash_computed(mock_logger, mock_compute_hash, mock_file):
|
|
787
|
+
"""Test upload when no hash is computed (file read error)."""
|
|
788
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import upload_file
|
|
789
|
+
|
|
790
|
+
# Mock file path
|
|
791
|
+
file_path = Path("/test/path/test_file.docx")
|
|
792
|
+
|
|
793
|
+
# Mock API object
|
|
794
|
+
mock_api = MagicMock()
|
|
795
|
+
|
|
796
|
+
# Mock compute_hash to return None (file read error)
|
|
797
|
+
mock_compute_hash.return_value = None
|
|
798
|
+
|
|
799
|
+
# Mock existing files (empty list)
|
|
800
|
+
mock_file.get_files_for_parent_from_regscale.return_value = []
|
|
801
|
+
|
|
802
|
+
# Mock file content
|
|
803
|
+
mock_file_content = b"test file content"
|
|
804
|
+
|
|
805
|
+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
|
|
806
|
+
# Call the function
|
|
807
|
+
upload_file(file_path, 789, "securityplans", mock_api)
|
|
808
|
+
|
|
809
|
+
# Verify compute_hash was called
|
|
810
|
+
mock_compute_hash.assert_called_once()
|
|
811
|
+
|
|
812
|
+
# Verify get_files_for_parent_from_regscale was called
|
|
813
|
+
mock_file.get_files_for_parent_from_regscale.assert_called_once_with(789, "securityplans")
|
|
814
|
+
|
|
815
|
+
# Verify upload_file_to_regscale was called (no hash means no duplicate check)
|
|
816
|
+
mock_file.upload_file_to_regscale.assert_called_once_with(
|
|
817
|
+
file_name=file_path.absolute(), parent_id=789, parent_module="securityplans", api=mock_api, tags="cis-crm"
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
@staticmethod
|
|
822
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.File")
|
|
823
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.compute_hash")
|
|
824
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
825
|
+
def test_upload_file_different_hash_files(mock_logger, mock_compute_hash, mock_file):
|
|
826
|
+
"""Test upload when files exist but with different hashes."""
|
|
827
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import upload_file
|
|
828
|
+
|
|
829
|
+
# Mock file path
|
|
830
|
+
file_path = Path("/test/path/test_file.docx")
|
|
831
|
+
|
|
832
|
+
# Mock API object
|
|
833
|
+
mock_api = MagicMock()
|
|
834
|
+
|
|
835
|
+
# Mock compute_hash to return a test hash
|
|
836
|
+
mock_compute_hash.return_value = "new_hash_456"
|
|
837
|
+
|
|
838
|
+
# Mock existing files with different hash
|
|
839
|
+
mock_existing_file = MagicMock()
|
|
840
|
+
mock_existing_file.shaHash = "different_hash_789"
|
|
841
|
+
mock_existing_file.trustedDisplayName = "existing_file.docx"
|
|
842
|
+
|
|
843
|
+
# Mock existing files list with different hash file
|
|
844
|
+
mock_file.get_files_for_parent_from_regscale.return_value = [mock_existing_file]
|
|
845
|
+
|
|
846
|
+
# Mock file content
|
|
847
|
+
mock_file_content = b"test file content"
|
|
848
|
+
|
|
849
|
+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
|
|
850
|
+
# Call the function
|
|
851
|
+
upload_file(file_path, 101, "securityplans", mock_api)
|
|
852
|
+
|
|
853
|
+
# Verify compute_hash was called
|
|
854
|
+
mock_compute_hash.assert_called_once()
|
|
855
|
+
|
|
856
|
+
# Verify get_files_for_parent_from_regscale was called
|
|
857
|
+
mock_file.get_files_for_parent_from_regscale.assert_called_once_with(101, "securityplans")
|
|
858
|
+
|
|
859
|
+
# Verify upload_file_to_regscale was called (different hash means new file)
|
|
860
|
+
mock_file.upload_file_to_regscale.assert_called_once_with(
|
|
861
|
+
file_name=file_path.absolute(), parent_id=101, parent_module="securityplans", api=mock_api, tags="cis-crm"
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
@staticmethod
|
|
866
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.File")
|
|
867
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.compute_hash")
|
|
868
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
869
|
+
def test_upload_file_multiple_existing_files(mock_logger, mock_compute_hash, mock_file):
|
|
870
|
+
"""Test upload with multiple existing files, one with matching hash."""
|
|
871
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import upload_file
|
|
872
|
+
|
|
873
|
+
# Mock file path
|
|
874
|
+
file_path = Path("/test/path/test_file.docx")
|
|
875
|
+
|
|
876
|
+
# Mock API object
|
|
877
|
+
mock_api = MagicMock()
|
|
878
|
+
|
|
879
|
+
# Mock compute_hash to return a test hash
|
|
880
|
+
mock_compute_hash.return_value = "target_hash_123"
|
|
881
|
+
|
|
882
|
+
# Mock multiple existing files
|
|
883
|
+
mock_file1 = MagicMock()
|
|
884
|
+
mock_file1.shaHash = "different_hash_1"
|
|
885
|
+
mock_file1.trustedDisplayName = "file1.docx"
|
|
886
|
+
|
|
887
|
+
mock_file2 = MagicMock()
|
|
888
|
+
mock_file2.shaHash = "target_hash_123" # Matching hash
|
|
889
|
+
mock_file2.trustedDisplayName = "matching_file.docx"
|
|
890
|
+
|
|
891
|
+
mock_file3 = MagicMock()
|
|
892
|
+
mock_file3.shaHash = "different_hash_2"
|
|
893
|
+
mock_file3.trustedDisplayName = "file3.docx"
|
|
894
|
+
|
|
895
|
+
# Mock existing files list
|
|
896
|
+
mock_file.get_files_for_parent_from_regscale.return_value = [mock_file1, mock_file2, mock_file3]
|
|
897
|
+
|
|
898
|
+
# Mock file content
|
|
899
|
+
mock_file_content = b"test file content"
|
|
900
|
+
|
|
901
|
+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
|
|
902
|
+
# Call the function
|
|
903
|
+
upload_file(file_path, 202, "securityplans", mock_api)
|
|
904
|
+
|
|
905
|
+
# Verify compute_hash was called
|
|
906
|
+
mock_compute_hash.assert_called_once()
|
|
907
|
+
|
|
908
|
+
# Verify get_files_for_parent_from_regscale was called
|
|
909
|
+
mock_file.get_files_for_parent_from_regscale.assert_called_once_with(202, "securityplans")
|
|
910
|
+
|
|
911
|
+
# Verify upload_file_to_regscale was NOT called (matching file found)
|
|
912
|
+
mock_file.upload_file_to_regscale.assert_not_called()
|
|
913
|
+
|
|
914
|
+
# Verify logger message was called with the matching file name
|
|
915
|
+
mock_logger.info.assert_called_once_with(
|
|
916
|
+
"An identical file matching_file.docx already exists in RegScale, skipping upload."
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
@staticmethod
|
|
921
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.File")
|
|
922
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.compute_hash")
|
|
923
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
924
|
+
def test_upload_file_file_read_error(mock_logger, mock_compute_hash, mock_file):
|
|
925
|
+
"""Test upload when file read fails."""
|
|
926
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import upload_file
|
|
927
|
+
|
|
928
|
+
# Mock file path
|
|
929
|
+
file_path = Path("/test/path/nonexistent_file.docx")
|
|
930
|
+
|
|
931
|
+
# Mock API object
|
|
932
|
+
mock_api = MagicMock()
|
|
933
|
+
|
|
934
|
+
# Mock existing files (empty list)
|
|
935
|
+
mock_file.get_files_for_parent_from_regscale.return_value = []
|
|
936
|
+
|
|
937
|
+
# Mock file open to raise an exception
|
|
938
|
+
with patch("builtins.open", side_effect=FileNotFoundError("File not found")):
|
|
939
|
+
# Call the function - should handle the exception gracefully
|
|
940
|
+
with pytest.raises(FileNotFoundError):
|
|
941
|
+
upload_file(file_path, 303, "securityplans", mock_api)
|
|
942
|
+
|
|
943
|
+
# Verify get_files_for_parent_from_regscale was NOT called (function exits early on file error)
|
|
944
|
+
mock_file.get_files_for_parent_from_regscale.assert_not_called()
|
|
945
|
+
|
|
946
|
+
# Verify upload_file_to_regscale was NOT called due to exception
|
|
947
|
+
mock_file.upload_file_to_regscale.assert_not_called()
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
@staticmethod
|
|
951
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.File")
|
|
952
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.compute_hash")
|
|
953
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
954
|
+
def test_upload_file_different_parent_module(mock_logger, mock_compute_hash, mock_file):
|
|
955
|
+
"""Test upload with different parent module."""
|
|
956
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import upload_file
|
|
957
|
+
|
|
958
|
+
# Mock file path
|
|
959
|
+
file_path = Path("/test/path/test_file.docx")
|
|
960
|
+
|
|
961
|
+
# Mock API object
|
|
962
|
+
mock_api = MagicMock()
|
|
963
|
+
|
|
964
|
+
# Mock compute_hash to return a test hash
|
|
965
|
+
mock_compute_hash.return_value = "test_hash_123"
|
|
966
|
+
|
|
967
|
+
# Mock existing files (empty list)
|
|
968
|
+
mock_file.get_files_for_parent_from_regscale.return_value = []
|
|
969
|
+
|
|
970
|
+
# Mock file content
|
|
971
|
+
mock_file_content = b"test file content"
|
|
972
|
+
|
|
973
|
+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
|
|
974
|
+
# Call the function with different parent module
|
|
975
|
+
upload_file(file_path, 404, "components", mock_api)
|
|
976
|
+
|
|
977
|
+
# Verify compute_hash was called
|
|
978
|
+
mock_compute_hash.assert_called_once()
|
|
979
|
+
|
|
980
|
+
# Verify get_files_for_parent_from_regscale was called with correct module
|
|
981
|
+
mock_file.get_files_for_parent_from_regscale.assert_called_once_with(404, "components")
|
|
982
|
+
|
|
983
|
+
# Verify upload_file_to_regscale was called with correct parameters
|
|
984
|
+
mock_file.upload_file_to_regscale.assert_called_once_with(
|
|
985
|
+
file_name=file_path.absolute(), parent_id=404, parent_module="components", api=mock_api, tags="cis-crm"
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
@staticmethod
|
|
990
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.File")
|
|
991
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.compute_hash")
|
|
992
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
993
|
+
def test_upload_file_empty_file(mock_logger, mock_compute_hash, mock_file):
|
|
994
|
+
"""Test upload of an empty file."""
|
|
995
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import upload_file
|
|
996
|
+
|
|
997
|
+
# Mock file path
|
|
998
|
+
file_path = Path("/test/path/empty_file.docx")
|
|
999
|
+
|
|
1000
|
+
# Mock API object
|
|
1001
|
+
mock_api = MagicMock()
|
|
1002
|
+
|
|
1003
|
+
# Mock compute_hash to return a hash for empty file
|
|
1004
|
+
mock_compute_hash.return_value = "empty_file_hash"
|
|
1005
|
+
|
|
1006
|
+
# Mock existing files (empty list)
|
|
1007
|
+
mock_file.get_files_for_parent_from_regscale.return_value = []
|
|
1008
|
+
|
|
1009
|
+
# Mock empty file content
|
|
1010
|
+
mock_file_content = b""
|
|
1011
|
+
|
|
1012
|
+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
|
|
1013
|
+
# Call the function
|
|
1014
|
+
upload_file(file_path, 505, "securityplans", mock_api)
|
|
1015
|
+
|
|
1016
|
+
# Verify compute_hash was called
|
|
1017
|
+
mock_compute_hash.assert_called_once()
|
|
1018
|
+
|
|
1019
|
+
# Verify get_files_for_parent_from_regscale was called
|
|
1020
|
+
mock_file.get_files_for_parent_from_regscale.assert_called_once_with(505, "securityplans")
|
|
1021
|
+
|
|
1022
|
+
# Verify upload_file_to_regscale was called
|
|
1023
|
+
mock_file.upload_file_to_regscale.assert_called_once_with(
|
|
1024
|
+
file_name=file_path.absolute(), parent_id=505, parent_module="securityplans", api=mock_api, tags="cis-crm"
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
@staticmethod
|
|
1029
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.File")
|
|
1030
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.compute_hash")
|
|
1031
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1032
|
+
def test_upload_file_large_file(mock_logger, mock_compute_hash, mock_file):
|
|
1033
|
+
"""Test upload of a large file."""
|
|
1034
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import upload_file
|
|
1035
|
+
|
|
1036
|
+
# Mock file path
|
|
1037
|
+
file_path = Path("/test/path/large_file.docx")
|
|
1038
|
+
|
|
1039
|
+
# Mock API object
|
|
1040
|
+
mock_api = MagicMock()
|
|
1041
|
+
|
|
1042
|
+
# Mock compute_hash to return a hash for large file
|
|
1043
|
+
mock_compute_hash.return_value = "large_file_hash"
|
|
1044
|
+
|
|
1045
|
+
# Mock existing files (empty list)
|
|
1046
|
+
mock_file.get_files_for_parent_from_regscale.return_value = []
|
|
1047
|
+
|
|
1048
|
+
# Mock large file content (1MB of data)
|
|
1049
|
+
mock_file_content = b"x" * (1024 * 1024)
|
|
1050
|
+
|
|
1051
|
+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
|
|
1052
|
+
# Call the function
|
|
1053
|
+
upload_file(file_path, 606, "securityplans", mock_api)
|
|
1054
|
+
|
|
1055
|
+
# Verify compute_hash was called
|
|
1056
|
+
mock_compute_hash.assert_called_once()
|
|
1057
|
+
|
|
1058
|
+
# Verify get_files_for_parent_from_regscale was called
|
|
1059
|
+
mock_file.get_files_for_parent_from_regscale.assert_called_once_with(606, "securityplans")
|
|
1060
|
+
|
|
1061
|
+
# Verify upload_file_to_regscale was called
|
|
1062
|
+
mock_file.upload_file_to_regscale.assert_called_once_with(
|
|
1063
|
+
file_name=file_path.absolute(), parent_id=606, parent_module="securityplans", api=mock_api, tags="cis-crm"
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
@staticmethod
|
|
1068
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1069
|
+
def test_map_implementation_status_single_implemented(mock_logger):
|
|
1070
|
+
"""Test mapping implementation status when all records are implemented"""
|
|
1071
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1072
|
+
|
|
1073
|
+
# Test data with single implemented status
|
|
1074
|
+
cis_data = {"record1": {"regscale_control_id": "AC-1", "implementation_status": "Implemented"}}
|
|
1075
|
+
|
|
1076
|
+
result = map_implementation_status("AC-1", cis_data)
|
|
1077
|
+
|
|
1078
|
+
assert result == ControlImplementationStatus.Implemented
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
@staticmethod
|
|
1082
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1083
|
+
def test_map_implementation_status_single_partially_implemented(mock_logger):
|
|
1084
|
+
"""Test mapping implementation status when all records are partially implemented"""
|
|
1085
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1086
|
+
|
|
1087
|
+
# Test data with single partially implemented status
|
|
1088
|
+
cis_data = {"record1": {"regscale_control_id": "AC-2", "implementation_status": "Partially Implemented"}}
|
|
1089
|
+
|
|
1090
|
+
result = map_implementation_status("AC-2", cis_data)
|
|
1091
|
+
|
|
1092
|
+
assert result == ControlImplementationStatus.PartiallyImplemented
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
@staticmethod
|
|
1096
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1097
|
+
def test_map_implementation_status_single_planned(mock_logger):
|
|
1098
|
+
"""Test mapping implementation status when all records are planned"""
|
|
1099
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1100
|
+
|
|
1101
|
+
# Test data with single planned status
|
|
1102
|
+
cis_data = {"record1": {"regscale_control_id": "AC-3", "implementation_status": "Planned"}}
|
|
1103
|
+
|
|
1104
|
+
result = map_implementation_status("AC-3", cis_data)
|
|
1105
|
+
|
|
1106
|
+
assert result == ControlImplementationStatus.Planned
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
@staticmethod
|
|
1110
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1111
|
+
def test_map_implementation_status_single_na(mock_logger):
|
|
1112
|
+
"""Test mapping implementation status when all records are N/A"""
|
|
1113
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1114
|
+
|
|
1115
|
+
# Test data with single N/A status
|
|
1116
|
+
cis_data = {"record1": {"regscale_control_id": "AC-4", "implementation_status": "N/A"}}
|
|
1117
|
+
|
|
1118
|
+
result = map_implementation_status("AC-4", cis_data)
|
|
1119
|
+
|
|
1120
|
+
assert result == ControlImplementationStatus.NA
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
@staticmethod
|
|
1124
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1125
|
+
def test_map_implementation_status_single_alternative_implementation(mock_logger):
|
|
1126
|
+
"""Test mapping implementation status when all records are alternative implementation"""
|
|
1127
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1128
|
+
|
|
1129
|
+
# Test data with single alternative implementation status
|
|
1130
|
+
cis_data = {"record1": {"regscale_control_id": "AC-5", "implementation_status": "Alternative Implementation"}}
|
|
1131
|
+
|
|
1132
|
+
result = map_implementation_status("AC-5", cis_data)
|
|
1133
|
+
|
|
1134
|
+
assert result == ControlImplementationStatus.Alternative
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
@staticmethod
|
|
1138
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1139
|
+
def test_map_implementation_status_single_alt_implementation(mock_logger):
|
|
1140
|
+
"""Test mapping implementation status when all records are alternate implementation"""
|
|
1141
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1142
|
+
|
|
1143
|
+
# Test data with single alternate implementation status
|
|
1144
|
+
cis_data = {"record1": {"regscale_control_id": "AC-6", "implementation_status": "Alternate Implementation"}}
|
|
1145
|
+
|
|
1146
|
+
result = map_implementation_status("AC-6", cis_data)
|
|
1147
|
+
|
|
1148
|
+
assert result == ControlImplementationStatus.Alternative
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
@staticmethod
|
|
1152
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1153
|
+
def test_map_implementation_status_single_unknown_status(mock_logger):
|
|
1154
|
+
"""Test mapping implementation status when status is unknown"""
|
|
1155
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1156
|
+
|
|
1157
|
+
# Test data with unknown status
|
|
1158
|
+
cis_data = {"record1": {"regscale_control_id": "AC-7", "implementation_status": "Unknown Status"}}
|
|
1159
|
+
|
|
1160
|
+
result = map_implementation_status("AC-7", cis_data)
|
|
1161
|
+
|
|
1162
|
+
assert result == ControlImplementationStatus.NotImplemented
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
@staticmethod
|
|
1166
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1167
|
+
def test_map_implementation_status_no_records_found(mock_logger):
|
|
1168
|
+
"""Test mapping implementation status when no records are found"""
|
|
1169
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1170
|
+
|
|
1171
|
+
# Test data with no matching records
|
|
1172
|
+
cis_data = {"record1": {"regscale_control_id": "AC-8", "implementation_status": "Implemented"}}
|
|
1173
|
+
|
|
1174
|
+
result = map_implementation_status("AC-9", cis_data)
|
|
1175
|
+
|
|
1176
|
+
assert result == ControlImplementationStatus.NotImplemented
|
|
1177
|
+
|
|
1178
|
+
|
|
1179
|
+
@staticmethod
|
|
1180
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1181
|
+
def test_map_implementation_status_all_implemented(mock_logger):
|
|
1182
|
+
"""Test mapping implementation status when all records are implemented"""
|
|
1183
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1184
|
+
|
|
1185
|
+
# Test data with multiple implemented records
|
|
1186
|
+
cis_data = {
|
|
1187
|
+
"record1": {"regscale_control_id": "AC-10", "implementation_status": "Implemented"},
|
|
1188
|
+
"record2": {"regscale_control_id": "AC-10", "implementation_status": "Implemented"},
|
|
1189
|
+
"record3": {"regscale_control_id": "AC-10", "implementation_status": "Implemented"},
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
result = map_implementation_status("AC-10", cis_data)
|
|
1193
|
+
|
|
1194
|
+
assert result == ControlImplementationStatus.Implemented
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
@staticmethod
|
|
1198
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1199
|
+
def test_map_implementation_status_mixed_implemented_and_partial(mock_logger):
|
|
1200
|
+
"""Test mapping implementation status with mixed implemented and partially implemented"""
|
|
1201
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1202
|
+
|
|
1203
|
+
# Test data with mixed statuses
|
|
1204
|
+
cis_data = {
|
|
1205
|
+
"record1": {"regscale_control_id": "AC-11", "implementation_status": "Implemented"},
|
|
1206
|
+
"record2": {"regscale_control_id": "AC-11", "implementation_status": "Partially Implemented"},
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
result = map_implementation_status("AC-11", cis_data)
|
|
1210
|
+
|
|
1211
|
+
assert result == ControlImplementationStatus.PartiallyImplemented
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
@staticmethod
|
|
1215
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1216
|
+
def test_map_implementation_status_mixed_with_na(mock_logger):
|
|
1217
|
+
"""Test mapping implementation status with mixed statuses including N/A"""
|
|
1218
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1219
|
+
|
|
1220
|
+
# Test data with mixed statuses including N/A
|
|
1221
|
+
cis_data = {
|
|
1222
|
+
"record1": {"regscale_control_id": "AC-12", "implementation_status": "Implemented"},
|
|
1223
|
+
"record2": {"regscale_control_id": "AC-12", "implementation_status": "N/A"},
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
result = map_implementation_status("AC-12", cis_data)
|
|
1227
|
+
|
|
1228
|
+
assert result == ControlImplementationStatus.PartiallyImplemented
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
@staticmethod
|
|
1232
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1233
|
+
def test_map_implementation_status_mixed_with_alternative_implementation(mock_logger):
|
|
1234
|
+
"""Test mapping implementation status with mixed statuses including alternative implementation"""
|
|
1235
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1236
|
+
|
|
1237
|
+
# Test data with mixed statuses including alternative implementation
|
|
1238
|
+
cis_data = {
|
|
1239
|
+
"record1": {"regscale_control_id": "AC-13", "implementation_status": "Implemented"},
|
|
1240
|
+
"record2": {"regscale_control_id": "AC-13", "implementation_status": "Alternative Implementation"},
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
result = map_implementation_status("AC-13", cis_data)
|
|
1244
|
+
|
|
1245
|
+
assert result == ControlImplementationStatus.PartiallyImplemented
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
@staticmethod
|
|
1249
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1250
|
+
def test_map_implementation_status_mixed_with_planned(mock_logger):
|
|
1251
|
+
"""Test mapping implementation status with mixed statuses including planned"""
|
|
1252
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1253
|
+
|
|
1254
|
+
# Test data with mixed statuses including planned
|
|
1255
|
+
cis_data = {
|
|
1256
|
+
"record1": {"regscale_control_id": "AC-14", "implementation_status": "Implemented"},
|
|
1257
|
+
"record2": {"regscale_control_id": "AC-14", "implementation_status": "Planned"},
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
result = map_implementation_status("AC-14", cis_data)
|
|
1261
|
+
|
|
1262
|
+
assert result == ControlImplementationStatus.PartiallyImplemented
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
@staticmethod
|
|
1266
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1267
|
+
def test_map_implementation_status_only_planned(mock_logger):
|
|
1268
|
+
"""Test mapping implementation status when only planned statuses exist"""
|
|
1269
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1270
|
+
|
|
1271
|
+
# Test data with only planned statuses
|
|
1272
|
+
cis_data = {
|
|
1273
|
+
"record1": {"regscale_control_id": "AC-15", "implementation_status": "Planned"},
|
|
1274
|
+
"record2": {"regscale_control_id": "AC-15", "implementation_status": "Planned"},
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
result = map_implementation_status("AC-15", cis_data)
|
|
1278
|
+
|
|
1279
|
+
assert result == ControlImplementationStatus.Planned
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
@staticmethod
|
|
1283
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1284
|
+
def test_map_implementation_status_case_insensitive_matching(mock_logger):
|
|
1285
|
+
"""Test mapping implementation status with case insensitive control ID matching"""
|
|
1286
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1287
|
+
|
|
1288
|
+
# Test data with different case control IDs
|
|
1289
|
+
cis_data = {
|
|
1290
|
+
"record1": {"regscale_control_id": "ac-16", "implementation_status": "Implemented"},
|
|
1291
|
+
"record2": {"regscale_control_id": "AC-16", "implementation_status": "Implemented"},
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
result = map_implementation_status("ac-16", cis_data)
|
|
1295
|
+
|
|
1296
|
+
assert result == ControlImplementationStatus.Implemented
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
@staticmethod
|
|
1300
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1301
|
+
def test_map_implementation_status_missing_implementation_status(mock_logger):
|
|
1302
|
+
"""Test mapping implementation status when implementation_status is missing"""
|
|
1303
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1304
|
+
|
|
1305
|
+
# Test data with missing implementation_status
|
|
1306
|
+
cis_data = {
|
|
1307
|
+
"record1": {
|
|
1308
|
+
"regscale_control_id": "AC-17"
|
|
1309
|
+
# Missing implementation_status
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
result = map_implementation_status("AC-17", cis_data)
|
|
1314
|
+
|
|
1315
|
+
assert result == ControlImplementationStatus.NotImplemented
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
@staticmethod
|
|
1319
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1320
|
+
def test_map_implementation_status_missing_regscale_control_id(mock_logger):
|
|
1321
|
+
"""Test mapping implementation status when regscale_control_id is missing"""
|
|
1322
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1323
|
+
|
|
1324
|
+
# Test data with missing regscale_control_id
|
|
1325
|
+
cis_data = {
|
|
1326
|
+
"record1": {
|
|
1327
|
+
"implementation_status": "Implemented"
|
|
1328
|
+
# Missing regscale_control_id
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
result = map_implementation_status("AC-18", cis_data)
|
|
1333
|
+
|
|
1334
|
+
assert result == ControlImplementationStatus.NotImplemented
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
@staticmethod
|
|
1338
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1339
|
+
def test_map_implementation_status_empty_cis_data(mock_logger):
|
|
1340
|
+
"""Test mapping implementation status with empty CIS data"""
|
|
1341
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1342
|
+
|
|
1343
|
+
# Test data with empty CIS data
|
|
1344
|
+
cis_data = {}
|
|
1345
|
+
|
|
1346
|
+
result = map_implementation_status("AC-19", cis_data)
|
|
1347
|
+
|
|
1348
|
+
assert result == ControlImplementationStatus.NotImplemented
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
@staticmethod
|
|
1352
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
1353
|
+
def test_map_implementation_status_complex_mixed_scenario(mock_logger):
|
|
1354
|
+
"""Test mapping implementation status with complex mixed scenario"""
|
|
1355
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_implementation_status
|
|
1356
|
+
|
|
1357
|
+
# Test data with complex mixed scenario
|
|
1358
|
+
cis_data = {
|
|
1359
|
+
"record1": {"regscale_control_id": "AC-20", "implementation_status": "Implemented"},
|
|
1360
|
+
"record2": {"regscale_control_id": "AC-20", "implementation_status": "Partially Implemented"},
|
|
1361
|
+
"record3": {"regscale_control_id": "AC-20", "implementation_status": "Planned"},
|
|
1362
|
+
"record4": {"regscale_control_id": "AC-20", "implementation_status": "N/A"},
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
result = map_implementation_status("AC-20", cis_data)
|
|
1366
|
+
|
|
1367
|
+
assert result == ControlImplementationStatus.PartiallyImplemented
|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
@staticmethod
|
|
1371
|
+
def test_map_origination_single_service_provider_corporate():
|
|
1372
|
+
"""Test mapping origination for single Service Provider Corporate record"""
|
|
1373
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1374
|
+
|
|
1375
|
+
# Test data with single Service Provider Corporate record
|
|
1376
|
+
cis_data = {"record1": {"regscale_control_id": "AC-1", "control_origination": "Service Provider Corporate"}}
|
|
1377
|
+
|
|
1378
|
+
result = map_origination("AC-1", cis_data)
|
|
1379
|
+
|
|
1380
|
+
# Verify the result structure
|
|
1381
|
+
assert isinstance(result, dict)
|
|
1382
|
+
assert "bServiceProviderCorporate" in result
|
|
1383
|
+
assert "bServiceProviderSystemSpecific" in result
|
|
1384
|
+
assert "bServiceProviderHybrid" in result
|
|
1385
|
+
assert "bProvidedByCustomer" in result
|
|
1386
|
+
assert "bConfiguredByCustomer" in result
|
|
1387
|
+
assert "bShared" in result
|
|
1388
|
+
assert "bInherited" in result
|
|
1389
|
+
assert "record_text" in result
|
|
1390
|
+
|
|
1391
|
+
# Verify the correct flag is set
|
|
1392
|
+
assert result["bServiceProviderCorporate"] is True
|
|
1393
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1394
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1395
|
+
assert result["bProvidedByCustomer"] is False
|
|
1396
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1397
|
+
assert result["bShared"] is False
|
|
1398
|
+
assert result["bInherited"] is False
|
|
1399
|
+
assert result["record_text"] == "Service Provider Corporate"
|
|
1400
|
+
|
|
1401
|
+
|
|
1402
|
+
@staticmethod
|
|
1403
|
+
def test_map_origination_single_service_provider_system_specific():
|
|
1404
|
+
"""Test mapping origination for single Service Provider System Specific record"""
|
|
1405
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1406
|
+
|
|
1407
|
+
# Test data with single Service Provider System Specific record
|
|
1408
|
+
cis_data = {"record1": {"regscale_control_id": "AC-2", "control_origination": "Service Provider System Specific"}}
|
|
1409
|
+
|
|
1410
|
+
result = map_origination("AC-2", cis_data)
|
|
1411
|
+
|
|
1412
|
+
# Verify the correct flag is set
|
|
1413
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1414
|
+
assert result["bServiceProviderSystemSpecific"] is True
|
|
1415
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1416
|
+
assert result["bProvidedByCustomer"] is False
|
|
1417
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1418
|
+
assert result["bShared"] is False
|
|
1419
|
+
assert result["bInherited"] is False
|
|
1420
|
+
assert result["record_text"] == "Service Provider System Specific"
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
@staticmethod
|
|
1424
|
+
def test_map_origination_single_service_provider_hybrid():
|
|
1425
|
+
"""Test mapping origination for single Service Provider Hybrid record"""
|
|
1426
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1427
|
+
|
|
1428
|
+
# Test data with single Service Provider Hybrid record
|
|
1429
|
+
cis_data = {
|
|
1430
|
+
"record1": {
|
|
1431
|
+
"regscale_control_id": "AC-3",
|
|
1432
|
+
"control_origination": "Service Provider Hybrid (Corporate and System Specific)",
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
result = map_origination("AC-3", cis_data)
|
|
1437
|
+
|
|
1438
|
+
# Verify the correct flag is set
|
|
1439
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1440
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1441
|
+
assert result["bServiceProviderHybrid"] is True
|
|
1442
|
+
assert result["bProvidedByCustomer"] is False
|
|
1443
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1444
|
+
assert result["bShared"] is False
|
|
1445
|
+
assert result["bInherited"] is False
|
|
1446
|
+
assert result["record_text"] == "Service Provider Hybrid (Corporate and System Specific)"
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
@staticmethod
|
|
1450
|
+
def test_map_origination_single_provided_by_customer():
|
|
1451
|
+
"""Test mapping origination for single Provided by Customer record"""
|
|
1452
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1453
|
+
|
|
1454
|
+
# Test data with single Provided by Customer record
|
|
1455
|
+
cis_data = {
|
|
1456
|
+
"record1": {
|
|
1457
|
+
"regscale_control_id": "AC-4",
|
|
1458
|
+
"control_origination": "Provided by Customer (Customer System Specific)",
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
result = map_origination("AC-4", cis_data)
|
|
1463
|
+
|
|
1464
|
+
# Verify the correct flag is set
|
|
1465
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1466
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1467
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1468
|
+
assert result["bProvidedByCustomer"] is True
|
|
1469
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1470
|
+
assert result["bShared"] is False
|
|
1471
|
+
assert result["bInherited"] is False
|
|
1472
|
+
assert result["record_text"] == "Provided by Customer (Customer System Specific)"
|
|
1473
|
+
|
|
1474
|
+
|
|
1475
|
+
@staticmethod
|
|
1476
|
+
def test_map_origination_single_configured_by_customer():
|
|
1477
|
+
"""Test mapping origination for single Configured by Customer record"""
|
|
1478
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1479
|
+
|
|
1480
|
+
# Test data with single Configured by Customer record
|
|
1481
|
+
cis_data = {
|
|
1482
|
+
"record1": {
|
|
1483
|
+
"regscale_control_id": "AC-5",
|
|
1484
|
+
"control_origination": "Configured by Customer (Customer System Specific)",
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
result = map_origination("AC-5", cis_data)
|
|
1489
|
+
|
|
1490
|
+
# Verify the correct flag is set
|
|
1491
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1492
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1493
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1494
|
+
assert result["bProvidedByCustomer"] is False
|
|
1495
|
+
assert result["bConfiguredByCustomer"] is True
|
|
1496
|
+
assert result["bShared"] is False
|
|
1497
|
+
assert result["bInherited"] is False
|
|
1498
|
+
assert result["record_text"] == "Configured by Customer (Customer System Specific)"
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
@staticmethod
|
|
1502
|
+
def test_map_origination_single_shared():
|
|
1503
|
+
"""Test mapping origination for single Shared record"""
|
|
1504
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1505
|
+
|
|
1506
|
+
# Test data with single Shared record
|
|
1507
|
+
cis_data = {
|
|
1508
|
+
"record1": {
|
|
1509
|
+
"regscale_control_id": "AC-6",
|
|
1510
|
+
"control_origination": "Shared (Service Provider and Customer Responsibility)",
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
result = map_origination("AC-6", cis_data)
|
|
1515
|
+
|
|
1516
|
+
# Verify the correct flag is set
|
|
1517
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1518
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1519
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1520
|
+
assert result["bProvidedByCustomer"] is False
|
|
1521
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1522
|
+
assert result["bShared"] is True
|
|
1523
|
+
assert result["bInherited"] is False
|
|
1524
|
+
assert result["record_text"] == "Shared (Service Provider and Customer Responsibility)"
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
@staticmethod
|
|
1528
|
+
def test_map_origination_single_inherited():
|
|
1529
|
+
"""Test mapping origination for single Inherited record"""
|
|
1530
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1531
|
+
|
|
1532
|
+
# Test data with single Inherited record
|
|
1533
|
+
cis_data = {
|
|
1534
|
+
"record1": {
|
|
1535
|
+
"regscale_control_id": "AC-7",
|
|
1536
|
+
"control_origination": "Inherited from pre-existing FedRAMP Authorization",
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
result = map_origination("AC-7", cis_data)
|
|
1541
|
+
|
|
1542
|
+
# Verify the correct flag is set
|
|
1543
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1544
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1545
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1546
|
+
assert result["bProvidedByCustomer"] is False
|
|
1547
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1548
|
+
assert result["bShared"] is False
|
|
1549
|
+
assert result["bInherited"] is True
|
|
1550
|
+
assert result["record_text"] == "Inherited from pre-existing FedRAMP Authorization"
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
@staticmethod
|
|
1554
|
+
def test_map_origination_multiple_records_same_control():
|
|
1555
|
+
"""Test mapping origination for multiple records with the same control ID"""
|
|
1556
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1557
|
+
|
|
1558
|
+
# Test data with multiple records for the same control
|
|
1559
|
+
cis_data = {
|
|
1560
|
+
"record1": {"regscale_control_id": "AC-8", "control_origination": "Service Provider Corporate"},
|
|
1561
|
+
"record2": {"regscale_control_id": "AC-8", "control_origination": "Service Provider System Specific"},
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
result = map_origination("AC-8", cis_data)
|
|
1565
|
+
|
|
1566
|
+
# Verify multiple flags are set
|
|
1567
|
+
assert result["bServiceProviderCorporate"] is True
|
|
1568
|
+
assert result["bServiceProviderSystemSpecific"] is True
|
|
1569
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1570
|
+
assert result["bProvidedByCustomer"] is False
|
|
1571
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1572
|
+
assert result["bShared"] is False
|
|
1573
|
+
assert result["bInherited"] is False
|
|
1574
|
+
# Verify record_text contains both originations
|
|
1575
|
+
assert "Service Provider Corporate" in result["record_text"]
|
|
1576
|
+
assert "Service Provider System Specific" in result["record_text"]
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
@staticmethod
|
|
1580
|
+
def test_map_origination_no_matching_records():
|
|
1581
|
+
"""Test mapping origination when no matching records are found"""
|
|
1582
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1583
|
+
|
|
1584
|
+
# Test data with no matching records
|
|
1585
|
+
cis_data = {"record1": {"regscale_control_id": "AC-9", "control_origination": "Service Provider Corporate"}}
|
|
1586
|
+
|
|
1587
|
+
result = map_origination("AC-10", cis_data)
|
|
1588
|
+
|
|
1589
|
+
# Verify all flags are False
|
|
1590
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1591
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1592
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1593
|
+
assert result["bProvidedByCustomer"] is False
|
|
1594
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1595
|
+
assert result["bShared"] is False
|
|
1596
|
+
assert result["bInherited"] is False
|
|
1597
|
+
assert result["record_text"] == ""
|
|
1598
|
+
|
|
1599
|
+
|
|
1600
|
+
@staticmethod
|
|
1601
|
+
def test_map_origination_case_insensitive_matching():
|
|
1602
|
+
"""Test mapping origination with case insensitive control ID matching"""
|
|
1603
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1604
|
+
|
|
1605
|
+
# Test data with different case control IDs
|
|
1606
|
+
cis_data = {
|
|
1607
|
+
"record1": {"regscale_control_id": "ac-11", "control_origination": "Service Provider Corporate"},
|
|
1608
|
+
"record2": {"regscale_control_id": "AC-11", "control_origination": "Service Provider System Specific"},
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
result = map_origination("ac-11", cis_data)
|
|
1612
|
+
|
|
1613
|
+
# Verify both records are matched (case insensitive)
|
|
1614
|
+
assert result["bServiceProviderCorporate"] is True
|
|
1615
|
+
assert result["bServiceProviderSystemSpecific"] is True
|
|
1616
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1617
|
+
assert result["bProvidedByCustomer"] is False
|
|
1618
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1619
|
+
assert result["bShared"] is False
|
|
1620
|
+
assert result["bInherited"] is False
|
|
1621
|
+
assert "Service Provider Corporate" in result["record_text"]
|
|
1622
|
+
assert "Service Provider System Specific" in result["record_text"]
|
|
1623
|
+
|
|
1624
|
+
|
|
1625
|
+
@staticmethod
|
|
1626
|
+
def test_map_origination_missing_control_origination():
|
|
1627
|
+
"""Test mapping origination when control_origination field is missing"""
|
|
1628
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1629
|
+
|
|
1630
|
+
# Test data with missing control_origination
|
|
1631
|
+
cis_data = {
|
|
1632
|
+
"record1": {
|
|
1633
|
+
"regscale_control_id": "AC-12"
|
|
1634
|
+
# Missing control_origination field
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
result = map_origination("AC-12", cis_data)
|
|
1639
|
+
|
|
1640
|
+
# Verify all flags are False
|
|
1641
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1642
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1643
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1644
|
+
assert result["bProvidedByCustomer"] is False
|
|
1645
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1646
|
+
assert result["bShared"] is False
|
|
1647
|
+
assert result["bInherited"] is False
|
|
1648
|
+
assert result["record_text"] == ""
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
@staticmethod
|
|
1652
|
+
def test_map_origination_missing_regscale_control_id():
|
|
1653
|
+
"""Test mapping origination when regscale_control_id field is missing"""
|
|
1654
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1655
|
+
|
|
1656
|
+
# Test data with missing regscale_control_id
|
|
1657
|
+
cis_data = {
|
|
1658
|
+
"record1": {
|
|
1659
|
+
"control_origination": "Service Provider Corporate"
|
|
1660
|
+
# Missing regscale_control_id field
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
result = map_origination("AC-13", cis_data)
|
|
1665
|
+
|
|
1666
|
+
# Verify all flags are False
|
|
1667
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1668
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1669
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1670
|
+
assert result["bProvidedByCustomer"] is False
|
|
1671
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1672
|
+
assert result["bShared"] is False
|
|
1673
|
+
assert result["bInherited"] is False
|
|
1674
|
+
assert result["record_text"] == ""
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
@staticmethod
|
|
1678
|
+
def test_map_origination_empty_cis_data():
|
|
1679
|
+
"""Test mapping origination with empty CIS data"""
|
|
1680
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1681
|
+
|
|
1682
|
+
# Test data with empty CIS data
|
|
1683
|
+
cis_data = {}
|
|
1684
|
+
|
|
1685
|
+
result = map_origination("AC-14", cis_data)
|
|
1686
|
+
|
|
1687
|
+
# Verify all flags are False
|
|
1688
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1689
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1690
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1691
|
+
assert result["bProvidedByCustomer"] is False
|
|
1692
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1693
|
+
assert result["bShared"] is False
|
|
1694
|
+
assert result["bInherited"] is False
|
|
1695
|
+
assert result["record_text"] == ""
|
|
1696
|
+
|
|
1697
|
+
|
|
1698
|
+
@staticmethod
|
|
1699
|
+
def test_map_origination_unknown_origination():
|
|
1700
|
+
"""Test mapping origination with unknown origination string"""
|
|
1701
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1702
|
+
|
|
1703
|
+
# Test data with unknown origination
|
|
1704
|
+
cis_data = {"record1": {"regscale_control_id": "AC-15", "control_origination": "Unknown Origination Type"}}
|
|
1705
|
+
|
|
1706
|
+
result = map_origination("AC-15", cis_data)
|
|
1707
|
+
|
|
1708
|
+
# Verify all flags are False (unknown origination doesn't match any mapping)
|
|
1709
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1710
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1711
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1712
|
+
assert result["bProvidedByCustomer"] is False
|
|
1713
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1714
|
+
assert result["bShared"] is False
|
|
1715
|
+
assert result["bInherited"] is False
|
|
1716
|
+
assert result["record_text"] == "Unknown Origination Type"
|
|
1717
|
+
|
|
1718
|
+
|
|
1719
|
+
@staticmethod
|
|
1720
|
+
def test_map_origination_complex_mixed_scenario():
|
|
1721
|
+
"""Test mapping origination with complex mixed scenario"""
|
|
1722
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1723
|
+
|
|
1724
|
+
# Test data with complex mixed scenario
|
|
1725
|
+
cis_data = {
|
|
1726
|
+
"record1": {"regscale_control_id": "AC-16", "control_origination": "Service Provider Corporate"},
|
|
1727
|
+
"record2": {
|
|
1728
|
+
"regscale_control_id": "AC-16",
|
|
1729
|
+
"control_origination": "Shared (Service Provider and Customer Responsibility)",
|
|
1730
|
+
},
|
|
1731
|
+
"record3": {
|
|
1732
|
+
"regscale_control_id": "AC-16",
|
|
1733
|
+
"control_origination": "Inherited from pre-existing FedRAMP Authorization",
|
|
1734
|
+
},
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
result = map_origination("AC-16", cis_data)
|
|
1738
|
+
|
|
1739
|
+
# Verify multiple flags are set
|
|
1740
|
+
assert result["bServiceProviderCorporate"] is True
|
|
1741
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1742
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1743
|
+
assert result["bProvidedByCustomer"] is False
|
|
1744
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1745
|
+
assert result["bShared"] is True
|
|
1746
|
+
assert result["bInherited"] is True
|
|
1747
|
+
# Verify record_text contains all originations
|
|
1748
|
+
assert "Service Provider Corporate" in result["record_text"]
|
|
1749
|
+
assert "Shared (Service Provider and Customer Responsibility)" in result["record_text"]
|
|
1750
|
+
assert "Inherited from pre-existing FedRAMP Authorization" in result["record_text"]
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
@staticmethod
|
|
1754
|
+
def test_map_origination_partial_string_matching():
|
|
1755
|
+
"""Test mapping origination with partial string matching"""
|
|
1756
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1757
|
+
|
|
1758
|
+
# Test data with partial string matches
|
|
1759
|
+
cis_data = {
|
|
1760
|
+
"record1": {
|
|
1761
|
+
"regscale_control_id": "AC-17",
|
|
1762
|
+
"control_origination": "Some text Service Provider Corporate more text",
|
|
1763
|
+
},
|
|
1764
|
+
"record2": {"regscale_control_id": "AC-17", "control_origination": "Prefix Shared suffix"},
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
result = map_origination("AC-17", cis_data)
|
|
1768
|
+
|
|
1769
|
+
# Verify flags are set based on partial matches
|
|
1770
|
+
assert result["bServiceProviderCorporate"] is True
|
|
1771
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1772
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1773
|
+
assert result["bProvidedByCustomer"] is False
|
|
1774
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1775
|
+
assert result["bShared"] is False
|
|
1776
|
+
assert result["bInherited"] is False
|
|
1777
|
+
# Verify record_text contains the full origination strings
|
|
1778
|
+
assert "Some text Service Provider Corporate more text" in result["record_text"]
|
|
1779
|
+
assert "Prefix Shared suffix" in result["record_text"]
|
|
1780
|
+
|
|
1781
|
+
|
|
1782
|
+
@staticmethod
|
|
1783
|
+
def test_map_origination_duplicate_origination_text():
|
|
1784
|
+
"""Test mapping origination with duplicate origination text"""
|
|
1785
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1786
|
+
|
|
1787
|
+
# Test data with duplicate origination text
|
|
1788
|
+
cis_data = {
|
|
1789
|
+
"record1": {"regscale_control_id": "AC-18", "control_origination": "Service Provider Corporate"},
|
|
1790
|
+
"record2": {"regscale_control_id": "AC-18", "control_origination": "Service Provider Corporate"}, # Duplicate
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
result = map_origination("AC-18", cis_data)
|
|
1794
|
+
|
|
1795
|
+
# Verify flag is set
|
|
1796
|
+
assert result["bServiceProviderCorporate"] is True
|
|
1797
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1798
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1799
|
+
assert result["bProvidedByCustomer"] is False
|
|
1800
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1801
|
+
assert result["bShared"] is False
|
|
1802
|
+
assert result["bInherited"] is False
|
|
1803
|
+
# Verify record_text contains the origination only once (no duplicates)
|
|
1804
|
+
assert result["record_text"] == "Service Provider Corporate"
|
|
1805
|
+
|
|
1806
|
+
|
|
1807
|
+
@staticmethod
|
|
1808
|
+
def test_map_origination_empty_control_origination():
|
|
1809
|
+
"""Test mapping origination with empty control_origination string"""
|
|
1810
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1811
|
+
|
|
1812
|
+
# Test data with empty control_origination
|
|
1813
|
+
cis_data = {"record1": {"regscale_control_id": "AC-19", "control_origination": ""}}
|
|
1814
|
+
|
|
1815
|
+
result = map_origination("AC-19", cis_data)
|
|
1816
|
+
|
|
1817
|
+
# Verify all flags are False
|
|
1818
|
+
assert result["bServiceProviderCorporate"] is False
|
|
1819
|
+
assert result["bServiceProviderSystemSpecific"] is False
|
|
1820
|
+
assert result["bServiceProviderHybrid"] is False
|
|
1821
|
+
assert result["bProvidedByCustomer"] is False
|
|
1822
|
+
assert result["bConfiguredByCustomer"] is False
|
|
1823
|
+
assert result["bShared"] is False
|
|
1824
|
+
assert result["bInherited"] is False
|
|
1825
|
+
assert result["record_text"] == ""
|
|
1826
|
+
|
|
1827
|
+
|
|
1828
|
+
@staticmethod
|
|
1829
|
+
def test_map_origination_result_structure():
|
|
1830
|
+
"""Test that the result structure is consistent"""
|
|
1831
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import map_origination
|
|
1832
|
+
|
|
1833
|
+
# Test data
|
|
1834
|
+
cis_data = {"record1": {"regscale_control_id": "AC-20", "control_origination": "Service Provider Corporate"}}
|
|
1835
|
+
|
|
1836
|
+
result = map_origination("AC-20", cis_data)
|
|
1837
|
+
|
|
1838
|
+
# Verify all expected keys are present
|
|
1839
|
+
expected_keys = [
|
|
1840
|
+
"bServiceProviderCorporate",
|
|
1841
|
+
"bServiceProviderSystemSpecific",
|
|
1842
|
+
"bServiceProviderHybrid",
|
|
1843
|
+
"bProvidedByCustomer",
|
|
1844
|
+
"bConfiguredByCustomer",
|
|
1845
|
+
"bShared",
|
|
1846
|
+
"bInherited",
|
|
1847
|
+
"record_text",
|
|
1848
|
+
]
|
|
1849
|
+
|
|
1850
|
+
for key in expected_keys:
|
|
1851
|
+
assert key in result
|
|
1852
|
+
assert isinstance(result[key], (bool, str))
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
@staticmethod
|
|
1856
|
+
def test_transform_control():
|
|
1857
|
+
"""Test the transform_control function with various control ID patterns."""
|
|
1858
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import transform_control
|
|
1859
|
+
|
|
1860
|
+
# Test cases for control IDs with numeric enhancements
|
|
1861
|
+
assert transform_control("AC-1 (1)") == "ac-1.1"
|
|
1862
|
+
assert transform_control("SI-2 (5)") == "si-2.5"
|
|
1863
|
+
assert transform_control("AU-3 (10)") == "au-3.10"
|
|
1864
|
+
assert transform_control("CM-4 (2)") == "cm-4.2"
|
|
1865
|
+
|
|
1866
|
+
# Test cases for control IDs with letter enhancements (should be stripped)
|
|
1867
|
+
assert transform_control("AC-1 (a)") == "ac-1"
|
|
1868
|
+
assert transform_control("SI-2 (b)") == "si-2"
|
|
1869
|
+
assert transform_control("AU-3 (z)") == "au-3"
|
|
1870
|
+
assert transform_control("CM-4 (x)") == "cm-4"
|
|
1871
|
+
|
|
1872
|
+
# Test cases for control IDs with uppercase letter enhancements
|
|
1873
|
+
assert transform_control("AC-1 (A)") == "ac-1 (a)"
|
|
1874
|
+
assert transform_control("SI-2 (B)") == "si-2 (b)"
|
|
1875
|
+
assert transform_control("AU-3 (Z)") == "au-3 (z)"
|
|
1876
|
+
|
|
1877
|
+
# Test cases for basic control IDs without enhancements
|
|
1878
|
+
assert transform_control("AC-1") == "ac-1"
|
|
1879
|
+
assert transform_control("SI-2") == "si-2"
|
|
1880
|
+
assert transform_control("AU-3") == "au-3"
|
|
1881
|
+
assert transform_control("CM-4") == "cm-4"
|
|
1882
|
+
|
|
1883
|
+
# Test cases for different control families
|
|
1884
|
+
assert transform_control("IA-1 (1)") == "ia-1.1"
|
|
1885
|
+
assert transform_control("MP-1 (2)") == "mp-1.2"
|
|
1886
|
+
assert transform_control("PE-1 (3)") == "pe-1.3"
|
|
1887
|
+
assert transform_control("PL-1 (4)") == "pl-1.4"
|
|
1888
|
+
assert transform_control("PS-1 (5)") == "ps-1.5"
|
|
1889
|
+
assert transform_control("RA-1 (6)") == "ra-1.6"
|
|
1890
|
+
assert transform_control("SA-1 (7)") == "sa-1.7"
|
|
1891
|
+
assert transform_control("SC-1 (8)") == "sc-1.8"
|
|
1892
|
+
assert transform_control("SR-1 (9)") == "sr-1.9"
|
|
1893
|
+
|
|
1894
|
+
# Test cases for edge cases and malformed patterns
|
|
1895
|
+
assert transform_control("AC1") == "ac1" # No dash
|
|
1896
|
+
assert transform_control("AC-1a") == "ac-1a" # Letter without parentheses
|
|
1897
|
+
assert transform_control("") == "" # Empty string
|
|
1898
|
+
|
|
1899
|
+
# Test cases for mixed case input
|
|
1900
|
+
assert transform_control("Ac-1 (1)") == "ac-1.1"
|
|
1901
|
+
assert transform_control("aC-1 (1)") == "ac-1.1"
|
|
1902
|
+
assert transform_control("AC-1 (A)") == "ac-1 (a)"
|
|
1903
|
+
|
|
1904
|
+
# Test cases for large numbers
|
|
1905
|
+
assert transform_control("AC-1 (999)") == "ac-1.999"
|
|
1906
|
+
assert transform_control("SI-2 (1000)") == "si-2.1000"
|
|
1907
|
+
|
|
1908
|
+
# Test cases for single digit controls
|
|
1909
|
+
assert transform_control("A-1 (1)") == "a-1.1"
|
|
1910
|
+
assert transform_control("B-2 (2)") == "b-2.2"
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
@staticmethod
|
|
1914
|
+
def test_get_responsibility_single_service_provider_corporate():
|
|
1915
|
+
"""Test getting responsibility for single Service Provider Corporate"""
|
|
1916
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
1917
|
+
|
|
1918
|
+
# Test data with single Service Provider Corporate responsibility
|
|
1919
|
+
origination_bool = {
|
|
1920
|
+
"bServiceProviderCorporate": True,
|
|
1921
|
+
"bServiceProviderSystemSpecific": False,
|
|
1922
|
+
"bServiceProviderHybrid": False,
|
|
1923
|
+
"bProvidedByCustomer": False,
|
|
1924
|
+
"bConfiguredByCustomer": False,
|
|
1925
|
+
"bInherited": False,
|
|
1926
|
+
"bShared": False,
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
result = get_responsibility(origination_bool)
|
|
1930
|
+
|
|
1931
|
+
assert result == "Service Provider Corporate"
|
|
1932
|
+
|
|
1933
|
+
|
|
1934
|
+
@staticmethod
|
|
1935
|
+
def test_get_responsibility_single_service_provider_system_specific():
|
|
1936
|
+
"""Test getting responsibility for single Service Provider System Specific"""
|
|
1937
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
1938
|
+
|
|
1939
|
+
# Test data with single Service Provider System Specific responsibility
|
|
1940
|
+
origination_bool = {
|
|
1941
|
+
"bServiceProviderCorporate": False,
|
|
1942
|
+
"bServiceProviderSystemSpecific": True,
|
|
1943
|
+
"bServiceProviderHybrid": False,
|
|
1944
|
+
"bProvidedByCustomer": False,
|
|
1945
|
+
"bConfiguredByCustomer": False,
|
|
1946
|
+
"bInherited": False,
|
|
1947
|
+
"bShared": False,
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
result = get_responsibility(origination_bool)
|
|
1951
|
+
|
|
1952
|
+
assert result == "Service Provider System Specific"
|
|
1953
|
+
|
|
1954
|
+
|
|
1955
|
+
@staticmethod
|
|
1956
|
+
def test_get_responsibility_single_service_provider_hybrid():
|
|
1957
|
+
"""Test getting responsibility for single Service Provider Hybrid"""
|
|
1958
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
1959
|
+
|
|
1960
|
+
# Test data with single Service Provider Hybrid responsibility
|
|
1961
|
+
origination_bool = {
|
|
1962
|
+
"bServiceProviderCorporate": False,
|
|
1963
|
+
"bServiceProviderSystemSpecific": False,
|
|
1964
|
+
"bServiceProviderHybrid": True,
|
|
1965
|
+
"bProvidedByCustomer": False,
|
|
1966
|
+
"bConfiguredByCustomer": False,
|
|
1967
|
+
"bInherited": False,
|
|
1968
|
+
"bShared": False,
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
result = get_responsibility(origination_bool)
|
|
1972
|
+
|
|
1973
|
+
assert result == "Service Provider Hybrid (Corporate and System Specific)"
|
|
1974
|
+
|
|
1975
|
+
|
|
1976
|
+
@staticmethod
|
|
1977
|
+
def test_get_responsibility_single_provided_by_customer():
|
|
1978
|
+
"""Test getting responsibility for single Provided by Customer"""
|
|
1979
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
1980
|
+
|
|
1981
|
+
# Test data with single Provided by Customer responsibility
|
|
1982
|
+
origination_bool = {
|
|
1983
|
+
"bServiceProviderCorporate": False,
|
|
1984
|
+
"bServiceProviderSystemSpecific": False,
|
|
1985
|
+
"bServiceProviderHybrid": False,
|
|
1986
|
+
"bProvidedByCustomer": True,
|
|
1987
|
+
"bConfiguredByCustomer": False,
|
|
1988
|
+
"bInherited": False,
|
|
1989
|
+
"bShared": False,
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
result = get_responsibility(origination_bool)
|
|
1993
|
+
|
|
1994
|
+
assert result == "Provided by Customer (Customer System Specific)"
|
|
1995
|
+
|
|
1996
|
+
|
|
1997
|
+
@staticmethod
|
|
1998
|
+
def test_get_responsibility_single_configured_by_customer():
|
|
1999
|
+
"""Test getting responsibility for single Configured by Customer"""
|
|
2000
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2001
|
+
|
|
2002
|
+
# Test data with single Configured by Customer responsibility
|
|
2003
|
+
origination_bool = {
|
|
2004
|
+
"bServiceProviderCorporate": False,
|
|
2005
|
+
"bServiceProviderSystemSpecific": False,
|
|
2006
|
+
"bServiceProviderHybrid": False,
|
|
2007
|
+
"bProvidedByCustomer": False,
|
|
2008
|
+
"bConfiguredByCustomer": True,
|
|
2009
|
+
"bInherited": False,
|
|
2010
|
+
"bShared": False,
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
result = get_responsibility(origination_bool)
|
|
2014
|
+
|
|
2015
|
+
assert result == "Configured by Customer (Customer System Specific)"
|
|
2016
|
+
|
|
2017
|
+
|
|
2018
|
+
@staticmethod
|
|
2019
|
+
def test_get_responsibility_single_inherited():
|
|
2020
|
+
"""Test getting responsibility for single Inherited"""
|
|
2021
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2022
|
+
|
|
2023
|
+
# Test data with single Inherited responsibility
|
|
2024
|
+
origination_bool = {
|
|
2025
|
+
"bServiceProviderCorporate": False,
|
|
2026
|
+
"bServiceProviderSystemSpecific": False,
|
|
2027
|
+
"bServiceProviderHybrid": False,
|
|
2028
|
+
"bProvidedByCustomer": False,
|
|
2029
|
+
"bConfiguredByCustomer": False,
|
|
2030
|
+
"bInherited": True,
|
|
2031
|
+
"bShared": False,
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
result = get_responsibility(origination_bool)
|
|
2035
|
+
|
|
2036
|
+
assert result == "Inherited from pre-existing FedRAMP Authorization"
|
|
2037
|
+
|
|
2038
|
+
|
|
2039
|
+
@staticmethod
|
|
2040
|
+
def test_get_responsibility_single_shared():
|
|
2041
|
+
"""Test getting responsibility for single Shared"""
|
|
2042
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2043
|
+
|
|
2044
|
+
# Test data with single Shared responsibility
|
|
2045
|
+
origination_bool = {
|
|
2046
|
+
"bServiceProviderCorporate": False,
|
|
2047
|
+
"bServiceProviderSystemSpecific": False,
|
|
2048
|
+
"bServiceProviderHybrid": False,
|
|
2049
|
+
"bProvidedByCustomer": False,
|
|
2050
|
+
"bConfiguredByCustomer": False,
|
|
2051
|
+
"bInherited": False,
|
|
2052
|
+
"bShared": True,
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
result = get_responsibility(origination_bool)
|
|
2056
|
+
|
|
2057
|
+
assert result == "Shared (Service Provider and Customer Responsibility)"
|
|
2058
|
+
|
|
2059
|
+
|
|
2060
|
+
@staticmethod
|
|
2061
|
+
def test_get_responsibility_multiple_responsibilities():
|
|
2062
|
+
"""Test getting responsibility for multiple responsibilities"""
|
|
2063
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2064
|
+
|
|
2065
|
+
# Test data with multiple responsibilities
|
|
2066
|
+
origination_bool = {
|
|
2067
|
+
"bServiceProviderCorporate": True,
|
|
2068
|
+
"bServiceProviderSystemSpecific": True,
|
|
2069
|
+
"bServiceProviderHybrid": False,
|
|
2070
|
+
"bProvidedByCustomer": False,
|
|
2071
|
+
"bConfiguredByCustomer": False,
|
|
2072
|
+
"bInherited": False,
|
|
2073
|
+
"bShared": False,
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
result = get_responsibility(origination_bool)
|
|
2077
|
+
|
|
2078
|
+
# Should return comma-separated string
|
|
2079
|
+
expected = "Service Provider Corporate,Service Provider System Specific"
|
|
2080
|
+
assert result == expected
|
|
2081
|
+
|
|
2082
|
+
|
|
2083
|
+
@staticmethod
|
|
2084
|
+
def test_get_responsibility_all_responsibilities():
|
|
2085
|
+
"""Test getting responsibility for all responsibilities"""
|
|
2086
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2087
|
+
|
|
2088
|
+
# Test data with all responsibilities
|
|
2089
|
+
origination_bool = {
|
|
2090
|
+
"bServiceProviderCorporate": True,
|
|
2091
|
+
"bServiceProviderSystemSpecific": True,
|
|
2092
|
+
"bServiceProviderHybrid": True,
|
|
2093
|
+
"bProvidedByCustomer": True,
|
|
2094
|
+
"bConfiguredByCustomer": True,
|
|
2095
|
+
"bInherited": True,
|
|
2096
|
+
"bShared": True,
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
result = get_responsibility(origination_bool)
|
|
2100
|
+
|
|
2101
|
+
# Should return comma-separated string with all responsibilities
|
|
2102
|
+
expected = "Service Provider Corporate,Service Provider System Specific,Service Provider Hybrid (Corporate and System Specific),Provided by Customer (Customer System Specific),Configured by Customer (Customer System Specific),Inherited from pre-existing FedRAMP Authorization,Shared (Service Provider and Customer Responsibility)"
|
|
2103
|
+
assert result == expected
|
|
2104
|
+
|
|
2105
|
+
|
|
2106
|
+
@staticmethod
|
|
2107
|
+
def test_get_responsibility_no_responsibilities():
|
|
2108
|
+
"""Test getting responsibility when no responsibilities are set"""
|
|
2109
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import ControlImplementationStatus, get_responsibility
|
|
2110
|
+
|
|
2111
|
+
# Test data with no responsibilities
|
|
2112
|
+
origination_bool = {
|
|
2113
|
+
"bServiceProviderCorporate": False,
|
|
2114
|
+
"bServiceProviderSystemSpecific": False,
|
|
2115
|
+
"bServiceProviderHybrid": False,
|
|
2116
|
+
"bProvidedByCustomer": False,
|
|
2117
|
+
"bConfiguredByCustomer": False,
|
|
2118
|
+
"bInherited": False,
|
|
2119
|
+
"bShared": False,
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
result = get_responsibility(origination_bool)
|
|
2123
|
+
|
|
2124
|
+
# Should return NA when no responsibilities are found
|
|
2125
|
+
assert result == ControlImplementationStatus.NA.value
|
|
2126
|
+
|
|
2127
|
+
|
|
2128
|
+
@staticmethod
|
|
2129
|
+
def test_get_responsibility_empty_dict():
|
|
2130
|
+
"""Test getting responsibility with empty dictionary"""
|
|
2131
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import ControlImplementationStatus, get_responsibility
|
|
2132
|
+
|
|
2133
|
+
# Test data with empty dictionary
|
|
2134
|
+
origination_bool = {}
|
|
2135
|
+
|
|
2136
|
+
result = get_responsibility(origination_bool)
|
|
2137
|
+
|
|
2138
|
+
# Should return NA when no responsibilities are found
|
|
2139
|
+
assert result == ControlImplementationStatus.NA.value
|
|
2140
|
+
|
|
2141
|
+
|
|
2142
|
+
@staticmethod
|
|
2143
|
+
def test_get_responsibility_missing_keys():
|
|
2144
|
+
"""Test getting responsibility with missing boolean keys"""
|
|
2145
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2146
|
+
|
|
2147
|
+
# Test data with missing keys
|
|
2148
|
+
origination_bool = {
|
|
2149
|
+
"bServiceProviderCorporate": True
|
|
2150
|
+
# Missing other keys
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
result = get_responsibility(origination_bool)
|
|
2154
|
+
|
|
2155
|
+
# Should return only the present responsibility
|
|
2156
|
+
assert result == "Service Provider Corporate"
|
|
2157
|
+
|
|
2158
|
+
|
|
2159
|
+
@staticmethod
|
|
2160
|
+
def test_get_responsibility_mixed_true_false():
|
|
2161
|
+
"""Test getting responsibility with mixed true and false values"""
|
|
2162
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2163
|
+
|
|
2164
|
+
# Test data with mixed true and false values
|
|
2165
|
+
origination_bool = {
|
|
2166
|
+
"bServiceProviderCorporate": True,
|
|
2167
|
+
"bServiceProviderSystemSpecific": False,
|
|
2168
|
+
"bServiceProviderHybrid": True,
|
|
2169
|
+
"bProvidedByCustomer": False,
|
|
2170
|
+
"bConfiguredByCustomer": True,
|
|
2171
|
+
"bInherited": False,
|
|
2172
|
+
"bShared": True,
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
result = get_responsibility(origination_bool)
|
|
2176
|
+
|
|
2177
|
+
# Should return only the True responsibilities
|
|
2178
|
+
expected = "Service Provider Corporate,Service Provider Hybrid (Corporate and System Specific),Configured by Customer (Customer System Specific),Shared (Service Provider and Customer Responsibility)"
|
|
2179
|
+
assert result == expected
|
|
2180
|
+
|
|
2181
|
+
|
|
2182
|
+
@staticmethod
|
|
2183
|
+
def test_get_responsibility_none_values():
|
|
2184
|
+
"""Test getting responsibility with None values"""
|
|
2185
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import ControlImplementationStatus, get_responsibility
|
|
2186
|
+
|
|
2187
|
+
# Test data with None values
|
|
2188
|
+
origination_bool = {
|
|
2189
|
+
"bServiceProviderCorporate": None,
|
|
2190
|
+
"bServiceProviderSystemSpecific": None,
|
|
2191
|
+
"bServiceProviderHybrid": None,
|
|
2192
|
+
"bProvidedByCustomer": None,
|
|
2193
|
+
"bConfiguredByCustomer": None,
|
|
2194
|
+
"bInherited": None,
|
|
2195
|
+
"bShared": None,
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
result = get_responsibility(origination_bool)
|
|
2199
|
+
|
|
2200
|
+
# Should return NA when all values are None (falsy)
|
|
2201
|
+
assert result == ControlImplementationStatus.NA.value
|
|
2202
|
+
|
|
2203
|
+
|
|
2204
|
+
@staticmethod
|
|
2205
|
+
def test_get_responsibility_mixed_none_and_true():
|
|
2206
|
+
"""Test getting responsibility with mixed None and True values"""
|
|
2207
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2208
|
+
|
|
2209
|
+
# Test data with mixed None and True values
|
|
2210
|
+
origination_bool = {
|
|
2211
|
+
"bServiceProviderCorporate": None,
|
|
2212
|
+
"bServiceProviderSystemSpecific": True,
|
|
2213
|
+
"bServiceProviderHybrid": None,
|
|
2214
|
+
"bProvidedByCustomer": True,
|
|
2215
|
+
"bConfiguredByCustomer": None,
|
|
2216
|
+
"bInherited": None,
|
|
2217
|
+
"bShared": None,
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
result = get_responsibility(origination_bool)
|
|
2221
|
+
|
|
2222
|
+
# Should return only the True responsibilities
|
|
2223
|
+
expected = "Service Provider System Specific,Provided by Customer (Customer System Specific)"
|
|
2224
|
+
assert result == expected
|
|
2225
|
+
|
|
2226
|
+
|
|
2227
|
+
@staticmethod
|
|
2228
|
+
def test_get_responsibility_mixed_values():
|
|
2229
|
+
"""Test getting responsibility with string values"""
|
|
2230
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2231
|
+
|
|
2232
|
+
# Test data with string values
|
|
2233
|
+
origination_bool = {
|
|
2234
|
+
"bServiceProviderCorporate": True,
|
|
2235
|
+
"bServiceProviderSystemSpecific": False,
|
|
2236
|
+
"bServiceProviderHybrid": True,
|
|
2237
|
+
"bProvidedByCustomer": False,
|
|
2238
|
+
"bConfiguredByCustomer": True,
|
|
2239
|
+
"bInherited": False,
|
|
2240
|
+
"bShared": True,
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
result = get_responsibility(origination_bool)
|
|
2244
|
+
|
|
2245
|
+
# Should return only the truthy string values
|
|
2246
|
+
expected = "Service Provider Corporate,Service Provider Hybrid (Corporate and System Specific),Configured by Customer (Customer System Specific),Shared (Service Provider and Customer Responsibility)"
|
|
2247
|
+
assert result == expected
|
|
2248
|
+
|
|
2249
|
+
|
|
2250
|
+
@staticmethod
|
|
2251
|
+
def test_get_responsibility_integer_values():
|
|
2252
|
+
"""Test getting responsibility with integer values"""
|
|
2253
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2254
|
+
|
|
2255
|
+
# Test data with integer values
|
|
2256
|
+
origination_bool = {
|
|
2257
|
+
"bServiceProviderCorporate": 1,
|
|
2258
|
+
"bServiceProviderSystemSpecific": 0,
|
|
2259
|
+
"bServiceProviderHybrid": 1,
|
|
2260
|
+
"bProvidedByCustomer": 0,
|
|
2261
|
+
"bConfiguredByCustomer": 1,
|
|
2262
|
+
"bInherited": 0,
|
|
2263
|
+
"bShared": 1,
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
result = get_responsibility(origination_bool)
|
|
2267
|
+
|
|
2268
|
+
# Should return only the non-zero values
|
|
2269
|
+
expected = "Service Provider Corporate,Service Provider Hybrid (Corporate and System Specific),Configured by Customer (Customer System Specific),Shared (Service Provider and Customer Responsibility)"
|
|
2270
|
+
assert result == expected
|
|
2271
|
+
|
|
2272
|
+
|
|
2273
|
+
@staticmethod
|
|
2274
|
+
def test_get_responsibility_extra_keys():
|
|
2275
|
+
"""Test getting responsibility with extra keys in dictionary"""
|
|
2276
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2277
|
+
|
|
2278
|
+
# Test data with extra keys
|
|
2279
|
+
origination_bool = {
|
|
2280
|
+
"bServiceProviderCorporate": True,
|
|
2281
|
+
"bServiceProviderSystemSpecific": False,
|
|
2282
|
+
"bServiceProviderHybrid": True,
|
|
2283
|
+
"bProvidedByCustomer": False,
|
|
2284
|
+
"bConfiguredByCustomer": True,
|
|
2285
|
+
"bInherited": False,
|
|
2286
|
+
"bShared": True,
|
|
2287
|
+
"extraKey1": True,
|
|
2288
|
+
"extraKey2": False,
|
|
2289
|
+
"record_text": "Some text",
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
result = get_responsibility(origination_bool)
|
|
2293
|
+
|
|
2294
|
+
# Should return only the expected responsibilities, ignoring extra keys
|
|
2295
|
+
expected = "Service Provider Corporate,Service Provider Hybrid (Corporate and System Specific),Configured by Customer (Customer System Specific),Shared (Service Provider and Customer Responsibility)"
|
|
2296
|
+
assert result == expected
|
|
2297
|
+
|
|
2298
|
+
|
|
2299
|
+
@staticmethod
|
|
2300
|
+
def test_get_responsibility_order_preservation():
|
|
2301
|
+
"""Test that responsibility order is preserved as defined in the function"""
|
|
2302
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import get_responsibility
|
|
2303
|
+
|
|
2304
|
+
# Test data with all responsibilities
|
|
2305
|
+
origination_bool = {
|
|
2306
|
+
"bServiceProviderCorporate": True,
|
|
2307
|
+
"bServiceProviderSystemSpecific": True,
|
|
2308
|
+
"bServiceProviderHybrid": True,
|
|
2309
|
+
"bProvidedByCustomer": True,
|
|
2310
|
+
"bConfiguredByCustomer": True,
|
|
2311
|
+
"bInherited": True,
|
|
2312
|
+
"bShared": True,
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
result = get_responsibility(origination_bool)
|
|
2316
|
+
|
|
2317
|
+
# Verify the order matches the order in the function
|
|
2318
|
+
responsibilities = result.split(",")
|
|
2319
|
+
assert responsibilities[0] == "Service Provider Corporate"
|
|
2320
|
+
assert responsibilities[1] == "Service Provider System Specific"
|
|
2321
|
+
assert responsibilities[2] == "Service Provider Hybrid (Corporate and System Specific)"
|
|
2322
|
+
assert responsibilities[3] == "Provided by Customer (Customer System Specific)"
|
|
2323
|
+
assert responsibilities[4] == "Configured by Customer (Customer System Specific)"
|
|
2324
|
+
assert responsibilities[5] == "Inherited from pre-existing FedRAMP Authorization"
|
|
2325
|
+
assert responsibilities[6] == "Shared (Service Provider and Customer Responsibility)"
|
|
2326
|
+
|
|
2327
|
+
|
|
2328
|
+
@staticmethod
|
|
2329
|
+
def test_get_responsibility_constant_values():
|
|
2330
|
+
"""Test that the responsibility strings match the defined constants"""
|
|
2331
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import (
|
|
2332
|
+
CONFIGURED_BY_CUSTOMER,
|
|
2333
|
+
INHERITED,
|
|
2334
|
+
PROVIDED_BY_CUSTOMER,
|
|
2335
|
+
SERVICE_PROVIDER_CORPORATE,
|
|
2336
|
+
SERVICE_PROVIDER_HYBRID,
|
|
2337
|
+
SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
2338
|
+
SHARED,
|
|
2339
|
+
get_responsibility,
|
|
2340
|
+
)
|
|
2341
|
+
|
|
2342
|
+
# Test data with single responsibility
|
|
2343
|
+
origination_bool = {
|
|
2344
|
+
"bServiceProviderCorporate": True,
|
|
2345
|
+
"bServiceProviderSystemSpecific": False,
|
|
2346
|
+
"bServiceProviderHybrid": False,
|
|
2347
|
+
"bProvidedByCustomer": False,
|
|
2348
|
+
"bConfiguredByCustomer": False,
|
|
2349
|
+
"bInherited": False,
|
|
2350
|
+
"bShared": False,
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
result = get_responsibility(origination_bool)
|
|
2354
|
+
|
|
2355
|
+
# Verify the result matches the constant
|
|
2356
|
+
assert result == SERVICE_PROVIDER_CORPORATE
|
|
2357
|
+
|
|
2358
|
+
# Test with different responsibility
|
|
2359
|
+
origination_bool = {
|
|
2360
|
+
"bServiceProviderCorporate": False,
|
|
2361
|
+
"bServiceProviderSystemSpecific": True,
|
|
2362
|
+
"bServiceProviderHybrid": False,
|
|
2363
|
+
"bProvidedByCustomer": False,
|
|
2364
|
+
"bConfiguredByCustomer": False,
|
|
2365
|
+
"bInherited": False,
|
|
2366
|
+
"bShared": False,
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
result = get_responsibility(origination_bool)
|
|
2370
|
+
assert result == SERVICE_PROVIDER_SYSTEM_SPECIFIC
|
|
2371
|
+
|
|
2372
|
+
|
|
2373
|
+
@staticmethod
|
|
2374
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
2375
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_current_datetime")
|
|
2376
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.map_implementation_status")
|
|
2377
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.map_origination")
|
|
2378
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_responsibility")
|
|
2379
|
+
def test_parse_control_details_basic(
|
|
2380
|
+
mock_get_responsibility,
|
|
2381
|
+
mock_map_origination,
|
|
2382
|
+
mock_map_implementation_status,
|
|
2383
|
+
mock_get_current_datetime,
|
|
2384
|
+
mock_logger,
|
|
2385
|
+
):
|
|
2386
|
+
"""Test basic functionality of parse_control_details method"""
|
|
2387
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import parse_control_details
|
|
2388
|
+
|
|
2389
|
+
# Mock return values
|
|
2390
|
+
mock_get_current_datetime.return_value = "2024-01-01T00:00:00Z"
|
|
2391
|
+
mock_map_implementation_status.return_value = "Implemented"
|
|
2392
|
+
mock_map_origination.return_value = {
|
|
2393
|
+
"bServiceProviderCorporate": True,
|
|
2394
|
+
"bServiceProviderSystemSpecific": False,
|
|
2395
|
+
"bServiceProviderHybrid": False,
|
|
2396
|
+
"bProvidedByCustomer": False,
|
|
2397
|
+
"bConfiguredByCustomer": False,
|
|
2398
|
+
"bInherited": False,
|
|
2399
|
+
"bShared": False,
|
|
2400
|
+
"record_text": "Service Provider Corporate",
|
|
2401
|
+
}
|
|
2402
|
+
mock_get_responsibility.return_value = "Service Provider Corporate"
|
|
2403
|
+
|
|
2404
|
+
# Test data
|
|
2405
|
+
regscale_control_mock = Mock(spec=ControlImplementation)
|
|
2406
|
+
# Create a mock SecurityControl
|
|
2407
|
+
security_control_mock = Mock(spec=SecurityControl)
|
|
2408
|
+
|
|
2409
|
+
# Set up the mock with typical attributes
|
|
2410
|
+
security_control_mock.id = 456
|
|
2411
|
+
security_control_mock.controlId = "AC-1"
|
|
2412
|
+
security_control_mock.sortId = "AC-1"
|
|
2413
|
+
security_control_mock.title = "Access Control Policy and Procedures"
|
|
2414
|
+
security_control_mock.description = "The organization develops, documents, and disseminates to [Assignment: organization-defined personnel or roles]:"
|
|
2415
|
+
security_control_mock.controlType = "Control"
|
|
2416
|
+
security_control_mock.references = "NIST SP 800-53 Rev 5"
|
|
2417
|
+
security_control_mock.relatedControls = "AC-2, AC-3, AC-4"
|
|
2418
|
+
security_control_mock.subControls = None
|
|
2419
|
+
security_control_mock.enhancements = None
|
|
2420
|
+
security_control_mock.family = "Access Control"
|
|
2421
|
+
security_control_mock.mappings = None
|
|
2422
|
+
security_control_mock.assessmentPlan = None
|
|
2423
|
+
security_control_mock.weight = 1.0
|
|
2424
|
+
security_control_mock.catalogueId = 1
|
|
2425
|
+
security_control_mock.catalogueID = 1 # Alias for catalogueId
|
|
2426
|
+
security_control_mock.practiceLevel = None
|
|
2427
|
+
security_control_mock.objectives = []
|
|
2428
|
+
security_control_mock.tests = []
|
|
2429
|
+
security_control_mock.parameters = []
|
|
2430
|
+
security_control_mock.archived = False
|
|
2431
|
+
security_control_mock.createdById = "user123"
|
|
2432
|
+
security_control_mock.dateCreated = "2024-01-01T00:00:00Z"
|
|
2433
|
+
security_control_mock.lastUpdatedById = "user123"
|
|
2434
|
+
security_control_mock.dateLastUpdated = "2024-01-01T00:00:00Z"
|
|
2435
|
+
security_control_mock.criticality = "High"
|
|
2436
|
+
security_control_mock.isPublic = True
|
|
2437
|
+
security_control_mock.uuid = "12345678-1234-1234-1234-123456789012"
|
|
2438
|
+
|
|
2439
|
+
# Mock class methods
|
|
2440
|
+
security_control_mock.get_list_by_catalog = Mock(return_value=[])
|
|
2441
|
+
security_control_mock.lookup_control = Mock(return_value=security_control_mock)
|
|
2442
|
+
security_control_mock.lookup_control_by_name = Mock(return_value=security_control_mock)
|
|
2443
|
+
|
|
2444
|
+
# Mock the __hash__ method
|
|
2445
|
+
security_control_mock.__hash__ = Mock(return_value=hash(("AC-1", 1)))
|
|
2446
|
+
|
|
2447
|
+
# Mock the __eq__ method
|
|
2448
|
+
security_control_mock.__eq__ = Mock(return_value=True)
|
|
2449
|
+
# Set up the mock with typical attributes
|
|
2450
|
+
regscale_control_mock.id = 123
|
|
2451
|
+
regscale_control_mock.controlID = 456
|
|
2452
|
+
regscale_control_mock.status = ControlImplementationStatus.FullyImplemented.value
|
|
2453
|
+
regscale_control_mock.parentId = 789
|
|
2454
|
+
regscale_control_mock.parentModule = "securityplans"
|
|
2455
|
+
regscale_control_mock.responsibility = "Service Provider Corporate"
|
|
2456
|
+
regscale_control_mock.dateCreated = "2024-01-01T00:00:00Z"
|
|
2457
|
+
regscale_control_mock.dateLastUpdated = "2024-01-01T00:00:00Z"
|
|
2458
|
+
regscale_control_mock.controlOwnerId = "user123"
|
|
2459
|
+
regscale_control_mock.createdById = "user123"
|
|
2460
|
+
regscale_control_mock.lastUpdatedById = "user123"
|
|
2461
|
+
|
|
2462
|
+
# Set up boolean flags for origination
|
|
2463
|
+
regscale_control_mock.bServiceProviderCorporate = True
|
|
2464
|
+
regscale_control_mock.bServiceProviderSystemSpecific = False
|
|
2465
|
+
regscale_control_mock.bServiceProviderHybrid = False
|
|
2466
|
+
regscale_control_mock.bConfiguredByCustomer = False
|
|
2467
|
+
regscale_control_mock.bProvidedByCustomer = False
|
|
2468
|
+
regscale_control_mock.bShared = False
|
|
2469
|
+
regscale_control_mock.bInherited = False
|
|
2470
|
+
regscale_control_mock.bInheritedFedrampAuthorization = False
|
|
2471
|
+
|
|
2472
|
+
# Set up status boolean flags
|
|
2473
|
+
regscale_control_mock.bStatusImplemented = True
|
|
2474
|
+
regscale_control_mock.bStatusPartiallyImplemented = False
|
|
2475
|
+
regscale_control_mock.bStatusPlanned = False
|
|
2476
|
+
regscale_control_mock.bStatusAlternative = False
|
|
2477
|
+
regscale_control_mock.bStatusNotApplicable = False
|
|
2478
|
+
|
|
2479
|
+
# Mock the save method
|
|
2480
|
+
regscale_control_mock.save.return_value = True
|
|
2481
|
+
|
|
2482
|
+
# Mock the update method
|
|
2483
|
+
regscale_control_mock.update.return_value = True
|
|
2484
|
+
cis_data = {
|
|
2485
|
+
"record1": {
|
|
2486
|
+
"regscale_control_id": "AC-1",
|
|
2487
|
+
"implementation_status": "Implemented",
|
|
2488
|
+
"control_origination": "Service Provider Corporate",
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
# Call the method
|
|
2493
|
+
result = parse_control_details(
|
|
2494
|
+
version="rev5", control_imp=regscale_control_mock, control=security_control_mock, cis_data=cis_data
|
|
2495
|
+
)
|
|
2496
|
+
if result is True:
|
|
2497
|
+
result = regscale_control_mock
|
|
2498
|
+
# Verify the result structure
|
|
2499
|
+
assert isinstance(result, ControlImplementation)
|
|
2500
|
+
|
|
2501
|
+
# Verify the values
|
|
2502
|
+
assert result.status == "Implemented"
|
|
2503
|
+
assert result.responsibility == "Service Provider Corporate"
|
|
2504
|
+
|
|
2505
|
+
|
|
2506
|
+
@staticmethod
|
|
2507
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
2508
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.RegscaleVersion")
|
|
2509
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.clean_customer_responsibility")
|
|
2510
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_multi_status")
|
|
2511
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.UPDATED_IMPLEMENTATION_OBJECTIVES")
|
|
2512
|
+
def test_update_imp_objective_new_objective_creation(
|
|
2513
|
+
mock_updated_objectives,
|
|
2514
|
+
mock_get_multi_status,
|
|
2515
|
+
mock_clean_customer_responsibility,
|
|
2516
|
+
mock_regscale_version,
|
|
2517
|
+
mock_logger,
|
|
2518
|
+
):
|
|
2519
|
+
"""Test creating a new implementation objective when none exists"""
|
|
2520
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import ImplementationObjective, update_imp_objective
|
|
2521
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementation
|
|
2522
|
+
from regscale.models.regscale_models.control_objective import ControlObjective
|
|
2523
|
+
|
|
2524
|
+
# Mock RegScale version
|
|
2525
|
+
mock_regscale_version.meets_minimum_version.return_value = True
|
|
2526
|
+
|
|
2527
|
+
# Mock helper functions
|
|
2528
|
+
mock_get_multi_status.return_value = "Implemented"
|
|
2529
|
+
mock_clean_customer_responsibility.return_value = "Customer responsibility text"
|
|
2530
|
+
|
|
2531
|
+
# Create mock control implementation
|
|
2532
|
+
control_imp_mock = Mock(spec=ControlImplementation)
|
|
2533
|
+
control_imp_mock.id = 123
|
|
2534
|
+
control_imp_mock.controlID = 456
|
|
2535
|
+
control_imp_mock.responsibility = "Service Provider Corporate"
|
|
2536
|
+
|
|
2537
|
+
# Create mock control objective
|
|
2538
|
+
control_objective_mock = Mock(spec=ControlObjective)
|
|
2539
|
+
control_objective_mock.id = 789
|
|
2540
|
+
control_objective_mock.name = "AC-1.1"
|
|
2541
|
+
control_objective_mock.securityControlId = 456
|
|
2542
|
+
control_objective_mock.parentObjectiveId = None
|
|
2543
|
+
|
|
2544
|
+
# Create mock existing implementation objectives (empty list)
|
|
2545
|
+
existing_imp_obj = []
|
|
2546
|
+
|
|
2547
|
+
# Test record data
|
|
2548
|
+
record = {
|
|
2549
|
+
"cis": {"control_origination": "Service Provider Corporate, Service Provider System Specific"},
|
|
2550
|
+
"crm": {
|
|
2551
|
+
"specific_inheritance_and_customer_agency_csp_responsibilities": "Customer specific responsibilities",
|
|
2552
|
+
"can_be_inherited_from_csp": "No",
|
|
2553
|
+
},
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
# Call the method
|
|
2557
|
+
update_imp_objective(
|
|
2558
|
+
leverage_auth_id=999,
|
|
2559
|
+
existing_imp_obj=existing_imp_obj,
|
|
2560
|
+
imp=control_imp_mock,
|
|
2561
|
+
objectives=[control_objective_mock],
|
|
2562
|
+
record=record,
|
|
2563
|
+
)
|
|
2564
|
+
|
|
2565
|
+
# Verify that a new ImplementationObjective was added to the set
|
|
2566
|
+
mock_updated_objectives.add.assert_called_once()
|
|
2567
|
+
|
|
2568
|
+
# Get the created objective
|
|
2569
|
+
created_objective = mock_updated_objectives.add.call_args[0][0]
|
|
2570
|
+
|
|
2571
|
+
# Verify the objective properties
|
|
2572
|
+
assert created_objective.id == 0
|
|
2573
|
+
assert created_objective.implementationId == 123
|
|
2574
|
+
assert created_objective.objectiveId == 789
|
|
2575
|
+
assert created_objective.securityControlId == 456
|
|
2576
|
+
assert created_objective.status == "Implemented"
|
|
2577
|
+
assert created_objective.responsibility == "Service Provider Corporate,Service Provider System Specific"
|
|
2578
|
+
assert created_objective.cloudResponsibility == ""
|
|
2579
|
+
assert created_objective.customerResponsibility == "Customer responsibility text"
|
|
2580
|
+
assert created_objective.inherited is False
|
|
2581
|
+
assert created_objective.authorizationId == 999
|
|
2582
|
+
|
|
2583
|
+
# Verify logging
|
|
2584
|
+
mock_logger.debug.assert_called()
|
|
2585
|
+
|
|
2586
|
+
|
|
2587
|
+
@staticmethod
|
|
2588
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
2589
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.RegscaleVersion")
|
|
2590
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.clean_customer_responsibility")
|
|
2591
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_multi_status")
|
|
2592
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.UPDATED_IMPLEMENTATION_OBJECTIVES")
|
|
2593
|
+
def test_update_imp_objective_existing_objective_update(
|
|
2594
|
+
mock_updated_objectives,
|
|
2595
|
+
mock_get_multi_status,
|
|
2596
|
+
mock_clean_customer_responsibility,
|
|
2597
|
+
mock_regscale_version,
|
|
2598
|
+
mock_logger,
|
|
2599
|
+
):
|
|
2600
|
+
"""Test updating an existing implementation objective"""
|
|
2601
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import ImplementationObjective, update_imp_objective
|
|
2602
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementation
|
|
2603
|
+
from regscale.models.regscale_models.control_objective import ControlObjective
|
|
2604
|
+
|
|
2605
|
+
# Mock RegScale version
|
|
2606
|
+
mock_regscale_version.meets_minimum_version.return_value = False
|
|
2607
|
+
|
|
2608
|
+
# Mock helper functions
|
|
2609
|
+
mock_get_multi_status.return_value = "Partially Implemented"
|
|
2610
|
+
mock_clean_customer_responsibility.return_value = "Updated customer responsibility"
|
|
2611
|
+
|
|
2612
|
+
# Create mock control implementation
|
|
2613
|
+
control_imp_mock = Mock(spec=ControlImplementation)
|
|
2614
|
+
control_imp_mock.id = 123
|
|
2615
|
+
control_imp_mock.controlID = 456
|
|
2616
|
+
control_imp_mock.responsibility = ""
|
|
2617
|
+
|
|
2618
|
+
# Create mock control objective
|
|
2619
|
+
control_objective_mock = Mock(spec=ControlObjective)
|
|
2620
|
+
control_objective_mock.id = 789
|
|
2621
|
+
control_objective_mock.name = "AC-1.1"
|
|
2622
|
+
control_objective_mock.securityControlId = 456
|
|
2623
|
+
control_objective_mock.parentObjectiveId = None
|
|
2624
|
+
|
|
2625
|
+
# Create mock existing implementation objective
|
|
2626
|
+
existing_imp_obj_mock = Mock(spec=ImplementationObjective)
|
|
2627
|
+
existing_imp_obj_mock.id = 555
|
|
2628
|
+
existing_imp_obj_mock.objectiveId = 789
|
|
2629
|
+
existing_imp_obj_mock.implementationId = 123
|
|
2630
|
+
existing_imp_obj_mock.status = "Implemented"
|
|
2631
|
+
existing_imp_obj_mock.responsibility = "Service Provider Corporate"
|
|
2632
|
+
existing_imp_obj_mock.cloudResponsibility = ""
|
|
2633
|
+
existing_imp_obj_mock.customerResponsibility = ""
|
|
2634
|
+
|
|
2635
|
+
existing_imp_obj = [existing_imp_obj_mock]
|
|
2636
|
+
|
|
2637
|
+
# Test record data
|
|
2638
|
+
record = {
|
|
2639
|
+
"cis": {"control_origination": "Service Provider Corporate"},
|
|
2640
|
+
"crm": {
|
|
2641
|
+
"specific_inheritance_and_customer_agency_csp_responsibilities": "Updated customer responsibilities",
|
|
2642
|
+
"can_be_inherited_from_csp": "Yes",
|
|
2643
|
+
},
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
# Call the method
|
|
2647
|
+
update_imp_objective(
|
|
2648
|
+
leverage_auth_id=999,
|
|
2649
|
+
existing_imp_obj=existing_imp_obj,
|
|
2650
|
+
imp=control_imp_mock,
|
|
2651
|
+
objectives=[control_objective_mock],
|
|
2652
|
+
record=record,
|
|
2653
|
+
)
|
|
2654
|
+
|
|
2655
|
+
# Verify the existing objective was updated
|
|
2656
|
+
assert existing_imp_obj_mock.status == "Partially Implemented"
|
|
2657
|
+
assert existing_imp_obj_mock.responsibility == "Service Provider Corporate"
|
|
2658
|
+
assert existing_imp_obj_mock.cloudResponsibility == "Updated customer responsibility"
|
|
2659
|
+
assert existing_imp_obj_mock.customerResponsibility == ""
|
|
2660
|
+
|
|
2661
|
+
# Verify the updated objective was added to the set
|
|
2662
|
+
mock_updated_objectives.add.assert_called_once_with(existing_imp_obj_mock)
|
|
2663
|
+
|
|
2664
|
+
# Verify logging
|
|
2665
|
+
mock_logger.debug.assert_called()
|
|
2666
|
+
|
|
2667
|
+
|
|
2668
|
+
@staticmethod
|
|
2669
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
2670
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.RegscaleVersion")
|
|
2671
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.clean_customer_responsibility")
|
|
2672
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_multi_status")
|
|
2673
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.UPDATED_IMPLEMENTATION_OBJECTIVES")
|
|
2674
|
+
def test_update_imp_objective_mismatched_control_id(
|
|
2675
|
+
mock_updated_objectives,
|
|
2676
|
+
mock_get_multi_status,
|
|
2677
|
+
mock_clean_customer_responsibility,
|
|
2678
|
+
mock_regscale_version,
|
|
2679
|
+
mock_logger,
|
|
2680
|
+
):
|
|
2681
|
+
"""Test that objectives with mismatched control IDs are skipped"""
|
|
2682
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import ImplementationObjective, update_imp_objective
|
|
2683
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementation
|
|
2684
|
+
from regscale.models.regscale_models.control_objective import ControlObjective
|
|
2685
|
+
|
|
2686
|
+
# Mock RegScale version
|
|
2687
|
+
mock_regscale_version.meets_minimum_version.return_value = True
|
|
2688
|
+
|
|
2689
|
+
# Create mock control implementation
|
|
2690
|
+
control_imp_mock = Mock(spec=ControlImplementation)
|
|
2691
|
+
control_imp_mock.id = 123
|
|
2692
|
+
control_imp_mock.controlID = 456
|
|
2693
|
+
control_imp_mock.responsibility = "Service Provider Corporate"
|
|
2694
|
+
|
|
2695
|
+
# Create mock control objective with mismatched securityControlId
|
|
2696
|
+
control_objective_mock = Mock(spec=ControlObjective)
|
|
2697
|
+
control_objective_mock.id = 789
|
|
2698
|
+
control_objective_mock.name = "AC-1.1"
|
|
2699
|
+
control_objective_mock.securityControlId = 999 # Different from control_imp_mock.controlID
|
|
2700
|
+
control_objective_mock.parentObjectiveId = None
|
|
2701
|
+
|
|
2702
|
+
# Create mock existing implementation objectives (empty list)
|
|
2703
|
+
existing_imp_obj = []
|
|
2704
|
+
|
|
2705
|
+
# Test record data
|
|
2706
|
+
record = {
|
|
2707
|
+
"cis": {"control_origination": "Service Provider Corporate"},
|
|
2708
|
+
"crm": {
|
|
2709
|
+
"specific_inheritance_and_customer_agency_csp_responsibilities": "Customer responsibilities",
|
|
2710
|
+
"can_be_inherited_from_csp": "No",
|
|
2711
|
+
},
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
# Call the method
|
|
2715
|
+
update_imp_objective(
|
|
2716
|
+
leverage_auth_id=999,
|
|
2717
|
+
existing_imp_obj=existing_imp_obj,
|
|
2718
|
+
imp=control_imp_mock,
|
|
2719
|
+
objectives=[control_objective_mock],
|
|
2720
|
+
record=record,
|
|
2721
|
+
)
|
|
2722
|
+
|
|
2723
|
+
# Verify that no objective was added (due to mismatched control ID)
|
|
2724
|
+
mock_updated_objectives.add.assert_not_called()
|
|
2725
|
+
|
|
2726
|
+
|
|
2727
|
+
@staticmethod
|
|
2728
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.logger")
|
|
2729
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.RegscaleVersion")
|
|
2730
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.clean_customer_responsibility")
|
|
2731
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_multi_status")
|
|
2732
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.UPDATED_IMPLEMENTATION_OBJECTIVES")
|
|
2733
|
+
def test_update_imp_objective_fallback_responsibility(
|
|
2734
|
+
mock_updated_objectives,
|
|
2735
|
+
mock_get_multi_status,
|
|
2736
|
+
mock_clean_customer_responsibility,
|
|
2737
|
+
mock_regscale_version,
|
|
2738
|
+
mock_logger,
|
|
2739
|
+
):
|
|
2740
|
+
"""Test fallback responsibility when control_origination is empty"""
|
|
2741
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import (
|
|
2742
|
+
SERVICE_PROVIDER_CORPORATE,
|
|
2743
|
+
ImplementationObjective,
|
|
2744
|
+
update_imp_objective,
|
|
2745
|
+
)
|
|
2746
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementation
|
|
2747
|
+
from regscale.models.regscale_models.control_objective import ControlObjective
|
|
2748
|
+
|
|
2749
|
+
# Mock RegScale version
|
|
2750
|
+
mock_regscale_version.meets_minimum_version.return_value = True
|
|
2751
|
+
|
|
2752
|
+
# Mock helper functions
|
|
2753
|
+
mock_get_multi_status.return_value = "Implemented"
|
|
2754
|
+
mock_clean_customer_responsibility.return_value = ""
|
|
2755
|
+
|
|
2756
|
+
# Create mock control implementation
|
|
2757
|
+
control_imp_mock = Mock(spec=ControlImplementation)
|
|
2758
|
+
control_imp_mock.id = 123
|
|
2759
|
+
control_imp_mock.controlID = 456
|
|
2760
|
+
control_imp_mock.responsibility = None # No existing responsibility
|
|
2761
|
+
|
|
2762
|
+
# Create mock control objective
|
|
2763
|
+
control_objective_mock = Mock(spec=ControlObjective)
|
|
2764
|
+
control_objective_mock.id = 789
|
|
2765
|
+
control_objective_mock.name = "AC-1.1"
|
|
2766
|
+
control_objective_mock.securityControlId = 456
|
|
2767
|
+
control_objective_mock.parentObjectiveId = None
|
|
2768
|
+
|
|
2769
|
+
# Create mock existing implementation objectives (empty list)
|
|
2770
|
+
existing_imp_obj = []
|
|
2771
|
+
|
|
2772
|
+
# Test record data with empty control_origination
|
|
2773
|
+
record = {
|
|
2774
|
+
"cis": {"control_origination": ""}, # Empty origination
|
|
2775
|
+
"crm": {"specific_inheritance_and_customer_agency_csp_responsibilities": "", "can_be_inherited_from_csp": "No"},
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
# Call the method
|
|
2779
|
+
update_imp_objective(
|
|
2780
|
+
leverage_auth_id=999,
|
|
2781
|
+
existing_imp_obj=existing_imp_obj,
|
|
2782
|
+
imp=control_imp_mock,
|
|
2783
|
+
objectives=[control_objective_mock],
|
|
2784
|
+
record=record,
|
|
2785
|
+
)
|
|
2786
|
+
|
|
2787
|
+
# Verify that a new ImplementationObjective was added to the set
|
|
2788
|
+
mock_updated_objectives.add.assert_called_once()
|
|
2789
|
+
|
|
2790
|
+
# Get the created objective
|
|
2791
|
+
created_objective = mock_updated_objectives.add.call_args[0][0]
|
|
2792
|
+
|
|
2793
|
+
# Verify the objective uses the fallback responsibility
|
|
2794
|
+
assert created_objective.responsibility == ""
|
|
2795
|
+
|
|
2796
|
+
|
|
2797
|
+
@staticmethod
|
|
2798
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
2799
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm._drop_rows_nan")
|
|
2800
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.error_and_exit")
|
|
2801
|
+
def test_parse_instructions_worksheet_rev5_success(
|
|
2802
|
+
mock_error_and_exit,
|
|
2803
|
+
mock_drop_rows_nan,
|
|
2804
|
+
mock_get_pandas,
|
|
2805
|
+
):
|
|
2806
|
+
"""Test successful parsing of Rev5 instructions worksheet (using real DataFrame for iloc and dropna)"""
|
|
2807
|
+
|
|
2808
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import parse_instructions_worksheet
|
|
2809
|
+
|
|
2810
|
+
# Create a real DataFrame that simulates the instructions sheet
|
|
2811
|
+
data = {
|
|
2812
|
+
"Unnamed: 0": ["", "", "System Name (CSP to complete all cells)", "Test System 1", "Test System 2"],
|
|
2813
|
+
"Unnamed: 1": ["", "", "CSP", "CSP A", "CSP B"],
|
|
2814
|
+
"Unnamed: 2": ["", "", "System Identifier", "SYS-001", "SYS-002"],
|
|
2815
|
+
"Unnamed: 3": ["", "", "Impact Level", "High", "Moderate"],
|
|
2816
|
+
"Unnamed: 4": ["", "", "Other Column", "Other Data", "Other Data"],
|
|
2817
|
+
}
|
|
2818
|
+
dict_df = {"Instructions": pd.DataFrame(data)}
|
|
2819
|
+
|
|
2820
|
+
# Call the method
|
|
2821
|
+
result = parse_instructions_worksheet(dict_df, "rev5", "Instructions")
|
|
2822
|
+
|
|
2823
|
+
# Verify the result
|
|
2824
|
+
expected_result = [
|
|
2825
|
+
{
|
|
2826
|
+
"Unnamed: 0": "System Name (CSP to complete all cells)",
|
|
2827
|
+
"Unnamed: 1": "CSP",
|
|
2828
|
+
"Unnamed: 2": "System Identifier",
|
|
2829
|
+
"Unnamed: 3": "Impact Level",
|
|
2830
|
+
"Unnamed: 4": "Other Column",
|
|
2831
|
+
},
|
|
2832
|
+
{
|
|
2833
|
+
"Unnamed: 0": "Test System 1",
|
|
2834
|
+
"Unnamed: 1": "CSP A",
|
|
2835
|
+
"Unnamed: 2": "SYS-001",
|
|
2836
|
+
"Unnamed: 3": "High",
|
|
2837
|
+
"Unnamed: 4": "Other Data",
|
|
2838
|
+
},
|
|
2839
|
+
{
|
|
2840
|
+
"Unnamed: 0": "Test System 2",
|
|
2841
|
+
"Unnamed: 1": "CSP B",
|
|
2842
|
+
"Unnamed: 2": "SYS-002",
|
|
2843
|
+
"Unnamed: 3": "Moderate",
|
|
2844
|
+
"Unnamed: 4": "Other Data",
|
|
2845
|
+
},
|
|
2846
|
+
]
|
|
2847
|
+
assert result == expected_result
|
|
2848
|
+
|
|
2849
|
+
|
|
2850
|
+
@staticmethod
|
|
2851
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
2852
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm._drop_rows_nan")
|
|
2853
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.error_and_exit")
|
|
2854
|
+
def test_parse_instructions_worksheet_rev4_success(
|
|
2855
|
+
mock_error_and_exit,
|
|
2856
|
+
mock_drop_rows_nan,
|
|
2857
|
+
mock_get_pandas,
|
|
2858
|
+
):
|
|
2859
|
+
"""Test successful parsing of Rev5 instructions worksheet (using real DataFrame for iloc and dropna)"""
|
|
2860
|
+
import pandas as pd
|
|
2861
|
+
|
|
2862
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import parse_instructions_worksheet
|
|
2863
|
+
|
|
2864
|
+
# Create a real DataFrame that simulates the instructions sheet
|
|
2865
|
+
data = {
|
|
2866
|
+
"Unnamed: 0": ["", "", "System Name (CSP to complete all cells)", "Test System 1", "Test System 2"],
|
|
2867
|
+
"Unnamed: 1": ["", "", "CSP", "CSP A", "CSP B"],
|
|
2868
|
+
"Unnamed: 2": ["", "", "System Identifier", "SYS-001", "SYS-002"],
|
|
2869
|
+
"Unnamed: 3": ["", "", "Impact Level", "High", "Moderate"],
|
|
2870
|
+
"Unnamed: 4": ["", "", "Other Column", "Other Data", "Other Data"],
|
|
2871
|
+
}
|
|
2872
|
+
dict_df = {"Instructions": pd.DataFrame(data)}
|
|
2873
|
+
|
|
2874
|
+
# Call the method
|
|
2875
|
+
result = parse_instructions_worksheet(dict_df, "rev", "Instructions")
|
|
2876
|
+
|
|
2877
|
+
# Verify the result
|
|
2878
|
+
expected_result = [
|
|
2879
|
+
{
|
|
2880
|
+
"System Name (CSP to complete all cells)": "Test System 1",
|
|
2881
|
+
"CSP": "CSP A",
|
|
2882
|
+
"System Identifier": "SYS-001",
|
|
2883
|
+
"Impact Level": "High",
|
|
2884
|
+
"Other Column": "Other Data",
|
|
2885
|
+
},
|
|
2886
|
+
{
|
|
2887
|
+
"System Name (CSP to complete all cells)": "Test System 2",
|
|
2888
|
+
"CSP": "CSP B",
|
|
2889
|
+
"System Identifier": "SYS-002",
|
|
2890
|
+
"Impact Level": "Moderate",
|
|
2891
|
+
"Other Column": "Other Data",
|
|
2892
|
+
},
|
|
2893
|
+
]
|
|
2894
|
+
assert result == expected_result
|
|
2895
|
+
|
|
2896
|
+
|
|
2897
|
+
@staticmethod
|
|
2898
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
2899
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm._drop_rows_nan")
|
|
2900
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.error_and_exit")
|
|
2901
|
+
def test_parse_instructions_worksheet_missing_columns(
|
|
2902
|
+
mock_error_and_exit,
|
|
2903
|
+
mock_drop_rows_nan,
|
|
2904
|
+
mock_get_pandas,
|
|
2905
|
+
):
|
|
2906
|
+
"""Test instructions worksheet parsing when required columns are missing"""
|
|
2907
|
+
import pandas as pd
|
|
2908
|
+
|
|
2909
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import parse_instructions_worksheet
|
|
2910
|
+
|
|
2911
|
+
# Create DataFrame with missing required columns
|
|
2912
|
+
data = {
|
|
2913
|
+
"Unnamed: 0": ["", "", "Wrong Column", "Data 1"],
|
|
2914
|
+
"Unnamed: 1": ["", "", "Another Wrong Column", "Data 2"],
|
|
2915
|
+
}
|
|
2916
|
+
dict_df = {"Instructions": pd.DataFrame(data)}
|
|
2917
|
+
|
|
2918
|
+
# Mock _drop_rows_nan to return dataframe with wrong columns
|
|
2919
|
+
mock_drop_rows_nan.return_value = pd.DataFrame(
|
|
2920
|
+
{
|
|
2921
|
+
"Wrong Column": ["Data 1"],
|
|
2922
|
+
"Another Wrong Column": ["Data 2"],
|
|
2923
|
+
}
|
|
2924
|
+
)
|
|
2925
|
+
|
|
2926
|
+
# Call the method - should raise KeyError and call error_and_exit
|
|
2927
|
+
parse_instructions_worksheet(dict_df, "rev4", "Instructions")
|
|
2928
|
+
|
|
2929
|
+
# Verify error_and_exit was called with appropriate message
|
|
2930
|
+
mock_error_and_exit.assert_called_once()
|
|
2931
|
+
error_message = mock_error_and_exit.call_args[0][0]
|
|
2932
|
+
assert "Unable to find the relevant columns" in error_message
|
|
2933
|
+
assert "rev4" in error_message
|
|
2934
|
+
|
|
2935
|
+
|
|
2936
|
+
@staticmethod
|
|
2937
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
2938
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm._drop_rows_nan")
|
|
2939
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.error_and_exit")
|
|
2940
|
+
def test_parse_instructions_worksheet_custom_sheet_name(
|
|
2941
|
+
mock_error_and_exit,
|
|
2942
|
+
mock_drop_rows_nan,
|
|
2943
|
+
mock_get_pandas,
|
|
2944
|
+
):
|
|
2945
|
+
"""Test instructions worksheet parsing with custom sheet name"""
|
|
2946
|
+
import pandas as pd
|
|
2947
|
+
|
|
2948
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import parse_instructions_worksheet
|
|
2949
|
+
|
|
2950
|
+
# Create DataFrame with custom sheet name
|
|
2951
|
+
data = {
|
|
2952
|
+
"Unnamed: 0": ["", "", "System Name", "Test System 1"],
|
|
2953
|
+
"Unnamed: 1": ["", "", "CSP", "CSP A"],
|
|
2954
|
+
"Unnamed: 2": ["", "", "Impact Level", "High"],
|
|
2955
|
+
}
|
|
2956
|
+
dict_df = {"Custom Instructions": pd.DataFrame(data)}
|
|
2957
|
+
|
|
2958
|
+
# Mock _drop_rows_nan to return processed dataframe
|
|
2959
|
+
mock_drop_rows_nan.return_value = pd.DataFrame(
|
|
2960
|
+
{
|
|
2961
|
+
"System Name": ["Test System 1"],
|
|
2962
|
+
"CSP": ["CSP A"],
|
|
2963
|
+
"Impact Level": ["High"],
|
|
2964
|
+
}
|
|
2965
|
+
)
|
|
2966
|
+
|
|
2967
|
+
# Call the method with custom sheet name
|
|
2968
|
+
result = parse_instructions_worksheet(dict_df, "rev4", "Custom Instructions")
|
|
2969
|
+
|
|
2970
|
+
# Verify the result
|
|
2971
|
+
expected_result = [
|
|
2972
|
+
{
|
|
2973
|
+
"System Name": "Test System 1",
|
|
2974
|
+
"CSP": "CSP A",
|
|
2975
|
+
"Impact Level": "High",
|
|
2976
|
+
},
|
|
2977
|
+
]
|
|
2978
|
+
assert result == expected_result
|
|
2979
|
+
|
|
2980
|
+
|
|
2981
|
+
@staticmethod
|
|
2982
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
2983
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm._drop_rows_nan")
|
|
2984
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.error_and_exit")
|
|
2985
|
+
def test_parse_instructions_worksheet_empty_data(
|
|
2986
|
+
mock_error_and_exit,
|
|
2987
|
+
mock_drop_rows_nan,
|
|
2988
|
+
mock_get_pandas,
|
|
2989
|
+
):
|
|
2990
|
+
"""Test instructions worksheet parsing with empty data"""
|
|
2991
|
+
import pandas as pd
|
|
2992
|
+
|
|
2993
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import parse_instructions_worksheet
|
|
2994
|
+
|
|
2995
|
+
# Create empty DataFrame
|
|
2996
|
+
data = {
|
|
2997
|
+
"Unnamed: 0": ["", "", ""],
|
|
2998
|
+
"Unnamed: 1": ["", "", ""],
|
|
2999
|
+
"Unnamed: 2": ["", "", ""],
|
|
3000
|
+
}
|
|
3001
|
+
dict_df = {"Instructions": pd.DataFrame(data)}
|
|
3002
|
+
|
|
3003
|
+
# Mock _drop_rows_nan to return empty dataframe
|
|
3004
|
+
mock_drop_rows_nan.return_value = pd.DataFrame()
|
|
3005
|
+
|
|
3006
|
+
# Call the method
|
|
3007
|
+
result = parse_instructions_worksheet(dict_df, "rev4", "Instructions")
|
|
3008
|
+
|
|
3009
|
+
assert result == [{"Unnamed: 0": "", "Unnamed: 1": "", "Unnamed: 2": ""}]
|
|
3010
|
+
|
|
3011
|
+
|
|
3012
|
+
@staticmethod
|
|
3013
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
3014
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm._drop_rows_nan")
|
|
3015
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.error_and_exit")
|
|
3016
|
+
def test_parse_instructions_worksheet_rev5_with_nan_handling(
|
|
3017
|
+
mock_error_and_exit,
|
|
3018
|
+
mock_drop_rows_nan,
|
|
3019
|
+
mock_get_pandas,
|
|
3020
|
+
):
|
|
3021
|
+
"""Test Rev5 instructions worksheet parsing with proper NaN handling"""
|
|
3022
|
+
import datetime
|
|
3023
|
+
|
|
3024
|
+
import pandas as pd
|
|
3025
|
+
|
|
3026
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import parse_instructions_worksheet
|
|
3027
|
+
|
|
3028
|
+
# Create DataFrame with NaN values in some columns
|
|
3029
|
+
data = [
|
|
3030
|
+
{
|
|
3031
|
+
"Unnamed: 0": pd.NA,
|
|
3032
|
+
"Unnamed: 1": pd.NA,
|
|
3033
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": pd.NA,
|
|
3034
|
+
"Unnamed: 3": pd.NA,
|
|
3035
|
+
"Unnamed: 4": pd.NA,
|
|
3036
|
+
"Unnamed: 5": pd.NA,
|
|
3037
|
+
},
|
|
3038
|
+
{
|
|
3039
|
+
"Unnamed: 0": pd.NA,
|
|
3040
|
+
"Unnamed: 1": "System Name",
|
|
3041
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": pd.NA,
|
|
3042
|
+
"Unnamed: 3": pd.NA,
|
|
3043
|
+
"Unnamed: 4": pd.NA,
|
|
3044
|
+
"Unnamed: 5": pd.NA,
|
|
3045
|
+
},
|
|
3046
|
+
{
|
|
3047
|
+
"Unnamed: 0": pd.NA,
|
|
3048
|
+
"Unnamed: 1": "CSP",
|
|
3049
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": "System Name",
|
|
3050
|
+
"Unnamed: 3": pd.NA,
|
|
3051
|
+
"Unnamed: 4": "System Identifier",
|
|
3052
|
+
"Unnamed: 5": "Impact Level",
|
|
3053
|
+
},
|
|
3054
|
+
{
|
|
3055
|
+
"Unnamed: 0": pd.NA,
|
|
3056
|
+
"Unnamed: 1": "Netskope, Inc.",
|
|
3057
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": "Netskope, Inc.",
|
|
3058
|
+
"Unnamed: 3": pd.NA,
|
|
3059
|
+
"Unnamed: 4": "NGC",
|
|
3060
|
+
"Unnamed: 5": "High",
|
|
3061
|
+
},
|
|
3062
|
+
{
|
|
3063
|
+
"Unnamed: 0": pd.NA,
|
|
3064
|
+
"Unnamed: 1": pd.NA,
|
|
3065
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": pd.NA,
|
|
3066
|
+
"Unnamed: 3": pd.NA,
|
|
3067
|
+
"Unnamed: 4": pd.NA,
|
|
3068
|
+
"Unnamed: 5": pd.NA,
|
|
3069
|
+
},
|
|
3070
|
+
{
|
|
3071
|
+
"Unnamed: 0": pd.NA,
|
|
3072
|
+
"Unnamed: 1": "Document Revision History",
|
|
3073
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": pd.NA,
|
|
3074
|
+
"Unnamed: 3": pd.NA,
|
|
3075
|
+
"Unnamed: 4": pd.NA,
|
|
3076
|
+
"Unnamed: 5": pd.NA,
|
|
3077
|
+
},
|
|
3078
|
+
]
|
|
3079
|
+
dict_df = {"Instructions": pd.DataFrame(data)}
|
|
3080
|
+
|
|
3081
|
+
# Call the method
|
|
3082
|
+
result = parse_instructions_worksheet(dict_df, "rev5", "Instructions")
|
|
3083
|
+
|
|
3084
|
+
# Verify the result
|
|
3085
|
+
expected_result = [
|
|
3086
|
+
{
|
|
3087
|
+
"Unnamed: 1": "CSP",
|
|
3088
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": "System Name",
|
|
3089
|
+
"Unnamed: 4": "System Identifier",
|
|
3090
|
+
"Unnamed: 5": "Impact Level",
|
|
3091
|
+
},
|
|
3092
|
+
{
|
|
3093
|
+
"Unnamed: 1": "Netskope, Inc.",
|
|
3094
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": "Netskope, Inc.",
|
|
3095
|
+
"Unnamed: 4": "NGC",
|
|
3096
|
+
"Unnamed: 5": "High",
|
|
3097
|
+
},
|
|
3098
|
+
{
|
|
3099
|
+
"Unnamed: 1": None,
|
|
3100
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": None,
|
|
3101
|
+
"Unnamed: 4": None,
|
|
3102
|
+
"Unnamed: 5": None,
|
|
3103
|
+
},
|
|
3104
|
+
{
|
|
3105
|
+
"Unnamed: 1": "Document Revision History",
|
|
3106
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": None,
|
|
3107
|
+
"Unnamed: 4": None,
|
|
3108
|
+
"Unnamed: 5": None,
|
|
3109
|
+
},
|
|
3110
|
+
]
|
|
3111
|
+
assert result == expected_result
|
|
3112
|
+
|
|
3113
|
+
|
|
3114
|
+
@staticmethod
|
|
3115
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.get_pandas")
|
|
3116
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm._drop_rows_nan")
|
|
3117
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.error_and_exit")
|
|
3118
|
+
def test_parse_instructions_worksheet_rev5_csp_row_processing_basic(
|
|
3119
|
+
mock_error_and_exit,
|
|
3120
|
+
mock_drop_rows_nan,
|
|
3121
|
+
mock_get_pandas,
|
|
3122
|
+
):
|
|
3123
|
+
"""Basic test for the CSP row processing logic in rev5 instructions worksheet parsing"""
|
|
3124
|
+
import pandas as pd
|
|
3125
|
+
|
|
3126
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import parse_instructions_worksheet
|
|
3127
|
+
|
|
3128
|
+
# Mock pandas module with real pandas functions
|
|
3129
|
+
mock_pandas = Mock()
|
|
3130
|
+
mock_get_pandas.return_value = mock_pandas
|
|
3131
|
+
mock_pandas.isna = pd.isna # Use real pandas isna function
|
|
3132
|
+
|
|
3133
|
+
# Create DataFrame that simulates the actual structure for rev5
|
|
3134
|
+
# The function expects to find a row with "CSP" in it, then use that row to set column names
|
|
3135
|
+
data = {
|
|
3136
|
+
"Unnamed: 0": [pd.NA, pd.NA, "CSP", "Netskope, Inc.", pd.NA],
|
|
3137
|
+
"Unnamed: 1": [pd.NA, pd.NA, "System Name", "Netskope, Inc.", pd.NA],
|
|
3138
|
+
"FedRAMP® System Security Plan (SSP) Appendix J: Netskope GovCloud CIS and CRM Workbook\n\n": [
|
|
3139
|
+
pd.NA,
|
|
3140
|
+
pd.NA,
|
|
3141
|
+
"System Identifier",
|
|
3142
|
+
"NGC",
|
|
3143
|
+
pd.NA,
|
|
3144
|
+
],
|
|
3145
|
+
"Unnamed: 3": [pd.NA, pd.NA, pd.NA, pd.NA, pd.NA],
|
|
3146
|
+
"Unnamed: 4": [pd.NA, pd.NA, "Impact Level", "High", pd.NA],
|
|
3147
|
+
"Unnamed: 5": [pd.NA, pd.NA, pd.NA, pd.NA, pd.NA],
|
|
3148
|
+
}
|
|
3149
|
+
dict_df = {"Instructions": pd.DataFrame(data)}
|
|
3150
|
+
|
|
3151
|
+
# Mock _drop_rows_nan to return the final processed dataframe
|
|
3152
|
+
final_df = pd.DataFrame(
|
|
3153
|
+
{
|
|
3154
|
+
"CSP": ["Netskope, Inc."],
|
|
3155
|
+
"System Name": ["Netskope, Inc."],
|
|
3156
|
+
"System Identifier": ["NGC"],
|
|
3157
|
+
"Impact Level": ["High"],
|
|
3158
|
+
}
|
|
3159
|
+
)
|
|
3160
|
+
mock_drop_rows_nan.return_value = final_df
|
|
3161
|
+
|
|
3162
|
+
# Call the method
|
|
3163
|
+
result = parse_instructions_worksheet(dict_df, "rev5", "Instructions")
|
|
3164
|
+
|
|
3165
|
+
# Verify the result
|
|
3166
|
+
expected_result = [
|
|
3167
|
+
{
|
|
3168
|
+
"CSP": "Netskope, Inc.",
|
|
3169
|
+
"System Name": "Netskope, Inc.",
|
|
3170
|
+
"System Identifier": "NGC",
|
|
3171
|
+
"Impact Level": "High",
|
|
3172
|
+
},
|
|
3173
|
+
]
|
|
3174
|
+
assert result == expected_result
|
|
3175
|
+
|
|
3176
|
+
# Verify that _drop_rows_nan was called
|
|
3177
|
+
mock_drop_rows_nan.assert_called_once()
|
|
3178
|
+
|
|
3179
|
+
# Verify that error_and_exit was not called
|
|
3180
|
+
mock_error_and_exit.assert_not_called()
|
|
3181
|
+
|
|
3182
|
+
|
|
3183
|
+
@staticmethod
|
|
3184
|
+
@patch("regscale.integrations.public.fedramp.fedramp_cis_crm.update_imp_objective")
|
|
3185
|
+
def test_process_single_record_basic(mock_update_imp_objective):
|
|
3186
|
+
"""Basic test for the process_single_record function"""
|
|
3187
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import process_single_record
|
|
3188
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementation
|
|
3189
|
+
from regscale.models.regscale_models.control_objective import ControlObjective
|
|
3190
|
+
|
|
3191
|
+
# Create mock control implementation
|
|
3192
|
+
mock_implementation = Mock(spec=ControlImplementation)
|
|
3193
|
+
mock_implementation.id = 123
|
|
3194
|
+
mock_implementation.controlID = 456
|
|
3195
|
+
|
|
3196
|
+
# Create mock control objective
|
|
3197
|
+
mock_objective = Mock(spec=ControlObjective)
|
|
3198
|
+
mock_objective.id = 789
|
|
3199
|
+
mock_objective.otherId = "ac-2_smt.h.1" # This should match the source returned by find_by_source
|
|
3200
|
+
mock_objective.name = "h.1"
|
|
3201
|
+
|
|
3202
|
+
# Test data
|
|
3203
|
+
kwargs = {
|
|
3204
|
+
"version": "rev5",
|
|
3205
|
+
"leveraged_auth_id": 999,
|
|
3206
|
+
"implementation": mock_implementation,
|
|
3207
|
+
"record": {
|
|
3208
|
+
"cis": {"control_id": "AC-2(h)"},
|
|
3209
|
+
"crm": {"can_be_inherited_from_csp": "No"},
|
|
3210
|
+
},
|
|
3211
|
+
"control_objectives": [mock_objective],
|
|
3212
|
+
"existing_objectives": [],
|
|
3213
|
+
}
|
|
3214
|
+
rev_4_kwargs = {
|
|
3215
|
+
"version": "rev4",
|
|
3216
|
+
"leveraged_auth_id": 999,
|
|
3217
|
+
"implementation": mock_implementation,
|
|
3218
|
+
"record": {"cis": {"control_id": "AC-02 (h)"}},
|
|
3219
|
+
"control_objectives": [mock_objective],
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
# Call the function
|
|
3223
|
+
errors, result = process_single_record(**kwargs)
|
|
3224
|
+
rev_4_errors, rev_4_result = process_single_record(**rev_4_kwargs)
|
|
3225
|
+
# Verify the result - the behavior changed due to improved control ID parsing
|
|
3226
|
+
# Now it finds sub-parts instead of failing to find the exact match for both rev4 and rev5
|
|
3227
|
+
assert errors == [
|
|
3228
|
+
"AC-2(h): Control exists with 1 sub-parts. Update import file.",
|
|
3229
|
+
]
|
|
3230
|
+
assert result is None
|
|
3231
|
+
assert rev_4_errors == [
|
|
3232
|
+
"AC-02 (h): Control exists with 1 sub-parts. Update import file.",
|
|
3233
|
+
]
|
|
3234
|
+
assert rev_4_result is None
|
|
3235
|
+
|
|
3236
|
+
|
|
3237
|
+
# Tests for new functions added after PartMapper removal
|
|
3238
|
+
|
|
3239
|
+
|
|
3240
|
+
@staticmethod
|
|
3241
|
+
def test_convert_to_oscal_identifier_basic_patterns():
|
|
3242
|
+
"""Test _convert_to_oscal_identifier with basic control ID patterns."""
|
|
3243
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _convert_to_oscal_identifier
|
|
3244
|
+
|
|
3245
|
+
# Pattern 1: Control enhancement - AC-6(1), AC-02 (01)
|
|
3246
|
+
assert _convert_to_oscal_identifier("AC-6(1)") == "ac-6.1_smt"
|
|
3247
|
+
assert _convert_to_oscal_identifier("AC-02(01)") == "ac-2.1_smt"
|
|
3248
|
+
assert _convert_to_oscal_identifier("SI-4(2)") == "si-4.2_smt"
|
|
3249
|
+
assert _convert_to_oscal_identifier("AU-12(10)") == "au-12.10_smt"
|
|
3250
|
+
|
|
3251
|
+
# Pattern 2: Control part - AC-1(a), AC-01 (a)
|
|
3252
|
+
assert _convert_to_oscal_identifier("AC-1(a)") == "ac-1_smt.a"
|
|
3253
|
+
assert _convert_to_oscal_identifier("AC-01(b)") == "ac-1_smt.b"
|
|
3254
|
+
assert _convert_to_oscal_identifier("SI-2(c)") == "si-2_smt.c"
|
|
3255
|
+
assert _convert_to_oscal_identifier("AU-3(z)") == "au-3_smt.z"
|
|
3256
|
+
|
|
3257
|
+
# Pattern 3: Control enhancement part - AC-6(1)(a), AC-02 (07) (a)
|
|
3258
|
+
assert _convert_to_oscal_identifier("AC-6(1)(a)") == "ac-6.1_smt.a"
|
|
3259
|
+
assert _convert_to_oscal_identifier("AC-02(07)(b)") == "ac-2.7_smt.b"
|
|
3260
|
+
assert _convert_to_oscal_identifier("SI-4(2)(c)") == "si-4.2_smt.c"
|
|
3261
|
+
assert _convert_to_oscal_identifier("AU-12(3)(z)") == "au-12.3_smt.z"
|
|
3262
|
+
|
|
3263
|
+
# Pattern 4: Base control - AC-1, AC-01
|
|
3264
|
+
assert _convert_to_oscal_identifier("AC-1") == "ac-1_smt"
|
|
3265
|
+
assert _convert_to_oscal_identifier("AC-01") == "ac-1_smt"
|
|
3266
|
+
assert _convert_to_oscal_identifier("SI-4") == "si-4_smt"
|
|
3267
|
+
assert _convert_to_oscal_identifier("AU-12") == "au-12_smt"
|
|
3268
|
+
|
|
3269
|
+
|
|
3270
|
+
@staticmethod
|
|
3271
|
+
def test_convert_to_oscal_identifier_with_spaces():
|
|
3272
|
+
"""Test _convert_to_oscal_identifier with various spacing patterns."""
|
|
3273
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _convert_to_oscal_identifier
|
|
3274
|
+
|
|
3275
|
+
# Spaces around parentheses
|
|
3276
|
+
assert _convert_to_oscal_identifier("AC-6 (1)") == "ac-6.1_smt"
|
|
3277
|
+
assert _convert_to_oscal_identifier("AC-6( 1)") == "ac-6.1_smt"
|
|
3278
|
+
assert _convert_to_oscal_identifier("AC-6(1 )") == "ac-6.1_smt"
|
|
3279
|
+
assert _convert_to_oscal_identifier("AC-6 ( 1 )") == "ac-6.1_smt"
|
|
3280
|
+
|
|
3281
|
+
# Multiple patterns with spaces
|
|
3282
|
+
assert _convert_to_oscal_identifier("AC-2 (a)") == "ac-2_smt.a"
|
|
3283
|
+
assert _convert_to_oscal_identifier("AC-2 ( a )") == "ac-2_smt.a"
|
|
3284
|
+
assert _convert_to_oscal_identifier("AC-6 (1) (a)") == "ac-6.1_smt.a"
|
|
3285
|
+
assert _convert_to_oscal_identifier("AC-6 ( 1 ) ( a )") == "ac-6.1_smt.a"
|
|
3286
|
+
|
|
3287
|
+
|
|
3288
|
+
@staticmethod
|
|
3289
|
+
def test_convert_to_oscal_identifier_invalid_patterns():
|
|
3290
|
+
"""Test _convert_to_oscal_identifier with invalid or unsupported patterns."""
|
|
3291
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _convert_to_oscal_identifier
|
|
3292
|
+
|
|
3293
|
+
# Invalid patterns should return None
|
|
3294
|
+
assert _convert_to_oscal_identifier("") is None
|
|
3295
|
+
assert _convert_to_oscal_identifier("AC") is None
|
|
3296
|
+
assert _convert_to_oscal_identifier("AC-") is None
|
|
3297
|
+
assert _convert_to_oscal_identifier("AC-1-2") is None
|
|
3298
|
+
assert _convert_to_oscal_identifier("1-AC") is None
|
|
3299
|
+
assert _convert_to_oscal_identifier("AC-1(") is None
|
|
3300
|
+
assert _convert_to_oscal_identifier("AC-1)") is None
|
|
3301
|
+
assert _convert_to_oscal_identifier("AC-1(a)(b)(c)") is None # Too many parts
|
|
3302
|
+
assert _convert_to_oscal_identifier("AC-1(1a)") is None # Mixed number and letter
|
|
3303
|
+
|
|
3304
|
+
|
|
3305
|
+
@staticmethod
|
|
3306
|
+
def test_convert_to_oscal_identifier_case_handling():
|
|
3307
|
+
"""Test _convert_to_oscal_identifier handles case correctly."""
|
|
3308
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _convert_to_oscal_identifier
|
|
3309
|
+
|
|
3310
|
+
# Mixed case input should still work
|
|
3311
|
+
assert _convert_to_oscal_identifier("ac-1") == "ac-1_smt"
|
|
3312
|
+
assert _convert_to_oscal_identifier("Ac-1") == "ac-1_smt"
|
|
3313
|
+
assert _convert_to_oscal_identifier("aC-1") == "ac-1_smt"
|
|
3314
|
+
assert _convert_to_oscal_identifier("AC-1") == "ac-1_smt"
|
|
3315
|
+
|
|
3316
|
+
# Letter parts should remain lowercase
|
|
3317
|
+
assert _convert_to_oscal_identifier("AC-1(A)") == "ac-1_smt.a"
|
|
3318
|
+
assert _convert_to_oscal_identifier("AC-1(a)") == "ac-1_smt.a"
|
|
3319
|
+
|
|
3320
|
+
|
|
3321
|
+
@staticmethod
|
|
3322
|
+
def test_find_exact_objective_by_other_id_found():
|
|
3323
|
+
"""Test _find_exact_objective_by_other_id when objective is found."""
|
|
3324
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _find_exact_objective_by_other_id
|
|
3325
|
+
|
|
3326
|
+
# Create mock control objectives
|
|
3327
|
+
mock_obj1 = Mock()
|
|
3328
|
+
mock_obj1.otherId = "ac-1_smt"
|
|
3329
|
+
|
|
3330
|
+
mock_obj2 = Mock()
|
|
3331
|
+
mock_obj2.otherId = "ac-2.1_smt"
|
|
3332
|
+
|
|
3333
|
+
mock_obj3 = Mock()
|
|
3334
|
+
mock_obj3.otherId = "ac-3_smt.a"
|
|
3335
|
+
|
|
3336
|
+
control_objectives = [mock_obj1, mock_obj2, mock_obj3]
|
|
3337
|
+
|
|
3338
|
+
# Test exact matches
|
|
3339
|
+
assert _find_exact_objective_by_other_id("ac-1_smt", control_objectives) is True
|
|
3340
|
+
assert _find_exact_objective_by_other_id("ac-2.1_smt", control_objectives) is True
|
|
3341
|
+
assert _find_exact_objective_by_other_id("ac-3_smt.a", control_objectives) is True
|
|
3342
|
+
|
|
3343
|
+
|
|
3344
|
+
@staticmethod
|
|
3345
|
+
def test_find_exact_objective_by_other_id_not_found():
|
|
3346
|
+
"""Test _find_exact_objective_by_other_id when objective is not found."""
|
|
3347
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _find_exact_objective_by_other_id
|
|
3348
|
+
|
|
3349
|
+
# Create mock control objectives
|
|
3350
|
+
mock_obj1 = Mock()
|
|
3351
|
+
mock_obj1.otherId = "ac-1_smt"
|
|
3352
|
+
|
|
3353
|
+
mock_obj2 = Mock()
|
|
3354
|
+
mock_obj2.otherId = "ac-2.1_smt"
|
|
3355
|
+
|
|
3356
|
+
control_objectives = [mock_obj1, mock_obj2]
|
|
3357
|
+
|
|
3358
|
+
# Test non-matching cases
|
|
3359
|
+
assert _find_exact_objective_by_other_id("ac-3_smt", control_objectives) is False
|
|
3360
|
+
assert _find_exact_objective_by_other_id("ac-1_smt.a", control_objectives) is False
|
|
3361
|
+
assert _find_exact_objective_by_other_id("si-1_smt", control_objectives) is False
|
|
3362
|
+
|
|
3363
|
+
|
|
3364
|
+
@staticmethod
|
|
3365
|
+
def test_find_exact_objective_by_other_id_missing_attribute():
|
|
3366
|
+
"""Test _find_exact_objective_by_other_id with objects missing otherId attribute."""
|
|
3367
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _find_exact_objective_by_other_id
|
|
3368
|
+
|
|
3369
|
+
# Create mock control objectives, some without otherId
|
|
3370
|
+
mock_obj1 = Mock()
|
|
3371
|
+
mock_obj1.otherId = "ac-1_smt"
|
|
3372
|
+
|
|
3373
|
+
mock_obj2 = Mock(spec=[]) # No otherId attribute
|
|
3374
|
+
|
|
3375
|
+
mock_obj3 = Mock()
|
|
3376
|
+
mock_obj3.otherId = "ac-2.1_smt"
|
|
3377
|
+
|
|
3378
|
+
control_objectives = [mock_obj1, mock_obj2, mock_obj3]
|
|
3379
|
+
|
|
3380
|
+
# Should still work despite missing attribute
|
|
3381
|
+
assert _find_exact_objective_by_other_id("ac-1_smt", control_objectives) is True
|
|
3382
|
+
assert _find_exact_objective_by_other_id("ac-2.1_smt", control_objectives) is True
|
|
3383
|
+
assert _find_exact_objective_by_other_id("ac-3_smt", control_objectives) is False
|
|
3384
|
+
|
|
3385
|
+
|
|
3386
|
+
@staticmethod
|
|
3387
|
+
def test_find_exact_objective_by_other_id_empty_list():
|
|
3388
|
+
"""Test _find_exact_objective_by_other_id with empty control objectives list."""
|
|
3389
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _find_exact_objective_by_other_id
|
|
3390
|
+
|
|
3391
|
+
control_objectives = []
|
|
3392
|
+
|
|
3393
|
+
# Should return False for empty list
|
|
3394
|
+
assert _find_exact_objective_by_other_id("ac-1_smt", control_objectives) is False
|
|
3395
|
+
assert _find_exact_objective_by_other_id("", control_objectives) is False
|
|
3396
|
+
|
|
3397
|
+
|
|
3398
|
+
@staticmethod
|
|
3399
|
+
def test_convert_oscal_to_rev4_control_label_basic():
|
|
3400
|
+
"""Test _convert_oscal_to_rev4_control_label with basic patterns."""
|
|
3401
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _convert_oscal_to_rev4_control_label
|
|
3402
|
+
|
|
3403
|
+
# Single digit numbers should be zero-padded
|
|
3404
|
+
assert _convert_oscal_to_rev4_control_label("ac-1") == "ac-01"
|
|
3405
|
+
assert _convert_oscal_to_rev4_control_label("ac-2") == "ac-02"
|
|
3406
|
+
assert _convert_oscal_to_rev4_control_label("si-4") == "si-04"
|
|
3407
|
+
assert _convert_oscal_to_rev4_control_label("au-9") == "au-09"
|
|
3408
|
+
|
|
3409
|
+
# Double digit numbers should remain unchanged
|
|
3410
|
+
assert _convert_oscal_to_rev4_control_label("ac-10") == "ac-10"
|
|
3411
|
+
assert _convert_oscal_to_rev4_control_label("ac-11") == "ac-11"
|
|
3412
|
+
assert _convert_oscal_to_rev4_control_label("si-12") == "si-12"
|
|
3413
|
+
|
|
3414
|
+
|
|
3415
|
+
@staticmethod
|
|
3416
|
+
def test_convert_oscal_to_rev4_control_label_with_enhancements():
|
|
3417
|
+
"""Test _convert_oscal_to_rev4_control_label strips enhancements correctly."""
|
|
3418
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _convert_oscal_to_rev4_control_label
|
|
3419
|
+
|
|
3420
|
+
# Control enhancements should be stripped, base control should be padded
|
|
3421
|
+
assert _convert_oscal_to_rev4_control_label("ac-1.2") == "ac-01"
|
|
3422
|
+
assert _convert_oscal_to_rev4_control_label("ac-2.7") == "ac-02"
|
|
3423
|
+
assert _convert_oscal_to_rev4_control_label("si-4.10") == "si-04"
|
|
3424
|
+
assert _convert_oscal_to_rev4_control_label("au-12.3") == "au-12"
|
|
3425
|
+
|
|
3426
|
+
# Multiple dots should also be handled
|
|
3427
|
+
assert _convert_oscal_to_rev4_control_label("ac-1.2.3") == "ac-01"
|
|
3428
|
+
|
|
3429
|
+
|
|
3430
|
+
@staticmethod
|
|
3431
|
+
def test_convert_oscal_to_rev4_control_label_edge_cases():
|
|
3432
|
+
"""Test _convert_oscal_to_rev4_control_label with edge cases."""
|
|
3433
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _convert_oscal_to_rev4_control_label
|
|
3434
|
+
|
|
3435
|
+
# Invalid formats should return as-is
|
|
3436
|
+
assert _convert_oscal_to_rev4_control_label("ac") == "ac"
|
|
3437
|
+
assert _convert_oscal_to_rev4_control_label("ac-") == "ac-"
|
|
3438
|
+
assert _convert_oscal_to_rev4_control_label("1-ac") == "1-ac"
|
|
3439
|
+
assert _convert_oscal_to_rev4_control_label("") == ""
|
|
3440
|
+
|
|
3441
|
+
# Already padded numbers should remain unchanged
|
|
3442
|
+
assert _convert_oscal_to_rev4_control_label("ac-01") == "ac-01"
|
|
3443
|
+
assert _convert_oscal_to_rev4_control_label("ac-01.2") == "ac-01"
|
|
3444
|
+
|
|
3445
|
+
# Three digit numbers should remain unchanged
|
|
3446
|
+
assert _convert_oscal_to_rev4_control_label("ac-100") == "ac-100"
|
|
3447
|
+
|
|
3448
|
+
|
|
3449
|
+
@staticmethod
|
|
3450
|
+
def test_find_subpart_objectives_by_other_id_found():
|
|
3451
|
+
"""Test _find_subpart_objectives_by_other_id when sub-parts are found."""
|
|
3452
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _find_subpart_objectives_by_other_id
|
|
3453
|
+
|
|
3454
|
+
# Create mock control objectives with sub-parts
|
|
3455
|
+
mock_obj1 = Mock()
|
|
3456
|
+
mock_obj1.otherId = "ac-2_smt.a"
|
|
3457
|
+
|
|
3458
|
+
mock_obj2 = Mock()
|
|
3459
|
+
mock_obj2.otherId = "ac-2_smt.b"
|
|
3460
|
+
|
|
3461
|
+
mock_obj3 = Mock()
|
|
3462
|
+
mock_obj3.otherId = "ac-2_smt.c"
|
|
3463
|
+
|
|
3464
|
+
mock_obj4 = Mock()
|
|
3465
|
+
mock_obj4.otherId = "ac-3_smt.a" # Different base control
|
|
3466
|
+
|
|
3467
|
+
mock_obj5 = Mock()
|
|
3468
|
+
mock_obj5.otherId = "ac-2.1_smt.a" # Enhancement sub-part
|
|
3469
|
+
|
|
3470
|
+
control_objectives = [mock_obj1, mock_obj2, mock_obj3, mock_obj4, mock_obj5]
|
|
3471
|
+
|
|
3472
|
+
# Test finding sub-parts for ac-2_smt
|
|
3473
|
+
result = _find_subpart_objectives_by_other_id("ac-2_smt", control_objectives)
|
|
3474
|
+
expected = ["ac-2_smt.a", "ac-2_smt.b", "ac-2_smt.c"]
|
|
3475
|
+
assert sorted(result) == sorted(expected)
|
|
3476
|
+
|
|
3477
|
+
# Test finding sub-parts for ac-2.1_smt
|
|
3478
|
+
result = _find_subpart_objectives_by_other_id("ac-2.1_smt", control_objectives)
|
|
3479
|
+
expected = ["ac-2.1_smt.a"]
|
|
3480
|
+
assert result == expected
|
|
3481
|
+
|
|
3482
|
+
|
|
3483
|
+
@staticmethod
|
|
3484
|
+
def test_find_subpart_objectives_by_other_id_not_found():
|
|
3485
|
+
"""Test _find_subpart_objectives_by_other_id when no sub-parts are found."""
|
|
3486
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _find_subpart_objectives_by_other_id
|
|
3487
|
+
|
|
3488
|
+
# Create mock control objectives without matching sub-parts
|
|
3489
|
+
mock_obj1 = Mock()
|
|
3490
|
+
mock_obj1.otherId = "ac-1_smt"
|
|
3491
|
+
|
|
3492
|
+
mock_obj2 = Mock()
|
|
3493
|
+
mock_obj2.otherId = "ac-2_smt"
|
|
3494
|
+
|
|
3495
|
+
mock_obj3 = Mock()
|
|
3496
|
+
mock_obj3.otherId = "si-4_smt.a" # Different family
|
|
3497
|
+
|
|
3498
|
+
control_objectives = [mock_obj1, mock_obj2, mock_obj3]
|
|
3499
|
+
|
|
3500
|
+
# Test with base control that has no sub-parts
|
|
3501
|
+
result = _find_subpart_objectives_by_other_id("ac-3_smt", control_objectives)
|
|
3502
|
+
assert result == []
|
|
3503
|
+
|
|
3504
|
+
# Test with non-existent control
|
|
3505
|
+
result = _find_subpart_objectives_by_other_id("zz-1_smt", control_objectives)
|
|
3506
|
+
assert result == []
|
|
3507
|
+
|
|
3508
|
+
|
|
3509
|
+
@staticmethod
|
|
3510
|
+
def test_find_subpart_objectives_by_other_id_empty_list():
|
|
3511
|
+
"""Test _find_subpart_objectives_by_other_id with empty control objectives list."""
|
|
3512
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _find_subpart_objectives_by_other_id
|
|
3513
|
+
|
|
3514
|
+
control_objectives = []
|
|
3515
|
+
|
|
3516
|
+
# Should return empty list for empty input
|
|
3517
|
+
result = _find_subpart_objectives_by_other_id("ac-1_smt", control_objectives)
|
|
3518
|
+
assert result == []
|
|
3519
|
+
|
|
3520
|
+
|
|
3521
|
+
@staticmethod
|
|
3522
|
+
def test_find_subpart_objectives_by_other_id_missing_attribute():
|
|
3523
|
+
"""Test _find_subpart_objectives_by_other_id with objects missing otherId attribute."""
|
|
3524
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import _find_subpart_objectives_by_other_id
|
|
3525
|
+
|
|
3526
|
+
# Create mock control objectives, some without otherId
|
|
3527
|
+
mock_obj1 = Mock()
|
|
3528
|
+
mock_obj1.otherId = "ac-2_smt.a"
|
|
3529
|
+
|
|
3530
|
+
mock_obj2 = Mock(spec=[]) # No otherId attribute
|
|
3531
|
+
|
|
3532
|
+
mock_obj3 = Mock()
|
|
3533
|
+
mock_obj3.otherId = "ac-2_smt.b"
|
|
3534
|
+
|
|
3535
|
+
control_objectives = [mock_obj1, mock_obj2, mock_obj3]
|
|
3536
|
+
|
|
3537
|
+
# Should work despite missing attributes
|
|
3538
|
+
result = _find_subpart_objectives_by_other_id("ac-2_smt", control_objectives)
|
|
3539
|
+
expected = ["ac-2_smt.a", "ac-2_smt.b"]
|
|
3540
|
+
assert sorted(result) == sorted(expected)
|
|
3541
|
+
|
|
3542
|
+
|
|
3543
|
+
@staticmethod
|
|
3544
|
+
def test_smart_find_by_source_exact_match():
|
|
3545
|
+
"""Test smart_find_by_source when exact match is found."""
|
|
3546
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import smart_find_by_source
|
|
3547
|
+
|
|
3548
|
+
# Create mock control objective with exact match
|
|
3549
|
+
mock_obj = Mock()
|
|
3550
|
+
mock_obj.otherId = "ac-1_smt"
|
|
3551
|
+
|
|
3552
|
+
control_objectives = [mock_obj]
|
|
3553
|
+
|
|
3554
|
+
# Test exact match scenarios
|
|
3555
|
+
source, parts, status = smart_find_by_source("AC-1", control_objectives)
|
|
3556
|
+
assert source == "ac-1_smt"
|
|
3557
|
+
assert parts == []
|
|
3558
|
+
assert status == "Found exact match: ac-1_smt"
|
|
3559
|
+
|
|
3560
|
+
source, parts, status = smart_find_by_source("AC-01", control_objectives)
|
|
3561
|
+
assert source == "ac-1_smt"
|
|
3562
|
+
assert parts == []
|
|
3563
|
+
assert status == "Found exact match: ac-1_smt"
|
|
3564
|
+
|
|
3565
|
+
|
|
3566
|
+
@staticmethod
|
|
3567
|
+
def test_smart_find_by_source_subparts_found():
|
|
3568
|
+
"""Test smart_find_by_source when sub-parts are found but no exact match."""
|
|
3569
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import smart_find_by_source
|
|
3570
|
+
|
|
3571
|
+
# Create mock control objectives with sub-parts but no exact match
|
|
3572
|
+
mock_obj1 = Mock()
|
|
3573
|
+
mock_obj1.otherId = "ac-1_smt.a"
|
|
3574
|
+
|
|
3575
|
+
mock_obj2 = Mock()
|
|
3576
|
+
mock_obj2.otherId = "ac-1_smt.b"
|
|
3577
|
+
|
|
3578
|
+
control_objectives = [mock_obj1, mock_obj2]
|
|
3579
|
+
|
|
3580
|
+
source, parts, status = smart_find_by_source("AC-1", control_objectives)
|
|
3581
|
+
assert source is None
|
|
3582
|
+
assert sorted(parts) == ["ac-1_smt.a", "ac-1_smt.b"]
|
|
3583
|
+
assert status == "Control exists with 2 sub-parts. Update import file."
|
|
3584
|
+
|
|
3585
|
+
|
|
3586
|
+
@staticmethod
|
|
3587
|
+
def test_smart_find_by_source_no_match():
|
|
3588
|
+
"""Test smart_find_by_source when no match is found."""
|
|
3589
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import smart_find_by_source
|
|
3590
|
+
|
|
3591
|
+
# Create mock control objectives that don't match
|
|
3592
|
+
mock_obj = Mock()
|
|
3593
|
+
mock_obj.otherId = "si-1_smt"
|
|
3594
|
+
|
|
3595
|
+
control_objectives = [mock_obj]
|
|
3596
|
+
|
|
3597
|
+
source, parts, status = smart_find_by_source("AC-1", control_objectives)
|
|
3598
|
+
assert source is None
|
|
3599
|
+
assert parts == []
|
|
3600
|
+
assert status == "No database match found for AC-1 (expected: ac-1_smt)"
|
|
3601
|
+
|
|
3602
|
+
|
|
3603
|
+
@staticmethod
|
|
3604
|
+
def test_smart_find_by_source_invalid_control():
|
|
3605
|
+
"""Test smart_find_by_source with invalid control ID."""
|
|
3606
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import smart_find_by_source
|
|
3607
|
+
|
|
3608
|
+
control_objectives = []
|
|
3609
|
+
|
|
3610
|
+
# Invalid control ID that can't be converted to OSCAL
|
|
3611
|
+
source, parts, status = smart_find_by_source("INVALID", control_objectives)
|
|
3612
|
+
assert source is None
|
|
3613
|
+
assert parts == []
|
|
3614
|
+
assert status == "Unable to convert control INVALID to OSCAL format"
|
|
3615
|
+
|
|
3616
|
+
|
|
3617
|
+
@staticmethod
|
|
3618
|
+
def test_smart_find_by_source_enhancement_patterns():
|
|
3619
|
+
"""Test smart_find_by_source with control enhancement patterns."""
|
|
3620
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import smart_find_by_source
|
|
3621
|
+
|
|
3622
|
+
# Test control enhancement
|
|
3623
|
+
mock_obj = Mock()
|
|
3624
|
+
mock_obj.otherId = "ac-6.1_smt"
|
|
3625
|
+
control_objectives = [mock_obj]
|
|
3626
|
+
|
|
3627
|
+
source, parts, status = smart_find_by_source("AC-6(1)", control_objectives)
|
|
3628
|
+
assert source == "ac-6.1_smt"
|
|
3629
|
+
assert parts == []
|
|
3630
|
+
assert status == "Found exact match: ac-6.1_smt"
|
|
3631
|
+
|
|
3632
|
+
# Test control enhancement part
|
|
3633
|
+
mock_obj2 = Mock()
|
|
3634
|
+
mock_obj2.otherId = "ac-6.1_smt.a"
|
|
3635
|
+
control_objectives = [mock_obj2]
|
|
3636
|
+
|
|
3637
|
+
source, parts, status = smart_find_by_source("AC-6(1)(a)", control_objectives)
|
|
3638
|
+
assert source == "ac-6.1_smt.a"
|
|
3639
|
+
assert parts == []
|
|
3640
|
+
assert status == "Found exact match: ac-6.1_smt.a"
|
|
3641
|
+
|
|
3642
|
+
|
|
3643
|
+
@staticmethod
|
|
3644
|
+
def test_smart_find_by_source_with_extra_spaces():
|
|
3645
|
+
"""Test smart_find_by_source handles control IDs with extra spaces."""
|
|
3646
|
+
from regscale.integrations.public.fedramp.fedramp_cis_crm import smart_find_by_source
|
|
3647
|
+
|
|
3648
|
+
mock_obj = Mock()
|
|
3649
|
+
mock_obj.otherId = "ac-6.1_smt.a"
|
|
3650
|
+
control_objectives = [mock_obj]
|
|
3651
|
+
|
|
3652
|
+
# Test various spacing patterns
|
|
3653
|
+
source, parts, status = smart_find_by_source("AC-6 ( 1 ) ( a )", control_objectives)
|
|
3654
|
+
assert source == "ac-6.1_smt.a"
|
|
3655
|
+
assert parts == []
|
|
3656
|
+
assert status == "Found exact match: ac-6.1_smt.a"
|
|
3657
|
+
|
|
3658
|
+
source, parts, status = smart_find_by_source("AC-6 ( 1 )", control_objectives)
|
|
3659
|
+
assert source is None
|
|
3660
|
+
assert parts == ["ac-6.1_smt.a"]
|
|
3661
|
+
assert status == "Control exists with 1 sub-parts. Update import file."
|