Files
claude-plugin/plugins/reviews/skills/audit-code/tests/test_cli.py
T
mroberts 37fc3fb291 Fix audit tool bootstrap and add per-run preflight
audit-code install-tools.sh:
- Buffer the opengrep release JSON before grep -m1; curl died with (23)
  under pipefail when grep quit early.
- Use ${m}: in the PowerShell block; $m: parsed as a scope-qualified var.
- On Arch, skip paru/yay when pacman -Q shows every package installed,
  since --needed still invokes sudo.
- Add --check-only (fast, installs nothing, non-zero naming missing tools)
  and --user-only (no system package managers, no sudo).

log-run.py (both skills): put the skill dir on sys.path so running it as
a script from any cwd no longer raises ModuleNotFoundError.

audit-terraform: move deps from requirements.txt into pyproject
dependency groups and add scripts/install-tools.sh (uv sync --group tools,
then check trivy, tflint, tofu, terragrunt, gh).

Both SKILL.md files gain a 0.5 Preflight step and call scripts through
uv run --project ${SKILL_DIR}. tools_unavailable is now a map of tool to
exact install command; audit-terraform skips trivy when absent and stops
with an install hint instead of crashing when tofu/terragrunt is missing.
2026-09-22 15:21:28 -05:00

197 lines
7.3 KiB
Python

import importlib.util
import json
from pathlib import Path
from unittest.mock import patch, MagicMock
_SKILL_ROOT = Path(__file__).resolve().parent.parent
def _load_cli():
spec = importlib.util.spec_from_file_location(
"collect_findings", _SKILL_ROOT / "scripts" / "collect-findings.py"
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
def _fake_subprocess(cmd, **kwargs):
if cmd[:2] == ["git", "-C"]:
sub = cmd[2:]
else:
sub = cmd
if "symbolic-ref" in sub:
return MagicMock(returncode=0, stdout="refs/remotes/origin/main\n", stderr="")
if "fetch" in sub:
return MagicMock(returncode=0, stdout="", stderr="")
if "rev-parse" in sub and "--verify" in sub:
return MagicMock(returncode=0, stdout="abc\n", stderr="")
if cmd[:1] == ["git"] and "diff" in cmd:
diff = (
"diff --git a/src/api.py b/src/api.py\n"
"index 1..2 100644\n"
"--- a/src/api.py\n"
"+++ b/src/api.py\n"
"@@ -12 +12,1 @@\n"
"+x = parse(user_input)\n"
)
return MagicMock(returncode=0, stdout=diff, stderr="")
if Path(cmd[0]).name == "bandit":
out = json.dumps({"results": [{
"filename": "src/api.py", "line_number": 12, "line_range": [12, 12],
"issue_severity": "HIGH", "issue_text": "Use of dynamic parsing", "test_id": "B307",
}]})
return MagicMock(returncode=0, stdout=out, stderr="")
return MagicMock(returncode=0, stdout="", stderr="")
def test_cli_aborts_when_no_supported_files(tmp_path):
repo = tmp_path / "repo"
repo.mkdir()
out_dir = tmp_path / "out"
mod = _load_cli()
def _fake(cmd, **kw):
if cmd[:1] == ["git"] and "diff" in cmd:
return MagicMock(returncode=0,
stdout="diff --git a/README.md b/README.md\nindex 1..2 100644\n--- a/README.md\n+++ b/README.md\n@@ -1 +1,1 @@\n+x\n",
stderr="")
return _fake_subprocess(cmd, **kw)
with patch("subprocess.run", side_effect=_fake):
with patch("shutil.which", return_value=None):
rc = mod.main([
"--repo", str(repo),
"--head", "HEAD",
"--output-dir", str(out_dir),
"--mode", "local",
])
assert rc == 1
manifest = json.loads((out_dir / "manifest.json").read_text())
assert any("no supported source files" in e.lower() for e in manifest["errors"])
def test_cli_happy_path_python_only(tmp_path):
from scripts import runner
repo = tmp_path / "repo"
repo.mkdir()
(repo / "src").mkdir()
(repo / "src" / "api.py").write_text("x = 1\n")
out_dir = tmp_path / "out"
mod = _load_cli()
empty_venv = tmp_path / "empty-venv"
empty_venv.mkdir()
def _which(binary):
return "/usr/bin/bandit" if binary == "bandit" else None
with patch.object(runner, "_SKILL_VENV_BIN", empty_venv):
with patch("subprocess.run", side_effect=_fake_subprocess):
with patch("shutil.which", side_effect=_which):
rc = mod.main([
"--repo", str(repo),
"--head", "HEAD",
"--output-dir", str(out_dir),
"--mode", "local",
])
assert rc == 0
manifest = json.loads((out_dir / "manifest.json").read_text())
assert manifest["mode"] == "local"
assert manifest["language_breakdown"]["python"] == 1
rule_ids = {f["rule_id"] for f in manifest["findings"]}
assert "B307" in rule_ids
for agent in ("security-triage", "type-safety", "dependency", "consistency", "secrets"):
assert (out_dir / f"manifest-{agent}.json").exists()
def test_cli_records_tool_unavailable(tmp_path):
from scripts import runner
repo = tmp_path / "repo"
repo.mkdir()
(repo / "src").mkdir()
(repo / "src" / "api.py").write_text("x = 1\n")
out_dir = tmp_path / "out"
mod = _load_cli()
empty_venv = tmp_path / "empty-venv"
empty_venv.mkdir()
with patch.object(runner, "_SKILL_VENV_BIN", empty_venv):
with patch("subprocess.run", side_effect=_fake_subprocess):
with patch("shutil.which", return_value=None):
rc = mod.main([
"--repo", str(repo),
"--head", "HEAD",
"--output-dir", str(out_dir),
"--mode", "local",
])
assert rc == 0
manifest = json.loads((out_dir / "manifest.json").read_text())
assert "bandit" in manifest["tools_unavailable"]
assert manifest["tools_unavailable"]["bandit"].endswith("scripts/install-tools.sh --user-only")
def test_cli_alerts_on_unsupported_languages(tmp_path):
"""Mixed PR: Python supported, Go file alerts — but review still proceeds."""
repo = tmp_path / "repo"
repo.mkdir()
(repo / "src").mkdir()
(repo / "src" / "api.py").write_text("x = 1\n")
out_dir = tmp_path / "out"
mod = _load_cli()
def _fake(cmd, **kw):
if cmd[:1] == ["git"] and "diff" in cmd:
diff = (
"diff --git a/src/api.py b/src/api.py\n"
"index 1..2 100644\n--- a/src/api.py\n+++ b/src/api.py\n"
"@@ -1 +1,1 @@\n+x = 1\n"
"diff --git a/cmd/server.go b/cmd/server.go\n"
"index 3..4 100644\n--- a/cmd/server.go\n+++ b/cmd/server.go\n"
"@@ -1 +1,1 @@\n+package main\n"
)
return MagicMock(returncode=0, stdout=diff, stderr="")
return _fake_subprocess(cmd, **kw)
with patch("subprocess.run", side_effect=_fake):
with patch("shutil.which", return_value=None):
rc = mod.main([
"--repo", str(repo),
"--head", "HEAD",
"--output-dir", str(out_dir),
"--mode", "local",
])
assert rc == 0, "review should continue when at least one supported file present"
manifest = json.loads((out_dir / "manifest.json").read_text())
assert any("Go" in e and "server.go" in e for e in manifest["errors"]), manifest["errors"]
assert manifest["language_breakdown"]["python"] == 1
def test_cli_alerts_on_unsupported_only_and_aborts(tmp_path):
"""Diff contains ONLY unsupported languages: alert + abort."""
repo = tmp_path / "repo"
repo.mkdir()
out_dir = tmp_path / "out"
mod = _load_cli()
def _fake(cmd, **kw):
if cmd[:1] == ["git"] and "diff" in cmd:
diff = (
"diff --git a/cmd/server.go b/cmd/server.go\n"
"index 3..4 100644\n--- a/cmd/server.go\n+++ b/cmd/server.go\n"
"@@ -1 +1,1 @@\n+package main\n"
)
return MagicMock(returncode=0, stdout=diff, stderr="")
return _fake_subprocess(cmd, **kw)
with patch("subprocess.run", side_effect=_fake):
with patch("shutil.which", return_value=None):
rc = mod.main([
"--repo", str(repo),
"--head", "HEAD",
"--output-dir", str(out_dir),
"--mode", "local",
])
assert rc == 1
manifest = json.loads((out_dir / "manifest.json").read_text())
errors_blob = " | ".join(manifest["errors"])
assert "Go" in errors_blob, errors_blob
assert "no supported source files" in errors_blob.lower()