regscale-cli 6.26.0.0__py3-none-any.whl → 6.27.0.1__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/__init__.py +0 -1
- 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.1.dist-info}/METADATA +1 -17
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/RECORD +94 -61
- 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.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Comprehensive unit tests for Wiz V2 parsers/main.py"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import unittest
|
|
8
|
+
from typing import Dict, List
|
|
9
|
+
from unittest.mock import MagicMock, Mock, patch, call
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from regscale.integrations.commercial.wizv2.parsers.main import (
|
|
14
|
+
collect_components_to_create,
|
|
15
|
+
handle_container_image_version,
|
|
16
|
+
handle_software_version,
|
|
17
|
+
get_software_name_from_cpe,
|
|
18
|
+
get_latest_version,
|
|
19
|
+
get_cloud_identifier,
|
|
20
|
+
handle_provider,
|
|
21
|
+
parse_memory,
|
|
22
|
+
parse_cpu,
|
|
23
|
+
get_resources,
|
|
24
|
+
pull_resource_info_from_props,
|
|
25
|
+
get_disk_storage,
|
|
26
|
+
get_network_info,
|
|
27
|
+
get_product_ids,
|
|
28
|
+
get_ip_address_from_props,
|
|
29
|
+
get_ip_v4_from_props,
|
|
30
|
+
get_ip_v6_from_props,
|
|
31
|
+
fetch_wiz_data,
|
|
32
|
+
get_ip_address,
|
|
33
|
+
)
|
|
34
|
+
from regscale.models import regscale_models
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger("regscale")
|
|
37
|
+
PATH = "regscale.integrations.commercial.wizv2.parsers.main"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ==================== Component Collection Tests ====================
|
|
41
|
+
class TestCollectComponents(unittest.TestCase):
|
|
42
|
+
"""Test component collection functions"""
|
|
43
|
+
|
|
44
|
+
def test_collect_components_to_create_empty_data(self):
|
|
45
|
+
"""Test collecting components with empty data"""
|
|
46
|
+
data = []
|
|
47
|
+
components_to_create = []
|
|
48
|
+
|
|
49
|
+
result = collect_components_to_create(data, components_to_create)
|
|
50
|
+
|
|
51
|
+
self.assertEqual(result, [])
|
|
52
|
+
|
|
53
|
+
def test_collect_components_to_create_single_component(self):
|
|
54
|
+
"""Test collecting single component"""
|
|
55
|
+
data = [{"type": "virtual_machine"}]
|
|
56
|
+
components_to_create = []
|
|
57
|
+
|
|
58
|
+
result = collect_components_to_create(data, components_to_create)
|
|
59
|
+
|
|
60
|
+
self.assertEqual(result, ["Virtual Machine"])
|
|
61
|
+
self.assertEqual(len(result), 1)
|
|
62
|
+
|
|
63
|
+
def test_collect_components_to_create_multiple_components(self):
|
|
64
|
+
"""Test collecting multiple unique components"""
|
|
65
|
+
data = [
|
|
66
|
+
{"type": "virtual_machine"},
|
|
67
|
+
{"type": "container"},
|
|
68
|
+
{"type": "database"},
|
|
69
|
+
]
|
|
70
|
+
components_to_create = []
|
|
71
|
+
|
|
72
|
+
result = collect_components_to_create(data, components_to_create)
|
|
73
|
+
|
|
74
|
+
self.assertIn("Virtual Machine", result)
|
|
75
|
+
self.assertIn("Container", result)
|
|
76
|
+
self.assertIn("Database", result)
|
|
77
|
+
self.assertEqual(len(result), 3)
|
|
78
|
+
|
|
79
|
+
def test_collect_components_to_create_duplicate_components(self):
|
|
80
|
+
"""Test collecting components with duplicates"""
|
|
81
|
+
data = [
|
|
82
|
+
{"type": "virtual_machine"},
|
|
83
|
+
{"type": "virtual_machine"},
|
|
84
|
+
{"type": "container"},
|
|
85
|
+
]
|
|
86
|
+
components_to_create = []
|
|
87
|
+
|
|
88
|
+
result = collect_components_to_create(data, components_to_create)
|
|
89
|
+
|
|
90
|
+
self.assertIn("Virtual Machine", result)
|
|
91
|
+
self.assertIn("Container", result)
|
|
92
|
+
self.assertEqual(len(result), 2)
|
|
93
|
+
|
|
94
|
+
def test_collect_components_to_create_missing_type(self):
|
|
95
|
+
"""Test collecting components with missing type"""
|
|
96
|
+
data = [{"type": "virtual_machine"}, {}, {"type": "container"}]
|
|
97
|
+
components_to_create = []
|
|
98
|
+
|
|
99
|
+
result = collect_components_to_create(data, components_to_create)
|
|
100
|
+
|
|
101
|
+
self.assertIn("Virtual Machine", result)
|
|
102
|
+
self.assertIn("Container", result)
|
|
103
|
+
self.assertEqual(len(result), 2)
|
|
104
|
+
|
|
105
|
+
def test_collect_components_to_create_existing_components(self):
|
|
106
|
+
"""Test collecting components with pre-existing list"""
|
|
107
|
+
data = [{"type": "virtual_machine"}, {"type": "container"}]
|
|
108
|
+
components_to_create = ["Database"]
|
|
109
|
+
|
|
110
|
+
result = collect_components_to_create(data, components_to_create)
|
|
111
|
+
|
|
112
|
+
self.assertIn("Virtual Machine", result)
|
|
113
|
+
self.assertIn("Container", result)
|
|
114
|
+
self.assertIn("Database", result)
|
|
115
|
+
self.assertEqual(len(result), 3)
|
|
116
|
+
|
|
117
|
+
def test_collect_components_to_create_empty_type(self):
|
|
118
|
+
"""Test collecting components with empty type string"""
|
|
119
|
+
data = [{"type": ""}, {"type": "container"}]
|
|
120
|
+
components_to_create = []
|
|
121
|
+
|
|
122
|
+
result = collect_components_to_create(data, components_to_create)
|
|
123
|
+
|
|
124
|
+
self.assertIn("Container", result)
|
|
125
|
+
self.assertEqual(len(result), 1)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# ==================== Container Image Version Tests ====================
|
|
129
|
+
class TestContainerImageVersion(unittest.TestCase):
|
|
130
|
+
"""Test container image version handling"""
|
|
131
|
+
|
|
132
|
+
def test_handle_container_image_version_with_tags(self):
|
|
133
|
+
"""Test handling container image with tags"""
|
|
134
|
+
image_tags = ["v1.2.3", "latest"]
|
|
135
|
+
name = "nginx:v1.2.3"
|
|
136
|
+
|
|
137
|
+
result = handle_container_image_version(image_tags, name)
|
|
138
|
+
|
|
139
|
+
self.assertEqual(result, "v1.2.3")
|
|
140
|
+
|
|
141
|
+
def test_handle_container_image_version_empty_tags_with_name(self):
|
|
142
|
+
"""Test handling container image with empty tags but version in name"""
|
|
143
|
+
image_tags = []
|
|
144
|
+
name = "nginx:v2.4.6"
|
|
145
|
+
|
|
146
|
+
result = handle_container_image_version(image_tags, name)
|
|
147
|
+
|
|
148
|
+
self.assertEqual(result, "v2.4.6")
|
|
149
|
+
|
|
150
|
+
def test_handle_container_image_version_empty_tags_no_version(self):
|
|
151
|
+
"""Test handling container image with empty tags and no version in name"""
|
|
152
|
+
image_tags = []
|
|
153
|
+
name = "nginx"
|
|
154
|
+
|
|
155
|
+
result = handle_container_image_version(image_tags, name)
|
|
156
|
+
|
|
157
|
+
self.assertEqual(result, "")
|
|
158
|
+
|
|
159
|
+
def test_handle_container_image_version_none_tags(self):
|
|
160
|
+
"""Test handling container image with None tags"""
|
|
161
|
+
image_tags = [None]
|
|
162
|
+
name = "nginx:v1.0.0"
|
|
163
|
+
|
|
164
|
+
result = handle_container_image_version(image_tags, name)
|
|
165
|
+
|
|
166
|
+
self.assertEqual(result, "v1.0.0")
|
|
167
|
+
|
|
168
|
+
def test_handle_container_image_version_empty_name(self):
|
|
169
|
+
"""Test handling container image with empty name"""
|
|
170
|
+
image_tags = []
|
|
171
|
+
name = ""
|
|
172
|
+
|
|
173
|
+
result = handle_container_image_version(image_tags, name)
|
|
174
|
+
|
|
175
|
+
self.assertEqual(result, "")
|
|
176
|
+
|
|
177
|
+
def test_handle_container_image_version_multiple_colons(self):
|
|
178
|
+
"""Test handling container image with multiple colons in name"""
|
|
179
|
+
image_tags = []
|
|
180
|
+
name = "registry.io/nginx:v1.2.3:extra"
|
|
181
|
+
|
|
182
|
+
result = handle_container_image_version(image_tags, name)
|
|
183
|
+
|
|
184
|
+
# The function splits on ":" and takes index [1], which is "v1.2.3"
|
|
185
|
+
self.assertEqual(result, "v1.2.3")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ==================== Software Version Tests ====================
|
|
189
|
+
class TestSoftwareVersion(unittest.TestCase):
|
|
190
|
+
"""Test software version handling"""
|
|
191
|
+
|
|
192
|
+
def test_handle_software_version_with_version_and_software_category(self):
|
|
193
|
+
"""Test handling software version with version present and software category"""
|
|
194
|
+
wiz_entity_properties = {"version": "1.2.3"}
|
|
195
|
+
asset_category = regscale_models.AssetCategory.Software
|
|
196
|
+
|
|
197
|
+
result = handle_software_version(wiz_entity_properties, asset_category)
|
|
198
|
+
|
|
199
|
+
self.assertEqual(result, "1.2.3")
|
|
200
|
+
|
|
201
|
+
def test_handle_software_version_with_version_and_hardware_category(self):
|
|
202
|
+
"""Test handling software version with hardware category"""
|
|
203
|
+
wiz_entity_properties = {"version": "1.2.3"}
|
|
204
|
+
asset_category = regscale_models.AssetCategory.Hardware
|
|
205
|
+
|
|
206
|
+
result = handle_software_version(wiz_entity_properties, asset_category)
|
|
207
|
+
|
|
208
|
+
self.assertIsNone(result)
|
|
209
|
+
|
|
210
|
+
def test_handle_software_version_no_version(self):
|
|
211
|
+
"""Test handling software version with no version"""
|
|
212
|
+
wiz_entity_properties = {}
|
|
213
|
+
asset_category = regscale_models.AssetCategory.Software
|
|
214
|
+
|
|
215
|
+
result = handle_software_version(wiz_entity_properties, asset_category)
|
|
216
|
+
|
|
217
|
+
self.assertIsNone(result)
|
|
218
|
+
|
|
219
|
+
def test_handle_software_version_empty_version(self):
|
|
220
|
+
"""Test handling software version with empty version"""
|
|
221
|
+
wiz_entity_properties = {"version": ""}
|
|
222
|
+
asset_category = regscale_models.AssetCategory.Software
|
|
223
|
+
|
|
224
|
+
result = handle_software_version(wiz_entity_properties, asset_category)
|
|
225
|
+
|
|
226
|
+
self.assertIsNone(result)
|
|
227
|
+
|
|
228
|
+
def test_handle_software_version_none_version(self):
|
|
229
|
+
"""Test handling software version with None version"""
|
|
230
|
+
wiz_entity_properties = {"version": None}
|
|
231
|
+
asset_category = regscale_models.AssetCategory.Software
|
|
232
|
+
|
|
233
|
+
result = handle_software_version(wiz_entity_properties, asset_category)
|
|
234
|
+
|
|
235
|
+
self.assertIsNone(result)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ==================== CPE Software Name Tests ====================
|
|
239
|
+
class TestGetSoftwareNameFromCpe(unittest.TestCase):
|
|
240
|
+
"""Test CPE software name extraction"""
|
|
241
|
+
|
|
242
|
+
@patch(f"{PATH}.extract_product_name_and_version")
|
|
243
|
+
def test_get_software_name_from_cpe_with_cpe(self, mock_extract):
|
|
244
|
+
"""Test getting software name with CPE present"""
|
|
245
|
+
mock_extract.return_value = {
|
|
246
|
+
"part": "a",
|
|
247
|
+
"software_vendor": "apache",
|
|
248
|
+
"software_name": "httpd",
|
|
249
|
+
"software_version": "2.4.46",
|
|
250
|
+
}
|
|
251
|
+
wiz_entity_properties = {"cpe": "cpe:2.3:a:apache:httpd:2.4.46"}
|
|
252
|
+
name = "Apache HTTP Server"
|
|
253
|
+
|
|
254
|
+
result = get_software_name_from_cpe(wiz_entity_properties, name)
|
|
255
|
+
|
|
256
|
+
self.assertEqual(result["name"], "Apache HTTP Server")
|
|
257
|
+
self.assertEqual(result["part"], "a")
|
|
258
|
+
self.assertEqual(result["software_vendor"], "apache")
|
|
259
|
+
self.assertEqual(result["software_name"], "httpd")
|
|
260
|
+
self.assertEqual(result["software_version"], "2.4.46")
|
|
261
|
+
mock_extract.assert_called_once_with("cpe:2.3:a:apache:httpd:2.4.46")
|
|
262
|
+
|
|
263
|
+
def test_get_software_name_from_cpe_no_cpe(self):
|
|
264
|
+
"""Test getting software name without CPE"""
|
|
265
|
+
wiz_entity_properties = {}
|
|
266
|
+
name = "Some Software"
|
|
267
|
+
|
|
268
|
+
result = get_software_name_from_cpe(wiz_entity_properties, name)
|
|
269
|
+
|
|
270
|
+
self.assertEqual(result["name"], "Some Software")
|
|
271
|
+
self.assertIsNone(result["part"])
|
|
272
|
+
self.assertIsNone(result["software_name"])
|
|
273
|
+
self.assertIsNone(result["software_version"])
|
|
274
|
+
self.assertIsNone(result["software_vendor"])
|
|
275
|
+
|
|
276
|
+
def test_get_software_name_from_cpe_empty_cpe(self):
|
|
277
|
+
"""Test getting software name with empty CPE"""
|
|
278
|
+
wiz_entity_properties = {"cpe": ""}
|
|
279
|
+
name = "Some Software"
|
|
280
|
+
|
|
281
|
+
result = get_software_name_from_cpe(wiz_entity_properties, name)
|
|
282
|
+
|
|
283
|
+
self.assertEqual(result["name"], "Some Software")
|
|
284
|
+
self.assertIsNone(result["part"])
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# ==================== Latest Version Tests ====================
|
|
288
|
+
class TestGetLatestVersion(unittest.TestCase):
|
|
289
|
+
"""Test latest version retrieval"""
|
|
290
|
+
|
|
291
|
+
def test_get_latest_version_with_latest_version(self):
|
|
292
|
+
"""Test getting latest version when present"""
|
|
293
|
+
wiz_entity_properties = {"latestVersion": "2.0.0", "version": "1.0.0"}
|
|
294
|
+
|
|
295
|
+
result = get_latest_version(wiz_entity_properties)
|
|
296
|
+
|
|
297
|
+
self.assertEqual(result, "2.0.0")
|
|
298
|
+
|
|
299
|
+
def test_get_latest_version_without_latest_version(self):
|
|
300
|
+
"""Test getting latest version when not present"""
|
|
301
|
+
wiz_entity_properties = {"version": "1.0.0"}
|
|
302
|
+
|
|
303
|
+
result = get_latest_version(wiz_entity_properties)
|
|
304
|
+
|
|
305
|
+
self.assertEqual(result, "1.0.0")
|
|
306
|
+
|
|
307
|
+
def test_get_latest_version_both_none(self):
|
|
308
|
+
"""Test getting latest version when both are None"""
|
|
309
|
+
wiz_entity_properties = {}
|
|
310
|
+
|
|
311
|
+
result = get_latest_version(wiz_entity_properties)
|
|
312
|
+
|
|
313
|
+
self.assertIsNone(result)
|
|
314
|
+
|
|
315
|
+
def test_get_latest_version_latest_is_none(self):
|
|
316
|
+
"""Test getting latest version when latest is explicitly None"""
|
|
317
|
+
wiz_entity_properties = {"latestVersion": None, "version": "1.0.0"}
|
|
318
|
+
|
|
319
|
+
result = get_latest_version(wiz_entity_properties)
|
|
320
|
+
|
|
321
|
+
self.assertEqual(result, "1.0.0")
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# ==================== Cloud Identifier Tests ====================
|
|
325
|
+
class TestCloudIdentifier(unittest.TestCase):
|
|
326
|
+
"""Test cloud identifier extraction"""
|
|
327
|
+
|
|
328
|
+
def test_get_cloud_identifier_aws(self):
|
|
329
|
+
"""Test identifying AWS provider"""
|
|
330
|
+
wiz_entity_properties = {"providerUniqueId": "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"}
|
|
331
|
+
|
|
332
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
333
|
+
|
|
334
|
+
self.assertEqual(provider, "aws")
|
|
335
|
+
self.assertIn("aws", identifier)
|
|
336
|
+
|
|
337
|
+
def test_get_cloud_identifier_aws_with_amazon(self):
|
|
338
|
+
"""Test identifying AWS provider with amazon keyword"""
|
|
339
|
+
wiz_entity_properties = {"providerUniqueId": "amazon-web-services-resource"}
|
|
340
|
+
|
|
341
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
342
|
+
|
|
343
|
+
self.assertEqual(provider, "aws")
|
|
344
|
+
|
|
345
|
+
def test_get_cloud_identifier_aws_with_ec2(self):
|
|
346
|
+
"""Test identifying AWS provider with ec2 keyword"""
|
|
347
|
+
wiz_entity_properties = {"providerUniqueId": "ec2-instance-12345"}
|
|
348
|
+
|
|
349
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
350
|
+
|
|
351
|
+
self.assertEqual(provider, "aws")
|
|
352
|
+
|
|
353
|
+
def test_get_cloud_identifier_azure(self):
|
|
354
|
+
"""Test identifying Azure provider"""
|
|
355
|
+
wiz_entity_properties = {
|
|
356
|
+
"providerUniqueId": "/subscriptions/12345/resourceGroups/mygroup/providers/Microsoft.Compute/virtualMachines/myvm"
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
360
|
+
|
|
361
|
+
self.assertEqual(provider, "azure")
|
|
362
|
+
self.assertIn("microsoft", identifier)
|
|
363
|
+
|
|
364
|
+
def test_get_cloud_identifier_azure_with_azure(self):
|
|
365
|
+
"""Test identifying Azure provider with azure keyword"""
|
|
366
|
+
wiz_entity_properties = {"providerUniqueId": "azure-resource-12345"}
|
|
367
|
+
|
|
368
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
369
|
+
|
|
370
|
+
self.assertEqual(provider, "azure")
|
|
371
|
+
|
|
372
|
+
def test_get_cloud_identifier_google(self):
|
|
373
|
+
"""Test identifying Google provider"""
|
|
374
|
+
wiz_entity_properties = {
|
|
375
|
+
"providerUniqueId": "//compute.googleapis.com/projects/myproject/zones/us-central1-a/instances/myinstance"
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
379
|
+
|
|
380
|
+
self.assertEqual(provider, "google")
|
|
381
|
+
self.assertIn("google", identifier)
|
|
382
|
+
|
|
383
|
+
def test_get_cloud_identifier_google_with_gcp(self):
|
|
384
|
+
"""Test identifying Google provider with gcp keyword"""
|
|
385
|
+
wiz_entity_properties = {"providerUniqueId": "gcp-resource-12345"}
|
|
386
|
+
|
|
387
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
388
|
+
|
|
389
|
+
self.assertEqual(provider, "google")
|
|
390
|
+
|
|
391
|
+
def test_get_cloud_identifier_other(self):
|
|
392
|
+
"""Test identifying other provider"""
|
|
393
|
+
wiz_entity_properties = {"providerUniqueId": "other-cloud-provider-12345"}
|
|
394
|
+
|
|
395
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
396
|
+
|
|
397
|
+
self.assertEqual(provider, "other")
|
|
398
|
+
self.assertEqual(identifier, "other-cloud-provider-12345")
|
|
399
|
+
|
|
400
|
+
def test_get_cloud_identifier_no_provider(self):
|
|
401
|
+
"""Test with no provider unique ID"""
|
|
402
|
+
wiz_entity_properties = {}
|
|
403
|
+
|
|
404
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
405
|
+
|
|
406
|
+
self.assertIsNone(provider)
|
|
407
|
+
self.assertIsNone(identifier)
|
|
408
|
+
|
|
409
|
+
def test_get_cloud_identifier_none_provider(self):
|
|
410
|
+
"""Test with None provider unique ID"""
|
|
411
|
+
wiz_entity_properties = {"providerUniqueId": None}
|
|
412
|
+
|
|
413
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
414
|
+
|
|
415
|
+
self.assertIsNone(provider)
|
|
416
|
+
self.assertIsNone(identifier)
|
|
417
|
+
|
|
418
|
+
def test_get_cloud_identifier_empty_provider(self):
|
|
419
|
+
"""Test with empty provider unique ID"""
|
|
420
|
+
wiz_entity_properties = {"providerUniqueId": ""}
|
|
421
|
+
|
|
422
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
423
|
+
|
|
424
|
+
self.assertIsNone(provider)
|
|
425
|
+
self.assertIsNone(identifier)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# ==================== Provider Handling Tests ====================
|
|
429
|
+
class TestHandleProvider(unittest.TestCase):
|
|
430
|
+
"""Test provider handling"""
|
|
431
|
+
|
|
432
|
+
@patch(f"{PATH}.get_cloud_identifier")
|
|
433
|
+
def test_handle_provider_aws(self, mock_get_identifier):
|
|
434
|
+
"""Test handling AWS provider"""
|
|
435
|
+
mock_get_identifier.return_value = ("aws", "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0")
|
|
436
|
+
wiz_entity_properties = {"providerUniqueId": "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"}
|
|
437
|
+
|
|
438
|
+
result = handle_provider(wiz_entity_properties)
|
|
439
|
+
|
|
440
|
+
self.assertEqual(result["awsIdentifier"], "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0")
|
|
441
|
+
self.assertIsNone(result["azureIdentifier"])
|
|
442
|
+
self.assertIsNone(result["googleIdentifier"])
|
|
443
|
+
self.assertIsNone(result["otherCloudIdentifier"])
|
|
444
|
+
|
|
445
|
+
@patch(f"{PATH}.get_cloud_identifier")
|
|
446
|
+
def test_handle_provider_azure(self, mock_get_identifier):
|
|
447
|
+
"""Test handling Azure provider"""
|
|
448
|
+
mock_get_identifier.return_value = (
|
|
449
|
+
"azure",
|
|
450
|
+
"/subscriptions/12345/resourceGroups/mygroup/providers/Microsoft.Compute/virtualMachines/myvm",
|
|
451
|
+
)
|
|
452
|
+
wiz_entity_properties = {
|
|
453
|
+
"providerUniqueId": "/subscriptions/12345/resourceGroups/mygroup/providers/Microsoft.Compute/virtualMachines/myvm"
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
result = handle_provider(wiz_entity_properties)
|
|
457
|
+
|
|
458
|
+
self.assertIsNone(result["awsIdentifier"])
|
|
459
|
+
self.assertEqual(
|
|
460
|
+
result["azureIdentifier"],
|
|
461
|
+
"/subscriptions/12345/resourceGroups/mygroup/providers/Microsoft.Compute/virtualMachines/myvm",
|
|
462
|
+
)
|
|
463
|
+
self.assertIsNone(result["googleIdentifier"])
|
|
464
|
+
self.assertIsNone(result["otherCloudIdentifier"])
|
|
465
|
+
|
|
466
|
+
@patch(f"{PATH}.get_cloud_identifier")
|
|
467
|
+
def test_handle_provider_google(self, mock_get_identifier):
|
|
468
|
+
"""Test handling Google provider"""
|
|
469
|
+
mock_get_identifier.return_value = (
|
|
470
|
+
"google",
|
|
471
|
+
"//compute.googleapis.com/projects/myproject/zones/us-central1-a/instances/myinstance",
|
|
472
|
+
)
|
|
473
|
+
wiz_entity_properties = {
|
|
474
|
+
"providerUniqueId": "//compute.googleapis.com/projects/myproject/zones/us-central1-a/instances/myinstance"
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
result = handle_provider(wiz_entity_properties)
|
|
478
|
+
|
|
479
|
+
self.assertIsNone(result["awsIdentifier"])
|
|
480
|
+
self.assertIsNone(result["azureIdentifier"])
|
|
481
|
+
self.assertEqual(
|
|
482
|
+
result["googleIdentifier"],
|
|
483
|
+
"//compute.googleapis.com/projects/myproject/zones/us-central1-a/instances/myinstance",
|
|
484
|
+
)
|
|
485
|
+
self.assertIsNone(result["otherCloudIdentifier"])
|
|
486
|
+
|
|
487
|
+
@patch(f"{PATH}.get_cloud_identifier")
|
|
488
|
+
def test_handle_provider_other(self, mock_get_identifier):
|
|
489
|
+
"""Test handling other provider"""
|
|
490
|
+
mock_get_identifier.return_value = ("other", "other-cloud-provider-12345")
|
|
491
|
+
wiz_entity_properties = {"providerUniqueId": "other-cloud-provider-12345"}
|
|
492
|
+
|
|
493
|
+
result = handle_provider(wiz_entity_properties)
|
|
494
|
+
|
|
495
|
+
self.assertIsNone(result["awsIdentifier"])
|
|
496
|
+
self.assertIsNone(result["azureIdentifier"])
|
|
497
|
+
self.assertIsNone(result["googleIdentifier"])
|
|
498
|
+
self.assertEqual(result["otherCloudIdentifier"], "other-cloud-provider-12345")
|
|
499
|
+
|
|
500
|
+
@patch(f"{PATH}.get_cloud_identifier")
|
|
501
|
+
def test_handle_provider_none(self, mock_get_identifier):
|
|
502
|
+
"""Test handling no provider"""
|
|
503
|
+
mock_get_identifier.return_value = (None, None)
|
|
504
|
+
wiz_entity_properties = {}
|
|
505
|
+
|
|
506
|
+
result = handle_provider(wiz_entity_properties)
|
|
507
|
+
|
|
508
|
+
self.assertIsNone(result["awsIdentifier"])
|
|
509
|
+
self.assertIsNone(result["azureIdentifier"])
|
|
510
|
+
self.assertIsNone(result["googleIdentifier"])
|
|
511
|
+
self.assertIsNone(result["otherCloudIdentifier"])
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
# ==================== Memory Parsing Tests ====================
|
|
515
|
+
class TestParseMemory(unittest.TestCase):
|
|
516
|
+
"""Test memory parsing"""
|
|
517
|
+
|
|
518
|
+
def test_parse_memory_gi_to_mib(self):
|
|
519
|
+
"""Test parsing GiB to MiB"""
|
|
520
|
+
result = parse_memory("2Gi")
|
|
521
|
+
self.assertEqual(result, 2048)
|
|
522
|
+
|
|
523
|
+
def test_parse_memory_mi(self):
|
|
524
|
+
"""Test parsing MiB"""
|
|
525
|
+
result = parse_memory("512Mi")
|
|
526
|
+
self.assertEqual(result, 512)
|
|
527
|
+
|
|
528
|
+
def test_parse_memory_decimal_gi(self):
|
|
529
|
+
"""Test parsing decimal GiB"""
|
|
530
|
+
result = parse_memory("1.5Gi")
|
|
531
|
+
self.assertEqual(result, 1536)
|
|
532
|
+
|
|
533
|
+
def test_parse_memory_empty_string(self):
|
|
534
|
+
"""Test parsing empty string"""
|
|
535
|
+
result = parse_memory("")
|
|
536
|
+
self.assertEqual(result, 0)
|
|
537
|
+
|
|
538
|
+
def test_parse_memory_zero(self):
|
|
539
|
+
"""Test parsing zero"""
|
|
540
|
+
result = parse_memory("0")
|
|
541
|
+
self.assertEqual(result, 0)
|
|
542
|
+
|
|
543
|
+
def test_parse_memory_invalid_format(self):
|
|
544
|
+
"""Test parsing invalid format"""
|
|
545
|
+
result = parse_memory("invalid")
|
|
546
|
+
self.assertEqual(result, 0)
|
|
547
|
+
|
|
548
|
+
def test_parse_memory_none(self):
|
|
549
|
+
"""Test parsing None"""
|
|
550
|
+
result = parse_memory(None)
|
|
551
|
+
self.assertEqual(result, 0)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
# ==================== CPU Parsing Tests ====================
|
|
555
|
+
class TestParseCpu(unittest.TestCase):
|
|
556
|
+
"""Test CPU parsing"""
|
|
557
|
+
|
|
558
|
+
def test_parse_cpu_integer_string(self):
|
|
559
|
+
"""Test parsing integer string"""
|
|
560
|
+
result = parse_cpu("4")
|
|
561
|
+
self.assertEqual(result, 4)
|
|
562
|
+
|
|
563
|
+
def test_parse_cpu_integer(self):
|
|
564
|
+
"""Test parsing integer"""
|
|
565
|
+
result = parse_cpu(4)
|
|
566
|
+
self.assertEqual(result, 4)
|
|
567
|
+
|
|
568
|
+
def test_parse_cpu_float_string(self):
|
|
569
|
+
"""Test parsing float string"""
|
|
570
|
+
result = parse_cpu("2.5")
|
|
571
|
+
self.assertEqual(result, 2)
|
|
572
|
+
|
|
573
|
+
def test_parse_cpu_float(self):
|
|
574
|
+
"""Test parsing float"""
|
|
575
|
+
result = parse_cpu(2.8)
|
|
576
|
+
self.assertEqual(result, 2)
|
|
577
|
+
|
|
578
|
+
def test_parse_cpu_invalid(self):
|
|
579
|
+
"""Test parsing invalid string"""
|
|
580
|
+
result = parse_cpu("invalid")
|
|
581
|
+
self.assertEqual(result, 0)
|
|
582
|
+
|
|
583
|
+
def test_parse_cpu_empty_string(self):
|
|
584
|
+
"""Test parsing empty string"""
|
|
585
|
+
result = parse_cpu("")
|
|
586
|
+
self.assertEqual(result, 0)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
# ==================== Resources Tests ====================
|
|
590
|
+
class TestGetResources(unittest.TestCase):
|
|
591
|
+
"""Test resources extraction"""
|
|
592
|
+
|
|
593
|
+
def test_get_resources_valid_json(self):
|
|
594
|
+
"""Test getting resources with valid JSON"""
|
|
595
|
+
resources_dict = {"requests": {"memory": "2Gi", "cpu": "500m"}, "limits": {"memory": "4Gi", "cpu": "1000m"}}
|
|
596
|
+
wiz_entity_properties = {"resources": json.dumps(resources_dict)}
|
|
597
|
+
|
|
598
|
+
result = get_resources(wiz_entity_properties)
|
|
599
|
+
|
|
600
|
+
self.assertEqual(result, {"memory": "2Gi", "cpu": "500m"})
|
|
601
|
+
|
|
602
|
+
def test_get_resources_no_resources(self):
|
|
603
|
+
"""Test getting resources with no resources key"""
|
|
604
|
+
wiz_entity_properties = {}
|
|
605
|
+
|
|
606
|
+
result = get_resources(wiz_entity_properties)
|
|
607
|
+
|
|
608
|
+
self.assertEqual(result, {})
|
|
609
|
+
|
|
610
|
+
def test_get_resources_invalid_json(self):
|
|
611
|
+
"""Test getting resources with invalid JSON"""
|
|
612
|
+
wiz_entity_properties = {"resources": "invalid json"}
|
|
613
|
+
|
|
614
|
+
result = get_resources(wiz_entity_properties)
|
|
615
|
+
|
|
616
|
+
self.assertEqual(result, {})
|
|
617
|
+
|
|
618
|
+
def test_get_resources_no_requests(self):
|
|
619
|
+
"""Test getting resources with no requests key"""
|
|
620
|
+
resources_dict = {"limits": {"memory": "4Gi", "cpu": "1000m"}}
|
|
621
|
+
wiz_entity_properties = {"resources": json.dumps(resources_dict)}
|
|
622
|
+
|
|
623
|
+
result = get_resources(wiz_entity_properties)
|
|
624
|
+
|
|
625
|
+
self.assertEqual(result, {})
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
# ==================== Resource Info Tests ====================
|
|
629
|
+
class TestPullResourceInfo(unittest.TestCase):
|
|
630
|
+
"""Test resource info extraction"""
|
|
631
|
+
|
|
632
|
+
@patch(f"{PATH}.get_resources")
|
|
633
|
+
@patch(f"{PATH}.parse_memory")
|
|
634
|
+
@patch(f"{PATH}.parse_cpu")
|
|
635
|
+
def test_pull_resource_info_from_props(self, mock_parse_cpu, mock_parse_memory, mock_get_resources):
|
|
636
|
+
"""Test pulling resource info from properties"""
|
|
637
|
+
mock_get_resources.return_value = {"memory": "2Gi", "cpu": "2"}
|
|
638
|
+
mock_parse_memory.return_value = 2048
|
|
639
|
+
mock_parse_cpu.side_effect = [2, 4]
|
|
640
|
+
|
|
641
|
+
wiz_entity_properties = {"vCPUs": "4"}
|
|
642
|
+
|
|
643
|
+
memory, cpu = pull_resource_info_from_props(wiz_entity_properties)
|
|
644
|
+
|
|
645
|
+
self.assertEqual(memory, 2048)
|
|
646
|
+
self.assertEqual(cpu, 4)
|
|
647
|
+
mock_get_resources.assert_called_once_with(wiz_entity_properties)
|
|
648
|
+
|
|
649
|
+
@patch(f"{PATH}.get_resources")
|
|
650
|
+
@patch(f"{PATH}.parse_memory")
|
|
651
|
+
@patch(f"{PATH}.parse_cpu")
|
|
652
|
+
def test_pull_resource_info_from_props_no_vcpus(self, mock_parse_cpu, mock_parse_memory, mock_get_resources):
|
|
653
|
+
"""Test pulling resource info without vCPUs"""
|
|
654
|
+
mock_get_resources.return_value = {"memory": "1Gi", "cpu": "1"}
|
|
655
|
+
mock_parse_memory.return_value = 1024
|
|
656
|
+
mock_parse_cpu.side_effect = [1, 1]
|
|
657
|
+
|
|
658
|
+
wiz_entity_properties = {}
|
|
659
|
+
|
|
660
|
+
memory, cpu = pull_resource_info_from_props(wiz_entity_properties)
|
|
661
|
+
|
|
662
|
+
self.assertEqual(memory, 1024)
|
|
663
|
+
self.assertEqual(cpu, 1)
|
|
664
|
+
|
|
665
|
+
@patch(f"{PATH}.get_resources")
|
|
666
|
+
@patch(f"{PATH}.parse_memory")
|
|
667
|
+
@patch(f"{PATH}.parse_cpu")
|
|
668
|
+
def test_pull_resource_info_from_props_empty_resources(self, mock_parse_cpu, mock_parse_memory, mock_get_resources):
|
|
669
|
+
"""Test pulling resource info with empty resources"""
|
|
670
|
+
mock_get_resources.return_value = {}
|
|
671
|
+
mock_parse_memory.return_value = 0
|
|
672
|
+
mock_parse_cpu.side_effect = [0, 0]
|
|
673
|
+
|
|
674
|
+
wiz_entity_properties = {}
|
|
675
|
+
|
|
676
|
+
memory, cpu = pull_resource_info_from_props(wiz_entity_properties)
|
|
677
|
+
|
|
678
|
+
self.assertEqual(memory, 0)
|
|
679
|
+
self.assertEqual(cpu, 0)
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
# ==================== Disk Storage Tests ====================
|
|
683
|
+
class TestGetDiskStorage(unittest.TestCase):
|
|
684
|
+
"""Test disk storage extraction"""
|
|
685
|
+
|
|
686
|
+
def test_get_disk_storage_valid(self):
|
|
687
|
+
"""Test getting disk storage with valid integer"""
|
|
688
|
+
wiz_entity_properties = {"totalDisks": 100}
|
|
689
|
+
|
|
690
|
+
result = get_disk_storage(wiz_entity_properties)
|
|
691
|
+
|
|
692
|
+
self.assertEqual(result, 100)
|
|
693
|
+
|
|
694
|
+
def test_get_disk_storage_string(self):
|
|
695
|
+
"""Test getting disk storage with string integer"""
|
|
696
|
+
wiz_entity_properties = {"totalDisks": "250"}
|
|
697
|
+
|
|
698
|
+
result = get_disk_storage(wiz_entity_properties)
|
|
699
|
+
|
|
700
|
+
self.assertEqual(result, 250)
|
|
701
|
+
|
|
702
|
+
def test_get_disk_storage_no_key(self):
|
|
703
|
+
"""Test getting disk storage with no key"""
|
|
704
|
+
wiz_entity_properties = {}
|
|
705
|
+
|
|
706
|
+
result = get_disk_storage(wiz_entity_properties)
|
|
707
|
+
|
|
708
|
+
self.assertEqual(result, 0)
|
|
709
|
+
|
|
710
|
+
def test_get_disk_storage_invalid(self):
|
|
711
|
+
"""Test getting disk storage with invalid value"""
|
|
712
|
+
wiz_entity_properties = {"totalDisks": "invalid"}
|
|
713
|
+
|
|
714
|
+
result = get_disk_storage(wiz_entity_properties)
|
|
715
|
+
|
|
716
|
+
self.assertEqual(result, 0)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
# ==================== Network Info Tests ====================
|
|
720
|
+
class TestGetNetworkInfo(unittest.TestCase):
|
|
721
|
+
"""Test network info extraction"""
|
|
722
|
+
|
|
723
|
+
@patch(f"{PATH}.get_ip_address")
|
|
724
|
+
def test_get_network_info_complete(self, mock_get_ip):
|
|
725
|
+
"""Test getting network info with all fields"""
|
|
726
|
+
mock_get_ip.return_value = ("192.168.1.1", None, None, None)
|
|
727
|
+
wiz_entity_properties = {"region": "us-east-1", "address": "192.168.1.1", "addressType": "IPV4"}
|
|
728
|
+
|
|
729
|
+
result = get_network_info(wiz_entity_properties)
|
|
730
|
+
|
|
731
|
+
self.assertEqual(result["region"], "us-east-1")
|
|
732
|
+
self.assertEqual(result["ip4_address"], "192.168.1.1")
|
|
733
|
+
self.assertIsNone(result["ip6_address"])
|
|
734
|
+
self.assertIsNone(result["dns"])
|
|
735
|
+
self.assertIsNone(result["url"])
|
|
736
|
+
|
|
737
|
+
@patch(f"{PATH}.get_ip_address")
|
|
738
|
+
def test_get_network_info_ipv6(self, mock_get_ip):
|
|
739
|
+
"""Test getting network info with IPv6"""
|
|
740
|
+
mock_get_ip.return_value = (None, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", None, None)
|
|
741
|
+
wiz_entity_properties = {
|
|
742
|
+
"region": "eu-west-1",
|
|
743
|
+
"address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
|
744
|
+
"addressType": "IPV6",
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
result = get_network_info(wiz_entity_properties)
|
|
748
|
+
|
|
749
|
+
self.assertEqual(result["region"], "eu-west-1")
|
|
750
|
+
self.assertIsNone(result["ip4_address"])
|
|
751
|
+
self.assertEqual(result["ip6_address"], "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
|
752
|
+
|
|
753
|
+
@patch(f"{PATH}.get_ip_address")
|
|
754
|
+
def test_get_network_info_dns(self, mock_get_ip):
|
|
755
|
+
"""Test getting network info with DNS"""
|
|
756
|
+
mock_get_ip.return_value = (None, None, "example.com", None)
|
|
757
|
+
wiz_entity_properties = {"region": "us-west-2", "address": "example.com", "addressType": "DNS"}
|
|
758
|
+
|
|
759
|
+
result = get_network_info(wiz_entity_properties)
|
|
760
|
+
|
|
761
|
+
self.assertEqual(result["dns"], "example.com")
|
|
762
|
+
|
|
763
|
+
@patch(f"{PATH}.get_ip_address")
|
|
764
|
+
def test_get_network_info_url(self, mock_get_ip):
|
|
765
|
+
"""Test getting network info with URL"""
|
|
766
|
+
mock_get_ip.return_value = (None, None, None, "https://example.com")
|
|
767
|
+
wiz_entity_properties = {"region": "ap-southeast-1", "address": "https://example.com", "addressType": "URL"}
|
|
768
|
+
|
|
769
|
+
result = get_network_info(wiz_entity_properties)
|
|
770
|
+
|
|
771
|
+
self.assertEqual(result["url"], "https://example.com")
|
|
772
|
+
|
|
773
|
+
@patch(f"{PATH}.get_ip_address")
|
|
774
|
+
def test_get_network_info_no_region(self, mock_get_ip):
|
|
775
|
+
"""Test getting network info without region"""
|
|
776
|
+
mock_get_ip.return_value = ("10.0.0.1", None, None, None)
|
|
777
|
+
wiz_entity_properties = {"address": "10.0.0.1", "addressType": "IPV4"}
|
|
778
|
+
|
|
779
|
+
result = get_network_info(wiz_entity_properties)
|
|
780
|
+
|
|
781
|
+
self.assertIsNone(result["region"])
|
|
782
|
+
self.assertEqual(result["ip4_address"], "10.0.0.1")
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
# ==================== Product IDs Tests ====================
|
|
786
|
+
class TestGetProductIds(unittest.TestCase):
|
|
787
|
+
"""Test product IDs extraction"""
|
|
788
|
+
|
|
789
|
+
def test_get_product_ids_list(self):
|
|
790
|
+
"""Test getting product IDs as list"""
|
|
791
|
+
wiz_entity_properties = {"_productIDs": ["prod-123", "prod-456", "prod-789"]}
|
|
792
|
+
|
|
793
|
+
result = get_product_ids(wiz_entity_properties)
|
|
794
|
+
|
|
795
|
+
self.assertEqual(result, "prod-123, prod-456, prod-789")
|
|
796
|
+
|
|
797
|
+
def test_get_product_ids_string(self):
|
|
798
|
+
"""Test getting product IDs as string"""
|
|
799
|
+
wiz_entity_properties = {"_productIDs": "prod-123"}
|
|
800
|
+
|
|
801
|
+
result = get_product_ids(wiz_entity_properties)
|
|
802
|
+
|
|
803
|
+
self.assertEqual(result, "prod-123")
|
|
804
|
+
|
|
805
|
+
def test_get_product_ids_empty_list(self):
|
|
806
|
+
"""Test getting product IDs with empty list"""
|
|
807
|
+
wiz_entity_properties = {"_productIDs": []}
|
|
808
|
+
|
|
809
|
+
result = get_product_ids(wiz_entity_properties)
|
|
810
|
+
|
|
811
|
+
# Empty list is falsy, so the function returns the list itself (not joined)
|
|
812
|
+
# Actually, the if condition checks "if product_ids and isinstance",
|
|
813
|
+
# empty list is falsy so it falls through to return product_ids (the empty list)
|
|
814
|
+
self.assertEqual(result, [])
|
|
815
|
+
|
|
816
|
+
def test_get_product_ids_no_key(self):
|
|
817
|
+
"""Test getting product IDs with no key"""
|
|
818
|
+
wiz_entity_properties = {}
|
|
819
|
+
|
|
820
|
+
result = get_product_ids(wiz_entity_properties)
|
|
821
|
+
|
|
822
|
+
self.assertIsNone(result)
|
|
823
|
+
|
|
824
|
+
def test_get_product_ids_none(self):
|
|
825
|
+
"""Test getting product IDs with None"""
|
|
826
|
+
wiz_entity_properties = {"_productIDs": None}
|
|
827
|
+
|
|
828
|
+
result = get_product_ids(wiz_entity_properties)
|
|
829
|
+
|
|
830
|
+
self.assertIsNone(result)
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
# ==================== IP Address Helper Tests ====================
|
|
834
|
+
class TestIpAddressHelpers(unittest.TestCase):
|
|
835
|
+
"""Test IP address helper functions"""
|
|
836
|
+
|
|
837
|
+
def test_get_ip_address_from_props_ipv4(self):
|
|
838
|
+
"""Test getting IP address from properties with IPv4"""
|
|
839
|
+
network_dict = {"ip4_address": "192.168.1.1", "ip6_address": None}
|
|
840
|
+
|
|
841
|
+
result = get_ip_address_from_props(network_dict)
|
|
842
|
+
|
|
843
|
+
self.assertEqual(result, "192.168.1.1")
|
|
844
|
+
|
|
845
|
+
def test_get_ip_address_from_props_ipv6(self):
|
|
846
|
+
"""Test getting IP address from properties with IPv6"""
|
|
847
|
+
network_dict = {"ip4_address": None, "ip6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334"}
|
|
848
|
+
|
|
849
|
+
result = get_ip_address_from_props(network_dict)
|
|
850
|
+
|
|
851
|
+
self.assertEqual(result, "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
|
852
|
+
|
|
853
|
+
def test_get_ip_address_from_props_both(self):
|
|
854
|
+
"""Test getting IP address from properties with both"""
|
|
855
|
+
network_dict = {"ip4_address": "192.168.1.1", "ip6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334"}
|
|
856
|
+
|
|
857
|
+
result = get_ip_address_from_props(network_dict)
|
|
858
|
+
|
|
859
|
+
# Should prefer IPv4
|
|
860
|
+
self.assertEqual(result, "192.168.1.1")
|
|
861
|
+
|
|
862
|
+
def test_get_ip_address_from_props_none(self):
|
|
863
|
+
"""Test getting IP address from properties with none"""
|
|
864
|
+
network_dict = {"ip4_address": None, "ip6_address": None}
|
|
865
|
+
|
|
866
|
+
result = get_ip_address_from_props(network_dict)
|
|
867
|
+
|
|
868
|
+
self.assertIsNone(result)
|
|
869
|
+
|
|
870
|
+
def test_get_ip_v4_from_props(self):
|
|
871
|
+
"""Test getting IPv4 from properties"""
|
|
872
|
+
network_dict = {"address": "10.0.0.1"}
|
|
873
|
+
|
|
874
|
+
result = get_ip_v4_from_props(network_dict)
|
|
875
|
+
|
|
876
|
+
self.assertEqual(result, "10.0.0.1")
|
|
877
|
+
|
|
878
|
+
def test_get_ip_v4_from_props_no_address(self):
|
|
879
|
+
"""Test getting IPv4 from properties with no address"""
|
|
880
|
+
network_dict = {}
|
|
881
|
+
|
|
882
|
+
result = get_ip_v4_from_props(network_dict)
|
|
883
|
+
|
|
884
|
+
self.assertIsNone(result)
|
|
885
|
+
|
|
886
|
+
def test_get_ip_v6_from_props(self):
|
|
887
|
+
"""Test getting IPv6 from properties"""
|
|
888
|
+
network_dict = {"ip6_address": "2001:0db8:85a3::8a2e:0370:7334"}
|
|
889
|
+
|
|
890
|
+
result = get_ip_v6_from_props(network_dict)
|
|
891
|
+
|
|
892
|
+
self.assertEqual(result, "2001:0db8:85a3::8a2e:0370:7334")
|
|
893
|
+
|
|
894
|
+
def test_get_ip_v6_from_props_no_address(self):
|
|
895
|
+
"""Test getting IPv6 from properties with no address"""
|
|
896
|
+
network_dict = {}
|
|
897
|
+
|
|
898
|
+
result = get_ip_v6_from_props(network_dict)
|
|
899
|
+
|
|
900
|
+
self.assertIsNone(result)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
# ==================== IP Address Extraction Tests ====================
|
|
904
|
+
class TestGetIpAddress(unittest.TestCase):
|
|
905
|
+
"""Test IP address extraction from Wiz entity properties"""
|
|
906
|
+
|
|
907
|
+
def test_get_ip_address_ipv4(self):
|
|
908
|
+
"""Test getting IPv4 address"""
|
|
909
|
+
wiz_entity_properties = {"address": "192.168.1.1", "addressType": "IPV4"}
|
|
910
|
+
|
|
911
|
+
ip4, ip6, dns, url = get_ip_address(wiz_entity_properties)
|
|
912
|
+
|
|
913
|
+
self.assertEqual(ip4, "192.168.1.1")
|
|
914
|
+
self.assertIsNone(ip6)
|
|
915
|
+
self.assertIsNone(dns)
|
|
916
|
+
self.assertIsNone(url)
|
|
917
|
+
|
|
918
|
+
def test_get_ip_address_ipv6(self):
|
|
919
|
+
"""Test getting IPv6 address"""
|
|
920
|
+
wiz_entity_properties = {"address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "addressType": "IPV6"}
|
|
921
|
+
|
|
922
|
+
ip4, ip6, dns, url = get_ip_address(wiz_entity_properties)
|
|
923
|
+
|
|
924
|
+
self.assertIsNone(ip4)
|
|
925
|
+
self.assertEqual(ip6, "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
|
926
|
+
self.assertIsNone(dns)
|
|
927
|
+
self.assertIsNone(url)
|
|
928
|
+
|
|
929
|
+
def test_get_ip_address_dns(self):
|
|
930
|
+
"""Test getting DNS address"""
|
|
931
|
+
wiz_entity_properties = {"address": "example.com", "addressType": "DNS"}
|
|
932
|
+
|
|
933
|
+
ip4, ip6, dns, url = get_ip_address(wiz_entity_properties)
|
|
934
|
+
|
|
935
|
+
self.assertIsNone(ip4)
|
|
936
|
+
self.assertIsNone(ip6)
|
|
937
|
+
self.assertEqual(dns, "example.com")
|
|
938
|
+
self.assertIsNone(url)
|
|
939
|
+
|
|
940
|
+
def test_get_ip_address_url(self):
|
|
941
|
+
"""Test getting URL address"""
|
|
942
|
+
wiz_entity_properties = {"address": "https://example.com", "addressType": "URL"}
|
|
943
|
+
|
|
944
|
+
ip4, ip6, dns, url = get_ip_address(wiz_entity_properties)
|
|
945
|
+
|
|
946
|
+
self.assertIsNone(ip4)
|
|
947
|
+
self.assertIsNone(ip6)
|
|
948
|
+
self.assertIsNone(dns)
|
|
949
|
+
self.assertEqual(url, "https://example.com")
|
|
950
|
+
|
|
951
|
+
def test_get_ip_address_no_address(self):
|
|
952
|
+
"""Test getting IP address with no address field"""
|
|
953
|
+
wiz_entity_properties = {}
|
|
954
|
+
|
|
955
|
+
ip4, ip6, dns, url = get_ip_address(wiz_entity_properties)
|
|
956
|
+
|
|
957
|
+
self.assertIsNone(ip4)
|
|
958
|
+
self.assertIsNone(ip6)
|
|
959
|
+
self.assertIsNone(dns)
|
|
960
|
+
self.assertIsNone(url)
|
|
961
|
+
|
|
962
|
+
def test_get_ip_address_unknown_type(self):
|
|
963
|
+
"""Test getting IP address with unknown type"""
|
|
964
|
+
wiz_entity_properties = {"address": "some-value", "addressType": "UNKNOWN"}
|
|
965
|
+
|
|
966
|
+
ip4, ip6, dns, url = get_ip_address(wiz_entity_properties)
|
|
967
|
+
|
|
968
|
+
self.assertIsNone(ip4)
|
|
969
|
+
self.assertIsNone(ip6)
|
|
970
|
+
self.assertIsNone(dns)
|
|
971
|
+
self.assertIsNone(url)
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
# ==================== Wiz Data Fetching Tests ====================
|
|
975
|
+
class TestFetchWizData(unittest.TestCase):
|
|
976
|
+
"""Test fetching data from Wiz API"""
|
|
977
|
+
|
|
978
|
+
@patch(f"{PATH}.WizVariables")
|
|
979
|
+
@patch(f"{PATH}.PaginatedGraphQLClient")
|
|
980
|
+
def test_fetch_wiz_data_success(self, mock_client_class, mock_vars):
|
|
981
|
+
"""Test successful Wiz data fetch"""
|
|
982
|
+
mock_vars.wizUrl = "https://api.wiz.io/graphql"
|
|
983
|
+
|
|
984
|
+
mock_client = MagicMock()
|
|
985
|
+
mock_client_class.return_value = mock_client
|
|
986
|
+
mock_client.fetch_all.return_value = [{"id": "1", "name": "test"}, {"id": "2", "name": "test2"}]
|
|
987
|
+
|
|
988
|
+
query = "query { test }"
|
|
989
|
+
variables = {"var1": "value1"}
|
|
990
|
+
topic_key = "testTopic"
|
|
991
|
+
token = "test-token"
|
|
992
|
+
|
|
993
|
+
result = fetch_wiz_data(query, variables, topic_key, token)
|
|
994
|
+
|
|
995
|
+
self.assertEqual(len(result), 2)
|
|
996
|
+
self.assertEqual(result[0]["id"], "1")
|
|
997
|
+
mock_client_class.assert_called_once()
|
|
998
|
+
mock_client.fetch_all.assert_called_once_with(variables=variables, topic_key=topic_key)
|
|
999
|
+
|
|
1000
|
+
@patch(f"{PATH}.WizVariables")
|
|
1001
|
+
@patch(f"{PATH}.PaginatedGraphQLClient")
|
|
1002
|
+
def test_fetch_wiz_data_custom_endpoint(self, mock_client_class, mock_vars):
|
|
1003
|
+
"""Test Wiz data fetch with custom endpoint"""
|
|
1004
|
+
mock_vars.wizUrl = "https://api.wiz.io/graphql"
|
|
1005
|
+
|
|
1006
|
+
mock_client = MagicMock()
|
|
1007
|
+
mock_client_class.return_value = mock_client
|
|
1008
|
+
mock_client.fetch_all.return_value = []
|
|
1009
|
+
|
|
1010
|
+
query = "query { test }"
|
|
1011
|
+
variables = {}
|
|
1012
|
+
topic_key = "testTopic"
|
|
1013
|
+
token = "test-token"
|
|
1014
|
+
custom_endpoint = "https://custom.wiz.io/graphql"
|
|
1015
|
+
|
|
1016
|
+
fetch_wiz_data(query, variables, topic_key, token, custom_endpoint)
|
|
1017
|
+
|
|
1018
|
+
# Should use custom endpoint instead of default
|
|
1019
|
+
call_args = mock_client_class.call_args
|
|
1020
|
+
self.assertEqual(call_args.kwargs["endpoint"], custom_endpoint)
|
|
1021
|
+
|
|
1022
|
+
@patch(f"{PATH}.WizVariables")
|
|
1023
|
+
@patch(f"{PATH}.PaginatedGraphQLClient")
|
|
1024
|
+
def test_fetch_wiz_data_empty_results(self, mock_client_class, mock_vars):
|
|
1025
|
+
"""Test Wiz data fetch with empty results"""
|
|
1026
|
+
mock_vars.wizUrl = "https://api.wiz.io/graphql"
|
|
1027
|
+
|
|
1028
|
+
mock_client = MagicMock()
|
|
1029
|
+
mock_client_class.return_value = mock_client
|
|
1030
|
+
mock_client.fetch_all.return_value = []
|
|
1031
|
+
|
|
1032
|
+
query = "query { test }"
|
|
1033
|
+
variables = {}
|
|
1034
|
+
topic_key = "testTopic"
|
|
1035
|
+
token = "test-token"
|
|
1036
|
+
|
|
1037
|
+
result = fetch_wiz_data(query, variables, topic_key, token)
|
|
1038
|
+
|
|
1039
|
+
self.assertEqual(result, [])
|
|
1040
|
+
|
|
1041
|
+
@patch(f"{PATH}.WizVariables")
|
|
1042
|
+
@patch(f"{PATH}.PaginatedGraphQLClient")
|
|
1043
|
+
def test_fetch_wiz_data_headers(self, mock_client_class, mock_vars):
|
|
1044
|
+
"""Test Wiz data fetch with proper headers"""
|
|
1045
|
+
mock_vars.wizUrl = "https://api.wiz.io/graphql"
|
|
1046
|
+
|
|
1047
|
+
mock_client = MagicMock()
|
|
1048
|
+
mock_client_class.return_value = mock_client
|
|
1049
|
+
mock_client.fetch_all.return_value = []
|
|
1050
|
+
|
|
1051
|
+
query = "query { test }"
|
|
1052
|
+
variables = {}
|
|
1053
|
+
topic_key = "testTopic"
|
|
1054
|
+
token = "test-token-12345"
|
|
1055
|
+
|
|
1056
|
+
fetch_wiz_data(query, variables, topic_key, token)
|
|
1057
|
+
|
|
1058
|
+
# Verify headers were set correctly
|
|
1059
|
+
call_args = mock_client_class.call_args
|
|
1060
|
+
self.assertIn("Authorization", call_args.kwargs["headers"])
|
|
1061
|
+
self.assertEqual(call_args.kwargs["headers"]["Authorization"], "Bearer test-token-12345")
|
|
1062
|
+
self.assertEqual(call_args.kwargs["headers"]["Content-Type"], "application/json")
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
# ==================== Edge Cases and Error Handling Tests ====================
|
|
1066
|
+
class TestEdgeCasesAndErrorHandling(unittest.TestCase):
|
|
1067
|
+
"""Test edge cases and error handling"""
|
|
1068
|
+
|
|
1069
|
+
def test_parse_memory_very_large_value(self):
|
|
1070
|
+
"""Test parsing very large memory value"""
|
|
1071
|
+
result = parse_memory("1024Gi")
|
|
1072
|
+
self.assertEqual(result, 1048576)
|
|
1073
|
+
|
|
1074
|
+
def test_parse_cpu_negative_value(self):
|
|
1075
|
+
"""Test parsing negative CPU value"""
|
|
1076
|
+
result = parse_cpu("-1")
|
|
1077
|
+
self.assertEqual(result, -1)
|
|
1078
|
+
|
|
1079
|
+
def test_collect_components_to_create_special_characters(self):
|
|
1080
|
+
"""Test collecting components with special characters"""
|
|
1081
|
+
data = [{"type": "virtual-machine_v2.1"}, {"type": "test_component-special"}]
|
|
1082
|
+
components_to_create = []
|
|
1083
|
+
|
|
1084
|
+
result = collect_components_to_create(data, components_to_create)
|
|
1085
|
+
|
|
1086
|
+
self.assertIn("Virtual-Machine V2.1", result)
|
|
1087
|
+
self.assertIn("Test Component-Special", result)
|
|
1088
|
+
|
|
1089
|
+
def test_handle_container_image_version_multiple_tags(self):
|
|
1090
|
+
"""Test handling container with multiple tags (should return first)"""
|
|
1091
|
+
image_tags = ["v1.0.0", "v1.0.1", "latest"]
|
|
1092
|
+
name = "nginx"
|
|
1093
|
+
|
|
1094
|
+
result = handle_container_image_version(image_tags, name)
|
|
1095
|
+
|
|
1096
|
+
self.assertEqual(result, "v1.0.0")
|
|
1097
|
+
|
|
1098
|
+
def test_get_cloud_identifier_case_insensitive(self):
|
|
1099
|
+
"""Test cloud identifier is case insensitive"""
|
|
1100
|
+
wiz_entity_properties = {"providerUniqueId": "AWS-RESOURCE-12345"}
|
|
1101
|
+
|
|
1102
|
+
provider, identifier = get_cloud_identifier(wiz_entity_properties)
|
|
1103
|
+
|
|
1104
|
+
self.assertEqual(provider, "aws")
|
|
1105
|
+
|
|
1106
|
+
@patch(f"{PATH}.extract_product_name_and_version")
|
|
1107
|
+
def test_get_software_name_from_cpe_exception_handling(self, mock_extract):
|
|
1108
|
+
"""Test CPE extraction with exception"""
|
|
1109
|
+
mock_extract.side_effect = Exception("Parse error")
|
|
1110
|
+
wiz_entity_properties = {"cpe": "invalid-cpe"}
|
|
1111
|
+
name = "Test Software"
|
|
1112
|
+
|
|
1113
|
+
# Should not raise, should return default dict
|
|
1114
|
+
with self.assertRaises(Exception):
|
|
1115
|
+
get_software_name_from_cpe(wiz_entity_properties, name)
|
|
1116
|
+
|
|
1117
|
+
def test_get_product_ids_mixed_types_in_list(self):
|
|
1118
|
+
"""Test product IDs with mixed types in list - should raise TypeError"""
|
|
1119
|
+
wiz_entity_properties = {"_productIDs": ["prod-123", 456, None, "prod-789"]}
|
|
1120
|
+
|
|
1121
|
+
# The function expects all strings in the list, mixed types will cause TypeError
|
|
1122
|
+
with self.assertRaises(TypeError):
|
|
1123
|
+
get_product_ids(wiz_entity_properties)
|
|
1124
|
+
|
|
1125
|
+
def test_parse_memory_float_mi(self):
|
|
1126
|
+
"""Test parsing float MiB value"""
|
|
1127
|
+
result = parse_memory("512.5Mi")
|
|
1128
|
+
self.assertEqual(result, 512)
|
|
1129
|
+
|
|
1130
|
+
def test_get_disk_storage_zero_string(self):
|
|
1131
|
+
"""Test disk storage with zero string"""
|
|
1132
|
+
wiz_entity_properties = {"totalDisks": "0"}
|
|
1133
|
+
|
|
1134
|
+
result = get_disk_storage(wiz_entity_properties)
|
|
1135
|
+
|
|
1136
|
+
self.assertEqual(result, 0)
|
|
1137
|
+
|
|
1138
|
+
def test_get_network_info_all_address_types(self):
|
|
1139
|
+
"""Test network info can only extract one address type at a time"""
|
|
1140
|
+
# Each call to get_ip_address returns one type based on addressType
|
|
1141
|
+
wiz_entity_properties = {"address": "192.168.1.1", "addressType": "IPV4", "region": "us-east-1"}
|
|
1142
|
+
|
|
1143
|
+
result = get_network_info(wiz_entity_properties)
|
|
1144
|
+
|
|
1145
|
+
# Should only get IPv4 since addressType is IPV4
|
|
1146
|
+
self.assertEqual(result["ip4_address"], "192.168.1.1")
|
|
1147
|
+
self.assertIsNone(result["ip6_address"])
|
|
1148
|
+
self.assertIsNone(result["dns"])
|
|
1149
|
+
self.assertIsNone(result["url"])
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
if __name__ == "__main__":
|
|
1153
|
+
unittest.main()
|