|
| 1 | +import os |
1 | 2 | import subprocess |
2 | 3 | from pathlib import Path |
3 | 4 |
|
4 | 5 | REPO_ROOT = Path(__file__).resolve().parents[2] |
5 | 6 | RUN_ALL_CHECKS = REPO_ROOT / "scripts" / "ci" / "run-all-checks.sh" |
6 | 7 | HEALTH_CHECK = REPO_ROOT / "scripts" / "ci" / "infrastructure" / "health-check.sh" |
| 8 | +BANDIT_SCAN = REPO_ROOT / "scripts" / "ci" / "security" / "bandit-scan.sh" |
| 9 | +NPM_AUDIT = REPO_ROOT / "scripts" / "ci" / "security" / "npm-audit.sh" |
| 10 | +TEST_EXECUTION_TIME = REPO_ROOT / "scripts" / "ci" / "testing" / "test-execution-time.sh" |
7 | 11 |
|
8 | 12 |
|
9 | | -def _run_script(script_path, *args): |
| 13 | +def _run_script(script_path, *args, env=None, timeout=60): |
10 | 14 | return subprocess.run( |
11 | 15 | ["bash", str(script_path), *args], |
12 | 16 | cwd=REPO_ROOT, |
13 | 17 | stdout=subprocess.PIPE, |
14 | 18 | stderr=subprocess.PIPE, |
15 | 19 | text=True, |
| 20 | + env=env, |
| 21 | + timeout=timeout, |
16 | 22 | ) |
17 | 23 |
|
18 | 24 |
|
19 | | -def test_run_all_checks_reports_summary_even_on_failure(): |
| 25 | +def test_run_all_checks_reports_summary_with_skips(): |
20 | 26 | result = _run_script(RUN_ALL_CHECKS) |
21 | 27 |
|
22 | | - assert result.returncode != 0, "Expected the aggregated checks to fail in the default dev environment" |
23 | | - combined_output = f"{result.stdout}\\n{result.stderr}" |
| 28 | + assert result.returncode == 0, "Aggregated checks should degrade to success with skips locally" |
| 29 | + combined_output = f"{result.stdout}\n{result.stderr}" |
24 | 30 | assert "FINAL CI/CD REPORT" in combined_output |
| 31 | + assert "Skipped:" in combined_output |
| 32 | + assert "[SKIP]" in combined_output or "Skipped: 0" in combined_output |
25 | 33 |
|
26 | 34 |
|
27 | | -def test_health_check_surfaces_underlying_error_details(): |
| 35 | +def test_health_check_degrades_gracefully_when_django_missing(): |
28 | 36 | result = _run_script(HEALTH_CHECK) |
29 | 37 |
|
30 | | - assert result.returncode != 0, "The health check should fail when dependencies are missing" |
31 | | - combined_output = f"{result.stdout}\\n{result.stderr}" |
32 | | - assert "ModuleNotFoundError" in combined_output or "No module named" in combined_output |
| 38 | + assert result.returncode in (0, 2), "Health check should pass or skip when Django is unavailable" |
| 39 | + combined_output = f"{result.stdout}\n{result.stderr}" |
| 40 | + assert "Skipping Django checks" in combined_output |
| 41 | + |
| 42 | + |
| 43 | +def test_run_all_checks_sets_strict_shell_flags(): |
| 44 | + contents = RUN_ALL_CHECKS.read_text() |
| 45 | + |
| 46 | + assert "set -euo pipefail" in contents, "run-all-checks.sh must opt into strict shell error handling" |
| 47 | + |
| 48 | + |
| 49 | +def test_bandit_scan_skips_quickly_when_cli_missing(): |
| 50 | + result = _run_script(BANDIT_SCAN, timeout=5) |
| 51 | + |
| 52 | + assert result.returncode == 2, "Bandit scan should degrade to SKIP without attempting long installations" |
| 53 | + combined_output = f"{result.stdout}\n{result.stderr}" |
| 54 | + assert "Bandit CLI not detected" in combined_output or "Bandit not installed" in combined_output |
| 55 | + |
| 56 | + |
| 57 | +def test_npm_audit_detects_ui_workspace_and_skips_without_npm(): |
| 58 | + env = os.environ.copy() |
| 59 | + env["PATH"] = ":".join( |
| 60 | + [ |
| 61 | + "/usr/local/sbin", |
| 62 | + "/usr/local/bin", |
| 63 | + "/usr/sbin", |
| 64 | + "/usr/bin", |
| 65 | + "/sbin", |
| 66 | + "/bin", |
| 67 | + ] |
| 68 | + ) |
| 69 | + |
| 70 | + result = _run_script(NPM_AUDIT, env=env, timeout=30) |
| 71 | + |
| 72 | + assert result.returncode == 2, "NPM audit should skip when npm CLI is not available" |
| 73 | + combined_output = f"{result.stdout}\n{result.stderr}" |
| 74 | + assert "Found package.json in ui" in combined_output |
| 75 | + assert "npm CLI not available" in combined_output or "npm not installed" in combined_output |
| 76 | + |
| 77 | + |
| 78 | +def test_test_execution_time_skips_without_django(): |
| 79 | + result = _run_script(TEST_EXECUTION_TIME, timeout=20) |
| 80 | + |
| 81 | + assert result.returncode == 2, "Test execution validation should skip when Django is absent" |
| 82 | + combined_output = f"{result.stdout}\n{result.stderr}" |
| 83 | + assert "Skipping test execution time validation" in combined_output |
0 commit comments