regscale-cli 6.25.0.1__py3-none-any.whl → 6.26.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +18 -3
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/integrations/commercial/sicura/api.py +14 -13
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +49 -39
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +17 -0
- regscale/integrations/commercial/wizv2/click.py +26 -26
- regscale/integrations/commercial/wizv2/compliance_report.py +152 -157
- regscale/integrations/commercial/wizv2/constants.py +20 -71
- regscale/integrations/commercial/wizv2/scanner.py +3 -3
- regscale/integrations/compliance_integration.py +67 -2
- regscale/integrations/control_matcher.py +358 -0
- regscale/integrations/due_date_handler.py +118 -6
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +1 -0
- regscale/integrations/public/cci_importer.py +37 -38
- regscale/integrations/public/fedramp/click.py +60 -2
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +199 -130
- regscale/models/integration_models/cisa_kev_data.json +199 -4
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +46 -21
- regscale/models/regscale_models/issue.py +256 -94
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- regscale/utils/threading/threadhandler.py +20 -15
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/RECORD +84 -37
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3731 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +1814 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +350 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1469 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1351 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +750 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +264 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +624 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/test_control_matcher.py +1314 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_issue.py +378 -1
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for Wiz Policy Compliance Integration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from unittest.mock import Mock, patch, mock_open
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from regscale.integrations.commercial.wizv2.policy_compliance import (
|
|
14
|
+
WizComplianceItem,
|
|
15
|
+
WizPolicyComplianceIntegration,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestWizComplianceItem:
|
|
20
|
+
"""Test the WizComplianceItem class."""
|
|
21
|
+
|
|
22
|
+
def setup_method(self):
|
|
23
|
+
"""Set up test data."""
|
|
24
|
+
self.mock_raw_data = {
|
|
25
|
+
"id": "assessment-123",
|
|
26
|
+
"result": "FAIL",
|
|
27
|
+
"policy": {
|
|
28
|
+
"id": "policy-456",
|
|
29
|
+
"name": "Test Policy",
|
|
30
|
+
"description": "Test policy description",
|
|
31
|
+
"severity": "HIGH",
|
|
32
|
+
"remediationInstructions": "Fix the issue",
|
|
33
|
+
"securitySubCategories": [
|
|
34
|
+
{
|
|
35
|
+
"externalId": "AC-3",
|
|
36
|
+
"category": {"framework": {"id": "wf-id-4", "name": "NIST SP 800-53 Revision 5"}},
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
"resource": {
|
|
41
|
+
"id": "resource-789",
|
|
42
|
+
"name": "Test Resource",
|
|
43
|
+
"type": "VIRTUAL_MACHINE",
|
|
44
|
+
"region": "us-east-1",
|
|
45
|
+
"subscription": {"cloudProvider": "Azure", "name": "Test Subscription", "externalId": "sub-123"},
|
|
46
|
+
"tags": [{"key": "Environment", "value": "Production"}, {"key": "Owner", "value": "TestTeam"}],
|
|
47
|
+
},
|
|
48
|
+
"output": {"someField": "someValue"},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
self.mock_integration = Mock()
|
|
52
|
+
self.mock_integration.framework_id = "wf-id-4" # Add framework_id for filtering
|
|
53
|
+
self.mock_integration.get_framework_name.return_value = "NIST SP 800-53 Revision 5"
|
|
54
|
+
|
|
55
|
+
def test_wiz_compliance_item_creation(self):
|
|
56
|
+
"""Test creating a WizComplianceItem from raw data."""
|
|
57
|
+
item = WizComplianceItem(self.mock_raw_data, self.mock_integration)
|
|
58
|
+
|
|
59
|
+
assert item.id == "assessment-123"
|
|
60
|
+
assert item.result == "FAIL"
|
|
61
|
+
assert item.resource_id == "resource-789"
|
|
62
|
+
assert item.resource_name == "Test Resource"
|
|
63
|
+
assert item.control_id == "AC-3"
|
|
64
|
+
assert item.compliance_result == "FAIL"
|
|
65
|
+
assert item.severity == "HIGH"
|
|
66
|
+
assert item.framework_id == "wf-id-4"
|
|
67
|
+
assert item.framework == "NIST SP 800-53 Revision 5"
|
|
68
|
+
assert item.is_fail
|
|
69
|
+
assert not item.is_pass
|
|
70
|
+
|
|
71
|
+
def test_wiz_compliance_item_pass_result(self):
|
|
72
|
+
"""Test WizComplianceItem with PASS result."""
|
|
73
|
+
self.mock_raw_data["result"] = "PASS"
|
|
74
|
+
item = WizComplianceItem(self.mock_raw_data, self.mock_integration)
|
|
75
|
+
|
|
76
|
+
assert item.compliance_result == "PASS"
|
|
77
|
+
assert item.is_pass
|
|
78
|
+
assert not item.is_fail
|
|
79
|
+
|
|
80
|
+
def test_wiz_compliance_item_missing_control_id(self):
|
|
81
|
+
"""Test WizComplianceItem with missing control ID."""
|
|
82
|
+
# Remove security subcategories
|
|
83
|
+
self.mock_raw_data["policy"]["securitySubCategories"] = []
|
|
84
|
+
item = WizComplianceItem(self.mock_raw_data, self.mock_integration)
|
|
85
|
+
|
|
86
|
+
assert item.control_id == ""
|
|
87
|
+
assert item.framework == ""
|
|
88
|
+
assert item.framework_id is None
|
|
89
|
+
|
|
90
|
+
def test_wiz_compliance_item_missing_policy(self):
|
|
91
|
+
"""Test WizComplianceItem with missing policy data."""
|
|
92
|
+
self.mock_raw_data["policy"] = {}
|
|
93
|
+
item = WizComplianceItem(self.mock_raw_data, self.mock_integration)
|
|
94
|
+
|
|
95
|
+
assert item.control_id == ""
|
|
96
|
+
assert item.severity is None
|
|
97
|
+
assert "unknown policy" in item.description.lower()
|
|
98
|
+
assert item.framework == ""
|
|
99
|
+
|
|
100
|
+
def test_wiz_compliance_item_description_fallback(self):
|
|
101
|
+
"""Test description fallback when policy description is missing."""
|
|
102
|
+
# Remove description but keep name
|
|
103
|
+
del self.mock_raw_data["policy"]["description"]
|
|
104
|
+
item = WizComplianceItem(self.mock_raw_data, self.mock_integration)
|
|
105
|
+
|
|
106
|
+
assert "Test Policy" in item.description
|
|
107
|
+
|
|
108
|
+
# Test with ruleDescription
|
|
109
|
+
self.mock_raw_data["policy"]["ruleDescription"] = "Rule description"
|
|
110
|
+
item = WizComplianceItem(self.mock_raw_data, self.mock_integration)
|
|
111
|
+
assert item.description == "Rule description"
|
|
112
|
+
|
|
113
|
+
def test_wiz_compliance_item_framework_caching(self):
|
|
114
|
+
"""Test that framework mapping uses integration cache."""
|
|
115
|
+
item = WizComplianceItem(self.mock_raw_data, self.mock_integration)
|
|
116
|
+
|
|
117
|
+
# Access framework property
|
|
118
|
+
framework = item.framework
|
|
119
|
+
|
|
120
|
+
# Verify integration's get_framework_name was called
|
|
121
|
+
self.mock_integration.get_framework_name.assert_called_with("wf-id-4")
|
|
122
|
+
assert framework == "NIST SP 800-53 Revision 5"
|
|
123
|
+
|
|
124
|
+
def test_wiz_compliance_item_no_integration(self):
|
|
125
|
+
"""Test WizComplianceItem without integration instance."""
|
|
126
|
+
item = WizComplianceItem(self.mock_raw_data, None)
|
|
127
|
+
|
|
128
|
+
# Should fallback to framework name from raw data
|
|
129
|
+
assert item.framework == "NIST SP 800-53 Revision 5"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class TestWizPolicyComplianceIntegration:
|
|
133
|
+
"""Test the WizPolicyComplianceIntegration class."""
|
|
134
|
+
|
|
135
|
+
def setup_method(self):
|
|
136
|
+
"""Set up test fixtures."""
|
|
137
|
+
self.integration = WizPolicyComplianceIntegration(
|
|
138
|
+
plan_id=123,
|
|
139
|
+
wiz_project_id="test-project-123",
|
|
140
|
+
client_id="test-client-id",
|
|
141
|
+
client_secret="test-client-secret",
|
|
142
|
+
framework_id="wf-id-4",
|
|
143
|
+
catalog_id=456,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Mock authentication
|
|
147
|
+
self.integration.access_token = "mock-token"
|
|
148
|
+
self.integration.wiz_endpoint = "https://api.wiz.io/graphql"
|
|
149
|
+
|
|
150
|
+
def test_initialization(self):
|
|
151
|
+
"""Test integration initialization."""
|
|
152
|
+
assert self.integration.plan_id == 123
|
|
153
|
+
assert self.integration.wiz_project_id == "test-project-123"
|
|
154
|
+
assert self.integration.client_id == "test-client-id"
|
|
155
|
+
assert self.integration.client_secret == "test-client-secret"
|
|
156
|
+
assert self.integration.framework_id == "wf-id-4"
|
|
157
|
+
assert self.integration.catalog_id == 456
|
|
158
|
+
assert self.integration.title == "Wiz Policy Compliance Integration"
|
|
159
|
+
assert self.integration.framework == "NIST800-53R5"
|
|
160
|
+
|
|
161
|
+
def test_framework_id_mapping(self):
|
|
162
|
+
"""Test framework ID to name mapping."""
|
|
163
|
+
assert self.integration._map_framework_id_to_name("wf-id-4") == "NIST800-53R5"
|
|
164
|
+
assert self.integration._map_framework_id_to_name("wf-id-48") == "NIST800-53R4"
|
|
165
|
+
assert self.integration._map_framework_id_to_name("wf-id-5") == "FedRAMP"
|
|
166
|
+
assert self.integration._map_framework_id_to_name("unknown-id") == "unknown-id"
|
|
167
|
+
|
|
168
|
+
def test_resource_type_mapping(self):
|
|
169
|
+
"""Test resource type to asset type mapping."""
|
|
170
|
+
# Create mock compliance items with different resource types
|
|
171
|
+
test_cases = [
|
|
172
|
+
("VIRTUAL_MACHINE", "Virtual Machine"),
|
|
173
|
+
("CONTAINER", "Container"),
|
|
174
|
+
("DATABASE", "Database"),
|
|
175
|
+
("BUCKET", "Storage"),
|
|
176
|
+
("UNKNOWN_TYPE", "Cloud Resource"),
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
for resource_type, expected_asset_type in test_cases:
|
|
180
|
+
mock_data = {
|
|
181
|
+
"id": "test-id",
|
|
182
|
+
"result": "PASS",
|
|
183
|
+
"policy": {},
|
|
184
|
+
"resource": {"type": resource_type},
|
|
185
|
+
"output": {},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
compliance_item = WizComplianceItem(mock_data)
|
|
189
|
+
asset_type = self.integration._map_resource_type_to_asset_type(compliance_item)
|
|
190
|
+
assert asset_type == expected_asset_type
|
|
191
|
+
|
|
192
|
+
@patch("regscale.integrations.commercial.wizv2.policy_compliance.wiz_authenticate")
|
|
193
|
+
@patch("regscale.integrations.commercial.wizv2.policy_compliance.check_license")
|
|
194
|
+
def test_authenticate_wiz(self, mock_check_license, mock_wiz_authenticate):
|
|
195
|
+
"""Test Wiz authentication."""
|
|
196
|
+
# Set up mocks
|
|
197
|
+
mock_app = Mock()
|
|
198
|
+
mock_app.config = {"wizUrl": "https://api.wiz.io/graphql"}
|
|
199
|
+
mock_check_license.return_value = mock_app
|
|
200
|
+
mock_wiz_authenticate.return_value = "test-token"
|
|
201
|
+
|
|
202
|
+
# Clear existing token
|
|
203
|
+
self.integration.access_token = ""
|
|
204
|
+
|
|
205
|
+
token = self.integration.authenticate_wiz()
|
|
206
|
+
|
|
207
|
+
assert token == "test-token"
|
|
208
|
+
assert self.integration.access_token == "test-token"
|
|
209
|
+
assert self.integration.wiz_endpoint == "https://api.wiz.io/graphql"
|
|
210
|
+
|
|
211
|
+
mock_wiz_authenticate.assert_called_once_with(client_id="test-client-id", client_secret="test-client-secret")
|
|
212
|
+
|
|
213
|
+
@patch("regscale.integrations.commercial.wizv2.policy_compliance.run_async_queries")
|
|
214
|
+
def test_fetch_policy_assessments_from_wiz(self, mock_run_async_queries):
|
|
215
|
+
"""Test fetching policy assessments from Wiz API."""
|
|
216
|
+
# Mock API response
|
|
217
|
+
mock_nodes = [
|
|
218
|
+
{"id": "assessment-1", "result": "PASS"},
|
|
219
|
+
{"id": "assessment-2", "result": "FAIL"},
|
|
220
|
+
]
|
|
221
|
+
mock_run_async_queries.return_value = [("configuration", mock_nodes, None)] # (query_type, nodes, error)
|
|
222
|
+
|
|
223
|
+
results = self.integration._fetch_policy_assessments_from_wiz()
|
|
224
|
+
|
|
225
|
+
assert len(results) == 2
|
|
226
|
+
assert results[0]["id"] == "assessment-1"
|
|
227
|
+
assert results[1]["result"] == "FAIL"
|
|
228
|
+
|
|
229
|
+
# Verify the query was called with correct parameters
|
|
230
|
+
mock_run_async_queries.assert_called_once()
|
|
231
|
+
call_args = mock_run_async_queries.call_args
|
|
232
|
+
assert call_args[1]["max_concurrent"] == 1
|
|
233
|
+
|
|
234
|
+
@patch("regscale.integrations.commercial.wizv2.policy_compliance.run_async_queries")
|
|
235
|
+
def test_fetch_policy_assessments_with_error(self, mock_run_async_queries):
|
|
236
|
+
"""Test handling API errors when fetching assessments."""
|
|
237
|
+
# Mock API error response
|
|
238
|
+
mock_run_async_queries.return_value = [("configuration", [], "API Error message")]
|
|
239
|
+
|
|
240
|
+
with pytest.raises(SystemExit): # error_and_exit raises SystemExit
|
|
241
|
+
self.integration._fetch_policy_assessments_from_wiz()
|
|
242
|
+
|
|
243
|
+
def test_create_compliance_item(self):
|
|
244
|
+
"""Test creating compliance item from raw data."""
|
|
245
|
+
raw_data = {
|
|
246
|
+
"id": "test-id",
|
|
247
|
+
"result": "FAIL",
|
|
248
|
+
"policy": {"name": "Test Policy"},
|
|
249
|
+
"resource": {"id": "res-123", "name": "Test Resource"},
|
|
250
|
+
"output": {},
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
item = self.integration.create_compliance_item(raw_data)
|
|
254
|
+
|
|
255
|
+
assert isinstance(item, WizComplianceItem)
|
|
256
|
+
assert item.id == "test-id"
|
|
257
|
+
assert item.result == "FAIL"
|
|
258
|
+
|
|
259
|
+
@patch.object(WizPolicyComplianceIntegration, "_load_regscale_assets")
|
|
260
|
+
@patch.object(WizPolicyComplianceIntegration, "_asset_exists_in_regscale")
|
|
261
|
+
def test_fetch_compliance_data(self, mock_asset_exists, mock_load_assets):
|
|
262
|
+
"""Test fetching raw compliance data with filtering."""
|
|
263
|
+
# Mock that all assets exist
|
|
264
|
+
mock_asset_exists.return_value = True
|
|
265
|
+
|
|
266
|
+
# Set up the asset cache that's used in the filtering logic
|
|
267
|
+
self.integration._regscale_assets_by_wiz_id = {
|
|
268
|
+
"res-1": Mock(name="Resource 1", wizId="res-1"),
|
|
269
|
+
"res-2": Mock(name="Resource 2", wizId="res-2"),
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# Mock _load_regscale_assets to not override our cache
|
|
273
|
+
mock_load_assets.return_value = None
|
|
274
|
+
|
|
275
|
+
# Mock the raw data fetch with proper framework data
|
|
276
|
+
mock_raw_data = [
|
|
277
|
+
{
|
|
278
|
+
"id": "assessment-1",
|
|
279
|
+
"result": "PASS",
|
|
280
|
+
"policy": {
|
|
281
|
+
"name": "Policy 1",
|
|
282
|
+
"securitySubCategories": [
|
|
283
|
+
{
|
|
284
|
+
"externalId": "AC-2",
|
|
285
|
+
"category": {"framework": {"id": "wf-id-4", "name": "NIST SP 800-53 Revision 5"}},
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
},
|
|
289
|
+
"resource": {"id": "res-1", "name": "Resource 1"},
|
|
290
|
+
"output": {},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"id": "assessment-2",
|
|
294
|
+
"result": "FAIL",
|
|
295
|
+
"policy": {
|
|
296
|
+
"name": "Policy 2",
|
|
297
|
+
"securitySubCategories": [
|
|
298
|
+
{
|
|
299
|
+
"externalId": "AC-3",
|
|
300
|
+
"category": {"framework": {"id": "wf-id-4", "name": "NIST SP 800-53 Revision 5"}},
|
|
301
|
+
}
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
"resource": {"id": "res-2", "name": "Resource 2"},
|
|
305
|
+
"output": {},
|
|
306
|
+
},
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
with patch.object(self.integration, "_fetch_policy_assessments_from_wiz", return_value=mock_raw_data):
|
|
310
|
+
raw_data = self.integration.fetch_compliance_data()
|
|
311
|
+
|
|
312
|
+
assert len(raw_data) == 2 # Both items should pass filtering
|
|
313
|
+
assert all(isinstance(item, dict) for item in raw_data)
|
|
314
|
+
assert raw_data[0]["result"] == "PASS"
|
|
315
|
+
assert raw_data[1]["result"] == "FAIL"
|
|
316
|
+
|
|
317
|
+
@patch("builtins.open", new_callable=mock_open)
|
|
318
|
+
@patch("os.makedirs")
|
|
319
|
+
@patch("regscale.integrations.commercial.wizv2.policy_compliance.datetime")
|
|
320
|
+
def test_write_policy_data_to_json(self, mock_datetime, mock_makedirs, mock_file):
|
|
321
|
+
"""Test writing policy data to JSON file."""
|
|
322
|
+
# Set up mock datetime
|
|
323
|
+
mock_datetime.now.return_value.strftime.return_value = "20230806_120000"
|
|
324
|
+
|
|
325
|
+
# Set up mock compliance data
|
|
326
|
+
self.integration.all_compliance_items = [
|
|
327
|
+
WizComplianceItem(
|
|
328
|
+
{
|
|
329
|
+
"id": "test-1",
|
|
330
|
+
"result": "PASS",
|
|
331
|
+
"policy": {"name": "Test Policy"},
|
|
332
|
+
"resource": {"id": "res-1", "name": "Resource 1"},
|
|
333
|
+
"output": {},
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
]
|
|
337
|
+
self.integration.failed_compliance_items = []
|
|
338
|
+
self.integration.framework_mapping = {"wf-id-4": "NIST SP 800-53 Revision 5"}
|
|
339
|
+
|
|
340
|
+
file_path = self.integration.write_policy_data_to_json()
|
|
341
|
+
|
|
342
|
+
# Verify file path
|
|
343
|
+
expected_path = os.path.join("artifacts", "wiz", "policy_compliance_report_20230806_120000.json")
|
|
344
|
+
assert file_path == expected_path
|
|
345
|
+
|
|
346
|
+
# Verify directory creation
|
|
347
|
+
mock_makedirs.assert_called_once_with(os.path.join("artifacts", "wiz"), exist_ok=True)
|
|
348
|
+
|
|
349
|
+
# Verify file write
|
|
350
|
+
mock_file.assert_called_once_with(expected_path, "w", encoding="utf-8")
|
|
351
|
+
|
|
352
|
+
# Verify JSON content was written
|
|
353
|
+
handle = mock_file()
|
|
354
|
+
assert handle.write.called
|
|
355
|
+
|
|
356
|
+
@patch("os.path.exists")
|
|
357
|
+
@patch("builtins.open", new_callable=mock_open)
|
|
358
|
+
def test_load_framework_mapping_from_cache(self, mock_file, mock_exists):
|
|
359
|
+
"""Test loading framework mapping from cache file."""
|
|
360
|
+
mock_exists.return_value = True
|
|
361
|
+
|
|
362
|
+
cache_data = {
|
|
363
|
+
"framework_mapping": {"wf-id-4": "NIST SP 800-53 Revision 5", "wf-id-5": "FedRAMP"},
|
|
364
|
+
"timestamp": "2023-08-06T12:00:00",
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
mock_file.return_value.read.return_value = json.dumps(cache_data)
|
|
368
|
+
|
|
369
|
+
mapping = self.integration.load_or_create_framework_mapping()
|
|
370
|
+
|
|
371
|
+
assert mapping == cache_data["framework_mapping"]
|
|
372
|
+
assert self.integration.framework_mapping == mapping
|
|
373
|
+
mock_file.assert_called_once()
|
|
374
|
+
|
|
375
|
+
@patch("os.path.exists")
|
|
376
|
+
@patch("regscale.integrations.commercial.wizv2.policy_compliance.run_async_queries")
|
|
377
|
+
@patch("builtins.open", new_callable=mock_open)
|
|
378
|
+
@patch("os.makedirs")
|
|
379
|
+
def test_fetch_and_cache_framework_mapping(self, mock_makedirs, mock_file, mock_run_async_queries, mock_exists):
|
|
380
|
+
"""Test fetching and caching framework mapping."""
|
|
381
|
+
mock_exists.return_value = False # No cache file
|
|
382
|
+
|
|
383
|
+
# Mock framework data from API
|
|
384
|
+
mock_frameworks = [
|
|
385
|
+
{"id": "wf-id-4", "name": "NIST SP 800-53 Revision 5"},
|
|
386
|
+
{"id": "wf-id-5", "name": "FedRAMP"},
|
|
387
|
+
]
|
|
388
|
+
mock_run_async_queries.return_value = [("configuration", mock_frameworks, None)]
|
|
389
|
+
|
|
390
|
+
mapping = self.integration.load_or_create_framework_mapping()
|
|
391
|
+
|
|
392
|
+
expected_mapping = {"wf-id-4": "NIST SP 800-53 Revision 5", "wf-id-5": "FedRAMP"}
|
|
393
|
+
|
|
394
|
+
assert mapping == expected_mapping
|
|
395
|
+
assert self.integration.framework_mapping == mapping
|
|
396
|
+
|
|
397
|
+
# Verify cache file was written
|
|
398
|
+
mock_file.assert_called()
|
|
399
|
+
mock_makedirs.assert_called()
|
|
400
|
+
|
|
401
|
+
def test_get_framework_name(self):
|
|
402
|
+
"""Test getting framework name by ID."""
|
|
403
|
+
self.integration.framework_mapping = {"wf-id-4": "NIST SP 800-53 Revision 5", "wf-id-5": "FedRAMP"}
|
|
404
|
+
|
|
405
|
+
assert self.integration.get_framework_name("wf-id-4") == "NIST SP 800-53 Revision 5"
|
|
406
|
+
assert self.integration.get_framework_name("wf-id-5") == "FedRAMP"
|
|
407
|
+
assert self.integration.get_framework_name("unknown-id") == "unknown-id"
|
|
408
|
+
|
|
409
|
+
def test_create_finding_from_compliance_item_wiz_specific(self):
|
|
410
|
+
"""Test creating a finding from Wiz compliance item with enhanced metadata."""
|
|
411
|
+
mock_data = {
|
|
412
|
+
"id": "assessment-123",
|
|
413
|
+
"result": "FAIL",
|
|
414
|
+
"policy": {
|
|
415
|
+
"name": "Test Policy",
|
|
416
|
+
"description": "Test policy description",
|
|
417
|
+
"severity": "HIGH",
|
|
418
|
+
"remediationInstructions": "Fix the issue",
|
|
419
|
+
},
|
|
420
|
+
"resource": {
|
|
421
|
+
"id": "resource-789",
|
|
422
|
+
"name": "Test Resource",
|
|
423
|
+
"type": "VIRTUAL_MACHINE",
|
|
424
|
+
"region": "us-east-1",
|
|
425
|
+
"subscription": {"cloudProvider": "Azure", "name": "Test Subscription"},
|
|
426
|
+
},
|
|
427
|
+
"output": {},
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
compliance_item = WizComplianceItem(mock_data, self.integration)
|
|
431
|
+
finding = self.integration.create_finding_from_compliance_item(compliance_item)
|
|
432
|
+
|
|
433
|
+
assert finding is not None
|
|
434
|
+
assert finding.title == "Policy Compliance Failure: Test Policy"
|
|
435
|
+
assert finding.external_id == "wiz-policy-assessment-123"
|
|
436
|
+
assert finding.asset_identifier == "resource-789" # Should use Wiz resource ID for wizId lookup
|
|
437
|
+
assert "Fix the issue" in finding.description
|
|
438
|
+
assert "Azure" in finding.description
|
|
439
|
+
|
|
440
|
+
def test_create_asset_from_compliance_item_wiz_specific(self):
|
|
441
|
+
"""Test creating an asset from Wiz compliance item with enhanced metadata."""
|
|
442
|
+
mock_data = {
|
|
443
|
+
"id": "assessment-123",
|
|
444
|
+
"result": "FAIL",
|
|
445
|
+
"policy": {"name": "Test Policy"},
|
|
446
|
+
"resource": {
|
|
447
|
+
"id": "resource-789",
|
|
448
|
+
"name": "Test Resource",
|
|
449
|
+
"type": "VIRTUAL_MACHINE",
|
|
450
|
+
"region": "us-east-1",
|
|
451
|
+
"subscription": {"cloudProvider": "Azure", "name": "Test Subscription", "externalId": "sub-123"},
|
|
452
|
+
"tags": [{"key": "Environment", "value": "Production"}, {"key": "Owner", "value": "TestTeam"}],
|
|
453
|
+
},
|
|
454
|
+
"output": {},
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
compliance_item = WizComplianceItem(mock_data, self.integration)
|
|
458
|
+
|
|
459
|
+
# Mock compliance mapping for notes
|
|
460
|
+
self.integration.asset_compliance_map = {"resource-789": [compliance_item]}
|
|
461
|
+
self.integration.FAIL_STATUSES = ["FAIL"]
|
|
462
|
+
|
|
463
|
+
with patch.object(self.integration, "_find_existing_asset_by_resource_id", return_value=None):
|
|
464
|
+
asset = self.integration.create_asset_from_compliance_item(compliance_item)
|
|
465
|
+
|
|
466
|
+
assert asset is not None
|
|
467
|
+
assert asset.name == "Test Resource"
|
|
468
|
+
assert asset.identifier == "Test Resource (resource-789)"
|
|
469
|
+
assert asset.asset_type == "Virtual Machine"
|
|
470
|
+
assert "Environment:Production" in asset.description
|
|
471
|
+
assert "Owner:TestTeam" in asset.description
|
|
472
|
+
assert "Azure" in asset.notes
|
|
473
|
+
assert "Test Subscription" in asset.notes
|
|
474
|
+
|
|
475
|
+
def test_create_asset_notes(self):
|
|
476
|
+
"""Test creating detailed asset notes."""
|
|
477
|
+
mock_data = {
|
|
478
|
+
"id": "assessment-123",
|
|
479
|
+
"result": "FAIL",
|
|
480
|
+
"policy": {"name": "Test Policy"},
|
|
481
|
+
"resource": {
|
|
482
|
+
"id": "resource-789",
|
|
483
|
+
"name": "Test Resource",
|
|
484
|
+
"type": "VIRTUAL_MACHINE",
|
|
485
|
+
"subscription": {"cloudProvider": "Azure", "name": "Test Subscription", "externalId": "sub-123"},
|
|
486
|
+
},
|
|
487
|
+
"output": {},
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
compliance_item = WizComplianceItem(mock_data, self.integration)
|
|
491
|
+
|
|
492
|
+
# Set up asset compliance map
|
|
493
|
+
self.integration.asset_compliance_map = {"resource-789": [compliance_item, compliance_item]} # 2 assessments
|
|
494
|
+
self.integration.FAIL_STATUSES = ["FAIL"]
|
|
495
|
+
|
|
496
|
+
notes = self.integration._create_asset_notes(compliance_item)
|
|
497
|
+
|
|
498
|
+
assert "Wiz Asset Details" in notes
|
|
499
|
+
assert "resource-789" in notes
|
|
500
|
+
assert "VIRTUAL_MACHINE" in notes
|
|
501
|
+
assert "Azure" in notes
|
|
502
|
+
assert "Test Subscription" in notes
|
|
503
|
+
assert "sub-123" in notes
|
|
504
|
+
assert "**Total Assessments:** 2" in notes
|
|
505
|
+
assert "**Failed Assessments:** 2" in notes
|
|
506
|
+
assert "**Compliance Rate:** 0.0%" in notes
|
|
507
|
+
|
|
508
|
+
def test_fetch_assets_no_assets_created(self):
|
|
509
|
+
"""Test that fetch_assets does not create any assets - they come from inventory import."""
|
|
510
|
+
# Create compliance items
|
|
511
|
+
failed_data = {
|
|
512
|
+
"id": "assessment-fail",
|
|
513
|
+
"result": "FAIL",
|
|
514
|
+
"policy": {
|
|
515
|
+
"name": "Fail Policy",
|
|
516
|
+
"securitySubCategories": [
|
|
517
|
+
{
|
|
518
|
+
"externalId": "AC-2",
|
|
519
|
+
"category": {"framework": {"id": "wf-id-4", "name": "NIST SP 800-53 Revision 5"}},
|
|
520
|
+
}
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
"resource": {"id": "resource-fail", "name": "Failed Resource", "type": "VIRTUAL_MACHINE"},
|
|
524
|
+
"output": {},
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
failed_item = WizComplianceItem(failed_data, self.integration)
|
|
528
|
+
self.integration.failed_compliance_items = [failed_item]
|
|
529
|
+
|
|
530
|
+
# Call fetch_assets - should return empty iterator
|
|
531
|
+
assets = list(self.integration.fetch_assets())
|
|
532
|
+
|
|
533
|
+
# Should not create any assets
|
|
534
|
+
assert len(assets) == 0
|
|
535
|
+
|
|
536
|
+
@patch.object(WizPolicyComplianceIntegration, "_asset_exists_in_regscale")
|
|
537
|
+
def test_framework_filtering_in_findings(self, mock_asset_exists):
|
|
538
|
+
"""Test that fetch_findings filters by framework and uses asset names."""
|
|
539
|
+
# Mock that the framework match resource exists in RegScale (using resource ID)
|
|
540
|
+
mock_asset_exists.side_effect = lambda resource_id: resource_id == "resource-framework-match"
|
|
541
|
+
|
|
542
|
+
# Create compliance items in different frameworks
|
|
543
|
+
framework_data = {
|
|
544
|
+
"id": "assessment-framework-match",
|
|
545
|
+
"result": "FAIL",
|
|
546
|
+
"policy": {
|
|
547
|
+
"name": "Framework Match Policy",
|
|
548
|
+
"securitySubCategories": [
|
|
549
|
+
{
|
|
550
|
+
"externalId": "AC-2",
|
|
551
|
+
"category": {"framework": {"id": "wf-id-4", "name": "NIST SP 800-53 Revision 5"}},
|
|
552
|
+
}
|
|
553
|
+
],
|
|
554
|
+
},
|
|
555
|
+
"resource": {
|
|
556
|
+
"id": "resource-framework-match",
|
|
557
|
+
"name": "Framework Match Resource",
|
|
558
|
+
"type": "VIRTUAL_MACHINE",
|
|
559
|
+
},
|
|
560
|
+
"output": {},
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
other_framework_data = {
|
|
564
|
+
"id": "assessment-other-framework",
|
|
565
|
+
"result": "FAIL",
|
|
566
|
+
"policy": {
|
|
567
|
+
"name": "Other Framework Policy",
|
|
568
|
+
"securitySubCategories": [
|
|
569
|
+
{
|
|
570
|
+
"externalId": "AC-3",
|
|
571
|
+
"category": {"framework": {"id": "wf-id-5", "name": "Other Framework"}},
|
|
572
|
+
}
|
|
573
|
+
],
|
|
574
|
+
},
|
|
575
|
+
"resource": {
|
|
576
|
+
"id": "resource-other-framework",
|
|
577
|
+
"name": "Other Framework Resource",
|
|
578
|
+
"type": "VIRTUAL_MACHINE",
|
|
579
|
+
},
|
|
580
|
+
"output": {},
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
framework_item = WizComplianceItem(framework_data, self.integration)
|
|
584
|
+
other_framework_item = WizComplianceItem(other_framework_data, self.integration)
|
|
585
|
+
|
|
586
|
+
# Mock integration data - both items are "failed" but only one belongs to the target framework
|
|
587
|
+
self.integration.all_compliance_items = [framework_item, other_framework_item]
|
|
588
|
+
self.integration.failed_compliance_items = [framework_item, other_framework_item]
|
|
589
|
+
self.integration.FAIL_STATUSES = ["FAIL"]
|
|
590
|
+
|
|
591
|
+
# Test that only framework-specific items have valid control IDs
|
|
592
|
+
assert framework_item.control_id == "AC-2" # Should match framework
|
|
593
|
+
assert other_framework_item.control_id == "" # Should be empty due to framework filtering
|
|
594
|
+
|
|
595
|
+
# Test fetch_assets - should return empty
|
|
596
|
+
assets = list(self.integration.fetch_assets())
|
|
597
|
+
assert len(assets) == 0 # No assets created
|
|
598
|
+
|
|
599
|
+
# Test fetch_findings
|
|
600
|
+
findings = list(self.integration.fetch_findings())
|
|
601
|
+
# Should only create finding for framework-matching item that has existing asset
|
|
602
|
+
assert len(findings) == 1
|
|
603
|
+
finding = findings[0]
|
|
604
|
+
assert finding.asset_identifier == "resource-framework-match" # Should use Wiz resource ID
|
|
605
|
+
|
|
606
|
+
@patch.object(WizPolicyComplianceIntegration, "authenticate_wiz")
|
|
607
|
+
@patch.object(WizPolicyComplianceIntegration, "load_or_create_framework_mapping")
|
|
608
|
+
@patch.object(WizPolicyComplianceIntegration, "write_policy_data_to_json")
|
|
609
|
+
@patch.object(WizPolicyComplianceIntegration, "sync_compliance")
|
|
610
|
+
def test_sync_policy_compliance(self, mock_sync_compliance, mock_write_json, mock_load_mapping, mock_auth):
|
|
611
|
+
"""Test the main sync policy compliance method."""
|
|
612
|
+
mock_write_json.return_value = "/path/to/output.json"
|
|
613
|
+
|
|
614
|
+
self.integration.sync_policy_compliance()
|
|
615
|
+
|
|
616
|
+
# Verify all steps were called in correct order
|
|
617
|
+
mock_auth.assert_called_once()
|
|
618
|
+
mock_load_mapping.assert_called_once()
|
|
619
|
+
# Note: process_compliance_data is called internally by sync_compliance, not directly
|
|
620
|
+
mock_sync_compliance.assert_called_once()
|
|
621
|
+
mock_write_json.assert_called_once()
|
|
622
|
+
|
|
623
|
+
def test_sync_wiz_compliance_backward_compatibility(self):
|
|
624
|
+
"""Test backward compatibility method."""
|
|
625
|
+
with patch.object(self.integration, "sync_policy_compliance") as mock_sync:
|
|
626
|
+
self.integration.sync_wiz_compliance()
|
|
627
|
+
mock_sync.assert_called_once()
|
|
628
|
+
|
|
629
|
+
@patch.object(WizPolicyComplianceIntegration, "_asset_exists_in_regscale")
|
|
630
|
+
@patch.object(WizPolicyComplianceIntegration, "_get_control_implementations")
|
|
631
|
+
@patch.object(WizPolicyComplianceIntegration, "_load_existing_records_cache")
|
|
632
|
+
@patch.object(WizPolicyComplianceIntegration, "_process_single_control_assessment")
|
|
633
|
+
def test_control_assessments_only_for_existing_assets(
|
|
634
|
+
self, mock_process_single, mock_load_cache, mock_get_implementations, mock_asset_exists
|
|
635
|
+
):
|
|
636
|
+
"""Test that control assessments are only created for controls with existing assets."""
|
|
637
|
+
from regscale.integrations.compliance_integration import ControlImplementation
|
|
638
|
+
from regscale.models.regscale_models import SecurityControl
|
|
639
|
+
|
|
640
|
+
# Mock that only one asset exists (for AC-2 control)
|
|
641
|
+
mock_asset_exists.side_effect = lambda resource_id: resource_id == "resource-with-asset"
|
|
642
|
+
|
|
643
|
+
# Mock single control assessment creation to return 1 (one assessment created)
|
|
644
|
+
mock_process_single.return_value = 1
|
|
645
|
+
|
|
646
|
+
# Mock control implementations
|
|
647
|
+
mock_impl1 = ControlImplementation(id=1, controlID=100, status="Not Satisfied", title="Test Implementation 1")
|
|
648
|
+
mock_impl2 = ControlImplementation(id=2, controlID=200, status="Not Satisfied", title="Test Implementation 2")
|
|
649
|
+
mock_get_implementations.return_value = [mock_impl1, mock_impl2]
|
|
650
|
+
|
|
651
|
+
# Mock SecurityControl.get_object to return controls
|
|
652
|
+
def mock_get_security_control(object_id):
|
|
653
|
+
if object_id == 100:
|
|
654
|
+
control = SecurityControl()
|
|
655
|
+
control.id = 100
|
|
656
|
+
control.controlId = "AC-2"
|
|
657
|
+
return control
|
|
658
|
+
elif object_id == 200:
|
|
659
|
+
control = SecurityControl()
|
|
660
|
+
control.id = 200
|
|
661
|
+
control.controlId = "AC-3"
|
|
662
|
+
return control
|
|
663
|
+
return None
|
|
664
|
+
|
|
665
|
+
# Create compliance items - one with existing asset, one without
|
|
666
|
+
compliance_data = [
|
|
667
|
+
{
|
|
668
|
+
"id": "assessment-with-asset",
|
|
669
|
+
"result": "FAIL",
|
|
670
|
+
"policy": {
|
|
671
|
+
"name": "Policy with Asset",
|
|
672
|
+
"securitySubCategories": [
|
|
673
|
+
{
|
|
674
|
+
"externalId": "AC-2",
|
|
675
|
+
"category": {"framework": {"id": "wf-id-4", "name": "NIST SP 800-53 Revision 5"}},
|
|
676
|
+
}
|
|
677
|
+
],
|
|
678
|
+
},
|
|
679
|
+
"resource": {"id": "resource-with-asset", "name": "Resource With Asset"},
|
|
680
|
+
"output": {},
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
"id": "assessment-no-asset",
|
|
684
|
+
"result": "FAIL",
|
|
685
|
+
"policy": {
|
|
686
|
+
"name": "Policy No Asset",
|
|
687
|
+
"securitySubCategories": [
|
|
688
|
+
{
|
|
689
|
+
"externalId": "AC-3",
|
|
690
|
+
"category": {"framework": {"id": "wf-id-4", "name": "NIST SP 800-53 Revision 5"}},
|
|
691
|
+
}
|
|
692
|
+
],
|
|
693
|
+
},
|
|
694
|
+
"resource": {"id": "resource-no-asset", "name": "Resource No Asset"},
|
|
695
|
+
"output": {},
|
|
696
|
+
},
|
|
697
|
+
]
|
|
698
|
+
|
|
699
|
+
# Process the compliance data
|
|
700
|
+
with patch.object(SecurityControl, "get_object", side_effect=mock_get_security_control):
|
|
701
|
+
with patch.object(self.integration, "_fetch_policy_assessments_from_wiz", return_value=compliance_data):
|
|
702
|
+
self.integration.process_compliance_data()
|
|
703
|
+
self.integration._process_control_assessments()
|
|
704
|
+
|
|
705
|
+
# Verify only one control assessment was processed (AC-2 with existing asset)
|
|
706
|
+
# AC-3 should be skipped because its asset doesn't exist
|
|
707
|
+
mock_process_single.assert_called_once_with(
|
|
708
|
+
control_id="ac-2",
|
|
709
|
+
implementations=[mock_impl1, mock_impl2],
|
|
710
|
+
processed_impl_today=set(),
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
class TestWizComplianceItemEdgeCases:
|
|
715
|
+
"""Test edge cases for WizComplianceItem."""
|
|
716
|
+
|
|
717
|
+
def test_empty_data(self):
|
|
718
|
+
"""Test WizComplianceItem with minimal data."""
|
|
719
|
+
minimal_data = {"id": "", "result": "", "policy": {}, "resource": {}, "output": {}}
|
|
720
|
+
|
|
721
|
+
item = WizComplianceItem(minimal_data)
|
|
722
|
+
|
|
723
|
+
assert item.id == ""
|
|
724
|
+
assert item.result == ""
|
|
725
|
+
assert item.resource_id == ""
|
|
726
|
+
assert item.resource_name == ""
|
|
727
|
+
assert item.control_id == ""
|
|
728
|
+
assert item.severity is None
|
|
729
|
+
assert item.framework == ""
|
|
730
|
+
assert not item.is_pass
|
|
731
|
+
assert not item.is_fail
|
|
732
|
+
|
|
733
|
+
def test_nested_missing_data(self):
|
|
734
|
+
"""Test WizComplianceItem with nested missing data."""
|
|
735
|
+
data_with_missing_nested = {
|
|
736
|
+
"id": "test",
|
|
737
|
+
"result": "PASS",
|
|
738
|
+
"policy": {"securitySubCategories": [{"category": {"framework": {}}}]}, # Missing id and name
|
|
739
|
+
"resource": {"subscription": {}}, # Missing cloudProvider
|
|
740
|
+
"output": {},
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
item = WizComplianceItem(data_with_missing_nested)
|
|
744
|
+
|
|
745
|
+
assert item.framework_id is None
|
|
746
|
+
assert item.framework == ""
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
if __name__ == "__main__":
|
|
750
|
+
pytest.main([__file__])
|