regscale-cli 6.25.1.0__py3-none-any.whl → 6.27.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +19 -4
- regscale/core/app/internal/evidence.py +419 -2
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/dev/code_gen.py +24 -20
- regscale/integrations/commercial/jira.py +367 -126
- regscale/integrations/commercial/qualys/__init__.py +7 -8
- regscale/integrations/commercial/qualys/scanner.py +8 -3
- regscale/integrations/commercial/sicura/api.py +14 -13
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +49 -39
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- 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 +64 -79
- 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 +161 -165
- 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 +39 -99
- 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 +60 -41
- regscale/integrations/control_matcher.py +377 -0
- regscale/integrations/due_date_handler.py +14 -8
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +1 -0
- regscale/integrations/public/cci_importer.py +37 -38
- regscale/integrations/public/fedramp/click.py +60 -2
- 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/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +277 -153
- regscale/models/integration_models/cisa_kev_data.json +282 -9
- regscale/models/integration_models/nexpose.py +36 -10
- 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/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +47 -22
- regscale/models/regscale_models/issue.py +256 -95
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +145 -65
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3731 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +350 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- 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_compliance_report_normalization.py +138 -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 +343 -0
- 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 +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- 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_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1132 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- 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/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_fedramp.py +301 -0
- tests/regscale/integrations/test_control_matcher.py +1397 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_issue.py +378 -1
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"""Tests for Wiz Report Management."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from unittest.mock import patch, MagicMock, mock_open
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from regscale.integrations.commercial.wizv2.reports import WizReportManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@patch("time.sleep", return_value=None)
|
|
11
|
+
class TestWizReportManager(unittest.TestCase):
|
|
12
|
+
"""Test cases for WizReportManager."""
|
|
13
|
+
|
|
14
|
+
def setUp(self):
|
|
15
|
+
"""Set up test fixtures."""
|
|
16
|
+
self.api_url = "https://api.wiz.io/graphql"
|
|
17
|
+
self.access_token = "test_token_123"
|
|
18
|
+
self.manager = WizReportManager(self.api_url, self.access_token)
|
|
19
|
+
self.project_id = "proj-123"
|
|
20
|
+
self.report_id = "report-456"
|
|
21
|
+
|
|
22
|
+
def test_init(self, mock_sleep):
|
|
23
|
+
"""Test WizReportManager initialization."""
|
|
24
|
+
self.assertEqual(self.manager.api_url, self.api_url)
|
|
25
|
+
self.assertEqual(self.manager.access_token, self.access_token)
|
|
26
|
+
self.assertIn("Authorization", self.manager.headers)
|
|
27
|
+
self.assertEqual(self.manager.headers["Authorization"], f"Bearer {self.access_token}")
|
|
28
|
+
|
|
29
|
+
@patch("requests.post")
|
|
30
|
+
def test_create_compliance_report_success(self, mock_post, mock_sleep):
|
|
31
|
+
"""Test successful compliance report creation."""
|
|
32
|
+
mock_response = MagicMock()
|
|
33
|
+
mock_response.json.return_value = {"data": {"createReport": {"report": {"id": "report-789"}}}}
|
|
34
|
+
mock_response.raise_for_status = MagicMock()
|
|
35
|
+
mock_post.return_value = mock_response
|
|
36
|
+
|
|
37
|
+
result = self.manager.create_compliance_report(self.project_id)
|
|
38
|
+
|
|
39
|
+
self.assertEqual(result, "report-789")
|
|
40
|
+
mock_post.assert_called_once()
|
|
41
|
+
|
|
42
|
+
@patch("requests.post")
|
|
43
|
+
def test_create_compliance_report_with_run_starts_at(self, mock_post, mock_sleep):
|
|
44
|
+
"""Test compliance report creation with run_starts_at."""
|
|
45
|
+
mock_response = MagicMock()
|
|
46
|
+
mock_response.json.return_value = {"data": {"createReport": {"report": {"id": "report-789"}}}}
|
|
47
|
+
mock_response.raise_for_status = MagicMock()
|
|
48
|
+
mock_post.return_value = mock_response
|
|
49
|
+
|
|
50
|
+
run_starts_at = "2024-01-01T00:00:00Z"
|
|
51
|
+
result = self.manager.create_compliance_report(self.project_id, run_starts_at)
|
|
52
|
+
|
|
53
|
+
self.assertEqual(result, "report-789")
|
|
54
|
+
mock_post.assert_called_once()
|
|
55
|
+
|
|
56
|
+
@patch("requests.post")
|
|
57
|
+
def test_create_compliance_report_graphql_errors(self, mock_post, mock_sleep):
|
|
58
|
+
"""Test compliance report creation with GraphQL errors."""
|
|
59
|
+
mock_response = MagicMock()
|
|
60
|
+
mock_response.json.return_value = {"errors": [{"message": "Project not found"}]}
|
|
61
|
+
mock_response.raise_for_status = MagicMock()
|
|
62
|
+
mock_post.return_value = mock_response
|
|
63
|
+
|
|
64
|
+
result = self.manager.create_compliance_report(self.project_id)
|
|
65
|
+
|
|
66
|
+
self.assertIsNone(result)
|
|
67
|
+
|
|
68
|
+
@patch("requests.post")
|
|
69
|
+
def test_create_compliance_report_no_report_id(self, mock_post, mock_sleep):
|
|
70
|
+
"""Test compliance report creation when no report ID is returned."""
|
|
71
|
+
mock_response = MagicMock()
|
|
72
|
+
mock_response.json.return_value = {"data": {"createReport": {"report": {}}}}
|
|
73
|
+
mock_response.raise_for_status = MagicMock()
|
|
74
|
+
mock_post.return_value = mock_response
|
|
75
|
+
|
|
76
|
+
result = self.manager.create_compliance_report(self.project_id)
|
|
77
|
+
|
|
78
|
+
self.assertIsNone(result)
|
|
79
|
+
|
|
80
|
+
@patch("requests.post")
|
|
81
|
+
def test_create_compliance_report_request_exception(self, mock_post, mock_sleep):
|
|
82
|
+
"""Test compliance report creation with request exception."""
|
|
83
|
+
mock_post.side_effect = requests.exceptions.RequestException("Network error")
|
|
84
|
+
|
|
85
|
+
result = self.manager.create_compliance_report(self.project_id)
|
|
86
|
+
|
|
87
|
+
self.assertIsNone(result)
|
|
88
|
+
|
|
89
|
+
@patch("requests.post")
|
|
90
|
+
def test_create_compliance_report_key_error(self, mock_post, mock_sleep):
|
|
91
|
+
"""Test compliance report creation with KeyError."""
|
|
92
|
+
mock_response = MagicMock()
|
|
93
|
+
mock_response.json.return_value = {"data": {}}
|
|
94
|
+
mock_response.raise_for_status = MagicMock()
|
|
95
|
+
mock_post.return_value = mock_response
|
|
96
|
+
|
|
97
|
+
result = self.manager.create_compliance_report(self.project_id)
|
|
98
|
+
|
|
99
|
+
self.assertIsNone(result)
|
|
100
|
+
|
|
101
|
+
@patch("requests.post")
|
|
102
|
+
def test_create_compliance_report_value_error(self, mock_post, mock_sleep):
|
|
103
|
+
"""Test compliance report creation with ValueError."""
|
|
104
|
+
mock_response = MagicMock()
|
|
105
|
+
mock_response.json.side_effect = ValueError("Invalid JSON")
|
|
106
|
+
mock_response.raise_for_status = MagicMock()
|
|
107
|
+
mock_post.return_value = mock_response
|
|
108
|
+
|
|
109
|
+
result = self.manager.create_compliance_report(self.project_id)
|
|
110
|
+
|
|
111
|
+
self.assertIsNone(result)
|
|
112
|
+
|
|
113
|
+
@patch("requests.post")
|
|
114
|
+
def test_get_report_status_success(self, mock_post, mock_sleep):
|
|
115
|
+
"""Test successful report status retrieval."""
|
|
116
|
+
mock_response = MagicMock()
|
|
117
|
+
mock_response.json.return_value = {
|
|
118
|
+
"data": {
|
|
119
|
+
"report": {
|
|
120
|
+
"id": "report-456",
|
|
121
|
+
"lastRun": {"status": "SUCCESS", "url": "https://download.url/report.csv"},
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
mock_response.raise_for_status = MagicMock()
|
|
126
|
+
mock_post.return_value = mock_response
|
|
127
|
+
|
|
128
|
+
result = self.manager.get_report_status(self.report_id)
|
|
129
|
+
|
|
130
|
+
self.assertEqual(result["status"], "SUCCESS")
|
|
131
|
+
self.assertEqual(result["url"], "https://download.url/report.csv")
|
|
132
|
+
self.assertIn("report_data", result)
|
|
133
|
+
|
|
134
|
+
@patch("requests.post")
|
|
135
|
+
def test_get_report_status_graphql_errors(self, mock_post, mock_sleep):
|
|
136
|
+
"""Test report status retrieval with GraphQL errors."""
|
|
137
|
+
mock_response = MagicMock()
|
|
138
|
+
mock_response.json.return_value = {"errors": [{"message": "Report not found"}]}
|
|
139
|
+
mock_response.raise_for_status = MagicMock()
|
|
140
|
+
mock_post.return_value = mock_response
|
|
141
|
+
|
|
142
|
+
result = self.manager.get_report_status(self.report_id)
|
|
143
|
+
|
|
144
|
+
self.assertEqual(result, {})
|
|
145
|
+
|
|
146
|
+
@patch("requests.post")
|
|
147
|
+
def test_get_report_status_request_exception(self, mock_post, mock_sleep):
|
|
148
|
+
"""Test report status retrieval with request exception."""
|
|
149
|
+
mock_post.side_effect = requests.exceptions.RequestException("Network error")
|
|
150
|
+
|
|
151
|
+
result = self.manager.get_report_status(self.report_id)
|
|
152
|
+
|
|
153
|
+
self.assertEqual(result, {})
|
|
154
|
+
|
|
155
|
+
@patch("requests.post")
|
|
156
|
+
def test_get_report_status_key_error(self, mock_post, mock_sleep):
|
|
157
|
+
"""Test report status retrieval with KeyError."""
|
|
158
|
+
mock_response = MagicMock()
|
|
159
|
+
mock_response.json.return_value = {"data": {}}
|
|
160
|
+
mock_response.raise_for_status = MagicMock()
|
|
161
|
+
mock_post.return_value = mock_response
|
|
162
|
+
|
|
163
|
+
result = self.manager.get_report_status(self.report_id)
|
|
164
|
+
|
|
165
|
+
self.assertEqual(result["status"], "UNKNOWN")
|
|
166
|
+
self.assertEqual(result["url"], "")
|
|
167
|
+
|
|
168
|
+
@patch("requests.post")
|
|
169
|
+
def test_get_report_status_value_error(self, mock_post, mock_sleep):
|
|
170
|
+
"""Test report status retrieval with ValueError."""
|
|
171
|
+
mock_response = MagicMock()
|
|
172
|
+
mock_response.json.side_effect = ValueError("Invalid JSON")
|
|
173
|
+
mock_response.raise_for_status = MagicMock()
|
|
174
|
+
mock_post.return_value = mock_response
|
|
175
|
+
|
|
176
|
+
result = self.manager.get_report_status(self.report_id)
|
|
177
|
+
|
|
178
|
+
self.assertEqual(result, {})
|
|
179
|
+
|
|
180
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
181
|
+
def test_wait_for_report_completion_success(self, mock_get_status, mock_sleep):
|
|
182
|
+
"""Test waiting for report completion - success case."""
|
|
183
|
+
mock_get_status.return_value = {"status": "SUCCESS", "url": "https://download.url/report.csv"}
|
|
184
|
+
|
|
185
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
186
|
+
|
|
187
|
+
self.assertEqual(result, "https://download.url/report.csv")
|
|
188
|
+
|
|
189
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
190
|
+
def test_wait_for_report_completion_completed_status(self, mock_get_status, mock_sleep):
|
|
191
|
+
"""Test waiting for report completion - COMPLETED status."""
|
|
192
|
+
mock_get_status.return_value = {"status": "COMPLETED", "url": "https://download.url/report.csv"}
|
|
193
|
+
|
|
194
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
195
|
+
|
|
196
|
+
self.assertEqual(result, "https://download.url/report.csv")
|
|
197
|
+
|
|
198
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
199
|
+
def test_wait_for_report_completion_no_url(self, mock_get_status, mock_sleep):
|
|
200
|
+
"""Test waiting for report completion - no download URL."""
|
|
201
|
+
mock_get_status.return_value = {"status": "SUCCESS", "url": ""}
|
|
202
|
+
|
|
203
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
204
|
+
|
|
205
|
+
self.assertIsNone(result)
|
|
206
|
+
|
|
207
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
208
|
+
def test_wait_for_report_completion_failed(self, mock_get_status, mock_sleep):
|
|
209
|
+
"""Test waiting for report completion - FAILED status."""
|
|
210
|
+
mock_get_status.return_value = {"status": "FAILED", "url": ""}
|
|
211
|
+
|
|
212
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
213
|
+
|
|
214
|
+
self.assertIsNone(result)
|
|
215
|
+
|
|
216
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
217
|
+
def test_wait_for_report_completion_cancelled(self, mock_get_status, mock_sleep):
|
|
218
|
+
"""Test waiting for report completion - CANCELLED status."""
|
|
219
|
+
mock_get_status.return_value = {"status": "CANCELLED", "url": ""}
|
|
220
|
+
|
|
221
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
222
|
+
|
|
223
|
+
self.assertIsNone(result)
|
|
224
|
+
|
|
225
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
226
|
+
def test_wait_for_report_completion_timeout_status(self, mock_get_status, mock_sleep):
|
|
227
|
+
"""Test waiting for report completion - TIMEOUT status."""
|
|
228
|
+
mock_get_status.return_value = {"status": "TIMEOUT", "url": ""}
|
|
229
|
+
|
|
230
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
231
|
+
|
|
232
|
+
self.assertIsNone(result)
|
|
233
|
+
|
|
234
|
+
@patch("time.sleep")
|
|
235
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
236
|
+
def test_wait_for_report_completion_pending_then_success(self, mock_get_status, mock_sleep_method, mock_sleep):
|
|
237
|
+
"""Test waiting for report completion - PENDING then SUCCESS."""
|
|
238
|
+
mock_get_status.side_effect = [
|
|
239
|
+
{"status": "PENDING", "url": ""},
|
|
240
|
+
{"status": "SUCCESS", "url": "https://download.url/report.csv"},
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
244
|
+
|
|
245
|
+
self.assertEqual(result, "https://download.url/report.csv")
|
|
246
|
+
# Class-level mock takes precedence, so we don't check the method-level mock
|
|
247
|
+
|
|
248
|
+
@patch("time.sleep")
|
|
249
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
250
|
+
def test_wait_for_report_completion_running_then_success(self, mock_get_status, mock_sleep_method, mock_sleep):
|
|
251
|
+
"""Test waiting for report completion - RUNNING then SUCCESS."""
|
|
252
|
+
mock_get_status.side_effect = [
|
|
253
|
+
{"status": "RUNNING", "url": ""},
|
|
254
|
+
{"status": "SUCCESS", "url": "https://download.url/report.csv"},
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
258
|
+
|
|
259
|
+
self.assertEqual(result, "https://download.url/report.csv")
|
|
260
|
+
# Class-level mock takes precedence, so we don't check the method-level mock
|
|
261
|
+
|
|
262
|
+
@patch("time.sleep")
|
|
263
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
264
|
+
def test_wait_for_report_completion_in_progress_then_success(self, mock_get_status, mock_sleep_method, mock_sleep):
|
|
265
|
+
"""Test waiting for report completion - IN_PROGRESS then SUCCESS."""
|
|
266
|
+
mock_get_status.side_effect = [
|
|
267
|
+
{"status": "IN_PROGRESS", "url": ""},
|
|
268
|
+
{"status": "SUCCESS", "url": "https://download.url/report.csv"},
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
272
|
+
|
|
273
|
+
self.assertEqual(result, "https://download.url/report.csv")
|
|
274
|
+
# Class-level mock takes precedence, so we don't check the method-level mock
|
|
275
|
+
|
|
276
|
+
@patch("time.sleep")
|
|
277
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
278
|
+
def test_wait_for_report_completion_unknown_then_success(self, mock_get_status, mock_sleep_method, mock_sleep):
|
|
279
|
+
"""Test waiting for report completion - UNKNOWN then SUCCESS."""
|
|
280
|
+
mock_get_status.side_effect = [
|
|
281
|
+
{"status": "UNKNOWN", "url": ""},
|
|
282
|
+
{"status": "SUCCESS", "url": "https://download.url/report.csv"},
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
286
|
+
|
|
287
|
+
self.assertEqual(result, "https://download.url/report.csv")
|
|
288
|
+
# Class-level mock takes precedence, so we don't check the method-level mock
|
|
289
|
+
|
|
290
|
+
@patch("time.sleep")
|
|
291
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
292
|
+
def test_wait_for_report_completion_weird_status(self, mock_get_status, mock_sleep_method, mock_sleep):
|
|
293
|
+
"""Test waiting for report completion - unrecognized status then SUCCESS."""
|
|
294
|
+
mock_get_status.side_effect = [
|
|
295
|
+
{"status": "WEIRD_STATUS", "url": ""},
|
|
296
|
+
{"status": "SUCCESS", "url": "https://download.url/report.csv"},
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
300
|
+
|
|
301
|
+
self.assertEqual(result, "https://download.url/report.csv")
|
|
302
|
+
# Class-level mock takes precedence, so we don't check the method-level mock
|
|
303
|
+
|
|
304
|
+
@patch("time.sleep")
|
|
305
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.get_report_status")
|
|
306
|
+
@patch("regscale.integrations.commercial.wizv2.reports.MAX_RETRIES", 3)
|
|
307
|
+
def test_wait_for_report_completion_max_retries(self, mock_get_status, mock_sleep_method, mock_sleep):
|
|
308
|
+
"""Test waiting for report completion - max retries exceeded."""
|
|
309
|
+
mock_get_status.return_value = {"status": "PENDING", "url": ""}
|
|
310
|
+
|
|
311
|
+
result = self.manager.wait_for_report_completion(self.report_id)
|
|
312
|
+
|
|
313
|
+
self.assertIsNone(result)
|
|
314
|
+
self.assertEqual(mock_get_status.call_count, 3)
|
|
315
|
+
|
|
316
|
+
@patch("builtins.open", new_callable=mock_open)
|
|
317
|
+
@patch("requests.get")
|
|
318
|
+
def test_download_report_success(self, mock_get, mock_file, mock_sleep):
|
|
319
|
+
"""Test successful report download."""
|
|
320
|
+
mock_response = MagicMock()
|
|
321
|
+
mock_response.content = b"report,data\n1,2\n"
|
|
322
|
+
mock_response.raise_for_status = MagicMock()
|
|
323
|
+
mock_get.return_value = mock_response
|
|
324
|
+
|
|
325
|
+
result = self.manager.download_report("https://download.url/report.csv", "/tmp/report.csv")
|
|
326
|
+
|
|
327
|
+
self.assertTrue(result)
|
|
328
|
+
mock_get.assert_called_once_with("https://download.url/report.csv", timeout=300)
|
|
329
|
+
mock_file.assert_called_once_with("/tmp/report.csv", "wb")
|
|
330
|
+
|
|
331
|
+
@patch("requests.get")
|
|
332
|
+
def test_download_report_request_exception(self, mock_get, mock_sleep):
|
|
333
|
+
"""Test report download with request exception."""
|
|
334
|
+
mock_get.side_effect = requests.exceptions.RequestException("Network error")
|
|
335
|
+
|
|
336
|
+
result = self.manager.download_report("https://download.url/report.csv", "/tmp/report.csv")
|
|
337
|
+
|
|
338
|
+
self.assertFalse(result)
|
|
339
|
+
|
|
340
|
+
@patch("builtins.open", new_callable=mock_open)
|
|
341
|
+
@patch("requests.get")
|
|
342
|
+
def test_download_report_io_error(self, mock_get, mock_file, mock_sleep):
|
|
343
|
+
"""Test report download with IO error."""
|
|
344
|
+
mock_response = MagicMock()
|
|
345
|
+
mock_response.content = b"report,data\n1,2\n"
|
|
346
|
+
mock_response.raise_for_status = MagicMock()
|
|
347
|
+
mock_get.return_value = mock_response
|
|
348
|
+
mock_file.side_effect = IOError("Permission denied")
|
|
349
|
+
|
|
350
|
+
result = self.manager.download_report("https://download.url/report.csv", "/tmp/report.csv")
|
|
351
|
+
|
|
352
|
+
self.assertFalse(result)
|
|
353
|
+
|
|
354
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.wait_for_report_completion")
|
|
355
|
+
@patch("requests.post")
|
|
356
|
+
def test_rerun_report_success(self, mock_post, mock_wait, mock_sleep):
|
|
357
|
+
"""Test successful report rerun."""
|
|
358
|
+
mock_response = MagicMock()
|
|
359
|
+
mock_response.json.return_value = {"data": {"rerunReport": {"success": True}}}
|
|
360
|
+
mock_response.raise_for_status = MagicMock()
|
|
361
|
+
mock_post.return_value = mock_response
|
|
362
|
+
mock_wait.return_value = "https://download.url/report.csv"
|
|
363
|
+
|
|
364
|
+
result = self.manager.rerun_report(self.report_id)
|
|
365
|
+
|
|
366
|
+
self.assertEqual(result, "https://download.url/report.csv")
|
|
367
|
+
mock_post.assert_called_once()
|
|
368
|
+
mock_wait.assert_called_once_with(self.report_id)
|
|
369
|
+
|
|
370
|
+
@patch("requests.post")
|
|
371
|
+
def test_rerun_report_graphql_errors(self, mock_post, mock_sleep):
|
|
372
|
+
"""Test report rerun with GraphQL errors."""
|
|
373
|
+
mock_response = MagicMock()
|
|
374
|
+
mock_response.json.return_value = {"errors": [{"message": "Report not found"}]}
|
|
375
|
+
mock_response.raise_for_status = MagicMock()
|
|
376
|
+
mock_post.return_value = mock_response
|
|
377
|
+
|
|
378
|
+
result = self.manager.rerun_report(self.report_id)
|
|
379
|
+
|
|
380
|
+
self.assertIsNone(result)
|
|
381
|
+
|
|
382
|
+
@patch("requests.post")
|
|
383
|
+
def test_rerun_report_request_exception(self, mock_post, mock_sleep):
|
|
384
|
+
"""Test report rerun with request exception."""
|
|
385
|
+
mock_post.side_effect = requests.exceptions.RequestException("Network error")
|
|
386
|
+
|
|
387
|
+
result = self.manager.rerun_report(self.report_id)
|
|
388
|
+
|
|
389
|
+
self.assertIsNone(result)
|
|
390
|
+
|
|
391
|
+
@patch("regscale.integrations.commercial.wizv2.reports.WizReportManager.wait_for_report_completion")
|
|
392
|
+
@patch("requests.post")
|
|
393
|
+
def test_rerun_report_key_error(self, mock_post, mock_wait, mock_sleep):
|
|
394
|
+
"""Test report rerun with KeyError."""
|
|
395
|
+
mock_response = MagicMock()
|
|
396
|
+
mock_response.json.return_value = {"data": {}}
|
|
397
|
+
mock_response.raise_for_status = MagicMock()
|
|
398
|
+
mock_post.return_value = mock_response
|
|
399
|
+
mock_wait.return_value = "https://download.url/report.csv"
|
|
400
|
+
|
|
401
|
+
result = self.manager.rerun_report(self.report_id)
|
|
402
|
+
|
|
403
|
+
# Should still try to wait for completion
|
|
404
|
+
self.assertIsNotNone(result)
|
|
405
|
+
|
|
406
|
+
@patch("requests.post")
|
|
407
|
+
def test_rerun_report_value_error(self, mock_post, mock_sleep):
|
|
408
|
+
"""Test report rerun with ValueError."""
|
|
409
|
+
mock_response = MagicMock()
|
|
410
|
+
mock_response.json.side_effect = ValueError("Invalid JSON")
|
|
411
|
+
mock_response.raise_for_status = MagicMock()
|
|
412
|
+
mock_post.return_value = mock_response
|
|
413
|
+
|
|
414
|
+
result = self.manager.rerun_report(self.report_id)
|
|
415
|
+
|
|
416
|
+
self.assertIsNone(result)
|
|
417
|
+
|
|
418
|
+
@patch("requests.post")
|
|
419
|
+
def test_list_reports_success(self, mock_post, mock_sleep):
|
|
420
|
+
"""Test successful report listing."""
|
|
421
|
+
mock_response = MagicMock()
|
|
422
|
+
mock_response.json.return_value = {
|
|
423
|
+
"data": {
|
|
424
|
+
"reports": {"nodes": [{"id": "report-1", "name": "Report 1"}, {"id": "report-2", "name": "Report 2"}]}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
mock_response.raise_for_status = MagicMock()
|
|
428
|
+
mock_post.return_value = mock_response
|
|
429
|
+
|
|
430
|
+
result = self.manager.list_reports()
|
|
431
|
+
|
|
432
|
+
self.assertEqual(len(result), 2)
|
|
433
|
+
self.assertEqual(result[0]["id"], "report-1")
|
|
434
|
+
self.assertEqual(result[1]["id"], "report-2")
|
|
435
|
+
|
|
436
|
+
@patch("requests.post")
|
|
437
|
+
def test_list_reports_with_filter(self, mock_post, mock_sleep):
|
|
438
|
+
"""Test report listing with filter."""
|
|
439
|
+
mock_response = MagicMock()
|
|
440
|
+
mock_response.json.return_value = {"data": {"reports": {"nodes": [{"id": "report-1", "name": "Report 1"}]}}}
|
|
441
|
+
mock_response.raise_for_status = MagicMock()
|
|
442
|
+
mock_post.return_value = mock_response
|
|
443
|
+
|
|
444
|
+
filter_by = {"name": {"equals": "Report 1"}}
|
|
445
|
+
result = self.manager.list_reports(filter_by)
|
|
446
|
+
|
|
447
|
+
self.assertEqual(len(result), 1)
|
|
448
|
+
self.assertEqual(result[0]["id"], "report-1")
|
|
449
|
+
|
|
450
|
+
@patch("requests.post")
|
|
451
|
+
def test_list_reports_graphql_errors(self, mock_post, mock_sleep):
|
|
452
|
+
"""Test report listing with GraphQL errors."""
|
|
453
|
+
mock_response = MagicMock()
|
|
454
|
+
mock_response.json.return_value = {"errors": [{"message": "Unauthorized"}]}
|
|
455
|
+
mock_response.raise_for_status = MagicMock()
|
|
456
|
+
mock_post.return_value = mock_response
|
|
457
|
+
|
|
458
|
+
result = self.manager.list_reports()
|
|
459
|
+
|
|
460
|
+
self.assertEqual(result, [])
|
|
461
|
+
|
|
462
|
+
@patch("requests.post")
|
|
463
|
+
def test_list_reports_request_exception(self, mock_post, mock_sleep):
|
|
464
|
+
"""Test report listing with request exception."""
|
|
465
|
+
mock_post.side_effect = requests.exceptions.RequestException("Network error")
|
|
466
|
+
|
|
467
|
+
result = self.manager.list_reports()
|
|
468
|
+
|
|
469
|
+
self.assertEqual(result, [])
|
|
470
|
+
|
|
471
|
+
@patch("requests.post")
|
|
472
|
+
def test_list_reports_key_error(self, mock_post, mock_sleep):
|
|
473
|
+
"""Test report listing with KeyError."""
|
|
474
|
+
mock_response = MagicMock()
|
|
475
|
+
mock_response.json.return_value = {"data": {}}
|
|
476
|
+
mock_response.raise_for_status = MagicMock()
|
|
477
|
+
mock_post.return_value = mock_response
|
|
478
|
+
|
|
479
|
+
result = self.manager.list_reports()
|
|
480
|
+
|
|
481
|
+
self.assertEqual(result, [])
|
|
482
|
+
|
|
483
|
+
@patch("requests.post")
|
|
484
|
+
def test_list_reports_value_error(self, mock_post, mock_sleep):
|
|
485
|
+
"""Test report listing with ValueError."""
|
|
486
|
+
mock_response = MagicMock()
|
|
487
|
+
mock_response.json.side_effect = ValueError("Invalid JSON")
|
|
488
|
+
mock_response.raise_for_status = MagicMock()
|
|
489
|
+
mock_post.return_value = mock_response
|
|
490
|
+
|
|
491
|
+
result = self.manager.list_reports()
|
|
492
|
+
|
|
493
|
+
self.assertEqual(result, [])
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
if __name__ == "__main__":
|
|
497
|
+
unittest.main()
|