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.

Files changed (96) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +1 -1
  3. regscale/core/app/internal/evidence.py +419 -2
  4. regscale/dev/code_gen.py +24 -20
  5. regscale/integrations/commercial/__init__.py +0 -1
  6. regscale/integrations/commercial/jira.py +367 -126
  7. regscale/integrations/commercial/qualys/__init__.py +7 -8
  8. regscale/integrations/commercial/qualys/scanner.py +8 -3
  9. regscale/integrations/commercial/synqly/assets.py +17 -0
  10. regscale/integrations/commercial/synqly/vulnerabilities.py +45 -28
  11. regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
  12. regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
  13. regscale/integrations/commercial/tenablev2/commands.py +142 -1
  14. regscale/integrations/commercial/tenablev2/scanner.py +0 -1
  15. regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
  16. regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
  17. regscale/integrations/commercial/wizv2/click.py +44 -59
  18. regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
  19. regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
  20. regscale/integrations/commercial/wizv2/compliance_report.py +10 -9
  21. regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
  22. regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +3 -3
  23. regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +1 -17
  24. regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
  25. regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
  26. regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +5 -9
  27. regscale/integrations/commercial/wizv2/issue.py +1 -1
  28. regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
  29. regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
  30. regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
  31. regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
  32. regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
  33. regscale/integrations/commercial/wizv2/reports.py +1 -1
  34. regscale/integrations/commercial/wizv2/sbom.py +1 -1
  35. regscale/integrations/commercial/wizv2/scanner.py +40 -100
  36. regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
  37. regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
  38. regscale/integrations/commercial/wizv2/variables.py +89 -3
  39. regscale/integrations/compliance_integration.py +0 -46
  40. regscale/integrations/control_matcher.py +22 -3
  41. regscale/integrations/due_date_handler.py +14 -8
  42. regscale/integrations/public/fedramp/docx_parser.py +10 -1
  43. regscale/integrations/public/fedramp/fedramp_cis_crm.py +393 -340
  44. regscale/integrations/public/fedramp/fedramp_five.py +1 -1
  45. regscale/integrations/scanner_integration.py +127 -57
  46. regscale/models/integration_models/cisa_kev_data.json +132 -9
  47. regscale/models/integration_models/qualys.py +3 -4
  48. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  49. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +24 -7
  50. regscale/models/integration_models/synqly_models/synqly_model.py +8 -1
  51. regscale/models/regscale_models/control_implementation.py +1 -1
  52. regscale/models/regscale_models/issue.py +0 -1
  53. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/METADATA +1 -17
  54. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/RECORD +94 -61
  55. tests/regscale/integrations/commercial/test_jira.py +481 -91
  56. tests/regscale/integrations/commercial/test_wiz.py +96 -200
  57. tests/regscale/integrations/commercial/wizv2/__init__.py +1 -1
  58. tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
  59. tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
  60. tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
  61. tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
  62. tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
  63. tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
  64. tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
  65. tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
  66. tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
  67. tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
  68. tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
  69. tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
  70. tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
  71. tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
  72. tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
  73. tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
  74. tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
  75. tests/regscale/integrations/commercial/wizv2/test_issue.py +1 -1
  76. tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
  77. tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
  78. tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
  79. tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
  80. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +1 -1
  81. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +72 -29
  82. tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
  83. tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
  84. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +946 -78
  85. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +97 -202
  86. tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
  87. tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
  88. tests/regscale/integrations/public/test_fedramp.py +301 -0
  89. tests/regscale/integrations/test_control_matcher.py +83 -0
  90. regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
  91. tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +0 -750
  92. /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
  93. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/LICENSE +0 -0
  94. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/WHEEL +0 -0
  95. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.dist-info}/entry_points.txt +0 -0
  96. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.1.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()