diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..7a4f745 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,123 @@ +"""Tests for CLI interface.""" + +import pytest +from click.testing import CliRunner +from pathlib import Path +import tempfile +import os + +from src.cli import cli, analyze, fix, rules, languages + + +class TestCLI: + """Tests for CLI commands.""" + + def test_cli_group_exists(self): + assert cli is not None + + def test_rules_command(self): + runner = CliRunner() + result = runner.invoke(rules) + assert result.exit_code == 0 + assert "security" in result.output.lower() or "Rule ID" in result.output + + def test_languages_command(self): + runner = CliRunner() + result = runner.invoke(languages) + assert result.exit_code == 0 + assert "python" in result.output.lower() + assert "javascript" in result.output.lower() + assert "typescript" in result.output.lower() + + def test_analyze_nonexistent_file(self): + runner = CliRunner() + result = runner.invoke(analyze, ["/nonexistent/path"]) + assert result.exit_code != 0 + + def test_analyze_file_with_issues(self): + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write('result = eval("os.system(\'rm -rf /\')")\n') + f.write('password = "secret123"\n') + temp_path = f.name + + try: + runner = CliRunner() + result = runner.invoke(analyze, [temp_path, "--json"]) + assert result.exit_code == 0 + assert "password" in result.output or "eval" in result.output + finally: + os.unlink(temp_path) + + def test_analyze_file_without_issues(self): + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write("def hello():\n print('Hello, World!')\n") + temp_path = f.name + + try: + runner = CliRunner() + result = runner.invoke(analyze, [temp_path]) + assert result.exit_code == 0 + finally: + os.unlink(temp_path) + + def test_analyze_with_fix_flag(self): + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write("result = eval('os.system(\"ls\")')\n") + temp_path = f.name + + try: + runner = CliRunner() + result = runner.invoke(analyze, [temp_path, "--fix"]) + assert result.exit_code == 0 + assert "# eval(" in result.output or "Fixed" in result.output + finally: + backup_path = temp_path + ".bak" + if os.path.exists(backup_path): + os.unlink(backup_path) + + def test_analyze_directory(self): + with tempfile.TemporaryDirectory() as tmpdir: + subdir = Path(tmpdir) / "subdir" + subdir.mkdir() + + test_file = subdir / "test.py" + test_file.write_text("result = eval('test')\n") + + runner = CliRunner() + result = runner.invoke(analyze, [tmpdir]) + assert result.exit_code == 0 + + def test_analyze_unsupported_file_type(self): + with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f: + f.write("This is a text file\n") + temp_path = f.name + + try: + runner = CliRunner() + result = runner.invoke(analyze, [temp_path]) + assert result.exit_code == 0 + finally: + os.unlink(temp_path) + + +class TestOutputFormatting: + """Tests for output formatting.""" + + def test_json_output(self): + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write('password = "secret123"\n') + temp_path = f.name + + try: + runner = CliRunner() + result = runner.invoke(analyze, [temp_path, "--json"]) + assert result.exit_code == 0 + import json + json_start = result.output.find('{') + json_end = result.output.rfind('}') + 1 + json_output = result.output[json_start:json_end] + data = json.loads(json_output) + assert "files_analyzed" in data + assert "results" in data + finally: + os.unlink(temp_path)