regscale-cli 6.25.0.1__py3-none-any.whl → 6.26.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.

Files changed (84) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/application.py +18 -3
  4. regscale/core/app/internal/login.py +0 -1
  5. regscale/core/app/utils/catalog_utils/common.py +1 -1
  6. regscale/integrations/commercial/sicura/api.py +14 -13
  7. regscale/integrations/commercial/sicura/commands.py +8 -2
  8. regscale/integrations/commercial/sicura/scanner.py +49 -39
  9. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  10. regscale/integrations/commercial/synqly/assets.py +17 -0
  11. regscale/integrations/commercial/wizv2/click.py +26 -26
  12. regscale/integrations/commercial/wizv2/compliance_report.py +152 -157
  13. regscale/integrations/commercial/wizv2/constants.py +20 -71
  14. regscale/integrations/commercial/wizv2/scanner.py +3 -3
  15. regscale/integrations/compliance_integration.py +67 -2
  16. regscale/integrations/control_matcher.py +358 -0
  17. regscale/integrations/due_date_handler.py +118 -6
  18. regscale/integrations/milestone_manager.py +291 -0
  19. regscale/integrations/public/__init__.py +1 -0
  20. regscale/integrations/public/cci_importer.py +37 -38
  21. regscale/integrations/public/fedramp/click.py +60 -2
  22. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  23. regscale/integrations/scanner_integration.py +199 -130
  24. regscale/models/integration_models/cisa_kev_data.json +199 -4
  25. regscale/models/integration_models/nexpose.py +36 -10
  26. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  27. regscale/models/locking.py +12 -8
  28. regscale/models/platform.py +1 -2
  29. regscale/models/regscale_models/control_implementation.py +46 -21
  30. regscale/models/regscale_models/issue.py +256 -94
  31. regscale/models/regscale_models/milestone.py +1 -1
  32. regscale/models/regscale_models/regscale_model.py +6 -1
  33. regscale/templates/__init__.py +0 -0
  34. regscale/utils/threading/threadhandler.py +20 -15
  35. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/METADATA +1 -1
  36. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/RECORD +84 -37
  37. tests/regscale/integrations/commercial/__init__.py +0 -0
  38. tests/regscale/integrations/commercial/conftest.py +28 -0
  39. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  40. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  41. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  42. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  43. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  44. tests/regscale/integrations/commercial/test_aws.py +3731 -0
  45. tests/regscale/integrations/commercial/test_burp.py +48 -0
  46. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  47. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  48. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  49. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  50. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  51. tests/regscale/integrations/commercial/test_jira.py +1814 -0
  52. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  53. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  54. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  55. tests/regscale/integrations/commercial/test_sicura.py +350 -0
  56. tests/regscale/integrations/commercial/test_snow.py +423 -0
  57. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  58. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  59. tests/regscale/integrations/commercial/test_stig.py +33 -0
  60. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  61. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  62. tests/regscale/integrations/commercial/test_wiz.py +1469 -0
  63. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  64. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  65. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  66. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  67. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  68. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1351 -0
  69. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  70. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  71. tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +750 -0
  72. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  73. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +264 -0
  74. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +624 -0
  75. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  76. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  77. tests/regscale/integrations/test_control_matcher.py +1314 -0
  78. tests/regscale/integrations/test_control_matching.py +155 -0
  79. tests/regscale/integrations/test_milestone_manager.py +408 -0
  80. tests/regscale/models/test_issue.py +378 -1
  81. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/LICENSE +0 -0
  82. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/WHEEL +0 -0
  83. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/entry_points.txt +0 -0
  84. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,149 @@
1
+ """
2
+ Unit tests for Wiz status mapping functionality.
3
+ Tests that Wiz issue statuses are correctly mapped to RegScale IssueStatus.
4
+ """
5
+
6
+ from unittest.mock import Mock, patch
7
+
8
+ import pytest
9
+
10
+ from regscale.integrations.commercial.wizv2.issue import WizIssue
11
+ from regscale.integrations.commercial.wizv2.scanner import WizVulnerabilityIntegration
12
+ from regscale.models import IssueStatus
13
+
14
+
15
+ class TestWizStatusMapping:
16
+ """Test class for Wiz status mapping functionality."""
17
+
18
+ @pytest.fixture
19
+ def mock_app(self):
20
+ """Create a mock app with config."""
21
+ app = Mock()
22
+ app.config = {}
23
+ return app
24
+
25
+ @pytest.fixture
26
+ def wiz_integration(self, mock_app):
27
+ """Create a WizVulnerabilityIntegration instance."""
28
+ with patch("regscale.integrations.scanner_integration.ScannerIntegration.__init__"):
29
+ integration = WizVulnerabilityIntegration(app=mock_app, plan_id=1)
30
+ integration.app = mock_app
31
+ integration._compliance_settings = None
32
+ return integration
33
+
34
+ @pytest.fixture
35
+ def wiz_issue_integration(self, mock_app):
36
+ """Create a WizIssue instance."""
37
+ with patch("regscale.integrations.scanner_integration.ScannerIntegration.__init__"):
38
+ integration = WizIssue(app=mock_app, plan_id=1)
39
+ integration.app = mock_app
40
+ integration._compliance_settings = None
41
+ return integration
42
+
43
+ def test_default_status_mapping_open_statuses(self, wiz_integration):
44
+ """Test that OPEN and IN_PROGRESS statuses map to IssueStatus.Open."""
45
+ open_statuses = ["OPEN", "Open", "open", "IN_PROGRESS", "In_Progress", "in_progress"]
46
+
47
+ for status in open_statuses:
48
+ result = wiz_integration._get_default_issue_status_mapping(status)
49
+ assert result == IssueStatus.Open, f"Status '{status}' should map to Open, got {result}"
50
+
51
+ def test_default_status_mapping_closed_statuses(self, wiz_integration):
52
+ """Test that RESOLVED and REJECTED statuses map to IssueStatus.Closed."""
53
+ closed_statuses = ["RESOLVED", "Resolved", "resolved", "REJECTED", "Rejected", "rejected"]
54
+
55
+ for status in closed_statuses:
56
+ result = wiz_integration._get_default_issue_status_mapping(status)
57
+ assert result == IssueStatus.Closed, f"Status '{status}' should map to Closed, got {result}"
58
+
59
+ def test_default_status_mapping_unknown_status(self, wiz_integration):
60
+ """Test that unknown statuses default to IssueStatus.Open."""
61
+ unknown_statuses = ["UNKNOWN", "PENDING", "WEIRD_STATUS", ""]
62
+
63
+ for status in unknown_statuses:
64
+ result = wiz_integration._get_default_issue_status_mapping(status)
65
+ assert result == IssueStatus.Open, f"Unknown status '{status}' should default to Open, got {result}"
66
+
67
+ def test_map_status_to_issue_status_without_compliance_settings(self, wiz_integration):
68
+ """Test status mapping without compliance settings (uses default mapping)."""
69
+ # Ensure no compliance settings
70
+ wiz_integration._compliance_settings = None
71
+
72
+ test_cases = [
73
+ ("OPEN", IssueStatus.Open),
74
+ ("IN_PROGRESS", IssueStatus.Open),
75
+ ("RESOLVED", IssueStatus.Closed),
76
+ ("REJECTED", IssueStatus.Closed),
77
+ ("UNKNOWN", IssueStatus.Open),
78
+ ]
79
+
80
+ for wiz_status, expected in test_cases:
81
+ result = wiz_integration.map_status_to_issue_status(wiz_status)
82
+ assert result == expected, f"Status '{wiz_status}' should map to {expected}, got {result}"
83
+
84
+ def test_map_status_to_issue_status_with_compliance_settings(self, wiz_integration):
85
+ """Test status mapping with compliance settings."""
86
+ # Mock compliance settings
87
+ mock_compliance = Mock()
88
+ mock_compliance.get_field_labels.return_value = ["Open", "In Progress", "Closed", "Resolved"]
89
+ wiz_integration._compliance_settings = mock_compliance
90
+
91
+ # Test that IN_PROGRESS still maps to Open even with compliance settings
92
+ result = wiz_integration.map_status_to_issue_status("IN_PROGRESS")
93
+ assert result == IssueStatus.Open, "IN_PROGRESS should map to Open with compliance settings"
94
+
95
+ # Test other statuses
96
+ result = wiz_integration.map_status_to_issue_status("RESOLVED")
97
+ assert result == IssueStatus.Closed, "RESOLVED should map to Closed with compliance settings"
98
+
99
+ def test_match_wiz_status_to_label(self, wiz_integration):
100
+ """Test the _match_wiz_status_to_label method."""
101
+ # Test open status mappings
102
+ assert wiz_integration._match_wiz_status_to_label("open", "Open") == IssueStatus.Open
103
+ assert wiz_integration._match_wiz_status_to_label("in_progress", "In Progress") == IssueStatus.Open
104
+ assert wiz_integration._match_wiz_status_to_label("in_progress", "Active") == IssueStatus.Open
105
+
106
+ # Test closed status mappings
107
+ assert wiz_integration._match_wiz_status_to_label("resolved", "Closed") == IssueStatus.Closed
108
+ assert wiz_integration._match_wiz_status_to_label("rejected", "Resolved") == IssueStatus.Closed
109
+
110
+ # Test non-matching combinations
111
+ assert wiz_integration._match_wiz_status_to_label("open", "Closed") is None
112
+ assert wiz_integration._match_wiz_status_to_label("resolved", "Open") is None
113
+
114
+ def test_wiz_issue_status_mapping(self, wiz_issue_integration):
115
+ """Test that WizIssue class uses the same status mapping."""
116
+ test_cases = [
117
+ ("OPEN", IssueStatus.Open),
118
+ ("IN_PROGRESS", IssueStatus.Open),
119
+ ("RESOLVED", IssueStatus.Closed),
120
+ ("REJECTED", IssueStatus.Closed),
121
+ ]
122
+
123
+ for wiz_status, expected in test_cases:
124
+ result = wiz_issue_integration.map_status_to_issue_status(wiz_status)
125
+ assert result == expected, f"WizIssue: Status '{wiz_status}' should map to {expected}, got {result}"
126
+
127
+ @pytest.mark.parametrize(
128
+ "status,expected",
129
+ [
130
+ ("OPEN", IssueStatus.Open),
131
+ ("Open", IssueStatus.Open),
132
+ ("open", IssueStatus.Open),
133
+ ("IN_PROGRESS", IssueStatus.Open),
134
+ ("In_Progress", IssueStatus.Open),
135
+ ("in_progress", IssueStatus.Open),
136
+ ("RESOLVED", IssueStatus.Closed),
137
+ ("Resolved", IssueStatus.Closed),
138
+ ("resolved", IssueStatus.Closed),
139
+ ("REJECTED", IssueStatus.Closed),
140
+ ("Rejected", IssueStatus.Closed),
141
+ ("rejected", IssueStatus.Closed),
142
+ ("UNKNOWN_STATUS", IssueStatus.Open),
143
+ ("", IssueStatus.Open),
144
+ ],
145
+ )
146
+ def test_comprehensive_status_mapping(self, wiz_integration, status, expected):
147
+ """Comprehensive parameterized test for all status variations."""
148
+ result = wiz_integration.map_status_to_issue_status(status)
149
+ assert result == expected, f"Status '{status}' should map to {expected}, got {result}"
@@ -0,0 +1,264 @@
1
+ """
2
+ Unit tests for WizVulnerabilityIntegration
3
+ """
4
+
5
+ import logging
6
+ import unittest
7
+ from unittest.mock import patch, MagicMock
8
+
9
+ from regscale.core.app.utils.api_handler import APIHandler
10
+ from regscale.integrations.commercial.wizv2.scanner import WizVulnerabilityIntegration
11
+ from regscale.integrations.commercial.wizv2.variables import WizVariables
12
+ from regscale.integrations.variables import ScannerVariables
13
+ from regscale.models import regscale_models
14
+ from tests.regscale.integrations.commercial.wizv2 import (
15
+ asset_nodes,
16
+ vuln_nodes,
17
+ PROJECT_ID,
18
+ PLAN_ID,
19
+ )
20
+
21
+ logger = logging.getLogger("regscale")
22
+
23
+
24
+ class TestWizVulnerabilityIntegration(unittest.TestCase):
25
+ regscale_version = APIHandler().regscale_version
26
+ project_id = PROJECT_ID
27
+ plan_id = PLAN_ID
28
+
29
+ def clean_plan(self, plan_id):
30
+ if self.regscale_version >= "5.64.0" or self.regscale_version == "localdev":
31
+ for scan in regscale_models.ScanHistory.get_all_by_parent(
32
+ plan_id, regscale_models.SecurityPlan.get_module_string()
33
+ ):
34
+ for vuln_mapping in regscale_models.VulnerabilityMapping.find_by_scan(scan.id):
35
+ vuln_mapping.delete()
36
+ # No delete api
37
+ # scan.delete()
38
+ for asset in regscale_models.Asset.get_all_by_parent(plan_id, regscale_models.SecurityPlan.get_module_string()):
39
+ for issue in regscale_models.Issue.get_all_by_parent(asset.id, asset.get_module_string()):
40
+ issue.delete()
41
+ asset.delete()
42
+ for issue in regscale_models.Issue.get_all_by_parent(plan_id, regscale_models.SecurityPlan.get_module_string()):
43
+ issue.delete()
44
+
45
+ def assert_vulnerability_counts(self, assets, expected_counts):
46
+ for asset in assets:
47
+ vulnerability_ids = self.get_vulnerability_ids(asset)
48
+ expected_count = expected_counts.get(asset.wizId, 0)
49
+ if expected_count != len(vulnerability_ids):
50
+ logger.error(f"Vulnerabilities for asset {asset.wizId}: {vulnerability_ids}")
51
+ self.assertEqual(
52
+ expected_count,
53
+ len(vulnerability_ids),
54
+ f"Expected {expected_count} vulnerability ids for asset {asset.wizId}, got {vulnerability_ids}",
55
+ )
56
+
57
+ def get_vulnerability_ids(self, asset):
58
+ if self.regscale_version >= "5.64.0" or self.regscale_version == "localdev":
59
+ return {
60
+ vuln_mapping.vulnerabilityId
61
+ for vuln_mapping in regscale_models.VulnerabilityMapping.find_by_asset(asset.id, status="Open")
62
+ }
63
+ return set()
64
+
65
+ def assert_open_issues_with_assets(self, assets, expected_count):
66
+ open_issues_with_assets = self.get_open_issues_with_assets(assets)
67
+ if expected_count != len(open_issues_with_assets):
68
+ logger.error(f"Open Issues: {open_issues_with_assets}")
69
+ self.assertEqual(
70
+ expected_count,
71
+ len(open_issues_with_assets),
72
+ f"Expected {expected_count} open issues tied to assets, but found {len(open_issues_with_assets)}",
73
+ )
74
+ self.verify_issue_asset_association(open_issues_with_assets, assets)
75
+
76
+ def get_open_issues_with_assets(self, assets):
77
+ open_issues = []
78
+ for asset in assets:
79
+ asset_issues = regscale_models.Issue.get_all_by_parent(
80
+ parent_id=asset.id, parent_module=asset.get_module_string()
81
+ )
82
+ open_issues.extend([issue for issue in asset_issues if issue.status == regscale_models.IssueStatus.Open])
83
+ return open_issues
84
+
85
+ def verify_issue_asset_association(self, issues, assets):
86
+ asset_names = [asset.wizId for asset in assets]
87
+ for issue in issues:
88
+ self.assertIsNotNone(issue.assetIdentifier, f"Issue {issue.id} is not associated with an asset")
89
+ self.assertIn(
90
+ issue.assetIdentifier.split("\n")[0],
91
+ asset_names,
92
+ f"Issue {issue.id} is associated with an asset not in the current set",
93
+ )
94
+
95
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
96
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
97
+ def test_wiz_vulnerability_integration_consolidated(self, mock_authenticate, mock_fetch_wiz_data):
98
+ mock_authenticate.return_value = None
99
+ self.clean_plan(self.plan_id)
100
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
101
+
102
+ mock_fetch_wiz_data.return_value = asset_nodes
103
+ assets = list(integration.fetch_assets(wiz_project_id=self.project_id))
104
+ self.assertEqual(2, len(assets))
105
+ integration.sync_assets(plan_id=self.plan_id, wiz_project_id=self.project_id)
106
+
107
+ mock_fetch_wiz_data.return_value = vuln_nodes
108
+ findings = integration.fetch_findings(wiz_project_id=self.project_id)
109
+ self.assertEqual(12, len(list(findings)))
110
+ integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
111
+
112
+ assets = regscale_models.Asset.get_all_by_parent(self.plan_id, regscale_models.SecurityPlan.get_module_string())
113
+ self.assertEqual(2, len(assets))
114
+
115
+ expected_counts = {
116
+ "52c50c20-3d07-58ac-ab2e-c412bf35351b": 2,
117
+ "52c50c20-3d07-58ac-ab2e-c412bf35351c": 1,
118
+ }
119
+ self.assert_vulnerability_counts(assets, expected_counts)
120
+
121
+ if self.regscale_version >= "5.64.0" or self.regscale_version == "localdev":
122
+ self.assert_open_issues_with_assets(assets, 2)
123
+
124
+ mock_fetch_wiz_data.return_value = vuln_nodes[:1]
125
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
126
+ integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
127
+
128
+ expected_counts = {
129
+ "52c50c20-3d07-58ac-ab2e-c412bf35351b": 1,
130
+ "52c50c20-3d07-58ac-ab2e-c412bf35351c": 0,
131
+ }
132
+ self.assert_vulnerability_counts(assets, expected_counts)
133
+
134
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
135
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
136
+ def test_wiz_vulnerability_integration_per_asset(self, mock_authenticate, mock_fetch_wiz_data):
137
+ ScannerVariables.issueCreation = "PerAsset"
138
+ mock_authenticate.return_value = None
139
+ self.clean_plan(self.plan_id)
140
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
141
+
142
+ mock_fetch_wiz_data.return_value = asset_nodes
143
+ assets = list(integration.fetch_assets(wiz_project_id=self.project_id))
144
+ self.assertEqual(2, len(assets))
145
+ integration.sync_assets(plan_id=self.plan_id, wiz_project_id=self.project_id)
146
+
147
+ mock_fetch_wiz_data.return_value = vuln_nodes
148
+ findings = integration.fetch_findings(wiz_project_id=self.project_id)
149
+ self.assertEqual(12, len(list(findings)))
150
+ integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
151
+
152
+ assets = regscale_models.Asset.get_all_by_parent(self.plan_id, regscale_models.SecurityPlan.get_module_string())
153
+ self.assertEqual(2, len(assets))
154
+
155
+ expected_counts = {
156
+ "52c50c20-3d07-58ac-ab2e-c412bf35351b": 2,
157
+ "52c50c20-3d07-58ac-ab2e-c412bf35351c": 1,
158
+ }
159
+ self.assert_vulnerability_counts(assets, expected_counts)
160
+
161
+ self.assert_open_issues_with_assets(assets, 3)
162
+
163
+ mock_fetch_wiz_data.return_value = vuln_nodes[:1]
164
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
165
+ integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
166
+
167
+ expected_counts = {
168
+ "52c50c20-3d07-58ac-ab2e-c412bf35351b": 1,
169
+ "52c50c20-3d07-58ac-ab2e-c412bf35351c": 0,
170
+ }
171
+ self.assert_vulnerability_counts(assets, expected_counts)
172
+
173
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
174
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
175
+ def test_wiz_assets_with_hardware_asset_types_enabled(self, mock_authenticate, mock_fetch_wiz_data):
176
+ WizVariables.useWizHardwareAssetTypes = True
177
+ WizVariables.wizHardwareAssetTypes = ["CLIENT_APPLICATION"]
178
+ mock_authenticate.return_value = None
179
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
180
+
181
+ mock_fetch_wiz_data.return_value = asset_nodes
182
+ assets = list(integration.fetch_assets(wiz_project_id=self.project_id))
183
+ self.assertEqual(2, len(assets))
184
+ integration.sync_assets(plan_id=self.plan_id, wiz_project_id=self.project_id)
185
+
186
+ mock_fetch_wiz_data.return_value = vuln_nodes
187
+ findings = integration.fetch_findings(wiz_project_id=self.project_id)
188
+ self.assertEqual(12, len(list(findings)))
189
+ integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
190
+
191
+ assets = regscale_models.Asset.get_all_by_parent(self.plan_id, regscale_models.SecurityPlan.get_module_string())
192
+ self.assertEqual(2, len(assets))
193
+ for asset in assets:
194
+ self.assertEqual(asset.assetCategory, regscale_models.AssetCategory.Hardware)
195
+
196
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
197
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
198
+ def test_wiz_assets_with_hardware_asset_types_disabled(self, mock_authenticate, mock_fetch_wiz_data):
199
+ WizVariables.useWizHardwareAssetTypes = False
200
+ WizVariables.wizHardwareAssetTypes = ["CLIENT_APPLICATION"]
201
+ mock_authenticate.return_value = None
202
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
203
+
204
+ mock_fetch_wiz_data.return_value = asset_nodes
205
+ assets = list(integration.fetch_assets(wiz_project_id=self.project_id))
206
+ self.assertEqual(2, len(assets))
207
+ integration.sync_assets(plan_id=self.plan_id, wiz_project_id=self.project_id)
208
+
209
+ mock_fetch_wiz_data.return_value = vuln_nodes
210
+ findings = integration.fetch_findings(wiz_project_id=self.project_id)
211
+ self.assertEqual(12, len(list(findings)))
212
+ integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
213
+
214
+ assets = regscale_models.Asset.get_all_by_parent(self.plan_id, regscale_models.SecurityPlan.get_module_string())
215
+ self.assertEqual(2, len(assets))
216
+ for asset in assets:
217
+ self.assertEqual(asset.assetCategory, regscale_models.AssetCategory.Software)
218
+
219
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
220
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
221
+ def test_wiz_due_date_calculation(self, mock_authenticate, mock_fetch_wiz_data):
222
+ from datetime import datetime, timedelta
223
+ from regscale.core.utils.date import date_obj
224
+
225
+ mock_authenticate.return_value = None
226
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
227
+
228
+ mock_fetch_wiz_data.return_value = vuln_nodes
229
+ mock_app = MagicMock()
230
+ mock_app.config = {"issues": {"wiz": {"critical": 1, "high": 2, "moderate": 3, "low": 4}}}
231
+ with patch.object(integration, "app", mock_app):
232
+ findings = integration.fetch_findings(wiz_project_id=self.project_id)
233
+ findings = list(findings)
234
+ self.assertEqual(12, len(findings))
235
+ for finding in findings:
236
+ # convert the due_date to a datetime object for comparison
237
+ finding_due_date = datetime.strptime(finding.due_date, "%Y-%m-%dT%H:%M:%S")
238
+ first_seen_date = date_obj(finding.first_seen)
239
+ if finding.severity == regscale_models.IssueSeverity.Critical.value:
240
+ self.assertEqual(
241
+ finding_due_date.date(),
242
+ (first_seen_date + timedelta(days=mock_app.config["issues"]["wiz"]["critical"])),
243
+ )
244
+ elif finding.severity == regscale_models.IssueSeverity.High.value:
245
+ self.assertEqual(
246
+ finding_due_date.date(),
247
+ (first_seen_date + timedelta(days=mock_app.config["issues"]["wiz"]["high"])),
248
+ )
249
+ elif finding.severity == regscale_models.IssueSeverity.Moderate.value:
250
+ self.assertEqual(
251
+ finding_due_date.date(),
252
+ (first_seen_date + timedelta(days=mock_app.config["issues"]["wiz"]["moderate"])),
253
+ )
254
+ elif finding.severity == regscale_models.IssueSeverity.Low.value:
255
+ self.assertEqual(
256
+ finding_due_date.date(),
257
+ (first_seen_date + timedelta(days=mock_app.config["issues"]["wiz"]["low"])),
258
+ )
259
+ else:
260
+ self.assertEqual(finding_due_date.date(), (first_seen_date + timedelta(days=60)))
261
+
262
+
263
+ if __name__ == "__main__":
264
+ unittest.main()