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,805 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive unit tests for WizVulnerabilityIntegration scanner class.
|
|
3
|
+
Focuses on achieving 90%+ code coverage with emphasis on previously uncovered lines.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
import unittest
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
from unittest.mock import MagicMock, Mock, AsyncMock, patch, call
|
|
12
|
+
|
|
13
|
+
from regscale.integrations.commercial.wizv2.core.constants import WizVulnerabilityType
|
|
14
|
+
from regscale.integrations.commercial.wizv2.scanner import WizVulnerabilityIntegration
|
|
15
|
+
from regscale.integrations.commercial.wizv2.variables import WizVariables
|
|
16
|
+
from regscale.integrations.variables import ScannerVariables
|
|
17
|
+
from regscale.models import regscale_models
|
|
18
|
+
from regscale.models.regscale_models.issue import IssueStatus
|
|
19
|
+
from tests.regscale.integrations.commercial.wizv2 import (
|
|
20
|
+
PROJECT_ID,
|
|
21
|
+
PLAN_ID,
|
|
22
|
+
asset_nodes,
|
|
23
|
+
vuln_nodes,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("regscale")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@patch("regscale.integrations.scanner_integration.ScannerIntegration.__init__", return_value=None)
|
|
30
|
+
class TestWizVulnerabilityIntegrationScanner(unittest.TestCase):
|
|
31
|
+
"""Test class for WizVulnerabilityIntegration scanner methods."""
|
|
32
|
+
|
|
33
|
+
def setUp(self, mock_parent_init=None):
|
|
34
|
+
"""Set up test fixtures."""
|
|
35
|
+
self.plan_id = PLAN_ID
|
|
36
|
+
self.project_id = PROJECT_ID
|
|
37
|
+
|
|
38
|
+
def _initialize_scanner_attributes(self, integration, plan_id=None):
|
|
39
|
+
"""Initialize parent class attributes that would normally be set by ScannerIntegration.__init__."""
|
|
40
|
+
from regscale.core.app.application import Application
|
|
41
|
+
from regscale.core.app.utils.app_utils import create_progress_object
|
|
42
|
+
from regscale.utils.threading import ThreadSafeList, ThreadSafeDict
|
|
43
|
+
|
|
44
|
+
integration.app = Application()
|
|
45
|
+
integration.plan_id = plan_id if plan_id is not None else self.plan_id
|
|
46
|
+
integration.tenant_id = 1
|
|
47
|
+
integration.is_component = False
|
|
48
|
+
integration.parent_module = regscale_models.SecurityPlan.get_module_string()
|
|
49
|
+
integration.asset_progress = create_progress_object()
|
|
50
|
+
integration.finding_progress = create_progress_object()
|
|
51
|
+
integration.components_by_title = ThreadSafeDict()
|
|
52
|
+
integration.components_by_id = ThreadSafeDict()
|
|
53
|
+
integration.components = ThreadSafeList()
|
|
54
|
+
integration.errors = []
|
|
55
|
+
integration.asset_map_by_identifier = ThreadSafeDict()
|
|
56
|
+
integration.software_to_create = ThreadSafeList()
|
|
57
|
+
integration.software_to_update = ThreadSafeList()
|
|
58
|
+
integration.data_to_create = ThreadSafeList()
|
|
59
|
+
integration.data_to_update = ThreadSafeList()
|
|
60
|
+
integration.link_to_create = ThreadSafeList()
|
|
61
|
+
integration.link_to_update = ThreadSafeList()
|
|
62
|
+
integration.existing_issues_map = ThreadSafeDict()
|
|
63
|
+
integration.alerted_assets = set()
|
|
64
|
+
from datetime import datetime
|
|
65
|
+
|
|
66
|
+
integration.scan_date = datetime.now().strftime("%Y-%m-%d")
|
|
67
|
+
|
|
68
|
+
# ========================================
|
|
69
|
+
# Initialization and Configuration Tests
|
|
70
|
+
# ========================================
|
|
71
|
+
|
|
72
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
73
|
+
def test_init_sets_suppress_asset_not_found_errors(self, mock_authenticate, mock_parent_init):
|
|
74
|
+
"""Test that initialization sets suppress_asset_not_found_errors to True."""
|
|
75
|
+
mock_authenticate.return_value = None
|
|
76
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
77
|
+
self.assertTrue(integration.suppress_asset_not_found_errors)
|
|
78
|
+
self.assertIsInstance(integration._missing_asset_types, set)
|
|
79
|
+
|
|
80
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
81
|
+
def test_get_query_types_with_filter_by(self, mock_authenticate, mock_parent_init):
|
|
82
|
+
"""Test get_query_types with custom filter_by parameter."""
|
|
83
|
+
mock_authenticate.return_value = None
|
|
84
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
85
|
+
filter_by = {"severity": "HIGH"}
|
|
86
|
+
query_types = integration.get_query_types(self.project_id, filter_by=filter_by)
|
|
87
|
+
self.assertIsInstance(query_types, list)
|
|
88
|
+
self.assertGreater(len(query_types), 0)
|
|
89
|
+
|
|
90
|
+
# ========================================
|
|
91
|
+
# Fetch Findings Tests (Lines 158-170, 388-396)
|
|
92
|
+
# ========================================
|
|
93
|
+
|
|
94
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
95
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_async")
|
|
96
|
+
def test_fetch_findings_uses_async_by_default(self, mock_async, mock_authenticate, mock_parent_init):
|
|
97
|
+
"""Test that fetch_findings uses async by default."""
|
|
98
|
+
mock_authenticate.return_value = None
|
|
99
|
+
mock_async.return_value = iter([])
|
|
100
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
101
|
+
list(integration.fetch_findings(wiz_project_id=self.project_id))
|
|
102
|
+
mock_async.assert_called_once()
|
|
103
|
+
|
|
104
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
105
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_sync")
|
|
106
|
+
def test_fetch_findings_uses_sync_when_requested(self, mock_sync, mock_authenticate, mock_parent_init):
|
|
107
|
+
"""Test that fetch_findings uses sync when use_async=False."""
|
|
108
|
+
mock_authenticate.return_value = None
|
|
109
|
+
mock_sync.return_value = iter([])
|
|
110
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
111
|
+
list(integration.fetch_findings(wiz_project_id=self.project_id, use_async=False))
|
|
112
|
+
mock_sync.assert_called_once()
|
|
113
|
+
|
|
114
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
115
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_async")
|
|
116
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_sync")
|
|
117
|
+
def test_fetch_findings_fallback_on_async_error(self, mock_sync, mock_async, mock_authenticate, mock_parent_init):
|
|
118
|
+
"""Test fetch_findings falls back to sync on async error (line 165)."""
|
|
119
|
+
mock_authenticate.return_value = None
|
|
120
|
+
mock_async.side_effect = Exception("Async error")
|
|
121
|
+
mock_sync.return_value = iter([])
|
|
122
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
123
|
+
list(integration.fetch_findings(wiz_project_id=self.project_id, use_async=True))
|
|
124
|
+
mock_sync.assert_called_once()
|
|
125
|
+
|
|
126
|
+
# ========================================
|
|
127
|
+
# Async Findings Fetch Tests (Lines 388-408)
|
|
128
|
+
# ========================================
|
|
129
|
+
|
|
130
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
131
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._validate_project_id")
|
|
132
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.get_query_types")
|
|
133
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._setup_authentication_headers")
|
|
134
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._execute_concurrent_queries")
|
|
135
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._should_fetch_fresh_data")
|
|
136
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._process_query_results")
|
|
137
|
+
def test_fetch_findings_async_success_with_missing_asset_types(
|
|
138
|
+
self,
|
|
139
|
+
mock_process,
|
|
140
|
+
mock_should_fetch,
|
|
141
|
+
mock_execute,
|
|
142
|
+
mock_headers,
|
|
143
|
+
mock_query_types,
|
|
144
|
+
mock_validate,
|
|
145
|
+
mock_authenticate,
|
|
146
|
+
mock_parent_init,
|
|
147
|
+
):
|
|
148
|
+
"""Test fetch_findings_async with missing asset types tracking (lines 399-404)."""
|
|
149
|
+
mock_authenticate.return_value = None
|
|
150
|
+
mock_validate.return_value = self.project_id
|
|
151
|
+
mock_query_types.return_value = [
|
|
152
|
+
{"type": WizVulnerabilityType.VULNERABILITY, "query": "test_query", "variables": {}}
|
|
153
|
+
]
|
|
154
|
+
mock_headers.return_value = {"Authorization": "Bearer test"}
|
|
155
|
+
mock_execute.return_value = []
|
|
156
|
+
mock_should_fetch.return_value = True
|
|
157
|
+
mock_process.return_value = iter([])
|
|
158
|
+
|
|
159
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
160
|
+
self._initialize_scanner_attributes(integration)
|
|
161
|
+
integration._missing_asset_types = {"VM", "DATABASE"}
|
|
162
|
+
list(integration.fetch_findings_async(wiz_project_id=self.project_id))
|
|
163
|
+
|
|
164
|
+
# Verify missing asset types were tracked
|
|
165
|
+
self.assertEqual(integration._missing_asset_types, {"VM", "DATABASE"})
|
|
166
|
+
|
|
167
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
168
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._validate_project_id")
|
|
169
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_sync")
|
|
170
|
+
def test_fetch_findings_async_error_fallback(self, mock_sync, mock_validate, mock_authenticate, mock_parent_init):
|
|
171
|
+
"""Test fetch_findings_async falls back to sync on error (lines 388-396)."""
|
|
172
|
+
mock_authenticate.return_value = None
|
|
173
|
+
mock_validate.side_effect = Exception("Validation error")
|
|
174
|
+
mock_sync.return_value = iter([])
|
|
175
|
+
|
|
176
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
177
|
+
list(integration.fetch_findings_async(wiz_project_id=self.project_id))
|
|
178
|
+
|
|
179
|
+
mock_sync.assert_called_once()
|
|
180
|
+
|
|
181
|
+
# ========================================
|
|
182
|
+
# Process Query Results Tests (Lines 264-273)
|
|
183
|
+
# ========================================
|
|
184
|
+
|
|
185
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
186
|
+
def test_process_query_results_with_error(self, mock_authenticate, mock_parent_init):
|
|
187
|
+
"""Test _process_query_results handles errors properly (lines 264-266)."""
|
|
188
|
+
mock_authenticate.return_value = None
|
|
189
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
190
|
+
self._initialize_scanner_attributes(integration)
|
|
191
|
+
|
|
192
|
+
results = [("VULNERABILITY", [], Exception("Test error"))]
|
|
193
|
+
query_configs = [
|
|
194
|
+
{
|
|
195
|
+
"type": WizVulnerabilityType.VULNERABILITY,
|
|
196
|
+
"query": "test",
|
|
197
|
+
"file_path": "/tmp/test.json",
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
findings = list(integration._process_query_results(results, query_configs, self.project_id, False))
|
|
202
|
+
self.assertEqual(len(findings), 0)
|
|
203
|
+
|
|
204
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
205
|
+
def test_process_query_results_missing_vulnerability_type(self, mock_authenticate, mock_parent_init):
|
|
206
|
+
"""Test _process_query_results with missing vulnerability type (lines 271-273)."""
|
|
207
|
+
mock_authenticate.return_value = None
|
|
208
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
209
|
+
self._initialize_scanner_attributes(integration)
|
|
210
|
+
|
|
211
|
+
results = [("UNKNOWN_TYPE", [{"id": "test"}], None)]
|
|
212
|
+
query_configs = [
|
|
213
|
+
{
|
|
214
|
+
"type": WizVulnerabilityType.VULNERABILITY,
|
|
215
|
+
"query": "test",
|
|
216
|
+
"file_path": "/tmp/test.json",
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
findings = list(integration._process_query_results(results, query_configs, self.project_id, False))
|
|
221
|
+
self.assertEqual(len(findings), 0)
|
|
222
|
+
|
|
223
|
+
# ========================================
|
|
224
|
+
# Cache Management Tests (Lines 230-241)
|
|
225
|
+
# ========================================
|
|
226
|
+
|
|
227
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
228
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.run_async_queries")
|
|
229
|
+
def test_execute_concurrent_queries_fetches_fresh_data(self, mock_run_async, mock_authenticate, mock_parent_init):
|
|
230
|
+
"""Test _execute_concurrent_queries fetches fresh data (lines 232-240)."""
|
|
231
|
+
mock_authenticate.return_value = None
|
|
232
|
+
mock_run_async.return_value = []
|
|
233
|
+
|
|
234
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
235
|
+
self._initialize_scanner_attributes(integration)
|
|
236
|
+
query_configs = [
|
|
237
|
+
{
|
|
238
|
+
"type": WizVulnerabilityType.VULNERABILITY,
|
|
239
|
+
"query": "test_query",
|
|
240
|
+
"variables": {},
|
|
241
|
+
"file_path": "/tmp/nonexistent.json",
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
headers = {"Authorization": "Bearer test"}
|
|
245
|
+
|
|
246
|
+
with patch.object(integration, "_should_fetch_fresh_data", return_value=True):
|
|
247
|
+
integration._execute_concurrent_queries(query_configs, headers)
|
|
248
|
+
|
|
249
|
+
mock_run_async.assert_called_once()
|
|
250
|
+
|
|
251
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
252
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.FileOperations.load_cached_findings")
|
|
253
|
+
def test_execute_concurrent_queries_loads_cached_data(self, mock_load_cached, mock_authenticate, mock_parent_init):
|
|
254
|
+
"""Test _execute_concurrent_queries loads cached data (line 241)."""
|
|
255
|
+
mock_authenticate.return_value = None
|
|
256
|
+
mock_load_cached.return_value = []
|
|
257
|
+
|
|
258
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
259
|
+
self._initialize_scanner_attributes(integration)
|
|
260
|
+
query_configs = [
|
|
261
|
+
{
|
|
262
|
+
"type": WizVulnerabilityType.VULNERABILITY,
|
|
263
|
+
"query": "test_query",
|
|
264
|
+
"variables": {},
|
|
265
|
+
"file_path": "/tmp/test.json",
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
headers = {"Authorization": "Bearer test"}
|
|
269
|
+
|
|
270
|
+
with patch.object(integration, "_should_fetch_fresh_data", return_value=False):
|
|
271
|
+
integration._execute_concurrent_queries(query_configs, headers)
|
|
272
|
+
|
|
273
|
+
mock_load_cached.assert_called_once()
|
|
274
|
+
|
|
275
|
+
# ========================================
|
|
276
|
+
# Should Fetch Fresh Data Tests
|
|
277
|
+
# ========================================
|
|
278
|
+
|
|
279
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
280
|
+
@patch("os.path.exists")
|
|
281
|
+
def test_should_fetch_fresh_data_missing_file(self, mock_exists, mock_authenticate, mock_parent_init):
|
|
282
|
+
"""Test _should_fetch_fresh_data returns True for missing file."""
|
|
283
|
+
mock_authenticate.return_value = None
|
|
284
|
+
mock_exists.return_value = False
|
|
285
|
+
|
|
286
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
287
|
+
query_configs = [{"file_path": "/tmp/missing.json"}]
|
|
288
|
+
|
|
289
|
+
result = integration._should_fetch_fresh_data(query_configs)
|
|
290
|
+
self.assertTrue(result)
|
|
291
|
+
|
|
292
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
293
|
+
@patch("os.path.exists")
|
|
294
|
+
@patch("os.path.getmtime")
|
|
295
|
+
def test_should_fetch_fresh_data_old_file(self, mock_getmtime, mock_exists, mock_authenticate, mock_parent_init):
|
|
296
|
+
"""Test _should_fetch_fresh_data returns True for old file."""
|
|
297
|
+
mock_authenticate.return_value = None
|
|
298
|
+
mock_exists.return_value = True
|
|
299
|
+
# File modified 10 hours ago
|
|
300
|
+
mock_getmtime.return_value = time.time() - (10 * 3600)
|
|
301
|
+
|
|
302
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
303
|
+
query_configs = [{"file_path": "/tmp/old.json"}]
|
|
304
|
+
|
|
305
|
+
result = integration._should_fetch_fresh_data(query_configs)
|
|
306
|
+
self.assertTrue(result)
|
|
307
|
+
|
|
308
|
+
# ========================================
|
|
309
|
+
# Load Cached Data with Progress Tests
|
|
310
|
+
# ========================================
|
|
311
|
+
|
|
312
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
313
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.FileOperations.load_cached_findings")
|
|
314
|
+
def test_load_cached_data_with_progress(self, mock_load_cached, mock_authenticate, mock_parent_init):
|
|
315
|
+
"""Test _load_cached_data_with_progress loads data correctly."""
|
|
316
|
+
mock_authenticate.return_value = None
|
|
317
|
+
mock_load_cached.return_value = [("VULNERABILITY", [{"id": "test"}], None)]
|
|
318
|
+
|
|
319
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
320
|
+
self._initialize_scanner_attributes(integration)
|
|
321
|
+
query_configs = [
|
|
322
|
+
{
|
|
323
|
+
"type": WizVulnerabilityType.VULNERABILITY,
|
|
324
|
+
"query": "test",
|
|
325
|
+
"file_path": "/tmp/test.json",
|
|
326
|
+
}
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
results = integration._load_cached_data_with_progress(query_configs)
|
|
330
|
+
self.assertEqual(len(results), 1)
|
|
331
|
+
mock_load_cached.assert_called_once()
|
|
332
|
+
|
|
333
|
+
# ========================================
|
|
334
|
+
# Save Data to Cache Tests
|
|
335
|
+
# ========================================
|
|
336
|
+
|
|
337
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
338
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.FileOperations.save_json_file")
|
|
339
|
+
def test_save_data_to_cache_success(self, mock_save, mock_authenticate, mock_parent_init):
|
|
340
|
+
"""Test _save_data_to_cache saves successfully."""
|
|
341
|
+
mock_authenticate.return_value = None
|
|
342
|
+
mock_save.return_value = True
|
|
343
|
+
|
|
344
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
345
|
+
nodes = [{"id": "test1"}, {"id": "test2"}]
|
|
346
|
+
integration._save_data_to_cache(nodes, "/tmp/cache.json")
|
|
347
|
+
|
|
348
|
+
mock_save.assert_called_once_with(nodes, "/tmp/cache.json", create_dir=True)
|
|
349
|
+
|
|
350
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
351
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.FileOperations.save_json_file")
|
|
352
|
+
def test_save_data_to_cache_no_path(self, mock_save, mock_authenticate, mock_parent_init):
|
|
353
|
+
"""Test _save_data_to_cache skips when no path provided."""
|
|
354
|
+
mock_authenticate.return_value = None
|
|
355
|
+
|
|
356
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
357
|
+
nodes = [{"id": "test1"}]
|
|
358
|
+
integration._save_data_to_cache(nodes, None)
|
|
359
|
+
|
|
360
|
+
mock_save.assert_not_called()
|
|
361
|
+
|
|
362
|
+
# ========================================
|
|
363
|
+
# Parse Findings Tests (Lines 744-748)
|
|
364
|
+
# ========================================
|
|
365
|
+
|
|
366
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
367
|
+
def test_parse_findings_all_filtered_by_severity(self, mock_authenticate, mock_parent_init):
|
|
368
|
+
"""Test parse_findings when all findings are filtered by severity (lines 744-748)."""
|
|
369
|
+
mock_authenticate.return_value = None
|
|
370
|
+
|
|
371
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
372
|
+
self._initialize_scanner_attributes(integration)
|
|
373
|
+
integration.app.config["scanners"] = {"wiz": {"minimumSeverity": "critical"}}
|
|
374
|
+
|
|
375
|
+
# Create nodes with only LOW severity
|
|
376
|
+
nodes = [
|
|
377
|
+
{"id": "test1", "severity": "LOW", "status": "OPEN"},
|
|
378
|
+
{"id": "test2", "severity": "MEDIUM", "status": "OPEN"},
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
findings = list(integration.parse_findings(nodes, WizVulnerabilityType.VULNERABILITY))
|
|
382
|
+
self.assertEqual(len(findings), 0)
|
|
383
|
+
|
|
384
|
+
# ========================================
|
|
385
|
+
# Consolidation Tests
|
|
386
|
+
# ========================================
|
|
387
|
+
|
|
388
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
389
|
+
def test_should_apply_consolidation_for_data_finding(self, mock_authenticate, mock_parent_init):
|
|
390
|
+
"""Test _should_apply_consolidation for DATA_FINDING type."""
|
|
391
|
+
mock_authenticate.return_value = None
|
|
392
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
393
|
+
|
|
394
|
+
result = integration._should_apply_consolidation(WizVulnerabilityType.DATA_FINDING)
|
|
395
|
+
self.assertTrue(result)
|
|
396
|
+
|
|
397
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
398
|
+
def test_should_not_apply_consolidation_for_secrets(self, mock_authenticate, mock_parent_init):
|
|
399
|
+
"""Test _should_apply_consolidation returns False for SECRET_FINDING."""
|
|
400
|
+
mock_authenticate.return_value = None
|
|
401
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
402
|
+
|
|
403
|
+
result = integration._should_apply_consolidation(WizVulnerabilityType.SECRET_FINDING)
|
|
404
|
+
self.assertFalse(result)
|
|
405
|
+
|
|
406
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
407
|
+
def test_group_findings_for_consolidation(self, mock_authenticate, mock_parent_init):
|
|
408
|
+
"""Test _group_findings_for_consolidation groups correctly."""
|
|
409
|
+
mock_authenticate.return_value = None
|
|
410
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
411
|
+
|
|
412
|
+
nodes = [
|
|
413
|
+
{"sourceRule": {"name": "Rule1"}, "entitySnapshot": {"providerId": "provider1"}},
|
|
414
|
+
{"sourceRule": {"name": "Rule1"}, "entitySnapshot": {"providerId": "provider1"}},
|
|
415
|
+
{"sourceRule": {"name": "Rule2"}, "entitySnapshot": {"providerId": "provider2"}},
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
groups = integration._group_findings_for_consolidation(nodes)
|
|
419
|
+
self.assertIsInstance(groups, dict)
|
|
420
|
+
# Should have 2 groups: Rule1|provider1 and Rule2|provider2
|
|
421
|
+
self.assertGreaterEqual(len(groups), 2)
|
|
422
|
+
|
|
423
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
424
|
+
def test_create_consolidated_scanner_finding(self, mock_authenticate, mock_parent_init):
|
|
425
|
+
"""Test _create_consolidated_scanner_finding creates consolidated finding."""
|
|
426
|
+
mock_authenticate.return_value = None
|
|
427
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
428
|
+
self._initialize_scanner_attributes(integration)
|
|
429
|
+
|
|
430
|
+
nodes = [
|
|
431
|
+
{
|
|
432
|
+
"id": "finding1",
|
|
433
|
+
"name": "CVE-2024-1234",
|
|
434
|
+
"severity": "HIGH",
|
|
435
|
+
"status": "OPEN",
|
|
436
|
+
"vulnerableAsset": {"id": "asset1", "name": "Asset 1", "providerUniqueId": "provider1"},
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"id": "finding2",
|
|
440
|
+
"name": "CVE-2024-1234",
|
|
441
|
+
"severity": "HIGH",
|
|
442
|
+
"status": "OPEN",
|
|
443
|
+
"vulnerableAsset": {"id": "asset2", "name": "Asset 2", "providerUniqueId": "provider2"},
|
|
444
|
+
},
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
result = integration._create_consolidated_scanner_finding(nodes, WizVulnerabilityType.VULNERABILITY)
|
|
448
|
+
self.assertIsNotNone(result)
|
|
449
|
+
|
|
450
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
451
|
+
def test_create_consolidated_scanner_finding_no_asset_ids(self, mock_authenticate, mock_parent_init):
|
|
452
|
+
"""Test _create_consolidated_scanner_finding falls back when no asset IDs."""
|
|
453
|
+
mock_authenticate.return_value = None
|
|
454
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
455
|
+
|
|
456
|
+
nodes = [
|
|
457
|
+
{
|
|
458
|
+
"id": "finding1",
|
|
459
|
+
"name": "CVE-2024-1234",
|
|
460
|
+
"severity": "HIGH",
|
|
461
|
+
"status": "OPEN",
|
|
462
|
+
}
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
with patch.object(integration, "get_asset_id_from_node", return_value=None):
|
|
466
|
+
with patch.object(integration, "parse_finding", return_value=Mock()) as mock_parse:
|
|
467
|
+
consolidated_finding = integration._create_consolidated_scanner_finding(
|
|
468
|
+
nodes, WizVulnerabilityType.VULNERABILITY
|
|
469
|
+
)
|
|
470
|
+
self.assertIsNotNone(consolidated_finding)
|
|
471
|
+
mock_parse.assert_called_once()
|
|
472
|
+
|
|
473
|
+
# ========================================
|
|
474
|
+
# Determine Grouping Scope Tests
|
|
475
|
+
# ========================================
|
|
476
|
+
|
|
477
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
478
|
+
def test_determine_grouping_scope_database(self, mock_authenticate, mock_parent_init):
|
|
479
|
+
"""Test _determine_grouping_scope for database resources."""
|
|
480
|
+
mock_authenticate.return_value = None
|
|
481
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
482
|
+
|
|
483
|
+
provider_id = "/subscriptions/abc/resourceGroups/rg1/providers/Microsoft.Sql/servers/server1/databases/db1"
|
|
484
|
+
result = integration._determine_grouping_scope(provider_id, "Database Rule")
|
|
485
|
+
|
|
486
|
+
self.assertEqual(result, "/subscriptions/abc/resourceGroups/rg1/providers/Microsoft.Sql/servers/server1")
|
|
487
|
+
|
|
488
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
489
|
+
def test_determine_grouping_scope_app_configuration(self, mock_authenticate, mock_parent_init):
|
|
490
|
+
"""Test _determine_grouping_scope for app configuration resources."""
|
|
491
|
+
mock_authenticate.return_value = None
|
|
492
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
493
|
+
|
|
494
|
+
provider_id = (
|
|
495
|
+
"/subscriptions/abc/resourcegroups/rg1/providers/microsoft.appconfiguration/configurationstores/store1"
|
|
496
|
+
)
|
|
497
|
+
result = integration._determine_grouping_scope(provider_id, "App Configuration Rule")
|
|
498
|
+
|
|
499
|
+
self.assertIn("resourcegroups/rg1", result)
|
|
500
|
+
|
|
501
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
502
|
+
def test_determine_grouping_scope_default(self, mock_authenticate, mock_parent_init):
|
|
503
|
+
"""Test _determine_grouping_scope default behavior."""
|
|
504
|
+
mock_authenticate.return_value = None
|
|
505
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
506
|
+
|
|
507
|
+
provider_id = "/subscriptions/abc/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1"
|
|
508
|
+
result = integration._determine_grouping_scope(provider_id, "Other Rule")
|
|
509
|
+
|
|
510
|
+
self.assertEqual(result, provider_id)
|
|
511
|
+
|
|
512
|
+
# ========================================
|
|
513
|
+
# Get Rule Name from Node Tests
|
|
514
|
+
# ========================================
|
|
515
|
+
|
|
516
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
517
|
+
def test_get_rule_name_from_node_with_source_rule(self, mock_authenticate, mock_parent_init):
|
|
518
|
+
"""Test _get_rule_name_from_node extracts from sourceRule."""
|
|
519
|
+
mock_authenticate.return_value = None
|
|
520
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
521
|
+
|
|
522
|
+
node = {"sourceRule": {"name": "Test Rule"}}
|
|
523
|
+
result = integration._get_rule_name_from_node(node)
|
|
524
|
+
self.assertEqual(result, "Test Rule")
|
|
525
|
+
|
|
526
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
527
|
+
def test_get_rule_name_from_node_fallback_to_name(self, mock_authenticate, mock_parent_init):
|
|
528
|
+
"""Test _get_rule_name_from_node falls back to name field."""
|
|
529
|
+
mock_authenticate.return_value = None
|
|
530
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
531
|
+
|
|
532
|
+
node = {"name": "Fallback Name"}
|
|
533
|
+
result = integration._get_rule_name_from_node(node)
|
|
534
|
+
self.assertEqual(result, "Fallback Name")
|
|
535
|
+
|
|
536
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
537
|
+
def test_get_rule_name_from_node_fallback_to_title(self, mock_authenticate, mock_parent_init):
|
|
538
|
+
"""Test _get_rule_name_from_node falls back to title field."""
|
|
539
|
+
mock_authenticate.return_value = None
|
|
540
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
541
|
+
|
|
542
|
+
node = {"title": "Title Name"}
|
|
543
|
+
result = integration._get_rule_name_from_node(node)
|
|
544
|
+
self.assertEqual(result, "Title Name")
|
|
545
|
+
|
|
546
|
+
# ========================================
|
|
547
|
+
# Get Provider ID from Node Tests
|
|
548
|
+
# ========================================
|
|
549
|
+
|
|
550
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
551
|
+
def test_get_provider_id_from_node_entity_snapshot(self, mock_authenticate, mock_parent_init):
|
|
552
|
+
"""Test _get_provider_id_from_node with entitySnapshot."""
|
|
553
|
+
mock_authenticate.return_value = None
|
|
554
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
555
|
+
|
|
556
|
+
node = {"entitySnapshot": {"providerId": "provider-123"}}
|
|
557
|
+
result = integration._get_provider_id_from_node(node)
|
|
558
|
+
self.assertEqual(result, "provider-123")
|
|
559
|
+
|
|
560
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
561
|
+
def test_get_provider_id_from_node_vulnerable_asset(self, mock_authenticate, mock_parent_init):
|
|
562
|
+
"""Test _get_provider_id_from_node with vulnerableAsset."""
|
|
563
|
+
mock_authenticate.return_value = None
|
|
564
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
565
|
+
|
|
566
|
+
node = {"vulnerableAsset": {"providerId": "provider-456"}}
|
|
567
|
+
result = integration._get_provider_id_from_node(node)
|
|
568
|
+
self.assertEqual(result, "provider-456")
|
|
569
|
+
|
|
570
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
571
|
+
def test_get_provider_id_from_node_fallback_to_asset_id(self, mock_authenticate, mock_parent_init):
|
|
572
|
+
"""Test _get_provider_id_from_node falls back to asset ID."""
|
|
573
|
+
mock_authenticate.return_value = None
|
|
574
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
575
|
+
|
|
576
|
+
node = {"vulnerableAsset": {"id": "asset-789"}}
|
|
577
|
+
result = integration._get_provider_id_from_node(node)
|
|
578
|
+
self.assertEqual(result, "asset-789")
|
|
579
|
+
|
|
580
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
581
|
+
def test_get_provider_id_from_node_empty(self, mock_authenticate, mock_parent_init):
|
|
582
|
+
"""Test _get_provider_id_from_node returns empty string when not found."""
|
|
583
|
+
mock_authenticate.return_value = None
|
|
584
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
585
|
+
|
|
586
|
+
node = {"someOtherField": "value"}
|
|
587
|
+
result = integration._get_provider_id_from_node(node)
|
|
588
|
+
self.assertEqual(result, "")
|
|
589
|
+
|
|
590
|
+
# ========================================
|
|
591
|
+
# Get Asset ID from Node Tests
|
|
592
|
+
# ========================================
|
|
593
|
+
|
|
594
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
595
|
+
def test_get_asset_id_from_node_configuration_finding(self, mock_authenticate, mock_parent_init):
|
|
596
|
+
"""Test get_asset_id_from_node for CONFIGURATION type."""
|
|
597
|
+
mock_authenticate.return_value = None
|
|
598
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
599
|
+
|
|
600
|
+
node = {"resource": {"id": "resource-123"}}
|
|
601
|
+
result = integration.get_asset_id_from_node(node, WizVulnerabilityType.CONFIGURATION)
|
|
602
|
+
self.assertEqual(result, "resource-123")
|
|
603
|
+
|
|
604
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
605
|
+
def test_get_asset_id_from_node_issue_type(self, mock_authenticate, mock_parent_init):
|
|
606
|
+
"""Test get_asset_id_from_node for ISSUE type."""
|
|
607
|
+
mock_authenticate.return_value = None
|
|
608
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
609
|
+
|
|
610
|
+
node = {"entitySnapshot": {"id": "entity-456"}}
|
|
611
|
+
result = integration.get_asset_id_from_node(node, WizVulnerabilityType.ISSUE)
|
|
612
|
+
self.assertEqual(result, "entity-456")
|
|
613
|
+
|
|
614
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
615
|
+
def test_get_asset_id_from_node_with_fallback(self, mock_authenticate, mock_parent_init):
|
|
616
|
+
"""Test get_asset_id_from_node uses fallback when primary key not found."""
|
|
617
|
+
mock_authenticate.return_value = None
|
|
618
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
619
|
+
|
|
620
|
+
# Node doesn't have vulnerableAsset, but has resource
|
|
621
|
+
node = {"resource": {"id": "fallback-resource"}}
|
|
622
|
+
result = integration.get_asset_id_from_node(node, WizVulnerabilityType.VULNERABILITY)
|
|
623
|
+
self.assertEqual(result, "fallback-resource")
|
|
624
|
+
|
|
625
|
+
# ========================================
|
|
626
|
+
# Get Provider Unique ID from Node Tests
|
|
627
|
+
# ========================================
|
|
628
|
+
|
|
629
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
630
|
+
def test_get_provider_unique_id_from_node_issue_type(self, mock_authenticate, mock_parent_init):
|
|
631
|
+
"""Test get_provider_unique_id_from_node for ISSUE type."""
|
|
632
|
+
mock_authenticate.return_value = None
|
|
633
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
634
|
+
|
|
635
|
+
node = {"entitySnapshot": {"providerId": "provider-issue-123"}}
|
|
636
|
+
result = integration.get_provider_unique_id_from_node(node, WizVulnerabilityType.ISSUE)
|
|
637
|
+
self.assertEqual(result, "provider-issue-123")
|
|
638
|
+
|
|
639
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
640
|
+
def test_get_provider_unique_id_from_node_issue_type_fallback(self, mock_authenticate, mock_parent_init):
|
|
641
|
+
"""Test get_provider_unique_id_from_node for ISSUE type with fallback."""
|
|
642
|
+
mock_authenticate.return_value = None
|
|
643
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
644
|
+
|
|
645
|
+
node = {"entitySnapshot": {"name": "issue-name"}}
|
|
646
|
+
result = integration.get_provider_unique_id_from_node(node, WizVulnerabilityType.ISSUE)
|
|
647
|
+
self.assertEqual(result, "issue-name")
|
|
648
|
+
|
|
649
|
+
# ========================================
|
|
650
|
+
# Parse Finding Tests
|
|
651
|
+
# ========================================
|
|
652
|
+
|
|
653
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
654
|
+
def test_parse_finding_configuration_type(self, mock_authenticate, mock_parent_init):
|
|
655
|
+
"""Test parse_finding routes to generic parsing for CONFIGURATION type."""
|
|
656
|
+
mock_authenticate.return_value = None
|
|
657
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
658
|
+
|
|
659
|
+
node = {
|
|
660
|
+
"id": "config-1",
|
|
661
|
+
"name": "Configuration Finding",
|
|
662
|
+
"severity": "HIGH",
|
|
663
|
+
"status": "OPEN",
|
|
664
|
+
"resource": {"id": "resource-1"},
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
with patch.object(integration, "_parse_generic_finding") as mock_parse:
|
|
668
|
+
mock_parse.return_value = Mock()
|
|
669
|
+
finding = integration.parse_finding(node, WizVulnerabilityType.CONFIGURATION)
|
|
670
|
+
self.assertIsNotNone(finding)
|
|
671
|
+
mock_parse.assert_called_once()
|
|
672
|
+
|
|
673
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
674
|
+
def test_parse_finding_error_handling(self, mock_authenticate, mock_parent_init):
|
|
675
|
+
"""Test parse_finding handles errors gracefully."""
|
|
676
|
+
mock_authenticate.return_value = None
|
|
677
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
678
|
+
|
|
679
|
+
node = {"id": "error-node"}
|
|
680
|
+
|
|
681
|
+
with patch.object(integration, "_parse_generic_finding", side_effect=KeyError("Missing key")):
|
|
682
|
+
finding = integration.parse_finding(node, WizVulnerabilityType.VULNERABILITY)
|
|
683
|
+
self.assertIsNone(finding)
|
|
684
|
+
|
|
685
|
+
# ========================================
|
|
686
|
+
# Severity Filtering Tests
|
|
687
|
+
# ========================================
|
|
688
|
+
|
|
689
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
690
|
+
def test_should_process_finding_by_severity_unknown_severity(self, mock_authenticate, mock_parent_init):
|
|
691
|
+
"""Test should_process_finding_by_severity with unknown severity defaults to processing."""
|
|
692
|
+
mock_authenticate.return_value = None
|
|
693
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
694
|
+
self._initialize_scanner_attributes(integration)
|
|
695
|
+
integration.app.config["scanners"] = {"wiz": {"minimumSeverity": "low"}}
|
|
696
|
+
|
|
697
|
+
result = integration.should_process_finding_by_severity("UNKNOWN_SEVERITY")
|
|
698
|
+
self.assertTrue(result)
|
|
699
|
+
|
|
700
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
701
|
+
def test_should_process_finding_by_severity_value_error(self, mock_authenticate, mock_parent_init):
|
|
702
|
+
"""Test should_process_finding_by_severity handles ValueError gracefully."""
|
|
703
|
+
mock_authenticate.return_value = None
|
|
704
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
705
|
+
self._initialize_scanner_attributes(integration)
|
|
706
|
+
integration.app.config["scanners"] = {"wiz": {"minimumSeverity": "invalid_severity"}}
|
|
707
|
+
|
|
708
|
+
result = integration.should_process_finding_by_severity("HIGH")
|
|
709
|
+
# Should default to processing when config is invalid
|
|
710
|
+
self.assertTrue(result)
|
|
711
|
+
|
|
712
|
+
# ========================================
|
|
713
|
+
# Finding Data Extraction Tests
|
|
714
|
+
# ========================================
|
|
715
|
+
|
|
716
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
717
|
+
def test_get_secret_finding_data(self, mock_authenticate, mock_parent_init):
|
|
718
|
+
"""Test _get_secret_finding_data extracts data correctly."""
|
|
719
|
+
mock_authenticate.return_value = None
|
|
720
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
721
|
+
|
|
722
|
+
node = {
|
|
723
|
+
"type": "AWS_SECRET_KEY",
|
|
724
|
+
"resource": {"name": "test-resource"},
|
|
725
|
+
"confidence": "High",
|
|
726
|
+
"isEncrypted": False,
|
|
727
|
+
"isManaged": True,
|
|
728
|
+
"rule": {"name": "AWS Secret Detection"},
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
result = integration._get_secret_finding_data(node)
|
|
732
|
+
self.assertEqual(result["category"], "Wiz Secret Detection")
|
|
733
|
+
self.assertIn("AWS_SECRET_KEY", result["title"])
|
|
734
|
+
self.assertIn("Confidence: High", result["description"])
|
|
735
|
+
|
|
736
|
+
# ========================================
|
|
737
|
+
# Status Mapping Tests
|
|
738
|
+
# ========================================
|
|
739
|
+
|
|
740
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
741
|
+
def test_map_wiz_status_active(self, mock_authenticate, mock_parent_init):
|
|
742
|
+
"""Test map_wiz_status for Active status."""
|
|
743
|
+
mock_authenticate.return_value = None
|
|
744
|
+
status = WizVulnerabilityIntegration.map_wiz_status("Active")
|
|
745
|
+
self.assertEqual(status, regscale_models.AssetStatus.Active)
|
|
746
|
+
|
|
747
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
748
|
+
def test_map_wiz_status_inactive(self, mock_authenticate, mock_parent_init):
|
|
749
|
+
"""Test map_wiz_status for Inactive status."""
|
|
750
|
+
mock_authenticate.return_value = None
|
|
751
|
+
status = WizVulnerabilityIntegration.map_wiz_status("Inactive")
|
|
752
|
+
self.assertEqual(status, regscale_models.AssetStatus.Inactive)
|
|
753
|
+
|
|
754
|
+
# ========================================
|
|
755
|
+
# Process Comments Tests
|
|
756
|
+
# ========================================
|
|
757
|
+
|
|
758
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
759
|
+
def test_process_comments_empty_edges(self, mock_authenticate, mock_parent_init):
|
|
760
|
+
"""Test process_comments with empty edges."""
|
|
761
|
+
mock_authenticate.return_value = None
|
|
762
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
763
|
+
|
|
764
|
+
comments_dict = {"comments": {"edges": []}}
|
|
765
|
+
result = integration.process_comments(comments_dict)
|
|
766
|
+
self.assertIsNone(result)
|
|
767
|
+
|
|
768
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
769
|
+
def test_process_comments_with_data(self, mock_authenticate, mock_parent_init):
|
|
770
|
+
"""Test process_comments with comment data."""
|
|
771
|
+
mock_authenticate.return_value = None
|
|
772
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
773
|
+
|
|
774
|
+
comments_dict = {
|
|
775
|
+
"comments": {
|
|
776
|
+
"edges": [
|
|
777
|
+
{"node": {"author": {"name": "John Doe"}, "body": "Comment 1"}},
|
|
778
|
+
{"node": {"author": {"name": "Jane Smith"}, "body": "Comment 2"}},
|
|
779
|
+
]
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
result = integration.process_comments(comments_dict)
|
|
783
|
+
self.assertIn("John Doe: Comment 1", result)
|
|
784
|
+
self.assertIn("Jane Smith: Comment 2", result)
|
|
785
|
+
|
|
786
|
+
# ========================================
|
|
787
|
+
# Integration Tests with Real Data
|
|
788
|
+
# ========================================
|
|
789
|
+
|
|
790
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
791
|
+
def test_parse_findings_integration_with_consolidation(self, mock_authenticate, mock_parent_init):
|
|
792
|
+
"""Integration test for parse_findings with consolidation."""
|
|
793
|
+
mock_authenticate.return_value = None
|
|
794
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
795
|
+
self._initialize_scanner_attributes(integration)
|
|
796
|
+
|
|
797
|
+
# Use real test data
|
|
798
|
+
findings = list(integration.parse_findings(vuln_nodes, WizVulnerabilityType.VULNERABILITY))
|
|
799
|
+
|
|
800
|
+
# Should have processed some findings
|
|
801
|
+
self.assertGreater(len(findings), 0)
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
if __name__ == "__main__":
|
|
805
|
+
unittest.main()
|