97 lines
4.0 KiB
Python
97 lines
4.0 KiB
Python
import contextlib
|
|
import io
|
|
import json
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://user:pass@localhost:5432/ai_ict_test")
|
|
|
|
from scripts import run_script_static_checks
|
|
|
|
|
|
class ScriptStaticChecksTests(unittest.TestCase):
|
|
def test_detects_ambient_env_fallback(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
script_dir = Path(tmpdir)
|
|
(script_dir / "bad.py").write_text("env = env or os.environ\n", encoding="utf-8")
|
|
|
|
result = run_script_static_checks.run_script_static_checks(script_dir=script_dir)
|
|
|
|
self.assertFalse(result["passed"])
|
|
self.assertEqual(result["findings"][0]["file"], "bad.py")
|
|
self.assertEqual(result["findings"][0]["reason"], "empty_env_must_not_fall_back_to_ambient_environment")
|
|
|
|
def test_passes_clean_scripts(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
script_dir = Path(tmpdir)
|
|
(script_dir / "ok.py").write_text("env = os.environ if env is None else env\n", encoding="utf-8")
|
|
|
|
result = run_script_static_checks.run_script_static_checks(script_dir=script_dir)
|
|
|
|
self.assertTrue(result["passed"])
|
|
self.assertEqual(result["script_count"], 1)
|
|
|
|
def test_detects_python_syntax_errors(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
script_dir = Path(tmpdir)
|
|
(script_dir / "broken.py").write_text("def nope(:\n", encoding="utf-8")
|
|
|
|
result = run_script_static_checks.run_script_static_checks(script_dir=script_dir)
|
|
|
|
self.assertFalse(result["passed"])
|
|
self.assertEqual(result["findings"][0]["reason"], "script_python_syntax_error")
|
|
|
|
def test_detects_missing_system_exit_for_automation_entrypoints(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
script_dir = Path(tmpdir)
|
|
(script_dir / "run_pipeline.py").write_text(
|
|
"def main() -> int:\n return 2\n\nif __name__ == '__main__':\n main()\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
result = run_script_static_checks.run_script_static_checks(script_dir=script_dir)
|
|
|
|
self.assertFalse(result["passed"])
|
|
self.assertEqual(result["findings"][0]["reason"], "automation_entrypoint_must_preserve_exit_code")
|
|
|
|
def test_detects_missing_system_exit_for_any_script_with_main(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
script_dir = Path(tmpdir)
|
|
(script_dir / "ad_hoc.py").write_text(
|
|
"def main() -> int:\n return 0\n\nif __name__ == '__main__':\n main()\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
result = run_script_static_checks.run_script_static_checks(script_dir=script_dir)
|
|
|
|
self.assertFalse(result["passed"])
|
|
self.assertEqual(result["findings"][0]["file"], "ad_hoc.py")
|
|
self.assertEqual(result["findings"][0]["reason"], "automation_entrypoint_must_preserve_exit_code")
|
|
|
|
def test_detects_python39_incompatible_optional_annotation(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
script_dir = Path(tmpdir)
|
|
(script_dir / "bad.py").write_text("def f(value: str | None = None):\n return value\n", encoding="utf-8")
|
|
|
|
result = run_script_static_checks.run_script_static_checks(script_dir=script_dir)
|
|
|
|
self.assertFalse(result["passed"])
|
|
self.assertEqual(result["findings"][0]["reason"], "python39_incompatible_pep604_optional_annotation")
|
|
|
|
def test_json_output_is_machine_readable(self) -> None:
|
|
output = io.StringIO()
|
|
|
|
with contextlib.redirect_stdout(output):
|
|
exit_code = run_script_static_checks.main(env={"SCRIPT_CHECKS_OUTPUT_JSON": "1"})
|
|
|
|
summary = json.loads(output.getvalue())
|
|
self.assertEqual(exit_code, 0)
|
|
self.assertTrue(summary["passed"])
|
|
self.assertGreaterEqual(summary["script_count"], 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|