regscale-cli 6.20.9.1__py3-none-any.whl → 6.21.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 +12 -5
- regscale/core/app/internal/set_permissions.py +58 -27
- regscale/integrations/commercial/defender.py +9 -0
- regscale/integrations/commercial/nessus/scanner.py +2 -0
- regscale/integrations/commercial/sonarcloud.py +35 -36
- regscale/integrations/commercial/synqly/ticketing.py +51 -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/integration_override.py +15 -6
- regscale/integrations/scanner_integration.py +221 -37
- regscale/integrations/variables.py +1 -0
- regscale/models/integration_models/amazon_models/inspector_scan.py +32 -57
- regscale/models/integration_models/aqua.py +92 -78
- regscale/models/integration_models/cisa_kev_data.json +47 -4
- regscale/models/integration_models/defenderimport.py +64 -59
- regscale/models/integration_models/ecr_models/ecr.py +100 -147
- regscale/models/integration_models/flat_file_importer/__init__.py +52 -38
- regscale/models/integration_models/ibm.py +29 -47
- regscale/models/integration_models/nexpose.py +156 -68
- regscale/models/integration_models/prisma.py +46 -66
- regscale/models/integration_models/qualys.py +99 -93
- regscale/models/integration_models/snyk.py +229 -158
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/veracode.py +15 -20
- regscale/models/integration_models/xray.py +276 -82
- regscale/models/regscale_models/__init__.py +13 -0
- regscale/models/regscale_models/classification.py +23 -0
- regscale/models/regscale_models/control_implementation.py +14 -12
- 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/rbac.py +22 -0
- 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.21.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/RECORD +56 -49
- tests/fixtures/test_fixture.py +58 -2
- tests/regscale/core/test_app.py +5 -3
- tests/regscale/integrations/test_integration_mapping.py +522 -40
- tests/regscale/integrations/test_issue_due_date.py +1 -1
- tests/regscale/integrations/test_property_and_milestone_creation.py +684 -0
- tests/regscale/integrations/test_update_finding_dates.py +336 -0
- tests/regscale/models/test_asset.py +406 -50
- tests/regscale/models/test_report.py +105 -29
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.21.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"""
|
|
2
|
+
Tests for the IntegrationOverride class.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive test coverage for the IntegrationOverride class,
|
|
5
|
+
which handles custom integration mappings for findings and field validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
from unittest.mock import MagicMock, Mock, patch
|
|
3
10
|
|
|
4
11
|
import pytest
|
|
5
12
|
|
|
@@ -7,8 +14,9 @@ from regscale.core.app.application import Application
|
|
|
7
14
|
from regscale.integrations.integration_override import IntegrationOverride
|
|
8
15
|
|
|
9
16
|
|
|
10
|
-
@pytest.fixture
|
|
11
|
-
def
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def basic_config() -> Dict[str, Any]:
|
|
19
|
+
"""Basic configuration fixture with finding mappings."""
|
|
12
20
|
return {
|
|
13
21
|
"findingFromMapping": {
|
|
14
22
|
"tenable_sc": {
|
|
@@ -16,45 +24,519 @@ def config():
|
|
|
16
24
|
"description": "details",
|
|
17
25
|
"remediation": "rando",
|
|
18
26
|
"title": "default",
|
|
19
|
-
}
|
|
27
|
+
},
|
|
28
|
+
"qualys": {
|
|
29
|
+
"severity": "cvss_score",
|
|
30
|
+
"description": "summary",
|
|
31
|
+
"solution": "fix",
|
|
32
|
+
},
|
|
20
33
|
}
|
|
21
34
|
}
|
|
22
35
|
|
|
23
36
|
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def config_with_unique_override() -> Dict[str, Any]:
|
|
39
|
+
"""Configuration fixture with unique override settings."""
|
|
40
|
+
return {
|
|
41
|
+
"findingFromMapping": {
|
|
42
|
+
"tenable_sc": {
|
|
43
|
+
"severity": "risk_level",
|
|
44
|
+
"description": "details",
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"uniqueOverride": {"asset": ["ipAddress"], "issue": ["title"]},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.fixture
|
|
52
|
+
def mock_app(basic_config: Dict[str, Any]) -> Application:
|
|
53
|
+
"""Mock application fixture with basic configuration."""
|
|
26
54
|
app = Application()
|
|
27
|
-
|
|
28
|
-
app.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# Test loading existing mapping
|
|
36
|
-
assert integration_mapping.load("tenable_sc", "severity") == "risk_level"
|
|
37
|
-
assert integration_mapping.load("tenable_sc", "description") == "details"
|
|
38
|
-
assert integration_mapping.load("tenable_sc", "remediation") == "rando"
|
|
39
|
-
|
|
40
|
-
# Test loading non-existing mapping
|
|
41
|
-
assert integration_mapping.load("tenable_sc", "title") is None # default is None
|
|
42
|
-
assert integration_mapping.load("tenable_sc", "non_existing_field") is None
|
|
43
|
-
assert integration_mapping.load("non_existing_integration", "severity") is None
|
|
44
|
-
# Uber fail
|
|
45
|
-
assert integration_mapping.load(None, None) is None
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def test_no_config():
|
|
49
|
-
mock_app = MagicMock()
|
|
50
|
-
mock_app.config = {}
|
|
51
|
-
integration_mapping = IntegrationOverride(mock_app)
|
|
52
|
-
assert integration_mapping.load("tenable_sc", "remediation") is None
|
|
53
|
-
assert integration_mapping.load(None, None) is None
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def test_singleton():
|
|
55
|
+
app.config = basic_config
|
|
56
|
+
app.logger = MagicMock()
|
|
57
|
+
return app
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def mock_app_with_unique_override(config_with_unique_override: Dict[str, Any]) -> Application:
|
|
62
|
+
"""Mock application fixture with unique override configuration."""
|
|
57
63
|
app = Application()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
app.config = config_with_unique_override
|
|
65
|
+
app.logger = MagicMock()
|
|
66
|
+
return app
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.fixture
|
|
70
|
+
def empty_config_app() -> Application:
|
|
71
|
+
"""Mock application fixture with empty configuration."""
|
|
72
|
+
app = Application()
|
|
73
|
+
app.config = {}
|
|
74
|
+
app.logger = MagicMock()
|
|
75
|
+
return app
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestIntegrationOverrideSingleton:
|
|
79
|
+
"""Test the singleton pattern implementation."""
|
|
80
|
+
|
|
81
|
+
def test_singleton_pattern(self, mock_app: Application):
|
|
82
|
+
"""Test that IntegrationOverride follows singleton pattern."""
|
|
83
|
+
# Reset singleton instance for clean test
|
|
84
|
+
IntegrationOverride._instance = None
|
|
85
|
+
|
|
86
|
+
instance1 = IntegrationOverride(mock_app)
|
|
87
|
+
instance2 = IntegrationOverride(mock_app)
|
|
88
|
+
|
|
89
|
+
assert instance1 is instance2
|
|
90
|
+
assert id(instance1) == id(instance2)
|
|
91
|
+
|
|
92
|
+
def test_singleton_thread_safety(self, mock_app: Application):
|
|
93
|
+
"""Test that singleton creation is thread-safe."""
|
|
94
|
+
# Reset singleton instance for clean test
|
|
95
|
+
IntegrationOverride._instance = None
|
|
96
|
+
|
|
97
|
+
import threading
|
|
98
|
+
import time
|
|
99
|
+
|
|
100
|
+
instances = []
|
|
101
|
+
|
|
102
|
+
def create_instance():
|
|
103
|
+
time.sleep(0.01) # Small delay to increase race condition chance
|
|
104
|
+
instances.append(IntegrationOverride(mock_app))
|
|
105
|
+
|
|
106
|
+
threads = [threading.Thread(target=create_instance) for _ in range(5)]
|
|
107
|
+
for thread in threads:
|
|
108
|
+
thread.start()
|
|
109
|
+
for thread in threads:
|
|
110
|
+
thread.join()
|
|
111
|
+
|
|
112
|
+
# All instances should be the same
|
|
113
|
+
assert len(set(instances)) == 1
|
|
114
|
+
assert all(instance is instances[0] for instance in instances)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TestIntegrationOverrideInitialization:
|
|
118
|
+
"""Test the initialization and configuration loading."""
|
|
119
|
+
|
|
120
|
+
def test_initialization_with_config(self, mock_app: Application):
|
|
121
|
+
"""Test initialization with valid configuration."""
|
|
122
|
+
integration_override = IntegrationOverride(mock_app)
|
|
123
|
+
|
|
124
|
+
assert integration_override.app == mock_app
|
|
125
|
+
assert "tenable_sc" in integration_override.mapping
|
|
126
|
+
assert "qualys" in integration_override.mapping
|
|
127
|
+
assert integration_override.mapping["tenable_sc"]["severity"] == "risk_level"
|
|
128
|
+
|
|
129
|
+
def test_initialization_with_empty_config(self, empty_config_app: Application):
|
|
130
|
+
"""Test initialization with empty configuration."""
|
|
131
|
+
integration_override = IntegrationOverride(empty_config_app)
|
|
132
|
+
|
|
133
|
+
assert integration_override.app == empty_config_app
|
|
134
|
+
assert integration_override.mapping == {}
|
|
135
|
+
|
|
136
|
+
def test_initialization_only_once(self, mock_app: Application):
|
|
137
|
+
"""Test that console and logging are only initialized once."""
|
|
138
|
+
# Reset singleton instance for clean test
|
|
139
|
+
IntegrationOverride._instance = None
|
|
140
|
+
|
|
141
|
+
# Patch the rich imports inside the method
|
|
142
|
+
with patch("rich.console.Console") as mock_console, patch("rich.table.Table") as mock_table:
|
|
143
|
+
mock_table_instance = Mock()
|
|
144
|
+
mock_table.return_value = mock_table_instance
|
|
145
|
+
mock_table_instance.row_count = 0
|
|
146
|
+
|
|
147
|
+
IntegrationOverride(mock_app)
|
|
148
|
+
IntegrationOverride(mock_app)
|
|
149
|
+
|
|
150
|
+
# Console should only be created once
|
|
151
|
+
mock_console.assert_called_once()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TestIntegrationOverrideMappingMethods:
|
|
155
|
+
"""Test the mapping-related methods."""
|
|
156
|
+
|
|
157
|
+
def test_get_mapping(self, mock_app: Application):
|
|
158
|
+
"""Test _get_mapping method."""
|
|
159
|
+
integration_override = IntegrationOverride(mock_app)
|
|
160
|
+
|
|
161
|
+
mapping = integration_override._get_mapping(mock_app.config)
|
|
162
|
+
assert mapping == mock_app.config["findingFromMapping"]
|
|
163
|
+
|
|
164
|
+
def test_get_mapping_without_finding_from_mapping(self, empty_config_app: Application):
|
|
165
|
+
"""Test _get_mapping when findingFromMapping is not present."""
|
|
166
|
+
integration_override = IntegrationOverride(empty_config_app)
|
|
167
|
+
|
|
168
|
+
mapping = integration_override._get_mapping(empty_config_app.config)
|
|
169
|
+
assert mapping == {}
|
|
170
|
+
|
|
171
|
+
def test_mapping_exists_valid_mapping(self, mock_app: Application):
|
|
172
|
+
"""Test mapping_exists with valid mapping."""
|
|
173
|
+
integration_override = IntegrationOverride(mock_app)
|
|
174
|
+
|
|
175
|
+
assert integration_override.mapping_exists("tenable_sc", "severity") is True
|
|
176
|
+
assert integration_override.mapping_exists("qualys", "severity") is True
|
|
177
|
+
|
|
178
|
+
def test_mapping_exists_default_value(self, mock_app: Application):
|
|
179
|
+
"""Test mapping_exists with default value."""
|
|
180
|
+
integration_override = IntegrationOverride(mock_app)
|
|
181
|
+
|
|
182
|
+
assert integration_override.mapping_exists("tenable_sc", "title") is False
|
|
183
|
+
|
|
184
|
+
def test_mapping_exists_nonexistent_integration(self, mock_app: Application):
|
|
185
|
+
"""Test mapping_exists with nonexistent integration."""
|
|
186
|
+
integration_override = IntegrationOverride(mock_app)
|
|
187
|
+
|
|
188
|
+
# The method now returns False when integration doesn't exist
|
|
189
|
+
assert integration_override.mapping_exists("nonexistent", "severity") is False
|
|
190
|
+
|
|
191
|
+
def test_mapping_exists_nonexistent_field(self, mock_app: Application):
|
|
192
|
+
"""Test mapping_exists with nonexistent field."""
|
|
193
|
+
integration_override = IntegrationOverride(mock_app)
|
|
194
|
+
|
|
195
|
+
assert integration_override.mapping_exists("tenable_sc", "nonexistent") is False
|
|
196
|
+
|
|
197
|
+
def test_mapping_exists_case_insensitive(self, mock_app: Application):
|
|
198
|
+
"""Test mapping_exists is case insensitive."""
|
|
199
|
+
integration_override = IntegrationOverride(mock_app)
|
|
200
|
+
|
|
201
|
+
assert integration_override.mapping_exists("TENABLE_SC", "SEVERITY") is True
|
|
202
|
+
assert integration_override.mapping_exists("Tenable_Sc", "Severity") is True
|
|
203
|
+
|
|
204
|
+
def test_mapping_exists_with_none_values(self, mock_app: Application):
|
|
205
|
+
"""Test mapping_exists with None values."""
|
|
206
|
+
integration_override = IntegrationOverride(mock_app)
|
|
207
|
+
|
|
208
|
+
assert integration_override.mapping_exists(None, "severity") is False
|
|
209
|
+
assert integration_override.mapping_exists("tenable_sc", None) is False
|
|
210
|
+
assert integration_override.mapping_exists(None, None) is False
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class TestIntegrationOverrideLoadMethod:
|
|
214
|
+
"""Test the load method."""
|
|
215
|
+
|
|
216
|
+
def test_load_valid_mapping(self, mock_app: Application):
|
|
217
|
+
"""Test load with valid mapping."""
|
|
218
|
+
integration_override = IntegrationOverride(mock_app)
|
|
219
|
+
|
|
220
|
+
assert integration_override.load("tenable_sc", "severity") == "risk_level"
|
|
221
|
+
assert integration_override.load("tenable_sc", "description") == "details"
|
|
222
|
+
assert integration_override.load("qualys", "severity") == "cvss_score"
|
|
223
|
+
|
|
224
|
+
def test_load_default_value(self, mock_app: Application):
|
|
225
|
+
"""Test load with default value."""
|
|
226
|
+
integration_override = IntegrationOverride(mock_app)
|
|
227
|
+
|
|
228
|
+
assert integration_override.load("tenable_sc", "title") is None
|
|
229
|
+
|
|
230
|
+
def test_load_nonexistent_integration(self, mock_app: Application):
|
|
231
|
+
"""Test load with nonexistent integration."""
|
|
232
|
+
integration_override = IntegrationOverride(mock_app)
|
|
233
|
+
|
|
234
|
+
assert integration_override.load("nonexistent", "severity") is None
|
|
235
|
+
|
|
236
|
+
def test_load_nonexistent_field(self, mock_app: Application):
|
|
237
|
+
"""Test load with nonexistent field."""
|
|
238
|
+
integration_override = IntegrationOverride(mock_app)
|
|
239
|
+
|
|
240
|
+
assert integration_override.load("tenable_sc", "nonexistent") is None
|
|
241
|
+
|
|
242
|
+
def test_load_none_parameters(self, mock_app: Application):
|
|
243
|
+
"""Test load with None parameters."""
|
|
244
|
+
integration_override = IntegrationOverride(mock_app)
|
|
245
|
+
|
|
246
|
+
assert integration_override.load(None, None) is None
|
|
247
|
+
# The method should handle None field_name gracefully
|
|
248
|
+
assert integration_override.load("tenable_sc", None) is None
|
|
249
|
+
assert integration_override.load(None, "severity") is None
|
|
250
|
+
|
|
251
|
+
def test_load_case_insensitive(self, mock_app: Application):
|
|
252
|
+
"""Test load is case insensitive."""
|
|
253
|
+
integration_override = IntegrationOverride(mock_app)
|
|
254
|
+
|
|
255
|
+
assert integration_override.load("TENABLE_SC", "SEVERITY") == "risk_level"
|
|
256
|
+
assert integration_override.load("Tenable_Sc", "Severity") == "risk_level"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class TestIntegrationOverrideFieldMapValidation:
|
|
260
|
+
"""Test the field_map_validation method."""
|
|
261
|
+
|
|
262
|
+
def test_field_map_validation_no_unique_override(self, mock_app: Application):
|
|
263
|
+
"""Test field_map_validation when no uniqueOverride is configured."""
|
|
264
|
+
integration_override = IntegrationOverride(mock_app)
|
|
265
|
+
obj = {"ip": "192.168.1.1"}
|
|
266
|
+
|
|
267
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
268
|
+
assert result is None
|
|
269
|
+
|
|
270
|
+
def test_field_map_validation_empty_unique_override(self, mock_app_with_unique_override: Application):
|
|
271
|
+
"""Test field_map_validation with empty uniqueOverride for model type."""
|
|
272
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
273
|
+
obj = {"ip": "192.168.1.1"}
|
|
274
|
+
|
|
275
|
+
# Override config to have empty list for asset
|
|
276
|
+
integration_override.app.config["uniqueOverride"]["asset"] = []
|
|
277
|
+
|
|
278
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
279
|
+
assert result is None
|
|
280
|
+
|
|
281
|
+
def test_field_map_validation_unsupported_field(self, mock_app_with_unique_override: Application):
|
|
282
|
+
"""Test field_map_validation with unsupported field."""
|
|
283
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
284
|
+
obj = {"ip": "192.168.1.1"}
|
|
285
|
+
|
|
286
|
+
# Override config to have unsupported field
|
|
287
|
+
integration_override.app.config["uniqueOverride"]["asset"] = ["unsupported_field"]
|
|
288
|
+
|
|
289
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
290
|
+
assert result is None
|
|
291
|
+
|
|
292
|
+
def test_field_map_validation_unsupported_model_type(self, mock_app_with_unique_override: Application):
|
|
293
|
+
"""Test field_map_validation with unsupported model type."""
|
|
294
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
295
|
+
obj = {"ip": "192.168.1.1"}
|
|
296
|
+
|
|
297
|
+
result = integration_override.field_map_validation(obj, "unsupported_type")
|
|
298
|
+
assert result is None
|
|
299
|
+
|
|
300
|
+
def test_field_map_validation_dict_object_tenableasset(self, mock_app_with_unique_override: Application):
|
|
301
|
+
"""Test field_map_validation with dict object for tenableasset model."""
|
|
302
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
303
|
+
|
|
304
|
+
# Create a dict-like object that has a class name
|
|
305
|
+
class DictLikeObject(dict):
|
|
306
|
+
"""Dict-like object with custom class name for testing."""
|
|
307
|
+
|
|
308
|
+
def __init__(self, *args, **kwargs):
|
|
309
|
+
super().__init__(*args, **kwargs)
|
|
310
|
+
self.__class__.__name__ = "TenableAsset"
|
|
311
|
+
|
|
312
|
+
obj = DictLikeObject({"ip": "192.168.1.1", "dnsName": "test.example.com"})
|
|
313
|
+
|
|
314
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
315
|
+
assert result == "192.168.1.1"
|
|
316
|
+
|
|
317
|
+
def test_field_map_validation_dict_object_dict(self, mock_app_with_unique_override: Application):
|
|
318
|
+
"""Test field_map_validation with dict object for dict model."""
|
|
319
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
320
|
+
|
|
321
|
+
# Create a dict-like object that has a class name
|
|
322
|
+
class DictLikeObject(dict):
|
|
323
|
+
"""Dict-like object with custom class name for testing."""
|
|
324
|
+
|
|
325
|
+
def __init__(self, *args, **kwargs):
|
|
326
|
+
super().__init__(*args, **kwargs)
|
|
327
|
+
self.__class__.__name__ = "dict"
|
|
328
|
+
|
|
329
|
+
obj = DictLikeObject({"ipv4": "192.168.1.1", "fqdn": "test.example.com"})
|
|
330
|
+
|
|
331
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
332
|
+
assert result == "192.168.1.1"
|
|
333
|
+
|
|
334
|
+
def test_field_map_validation_object_with_attributes(self, mock_app_with_unique_override: Application):
|
|
335
|
+
"""Test field_map_validation with object that has attributes."""
|
|
336
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
337
|
+
|
|
338
|
+
# Create mock object with attributes
|
|
339
|
+
obj = Mock()
|
|
340
|
+
obj.__class__.__name__ = "TenableAsset"
|
|
341
|
+
obj.ip = "192.168.1.1"
|
|
342
|
+
obj.dnsName = "test.example.com"
|
|
343
|
+
|
|
344
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
345
|
+
assert result == "192.168.1.1"
|
|
346
|
+
|
|
347
|
+
def test_field_map_validation_missing_mapped_field(self, mock_app_with_unique_override: Application):
|
|
348
|
+
"""Test field_map_validation when mapped field is missing."""
|
|
349
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
350
|
+
|
|
351
|
+
# Create a dict-like object missing the mapped field
|
|
352
|
+
class DictLikeObject(dict):
|
|
353
|
+
"""Dict-like object with custom class name for testing."""
|
|
354
|
+
|
|
355
|
+
def __init__(self, *args, **kwargs):
|
|
356
|
+
super().__init__(*args, **kwargs)
|
|
357
|
+
self.__class__.__name__ = "TenableAsset"
|
|
358
|
+
|
|
359
|
+
obj = DictLikeObject({"dnsName": "test.example.com"}) # Missing "ip"
|
|
360
|
+
|
|
361
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
362
|
+
assert result is None
|
|
363
|
+
|
|
364
|
+
def test_field_map_validation_exception_handling(self, mock_app_with_unique_override: Application):
|
|
365
|
+
"""Test field_map_validation handles exceptions gracefully."""
|
|
366
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
367
|
+
|
|
368
|
+
# Create an object that will cause an exception when accessing __class__.__name__
|
|
369
|
+
class ProblematicObject:
|
|
370
|
+
"""Object that raises AttributeError when accessing __class__.__name__."""
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def __class__(self):
|
|
374
|
+
# This will cause an AttributeError when trying to access __name__
|
|
375
|
+
raise AttributeError("Simulated error")
|
|
376
|
+
|
|
377
|
+
obj = ProblematicObject()
|
|
378
|
+
|
|
379
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
380
|
+
assert result is None
|
|
381
|
+
integration_override.app.logger.warning.assert_called_once()
|
|
382
|
+
|
|
383
|
+
def test_field_map_validation_different_field_mappings(self, mock_app_with_unique_override: Application):
|
|
384
|
+
"""Test field_map_validation with different field mappings."""
|
|
385
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
386
|
+
|
|
387
|
+
# Test name field mapping
|
|
388
|
+
integration_override.app.config["uniqueOverride"]["asset"] = ["name"]
|
|
389
|
+
|
|
390
|
+
class DictLikeObject(dict):
|
|
391
|
+
"""Dict-like object with custom class name for testing."""
|
|
392
|
+
|
|
393
|
+
def __init__(self, *args, **kwargs):
|
|
394
|
+
super().__init__(*args, **kwargs)
|
|
395
|
+
self.__class__.__name__ = "TenableAsset"
|
|
396
|
+
|
|
397
|
+
obj = DictLikeObject({"dnsName": "test.example.com"})
|
|
398
|
+
|
|
399
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
400
|
+
assert result == "test.example.com"
|
|
401
|
+
|
|
402
|
+
# Test dns field mapping
|
|
403
|
+
integration_override.app.config["uniqueOverride"]["asset"] = ["dns"]
|
|
404
|
+
|
|
405
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
406
|
+
assert result == "test.example.com"
|
|
407
|
+
|
|
408
|
+
# Test fqdn field mapping
|
|
409
|
+
integration_override.app.config["uniqueOverride"]["asset"] = ["fqdn"]
|
|
410
|
+
|
|
411
|
+
result = integration_override.field_map_validation(obj, "asset")
|
|
412
|
+
assert result == "test.example.com"
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class TestIntegrationOverrideLogging:
|
|
416
|
+
"""Test the logging functionality."""
|
|
417
|
+
|
|
418
|
+
def test_log_mappings_with_mappings(self, mock_app: Application):
|
|
419
|
+
"""Test _log_mappings when mappings exist."""
|
|
420
|
+
# Reset singleton instance for clean test
|
|
421
|
+
IntegrationOverride._instance = None
|
|
422
|
+
|
|
423
|
+
# Patch the rich imports inside the method
|
|
424
|
+
with patch("rich.table.Table") as mock_table, patch("rich.console.Console") as mock_console:
|
|
425
|
+
mock_table_instance = Mock()
|
|
426
|
+
mock_table.return_value = mock_table_instance
|
|
427
|
+
mock_table_instance.row_count = 2 # Simulate rows in table
|
|
428
|
+
|
|
429
|
+
IntegrationOverride(mock_app)
|
|
430
|
+
|
|
431
|
+
# Verify table was created and printed
|
|
432
|
+
mock_table.assert_called_once()
|
|
433
|
+
mock_console_instance = mock_console.return_value
|
|
434
|
+
mock_console_instance.print.assert_called_once_with(mock_table_instance)
|
|
435
|
+
|
|
436
|
+
def test_log_mappings_no_mappings(self, empty_config_app: Application):
|
|
437
|
+
"""Test _log_mappings when no mappings exist."""
|
|
438
|
+
# Reset singleton instance for clean test
|
|
439
|
+
IntegrationOverride._instance = None
|
|
440
|
+
|
|
441
|
+
# Patch the rich imports inside the method
|
|
442
|
+
with patch("rich.table.Table") as mock_table, patch("rich.console.Console") as mock_console:
|
|
443
|
+
mock_table_instance = Mock()
|
|
444
|
+
mock_table.return_value = mock_table_instance
|
|
445
|
+
mock_table_instance.row_count = 0 # No rows in table
|
|
446
|
+
|
|
447
|
+
IntegrationOverride(empty_config_app)
|
|
448
|
+
|
|
449
|
+
# Verify table was created but not printed (no rows)
|
|
450
|
+
mock_table.assert_called_once()
|
|
451
|
+
mock_console_instance = mock_console.return_value
|
|
452
|
+
mock_console_instance.print.assert_not_called()
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class TestIntegrationOverrideEdgeCases:
|
|
456
|
+
"""Test edge cases and error conditions."""
|
|
457
|
+
|
|
458
|
+
def test_load_with_empty_strings(self, mock_app: Application):
|
|
459
|
+
"""Test load with empty strings."""
|
|
460
|
+
integration_override = IntegrationOverride(mock_app)
|
|
461
|
+
|
|
462
|
+
assert integration_override.load("", "") is None
|
|
463
|
+
assert integration_override.load("tenable_sc", "") is None
|
|
464
|
+
assert integration_override.load("", "severity") is None
|
|
465
|
+
|
|
466
|
+
def test_mapping_exists_with_empty_strings(self, mock_app: Application):
|
|
467
|
+
"""Test mapping_exists with empty strings."""
|
|
468
|
+
integration_override = IntegrationOverride(mock_app)
|
|
469
|
+
|
|
470
|
+
# Empty strings should be handled gracefully and return False
|
|
471
|
+
assert integration_override.mapping_exists("", "") is False
|
|
472
|
+
assert integration_override.mapping_exists("tenable_sc", "") is False
|
|
473
|
+
assert integration_override.mapping_exists("", "severity") is False
|
|
474
|
+
|
|
475
|
+
def test_field_map_validation_with_none_object(self, mock_app_with_unique_override: Application):
|
|
476
|
+
"""Test field_map_validation with None object."""
|
|
477
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
478
|
+
|
|
479
|
+
result = integration_override.field_map_validation(None, "asset")
|
|
480
|
+
assert result is None
|
|
481
|
+
|
|
482
|
+
def test_field_map_validation_with_none_model_type(self, mock_app_with_unique_override: Application):
|
|
483
|
+
"""Test field_map_validation with None model type."""
|
|
484
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
485
|
+
obj = {"ip": "192.168.1.1"}
|
|
486
|
+
|
|
487
|
+
result = integration_override.field_map_validation(obj, None)
|
|
488
|
+
assert result is None
|
|
489
|
+
|
|
490
|
+
def test_field_map_validation_with_empty_string_model_type(self, mock_app_with_unique_override: Application):
|
|
491
|
+
"""Test field_map_validation with empty string model type."""
|
|
492
|
+
integration_override = IntegrationOverride(mock_app_with_unique_override)
|
|
493
|
+
obj = {"ip": "192.168.1.1"}
|
|
494
|
+
|
|
495
|
+
result = integration_override.field_map_validation(obj, "")
|
|
496
|
+
assert result is None
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@pytest.mark.integration
|
|
500
|
+
class TestIntegrationOverrideIntegration:
|
|
501
|
+
"""Integration tests for IntegrationOverride."""
|
|
502
|
+
|
|
503
|
+
def test_full_workflow_with_tenable_sc(self, mock_app: Application):
|
|
504
|
+
"""Test complete workflow with Tenable SC integration."""
|
|
505
|
+
integration_override = IntegrationOverride(mock_app)
|
|
506
|
+
|
|
507
|
+
# Test mapping exists
|
|
508
|
+
assert integration_override.mapping_exists("tenable_sc", "severity") is True
|
|
509
|
+
|
|
510
|
+
# Test load mapping
|
|
511
|
+
mapped_field = integration_override.load("tenable_sc", "severity")
|
|
512
|
+
assert mapped_field == "risk_level"
|
|
513
|
+
|
|
514
|
+
# Test that default values are not loaded
|
|
515
|
+
assert integration_override.load("tenable_sc", "title") is None
|
|
516
|
+
|
|
517
|
+
def test_full_workflow_with_qualys(self, mock_app: Application):
|
|
518
|
+
"""Test complete workflow with Qualys integration."""
|
|
519
|
+
integration_override = IntegrationOverride(mock_app)
|
|
520
|
+
|
|
521
|
+
# Test mapping exists
|
|
522
|
+
assert integration_override.mapping_exists("qualys", "severity") is True
|
|
523
|
+
|
|
524
|
+
# Test load mapping
|
|
525
|
+
mapped_field = integration_override.load("qualys", "severity")
|
|
526
|
+
assert mapped_field == "cvss_score"
|
|
527
|
+
|
|
528
|
+
# Test solution mapping
|
|
529
|
+
solution_field = integration_override.load("qualys", "solution")
|
|
530
|
+
assert solution_field == "fix"
|
|
531
|
+
|
|
532
|
+
def test_case_insensitive_workflow(self, mock_app: Application):
|
|
533
|
+
"""Test complete workflow with case insensitive handling."""
|
|
534
|
+
integration_override = IntegrationOverride(mock_app)
|
|
535
|
+
|
|
536
|
+
# Test with mixed case
|
|
537
|
+
assert integration_override.mapping_exists("TENABLE_SC", "SEVERITY") is True
|
|
538
|
+
assert integration_override.load("TENABLE_SC", "SEVERITY") == "risk_level"
|
|
539
|
+
|
|
540
|
+
# Test with title case
|
|
541
|
+
assert integration_override.mapping_exists("Tenable_Sc", "Severity") is True
|
|
542
|
+
assert integration_override.load("Tenable_Sc", "Severity") == "risk_level"
|
|
@@ -30,7 +30,7 @@ def test_issue_due_date():
|
|
|
30
30
|
|
|
31
31
|
# Test with defaults
|
|
32
32
|
severity = regscale_models.IssueSeverity.Moderate
|
|
33
|
-
expected_due_date = "2023-
|
|
33
|
+
expected_due_date = "2023-05-01"
|
|
34
34
|
result = issue_due_date(severity, created_date)
|
|
35
35
|
assert result == expected_due_date
|
|
36
36
|
|