regscale-cli 6.20.9.1__py3-none-any.whl → 6.20.10.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/integrations/commercial/defender.py +9 -0
- regscale/integrations/commercial/wizv2/async_client.py +325 -0
- regscale/integrations/commercial/wizv2/constants.py +756 -0
- regscale/integrations/commercial/wizv2/scanner.py +1301 -89
- regscale/integrations/commercial/wizv2/utils.py +280 -36
- regscale/integrations/commercial/wizv2/variables.py +2 -10
- regscale/integrations/scanner_integration.py +58 -2
- regscale/integrations/variables.py +1 -0
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/__init__.py +13 -0
- regscale/models/regscale_models/classification.py +23 -0
- regscale/models/regscale_models/cryptography.py +56 -0
- regscale/models/regscale_models/deviation.py +4 -4
- regscale/models/regscale_models/group.py +3 -2
- regscale/models/regscale_models/interconnection.py +1 -1
- regscale/models/regscale_models/issue.py +140 -41
- regscale/models/regscale_models/milestone.py +40 -0
- regscale/models/regscale_models/property.py +0 -1
- regscale/models/regscale_models/regscale_model.py +29 -18
- regscale/models/regscale_models/team.py +55 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/RECORD +29 -23
- tests/regscale/integrations/test_property_and_milestone_creation.py +684 -0
- tests/regscale/models/test_report.py +105 -29
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for the _handle_property_and_milestone_creation method in ScannerIntegration.
|
|
5
|
+
|
|
6
|
+
This module provides comprehensive test coverage for the property and milestone creation
|
|
7
|
+
functionality in scanner integrations.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import unittest
|
|
11
|
+
from typing import Iterator, Optional
|
|
12
|
+
from unittest.mock import MagicMock, patch, call
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from regscale.integrations.scanner_integration import (
|
|
17
|
+
ScannerIntegration,
|
|
18
|
+
ScannerIntegrationType,
|
|
19
|
+
IntegrationFinding,
|
|
20
|
+
)
|
|
21
|
+
from regscale.integrations.variables import ScannerVariables
|
|
22
|
+
from regscale.models import regscale_models
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestScannerIntegration(ScannerIntegration):
|
|
26
|
+
"""
|
|
27
|
+
A concrete implementation of ScannerIntegration for testing purposes.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
title = "Test Scanner"
|
|
31
|
+
type = ScannerIntegrationType.VULNERABILITY
|
|
32
|
+
|
|
33
|
+
def fetch_assets(self, *args, **kwargs) -> Iterator[IntegrationFinding]:
|
|
34
|
+
"""Mock implementation for testing."""
|
|
35
|
+
return iter([])
|
|
36
|
+
|
|
37
|
+
def fetch_findings(self, *args, **kwargs) -> Iterator[IntegrationFinding]:
|
|
38
|
+
"""Mock implementation for testing."""
|
|
39
|
+
return iter([])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestPropertyAndMilestoneCreation:
|
|
43
|
+
"""
|
|
44
|
+
Test cases for the _handle_property_and_milestone_creation method.
|
|
45
|
+
|
|
46
|
+
This class provides comprehensive test coverage for all code paths in the method,
|
|
47
|
+
including property creation for POC and CWE, and milestone creation for various
|
|
48
|
+
issue status transitions.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@pytest.fixture(autouse=True)
|
|
52
|
+
def setup_scanner(self) -> TestScannerIntegration:
|
|
53
|
+
"""
|
|
54
|
+
Set up a test scanner instance with mocked dependencies.
|
|
55
|
+
|
|
56
|
+
:return: Configured test scanner instance
|
|
57
|
+
:rtype: TestScannerIntegration
|
|
58
|
+
"""
|
|
59
|
+
scanner = TestScannerIntegration(plan_id=1, tenant_id=1)
|
|
60
|
+
scanner.assessor_id = "test_assessor"
|
|
61
|
+
scanner.scan_date = "2024-01-01 00:00:00"
|
|
62
|
+
return scanner
|
|
63
|
+
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def mock_issue(self) -> MagicMock:
|
|
66
|
+
"""
|
|
67
|
+
Create a mock issue for testing.
|
|
68
|
+
|
|
69
|
+
:return: Mock issue object
|
|
70
|
+
:rtype: MagicMock
|
|
71
|
+
"""
|
|
72
|
+
issue = MagicMock(spec=regscale_models.Issue)
|
|
73
|
+
issue.id = 123
|
|
74
|
+
issue.status = regscale_models.IssueStatus.Open
|
|
75
|
+
issue.dateCompleted = "2024-01-02 00:00:00"
|
|
76
|
+
return issue
|
|
77
|
+
|
|
78
|
+
@pytest.fixture
|
|
79
|
+
def mock_finding(self) -> MagicMock:
|
|
80
|
+
"""
|
|
81
|
+
Create a mock finding for testing.
|
|
82
|
+
|
|
83
|
+
:return: Mock finding object
|
|
84
|
+
:rtype: MagicMock
|
|
85
|
+
"""
|
|
86
|
+
finding = MagicMock(spec=IntegrationFinding)
|
|
87
|
+
finding.external_id = "test_external_id"
|
|
88
|
+
finding.point_of_contact = None
|
|
89
|
+
finding.is_cwe = False
|
|
90
|
+
finding.plugin_id = "CWE-123"
|
|
91
|
+
return finding
|
|
92
|
+
|
|
93
|
+
@pytest.fixture
|
|
94
|
+
def mock_property_class(self) -> MagicMock:
|
|
95
|
+
"""
|
|
96
|
+
Create a mock Property class for testing.
|
|
97
|
+
|
|
98
|
+
:return: Mock Property class
|
|
99
|
+
:rtype: MagicMock
|
|
100
|
+
"""
|
|
101
|
+
return MagicMock(spec=regscale_models.Property)
|
|
102
|
+
|
|
103
|
+
@pytest.fixture
|
|
104
|
+
def mock_milestone_class(self) -> MagicMock:
|
|
105
|
+
"""
|
|
106
|
+
Create a mock Milestone class for testing.
|
|
107
|
+
|
|
108
|
+
:return: Mock Milestone class
|
|
109
|
+
:rtype: MagicMock
|
|
110
|
+
"""
|
|
111
|
+
return MagicMock(spec=regscale_models.Milestone)
|
|
112
|
+
|
|
113
|
+
def test_handle_property_and_milestone_creation_no_properties_no_milestones(
|
|
114
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
115
|
+
):
|
|
116
|
+
"""
|
|
117
|
+
Test when no properties or milestones should be created.
|
|
118
|
+
|
|
119
|
+
This test covers the case where:
|
|
120
|
+
- finding.point_of_contact is None
|
|
121
|
+
- finding.is_cwe is False
|
|
122
|
+
- ScannerVariables.useMilestones is False
|
|
123
|
+
"""
|
|
124
|
+
# Arrange
|
|
125
|
+
scanner = setup_scanner
|
|
126
|
+
issue = mock_issue
|
|
127
|
+
finding = mock_finding
|
|
128
|
+
|
|
129
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
130
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
131
|
+
), patch.object(ScannerVariables, "useMilestones", False):
|
|
132
|
+
|
|
133
|
+
# Act
|
|
134
|
+
scanner._handle_property_and_milestone_creation(issue, finding)
|
|
135
|
+
|
|
136
|
+
# Assert
|
|
137
|
+
mock_property_class.assert_not_called()
|
|
138
|
+
mock_milestone_class.assert_not_called()
|
|
139
|
+
|
|
140
|
+
def test_handle_property_and_milestone_creation_with_poc_property(
|
|
141
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
142
|
+
):
|
|
143
|
+
"""
|
|
144
|
+
Test creation of POC property when point_of_contact is provided.
|
|
145
|
+
|
|
146
|
+
This test covers the case where:
|
|
147
|
+
- finding.point_of_contact has a value
|
|
148
|
+
- finding.is_cwe is False
|
|
149
|
+
- ScannerVariables.useMilestones is False
|
|
150
|
+
"""
|
|
151
|
+
# Arrange
|
|
152
|
+
scanner = setup_scanner
|
|
153
|
+
issue = mock_issue
|
|
154
|
+
finding = mock_finding
|
|
155
|
+
finding.point_of_contact = "test_poc@example.com"
|
|
156
|
+
|
|
157
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
158
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
159
|
+
), patch.object(ScannerVariables, "useMilestones", False):
|
|
160
|
+
|
|
161
|
+
# Act
|
|
162
|
+
scanner._handle_property_and_milestone_creation(issue, finding)
|
|
163
|
+
|
|
164
|
+
# Assert
|
|
165
|
+
mock_property_class.assert_called_once_with(
|
|
166
|
+
key="POC",
|
|
167
|
+
value="test_poc@example.com",
|
|
168
|
+
parentId=123,
|
|
169
|
+
parentModule="issues",
|
|
170
|
+
)
|
|
171
|
+
mock_property_class.return_value.create_or_update.assert_called_once()
|
|
172
|
+
mock_milestone_class.assert_not_called()
|
|
173
|
+
|
|
174
|
+
def test_handle_property_and_milestone_creation_with_cwe_property(
|
|
175
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
176
|
+
):
|
|
177
|
+
"""
|
|
178
|
+
Test creation of CWE property when is_cwe is True.
|
|
179
|
+
|
|
180
|
+
This test covers the case where:
|
|
181
|
+
- finding.point_of_contact is None
|
|
182
|
+
- finding.is_cwe is True
|
|
183
|
+
- ScannerVariables.useMilestones is False
|
|
184
|
+
"""
|
|
185
|
+
# Arrange
|
|
186
|
+
scanner = setup_scanner
|
|
187
|
+
issue = mock_issue
|
|
188
|
+
finding = mock_finding
|
|
189
|
+
finding.is_cwe = True
|
|
190
|
+
|
|
191
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
192
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
193
|
+
), patch.object(ScannerVariables, "useMilestones", False):
|
|
194
|
+
|
|
195
|
+
# Act
|
|
196
|
+
scanner._handle_property_and_milestone_creation(issue, finding)
|
|
197
|
+
|
|
198
|
+
# Assert
|
|
199
|
+
mock_property_class.assert_called_once_with(
|
|
200
|
+
key="CWE",
|
|
201
|
+
value="CWE-123",
|
|
202
|
+
parentId=123,
|
|
203
|
+
parentModule="issues",
|
|
204
|
+
)
|
|
205
|
+
mock_property_class.return_value.create_or_update.assert_called_once()
|
|
206
|
+
mock_milestone_class.assert_not_called()
|
|
207
|
+
|
|
208
|
+
def test_handle_property_and_milestone_creation_with_both_properties(
|
|
209
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
210
|
+
):
|
|
211
|
+
"""
|
|
212
|
+
Test creation of both POC and CWE properties.
|
|
213
|
+
|
|
214
|
+
This test covers the case where:
|
|
215
|
+
- finding.point_of_contact has a value
|
|
216
|
+
- finding.is_cwe is True
|
|
217
|
+
- ScannerVariables.useMilestones is False
|
|
218
|
+
"""
|
|
219
|
+
# Arrange
|
|
220
|
+
scanner = setup_scanner
|
|
221
|
+
issue = mock_issue
|
|
222
|
+
finding = mock_finding
|
|
223
|
+
finding.point_of_contact = "test_poc@example.com"
|
|
224
|
+
finding.is_cwe = True
|
|
225
|
+
|
|
226
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
227
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
228
|
+
), patch.object(ScannerVariables, "useMilestones", False):
|
|
229
|
+
|
|
230
|
+
# Act
|
|
231
|
+
scanner._handle_property_and_milestone_creation(issue, finding)
|
|
232
|
+
|
|
233
|
+
# Assert
|
|
234
|
+
# Check that Property was called twice (once for each property)
|
|
235
|
+
assert mock_property_class.call_count == 2
|
|
236
|
+
|
|
237
|
+
# Check the first call (POC property)
|
|
238
|
+
first_call = mock_property_class.call_args_list[0]
|
|
239
|
+
assert first_call[1]["key"] == "POC"
|
|
240
|
+
assert first_call[1]["value"] == "test_poc@example.com"
|
|
241
|
+
assert first_call[1]["parentId"] == 123
|
|
242
|
+
assert first_call[1]["parentModule"] == "issues"
|
|
243
|
+
|
|
244
|
+
# Check the second call (CWE property)
|
|
245
|
+
second_call = mock_property_class.call_args_list[1]
|
|
246
|
+
assert second_call[1]["key"] == "CWE"
|
|
247
|
+
assert second_call[1]["value"] == "CWE-123"
|
|
248
|
+
assert second_call[1]["parentId"] == 123
|
|
249
|
+
assert second_call[1]["parentModule"] == "issues"
|
|
250
|
+
|
|
251
|
+
# Verify create_or_update was called for each property
|
|
252
|
+
assert mock_property_class.return_value.create_or_update.call_count == 2
|
|
253
|
+
mock_milestone_class.assert_not_called()
|
|
254
|
+
|
|
255
|
+
def test_handle_property_and_milestone_creation_milestones_disabled(
|
|
256
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
257
|
+
):
|
|
258
|
+
"""
|
|
259
|
+
Test that milestones are not created when useMilestones is False.
|
|
260
|
+
|
|
261
|
+
This test covers the case where:
|
|
262
|
+
- ScannerVariables.useMilestones is False
|
|
263
|
+
- All milestone creation logic should be skipped
|
|
264
|
+
"""
|
|
265
|
+
# Arrange
|
|
266
|
+
scanner = setup_scanner
|
|
267
|
+
issue = mock_issue
|
|
268
|
+
finding = mock_finding
|
|
269
|
+
|
|
270
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
271
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
272
|
+
), patch.object(ScannerVariables, "useMilestones", False):
|
|
273
|
+
|
|
274
|
+
# Act
|
|
275
|
+
scanner._handle_property_and_milestone_creation(issue, finding)
|
|
276
|
+
|
|
277
|
+
# Assert
|
|
278
|
+
mock_milestone_class.assert_not_called()
|
|
279
|
+
|
|
280
|
+
def test_handle_property_and_milestone_creation_issue_reopened_milestone(
|
|
281
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
282
|
+
):
|
|
283
|
+
"""
|
|
284
|
+
Test creation of milestone when issue is reopened.
|
|
285
|
+
|
|
286
|
+
This test covers the case where:
|
|
287
|
+
- ScannerVariables.useMilestones is True
|
|
288
|
+
- existing_issue.status is Closed
|
|
289
|
+
- issue.status is Open
|
|
290
|
+
"""
|
|
291
|
+
# Arrange
|
|
292
|
+
scanner = setup_scanner
|
|
293
|
+
issue = mock_issue
|
|
294
|
+
issue.status = regscale_models.IssueStatus.Open
|
|
295
|
+
finding = mock_finding
|
|
296
|
+
|
|
297
|
+
existing_issue = MagicMock(spec=regscale_models.Issue)
|
|
298
|
+
existing_issue.status = regscale_models.IssueStatus.Closed
|
|
299
|
+
|
|
300
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
301
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
302
|
+
), patch(
|
|
303
|
+
"regscale.integrations.scanner_integration.get_current_datetime", return_value="2024-01-03 00:00:00"
|
|
304
|
+
), patch.object(
|
|
305
|
+
ScannerVariables, "useMilestones", True
|
|
306
|
+
):
|
|
307
|
+
|
|
308
|
+
# Act
|
|
309
|
+
scanner._handle_property_and_milestone_creation(issue, finding, existing_issue)
|
|
310
|
+
|
|
311
|
+
# Assert
|
|
312
|
+
mock_milestone_class.assert_called_once_with(
|
|
313
|
+
title="Issue reopened from Test Scanner scan",
|
|
314
|
+
milestoneDate="2024-01-03 00:00:00",
|
|
315
|
+
responsiblePersonId="test_assessor",
|
|
316
|
+
parentID=123,
|
|
317
|
+
parentModule="issues",
|
|
318
|
+
)
|
|
319
|
+
mock_milestone_class.return_value.create_or_update.assert_called_once()
|
|
320
|
+
|
|
321
|
+
def test_handle_property_and_milestone_creation_issue_closed_milestone(
|
|
322
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
323
|
+
):
|
|
324
|
+
"""
|
|
325
|
+
Test creation of milestone when issue is closed.
|
|
326
|
+
|
|
327
|
+
This test covers the case where:
|
|
328
|
+
- ScannerVariables.useMilestones is True
|
|
329
|
+
- existing_issue.status is Open
|
|
330
|
+
- issue.status is Closed
|
|
331
|
+
"""
|
|
332
|
+
# Arrange
|
|
333
|
+
scanner = setup_scanner
|
|
334
|
+
issue = mock_issue
|
|
335
|
+
issue.status = regscale_models.IssueStatus.Closed
|
|
336
|
+
issue.dateCompleted = "2024-01-02 00:00:00"
|
|
337
|
+
finding = mock_finding
|
|
338
|
+
|
|
339
|
+
existing_issue = MagicMock(spec=regscale_models.Issue)
|
|
340
|
+
existing_issue.status = regscale_models.IssueStatus.Open
|
|
341
|
+
|
|
342
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
343
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
344
|
+
), patch.object(ScannerVariables, "useMilestones", True):
|
|
345
|
+
|
|
346
|
+
# Act
|
|
347
|
+
scanner._handle_property_and_milestone_creation(issue, finding, existing_issue)
|
|
348
|
+
|
|
349
|
+
# Assert
|
|
350
|
+
mock_milestone_class.assert_called_once_with(
|
|
351
|
+
title="Issue closed from Test Scanner scan",
|
|
352
|
+
milestoneDate="2024-01-02 00:00:00",
|
|
353
|
+
responsiblePersonId="test_assessor",
|
|
354
|
+
parentID=123,
|
|
355
|
+
parentModule="issues",
|
|
356
|
+
)
|
|
357
|
+
mock_milestone_class.return_value.create_or_update.assert_called_once()
|
|
358
|
+
|
|
359
|
+
def test_handle_property_and_milestone_creation_new_issue_milestone(
|
|
360
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
361
|
+
):
|
|
362
|
+
"""
|
|
363
|
+
Test creation of milestone for a new issue.
|
|
364
|
+
|
|
365
|
+
This test covers the case where:
|
|
366
|
+
- ScannerVariables.useMilestones is True
|
|
367
|
+
- existing_issue is None (new issue)
|
|
368
|
+
"""
|
|
369
|
+
# Arrange
|
|
370
|
+
scanner = setup_scanner
|
|
371
|
+
issue = mock_issue
|
|
372
|
+
finding = mock_finding
|
|
373
|
+
|
|
374
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
375
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
376
|
+
), patch.object(ScannerVariables, "useMilestones", True):
|
|
377
|
+
|
|
378
|
+
# Act
|
|
379
|
+
scanner._handle_property_and_milestone_creation(issue, finding)
|
|
380
|
+
|
|
381
|
+
# Assert
|
|
382
|
+
mock_milestone_class.assert_called_once_with(
|
|
383
|
+
title="Issue created from Test Scanner scan",
|
|
384
|
+
milestoneDate="2024-01-01 00:00:00",
|
|
385
|
+
responsiblePersonId="test_assessor",
|
|
386
|
+
parentID=123,
|
|
387
|
+
parentModule="issues",
|
|
388
|
+
)
|
|
389
|
+
mock_milestone_class.return_value.create_or_update.assert_called_once()
|
|
390
|
+
|
|
391
|
+
def test_handle_property_and_milestone_creation_no_milestone_created(
|
|
392
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
393
|
+
):
|
|
394
|
+
"""
|
|
395
|
+
Test case where no milestone is created due to status conditions.
|
|
396
|
+
|
|
397
|
+
This test covers the case where:
|
|
398
|
+
- ScannerVariables.useMilestones is True
|
|
399
|
+
- existing_issue.status is Open
|
|
400
|
+
- issue.status is Open (no status change)
|
|
401
|
+
"""
|
|
402
|
+
# Arrange
|
|
403
|
+
scanner = setup_scanner
|
|
404
|
+
issue = mock_issue
|
|
405
|
+
issue.status = regscale_models.IssueStatus.Open
|
|
406
|
+
finding = mock_finding
|
|
407
|
+
|
|
408
|
+
existing_issue = MagicMock(spec=regscale_models.Issue)
|
|
409
|
+
existing_issue.status = regscale_models.IssueStatus.Open
|
|
410
|
+
|
|
411
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
412
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
413
|
+
), patch.object(ScannerVariables, "useMilestones", True):
|
|
414
|
+
|
|
415
|
+
# Act
|
|
416
|
+
scanner._handle_property_and_milestone_creation(issue, finding, existing_issue)
|
|
417
|
+
|
|
418
|
+
# Assert
|
|
419
|
+
mock_milestone_class.assert_not_called()
|
|
420
|
+
|
|
421
|
+
def test_handle_property_and_milestone_creation_comprehensive_scenario(
|
|
422
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
423
|
+
):
|
|
424
|
+
"""
|
|
425
|
+
Test comprehensive scenario with all properties and milestone creation.
|
|
426
|
+
|
|
427
|
+
This test covers the case where:
|
|
428
|
+
- finding.point_of_contact has a value
|
|
429
|
+
- finding.is_cwe is True
|
|
430
|
+
- ScannerVariables.useMilestones is True
|
|
431
|
+
- Issue is being reopened (milestone creation)
|
|
432
|
+
"""
|
|
433
|
+
# Arrange
|
|
434
|
+
scanner = setup_scanner
|
|
435
|
+
issue = mock_issue
|
|
436
|
+
issue.status = regscale_models.IssueStatus.Open
|
|
437
|
+
finding = mock_finding
|
|
438
|
+
finding.point_of_contact = "test_poc@example.com"
|
|
439
|
+
finding.is_cwe = True
|
|
440
|
+
|
|
441
|
+
existing_issue = MagicMock(spec=regscale_models.Issue)
|
|
442
|
+
existing_issue.status = regscale_models.IssueStatus.Closed
|
|
443
|
+
|
|
444
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
445
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
446
|
+
), patch(
|
|
447
|
+
"regscale.integrations.scanner_integration.get_current_datetime", return_value="2024-01-03 00:00:00"
|
|
448
|
+
), patch.object(
|
|
449
|
+
ScannerVariables, "useMilestones", True
|
|
450
|
+
):
|
|
451
|
+
|
|
452
|
+
# Act
|
|
453
|
+
scanner._handle_property_and_milestone_creation(issue, finding, existing_issue)
|
|
454
|
+
|
|
455
|
+
# Assert
|
|
456
|
+
# Check that Property was called twice (once for each property)
|
|
457
|
+
assert mock_property_class.call_count == 2
|
|
458
|
+
|
|
459
|
+
# Check the first call (POC property)
|
|
460
|
+
first_call = mock_property_class.call_args_list[0]
|
|
461
|
+
assert first_call[1]["key"] == "POC"
|
|
462
|
+
assert first_call[1]["value"] == "test_poc@example.com"
|
|
463
|
+
assert first_call[1]["parentId"] == 123
|
|
464
|
+
assert first_call[1]["parentModule"] == "issues"
|
|
465
|
+
|
|
466
|
+
# Check the second call (CWE property)
|
|
467
|
+
second_call = mock_property_class.call_args_list[1]
|
|
468
|
+
assert second_call[1]["key"] == "CWE"
|
|
469
|
+
assert second_call[1]["value"] == "CWE-123"
|
|
470
|
+
assert second_call[1]["parentId"] == 123
|
|
471
|
+
assert second_call[1]["parentModule"] == "issues"
|
|
472
|
+
|
|
473
|
+
# Check milestone creation call
|
|
474
|
+
mock_milestone_class.assert_called_once_with(
|
|
475
|
+
title="Issue reopened from Test Scanner scan",
|
|
476
|
+
milestoneDate="2024-01-03 00:00:00",
|
|
477
|
+
responsiblePersonId="test_assessor",
|
|
478
|
+
parentID=123,
|
|
479
|
+
parentModule="issues",
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Verify create_or_update was called for all created objects
|
|
483
|
+
assert mock_property_class.return_value.create_or_update.call_count == 2
|
|
484
|
+
mock_milestone_class.return_value.create_or_update.assert_called_once()
|
|
485
|
+
|
|
486
|
+
def test_handle_property_and_milestone_creation_with_logging(
|
|
487
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
488
|
+
):
|
|
489
|
+
"""
|
|
490
|
+
Test that logging occurs when properties and milestones are created.
|
|
491
|
+
|
|
492
|
+
This test verifies that debug logging is called appropriately.
|
|
493
|
+
"""
|
|
494
|
+
# Arrange
|
|
495
|
+
scanner = setup_scanner
|
|
496
|
+
issue = mock_issue
|
|
497
|
+
finding = mock_finding
|
|
498
|
+
finding.point_of_contact = "test_poc@example.com"
|
|
499
|
+
finding.is_cwe = True
|
|
500
|
+
|
|
501
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
502
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
503
|
+
), patch("regscale.integrations.scanner_integration.logger") as mock_logger, patch.object(
|
|
504
|
+
ScannerVariables, "useMilestones", True
|
|
505
|
+
):
|
|
506
|
+
|
|
507
|
+
# Act
|
|
508
|
+
scanner._handle_property_and_milestone_creation(issue, finding)
|
|
509
|
+
|
|
510
|
+
# Assert
|
|
511
|
+
# Verify debug logging calls
|
|
512
|
+
expected_log_calls = [
|
|
513
|
+
call.debug("Added POC property %s to issue %s", "test_poc@example.com", 123),
|
|
514
|
+
call.debug("Added CWE property %s to issue %s", "CWE-123", 123),
|
|
515
|
+
call.debug("Created milestone for issue %s from finding %s", 123, "test_external_id"),
|
|
516
|
+
]
|
|
517
|
+
mock_logger.assert_has_calls(expected_log_calls)
|
|
518
|
+
|
|
519
|
+
def test_handle_property_and_milestone_creation_no_milestone_logging(
|
|
520
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
521
|
+
):
|
|
522
|
+
"""
|
|
523
|
+
Test logging when no milestone is created.
|
|
524
|
+
|
|
525
|
+
This test verifies that the appropriate debug log is called when no milestone
|
|
526
|
+
is created due to status conditions.
|
|
527
|
+
"""
|
|
528
|
+
# Arrange
|
|
529
|
+
scanner = setup_scanner
|
|
530
|
+
issue = mock_issue
|
|
531
|
+
issue.status = regscale_models.IssueStatus.Open
|
|
532
|
+
finding = mock_finding
|
|
533
|
+
|
|
534
|
+
existing_issue = MagicMock(spec=regscale_models.Issue)
|
|
535
|
+
existing_issue.status = regscale_models.IssueStatus.Open
|
|
536
|
+
|
|
537
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
538
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
539
|
+
), patch("regscale.integrations.scanner_integration.logger") as mock_logger, patch.object(
|
|
540
|
+
ScannerVariables, "useMilestones", True
|
|
541
|
+
):
|
|
542
|
+
|
|
543
|
+
# Act
|
|
544
|
+
scanner._handle_property_and_milestone_creation(issue, finding, existing_issue)
|
|
545
|
+
|
|
546
|
+
# Assert
|
|
547
|
+
mock_logger.debug.assert_called_with(
|
|
548
|
+
"No milestone created for issue %s from finding %s", 123, "test_external_id"
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
def test_handle_property_and_milestone_creation_edge_cases(
|
|
552
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
553
|
+
):
|
|
554
|
+
"""
|
|
555
|
+
Test edge cases and boundary conditions.
|
|
556
|
+
|
|
557
|
+
This test covers various edge cases to ensure robust behavior.
|
|
558
|
+
"""
|
|
559
|
+
# Arrange
|
|
560
|
+
scanner = setup_scanner
|
|
561
|
+
issue = mock_issue
|
|
562
|
+
finding = mock_finding
|
|
563
|
+
|
|
564
|
+
# Test with empty string values
|
|
565
|
+
finding.point_of_contact = ""
|
|
566
|
+
finding.is_cwe = True
|
|
567
|
+
|
|
568
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
569
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
570
|
+
), patch.object(ScannerVariables, "useMilestones", False):
|
|
571
|
+
|
|
572
|
+
# Act
|
|
573
|
+
scanner._handle_property_and_milestone_creation(issue, finding)
|
|
574
|
+
|
|
575
|
+
# Assert
|
|
576
|
+
# Should only create CWE property (empty string is falsy for POC)
|
|
577
|
+
mock_property_class.assert_called_once_with(
|
|
578
|
+
key="CWE",
|
|
579
|
+
value="CWE-123",
|
|
580
|
+
parentId=123,
|
|
581
|
+
parentModule="issues",
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
def test_handle_property_and_milestone_creation_with_none_values(
|
|
585
|
+
self, setup_scanner, mock_issue, mock_finding, mock_property_class, mock_milestone_class
|
|
586
|
+
):
|
|
587
|
+
"""
|
|
588
|
+
Test behavior with None values for optional fields.
|
|
589
|
+
|
|
590
|
+
This test ensures the method handles None values gracefully.
|
|
591
|
+
"""
|
|
592
|
+
# Arrange
|
|
593
|
+
scanner = setup_scanner
|
|
594
|
+
issue = mock_issue
|
|
595
|
+
finding = mock_finding
|
|
596
|
+
finding.point_of_contact = None
|
|
597
|
+
finding.is_cwe = False
|
|
598
|
+
finding.plugin_id = None
|
|
599
|
+
|
|
600
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
601
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
602
|
+
), patch.object(ScannerVariables, "useMilestones", True):
|
|
603
|
+
|
|
604
|
+
# Act
|
|
605
|
+
scanner._handle_property_and_milestone_creation(issue, finding)
|
|
606
|
+
|
|
607
|
+
# Assert
|
|
608
|
+
# Should only create milestone (no properties due to None values)
|
|
609
|
+
mock_property_class.assert_not_called()
|
|
610
|
+
mock_milestone_class.assert_called_once()
|
|
611
|
+
|
|
612
|
+
@pytest.mark.parametrize(
|
|
613
|
+
"issue_status,existing_issue_status,expected_title,expected_date",
|
|
614
|
+
[
|
|
615
|
+
(
|
|
616
|
+
regscale_models.IssueStatus.Open,
|
|
617
|
+
regscale_models.IssueStatus.Closed,
|
|
618
|
+
"Issue reopened from Test Scanner scan",
|
|
619
|
+
"2024-01-03 00:00:00",
|
|
620
|
+
),
|
|
621
|
+
(
|
|
622
|
+
regscale_models.IssueStatus.Closed,
|
|
623
|
+
regscale_models.IssueStatus.Open,
|
|
624
|
+
"Issue closed from Test Scanner scan",
|
|
625
|
+
"2024-01-02 00:00:00",
|
|
626
|
+
),
|
|
627
|
+
(regscale_models.IssueStatus.Open, None, "Issue created from Test Scanner scan", "2024-01-01 00:00:00"),
|
|
628
|
+
],
|
|
629
|
+
)
|
|
630
|
+
def test_handle_property_and_milestone_creation_milestone_status_transitions(
|
|
631
|
+
self,
|
|
632
|
+
setup_scanner,
|
|
633
|
+
mock_issue,
|
|
634
|
+
mock_finding,
|
|
635
|
+
mock_property_class,
|
|
636
|
+
mock_milestone_class,
|
|
637
|
+
issue_status,
|
|
638
|
+
existing_issue_status,
|
|
639
|
+
expected_title,
|
|
640
|
+
expected_date,
|
|
641
|
+
):
|
|
642
|
+
"""
|
|
643
|
+
Test all possible milestone status transitions.
|
|
644
|
+
|
|
645
|
+
This test ensures all milestone creation scenarios are covered.
|
|
646
|
+
"""
|
|
647
|
+
# Arrange
|
|
648
|
+
scanner = setup_scanner
|
|
649
|
+
finding = mock_finding
|
|
650
|
+
|
|
651
|
+
# Reset mocks
|
|
652
|
+
mock_property_class.reset_mock()
|
|
653
|
+
mock_milestone_class.reset_mock()
|
|
654
|
+
|
|
655
|
+
# Setup issue
|
|
656
|
+
issue = mock_issue
|
|
657
|
+
issue.status = issue_status
|
|
658
|
+
|
|
659
|
+
# Setup existing issue if needed
|
|
660
|
+
existing_issue = None
|
|
661
|
+
if existing_issue_status is not None:
|
|
662
|
+
existing_issue = MagicMock(spec=regscale_models.Issue)
|
|
663
|
+
existing_issue.status = existing_issue_status
|
|
664
|
+
|
|
665
|
+
with patch("regscale.integrations.scanner_integration.regscale_models.Property", mock_property_class), patch(
|
|
666
|
+
"regscale.integrations.scanner_integration.regscale_models.Milestone", mock_milestone_class
|
|
667
|
+
), patch(
|
|
668
|
+
"regscale.integrations.scanner_integration.get_current_datetime", return_value="2024-01-03 00:00:00"
|
|
669
|
+
), patch.object(
|
|
670
|
+
ScannerVariables, "useMilestones", True
|
|
671
|
+
):
|
|
672
|
+
|
|
673
|
+
# Act
|
|
674
|
+
scanner._handle_property_and_milestone_creation(issue, finding, existing_issue)
|
|
675
|
+
|
|
676
|
+
# Assert
|
|
677
|
+
mock_milestone_class.assert_called_once_with(
|
|
678
|
+
title=expected_title,
|
|
679
|
+
milestoneDate=expected_date,
|
|
680
|
+
responsiblePersonId="test_assessor",
|
|
681
|
+
parentID=123,
|
|
682
|
+
parentModule="issues",
|
|
683
|
+
)
|
|
684
|
+
mock_milestone_class.return_value.create_or_update.assert_called_once()
|