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,851 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Comprehensive tests for Wiz Click command handlers with focus on uncovered lines."""
4
+
5
+ import pytest
6
+ from click.testing import CliRunner
7
+ from unittest.mock import patch, MagicMock
8
+ from regscale.integrations.commercial.wizv2.click import wiz
9
+
10
+
11
+ class TestWizClickComprehensive:
12
+ """Comprehensive tests for Wiz CLI commands focusing on uncovered lines."""
13
+
14
+ @pytest.fixture
15
+ def runner(self):
16
+ """Create a Click test runner."""
17
+ return CliRunner()
18
+
19
+ @pytest.fixture
20
+ def mock_wiz_variables(self):
21
+ """Mock WizVariables to avoid environment dependencies."""
22
+ with patch("regscale.integrations.commercial.wizv2.click.WizVariables") as mock:
23
+ mock.wizClientId = "env_client_id"
24
+ mock.wizClientSecret = "env_client_secret"
25
+ mock.wizInventoryFilterBy = "{}"
26
+ mock.wizIssueFilterBy = "{}"
27
+ yield mock
28
+
29
+ # ========================================
30
+ # Test authenticate command (lines 28-30)
31
+ # ========================================
32
+
33
+ def test_authenticate_with_provided_credentials(self, runner, mock_wiz_variables):
34
+ """Test authenticate command with provided client_id and client_secret."""
35
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock_auth:
36
+ mock_auth.return_value = "test_token"
37
+
38
+ result = runner.invoke(
39
+ wiz,
40
+ [
41
+ "authenticate",
42
+ "--client_id",
43
+ "test_client_id",
44
+ "--client_secret",
45
+ "test_secret",
46
+ ],
47
+ )
48
+
49
+ # Verify wiz_authenticate was called with the provided credentials
50
+ mock_auth.assert_called_once_with("test_client_id", "test_secret")
51
+ assert result.exit_code == 0
52
+
53
+ def test_authenticate_without_credentials(self, runner, mock_wiz_variables):
54
+ """Test authenticate command without providing credentials (uses env vars)."""
55
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock_auth:
56
+ mock_auth.return_value = "test_token"
57
+
58
+ result = runner.invoke(wiz, ["authenticate"])
59
+
60
+ # Verify wiz_authenticate was called with None values (will use env vars internally)
61
+ mock_auth.assert_called_once_with(None, None)
62
+ assert result.exit_code == 0
63
+
64
+ # ========================================
65
+ # Test issues command (lines 143-146)
66
+ # ========================================
67
+
68
+ @pytest.fixture
69
+ def mock_wiz_issue(self):
70
+ """Mock the WizIssue scanner."""
71
+ with patch("regscale.integrations.commercial.wizv2.issue.WizIssue") as mock:
72
+ instance = MagicMock()
73
+ mock.return_value = instance
74
+ yield instance
75
+
76
+ @pytest.fixture
77
+ def mock_wiz_auth(self):
78
+ """Mock wiz_authenticate."""
79
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock:
80
+ yield mock
81
+
82
+ @pytest.fixture
83
+ def mock_check_license(self):
84
+ """Mock check_license."""
85
+ with patch("regscale.core.app.utils.app_utils.check_license") as mock:
86
+ yield mock
87
+
88
+ def test_issues_with_none_client_secret(
89
+ self,
90
+ runner,
91
+ mock_wiz_variables,
92
+ mock_wiz_issue,
93
+ mock_wiz_auth,
94
+ mock_check_license,
95
+ ):
96
+ """Test issues command when client_secret is None (line 143-144)."""
97
+ result = runner.invoke(
98
+ wiz,
99
+ [
100
+ "issues",
101
+ "--wiz_project_id",
102
+ "test-project",
103
+ "--regscale_ssp_id",
104
+ "2288",
105
+ # Not providing client_secret, so it will be None and use env var
106
+ ],
107
+ )
108
+
109
+ # Check that the command ran
110
+ assert result.exit_code == 0 or "successfully" in result.output.lower()
111
+
112
+ # Verify that wiz_authenticate was called with env credentials
113
+ mock_wiz_auth.assert_called_once_with("env_client_id", "env_client_secret")
114
+
115
+ def test_issues_with_none_client_id(
116
+ self,
117
+ runner,
118
+ mock_wiz_variables,
119
+ mock_wiz_issue,
120
+ mock_wiz_auth,
121
+ mock_check_license,
122
+ ):
123
+ """Test issues command when client_id is None (line 145-146)."""
124
+ result = runner.invoke(
125
+ wiz,
126
+ [
127
+ "issues",
128
+ "--wiz_project_id",
129
+ "test-project",
130
+ "--regscale_ssp_id",
131
+ "2288",
132
+ "--client_secret",
133
+ "provided_secret",
134
+ # Not providing client_id, so it will be None and use env var
135
+ ],
136
+ )
137
+
138
+ # Check that the command ran
139
+ assert result.exit_code == 0 or "successfully" in result.output.lower()
140
+
141
+ # Verify that wiz_authenticate was called with env client_id
142
+ mock_wiz_auth.assert_called_once_with("env_client_id", "provided_secret")
143
+
144
+ # ========================================
145
+ # Test attach_sbom command (lines 194-206)
146
+ # ========================================
147
+
148
+ def test_attach_sbom_with_none_client_secret(self, runner, mock_wiz_variables):
149
+ """Test attach_sbom command when client_secret is None (lines 197-198)."""
150
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock_auth:
151
+ with patch("regscale.integrations.commercial.wizv2.utils.fetch_sbom_report") as mock_fetch:
152
+ mock_auth.return_value = "test_token"
153
+ mock_fetch.return_value = None
154
+
155
+ result = runner.invoke(
156
+ wiz,
157
+ [
158
+ "attach_sbom",
159
+ "--regscale_ssp_id",
160
+ "2288",
161
+ "--report_id",
162
+ "test-report-id",
163
+ # Not providing client_secret
164
+ ],
165
+ )
166
+
167
+ # Verify wiz_authenticate was called with env secret
168
+ mock_auth.assert_called_once_with(
169
+ client_id="env_client_id",
170
+ client_secret="env_client_secret",
171
+ )
172
+ assert result.exit_code == 0 or "report" in result.output.lower()
173
+
174
+ def test_attach_sbom_with_none_client_id(self, runner, mock_wiz_variables):
175
+ """Test attach_sbom command when client_id is None (lines 199-200)."""
176
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock_auth:
177
+ with patch("regscale.integrations.commercial.wizv2.utils.fetch_sbom_report") as mock_fetch:
178
+ mock_auth.return_value = "test_token"
179
+ mock_fetch.return_value = None
180
+
181
+ result = runner.invoke(
182
+ wiz,
183
+ [
184
+ "attach_sbom",
185
+ "--regscale_ssp_id",
186
+ "2288",
187
+ "--report_id",
188
+ "test-report-id",
189
+ "--client_secret",
190
+ "provided_secret",
191
+ # Not providing client_id
192
+ ],
193
+ )
194
+
195
+ # Verify wiz_authenticate was called with env client_id
196
+ mock_auth.assert_called_once_with(
197
+ client_id="env_client_id",
198
+ client_secret="provided_secret",
199
+ )
200
+ assert result.exit_code == 0 or "report" in result.output.lower()
201
+
202
+ def test_attach_sbom_full_flow(self, runner, mock_wiz_variables):
203
+ """Test attach_sbom command full flow (lines 202-212)."""
204
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock_auth:
205
+ with patch("regscale.integrations.commercial.wizv2.utils.fetch_sbom_report") as mock_fetch:
206
+ mock_auth.return_value = "test_token"
207
+ mock_fetch.return_value = None
208
+
209
+ result = runner.invoke(
210
+ wiz,
211
+ [
212
+ "attach_sbom",
213
+ "--client_id",
214
+ "test_client",
215
+ "--client_secret",
216
+ "test_secret",
217
+ "--regscale_ssp_id",
218
+ "2288",
219
+ "--report_id",
220
+ "test-report-id",
221
+ "--standard",
222
+ "SPDX",
223
+ ],
224
+ )
225
+
226
+ # Verify fetch_sbom_report was called with correct parameters
227
+ mock_fetch.assert_called_once_with(
228
+ "test-report-id",
229
+ parent_id=2288, # parent_id is an integer from click
230
+ report_file_name="sbom_report",
231
+ report_file_extension="zip",
232
+ standard="SPDX",
233
+ )
234
+ assert result.exit_code == 0
235
+
236
+ # ========================================
237
+ # Test vulnerabilities command (lines 261-264)
238
+ # ========================================
239
+
240
+ @pytest.fixture
241
+ def mock_scanner(self):
242
+ """Mock the WizVulnerabilityIntegration scanner."""
243
+ with patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration") as mock:
244
+ instance = MagicMock()
245
+ mock.return_value = instance
246
+ yield instance
247
+
248
+ def test_vulnerabilities_with_none_client_secret(self, runner, mock_wiz_variables, mock_scanner):
249
+ """Test vulnerabilities command when client_secret is None (lines 261-262)."""
250
+ result = runner.invoke(
251
+ wiz,
252
+ [
253
+ "vulnerabilities",
254
+ "--wiz_project_id",
255
+ "test-project",
256
+ "--regscale_ssp_id",
257
+ "2288",
258
+ # Not providing client_secret
259
+ ],
260
+ )
261
+
262
+ # Check that the command ran
263
+ assert result.exit_code == 0 or "successfully" in result.output.lower()
264
+
265
+ # Verify that sync_findings was called with env credentials
266
+ mock_scanner.sync_findings.assert_called_once()
267
+ call_kwargs = mock_scanner.sync_findings.call_args[1]
268
+ assert call_kwargs["client_secret"] == "env_client_secret"
269
+
270
+ def test_vulnerabilities_with_none_client_id(self, runner, mock_wiz_variables, mock_scanner):
271
+ """Test vulnerabilities command when client_id is None (lines 263-264)."""
272
+ result = runner.invoke(
273
+ wiz,
274
+ [
275
+ "vulnerabilities",
276
+ "--wiz_project_id",
277
+ "test-project",
278
+ "--regscale_ssp_id",
279
+ "2288",
280
+ "--client_secret",
281
+ "provided_secret",
282
+ # Not providing client_id
283
+ ],
284
+ )
285
+
286
+ # Check that the command ran
287
+ assert result.exit_code == 0 or "successfully" in result.output.lower()
288
+
289
+ # Verify that sync_findings was called with env client_id
290
+ mock_scanner.sync_findings.assert_called_once()
291
+ call_kwargs = mock_scanner.sync_findings.call_args[1]
292
+ assert call_kwargs["client_id"] == "env_client_id"
293
+
294
+ # ========================================
295
+ # Test add_report_evidence command (lines 310-322)
296
+ # ========================================
297
+
298
+ def test_add_report_evidence_with_none_client_secret(self, runner, mock_wiz_variables):
299
+ """Test add_report_evidence command when client_secret is None (lines 313-314)."""
300
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock_auth:
301
+ with patch("regscale.integrations.commercial.wizv2.utils.fetch_report_by_id") as mock_fetch:
302
+ mock_auth.return_value = "test_token"
303
+ mock_fetch.return_value = None
304
+
305
+ result = runner.invoke(
306
+ wiz,
307
+ [
308
+ "add_report_evidence",
309
+ "--evidence_id",
310
+ "123",
311
+ "--report_id",
312
+ "test-report-id",
313
+ # Not providing client_secret
314
+ ],
315
+ )
316
+
317
+ # Verify wiz_authenticate was called with env secret
318
+ mock_auth.assert_called_once_with(
319
+ client_id="env_client_id",
320
+ client_secret="env_client_secret",
321
+ )
322
+ assert result.exit_code == 0 or "report" in result.output.lower()
323
+
324
+ def test_add_report_evidence_with_none_client_id(self, runner, mock_wiz_variables):
325
+ """Test add_report_evidence command when client_id is None (lines 315-316)."""
326
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock_auth:
327
+ with patch("regscale.integrations.commercial.wizv2.utils.fetch_report_by_id") as mock_fetch:
328
+ mock_auth.return_value = "test_token"
329
+ mock_fetch.return_value = None
330
+
331
+ result = runner.invoke(
332
+ wiz,
333
+ [
334
+ "add_report_evidence",
335
+ "--evidence_id",
336
+ "123",
337
+ "--report_id",
338
+ "test-report-id",
339
+ "--client_secret",
340
+ "provided_secret",
341
+ # Not providing client_id
342
+ ],
343
+ )
344
+
345
+ # Verify wiz_authenticate was called with env client_id
346
+ mock_auth.assert_called_once_with(
347
+ client_id="env_client_id",
348
+ client_secret="provided_secret",
349
+ )
350
+ assert result.exit_code == 0 or "report" in result.output.lower()
351
+
352
+ def test_add_report_evidence_full_flow(self, runner, mock_wiz_variables):
353
+ """Test add_report_evidence command full flow (lines 318-327)."""
354
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock_auth:
355
+ with patch("regscale.integrations.commercial.wizv2.utils.fetch_report_by_id") as mock_fetch:
356
+ mock_auth.return_value = "test_token"
357
+ mock_fetch.return_value = None
358
+
359
+ result = runner.invoke(
360
+ wiz,
361
+ [
362
+ "add_report_evidence",
363
+ "--client_id",
364
+ "test_client",
365
+ "--client_secret",
366
+ "test_secret",
367
+ "--evidence_id",
368
+ "456",
369
+ "--report_id",
370
+ "report-789",
371
+ "--report_file_name",
372
+ "custom_report",
373
+ "--report_file_extension",
374
+ "xlsx",
375
+ ],
376
+ )
377
+
378
+ # Verify fetch_report_by_id was called with correct parameters
379
+ mock_fetch.assert_called_once_with(
380
+ "report-789",
381
+ parent_id=456,
382
+ report_file_name="custom_report",
383
+ report_file_extension="xlsx",
384
+ )
385
+ assert result.exit_code == 0
386
+
387
+ # ========================================
388
+ # Test sync_compliance command (lines 433-468)
389
+ # ========================================
390
+
391
+ def test_sync_compliance_list_frameworks_flag(self, runner, mock_wiz_variables):
392
+ """Test sync_compliance command with --list-frameworks flag (lines 437-440)."""
393
+ result = runner.invoke(
394
+ wiz,
395
+ [
396
+ "sync_compliance",
397
+ "--wiz_project_id",
398
+ "test-project",
399
+ "--regscale_id",
400
+ "123",
401
+ "--list-frameworks",
402
+ ],
403
+ )
404
+
405
+ # Should output deprecation message and list-frameworks not supported message
406
+ assert "sync_compliance is deprecated" in result.output
407
+ assert "list-frameworks is no longer supported" in result.output
408
+ assert result.exit_code == 0
409
+
410
+ def test_sync_compliance_with_none_or_empty_client_secret(self, runner, mock_wiz_variables):
411
+ """Test sync_compliance when client_secret is None or empty (lines 443-444)."""
412
+ with patch(
413
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
414
+ ) as mock_processor:
415
+ mock_instance = MagicMock()
416
+ mock_processor.return_value = mock_instance
417
+
418
+ result = runner.invoke(
419
+ wiz,
420
+ [
421
+ "sync_compliance",
422
+ "--wiz_project_id",
423
+ "test-project",
424
+ "--regscale_id",
425
+ "123",
426
+ # Not providing client_secret
427
+ ],
428
+ )
429
+
430
+ # Verify processor was created with env credentials
431
+ mock_processor.assert_called_once()
432
+ call_kwargs = mock_processor.call_args[1]
433
+ assert call_kwargs["client_secret"] == "env_client_secret"
434
+ assert result.exit_code == 0
435
+
436
+ def test_sync_compliance_with_empty_string_client_secret(self, runner, mock_wiz_variables):
437
+ """Test sync_compliance when client_secret is empty string (lines 443-444)."""
438
+ with patch(
439
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
440
+ ) as mock_processor:
441
+ mock_instance = MagicMock()
442
+ mock_processor.return_value = mock_instance
443
+
444
+ result = runner.invoke(
445
+ wiz,
446
+ [
447
+ "sync_compliance",
448
+ "--wiz_project_id",
449
+ "test-project",
450
+ "--regscale_id",
451
+ "123",
452
+ "--client_secret",
453
+ "", # Empty string
454
+ ],
455
+ )
456
+
457
+ # Verify processor was created with env credentials
458
+ mock_processor.assert_called_once()
459
+ call_kwargs = mock_processor.call_args[1]
460
+ assert call_kwargs["client_secret"] == "env_client_secret"
461
+ assert result.exit_code == 0
462
+
463
+ def test_sync_compliance_with_none_or_empty_client_id(self, runner, mock_wiz_variables):
464
+ """Test sync_compliance when client_id is None or empty (lines 445-446)."""
465
+ with patch(
466
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
467
+ ) as mock_processor:
468
+ mock_instance = MagicMock()
469
+ mock_processor.return_value = mock_instance
470
+
471
+ result = runner.invoke(
472
+ wiz,
473
+ [
474
+ "sync_compliance",
475
+ "--wiz_project_id",
476
+ "test-project",
477
+ "--regscale_id",
478
+ "123",
479
+ # Not providing client_id
480
+ ],
481
+ )
482
+
483
+ # Verify processor was created with env credentials
484
+ mock_processor.assert_called_once()
485
+ call_kwargs = mock_processor.call_args[1]
486
+ assert call_kwargs["client_id"] == "env_client_id"
487
+ assert result.exit_code == 0
488
+
489
+ def test_sync_compliance_full_flow(self, runner, mock_wiz_variables):
490
+ """Test sync_compliance command full flow (lines 449-468)."""
491
+ with patch(
492
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
493
+ ) as mock_processor:
494
+ mock_instance = MagicMock()
495
+ mock_processor.return_value = mock_instance
496
+
497
+ result = runner.invoke(
498
+ wiz,
499
+ [
500
+ "sync_compliance",
501
+ "--wiz_project_id",
502
+ "test-project",
503
+ "--regscale_id",
504
+ "456",
505
+ "--client_id",
506
+ "test_client",
507
+ "--client_secret",
508
+ "test_secret",
509
+ "--framework_id",
510
+ "wf-id-5",
511
+ "--create-issues",
512
+ "--update-control-status",
513
+ "--create-poams",
514
+ "--refresh",
515
+ "--cache-duration",
516
+ "720",
517
+ ],
518
+ )
519
+
520
+ # Verify processor was created with correct parameters
521
+ mock_processor.assert_called_once()
522
+ call_kwargs = mock_processor.call_args[1]
523
+ assert call_kwargs["plan_id"] == 456
524
+ assert call_kwargs["wiz_project_id"] == "test-project"
525
+ assert call_kwargs["client_id"] == "test_client"
526
+ assert call_kwargs["client_secret"] == "test_secret"
527
+ assert call_kwargs["create_poams"] is True
528
+ assert call_kwargs["create_issues"] is True
529
+ assert call_kwargs["update_control_status"] is True
530
+ assert call_kwargs["force_fresh_report"] is True
531
+ assert call_kwargs["reuse_existing_reports"] is False
532
+ assert call_kwargs["bypass_control_filtering"] is True
533
+
534
+ # Verify process_compliance_sync was called
535
+ mock_instance.process_compliance_sync.assert_called_once()
536
+ assert result.exit_code == 0
537
+
538
+ # ========================================
539
+ # Test compliance_report command (lines 568-594)
540
+ # ========================================
541
+
542
+ def test_compliance_report_with_empty_client_secret(self, runner, mock_wiz_variables):
543
+ """Test compliance_report when client_secret is empty (lines 571-572)."""
544
+ with patch(
545
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
546
+ ) as mock_processor:
547
+ mock_instance = MagicMock()
548
+ mock_processor.return_value = mock_instance
549
+
550
+ result = runner.invoke(
551
+ wiz,
552
+ [
553
+ "compliance_report",
554
+ "--wiz_project_id",
555
+ "test-project",
556
+ "--regscale_id",
557
+ "789",
558
+ "--client_secret",
559
+ "", # Empty string
560
+ ],
561
+ )
562
+
563
+ # Verify processor was created with env credentials
564
+ mock_processor.assert_called_once()
565
+ call_kwargs = mock_processor.call_args[1]
566
+ assert call_kwargs["client_secret"] == "env_client_secret"
567
+ assert result.exit_code == 0
568
+
569
+ def test_compliance_report_with_none_client_secret(self, runner, mock_wiz_variables):
570
+ """Test compliance_report when client_secret is None (lines 571-572)."""
571
+ with patch(
572
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
573
+ ) as mock_processor:
574
+ mock_instance = MagicMock()
575
+ mock_processor.return_value = mock_instance
576
+
577
+ result = runner.invoke(
578
+ wiz,
579
+ [
580
+ "compliance_report",
581
+ "--wiz_project_id",
582
+ "test-project",
583
+ "--regscale_id",
584
+ "789",
585
+ # Not providing client_secret
586
+ ],
587
+ )
588
+
589
+ # Verify processor was created with env credentials
590
+ mock_processor.assert_called_once()
591
+ call_kwargs = mock_processor.call_args[1]
592
+ assert call_kwargs["client_secret"] == "env_client_secret"
593
+ assert result.exit_code == 0
594
+
595
+ def test_compliance_report_with_empty_client_id(self, runner, mock_wiz_variables):
596
+ """Test compliance_report when client_id is empty (lines 573-574)."""
597
+ with patch(
598
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
599
+ ) as mock_processor:
600
+ mock_instance = MagicMock()
601
+ mock_processor.return_value = mock_instance
602
+
603
+ result = runner.invoke(
604
+ wiz,
605
+ [
606
+ "compliance_report",
607
+ "--wiz_project_id",
608
+ "test-project",
609
+ "--regscale_id",
610
+ "789",
611
+ "--client_id",
612
+ "", # Empty string
613
+ ],
614
+ )
615
+
616
+ # Verify processor was created with env credentials
617
+ mock_processor.assert_called_once()
618
+ call_kwargs = mock_processor.call_args[1]
619
+ assert call_kwargs["client_id"] == "env_client_id"
620
+ assert result.exit_code == 0
621
+
622
+ def test_compliance_report_full_flow(self, runner, mock_wiz_variables):
623
+ """Test compliance_report command full flow (lines 578-594)."""
624
+ with patch(
625
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
626
+ ) as mock_processor:
627
+ mock_instance = MagicMock()
628
+ mock_processor.return_value = mock_instance
629
+
630
+ result = runner.invoke(
631
+ wiz,
632
+ [
633
+ "compliance_report",
634
+ "--wiz_project_id",
635
+ "test-project-2",
636
+ "--regscale_id",
637
+ "999",
638
+ "--client_id",
639
+ "custom_client",
640
+ "--client_secret",
641
+ "custom_secret",
642
+ "--report_file_path",
643
+ "/path/to/report.csv",
644
+ "--create-issues",
645
+ "--update-control-status",
646
+ "--create-poams",
647
+ "--no-reuse-existing-reports",
648
+ "--force-fresh-report",
649
+ ],
650
+ )
651
+
652
+ # Verify processor was created with correct parameters
653
+ mock_processor.assert_called_once()
654
+ call_kwargs = mock_processor.call_args[1]
655
+ assert call_kwargs["plan_id"] == 999
656
+ assert call_kwargs["wiz_project_id"] == "test-project-2"
657
+ assert call_kwargs["client_id"] == "custom_client"
658
+ assert call_kwargs["client_secret"] == "custom_secret"
659
+ assert call_kwargs["report_file_path"] == "/path/to/report.csv"
660
+ assert call_kwargs["create_poams"] is True
661
+ assert call_kwargs["create_issues"] is True
662
+ assert call_kwargs["update_control_status"] is True
663
+ assert call_kwargs["bypass_control_filtering"] is True
664
+ assert call_kwargs["reuse_existing_reports"] is False
665
+ assert call_kwargs["force_fresh_report"] is True
666
+
667
+ # Verify process_compliance_sync was called
668
+ mock_instance.process_compliance_sync.assert_called_once()
669
+ assert result.exit_code == 0
670
+
671
+ def test_compliance_report_with_defaults(self, runner, mock_wiz_variables):
672
+ """Test compliance_report command with default values."""
673
+ with patch(
674
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
675
+ ) as mock_processor:
676
+ mock_instance = MagicMock()
677
+ mock_processor.return_value = mock_instance
678
+
679
+ result = runner.invoke(
680
+ wiz,
681
+ [
682
+ "compliance_report",
683
+ "--wiz_project_id",
684
+ "test-project-defaults",
685
+ "--regscale_id",
686
+ "111",
687
+ ],
688
+ )
689
+
690
+ # Verify processor was created with default values
691
+ mock_processor.assert_called_once()
692
+ call_kwargs = mock_processor.call_args[1]
693
+ assert call_kwargs["create_issues"] is True # Default
694
+ assert call_kwargs["update_control_status"] is True # Default
695
+ assert call_kwargs["create_poams"] is False # Default
696
+ assert call_kwargs["reuse_existing_reports"] is True # Default
697
+ assert call_kwargs["force_fresh_report"] is False # Default
698
+ assert call_kwargs["report_file_path"] is None # Default
699
+
700
+ # Verify process_compliance_sync was called
701
+ mock_instance.process_compliance_sync.assert_called_once()
702
+ assert result.exit_code == 0
703
+
704
+ # ========================================
705
+ # Edge cases and error handling
706
+ # ========================================
707
+
708
+ def test_inventory_with_filter_override(self, runner, mock_wiz_variables, mock_scanner):
709
+ """Test inventory command with filter_by_override parameter."""
710
+ result = runner.invoke(
711
+ wiz,
712
+ [
713
+ "inventory",
714
+ "--wiz_project_id",
715
+ "test-project",
716
+ "--regscale_ssp_id",
717
+ "2288",
718
+ "--filter_by_override",
719
+ '{"projectId": ["test-123"]}',
720
+ ],
721
+ )
722
+
723
+ # Verify sync_assets was called with the filter
724
+ mock_scanner.sync_assets.assert_called_once()
725
+ call_kwargs = mock_scanner.sync_assets.call_args[1]
726
+ assert call_kwargs["filter_by_override"] == '{"projectId": ["test-123"]}'
727
+ assert result.exit_code == 0
728
+
729
+ def test_issues_with_filter_override(
730
+ self,
731
+ runner,
732
+ mock_wiz_variables,
733
+ mock_wiz_issue,
734
+ mock_wiz_auth,
735
+ mock_check_license,
736
+ ):
737
+ """Test issues command with filter_by_override parameter."""
738
+ result = runner.invoke(
739
+ wiz,
740
+ [
741
+ "issues",
742
+ "--wiz_project_id",
743
+ "test-project",
744
+ "--regscale_ssp_id",
745
+ "2288",
746
+ "--filter_by_override",
747
+ '{"severity": ["CRITICAL"]}',
748
+ ],
749
+ )
750
+
751
+ # Verify sync_findings was called
752
+ mock_wiz_issue.sync_findings.assert_called_once()
753
+ assert result.exit_code == 0
754
+
755
+ def test_vulnerabilities_with_filter_override(self, runner, mock_wiz_variables, mock_scanner):
756
+ """Test vulnerabilities command with filter_by_override parameter."""
757
+ result = runner.invoke(
758
+ wiz,
759
+ [
760
+ "vulnerabilities",
761
+ "--wiz_project_id",
762
+ "test-project",
763
+ "--regscale_ssp_id",
764
+ "2288",
765
+ "--filter_by_override",
766
+ '{"type": ["CRITICAL"]}',
767
+ ],
768
+ )
769
+
770
+ # Verify sync_findings was called with the filter
771
+ mock_scanner.sync_findings.assert_called_once()
772
+ call_kwargs = mock_scanner.sync_findings.call_args[1]
773
+ assert call_kwargs["filter_by_override"] == '{"type": ["CRITICAL"]}'
774
+ assert result.exit_code == 0
775
+
776
+ def test_attach_sbom_with_default_standard(self, runner, mock_wiz_variables):
777
+ """Test attach_sbom command with default standard (CycloneDX)."""
778
+ with patch("regscale.integrations.commercial.wizv2.core.auth.wiz_authenticate") as mock_auth:
779
+ with patch("regscale.integrations.commercial.wizv2.utils.fetch_sbom_report") as mock_fetch:
780
+ mock_auth.return_value = "test_token"
781
+ mock_fetch.return_value = None
782
+
783
+ result = runner.invoke(
784
+ wiz,
785
+ [
786
+ "attach_sbom",
787
+ "--regscale_ssp_id",
788
+ "2288",
789
+ "--report_id",
790
+ "test-report-id",
791
+ # Not providing --standard, should default to CycloneDX
792
+ ],
793
+ )
794
+
795
+ # Verify fetch_sbom_report was called with default standard
796
+ mock_fetch.assert_called_once()
797
+ call_args = mock_fetch.call_args[1]
798
+ assert call_args["standard"] == "CycloneDX"
799
+ assert result.exit_code == 0
800
+
801
+ def test_sync_compliance_no_create_issues(self, runner, mock_wiz_variables):
802
+ """Test sync_compliance with --no-create-issues flag."""
803
+ with patch(
804
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
805
+ ) as mock_processor:
806
+ mock_instance = MagicMock()
807
+ mock_processor.return_value = mock_instance
808
+
809
+ result = runner.invoke(
810
+ wiz,
811
+ [
812
+ "sync_compliance",
813
+ "--wiz_project_id",
814
+ "test-project",
815
+ "--regscale_id",
816
+ "123",
817
+ "--no-create-issues",
818
+ ],
819
+ )
820
+
821
+ # Verify processor was created with create_issues=False
822
+ mock_processor.assert_called_once()
823
+ call_kwargs = mock_processor.call_args[1]
824
+ assert call_kwargs["create_issues"] is False
825
+ assert result.exit_code == 0
826
+
827
+ def test_compliance_report_no_update_control_status(self, runner, mock_wiz_variables):
828
+ """Test compliance_report with --no-update-control-status flag."""
829
+ with patch(
830
+ "regscale.integrations.commercial.wizv2.compliance_report.WizComplianceReportProcessor"
831
+ ) as mock_processor:
832
+ mock_instance = MagicMock()
833
+ mock_processor.return_value = mock_instance
834
+
835
+ result = runner.invoke(
836
+ wiz,
837
+ [
838
+ "compliance_report",
839
+ "--wiz_project_id",
840
+ "test-project",
841
+ "--regscale_id",
842
+ "789",
843
+ "--no-update-control-status",
844
+ ],
845
+ )
846
+
847
+ # Verify processor was created with update_control_status=False
848
+ mock_processor.assert_called_once()
849
+ call_kwargs = mock_processor.call_args[1]
850
+ assert call_kwargs["update_control_status"] is False
851
+ assert result.exit_code == 0