Files
Open_Ultra_SAST/tests/test_cli_hx_dispatch.py
T

63 lines
2.8 KiB
Python

"""CLI capability-gated dispatch + graceful degradation (Phase 3 task 6.5).
Simulates the extra-absent environment (via a monkeypatched capability probe so the
behaviour is deterministic whether or not the optional extra is installed): standard
mode with models configured must fall back to the deterministic hunter/structural
verifier and record a degradation; with no models configured the manifest stays
byte-identical.
"""
import json
from pathlib import Path
import pytest
from openultrasast import cli
from openultrasast.cli import main
_VULN = "@app.route('/admin')\ndef admin():\n return eval(request.data)\n"
def _run(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, config_body: str | None) -> dict:
repo = tmp_path / "repo"
repo.mkdir()
(repo / "app.py").write_text(_VULN)
monkeypatch.setattr(cli, "has_harnessx", lambda: False) # simulate the extra being absent
monkeypatch.setenv("OPENULTRASAST_RUNS_DIR", ".runs")
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
monkeypatch.delenv("OPENULTRASAST_HUNTER_CLIENT", raising=False)
argv = ["scan", str(repo), "--mode", "standard"]
if config_body is not None:
cfg = tmp_path / "openultrasast.toml"
cfg.write_text(config_body)
argv += ["--config", str(cfg)]
assert main(argv) == 0
run_dir = sorted((repo / ".runs").iterdir())[-1]
return json.loads((run_dir / "manifest.json").read_text())
def test_degradation_recorded_when_models_set_but_extra_absent(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
manifest = _run(tmp_path, monkeypatch, '[models]\nhunter = "anthropic/claude"\nverifier = "anthropic/claude"\n')
degradations = manifest.get("degradations", [])
stages = {entry["stage"] for entry in degradations}
assert stages == {"hunter_pool", "verify"}
assert all(entry["reason"] == "harnessx_extra_unavailable" for entry in degradations)
# Fallback still produced findings (deterministic hunter ran).
assert manifest["findings"]
def test_manifest_has_no_harnessx_degradations_without_models(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
manifest = _run(tmp_path, monkeypatch, config_body=None)
degradations = manifest.get("degradations", [])
assert all(entry.get("reason") != "harnessx_extra_unavailable" for entry in degradations)
assert any(entry.get("reason") == "hunter_model_unavailable" for entry in degradations)
def test_standard_scan_falls_back_to_run_hunter_pool(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
calls: list[str] = []
real = cli.run_hunter_pool
monkeypatch.setattr(cli, "run_hunter_pool", lambda *a, **k: calls.append("fallback") or real(*a, **k))
_run(tmp_path, monkeypatch, '[models]\nhunter = "anthropic/claude"\n')
assert calls == ["fallback"]