|
2 | 2 | from os.path import normpath |
3 | 3 | from unittest.mock import MagicMock, Mock, patch |
4 | 4 |
|
| 5 | +import pytest |
| 6 | + |
5 | 7 | from cycode.cli import consts |
6 | | -from cycode.cli.apps.scan.code_scanner import scan_disk_files |
| 8 | +from cycode.cli.apps.scan.code_scanner import _perform_scan, scan_disk_files, scan_documents |
| 9 | +from cycode.cli.exceptions import custom_exceptions |
7 | 10 | from cycode.cli.files_collector.file_excluder import _is_file_relevant_for_sca_scan |
8 | 11 | from cycode.cli.files_collector.path_documents import _generate_document |
9 | 12 | from cycode.cli.models import Document |
@@ -162,3 +165,75 @@ def test_entrypoint_cycode_not_added_for_single_file( |
162 | 165 | assert len(entrypoint_docs) == 0 |
163 | 166 | # Verify only the original documents are present |
164 | 167 | assert len(documents_passed) == len(mock_documents) |
| 168 | + |
| 169 | + |
| 170 | +@pytest.mark.parametrize( |
| 171 | + ('scan_type', 'command_scan_type', 'sync_option', 'expect_presigned'), |
| 172 | + [ |
| 173 | + # SAST keeps uploading directly to S3 via a presigned URL (regression guard for the new sync gate). |
| 174 | + (consts.SAST_SCAN_TYPE, 'path', False, True), |
| 175 | + # Async secret scans now upload as a single file directly to S3 via a presigned URL. |
| 176 | + (consts.SECRET_SCAN_TYPE, 'path', False, True), |
| 177 | + # A --sync secret scan must stay on the batched inline path and never build one giant zip. |
| 178 | + (consts.SECRET_SCAN_TYPE, 'path', True, False), |
| 179 | + ], |
| 180 | +) |
| 181 | +@patch('cycode.cli.apps.scan.code_scanner.print_local_scan_results') |
| 182 | +@patch('cycode.cli.apps.scan.code_scanner.set_issue_detected_by_scan_results') |
| 183 | +@patch('cycode.cli.apps.scan.code_scanner.try_set_aggregation_report_url_if_needed') |
| 184 | +@patch('cycode.cli.apps.scan.code_scanner.run_parallel_batched_scan') |
| 185 | +@patch('cycode.cli.apps.scan.code_scanner._run_presigned_upload_scan') |
| 186 | +def test_scan_documents_routes_upload_by_scan_type_and_sync( |
| 187 | + mock_presigned_upload: Mock, |
| 188 | + mock_batched_scan: Mock, |
| 189 | + mock_aggregation: Mock, |
| 190 | + mock_set_issue: Mock, |
| 191 | + mock_print: Mock, |
| 192 | + scan_type: str, |
| 193 | + command_scan_type: str, |
| 194 | + sync_option: bool, |
| 195 | + expect_presigned: bool, |
| 196 | +) -> None: |
| 197 | + mock_presigned_upload.return_value = ([], []) |
| 198 | + mock_batched_scan.return_value = ([], []) |
| 199 | + |
| 200 | + mock_ctx = MagicMock() |
| 201 | + mock_ctx.info_name = command_scan_type |
| 202 | + mock_ctx.obj = { |
| 203 | + 'scan_type': scan_type, |
| 204 | + 'progress_bar': MagicMock(), |
| 205 | + 'console_printer': MagicMock(), |
| 206 | + 'client': MagicMock(), |
| 207 | + 'severity_threshold': None, |
| 208 | + 'sync': sync_option, |
| 209 | + } |
| 210 | + documents = [Document('/repo/file.py', 'content', is_git_diff_format=False)] |
| 211 | + |
| 212 | + scan_documents(mock_ctx, documents, {}) |
| 213 | + |
| 214 | + assert mock_presigned_upload.called is expect_presigned |
| 215 | + assert mock_batched_scan.called is (not expect_presigned) |
| 216 | + |
| 217 | + |
| 218 | +@patch('cycode.cli.apps.scan.code_scanner._perform_scan_async') |
| 219 | +@patch('cycode.cli.apps.scan.code_scanner._perform_scan_v4_async') |
| 220 | +def test_perform_scan_falls_back_to_api_when_presigned_upload_raises_wrapped_error( |
| 221 | + mock_v4_async: Mock, mock_async: Mock |
| 222 | +) -> None: |
| 223 | + # RequestConnectionError is a CycodeError, not a requests.RequestException — the fallback must still catch it. |
| 224 | + mock_v4_async.side_effect = custom_exceptions.RequestConnectionError |
| 225 | + fallback_result = object() |
| 226 | + mock_async.return_value = fallback_result |
| 227 | + |
| 228 | + result = _perform_scan( |
| 229 | + cycode_client=MagicMock(), |
| 230 | + zipped_documents=MagicMock(), |
| 231 | + scan_type=consts.SAST_SCAN_TYPE, |
| 232 | + is_git_diff=False, |
| 233 | + is_commit_range=False, |
| 234 | + scan_parameters={}, |
| 235 | + ) |
| 236 | + |
| 237 | + assert result is fallback_result |
| 238 | + mock_v4_async.assert_called_once() |
| 239 | + mock_async.assert_called_once() |
0 commit comments