Files
graphify/tests/test_pi_guard_install.py
Andre Brait 3b3ad322a6 feat(pi): add a native Pi guard extension that also loads in Oh My Pi
Pi has no PreToolUse hook, so the installed hook-guard policy never ran
there. graphify/pi/ ships a Pi package (Node built-ins only, no
node_modules) that runs `graphify hook-guard read|search` before Pi's
read, grep, find and search-style bash calls: strict-mode denials become
tool_call blocks, and guidance arrives as a graphify system reminder.
Only local targets inside the project are checked, the hook uses the first
`graphify` on PATH outside the project, and every call is bounded and
fails open.

`graphify pi guard install|uninstall` delegates registration to Pi's own
`pi install|remove <dir>`; `graphify pi guard path` prints the package
directory. Oh My Pi reads the same `pi` manifest key, so
`omp plugin install "$(graphify pi guard path)"` loads it there too,
where guidance uses OMP's passive tool-call context channel.

(cherry picked from commit a6281363bb)
2026-10-13 10:27:02 +01:00

57 lines
2.2 KiB
Python

"""Pi owns registration; Graphify supplies a wheel-contained native package."""
import json
from pathlib import Path
import sys
from types import SimpleNamespace
import pytest
from graphify.install import dispatch_install_cli
try:
import tomllib
except ModuleNotFoundError: # Python 3.10
import tomli as tomllib # type: ignore[no-redef]
def test_pi_guard_path_is_a_complete_packaged_extension(monkeypatch, capsys):
monkeypatch.setattr(sys, "argv", ["graphify", "pi", "guard", "path"])
assert dispatch_install_cli("pi")
package = Path(capsys.readouterr().out.strip())
manifest = json.loads((package / "package.json").read_text(encoding="utf-8"))
entries = manifest["pi"]["extensions"]
assert entries
pyproject = tomllib.loads((package.parents[1] / "pyproject.toml").read_text(encoding="utf-8"))
package_data = pyproject["tool"]["setuptools"]["package-data"]["graphify"]
assert "pi/package.json" in package_data
for entry in entries:
assert (package / entry).is_file()
assert (package / entry).resolve().is_relative_to(package)
assert f"pi/{Path(entry).name}" in package_data
@pytest.mark.parametrize(("action", "verb"), [("install", "install"), ("uninstall", "remove")])
def test_pi_guard_delegates_to_host_and_propagates_failure(monkeypatch, action, verb):
monkeypatch.setattr(sys, "argv", ["graphify", "pi", "guard", action])
monkeypatch.setattr("graphify.install.shutil.which", lambda name: "/usr/bin/pi")
def host(argv, *, check):
assert argv[:2] == ["/usr/bin/pi", verb]
assert (Path(argv[2]) / "package.json").is_file()
return SimpleNamespace(returncode=23)
monkeypatch.setattr("subprocess.run", host)
with pytest.raises(SystemExit) as error:
dispatch_install_cli("pi")
assert error.value.code == 23
def test_pi_guard_without_host_does_not_create_configuration(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(sys, "argv", ["graphify", "pi", "guard", "install"])
monkeypatch.setattr("graphify.install.shutil.which", lambda name: None)
with pytest.raises(SystemExit) as error:
dispatch_install_cli("pi")
assert error.value.code == 1
assert not list(tmp_path.iterdir())