regscale-cli 6.23.0.1__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.

Files changed (43) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +2 -0
  3. regscale/integrations/commercial/__init__.py +1 -0
  4. regscale/integrations/commercial/sarif/sarif_converter.py +1 -1
  5. regscale/integrations/commercial/wizv2/click.py +109 -2
  6. regscale/integrations/commercial/wizv2/compliance_report.py +1485 -0
  7. regscale/integrations/commercial/wizv2/constants.py +72 -2
  8. regscale/integrations/commercial/wizv2/data_fetcher.py +61 -0
  9. regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
  10. regscale/integrations/commercial/wizv2/issue.py +775 -27
  11. regscale/integrations/commercial/wizv2/policy_compliance.py +599 -181
  12. regscale/integrations/commercial/wizv2/reports.py +243 -0
  13. regscale/integrations/commercial/wizv2/scanner.py +668 -245
  14. regscale/integrations/compliance_integration.py +304 -51
  15. regscale/integrations/due_date_handler.py +210 -0
  16. regscale/integrations/public/cci_importer.py +444 -0
  17. regscale/integrations/scanner_integration.py +718 -153
  18. regscale/models/integration_models/CCI_List.xml +1 -0
  19. regscale/models/integration_models/cisa_kev_data.json +18 -3
  20. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  21. regscale/models/regscale_models/form_field_value.py +1 -1
  22. regscale/models/regscale_models/milestone.py +1 -0
  23. regscale/models/regscale_models/regscale_model.py +225 -60
  24. regscale/models/regscale_models/security_plan.py +3 -2
  25. regscale/regscale.py +7 -0
  26. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.0.dist-info}/METADATA +9 -9
  27. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.0.dist-info}/RECORD +43 -26
  28. tests/fixtures/test_fixture.py +13 -8
  29. tests/regscale/integrations/public/__init__.py +0 -0
  30. tests/regscale/integrations/public/test_alienvault.py +220 -0
  31. tests/regscale/integrations/public/test_cci.py +458 -0
  32. tests/regscale/integrations/public/test_cisa.py +1021 -0
  33. tests/regscale/integrations/public/test_emass.py +518 -0
  34. tests/regscale/integrations/public/test_fedramp.py +851 -0
  35. tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
  36. tests/regscale/integrations/public/test_file_uploads.py +506 -0
  37. tests/regscale/integrations/public/test_oscal.py +453 -0
  38. tests/regscale/models/test_form_field_value_integration.py +304 -0
  39. tests/regscale/models/test_module_integration.py +582 -0
  40. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.0.dist-info}/LICENSE +0 -0
  41. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.0.dist-info}/WHEEL +0 -0
  42. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.0.dist-info}/entry_points.txt +0 -0
  43. {regscale_cli-6.23.0.1.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."