autoscan / tests /test_rules.py
Chris4K's picture
Initial commit v5.0.0.
5248e3b verified
"""Per-scanner unit tests β€” direct function calls, no UI needed.
All tests are fast (no network, no git clone). They create an isolated
temp directory with a single fixture file and call the scanner function
directly, asserting on the returned (findings, log_msg) tuple.
"""
import os
import shutil
import sys
import tempfile
from pathlib import Path
import pytest
ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(ROOT))
FIXTURES = ROOT / "tests" / "fixtures"
def _copy_to_tmp(fixture_rel: str) -> Path:
"""Copy a fixture file into a fresh temp dir and return the temp dir path."""
src = FIXTURES / fixture_rel
tmp = Path(tempfile.mkdtemp(prefix="scanner_test_"))
shutil.copy(src, tmp / src.name)
return tmp
def _cleanup(path: Path) -> None:
shutil.rmtree(str(path), ignore_errors=True)
# ── bandit ────────────────────────────────────────────────────────────────────
class TestBandit:
def test_finds_vulnerabilities_in_bad_agent(self):
d = _copy_to_tmp("security/bad_agent.py")
try:
from scanners.bandit_runner import bandit
findings, msg = bandit(str(d))
assert len(findings) > 0, f"Expected findings, got none. msg={msg}"
finally:
_cleanup(d)
def test_clean_file_has_few_findings(self):
"""Clean file may still have minor bandit notices; just check no HIGH/ERROR."""
d = _copy_to_tmp("security/clean.py")
try:
from scanners.bandit_runner import bandit
findings, _ = bandit(str(d))
high_plus = [f for f in findings if f.get("severity") in ("ERROR", "HIGH")]
assert len(high_plus) == 0, f"Unexpected HIGH/ERROR findings: {high_plus}"
finally:
_cleanup(d)
# ── detect-secrets ────────────────────────────────────────────────────────────
class TestDetectSecrets:
def test_finds_hardcoded_key(self, tmp_path):
"""detect-secrets should flag hardcoded secrets. We write a fake private key."""
secret_file = tmp_path / "credentials.py"
# Use a fake PEM block β€” PrivateKeyDetector always fires on this pattern.
secret_file.write_text(
"# test fixture\n"
"PRIVATE_KEY = (\n"
" '-----BEGIN RSA PRIVATE KEY-----\\n'\n"
" 'MIIEpAIBAAKCAQEA0Z3VS5JJcds3xHn/ygWep4sDYBD\\n'\n"
" '-----END RSA PRIVATE KEY-----\\n'\n"
")\n",
encoding="utf-8",
)
from scanners.semgrep_runner import detect_secrets
findings, msg = detect_secrets(str(tmp_path))
# detect-secrets may not be available or may need git context β€” skip gracefully
if not findings:
pytest.skip(f"detect-secrets found nothing (tool unavailable or filtered). msg={msg}")
assert len(findings) > 0, f"Expected secret findings. msg={msg}"
def test_clean_file_no_secrets(self, tmp_path):
(tmp_path / "clean.py").write_text("x = 1\n", encoding="utf-8")
from scanners.semgrep_runner import detect_secrets
findings, _ = detect_secrets(str(tmp_path))
assert len(findings) == 0, f"Unexpected secrets in clean file: {findings}"
# ── forbidden files ───────────────────────────────────────────────────────────
class TestForbiddenFiles:
def test_finds_env_file(self, tmp_path):
(tmp_path / ".env").write_text("SECRET=abc123\n", encoding="utf-8")
from scanners.forbidden_files import forbidden_files
findings, _ = forbidden_files(str(tmp_path))
assert any(".env" in f.get("file", "") for f in findings), \
f"Expected .env finding, got: {findings}"
def test_clean_dir_no_findings(self, tmp_path):
(tmp_path / "main.py").write_text("print('hello')\n", encoding="utf-8")
from scanners.forbidden_files import forbidden_files
findings, _ = forbidden_files(str(tmp_path))
assert findings == [], f"Unexpected findings in clean dir: {findings}"
# ── ruff perf ─────────────────────────────────────────────────────────────────
class TestRuffPerf:
def test_finds_perf_issues(self):
d = _copy_to_tmp("performance/bad_perf.py")
try:
from scanners.ruff_runner import ruff_perf
findings, msg = ruff_perf(str(d))
assert len(findings) > 0, f"Expected PERF findings. msg={msg}"
finally:
_cleanup(d)
# ── semgrep (security pack) ───────────────────────────────────────────────────
class TestSemgrepSecurity:
@pytest.mark.slow
def test_core_rules_find_issues(self):
d = _copy_to_tmp("security/bad_agent.py")
try:
from scanners.semgrep_runner import semgrep_pack
from rules import ALL_SECURITY
label, rules_path, category = ALL_SECURITY[0] # core.yaml
findings, msg = semgrep_pack(rules_path, str(d), label, category)
# semgrep may or may not match depending on rule set; just verify it ran
assert isinstance(findings, list)
finally:
_cleanup(d)
# ── pip-audit ─────────────────────────────────────────────────────────────────
class TestPipAudit:
@pytest.mark.slow
def test_clean_requirements_no_cves(self):
d = _copy_to_tmp("clean/requirements.txt")
try:
from scanners.pip_audit_runner import pip_audit
findings, msg = pip_audit(str(d))
# Clean fixture should have few or no known CVEs
assert isinstance(findings, list)
finally:
_cleanup(d)
# ── agent-audit ───────────────────────────────────────────────────────────────
class TestAgentAudit:
@pytest.mark.slow
def test_runs_without_error(self):
d = _copy_to_tmp("llm/indirect_injection.py")
try:
from scanners.agent_audit_runner import agent_audit
findings, msg = agent_audit(str(d))
assert isinstance(findings, list)
assert isinstance(msg, str)
finally:
_cleanup(d)