|
1 | 1 | """Tests for AI guardrails handlers.""" |
2 | 2 |
|
| 3 | +import os |
3 | 4 | from typing import Any |
4 | 5 | from unittest.mock import MagicMock, patch |
5 | 6 |
|
|
8 | 9 |
|
9 | 10 | from cycode.cli.apps.ai_guardrails.ides.base import DecisionAction, HookDecision |
10 | 11 | from cycode.cli.apps.ai_guardrails.scan.handlers import ( |
| 12 | + _perform_scan, |
| 13 | + _scan_path_for_secrets, |
11 | 14 | handle_before_mcp_execution, |
12 | 15 | handle_before_read_file, |
13 | 16 | handle_before_submit_prompt, |
14 | 17 | ) |
15 | 18 | from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload |
16 | 19 | from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType, AIHookOutcome, BlockReason |
| 20 | +from cycode.cli.models import Document, LocalScanResult |
17 | 21 |
|
18 | 22 |
|
19 | 23 | @pytest.fixture |
@@ -357,15 +361,56 @@ def test_handle_before_read_file_sensitive_path_scan_disabled_warns( |
357 | 361 |
|
358 | 362 | def test_scan_path_for_secrets_directory(mock_ctx: MagicMock, default_policy: dict[str, Any], fs: Any) -> None: |
359 | 363 | """Test that _scan_path_for_secrets returns (None, None) for directories.""" |
360 | | - from cycode.cli.apps.ai_guardrails.scan.handlers import _scan_path_for_secrets |
361 | | - |
362 | 364 | fs.create_dir('/path/to/some_directory') |
363 | 365 |
|
364 | 366 | result = _scan_path_for_secrets(mock_ctx, '/path/to/some_directory', default_policy) |
365 | 367 |
|
366 | 368 | assert result == (None, None) |
367 | 369 |
|
368 | 370 |
|
| 371 | +@patch('cycode.cli.apps.ai_guardrails.scan.handlers._perform_scan') |
| 372 | +def test_scan_path_for_secrets_skips_path_configured_in_exclusions( |
| 373 | + mock_perform_scan: MagicMock, mock_ctx: MagicMock, default_policy: dict[str, Any], fs: Any |
| 374 | +) -> None: |
| 375 | + """Test that a path ignored via `cycode ignore --by-path` is not scanned.""" |
| 376 | + # `cycode ignore --by-path` stores absolute paths; on Windows that includes the drive prefix |
| 377 | + excluded_dir = os.path.abspath(os.path.join(os.sep, 'project', 'secrets')) |
| 378 | + file_path = os.path.join(excluded_dir, 'creds.env') |
| 379 | + fs.create_file(file_path, contents='password=hunter2') |
| 380 | + mock_perform_scan.return_value = ('Cycode found 1 violations', 'scan-id-123') |
| 381 | + |
| 382 | + with patch( |
| 383 | + 'cycode.cli.files_collector.file_excluder.configuration_manager.get_exclusions_by_scan_type', |
| 384 | + return_value={'paths': [excluded_dir]}, |
| 385 | + ): |
| 386 | + result = _scan_path_for_secrets(mock_ctx, file_path, default_policy) |
| 387 | + |
| 388 | + assert result == (None, None) |
| 389 | + mock_perform_scan.assert_not_called() |
| 390 | + |
| 391 | + |
| 392 | +def test_perform_scan_no_violation_when_all_detections_excluded(mock_ctx: MagicMock) -> None: |
| 393 | + """Test that detections filtered out by ignore rules do not produce a violation.""" |
| 394 | + local_scan_result = LocalScanResult( |
| 395 | + scan_id='scan-id-123', |
| 396 | + report_url=None, |
| 397 | + document_detections=[], |
| 398 | + issue_detected=False, |
| 399 | + detections_count=1, |
| 400 | + relevant_detections_count=0, |
| 401 | + ) |
| 402 | + document = Document(path='prompt-content.txt', content='some content', is_git_diff_format=False) |
| 403 | + |
| 404 | + with patch( |
| 405 | + 'cycode.cli.apps.ai_guardrails.scan.handlers._get_scan_documents_thread_func', |
| 406 | + return_value=lambda batch: ('scan-id-123', None, local_scan_result), |
| 407 | + ): |
| 408 | + violation_summary, scan_id = _perform_scan(mock_ctx, [document], {}, timeout_seconds=5.0) |
| 409 | + |
| 410 | + assert violation_summary is None |
| 411 | + assert scan_id == 'scan-id-123' |
| 412 | + |
| 413 | + |
369 | 414 | # Tests for handle_before_mcp_execution |
370 | 415 |
|
371 | 416 |
|
|
0 commit comments