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.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +18 -3
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/integrations/commercial/sicura/api.py +14 -13
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +49 -39
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +17 -0
- regscale/integrations/commercial/wizv2/click.py +26 -26
- regscale/integrations/commercial/wizv2/compliance_report.py +152 -157
- regscale/integrations/commercial/wizv2/constants.py +20 -71
- regscale/integrations/commercial/wizv2/scanner.py +3 -3
- regscale/integrations/compliance_integration.py +67 -2
- regscale/integrations/control_matcher.py +358 -0
- regscale/integrations/due_date_handler.py +118 -6
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +1 -0
- regscale/integrations/public/cci_importer.py +37 -38
- regscale/integrations/public/fedramp/click.py +60 -2
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +199 -130
- regscale/models/integration_models/cisa_kev_data.json +199 -4
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +46 -21
- regscale/models/regscale_models/issue.py +256 -94
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- regscale/utils/threading/threadhandler.py +20 -15
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/RECORD +84 -37
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3731 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +1814 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +350 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1469 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1351 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +750 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +264 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +624 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/test_control_matcher.py +1314 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_issue.py +378 -1
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
1
6
|
import pytest
|
|
2
7
|
|
|
3
8
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
4
|
-
from regscale.models.regscale_models.issue import Issue, IssueSeverity, IssueStatus
|
|
9
|
+
from regscale.models.regscale_models.issue import Issue, IssueSeverity, IssueStatus, OpenIssueDict
|
|
5
10
|
|
|
6
11
|
|
|
7
12
|
def test_bad_issue_instance():
|
|
@@ -34,3 +39,375 @@ def test_good_issue_instance():
|
|
|
34
39
|
dateCreated=get_current_datetime(),
|
|
35
40
|
)
|
|
36
41
|
assert isinstance(issue, Issue)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TestOpenIssuesRefactoredMethods:
|
|
45
|
+
"""Test suite for refactored get_open_issues_ids_by_implementation_id methods"""
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def mock_logger(self):
|
|
49
|
+
"""Create a mock logger"""
|
|
50
|
+
return MagicMock(spec=logging.Logger)
|
|
51
|
+
|
|
52
|
+
@pytest.fixture
|
|
53
|
+
def sample_open_issues(self) -> Dict[int, List[OpenIssueDict]]:
|
|
54
|
+
"""Create sample open issues data"""
|
|
55
|
+
return {
|
|
56
|
+
1: [
|
|
57
|
+
OpenIssueDict(id=101, otherIdentifier="ISS-101", integrationFindingId="FIND-101"),
|
|
58
|
+
OpenIssueDict(id=102, otherIdentifier="ISS-102", integrationFindingId="FIND-102"),
|
|
59
|
+
],
|
|
60
|
+
2: [
|
|
61
|
+
OpenIssueDict(id=103, otherIdentifier="ISS-103", integrationFindingId="FIND-103"),
|
|
62
|
+
],
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@pytest.fixture
|
|
66
|
+
def mock_api_response_single_control(self):
|
|
67
|
+
"""Mock API response for single control support"""
|
|
68
|
+
return {
|
|
69
|
+
"issues": {
|
|
70
|
+
"items": [
|
|
71
|
+
{"id": 101, "controlId": 1, "otherIdentifier": "ISS-101", "integrationFindingId": "FIND-101"},
|
|
72
|
+
{"id": 102, "controlId": 1, "otherIdentifier": "ISS-102", "integrationFindingId": "FIND-102"},
|
|
73
|
+
{"id": 103, "controlId": 2, "otherIdentifier": "ISS-103", "integrationFindingId": "FIND-103"},
|
|
74
|
+
],
|
|
75
|
+
"pageInfo": {"hasNextPage": False},
|
|
76
|
+
"totalCount": 3,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@pytest.fixture
|
|
81
|
+
def mock_api_response_multiple_controls(self):
|
|
82
|
+
"""Mock API response for multiple control support"""
|
|
83
|
+
return {
|
|
84
|
+
"issues": {
|
|
85
|
+
"items": [
|
|
86
|
+
{
|
|
87
|
+
"id": 101,
|
|
88
|
+
"otherIdentifier": "ISS-101",
|
|
89
|
+
"integrationFindingId": "FIND-101",
|
|
90
|
+
"controlImplementations": [{"id": 1}, {"id": 2}],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": 102,
|
|
94
|
+
"otherIdentifier": "ISS-102",
|
|
95
|
+
"integrationFindingId": "FIND-102",
|
|
96
|
+
"controlImplementations": [{"id": 1}],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
"pageInfo": {"hasNextPage": False},
|
|
100
|
+
"totalCount": 2,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
def test_check_cache_with_cache_disabled(self, mock_logger):
|
|
105
|
+
"""Test _check_cache when cache is disabled"""
|
|
106
|
+
with patch.object(Issue, "_is_cache_disabled", return_value=True):
|
|
107
|
+
result = Issue._check_cache(123, mock_logger)
|
|
108
|
+
assert result is None
|
|
109
|
+
mock_logger.info.assert_not_called()
|
|
110
|
+
|
|
111
|
+
def test_check_cache_with_valid_cache(self, mock_logger, sample_open_issues):
|
|
112
|
+
"""Test _check_cache when valid cache exists"""
|
|
113
|
+
with patch.object(Issue, "_is_cache_disabled", return_value=False), patch.object(
|
|
114
|
+
Issue, "_get_from_cache", return_value=sample_open_issues
|
|
115
|
+
):
|
|
116
|
+
result = Issue._check_cache(123, mock_logger)
|
|
117
|
+
assert result == sample_open_issues
|
|
118
|
+
mock_logger.info.assert_called_once()
|
|
119
|
+
|
|
120
|
+
def test_check_cache_with_no_cache(self, mock_logger):
|
|
121
|
+
"""Test _check_cache when no cache exists"""
|
|
122
|
+
with patch.object(Issue, "_is_cache_disabled", return_value=False), patch.object(
|
|
123
|
+
Issue, "_get_from_cache", return_value=None
|
|
124
|
+
):
|
|
125
|
+
result = Issue._check_cache(123, mock_logger)
|
|
126
|
+
assert result is None
|
|
127
|
+
|
|
128
|
+
def test_get_query_fields_with_multiple_controls(self):
|
|
129
|
+
"""Test _get_query_fields for multiple control support"""
|
|
130
|
+
result = Issue._get_query_fields(supports_multiple_controls=True)
|
|
131
|
+
assert "controlImplementations" in result
|
|
132
|
+
assert "id" in result
|
|
133
|
+
assert "otherIdentifier" in result
|
|
134
|
+
assert "integrationFindingId" in result
|
|
135
|
+
|
|
136
|
+
def test_get_query_fields_with_single_control(self):
|
|
137
|
+
"""Test _get_query_fields for single control support"""
|
|
138
|
+
result = Issue._get_query_fields(supports_multiple_controls=False)
|
|
139
|
+
assert "controlId" in result
|
|
140
|
+
assert "id" in result
|
|
141
|
+
assert "otherIdentifier" in result
|
|
142
|
+
assert "integrationFindingId" in result
|
|
143
|
+
assert "controlImplementations" not in result
|
|
144
|
+
|
|
145
|
+
def test_build_query_for_security_plan(self):
|
|
146
|
+
"""Test _build_query for security plan"""
|
|
147
|
+
with patch.object(Issue, "get_module_string", return_value="issues"):
|
|
148
|
+
query = Issue._build_query(plan_id=123, is_component=False, skip=0, take=50, fields="id, title")
|
|
149
|
+
assert "securityPlanId" in query
|
|
150
|
+
assert "eq: 123" in query
|
|
151
|
+
assert 'status: {eq: "Open"}' in query
|
|
152
|
+
assert "componentId" not in query
|
|
153
|
+
|
|
154
|
+
def test_build_query_for_component(self):
|
|
155
|
+
"""Test _build_query for component"""
|
|
156
|
+
with patch.object(Issue, "get_module_string", return_value="issues"):
|
|
157
|
+
query = Issue._build_query(plan_id=456, is_component=True, skip=10, take=25, fields="id, title")
|
|
158
|
+
assert "componentId" in query
|
|
159
|
+
assert "eq: 456" in query
|
|
160
|
+
assert 'status: {eq: "Open"}' in query
|
|
161
|
+
assert "securityPlanId" not in query
|
|
162
|
+
|
|
163
|
+
def test_log_progress_with_large_dataset(self, mock_logger):
|
|
164
|
+
"""Test _log_progress logs for large datasets"""
|
|
165
|
+
Issue._log_progress(skip=100, take=50, items_count=50, total_count=2000, logger=mock_logger)
|
|
166
|
+
mock_logger.info.assert_called_once()
|
|
167
|
+
assert "Processing batch 3" in mock_logger.info.call_args[0][0]
|
|
168
|
+
|
|
169
|
+
def test_log_progress_with_small_dataset(self, mock_logger):
|
|
170
|
+
"""Test _log_progress does not log for small datasets"""
|
|
171
|
+
Issue._log_progress(skip=0, take=50, items_count=50, total_count=100, logger=mock_logger)
|
|
172
|
+
mock_logger.info.assert_not_called()
|
|
173
|
+
|
|
174
|
+
def test_add_issue_to_single_control(self):
|
|
175
|
+
"""Test _add_issue_to_single_control"""
|
|
176
|
+
control_issues = defaultdict(list)
|
|
177
|
+
issue_dict = OpenIssueDict(id=101, otherIdentifier="ISS-101", integrationFindingId="FIND-101")
|
|
178
|
+
item = {"id": 101, "controlId": 5}
|
|
179
|
+
|
|
180
|
+
Issue._add_issue_to_single_control(item, issue_dict, control_issues)
|
|
181
|
+
|
|
182
|
+
assert len(control_issues) == 1
|
|
183
|
+
assert 5 in control_issues
|
|
184
|
+
assert control_issues[5][0] == issue_dict
|
|
185
|
+
|
|
186
|
+
def test_add_issue_to_single_control_no_control_id(self):
|
|
187
|
+
"""Test _add_issue_to_single_control when no controlId"""
|
|
188
|
+
control_issues = defaultdict(list)
|
|
189
|
+
issue_dict = OpenIssueDict(id=101, otherIdentifier="ISS-101", integrationFindingId="FIND-101")
|
|
190
|
+
item = {"id": 101}
|
|
191
|
+
|
|
192
|
+
Issue._add_issue_to_single_control(item, issue_dict, control_issues)
|
|
193
|
+
|
|
194
|
+
assert len(control_issues) == 0
|
|
195
|
+
|
|
196
|
+
def test_add_issue_to_multiple_controls(self):
|
|
197
|
+
"""Test _add_issue_to_multiple_controls"""
|
|
198
|
+
control_issues = defaultdict(list)
|
|
199
|
+
issue_dict = OpenIssueDict(id=101, otherIdentifier="ISS-101", integrationFindingId="FIND-101")
|
|
200
|
+
item = {"id": 101, "controlImplementations": [{"id": 1}, {"id": 2}, {"id": 3}]}
|
|
201
|
+
|
|
202
|
+
Issue._add_issue_to_multiple_controls(item, issue_dict, control_issues)
|
|
203
|
+
|
|
204
|
+
assert len(control_issues) == 3
|
|
205
|
+
assert 1 in control_issues
|
|
206
|
+
assert 2 in control_issues
|
|
207
|
+
assert 3 in control_issues
|
|
208
|
+
assert control_issues[1][0] == issue_dict
|
|
209
|
+
|
|
210
|
+
def test_add_issue_to_multiple_controls_no_implementations(self):
|
|
211
|
+
"""Test _add_issue_to_multiple_controls when no implementations"""
|
|
212
|
+
control_issues = defaultdict(list)
|
|
213
|
+
issue_dict = OpenIssueDict(id=101, otherIdentifier="ISS-101", integrationFindingId="FIND-101")
|
|
214
|
+
item = {"id": 101}
|
|
215
|
+
|
|
216
|
+
Issue._add_issue_to_multiple_controls(item, issue_dict, control_issues)
|
|
217
|
+
|
|
218
|
+
assert len(control_issues) == 0
|
|
219
|
+
|
|
220
|
+
def test_process_issue_items_single_control(self):
|
|
221
|
+
"""Test _process_issue_items with single control support"""
|
|
222
|
+
items = [
|
|
223
|
+
{"id": 101, "controlId": 1, "otherIdentifier": "ISS-101", "integrationFindingId": "FIND-101"},
|
|
224
|
+
{"id": 102, "controlId": 2, "otherIdentifier": "ISS-102", "integrationFindingId": "FIND-102"},
|
|
225
|
+
]
|
|
226
|
+
control_issues = defaultdict(list)
|
|
227
|
+
|
|
228
|
+
Issue._process_issue_items(items, supports_multiple_controls=False, control_issues=control_issues)
|
|
229
|
+
|
|
230
|
+
assert len(control_issues) == 2
|
|
231
|
+
assert len(control_issues[1]) == 1
|
|
232
|
+
assert len(control_issues[2]) == 1
|
|
233
|
+
assert control_issues[1][0]["id"] == 101
|
|
234
|
+
assert control_issues[2][0]["id"] == 102
|
|
235
|
+
|
|
236
|
+
def test_process_issue_items_multiple_controls(self):
|
|
237
|
+
"""Test _process_issue_items with multiple control support"""
|
|
238
|
+
items = [
|
|
239
|
+
{
|
|
240
|
+
"id": 101,
|
|
241
|
+
"otherIdentifier": "ISS-101",
|
|
242
|
+
"integrationFindingId": "FIND-101",
|
|
243
|
+
"controlImplementations": [{"id": 1}, {"id": 2}],
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"id": 102,
|
|
247
|
+
"otherIdentifier": "ISS-102",
|
|
248
|
+
"integrationFindingId": "FIND-102",
|
|
249
|
+
"controlImplementations": [{"id": 1}],
|
|
250
|
+
},
|
|
251
|
+
]
|
|
252
|
+
control_issues = defaultdict(list)
|
|
253
|
+
|
|
254
|
+
Issue._process_issue_items(items, supports_multiple_controls=True, control_issues=control_issues)
|
|
255
|
+
|
|
256
|
+
assert len(control_issues) == 2
|
|
257
|
+
assert len(control_issues[1]) == 2
|
|
258
|
+
assert len(control_issues[2]) == 1
|
|
259
|
+
assert control_issues[1][0]["id"] == 101
|
|
260
|
+
assert control_issues[1][1]["id"] == 102
|
|
261
|
+
|
|
262
|
+
def test_log_completion(self, mock_logger):
|
|
263
|
+
"""Test _log_completion logs completion message"""
|
|
264
|
+
import time
|
|
265
|
+
|
|
266
|
+
start_time = time.time() - 5.5 # 5.5 seconds ago
|
|
267
|
+
|
|
268
|
+
Issue._log_completion(
|
|
269
|
+
plan_id=123, total_fetched=150, control_count=25, start_time=start_time, logger=mock_logger
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
mock_logger.info.assert_called_once()
|
|
273
|
+
log_message = mock_logger.info.call_args[0][0]
|
|
274
|
+
assert "150 open issue(s)" in log_message
|
|
275
|
+
assert "25 control(s)" in log_message
|
|
276
|
+
assert "123" in log_message
|
|
277
|
+
assert "5.5" in log_message
|
|
278
|
+
|
|
279
|
+
@patch.object(Issue, "_get_api_handler")
|
|
280
|
+
@patch.object(Issue, "get_module_string", return_value="issues")
|
|
281
|
+
@patch.object(Issue, "is_multiple_controls_supported", return_value=False)
|
|
282
|
+
def test_paginate_and_process_issues_single_page(
|
|
283
|
+
self, mock_supports, mock_module_string, mock_api_handler, mock_logger
|
|
284
|
+
):
|
|
285
|
+
"""Test _paginate_and_process_issues with single page response"""
|
|
286
|
+
mock_api = MagicMock()
|
|
287
|
+
mock_api.graph.return_value = {
|
|
288
|
+
"issues": {
|
|
289
|
+
"items": [
|
|
290
|
+
{"id": 101, "controlId": 1, "otherIdentifier": "ISS-101", "integrationFindingId": "FIND-101"},
|
|
291
|
+
],
|
|
292
|
+
"pageInfo": {"hasNextPage": False},
|
|
293
|
+
"totalCount": 1,
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
mock_api_handler.return_value = mock_api
|
|
297
|
+
control_issues = defaultdict(list)
|
|
298
|
+
|
|
299
|
+
total_fetched = Issue._paginate_and_process_issues(
|
|
300
|
+
plan_id=123, is_component=False, control_issues=control_issues, logger=mock_logger
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
assert total_fetched == 1
|
|
304
|
+
assert len(control_issues) == 1
|
|
305
|
+
assert len(control_issues[1]) == 1
|
|
306
|
+
|
|
307
|
+
@patch.object(Issue, "_get_api_handler")
|
|
308
|
+
@patch.object(Issue, "get_module_string", return_value="issues")
|
|
309
|
+
@patch.object(Issue, "is_multiple_controls_supported", return_value=False)
|
|
310
|
+
def test_paginate_and_process_issues_multiple_pages(
|
|
311
|
+
self, mock_supports, mock_module_string, mock_api_handler, mock_logger
|
|
312
|
+
):
|
|
313
|
+
"""Test _paginate_and_process_issues with multiple pages"""
|
|
314
|
+
mock_api = MagicMock()
|
|
315
|
+
# Simulate two pages
|
|
316
|
+
mock_api.graph.side_effect = [
|
|
317
|
+
{
|
|
318
|
+
"issues": {
|
|
319
|
+
"items": [
|
|
320
|
+
{"id": 101, "controlId": 1, "otherIdentifier": "ISS-101", "integrationFindingId": "FIND-101"},
|
|
321
|
+
],
|
|
322
|
+
"pageInfo": {"hasNextPage": True},
|
|
323
|
+
"totalCount": 2,
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"issues": {
|
|
328
|
+
"items": [
|
|
329
|
+
{"id": 102, "controlId": 2, "otherIdentifier": "ISS-102", "integrationFindingId": "FIND-102"},
|
|
330
|
+
],
|
|
331
|
+
"pageInfo": {"hasNextPage": False},
|
|
332
|
+
"totalCount": 2,
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
]
|
|
336
|
+
mock_api_handler.return_value = mock_api
|
|
337
|
+
control_issues = defaultdict(list)
|
|
338
|
+
|
|
339
|
+
total_fetched = Issue._paginate_and_process_issues(
|
|
340
|
+
plan_id=123, is_component=False, control_issues=control_issues, logger=mock_logger
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
assert total_fetched == 2
|
|
344
|
+
assert len(control_issues) == 2
|
|
345
|
+
assert mock_api.graph.call_count == 2
|
|
346
|
+
|
|
347
|
+
@patch.object(Issue, "_paginate_and_process_issues")
|
|
348
|
+
def test_fetch_open_issues_from_api_success(self, mock_paginate, mock_logger):
|
|
349
|
+
"""Test _fetch_open_issues_from_api successful execution"""
|
|
350
|
+
mock_paginate.return_value = 150
|
|
351
|
+
control_issues = Issue._fetch_open_issues_from_api(plan_id=123, is_component=False, logger=mock_logger)
|
|
352
|
+
|
|
353
|
+
assert isinstance(control_issues, defaultdict)
|
|
354
|
+
mock_logger.info.assert_called()
|
|
355
|
+
|
|
356
|
+
@patch.object(Issue, "_paginate_and_process_issues")
|
|
357
|
+
def test_fetch_open_issues_from_api_exception(self, mock_paginate, mock_logger):
|
|
358
|
+
"""Test _fetch_open_issues_from_api handles exceptions"""
|
|
359
|
+
mock_paginate.side_effect = Exception("API Error")
|
|
360
|
+
|
|
361
|
+
control_issues = Issue._fetch_open_issues_from_api(plan_id=123, is_component=False, logger=mock_logger)
|
|
362
|
+
|
|
363
|
+
assert isinstance(control_issues, defaultdict)
|
|
364
|
+
assert len(control_issues) == 0
|
|
365
|
+
mock_logger.error.assert_called_once()
|
|
366
|
+
|
|
367
|
+
@patch.object(Issue, "_check_cache")
|
|
368
|
+
@patch.object(Issue, "_fetch_open_issues_from_api")
|
|
369
|
+
@patch.object(Issue, "_is_cache_disabled", return_value=False)
|
|
370
|
+
@patch.object(Issue, "_cache_data")
|
|
371
|
+
def test_get_open_issues_ids_by_implementation_id_with_cache(
|
|
372
|
+
self, mock_cache_data, mock_disabled, mock_fetch, mock_check_cache, sample_open_issues
|
|
373
|
+
):
|
|
374
|
+
"""Test get_open_issues_ids_by_implementation_id returns cached data"""
|
|
375
|
+
mock_check_cache.return_value = sample_open_issues
|
|
376
|
+
|
|
377
|
+
result = Issue.get_open_issues_ids_by_implementation_id(plan_id=123)
|
|
378
|
+
|
|
379
|
+
assert result == sample_open_issues
|
|
380
|
+
mock_fetch.assert_not_called()
|
|
381
|
+
mock_cache_data.assert_not_called()
|
|
382
|
+
|
|
383
|
+
@patch.object(Issue, "_check_cache", return_value=None)
|
|
384
|
+
@patch.object(Issue, "_fetch_open_issues_from_api")
|
|
385
|
+
@patch.object(Issue, "_is_cache_disabled", return_value=False)
|
|
386
|
+
@patch.object(Issue, "_cache_data")
|
|
387
|
+
def test_get_open_issues_ids_by_implementation_id_without_cache(
|
|
388
|
+
self, mock_cache_data, mock_disabled, mock_fetch, mock_check_cache, sample_open_issues
|
|
389
|
+
):
|
|
390
|
+
"""Test get_open_issues_ids_by_implementation_id fetches and caches data"""
|
|
391
|
+
mock_fetch.return_value = sample_open_issues
|
|
392
|
+
|
|
393
|
+
result = Issue.get_open_issues_ids_by_implementation_id(plan_id=123)
|
|
394
|
+
|
|
395
|
+
assert result == sample_open_issues
|
|
396
|
+
mock_fetch.assert_called_once()
|
|
397
|
+
mock_cache_data.assert_called_once_with(123, sample_open_issues)
|
|
398
|
+
|
|
399
|
+
@patch.object(Issue, "_check_cache", return_value=None)
|
|
400
|
+
@patch.object(Issue, "_fetch_open_issues_from_api")
|
|
401
|
+
@patch.object(Issue, "_is_cache_disabled", return_value=True)
|
|
402
|
+
@patch.object(Issue, "_cache_data")
|
|
403
|
+
def test_get_open_issues_ids_by_implementation_id_cache_disabled(
|
|
404
|
+
self, mock_cache_data, mock_disabled, mock_fetch, mock_check_cache, sample_open_issues
|
|
405
|
+
):
|
|
406
|
+
"""Test get_open_issues_ids_by_implementation_id with cache disabled"""
|
|
407
|
+
mock_fetch.return_value = sample_open_issues
|
|
408
|
+
|
|
409
|
+
result = Issue.get_open_issues_ids_by_implementation_id(plan_id=123)
|
|
410
|
+
|
|
411
|
+
assert result == sample_open_issues
|
|
412
|
+
mock_fetch.assert_called_once()
|
|
413
|
+
mock_cache_data.assert_not_called()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|