regscale-cli 6.26.0.0__py3-none-any.whl → 6.27.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/application.py +1 -1
- regscale/core/app/internal/evidence.py +419 -2
- regscale/dev/code_gen.py +24 -20
- regscale/integrations/commercial/jira.py +367 -126
- regscale/integrations/commercial/qualys/__init__.py +7 -8
- regscale/integrations/commercial/qualys/scanner.py +8 -3
- regscale/integrations/commercial/synqly/assets.py +17 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +45 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +142 -1
- regscale/integrations/commercial/tenablev2/scanner.py +0 -1
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +44 -59
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +10 -9
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +3 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +1 -17
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +5 -9
- regscale/integrations/commercial/wizv2/issue.py +1 -1
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +1 -1
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +40 -100
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +0 -46
- regscale/integrations/control_matcher.py +22 -3
- regscale/integrations/due_date_handler.py +14 -8
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +393 -340
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/scanner_integration.py +127 -57
- regscale/models/integration_models/cisa_kev_data.json +132 -9
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +24 -7
- regscale/models/integration_models/synqly_models/synqly_model.py +8 -1
- regscale/models/regscale_models/control_implementation.py +1 -1
- regscale/models/regscale_models/issue.py +0 -1
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +93 -60
- tests/regscale/integrations/commercial/test_jira.py +481 -91
- tests/regscale/integrations/commercial/test_wiz.py +96 -200
- tests/regscale/integrations/commercial/wizv2/__init__.py +1 -1
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +1 -1
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +1 -1
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +72 -29
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +946 -78
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +97 -202
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/test_fedramp.py +301 -0
- tests/regscale/integrations/test_control_matcher.py +83 -0
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
- tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +0 -750
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for WizSbomIntegration
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
import unittest
|
|
9
|
+
from unittest.mock import patch, MagicMock, mock_open
|
|
10
|
+
|
|
11
|
+
from regscale.integrations.commercial.wizv2.sbom import WizSbomIntegration
|
|
12
|
+
from regscale.models.regscale_models.sbom import Sbom
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("regscale")
|
|
15
|
+
|
|
16
|
+
PATH = "regscale.integrations.commercial.wizv2.sbom"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestWizSbomIntegration(unittest.TestCase):
|
|
20
|
+
"""Test cases for WizSbomIntegration class"""
|
|
21
|
+
|
|
22
|
+
def setUp(self):
|
|
23
|
+
"""Set up test fixtures"""
|
|
24
|
+
self.wiz_project_id = "test-project-123"
|
|
25
|
+
self.regscale_id = 100
|
|
26
|
+
self.regscale_module = "securityPlans"
|
|
27
|
+
self.client_id = "test-client-id"
|
|
28
|
+
self.client_secret = "test-client-secret"
|
|
29
|
+
self.filter_by_override = None
|
|
30
|
+
|
|
31
|
+
# Sample SBOM data from Wiz
|
|
32
|
+
self.sample_sbom_nodes = [
|
|
33
|
+
{
|
|
34
|
+
"id": "sbom-1",
|
|
35
|
+
"name": "express",
|
|
36
|
+
"type": {
|
|
37
|
+
"group": "npm",
|
|
38
|
+
"codeLibraryLanguage": "JavaScript",
|
|
39
|
+
"osPackageManager": None,
|
|
40
|
+
"hostedTechnology": None,
|
|
41
|
+
"plugin": None,
|
|
42
|
+
},
|
|
43
|
+
"versions": {
|
|
44
|
+
"nodes": [
|
|
45
|
+
{"version": "4.18.0"},
|
|
46
|
+
{"version": "4.18.1"},
|
|
47
|
+
{"version": "4.18.2"},
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "sbom-2",
|
|
53
|
+
"name": "lodash",
|
|
54
|
+
"type": {
|
|
55
|
+
"group": "npm",
|
|
56
|
+
"codeLibraryLanguage": "JavaScript",
|
|
57
|
+
"osPackageManager": None,
|
|
58
|
+
"hostedTechnology": None,
|
|
59
|
+
"plugin": None,
|
|
60
|
+
},
|
|
61
|
+
"versions": {
|
|
62
|
+
"nodes": [
|
|
63
|
+
{"version": "4.17.21"},
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "sbom-3",
|
|
69
|
+
"name": "pytest",
|
|
70
|
+
"type": {
|
|
71
|
+
"group": "pip",
|
|
72
|
+
"codeLibraryLanguage": "Python",
|
|
73
|
+
"osPackageManager": None,
|
|
74
|
+
"hostedTechnology": None,
|
|
75
|
+
"plugin": None,
|
|
76
|
+
},
|
|
77
|
+
"versions": {"nodes": []},
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
82
|
+
def test_init_with_project_id_from_parameter(self, mock_wiz_mixin_init):
|
|
83
|
+
"""Test initialization with project ID passed as parameter"""
|
|
84
|
+
mock_wiz_mixin_init.return_value = None
|
|
85
|
+
|
|
86
|
+
# Mock the config attribute on the instance
|
|
87
|
+
mock_config = {"wizProjectId": "config-project-id"}
|
|
88
|
+
|
|
89
|
+
integration = WizSbomIntegration(
|
|
90
|
+
wiz_project_id=self.wiz_project_id,
|
|
91
|
+
regscale_id=self.regscale_id,
|
|
92
|
+
regscale_module=self.regscale_module,
|
|
93
|
+
filter_by_override=None,
|
|
94
|
+
client_id=self.client_id,
|
|
95
|
+
client_secret=self.client_secret,
|
|
96
|
+
)
|
|
97
|
+
integration.config = mock_config
|
|
98
|
+
|
|
99
|
+
self.assertEqual(integration.wiz_project_id, self.wiz_project_id)
|
|
100
|
+
self.assertEqual(integration.regscale_id, self.regscale_id)
|
|
101
|
+
self.assertEqual(integration.regscale_module, self.regscale_module)
|
|
102
|
+
self.assertEqual(integration.filter_by, None)
|
|
103
|
+
self.assertEqual(integration.topic_key, "sbomArtifactsGroupedByName")
|
|
104
|
+
self.assertIsInstance(integration.sbom_list, list)
|
|
105
|
+
self.assertEqual(len(integration.sbom_list), 0)
|
|
106
|
+
|
|
107
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
108
|
+
def test_init_with_project_id_from_config(self, mock_wiz_mixin_init):
|
|
109
|
+
"""Test initialization with project ID from config when parameter is None"""
|
|
110
|
+
mock_wiz_mixin_init.return_value = None
|
|
111
|
+
|
|
112
|
+
# Create the instance first and manually set config before checking wiz_project_id
|
|
113
|
+
integration = WizSbomIntegration.__new__(WizSbomIntegration)
|
|
114
|
+
integration.config = {"wizProjectId": "config-project-id"}
|
|
115
|
+
|
|
116
|
+
# Now call __init__ with None for wiz_project_id
|
|
117
|
+
WizSbomIntegration.__init__(
|
|
118
|
+
integration,
|
|
119
|
+
wiz_project_id=None,
|
|
120
|
+
regscale_id=self.regscale_id,
|
|
121
|
+
regscale_module=self.regscale_module,
|
|
122
|
+
filter_by_override=None,
|
|
123
|
+
client_id=self.client_id,
|
|
124
|
+
client_secret=self.client_secret,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Should pick up project ID from config
|
|
128
|
+
self.assertEqual(integration.wiz_project_id, "config-project-id")
|
|
129
|
+
|
|
130
|
+
@patch(f"{PATH}.error_and_exit")
|
|
131
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
132
|
+
def test_init_without_project_id_exits(self, mock_wiz_mixin_init, mock_error_exit):
|
|
133
|
+
"""Test initialization without project ID calls error_and_exit"""
|
|
134
|
+
mock_wiz_mixin_init.return_value = None
|
|
135
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
136
|
+
|
|
137
|
+
# Create an instance and manually set config to empty dict
|
|
138
|
+
with self.assertRaises(SystemExit):
|
|
139
|
+
integration = WizSbomIntegration.__new__(WizSbomIntegration)
|
|
140
|
+
integration.config = {}
|
|
141
|
+
# Call __init__ manually to trigger the error check
|
|
142
|
+
WizSbomIntegration.__init__(
|
|
143
|
+
integration,
|
|
144
|
+
wiz_project_id=None,
|
|
145
|
+
regscale_id=self.regscale_id,
|
|
146
|
+
regscale_module=self.regscale_module,
|
|
147
|
+
filter_by_override=None,
|
|
148
|
+
client_id=self.client_id,
|
|
149
|
+
client_secret=self.client_secret,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
mock_error_exit.assert_called_once_with("Wiz project ID not provided")
|
|
153
|
+
|
|
154
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
155
|
+
def test_init_with_filter_by_override(self, mock_wiz_mixin_init):
|
|
156
|
+
"""Test initialization with filterBy override"""
|
|
157
|
+
mock_wiz_mixin_init.return_value = None
|
|
158
|
+
filter_by_json = '{"project": "override-project", "custom": "filter"}'
|
|
159
|
+
|
|
160
|
+
# Create the instance first and manually set config
|
|
161
|
+
integration = WizSbomIntegration.__new__(WizSbomIntegration)
|
|
162
|
+
integration.config = {"wizProjectId": self.wiz_project_id}
|
|
163
|
+
|
|
164
|
+
# Now call __init__ with filter_by_override
|
|
165
|
+
WizSbomIntegration.__init__(
|
|
166
|
+
integration,
|
|
167
|
+
wiz_project_id=self.wiz_project_id,
|
|
168
|
+
regscale_id=self.regscale_id,
|
|
169
|
+
regscale_module=self.regscale_module,
|
|
170
|
+
filter_by_override=filter_by_json,
|
|
171
|
+
client_id=self.client_id,
|
|
172
|
+
client_secret=self.client_secret,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
expected_filter = {"project": "override-project", "custom": "filter"}
|
|
176
|
+
self.assertEqual(integration.filter_by, expected_filter)
|
|
177
|
+
# Verify variables use the override filter
|
|
178
|
+
self.assertEqual(integration.variables["filterBy"], expected_filter)
|
|
179
|
+
|
|
180
|
+
@patch(f"{PATH}.Sbom.get_all_by_parent")
|
|
181
|
+
@patch(f"{PATH}.WizSbomIntegration.fetch_sbom_data")
|
|
182
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
183
|
+
def test_run_with_no_sbom_data(self, mock_wiz_mixin_init, mock_fetch_sbom, mock_get_all):
|
|
184
|
+
"""Test run method when no SBOM data is found"""
|
|
185
|
+
mock_wiz_mixin_init.return_value = None
|
|
186
|
+
|
|
187
|
+
with patch.object(WizSbomIntegration, "config", {"wizProjectId": self.wiz_project_id}):
|
|
188
|
+
integration = WizSbomIntegration(
|
|
189
|
+
wiz_project_id=self.wiz_project_id,
|
|
190
|
+
regscale_id=self.regscale_id,
|
|
191
|
+
regscale_module=self.regscale_module,
|
|
192
|
+
filter_by_override=None,
|
|
193
|
+
client_id=self.client_id,
|
|
194
|
+
client_secret=self.client_secret,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Mock fetch_sbom_data to set empty list
|
|
198
|
+
def set_empty_list():
|
|
199
|
+
integration.sbom_list = []
|
|
200
|
+
|
|
201
|
+
mock_fetch_sbom.side_effect = set_empty_list
|
|
202
|
+
|
|
203
|
+
with self.assertRaises(SystemExit) as cm:
|
|
204
|
+
integration.run()
|
|
205
|
+
|
|
206
|
+
self.assertEqual(cm.exception.code, 0)
|
|
207
|
+
mock_fetch_sbom.assert_called_once()
|
|
208
|
+
mock_get_all.assert_not_called()
|
|
209
|
+
|
|
210
|
+
@patch(f"{PATH}.Sbom.get_all_by_parent")
|
|
211
|
+
@patch(f"{PATH}.WizSbomIntegration.fetch_sbom_data")
|
|
212
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
213
|
+
def test_run_creates_new_sboms(self, mock_wiz_mixin_init, mock_fetch_sbom, mock_get_all):
|
|
214
|
+
"""Test run method creates new SBOMs when they don't exist"""
|
|
215
|
+
mock_wiz_mixin_init.return_value = None
|
|
216
|
+
|
|
217
|
+
with patch.object(WizSbomIntegration, "config", {"wizProjectId": self.wiz_project_id}):
|
|
218
|
+
integration = WizSbomIntegration(
|
|
219
|
+
wiz_project_id=self.wiz_project_id,
|
|
220
|
+
regscale_id=self.regscale_id,
|
|
221
|
+
regscale_module=self.regscale_module,
|
|
222
|
+
filter_by_override=None,
|
|
223
|
+
client_id=self.client_id,
|
|
224
|
+
client_secret=self.client_secret,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Create mock SBOM objects
|
|
228
|
+
mock_sbom1 = MagicMock(spec=Sbom)
|
|
229
|
+
mock_sbom1.name = "express"
|
|
230
|
+
mock_sbom1.create = MagicMock()
|
|
231
|
+
|
|
232
|
+
mock_sbom2 = MagicMock(spec=Sbom)
|
|
233
|
+
mock_sbom2.name = "lodash"
|
|
234
|
+
mock_sbom2.create = MagicMock()
|
|
235
|
+
|
|
236
|
+
# Mock fetch_sbom_data to populate sbom_list
|
|
237
|
+
def populate_sbom_list():
|
|
238
|
+
integration.sbom_list = [mock_sbom1, mock_sbom2]
|
|
239
|
+
|
|
240
|
+
mock_fetch_sbom.side_effect = populate_sbom_list
|
|
241
|
+
|
|
242
|
+
# Mock get_all_by_parent to return empty list (no existing SBOMs)
|
|
243
|
+
mock_get_all.return_value = []
|
|
244
|
+
|
|
245
|
+
integration.run()
|
|
246
|
+
|
|
247
|
+
mock_fetch_sbom.assert_called_once()
|
|
248
|
+
mock_get_all.assert_called_once_with(parent_id=self.regscale_id, parent_module=self.regscale_module)
|
|
249
|
+
mock_sbom1.create.assert_called_once()
|
|
250
|
+
mock_sbom2.create.assert_called_once()
|
|
251
|
+
|
|
252
|
+
@patch(f"{PATH}.Sbom.get_all_by_parent")
|
|
253
|
+
@patch(f"{PATH}.WizSbomIntegration.fetch_sbom_data")
|
|
254
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
255
|
+
def test_run_skips_existing_sboms(self, mock_wiz_mixin_init, mock_fetch_sbom, mock_get_all):
|
|
256
|
+
"""Test run method skips SBOMs that already exist"""
|
|
257
|
+
mock_wiz_mixin_init.return_value = None
|
|
258
|
+
|
|
259
|
+
with patch.object(WizSbomIntegration, "config", {"wizProjectId": self.wiz_project_id}):
|
|
260
|
+
integration = WizSbomIntegration(
|
|
261
|
+
wiz_project_id=self.wiz_project_id,
|
|
262
|
+
regscale_id=self.regscale_id,
|
|
263
|
+
regscale_module=self.regscale_module,
|
|
264
|
+
filter_by_override=None,
|
|
265
|
+
client_id=self.client_id,
|
|
266
|
+
client_secret=self.client_secret,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Create mock SBOM objects
|
|
270
|
+
mock_sbom1 = MagicMock(spec=Sbom)
|
|
271
|
+
mock_sbom1.name = "express"
|
|
272
|
+
mock_sbom1.create = MagicMock()
|
|
273
|
+
|
|
274
|
+
mock_sbom2 = MagicMock(spec=Sbom)
|
|
275
|
+
mock_sbom2.name = "lodash"
|
|
276
|
+
mock_sbom2.create = MagicMock()
|
|
277
|
+
|
|
278
|
+
# Mock fetch_sbom_data to populate sbom_list
|
|
279
|
+
def populate_sbom_list():
|
|
280
|
+
integration.sbom_list = [mock_sbom1, mock_sbom2]
|
|
281
|
+
|
|
282
|
+
mock_fetch_sbom.side_effect = populate_sbom_list
|
|
283
|
+
|
|
284
|
+
# Mock existing SBOM with name "express"
|
|
285
|
+
existing_sbom = MagicMock(spec=Sbom)
|
|
286
|
+
existing_sbom.name = "express"
|
|
287
|
+
mock_get_all.return_value = [existing_sbom]
|
|
288
|
+
|
|
289
|
+
integration.run()
|
|
290
|
+
|
|
291
|
+
mock_fetch_sbom.assert_called_once()
|
|
292
|
+
mock_get_all.assert_called_once_with(parent_id=self.regscale_id, parent_module=self.regscale_module)
|
|
293
|
+
# express should not be created (already exists)
|
|
294
|
+
mock_sbom1.create.assert_not_called()
|
|
295
|
+
# lodash should be created (doesn't exist)
|
|
296
|
+
mock_sbom2.create.assert_called_once()
|
|
297
|
+
|
|
298
|
+
@patch(f"{PATH}.WizSbomIntegration.map_sbom_data")
|
|
299
|
+
@patch(f"{PATH}.WizSbomIntegration.fetch_data_if_needed")
|
|
300
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
301
|
+
def test_fetch_sbom_data(self, mock_wiz_mixin_init, mock_fetch_data, mock_map_sbom):
|
|
302
|
+
"""Test fetch_sbom_data method"""
|
|
303
|
+
mock_wiz_mixin_init.return_value = None
|
|
304
|
+
|
|
305
|
+
with patch.object(
|
|
306
|
+
WizSbomIntegration, "config", {"wizProjectId": self.wiz_project_id, "wizFullPullLimitHours": 8}
|
|
307
|
+
):
|
|
308
|
+
integration = WizSbomIntegration(
|
|
309
|
+
wiz_project_id=self.wiz_project_id,
|
|
310
|
+
regscale_id=self.regscale_id,
|
|
311
|
+
regscale_module=self.regscale_module,
|
|
312
|
+
filter_by_override=None,
|
|
313
|
+
client_id=self.client_id,
|
|
314
|
+
client_secret=self.client_secret,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
mock_fetch_data.return_value = self.sample_sbom_nodes
|
|
318
|
+
|
|
319
|
+
integration.fetch_sbom_data()
|
|
320
|
+
|
|
321
|
+
# Verify fetch_data_if_needed was called with correct parameters
|
|
322
|
+
from regscale.integrations.commercial.wizv2.core.constants import SBOM_QUERY, SBOM_FILE_PATH
|
|
323
|
+
|
|
324
|
+
mock_fetch_data.assert_called_once_with(
|
|
325
|
+
file_path=SBOM_FILE_PATH,
|
|
326
|
+
query=SBOM_QUERY,
|
|
327
|
+
topic_key="sbomArtifactsGroupedByName",
|
|
328
|
+
interval_hours=8,
|
|
329
|
+
variables={
|
|
330
|
+
"first": 200,
|
|
331
|
+
"filterBy": {"project": self.wiz_project_id},
|
|
332
|
+
"orderBy": {"field": "NAME", "direction": "ASC"},
|
|
333
|
+
},
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Verify map_sbom_data was called with the nodes
|
|
337
|
+
mock_map_sbom.assert_called_once_with(self.sample_sbom_nodes)
|
|
338
|
+
|
|
339
|
+
@patch(f"{PATH}.WizSbomIntegration.map_sbom_data")
|
|
340
|
+
@patch(f"{PATH}.WizSbomIntegration.fetch_data_if_needed")
|
|
341
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
342
|
+
def test_fetch_sbom_data_with_custom_interval(self, mock_wiz_mixin_init, mock_fetch_data, mock_map_sbom):
|
|
343
|
+
"""Test fetch_sbom_data with custom interval hours from config"""
|
|
344
|
+
mock_wiz_mixin_init.return_value = None
|
|
345
|
+
|
|
346
|
+
with patch.object(
|
|
347
|
+
WizSbomIntegration, "config", {"wizProjectId": self.wiz_project_id, "wizFullPullLimitHours": 12}
|
|
348
|
+
):
|
|
349
|
+
integration = WizSbomIntegration(
|
|
350
|
+
wiz_project_id=self.wiz_project_id,
|
|
351
|
+
regscale_id=self.regscale_id,
|
|
352
|
+
regscale_module=self.regscale_module,
|
|
353
|
+
filter_by_override=None,
|
|
354
|
+
client_id=self.client_id,
|
|
355
|
+
client_secret=self.client_secret,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
mock_fetch_data.return_value = self.sample_sbom_nodes
|
|
359
|
+
|
|
360
|
+
integration.fetch_sbom_data()
|
|
361
|
+
|
|
362
|
+
# Verify interval_hours was set to custom value
|
|
363
|
+
call_kwargs = mock_fetch_data.call_args[1]
|
|
364
|
+
self.assertEqual(call_kwargs["interval_hours"], 12)
|
|
365
|
+
|
|
366
|
+
@patch("regscale.models.regscale_models.sbom.RegScaleModel.get_user_id")
|
|
367
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
368
|
+
def test_map_sbom_data(self, mock_wiz_mixin_init, mock_get_user_id):
|
|
369
|
+
"""Test map_sbom_data method with valid data"""
|
|
370
|
+
mock_wiz_mixin_init.return_value = None
|
|
371
|
+
mock_get_user_id.return_value = "test-user-id"
|
|
372
|
+
|
|
373
|
+
with patch.object(WizSbomIntegration, "config", {"wizProjectId": self.wiz_project_id}):
|
|
374
|
+
integration = WizSbomIntegration(
|
|
375
|
+
wiz_project_id=self.wiz_project_id,
|
|
376
|
+
regscale_id=self.regscale_id,
|
|
377
|
+
regscale_module=self.regscale_module,
|
|
378
|
+
filter_by_override=None,
|
|
379
|
+
client_id=self.client_id,
|
|
380
|
+
client_secret=self.client_secret,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
integration.map_sbom_data(self.sample_sbom_nodes)
|
|
384
|
+
|
|
385
|
+
# Verify SBOM objects were created
|
|
386
|
+
self.assertEqual(len(integration.sbom_list), 3)
|
|
387
|
+
|
|
388
|
+
# Check first SBOM
|
|
389
|
+
sbom1 = integration.sbom_list[0]
|
|
390
|
+
self.assertEqual(sbom1.name, "express")
|
|
391
|
+
self.assertEqual(sbom1.sbomStandard, "JavaScript")
|
|
392
|
+
self.assertEqual(sbom1.standardVersion, "4.18.0,4.18.1,4.18.2")
|
|
393
|
+
self.assertEqual(sbom1.tool, "npm")
|
|
394
|
+
self.assertEqual(sbom1.parentId, self.regscale_id)
|
|
395
|
+
self.assertEqual(sbom1.parentModule, self.regscale_module)
|
|
396
|
+
self.assertIsNotNone(sbom1.results)
|
|
397
|
+
self.assertIsInstance(json.loads(sbom1.results), dict)
|
|
398
|
+
|
|
399
|
+
# Check second SBOM
|
|
400
|
+
sbom2 = integration.sbom_list[1]
|
|
401
|
+
self.assertEqual(sbom2.name, "lodash")
|
|
402
|
+
self.assertEqual(sbom2.sbomStandard, "JavaScript")
|
|
403
|
+
self.assertEqual(sbom2.standardVersion, "4.17.21")
|
|
404
|
+
self.assertEqual(sbom2.tool, "npm")
|
|
405
|
+
|
|
406
|
+
# Check third SBOM (with no versions)
|
|
407
|
+
sbom3 = integration.sbom_list[2]
|
|
408
|
+
self.assertEqual(sbom3.name, "pytest")
|
|
409
|
+
self.assertEqual(sbom3.sbomStandard, "Python")
|
|
410
|
+
self.assertEqual(sbom3.standardVersion, "")
|
|
411
|
+
self.assertEqual(sbom3.tool, "pip")
|
|
412
|
+
|
|
413
|
+
@patch("regscale.models.regscale_models.sbom.RegScaleModel.get_user_id")
|
|
414
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
415
|
+
def test_map_sbom_data_with_missing_type_fields(self, mock_wiz_mixin_init, mock_get_user_id):
|
|
416
|
+
"""Test map_sbom_data with missing type fields falls back to name"""
|
|
417
|
+
mock_wiz_mixin_init.return_value = None
|
|
418
|
+
mock_get_user_id.return_value = "test-user-id"
|
|
419
|
+
|
|
420
|
+
integration = WizSbomIntegration(
|
|
421
|
+
wiz_project_id=self.wiz_project_id,
|
|
422
|
+
regscale_id=self.regscale_id,
|
|
423
|
+
regscale_module=self.regscale_module,
|
|
424
|
+
filter_by_override=None,
|
|
425
|
+
client_id=self.client_id,
|
|
426
|
+
client_secret=self.client_secret,
|
|
427
|
+
)
|
|
428
|
+
integration.config = {"wizProjectId": self.wiz_project_id}
|
|
429
|
+
# Clear any existing SBOM data
|
|
430
|
+
integration.sbom_list = []
|
|
431
|
+
|
|
432
|
+
# SBOM data with missing type fields
|
|
433
|
+
nodes_with_missing_fields = [
|
|
434
|
+
{
|
|
435
|
+
"id": "sbom-4",
|
|
436
|
+
"name": "unknown-package",
|
|
437
|
+
"type": {
|
|
438
|
+
"group": None,
|
|
439
|
+
"codeLibraryLanguage": None,
|
|
440
|
+
"osPackageManager": None,
|
|
441
|
+
"hostedTechnology": None,
|
|
442
|
+
"plugin": None,
|
|
443
|
+
},
|
|
444
|
+
"versions": {"nodes": [{"version": "1.0.0"}]},
|
|
445
|
+
}
|
|
446
|
+
]
|
|
447
|
+
|
|
448
|
+
integration.map_sbom_data(nodes_with_missing_fields)
|
|
449
|
+
|
|
450
|
+
self.assertEqual(len(integration.sbom_list), 1)
|
|
451
|
+
sbom = integration.sbom_list[0]
|
|
452
|
+
# When codeLibraryLanguage is None, it should fall back to name
|
|
453
|
+
self.assertEqual(sbom.sbomStandard, "unknown-package")
|
|
454
|
+
self.assertEqual(sbom.tool, None)
|
|
455
|
+
|
|
456
|
+
@patch("regscale.models.regscale_models.sbom.RegScaleModel.get_user_id")
|
|
457
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
458
|
+
def test_map_sbom_data_with_empty_versions(self, mock_wiz_mixin_init, mock_get_user_id):
|
|
459
|
+
"""Test map_sbom_data handles empty versions gracefully"""
|
|
460
|
+
mock_wiz_mixin_init.return_value = None
|
|
461
|
+
mock_get_user_id.return_value = "test-user-id"
|
|
462
|
+
|
|
463
|
+
integration = WizSbomIntegration(
|
|
464
|
+
wiz_project_id=self.wiz_project_id,
|
|
465
|
+
regscale_id=self.regscale_id,
|
|
466
|
+
regscale_module=self.regscale_module,
|
|
467
|
+
filter_by_override=None,
|
|
468
|
+
client_id=self.client_id,
|
|
469
|
+
client_secret=self.client_secret,
|
|
470
|
+
)
|
|
471
|
+
integration.config = {"wizProjectId": self.wiz_project_id}
|
|
472
|
+
# Clear any existing SBOM data
|
|
473
|
+
integration.sbom_list = []
|
|
474
|
+
|
|
475
|
+
nodes_with_no_versions = [
|
|
476
|
+
{
|
|
477
|
+
"id": "sbom-5",
|
|
478
|
+
"name": "no-version-package",
|
|
479
|
+
"type": {
|
|
480
|
+
"group": "maven",
|
|
481
|
+
"codeLibraryLanguage": "Java",
|
|
482
|
+
"osPackageManager": None,
|
|
483
|
+
"hostedTechnology": None,
|
|
484
|
+
"plugin": None,
|
|
485
|
+
},
|
|
486
|
+
"versions": {"nodes": []},
|
|
487
|
+
}
|
|
488
|
+
]
|
|
489
|
+
|
|
490
|
+
integration.map_sbom_data(nodes_with_no_versions)
|
|
491
|
+
|
|
492
|
+
self.assertEqual(len(integration.sbom_list), 1)
|
|
493
|
+
sbom = integration.sbom_list[0]
|
|
494
|
+
self.assertEqual(sbom.standardVersion, "")
|
|
495
|
+
|
|
496
|
+
@patch("regscale.models.regscale_models.sbom.RegScaleModel.get_user_id")
|
|
497
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
498
|
+
def test_map_sbom_data_preserves_json_structure(self, mock_wiz_mixin_init, mock_get_user_id):
|
|
499
|
+
"""Test that map_sbom_data preserves the original JSON in results field"""
|
|
500
|
+
mock_wiz_mixin_init.return_value = None
|
|
501
|
+
mock_get_user_id.return_value = "test-user-id"
|
|
502
|
+
|
|
503
|
+
with patch.object(WizSbomIntegration, "config", {"wizProjectId": self.wiz_project_id}):
|
|
504
|
+
integration = WizSbomIntegration(
|
|
505
|
+
wiz_project_id=self.wiz_project_id,
|
|
506
|
+
regscale_id=self.regscale_id,
|
|
507
|
+
regscale_module=self.regscale_module,
|
|
508
|
+
filter_by_override=None,
|
|
509
|
+
client_id=self.client_id,
|
|
510
|
+
client_secret=self.client_secret,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
integration.map_sbom_data(self.sample_sbom_nodes)
|
|
514
|
+
|
|
515
|
+
# Verify the results field contains valid JSON
|
|
516
|
+
sbom = integration.sbom_list[0]
|
|
517
|
+
results_data = json.loads(sbom.results)
|
|
518
|
+
|
|
519
|
+
self.assertEqual(results_data["id"], "sbom-1")
|
|
520
|
+
self.assertEqual(results_data["name"], "express")
|
|
521
|
+
self.assertIn("type", results_data)
|
|
522
|
+
self.assertIn("versions", results_data)
|
|
523
|
+
|
|
524
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
525
|
+
def test_map_sbom_data_with_none_values(self, mock_wiz_mixin_init):
|
|
526
|
+
"""Test map_sbom_data with None values raises TypeError due to get_value returning None"""
|
|
527
|
+
mock_wiz_mixin_init.return_value = None
|
|
528
|
+
|
|
529
|
+
integration = WizSbomIntegration(
|
|
530
|
+
wiz_project_id=self.wiz_project_id,
|
|
531
|
+
regscale_id=self.regscale_id,
|
|
532
|
+
regscale_module=self.regscale_module,
|
|
533
|
+
filter_by_override=None,
|
|
534
|
+
client_id=self.client_id,
|
|
535
|
+
client_secret=self.client_secret,
|
|
536
|
+
)
|
|
537
|
+
integration.config = {"wizProjectId": self.wiz_project_id}
|
|
538
|
+
# Clear any existing SBOM data
|
|
539
|
+
integration.sbom_list = []
|
|
540
|
+
|
|
541
|
+
nodes_with_nones = [
|
|
542
|
+
{
|
|
543
|
+
"id": "sbom-6",
|
|
544
|
+
"name": "test-package",
|
|
545
|
+
"type": None,
|
|
546
|
+
"versions": None,
|
|
547
|
+
}
|
|
548
|
+
]
|
|
549
|
+
|
|
550
|
+
# This should raise a TypeError since get_value returns None for versions.nodes
|
|
551
|
+
# and we try to iterate over it
|
|
552
|
+
with self.assertRaises(TypeError) as context:
|
|
553
|
+
integration.map_sbom_data(nodes_with_nones)
|
|
554
|
+
|
|
555
|
+
self.assertIn("'NoneType' object is not iterable", str(context.exception))
|
|
556
|
+
|
|
557
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
558
|
+
def test_variables_structure(self, mock_wiz_mixin_init):
|
|
559
|
+
"""Test that variables are correctly structured for GraphQL query"""
|
|
560
|
+
mock_wiz_mixin_init.return_value = None
|
|
561
|
+
|
|
562
|
+
with patch.object(WizSbomIntegration, "config", {"wizProjectId": self.wiz_project_id}):
|
|
563
|
+
integration = WizSbomIntegration(
|
|
564
|
+
wiz_project_id=self.wiz_project_id,
|
|
565
|
+
regscale_id=self.regscale_id,
|
|
566
|
+
regscale_module=self.regscale_module,
|
|
567
|
+
filter_by_override=None,
|
|
568
|
+
client_id=self.client_id,
|
|
569
|
+
client_secret=self.client_secret,
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
expected_variables = {
|
|
573
|
+
"first": 200,
|
|
574
|
+
"filterBy": {"project": self.wiz_project_id},
|
|
575
|
+
"orderBy": {"field": "NAME", "direction": "ASC"},
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
self.assertEqual(integration.variables, expected_variables)
|
|
579
|
+
|
|
580
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
581
|
+
def test_filter_by_override_logging(self, mock_wiz_mixin_init):
|
|
582
|
+
"""Test that filterBy override is applied correctly"""
|
|
583
|
+
mock_wiz_mixin_init.return_value = None
|
|
584
|
+
filter_by_json = '{"custom": "filter"}'
|
|
585
|
+
|
|
586
|
+
# Create the instance first and manually set config
|
|
587
|
+
integration = WizSbomIntegration.__new__(WizSbomIntegration)
|
|
588
|
+
integration.config = {"wizProjectId": self.wiz_project_id}
|
|
589
|
+
|
|
590
|
+
# Now call __init__ with filter_by_override
|
|
591
|
+
WizSbomIntegration.__init__(
|
|
592
|
+
integration,
|
|
593
|
+
wiz_project_id=self.wiz_project_id,
|
|
594
|
+
regscale_id=self.regscale_id,
|
|
595
|
+
regscale_module=self.regscale_module,
|
|
596
|
+
filter_by_override=filter_by_json,
|
|
597
|
+
client_id=self.client_id,
|
|
598
|
+
client_secret=self.client_secret,
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
# Verify that filter_by was set correctly
|
|
602
|
+
expected_filter = {"custom": "filter"}
|
|
603
|
+
self.assertEqual(integration.filter_by, expected_filter)
|
|
604
|
+
self.assertEqual(integration.variables["filterBy"], expected_filter)
|
|
605
|
+
|
|
606
|
+
@patch(f"{PATH}.Sbom")
|
|
607
|
+
@patch(f"{PATH}.WizMixin.__init__")
|
|
608
|
+
def test_map_sbom_data_creates_sbom_instances(self, mock_wiz_mixin_init, mock_sbom_class):
|
|
609
|
+
"""Test that map_sbom_data creates Sbom model instances with correct parameters"""
|
|
610
|
+
mock_wiz_mixin_init.return_value = None
|
|
611
|
+
|
|
612
|
+
with patch.object(WizSbomIntegration, "config", {"wizProjectId": self.wiz_project_id}):
|
|
613
|
+
integration = WizSbomIntegration(
|
|
614
|
+
wiz_project_id=self.wiz_project_id,
|
|
615
|
+
regscale_id=self.regscale_id,
|
|
616
|
+
regscale_module=self.regscale_module,
|
|
617
|
+
filter_by_override=None,
|
|
618
|
+
client_id=self.client_id,
|
|
619
|
+
client_secret=self.client_secret,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
# Create a simple mock for Sbom that returns itself
|
|
623
|
+
mock_sbom_instance = MagicMock(spec=Sbom)
|
|
624
|
+
mock_sbom_class.return_value = mock_sbom_instance
|
|
625
|
+
|
|
626
|
+
single_node = [self.sample_sbom_nodes[0]]
|
|
627
|
+
integration.map_sbom_data(single_node)
|
|
628
|
+
|
|
629
|
+
# Verify Sbom was instantiated with correct parameters
|
|
630
|
+
mock_sbom_class.assert_called_once()
|
|
631
|
+
call_kwargs = mock_sbom_class.call_args[1]
|
|
632
|
+
|
|
633
|
+
self.assertEqual(call_kwargs["name"], "express")
|
|
634
|
+
self.assertEqual(call_kwargs["sbomStandard"], "JavaScript")
|
|
635
|
+
self.assertEqual(call_kwargs["standardVersion"], "4.18.0,4.18.1,4.18.2")
|
|
636
|
+
self.assertEqual(call_kwargs["tool"], "npm")
|
|
637
|
+
self.assertEqual(call_kwargs["parentId"], self.regscale_id)
|
|
638
|
+
self.assertEqual(call_kwargs["parentModule"], self.regscale_module)
|
|
639
|
+
self.assertIsNotNone(call_kwargs["results"])
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
if __name__ == "__main__":
|
|
643
|
+
unittest.main()
|