Download tests/test_cli.py from Chris4K/autoscan: direct link, hf CLI and curl.
- Browser
- Download file 9.94 kB
-
https://huggingface.co/spaces/Chris4K/autoscan/resolve/main/tests/test_cli.py
- Command line
-
hf download hf://spaces/Chris4K/autoscan/tests/test_cli.py
-
curl -L -o test_cli.py https://huggingface.co/spaces/Chris4K/autoscan/resolve/main/tests/test_cli.py
9.94 kB
| from typer.testing import CliRunner | |
| import json | |
| from pathlib import Path | |
| import cli as cli_mod | |
| runner = CliRunner() | |
| def test_version_command(): | |
| result = runner.invoke(cli_mod.app, ["version"]) | |
| assert result.exit_code == 0 | |
| assert "hf-scanner" in result.stdout | |
| def test_scan_writes_reports_and_exits_clean(monkeypatch, tmp_path): | |
| findings = [ | |
| {"severity": "ERROR", "tool": "bandit", "rule": "B1", "file": "a.py", "line": 1, "message": "x", "owasp": [], "remediation": "r"} | |
| ] | |
| monkeypatch.setattr(cli_mod, "scan_repo", lambda target, **kwargs: (findings, ["ok"])) | |
| monkeypatch.setattr(cli_mod, "generate_html_report", lambda f, m: "<html></html>") | |
| monkeypatch.setattr(cli_mod, "generate_sarif", lambda f, m: {"sarif": True}) | |
| out_stem = tmp_path / "out_stem" | |
| result = runner.invoke(cli_mod.app, ["scan", "./", "--format", "both", "--out", str(out_stem)]) | |
| # findings contain an ERROR so the CLI exits with EXIT_FINDINGS (1) | |
| assert result.exit_code == 1 | |
| assert (str(out_stem) + ".html") in result.stdout | |
| assert (str(out_stem) + ".sarif") in result.stdout | |
| assert Path(str(out_stem) + ".html").exists() | |
| def test_scan_json_and_stdout(monkeypatch, tmp_path): | |
| findings = [{"severity": "INFO", "tool": "t", "rule": "r", "file": "f", "line": 0, "message": "m", "owasp": [], "remediation": ""}] | |
| monkeypatch.setattr(cli_mod, "scan_repo", lambda target, **kwargs: (findings, [])) | |
| # pass directory as --out so the CLI writes scan_report.json inside it | |
| out_dir = tmp_path | |
| result = runner.invoke(cli_mod.app, ["scan", "./", "--format", "json", "--out", str(out_dir)]) | |
| assert result.exit_code == 0 | |
| # stdout contains JSON | |
| assert "[" in result.stdout | |
| assert (out_dir / "scan_report.json").exists() | |
| """Tests for CLI entrypoint (cli.py) β black-box subprocess tests.""" | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| import tempfile | |
| from pathlib import Path | |
| import pytest | |
| ROOT = Path(__file__).parent.parent | |
| PYTHON = sys.executable | |
| CLI = str(ROOT / "cli.py") | |
| FIXTURES = ROOT / "tests" / "fixtures" | |
| def run_cli(*args, **kwargs) -> subprocess.CompletedProcess: | |
| """Run hf-scanner via `python cli.py ...` and return the completed process.""" | |
| env = os.environ.copy() | |
| env["PYTHONIOENCODING"] = "utf-8" | |
| return subprocess.run( | |
| [PYTHON, CLI, *args], | |
| capture_output=True, | |
| text=True, | |
| encoding="utf-8", | |
| env=env, | |
| **kwargs, | |
| ) | |
| # ββ basic commands ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestCliBasic: | |
| def test_help_exits_zero(self): | |
| result = run_cli("--help") | |
| assert result.returncode == 0 | |
| assert "scan" in result.stdout | |
| assert "version" in result.stdout | |
| def test_version_command(self): | |
| result = run_cli("version") | |
| assert result.returncode == 0 | |
| assert "hf-scanner" in result.stdout | |
| assert "4." in result.stdout | |
| def test_list_rules(self): | |
| result = run_cli("list-rules") | |
| assert result.returncode == 0 | |
| assert "Semgrep" in result.stdout | |
| def test_self_test_runs(self): | |
| result = run_cli("self-test") | |
| # 0 = all ok, 2 = some tools missing β both are acceptable here | |
| assert result.returncode in (0, 2) | |
| assert "semgrep" in result.stdout.lower() or "semgrep" in result.stderr.lower() | |
| def test_no_args_shows_help(self): | |
| result = run_cli() | |
| # typer shows help and exits 0 when no_args_is_help=True | |
| assert result.returncode == 0 | |
| def test_unknown_format_exits_usage(self, tmp_path): | |
| result = run_cli("scan", str(tmp_path), "--format", "pdf") | |
| assert result.returncode == 3 | |
| # ββ scan output formats βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestCliScanOutput: | |
| def test_scan_produces_html(self, tmp_path): | |
| src = FIXTURES / "security" | |
| result = run_cli( | |
| "scan", str(src), | |
| "--format", "html", | |
| "--out", str(tmp_path / "report"), | |
| "--no-llm", "--no-performance", | |
| cwd=str(ROOT), | |
| ) | |
| html_path = tmp_path / "report.html" | |
| assert html_path.exists(), f"No HTML produced. stdout: {result.stdout[:500]}" | |
| def test_scan_produces_sarif(self, tmp_path): | |
| src = FIXTURES / "security" | |
| run_cli( | |
| "scan", str(src), | |
| "--format", "sarif", | |
| "--out", str(tmp_path / "report"), | |
| "--no-llm", "--no-performance", | |
| cwd=str(ROOT), | |
| ) | |
| sarif_path = tmp_path / "report.sarif" | |
| assert sarif_path.exists(), "No SARIF file produced" | |
| with open(sarif_path, encoding="utf-8") as fh: | |
| doc = json.load(fh) | |
| assert doc["version"] == "2.1.0" | |
| def test_scan_json_format(self, tmp_path): | |
| src = FIXTURES / "security" | |
| result = run_cli( | |
| "scan", str(src), | |
| "--format", "json", | |
| "--out", str(tmp_path / "report"), | |
| "--no-llm", "--no-performance", | |
| cwd=str(ROOT), | |
| ) | |
| # JSON is also printed to stdout | |
| try: | |
| findings = json.loads(result.stdout.strip().split("\n", 1)[-1] | |
| if "\n" in result.stdout else result.stdout) | |
| except json.JSONDecodeError: | |
| # may be in the output file instead | |
| json_path = tmp_path / "report.json" | |
| if json_path.exists(): | |
| findings = json.loads(json_path.read_text(encoding="utf-8")) | |
| else: | |
| pytest.skip("JSON output format not verifiable in this environment") | |
| if isinstance(findings, list): | |
| for f in findings: | |
| assert "rule" in f or "tool" in f | |
| # ββ exit codes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestCliExitCodes: | |
| def test_bad_fixture_exits_1(self, tmp_path): | |
| """Known-bad fixture should trigger findings β exit 1.""" | |
| src = FIXTURES / "security" | |
| result = run_cli( | |
| "scan", str(src), | |
| "--format", "sarif", | |
| "--out", str(tmp_path / "r"), | |
| "--no-llm", "--no-performance", | |
| "--severity-threshold", "WARNING", | |
| cwd=str(ROOT), | |
| ) | |
| assert result.returncode == 1, ( | |
| f"Expected exit 1 (findings), got {result.returncode}\n" | |
| f"stdout: {result.stdout[:500]}\nstderr: {result.stderr[:500]}" | |
| ) | |
| def test_clean_fixture_exits_0(self, tmp_path): | |
| """Clean fixture has no findings β exit 0.""" | |
| src = FIXTURES / "clean" | |
| result = run_cli( | |
| "scan", str(src), | |
| "--format", "sarif", | |
| "--out", str(tmp_path / "r"), | |
| "--no-llm", "--no-performance", "--no-security", | |
| cwd=str(ROOT), | |
| ) | |
| assert result.returncode == 0, ( | |
| f"Expected exit 0 (clean), got {result.returncode}\n" | |
| f"stdout: {result.stdout[:500]}\nstderr: {result.stderr[:500]}" | |
| ) | |
| # ββ scanner flags βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestCliScanFlags: | |
| def test_no_security_skips_bandit(self, tmp_path): | |
| src = FIXTURES / "security" | |
| result = run_cli( | |
| "scan", str(src), | |
| "--no-security", | |
| "--format", "json", | |
| "--out", str(tmp_path / "out"), | |
| cwd=str(ROOT), | |
| ) | |
| # Only examine log lines (not paths which may contain the test function name) | |
| log_lines = [ | |
| ln for ln in (result.stdout + result.stderr).splitlines() | |
| if ln.strip().startswith(("bandit", " bandit", "semgrep", "pip-audit", | |
| "gitleaks", "hadolint", "detect-secrets", | |
| "forbidden-files", "ruff", "agent-audit")) | |
| ] | |
| assert not any("bandit" in ln.lower() for ln in log_lines), \ | |
| f"bandit should not appear in scanner log lines: {log_lines}" | |
| def test_baseline_roundtrip(self, tmp_path): | |
| """Create baseline from bad fixture, then scan again β findings suppressed.""" | |
| src = FIXTURES / "security" | |
| baseline_path = str(tmp_path / "baseline.json") | |
| # First scan: create baseline | |
| run_cli( | |
| "scan", str(src), | |
| "--create-baseline", baseline_path, | |
| "--format", "json", | |
| "--out", str(tmp_path / "r1"), | |
| "--no-llm", "--no-performance", | |
| cwd=str(ROOT), | |
| ) | |
| assert Path(baseline_path).exists(), "Baseline file not created" | |
| # Second scan: apply baseline | |
| result2 = run_cli( | |
| "scan", str(src), | |
| "--baseline", baseline_path, | |
| "--format", "json", | |
| "--out", str(tmp_path / "r2"), | |
| "--no-llm", "--no-performance", | |
| cwd=str(ROOT), | |
| ) | |
| output = result2.stdout + result2.stderr | |
| assert "suppressed" in output.lower() or result2.returncode == 0 | |