regscale-cli 6.25.0.1__py3-none-any.whl → 6.26.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (84) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/application.py +18 -3
  4. regscale/core/app/internal/login.py +0 -1
  5. regscale/core/app/utils/catalog_utils/common.py +1 -1
  6. regscale/integrations/commercial/sicura/api.py +14 -13
  7. regscale/integrations/commercial/sicura/commands.py +8 -2
  8. regscale/integrations/commercial/sicura/scanner.py +49 -39
  9. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  10. regscale/integrations/commercial/synqly/assets.py +17 -0
  11. regscale/integrations/commercial/wizv2/click.py +26 -26
  12. regscale/integrations/commercial/wizv2/compliance_report.py +152 -157
  13. regscale/integrations/commercial/wizv2/constants.py +20 -71
  14. regscale/integrations/commercial/wizv2/scanner.py +3 -3
  15. regscale/integrations/compliance_integration.py +67 -2
  16. regscale/integrations/control_matcher.py +358 -0
  17. regscale/integrations/due_date_handler.py +118 -6
  18. regscale/integrations/milestone_manager.py +291 -0
  19. regscale/integrations/public/__init__.py +1 -0
  20. regscale/integrations/public/cci_importer.py +37 -38
  21. regscale/integrations/public/fedramp/click.py +60 -2
  22. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  23. regscale/integrations/scanner_integration.py +199 -130
  24. regscale/models/integration_models/cisa_kev_data.json +199 -4
  25. regscale/models/integration_models/nexpose.py +36 -10
  26. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  27. regscale/models/locking.py +12 -8
  28. regscale/models/platform.py +1 -2
  29. regscale/models/regscale_models/control_implementation.py +46 -21
  30. regscale/models/regscale_models/issue.py +256 -94
  31. regscale/models/regscale_models/milestone.py +1 -1
  32. regscale/models/regscale_models/regscale_model.py +6 -1
  33. regscale/templates/__init__.py +0 -0
  34. regscale/utils/threading/threadhandler.py +20 -15
  35. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/METADATA +1 -1
  36. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/RECORD +84 -37
  37. tests/regscale/integrations/commercial/__init__.py +0 -0
  38. tests/regscale/integrations/commercial/conftest.py +28 -0
  39. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  40. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  41. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  42. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  43. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  44. tests/regscale/integrations/commercial/test_aws.py +3731 -0
  45. tests/regscale/integrations/commercial/test_burp.py +48 -0
  46. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  47. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  48. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  49. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  50. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  51. tests/regscale/integrations/commercial/test_jira.py +1814 -0
  52. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  53. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  54. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  55. tests/regscale/integrations/commercial/test_sicura.py +350 -0
  56. tests/regscale/integrations/commercial/test_snow.py +423 -0
  57. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  58. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  59. tests/regscale/integrations/commercial/test_stig.py +33 -0
  60. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  61. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  62. tests/regscale/integrations/commercial/test_wiz.py +1469 -0
  63. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  64. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  65. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  66. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  67. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  68. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1351 -0
  69. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  70. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  71. tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +750 -0
  72. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  73. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +264 -0
  74. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +624 -0
  75. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  76. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  77. tests/regscale/integrations/test_control_matcher.py +1314 -0
  78. tests/regscale/integrations/test_control_matching.py +155 -0
  79. tests/regscale/integrations/test_milestone_manager.py +408 -0
  80. tests/regscale/models/test_issue.py +378 -1
  81. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/LICENSE +0 -0
  82. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/WHEEL +0 -0
  83. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/entry_points.txt +0 -0
  84. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,549 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Test for GitLab integration in RegScale CLI
5
+ """
6
+ import unittest
7
+ from unittest.mock import MagicMock, Mock, patch
8
+
9
+ from regscale.integrations.commercial.gitlab import (
10
+ # run_sync_issues,
11
+ get_issues_from_gitlab,
12
+ get_regscale_issues,
13
+ save_or_update_issues,
14
+ extract_links_with_labels,
15
+ convert_issues,
16
+ )
17
+ from regscale.models.regscale_models.issue import Issue
18
+ from regscale.models.regscale_models.link import Link
19
+ from tests import CLITestFixture
20
+
21
+
22
+ class TestGitLabIntegration(CLITestFixture, unittest.TestCase):
23
+ @patch("regscale.integrations.commercial.gitlab.check_license")
24
+ @patch("regscale.integrations.commercial.gitlab.Issue")
25
+ @patch("regscale.integrations.commercial.gitlab.logger")
26
+ def test_get_regscale_issues_securityplans(self, mock_logger, mock_issue, mock_check_license):
27
+ # Mocking the check_license function
28
+ mock_app = MagicMock()
29
+ mock_check_license.return_value = mock_app
30
+
31
+ # Mocking the fetch_issues_by_ssp method and setting return value
32
+ mock_issue.fetch_issues_by_ssp.return_value = ["issue1", "issue2"]
33
+
34
+ # Mocking the job_progress object
35
+ mock_job_progress = MagicMock()
36
+ mock_task = MagicMock()
37
+ mock_job_progress.add_task.return_value = mock_task
38
+
39
+ # Call the method to test
40
+ issues = get_regscale_issues(1, "securityplans", mock_job_progress)
41
+
42
+ # Assertions to check if the results are as expected
43
+ self.assertEqual(len(issues), 2)
44
+ mock_logger.info.assert_called_with("Fetched 2 issues from RegScale by SSP.")
45
+ mock_job_progress.add_task.assert_called_once()
46
+ mock_job_progress.update.assert_called_with(mock_task, advance=1)
47
+
48
+ @patch("regscale.integrations.commercial.gitlab.check_license")
49
+ @patch("regscale.integrations.commercial.gitlab.Issue")
50
+ @patch("regscale.integrations.commercial.gitlab.logger")
51
+ def test_get_regscale_issues_not_securityplan(self, mock_logger, mock_issue, mock_check_license):
52
+ # Mocking the check_license function
53
+ mock_app = MagicMock()
54
+ mock_check_license.return_value = mock_app
55
+
56
+ # Mocking the fetch_issues_by_parent method and setting return value
57
+ mock_issue.fetch_issues_by_parent.return_value = ["issue1", "issue2"]
58
+
59
+ # Mocking the job_progress object
60
+ mock_job_progress = MagicMock()
61
+ mock_task = MagicMock()
62
+ mock_job_progress.add_task.return_value = mock_task
63
+
64
+ # Call the method to test
65
+ issues = get_regscale_issues(1, "not_securityplans", mock_job_progress)
66
+
67
+ # Assertions to check if the results are as expected
68
+ self.assertEqual(len(issues), 2)
69
+ mock_logger.info.assert_called_with("Fetched 2 issues from RegScale by issue parent.")
70
+ mock_job_progress.add_task.assert_called_once()
71
+ mock_job_progress.update.assert_called_with(mock_task, advance=1)
72
+
73
+ @patch("regscale.integrations.commercial.gitlab.check_license")
74
+ @patch("regscale.integrations.commercial.gitlab.Issue")
75
+ def test_save_issues(self, mock_issue, mock_check_license):
76
+ """Test save_or_update_issues function intended to save"""
77
+ mock_app = MagicMock()
78
+ mock_check_license.return_value = mock_app
79
+
80
+ # Mocking the job_progress object
81
+ mock_job_progress = MagicMock()
82
+ mock_task = MagicMock()
83
+ mock_job_progress.add_task.return_value = mock_task
84
+
85
+ gitlab_issues = [
86
+ {
87
+ "issue": Issue(
88
+ id=1,
89
+ title="Issue 1",
90
+ description="Description 1",
91
+ status="Open",
92
+ severityLevel="Low",
93
+ dependabotId="1",
94
+ ),
95
+ "links": [],
96
+ }
97
+ ]
98
+
99
+ regscale_issues = [
100
+ Issue(
101
+ id=1,
102
+ title="Issue 2",
103
+ description="Description 2",
104
+ status="Open",
105
+ severityLevel="Low",
106
+ dependabotId="2",
107
+ )
108
+ ]
109
+
110
+ # mock the api call
111
+ mock_issue.insert_issue.return_value = gitlab_issues[0]["issue"]
112
+
113
+ # gitlab dependabot id not in regscale, will save new issue
114
+ save_or_update_issues(gitlab_issues, regscale_issues, mock_job_progress)
115
+
116
+ # Verify the issue was inserted with correct parameters
117
+ mock_issue.insert_issue.assert_called_once_with(app=mock_app, issue=gitlab_issues[0]["issue"])
118
+
119
+ # Verify job progress was updated
120
+ mock_job_progress.update.assert_called_with(mock_task, advance=1)
121
+
122
+ @patch("regscale.integrations.commercial.gitlab.check_license")
123
+ @patch("regscale.integrations.commercial.gitlab.Issue")
124
+ @patch("regscale.integrations.commercial.gitlab.Link")
125
+ def test_save_issues_with_links(self, mock_link, mock_issue, mock_check_license):
126
+ """Test save_or_update_issues function intended to save with links in gitlab issue"""
127
+ mock_app = MagicMock()
128
+ mock_check_license.return_value = mock_app
129
+
130
+ # Mocking the job_progress object
131
+ mock_job_progress = MagicMock()
132
+ mock_task = MagicMock()
133
+ mock_job_progress.add_task.return_value = mock_task
134
+
135
+ gitlab_issues = [
136
+ {
137
+ "issue": Issue(
138
+ id=1,
139
+ title="Issue 1",
140
+ description="Description 1",
141
+ status="Open",
142
+ severityLevel="Low",
143
+ dependabotId="1",
144
+ ),
145
+ "links": [
146
+ Link(
147
+ id=None,
148
+ title="Link to GitLab repo",
149
+ url="https://www.gitlab.com",
150
+ createdById=None,
151
+ lastUpdatedById=None,
152
+ dateLastUpdated=None,
153
+ isPublic=True,
154
+ ),
155
+ ],
156
+ }
157
+ ]
158
+
159
+ regscale_issues = [
160
+ Issue(
161
+ id=1,
162
+ title="Issue 2",
163
+ description="Description 2",
164
+ status="Open",
165
+ severityLevel="Low",
166
+ dependabotId="2",
167
+ )
168
+ ]
169
+
170
+ # mock the api calls
171
+ mock_issue.insert_issue.return_value = gitlab_issues[0]["issue"]
172
+ mock_link.insert_link.return_value = gitlab_issues[0]["links"][0]
173
+
174
+ # gitlab dependabot id not in regscale, will save new issue
175
+ save_or_update_issues(gitlab_issues, regscale_issues, mock_job_progress)
176
+
177
+ # Verify the issue was inserted with correct parameters
178
+ mock_issue.insert_issue.assert_called_once_with(app=mock_app, issue=gitlab_issues[0]["issue"])
179
+
180
+ # verify the link was inserted with correct parameters
181
+ mock_link.insert_link.assert_called_once_with(app=mock_app, link=gitlab_issues[0]["links"][0])
182
+
183
+ # Verify job progress was updated
184
+ mock_job_progress.update.assert_called_with(mock_task, advance=1)
185
+
186
+ @patch("regscale.integrations.commercial.gitlab.check_license")
187
+ @patch("regscale.integrations.commercial.gitlab.Issue")
188
+ def test_update_issues(self, mock_issue, mock_check_license):
189
+ """Test save_or_update_issues function intended to update"""
190
+ mock_app = MagicMock()
191
+ mock_check_license.return_value = mock_app
192
+
193
+ # Mocking the job_progress object
194
+ mock_job_progress = MagicMock()
195
+ mock_task = MagicMock()
196
+ mock_job_progress.add_task.return_value = mock_task
197
+
198
+ gitlab_issues = [
199
+ {
200
+ "issue": Issue(
201
+ id=1,
202
+ title="Issue 1",
203
+ description="Description 1",
204
+ status="Closed",
205
+ severityLevel="High",
206
+ dependabotId="1",
207
+ ),
208
+ "links": [],
209
+ }
210
+ ]
211
+
212
+ regscale_issues = [
213
+ Issue(
214
+ id=2,
215
+ title="Issue 2",
216
+ description="Description 2",
217
+ status="Open",
218
+ severityLevel="Low",
219
+ dependabotId="1",
220
+ )
221
+ ]
222
+
223
+ # Verify the issues are different
224
+ self.assertNotEqual(
225
+ gitlab_issues[0]["issue"], regscale_issues[0], "Issues should be different but __eq__ is returning True"
226
+ )
227
+
228
+ # mock api call
229
+ mock_issue.update_issue.return_value = gitlab_issues[0]["issue"]
230
+
231
+ save_or_update_issues(gitlab_issues, regscale_issues, mock_job_progress)
232
+
233
+ # Verify the issue was updated with correct parameters
234
+ mock_issue.update_issue.assert_called_once_with(app=mock_app, issue=gitlab_issues[0]["issue"])
235
+
236
+ # Verify job progress was updated
237
+ mock_job_progress.update.assert_called_with(mock_task, advance=1)
238
+
239
+ @patch("regscale.integrations.commercial.gitlab.check_license")
240
+ @patch("regscale.integrations.commercial.gitlab.Issue")
241
+ @patch("regscale.integrations.commercial.gitlab.Link")
242
+ def test_update_issues_with_links(self, mock_link, mock_issue, mock_check_license):
243
+ """Test save_or_update_issues function intended to update with links in gitlab issue"""
244
+ mock_app = MagicMock()
245
+ mock_check_license.return_value = mock_app
246
+
247
+ # Mocking the job_progress object
248
+ mock_job_progress = MagicMock()
249
+ mock_task = MagicMock()
250
+ mock_job_progress.add_task.return_value = mock_task
251
+
252
+ gitlab_issues = [
253
+ {
254
+ "issue": Issue(
255
+ id=1,
256
+ title="Issue 1",
257
+ description="Description 1",
258
+ status="Closed",
259
+ severityLevel="High",
260
+ dependabotId="1",
261
+ ),
262
+ "links": [
263
+ Link(
264
+ id=None,
265
+ title="Link to GitLab repo",
266
+ url="https://www.gitlab.com",
267
+ createdById=None,
268
+ lastUpdatedById=None,
269
+ dateLastUpdated=None,
270
+ isPublic=True,
271
+ ),
272
+ ],
273
+ }
274
+ ]
275
+
276
+ regscale_issues = [
277
+ Issue(
278
+ id=1,
279
+ title="Issue 2",
280
+ description="Description 2",
281
+ status="Open",
282
+ severityLevel="Low",
283
+ dependabotId="1",
284
+ )
285
+ ]
286
+
287
+ # mock api calls
288
+ mock_issue.update_issue.return_value = gitlab_issues[0]["issue"]
289
+ mock_link.insert_link.return_value = gitlab_issues[0]["links"][0]
290
+
291
+ save_or_update_issues(gitlab_issues, regscale_issues, mock_job_progress)
292
+
293
+ # Verify the issue was inserted with correct parameters
294
+ mock_issue.update_issue.assert_called_once_with(app=mock_app, issue=gitlab_issues[0]["issue"])
295
+ mock_link.insert_link.assert_called_once_with(app=mock_app, link=gitlab_issues[0]["links"][0])
296
+
297
+ # Verify job progress was updated
298
+ mock_job_progress.update.assert_called_with(mock_task, advance=1)
299
+
300
+ def test_extract_links_with_labels(self):
301
+ # Test data
302
+ text = "Link to GitLab repo: https: https://www.gitlab.com <br>\nAnother link: https: https://example.com"
303
+ parent_id = 1
304
+ parent_module = "issues"
305
+ # Expected result
306
+ expected_links = [
307
+ Link(
308
+ id=None,
309
+ title="Link to GitLab repo",
310
+ url="https://www.gitlab.com",
311
+ parentID=1,
312
+ parentModule=parent_module,
313
+ createdById=None,
314
+ lastUpdatedById=None,
315
+ dateLastUpdated=None,
316
+ isPublic=True,
317
+ ),
318
+ Link(
319
+ id=None,
320
+ title="Another link",
321
+ url="https://example.com",
322
+ parentID=1,
323
+ parentModule=parent_module,
324
+ createdById=None,
325
+ lastUpdatedById=None,
326
+ dateLastUpdated=None,
327
+ isPublic=True,
328
+ ),
329
+ ]
330
+
331
+ # Call the function
332
+ result = extract_links_with_labels(text, parent_id, parent_module)
333
+ print(result)
334
+ # Assert
335
+ self.assertEqual(result, expected_links)
336
+
337
+ def test_convert_issues_basic_properties(self):
338
+ """Test basic issue property conversion"""
339
+ gitlab_issues = [
340
+ {
341
+ "title": "Test issue",
342
+ "description": "Test description",
343
+ "state": "open",
344
+ "weight": 3,
345
+ "due_date": "2023-06-25",
346
+ "id": 1,
347
+ "created_at": "2023-06-24",
348
+ }
349
+ ]
350
+
351
+ job_progress = Mock()
352
+ job_progress.add_task.return_value = "task"
353
+
354
+ result = convert_issues(gitlab_issues, 1, "securityplans", True, job_progress)
355
+
356
+ self.assertIsInstance(result, list)
357
+ self.assertEqual(len(result), 1)
358
+
359
+ issue = result[0]["issue"]
360
+ self.assertIsInstance(issue, Issue)
361
+ self.assertEqual(issue.title, "Test issue")
362
+ self.assertEqual(issue.severityLevel, Issue.assign_severity(gitlab_issues[0]["weight"]))
363
+ self.assertEqual(issue.securityPlanId, 1)
364
+ self.assertIsNone(issue.componentId)
365
+
366
+ def test_convert_issues_status_handling(self):
367
+ """Test issue status conversion for different states"""
368
+ gitlab_issues = [
369
+ {
370
+ "title": "Open issue",
371
+ "description": "Test description",
372
+ "state": "open",
373
+ "weight": 3,
374
+ "due_date": "2023-06-25",
375
+ "id": 1,
376
+ "created_at": "2023-06-24",
377
+ },
378
+ {
379
+ "title": "Closed issue",
380
+ "description": "Test description",
381
+ "state": "closed",
382
+ "weight": 3,
383
+ "due_date": "2023-06-25",
384
+ "id": 2,
385
+ "created_at": "2023-06-24",
386
+ "closed_at": "2023-06-25",
387
+ },
388
+ ]
389
+
390
+ job_progress = Mock()
391
+ job_progress.add_task.return_value = "task"
392
+
393
+ result = convert_issues(gitlab_issues, 1, "securityplans", True, job_progress)
394
+
395
+ self.assertEqual(result[0]["issue"].status, "Open")
396
+ self.assertEqual(result[1]["issue"].status, "Closed")
397
+
398
+ @patch("regscale.integrations.commercial.gitlab.get_current_datetime", return_value="2024-06-25")
399
+ def test_convert_issues_date_handling(self, mock_get_current_datetime):
400
+ """Test dateCompleted handling for closed issues"""
401
+ gitlab_issues = [
402
+ {
403
+ "title": "Closed with date",
404
+ "description": "Test description",
405
+ "state": "closed",
406
+ "weight": 3,
407
+ "due_date": "2023-06-25",
408
+ "id": 1,
409
+ "created_at": "2023-06-24",
410
+ "closed_at": "2023-06-25",
411
+ },
412
+ {
413
+ "title": "Closed without date",
414
+ "description": "Test description",
415
+ "state": "closed",
416
+ "weight": 3,
417
+ "due_date": "2023-06-25",
418
+ "id": 2,
419
+ "created_at": "2023-06-24",
420
+ },
421
+ ]
422
+
423
+ job_progress = Mock()
424
+ job_progress.add_task.return_value = "task"
425
+
426
+ result = convert_issues(gitlab_issues, 1, "securityplans", True, job_progress)
427
+
428
+ self.assertEqual(result[0]["issue"].dateCompleted, "2023-06-25") # Uses closed_at
429
+ self.assertEqual(result[1]["issue"].dateCompleted, "2024-06-25") # Uses current datetime
430
+
431
+ def test_convert_issues_with_links(self):
432
+ """Test link extraction and inclusion"""
433
+ gitlab_issues = [
434
+ {
435
+ "title": "Test issue",
436
+ "description": "This is a test issue with a link: https: https://example.com",
437
+ "state": "open",
438
+ "weight": 3,
439
+ "due_date": "2023-06-25",
440
+ "id": 1,
441
+ "created_at": "2023-06-24",
442
+ }
443
+ ]
444
+
445
+ job_progress = Mock()
446
+ job_progress.add_task.return_value = "task"
447
+
448
+ result = convert_issues(gitlab_issues, 1, "securityplans", True, job_progress)
449
+
450
+ self.assertEqual(len(result[0]["links"]), 1)
451
+ self.assertEqual(result[0]["links"][0].url, "https://example.com")
452
+ job_progress.update.assert_called_with("task", advance=1)
453
+
454
+ def test_convert_issues_no_links(self):
455
+ """Test convert issues with include_links set to False"""
456
+ gitlab_issues = [
457
+ {
458
+ "title": "Test issue",
459
+ "description": "This is a test issue with a link: https: https://example.com",
460
+ "state": "closed",
461
+ "weight": 3,
462
+ "due_date": "2023-06-25",
463
+ "id": 1,
464
+ "created_at": "2023-06-24",
465
+ "closed_at": "2023-06-25",
466
+ },
467
+ ]
468
+
469
+ job_progress = Mock()
470
+ job_progress.add_task.return_value = "task"
471
+
472
+ result = convert_issues(gitlab_issues, 1, "securityplans", False, job_progress)
473
+
474
+ # Assertions
475
+ self.assertIsInstance(result, list)
476
+ self.assertEqual(len(result), 1)
477
+ self.assertEqual(result[0]["links"], [])
478
+
479
+ def test_convert_issues_empty(self):
480
+ """Test with empty issue list from gitlab"""
481
+ job_progress = Mock()
482
+ job_progress.add_task.return_value = "task"
483
+
484
+ result = convert_issues([], 1, "securityplans", True, job_progress)
485
+ assert result == []
486
+
487
+ @patch("regscale.integrations.commercial.gitlab.requests.get")
488
+ @patch("regscale.integrations.commercial.gitlab.job_progress.add_task")
489
+ @patch("regscale.integrations.commercial.gitlab.job_progress.update")
490
+ def test_get_issues_from_gitlab_success(self, mock_update, mock_add_task, mock_get):
491
+ # Mock the response from the GitLab API
492
+ mock_response = MagicMock()
493
+ mock_response.ok = True
494
+ mock_response.json.return_value = [
495
+ {"id": 1, "title": "Issue 1"},
496
+ {"id": 2, "title": "Issue 2"},
497
+ ]
498
+ mock_get.return_value = mock_response
499
+
500
+ # Mock job_progress
501
+ mock_job_progress = MagicMock()
502
+ mock_add_task.return_value = "fetching_issues"
503
+
504
+ # Call the function
505
+ issues = get_issues_from_gitlab("https://gitlab.com", 1, "api_token", mock_job_progress)
506
+
507
+ # Assertions
508
+ self.assertEqual(len(issues), 2)
509
+ self.assertEqual(issues[0]["id"], 1)
510
+ self.assertEqual(issues[0]["title"], "Issue 1")
511
+ self.assertEqual(issues[1]["id"], 2)
512
+ self.assertEqual(issues[1]["title"], "Issue 2")
513
+
514
+ # Assert that the mock methods were called
515
+ # mock_add_task.assert_called_once()
516
+ # mock_update.assert_called_once_with("fetching_issues", advance=1)
517
+ mock_get.assert_called_once_with(
518
+ "https://gitlab.com/api/v4/projects/1/issues",
519
+ headers={"Private-Token": "api_token"},
520
+ )
521
+
522
+ @patch("regscale.integrations.commercial.gitlab.requests.get")
523
+ @patch("regscale.integrations.commercial.gitlab.job_progress.add_task")
524
+ @patch("regscale.integrations.commercial.gitlab.job_progress.update")
525
+ def test_get_issues_from_gitlab_failure(self, mock_update, mock_add_task, mock_get):
526
+ # Mock the response from the GitLab API
527
+ mock_response = MagicMock()
528
+ mock_response.ok = False
529
+ mock_response.status_code = 404
530
+ mock_response.text = "Not Found"
531
+
532
+ mock_get.return_value = mock_response
533
+
534
+ # Mock job_progress
535
+ mock_job_progress = MagicMock()
536
+ mock_add_task.return_value = "fetching_issues"
537
+
538
+ # Call the function and verify it raises SystemExit
539
+ with self.assertRaises(SystemExit) as context:
540
+ get_issues_from_gitlab("https://gitlab.com", 1, "api_token", mock_job_progress)
541
+
542
+ # Verify the exit code
543
+ self.assertEqual(context.exception.code, 1)
544
+
545
+ # Verify the API call was made correctly
546
+ mock_get.assert_called_once_with(
547
+ "https://gitlab.com/api/v4/projects/1/issues",
548
+ headers={"Private-Token": "api_token"},
549
+ )
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env python
2
+ from regscale.integrations.commercial.tenablev2.scanner import TenableIntegration
3
+
4
+
5
+ def test_build_limited_string():
6
+ items = {"item1", "item2", "item3"}
7
+ result = TenableIntegration._build_limited_string(items, char_limit=10)
8
+ assert len(result) <= 10
9
+ assert isinstance(result, str)
10
+
11
+ # Test empty set
12
+ assert TenableIntegration._build_limited_string(set()) == ""
13
+
14
+ # Test with exact limit
15
+ items = {"a", "b"}
16
+ assert "a" in TenableIntegration._build_limited_string(
17
+ items, char_limit=4
18
+ ) and "b" in TenableIntegration._build_limited_string(items, char_limit=4)
19
+
20
+
21
+ def test_get_all_mac_addresses():
22
+ # Test case 1: Normal case with multiple MAC addresses
23
+ node = {
24
+ "network_interfaces": [
25
+ {"mac_addresses": ["00:11:22:33:44:55", "66:77:88:99:AA:BB"]},
26
+ {"mac_addresses": ["CC:DD:EE:FF:00:11"]},
27
+ ]
28
+ }
29
+ result = TenableIntegration.get_all_mac_addresses(node)
30
+ assert "00:11:22:33:44:55" in result
31
+ assert "66:77:88:99:AA:BB" in result
32
+ assert "CC:DD:EE:FF:00:11" in result
33
+
34
+ # Test case 2: Empty node
35
+ empty_node = {}
36
+ assert TenableIntegration.get_all_mac_addresses(empty_node) == ""
37
+
38
+ # Test case 3: Node with empty network interfaces
39
+ node_empty_interfaces = {"network_interfaces": []}
40
+ assert TenableIntegration.get_all_mac_addresses(node_empty_interfaces) == ""
41
+
42
+ # Test case 4: Test character limit (450 chars)
43
+ long_mac = "00:11:22:33:44:55"
44
+ print(f"testing {len(long_mac) * 30} chars")
45
+ node_many_macs = {"network_interfaces": [{"mac_addresses": [long_mac] * 30}]} # Should exceed 450 chars with commas
46
+ result = TenableIntegration.get_all_mac_addresses(node_many_macs)
47
+ assert len(result) <= 450
48
+ assert long_mac in result
49
+
50
+
51
+ def test_get_all_ip_addresses():
52
+ ipv_node = {"ipv4s": ["192.168.1.1", "192.168.1.2"], "ipv6s": ["fe80::1", "fe80::2"]}
53
+
54
+ expected_result = "192.168.1.1, 192.168.1.2, fe80::1, fe80::2"
55
+ result = TenableIntegration.get_all_ip_addresses(ipv_node)
56
+
57
+ for r in result.split(","):
58
+ assert r.strip() in expected_result
59
+
60
+
61
+ def test_get_all_ip_addresses_length():
62
+ ipv4s = [f"192.168.1.{i}" for i in range(1, 1001)]
63
+ ipv6s = [f"fe80::{i}" for i in range(1, 1001)]
64
+
65
+ ipv_node = {"ipv4s": ipv4s, "ipv6s": ipv6s}
66
+
67
+ result = TenableIntegration.get_all_ip_addresses(ipv_node)
68
+ # we need to stay under 450 and warn the user that all the data may not have fit in the field.
69
+ assert len(result) <= 450
70
+
71
+
72
+ def test_get_all_mac_address_length():
73
+ mac_addresses = [f"00:11:22:33:44:{i}" for i in range(1, 1001)]
74
+ node = {"network_interfaces": [{"mac_addresses": mac_addresses}]}
75
+ result = TenableIntegration.get_all_mac_addresses(node)
76
+ assert len(result) <= 450
77
+
78
+
79
+ if __name__ == "__main__":
80
+ test_build_limited_string()
81
+ test_get_all_mac_addresses()
82
+ test_get_all_ip_addresses()
83
+ test_get_all_mac_address_length()
84
+ test_get_all_ip_addresses_length()