mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 19:59:26 +00:00
The installed hook exits 0 with one line when ousast (or OUSAST_COMMAND) is not on PATH, still chaining a prior hook. In Git hook mode without an explicit --mode blocking, argument/usage errors (an invalid OUSAST_PUSH_DEADLINE, a cache directory inside the repository, a refused experimental flag) and unexpected exceptions print one "check did not run" line and exit 0. Blocking mode keeps its usage errors. Requirement 9.1. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
147 lines
6.1 KiB
Python
147 lines
6.1 KiB
Python
"""Real local Git pushes; never install in the project being developed."""
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from test_push_runner import history
|
|
|
|
|
|
@pytest.mark.parametrize("prior_exit", [0, 7])
|
|
def test_existing_hook_stdin_remote_exit_and_removal(tmp_path, prior_exit):
|
|
root, base, head, git = history(tmp_path)
|
|
remote = tmp_path / "remote.git"
|
|
subprocess.run(["git", "init", "--bare", str(remote)], check=True, capture_output=True)
|
|
git("remote", "add", "origin", str(remote))
|
|
hooks = root / ".custom-hooks"
|
|
hooks.mkdir()
|
|
git("config", "core.hooksPath", ".custom-hooks")
|
|
hook = hooks / "pre-push"
|
|
prior = hooks / "pre-push.before-ousast"
|
|
log = tmp_path / "prior-input"
|
|
args = tmp_path / "prior-args"
|
|
prior.write_text(f'#!/bin/sh\nprintf "%s\\n" "$@" > "{args}"\ncat > "{log}"\nexit {prior_exit}\n')
|
|
prior.chmod(0o755)
|
|
shutil.copyfile(Path(__file__).resolve().parents[1] / "ops/pre-push", hook)
|
|
hook.chmod(0o755)
|
|
before = (root / ".git/index").read_bytes(), (root / "api.js").read_bytes(), git("rev-parse", "HEAD")
|
|
env = {
|
|
**os.environ,
|
|
"PATH": str(Path(".venv/bin").resolve()) + os.pathsep + os.environ["PATH"],
|
|
"OUSAST_ARTIFACT_DIR": str(tmp_path / "artifacts"),
|
|
"OUSAST_PUSH_DEADLINE": "0.1",
|
|
}
|
|
result = subprocess.run(["git", "-C", str(root), "push", "origin", "HEAD:refs/heads/test"], env=env, capture_output=True)
|
|
assert (result.returncode == 0) == (prior_exit == 0), result.stderr
|
|
assert log.read_text() == f"HEAD {head} refs/heads/test {'0' * 40}\n"
|
|
assert args.read_text().splitlines() == ["origin", str(remote)]
|
|
assert ((root / ".git/index").read_bytes(), (root / "api.js").read_bytes(), git("rev-parse", "HEAD")) == before
|
|
assert git("config", "core.hooksPath") == ".custom-hooks"
|
|
assert list((tmp_path / "artifacts").glob("*.json"))
|
|
# Explicit removal restores the prior hook byte-for-byte, without config/source/ref edits.
|
|
original = prior.read_bytes()
|
|
hook.unlink()
|
|
prior.rename(hook)
|
|
assert hook.read_bytes() == original
|
|
result = subprocess.run(["git", "-C", str(root), "push", "origin", "HEAD:refs/heads/after-removal"], env=env, capture_output=True)
|
|
assert (result.returncode == 0) == (prior_exit == 0)
|
|
assert "refs/heads/after-removal" in log.read_text()
|
|
|
|
|
|
def test_install_refuses_existing_and_honors_hooks_path(tmp_path):
|
|
root, _, _, git = history(tmp_path)
|
|
git("config", "core.hooksPath", ".custom-hooks")
|
|
installer = Path(__file__).resolve().parents[1] / "ops/install-pre-push"
|
|
first = subprocess.run([str(installer), str(root)], capture_output=True)
|
|
assert first.returncode == 0, first.stderr
|
|
hook = root / ".custom-hooks/pre-push"
|
|
hook.write_text("#!/bin/sh\nexit 23\n")
|
|
before = hook.read_bytes()
|
|
second = subprocess.run([str(installer), str(root)], capture_output=True)
|
|
assert second.returncode != 0
|
|
assert hook.read_bytes() == before
|
|
assert git("config", "core.hooksPath") == ".custom-hooks"
|
|
|
|
|
|
def test_chain_preserves_exact_nonzero_even_for_advisory(tmp_path):
|
|
import sys
|
|
|
|
root, _, _, _ = history(tmp_path)
|
|
prior = tmp_path / "prior"
|
|
prior.write_text("#!/bin/sh\ncat >/dev/null\nexit 23\n")
|
|
prior.chmod(0o755)
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"openultrasast.cli",
|
|
"pre-push",
|
|
str(root),
|
|
"--artifact",
|
|
str(tmp_path / "result.json"),
|
|
"--prior-hook",
|
|
str(prior),
|
|
"--remote",
|
|
"origin",
|
|
"/tmp/remote",
|
|
],
|
|
input=b"",
|
|
capture_output=True,
|
|
timeout=10,
|
|
)
|
|
assert result.returncode == 23
|
|
|
|
|
|
def test_install_refuses_dangling_symlink(tmp_path):
|
|
root, _, _, git = history(tmp_path)
|
|
git("config", "core.hooksPath", ".custom-hooks")
|
|
hooks = root / ".custom-hooks"
|
|
hooks.mkdir()
|
|
target = tmp_path / "must-not-create"
|
|
(hooks / "pre-push").symlink_to(target)
|
|
installer = Path(__file__).resolve().parents[1] / "ops/install-pre-push"
|
|
result = subprocess.run([str(installer), str(root)], capture_output=True)
|
|
assert result.returncode != 0
|
|
assert not target.exists()
|
|
|
|
|
|
def test_missing_ousast_never_blocks_the_push(tmp_path):
|
|
"""17.1 / Req 9.1: a hook whose executable is absent allows the push and says the check did not run."""
|
|
root, _, head, git = history(tmp_path)
|
|
remote = tmp_path / "remote.git"
|
|
subprocess.run(["git", "init", "--bare", str(remote)], check=True, capture_output=True)
|
|
git("remote", "add", "origin", str(remote))
|
|
hooks = root / ".hooks"
|
|
hooks.mkdir()
|
|
git("config", "core.hooksPath", ".hooks")
|
|
shutil.copyfile(Path(__file__).resolve().parents[1] / "ops/pre-push", hooks / "pre-push")
|
|
(hooks / "pre-push").chmod(0o755)
|
|
env = {**os.environ, "OUSAST_COMMAND": "ousast-not-installed-anywhere", "OUSAST_ARTIFACT_DIR": str(tmp_path / "a")}
|
|
result = subprocess.run(["git", "-C", str(root), "push", "origin", "HEAD:refs/heads/main"], env=env, capture_output=True)
|
|
assert result.returncode == 0, result.stderr
|
|
assert b"pre-push check did not run" in result.stdout + result.stderr
|
|
assert git("ls-remote", str(remote)).startswith(head)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("extra", "expected"),
|
|
[
|
|
(["--deadline", "abc"], 0),
|
|
(["--cache-dir", "INSIDE"], 0),
|
|
(["--deadline", "abc", "--mode", "blocking"], 2),
|
|
],
|
|
)
|
|
def test_hook_mode_usage_errors_are_advisory_unless_blocking(tmp_path, extra, expected):
|
|
"""17.1 / Req 9.1: invalid settings in hook mode allow the push; explicit blocking keeps exit 2."""
|
|
import sys
|
|
|
|
root, _, _, _ = history(tmp_path)
|
|
extra = [str(root / "cache") if item == "INSIDE" else item for item in extra]
|
|
command = [sys.executable, "-m", "openultrasast.cli", "pre-push", str(root), "--artifact", str(tmp_path / "r.json")]
|
|
result = subprocess.run([*command, *extra, "--remote", "origin", "/tmp/remote"], input=b"", capture_output=True, timeout=60)
|
|
assert result.returncode == expected, result.stderr
|
|
if expected == 0:
|
|
assert b"pre-push check did not run" in result.stdout
|