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.
128 lines
4.7 KiB
Python
128 lines
4.7 KiB
Python
"""Tests for scripts/install-tools.sh, run against a copy with stubbed binaries."""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "install-tools.sh"
|
|
|
|
_VENV_TOOLS = ("bandit", "ruff", "mypy", "pip-audit", "vulture", "radon", "interrogate", "lizard", "opengrep")
|
|
_NATIVE_TOOLS = ("gitleaks", "osv-scanner", "gh")
|
|
|
|
|
|
def _skill_copy(tmp_path: Path) -> Path:
|
|
skill = tmp_path / "skill"
|
|
(skill / "scripts").mkdir(parents=True)
|
|
shutil.copy(_SCRIPT, skill / "scripts" / "install-tools.sh")
|
|
return skill / "scripts" / "install-tools.sh"
|
|
|
|
|
|
def _stub(bin_dir: Path, name: str, body: str) -> None:
|
|
bin_dir.mkdir(parents=True, exist_ok=True)
|
|
path = bin_dir / name
|
|
path.write_text("#!/usr/bin/env bash\n" + body)
|
|
path.chmod(0o755)
|
|
|
|
|
|
def _run(cmd: str, bin_dir: Path, **env: str) -> subprocess.CompletedProcess:
|
|
return subprocess.run(
|
|
["bash", "-c", cmd],
|
|
capture_output=True, text=True, timeout=60, check=False,
|
|
env={**os.environ, "PATH": f"{bin_dir}:/usr/bin:/bin", **env},
|
|
)
|
|
|
|
|
|
def test_opengrep_tag_lookup_survives_large_release_body(tmp_path: Path) -> None:
|
|
script = _skill_copy(tmp_path)
|
|
bin_dir = tmp_path / "bin"
|
|
log = tmp_path / "curl.log"
|
|
_stub(bin_dir, "curl", r'''
|
|
for ((i = 1; i <= $#; i++)); do
|
|
if [[ "${!i}" == "-o" ]]; then
|
|
j=$((i + 1)); echo "$*" >> "$CURL_LOG"; echo bin > "${!j}"; exit 0
|
|
fi
|
|
done
|
|
printf '{\n "tag_name": "v9.9.9",\n "body": "'
|
|
head -c 2000000 /dev/zero | tr '\0' x
|
|
printf '"\n}\n'
|
|
''')
|
|
r = _run(f"source {script} && install_opengrep", bin_dir, CURL_LOG=str(log))
|
|
assert r.returncode == 0, r.stderr
|
|
assert (script.parent.parent / ".venv" / "bin" / "opengrep").is_file()
|
|
assert "/releases/download/v9.9.9/" in log.read_text()
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("pwsh") is None, reason="pwsh not installed")
|
|
def test_powershell_module_block_parses() -> None:
|
|
block = re.search(r"pwsh -NoProfile -NonInteractive -Command '(.*?)'", _SCRIPT.read_text(), re.DOTALL)
|
|
assert block, "embedded pwsh -Command block not found"
|
|
check = (
|
|
"$errs = $null; "
|
|
"[System.Management.Automation.Language.Parser]::ParseInput($env:PS_BLOCK, [ref]$null, [ref]$errs) | Out-Null; "
|
|
"$errs | ForEach-Object { $_.Message }; exit $errs.Count"
|
|
)
|
|
r = subprocess.run(
|
|
["pwsh", "-NoProfile", "-NonInteractive", "-Command", check],
|
|
capture_output=True, text=True, timeout=60, check=False,
|
|
env={**os.environ, "PS_BLOCK": block.group(1)},
|
|
)
|
|
assert r.returncode == 0, r.stdout + r.stderr
|
|
|
|
|
|
def _arch_stubs(bin_dir: Path, installed: set[str]) -> None:
|
|
_stub(bin_dir, "pacman", f'''
|
|
[[ "$1" == "-Q" ]] || {{ echo "pacman $*" >> "$CALLS"; exit 1; }}
|
|
case "$2" in {"|".join(installed) or "__none__"}) exit 0 ;; *) exit 1 ;; esac
|
|
''')
|
|
_stub(bin_dir, "paru", 'echo "paru $*" >> "$CALLS"\n')
|
|
_stub(bin_dir, "sudo", 'echo "sudo $*" >> "$CALLS"; exit 1\n')
|
|
|
|
|
|
def test_arch_skips_helper_when_everything_installed(tmp_path: Path) -> None:
|
|
script = _skill_copy(tmp_path)
|
|
bin_dir = tmp_path / "bin"
|
|
calls = tmp_path / "calls.log"
|
|
_arch_stubs(bin_dir, {"gitleaks", "github-cli", "osv-scanner"})
|
|
r = _run(f"source {script} && install_native_arch", bin_dir, CALLS=str(calls))
|
|
assert r.returncode == 0, r.stderr
|
|
assert not calls.exists(), calls.read_text()
|
|
|
|
|
|
def test_arch_helper_installs_only_missing_packages(tmp_path: Path) -> None:
|
|
script = _skill_copy(tmp_path)
|
|
bin_dir = tmp_path / "bin"
|
|
calls = tmp_path / "calls.log"
|
|
_arch_stubs(bin_dir, {"gitleaks", "github-cli"})
|
|
r = _run(f"source {script} && install_native_arch", bin_dir, CALLS=str(calls))
|
|
assert r.returncode == 0, r.stderr
|
|
assert calls.read_text().splitlines() == ["paru -S --needed --noconfirm osv-scanner"]
|
|
|
|
|
|
def test_check_only_on_empty_venv_fails_and_names_missing_tools(tmp_path: Path) -> None:
|
|
script = _skill_copy(tmp_path)
|
|
bin_dir = tmp_path / "bin"
|
|
for tool in _NATIVE_TOOLS:
|
|
_stub(bin_dir, tool, "")
|
|
r = _run(f"bash {script} --check-only", bin_dir)
|
|
assert r.returncode != 0
|
|
assert "lizard" in r.stdout
|
|
assert "opengrep" in r.stdout
|
|
assert not (script.parent.parent / ".venv").exists(), "--check-only must not install anything"
|
|
|
|
|
|
def test_check_only_passes_when_everything_present(tmp_path: Path) -> None:
|
|
script = _skill_copy(tmp_path)
|
|
venv_bin = script.parent.parent / ".venv" / "bin"
|
|
for tool in _VENV_TOOLS:
|
|
_stub(venv_bin, tool, "")
|
|
bin_dir = tmp_path / "bin"
|
|
for tool in _NATIVE_TOOLS:
|
|
_stub(bin_dir, tool, "")
|
|
r = _run(f"bash {script} --check-only", bin_dir)
|
|
assert r.returncode == 0, r.stdout + r.stderr
|