Ctrl K
ComunidadGuías…

tests/test_hook_strict_grep.py

Rama v8 · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Dedicated Grep shares Read's once-per-session strict orientation gate."""
import io
import json
import time

import pytest

from tests.test_hook_strict import _fixture, _invoke, _is_deny, _read


def _grep(path=None, sid="s1"):
    tool_input = {"pattern": "load_config"}
    if path is not None:
        tool_input["path"] = str(path)
    return {"session_id": sid, "tool_name": "Grep", "tool_input": tool_input}


def test_first_grep_denies_then_nudges(tmp_path, monkeypatch):
    _fixture(tmp_path, indexed=False)  # directory searches need no manifest match
    payload = _grep(tmp_path / "src")
    first = _invoke("search", payload, tmp_path, monkeypatch, strict=True)
    assert _is_deny(first)
    reason = json.loads(first)["hookSpecificOutput"]["permissionDecisionReason"]
    assert "graphify query" in reason and "Grep" in reason and "Read" not in reason
    second = _invoke("search", payload, tmp_path, monkeypatch, strict=True)
    assert not _is_deny(second) and "knowledge graph at" in second


def test_fresh_query_bypasses_grep_deny(tmp_path, monkeypatch):
    _fixture(tmp_path)
    stamp = tmp_path / "graphify-out" / "cache" / "last_query_stamp"
    stamp.parent.mkdir(parents=True)
    stamp.write_text(str(time.time()), encoding="utf-8")
    out = _invoke("search", _grep(), tmp_path, monkeypatch, strict=True)
    assert not _is_deny(out) and "knowledge graph at" in out
    assert _invoke("search", _grep(), tmp_path, monkeypatch, strict=True) == ""


@pytest.mark.parametrize("strict,env,denied", [
    (False, {}, False),
    (True, {}, True),
    (False, {"GRAPHIFY_HOOK_STRICT": "1"}, True),
    (True, {"GRAPHIFY_HOOK_STRICT": "0"}, False),
])
def test_grep_strict_flag_and_environment(tmp_path, monkeypatch, strict, env, denied):
    _fixture(tmp_path)
    out = _invoke("search", _grep(), tmp_path, monkeypatch, strict=strict, env=env)
    assert _is_deny(out) is denied
    assert "graphify query" in out


@pytest.mark.parametrize("first_kind", ["read", "search"])
def test_read_and_grep_share_one_deny(tmp_path, monkeypatch, first_kind):
    f = _fixture(tmp_path)
    calls = [("read", _read(f)), ("search", _grep())]
    if first_kind == "search":
        calls.reverse()
    first, second = calls
    assert _is_deny(_invoke(*first, tmp_path, monkeypatch, strict=True))
    out = _invoke(*second, tmp_path, monkeypatch, strict=True)
    assert not _is_deny(out) and "knowledge graph at" in out


@pytest.mark.parametrize("target", ["external", "../outside", "graphify-out", "graphify-out/graph.json"])
def test_grep_external_and_output_paths_bypass(tmp_path, monkeypatch, target):
    _fixture(tmp_path)
    path = tmp_path.parent / "outside" if target == "external" else target
    assert _invoke("search", _grep(path), tmp_path, monkeypatch, strict=True) == ""
    # Bypassed searches must not consume the session's deny.
    assert _is_deny(_invoke("search", _grep(), tmp_path, monkeypatch, strict=True))


@pytest.mark.parametrize("target", ["external", "../outside", "graphify-out", "graphify-out/graph.json"])
def test_soft_grep_suppresses_irrelevant_path_nudges(tmp_path, monkeypatch, target):
    _fixture(tmp_path)
    path = tmp_path.parent / "outside" if target == "external" else target
    assert _invoke("search", _grep(path), tmp_path, monkeypatch, strict=False) == ""
    assert "knowledge graph at" in _invoke("search", _grep("src"), tmp_path, monkeypatch, strict=False)


def test_grep_on_one_unindexed_file_keeps_the_deny(tmp_path, monkeypatch):
    _fixture(tmp_path)  # manifest indexes src/mod.py only
    (tmp_path / "pyproject.toml").write_text("[project]\n", encoding="utf-8")
    out = _invoke("search", _grep("pyproject.toml"), tmp_path, monkeypatch, strict=True)
    assert not _is_deny(out) and "knowledge graph at" in out
    # The session's one deny is still available for an indexed target.
    assert _is_deny(_invoke("search", _grep("src/mod.py"), tmp_path, monkeypatch, strict=True))


def test_grep_custom_output_bypasses(tmp_path, monkeypatch):
    from graphify import paths
    _fixture(tmp_path)
    custom = tmp_path / "artifacts" / "graphify"
    custom.mkdir(parents=True)
    (custom / "graph.json").write_text('{"nodes":[],"links":[]}', encoding="utf-8")
    monkeypatch.setattr(paths, "GRAPHIFY_OUT", str(custom))
    assert _invoke("search", _grep("artifacts/graphify"), tmp_path, monkeypatch, strict=True) == ""
    assert _invoke("search", _grep("graphify-out"), tmp_path, monkeypatch, strict=True) == ""
    assert _is_deny(_invoke("search", _grep("src"), tmp_path, monkeypatch, strict=True))


@pytest.mark.parametrize("state", ["no_session", "no_graph", "needs_update", "marker_error"])
def test_grep_safe_without_gate_prerequisites(tmp_path, monkeypatch, state):
    _fixture(tmp_path)
    payload = _grep()
    out_dir = tmp_path / "graphify-out"
    if state == "no_session":
        payload.pop("session_id")
    elif state == "no_graph":
        (out_dir / "graph.json").unlink()
    elif state == "needs_update":
        (out_dir / "needs_update").touch()
    else:
        (out_dir / "cache").write_text("not a directory", encoding="utf-8")
    out = _invoke("search", payload, tmp_path, monkeypatch, strict=True)
    assert not _is_deny(out)
    assert not (out_dir / "cache" / "hook_sessions" / "s1.denied").exists()


@pytest.mark.parametrize("payload", [
    b"{not json", [], {"tool_input": []},
    {"session_id": "s1", "tool_name": "Grep", "tool_input": {"pattern": ["foo"]}},
    {"session_id": "s1", "tool_name": "Grep", "tool_input": {"pattern": "foo", "path": ["src"]}},
])
def test_grep_bad_payload_fails_open(tmp_path, monkeypatch, payload):
    _fixture(tmp_path)
    out = _invoke("search", payload, tmp_path, monkeypatch, strict=True)
    assert not _is_deny(out)


def test_non_grep_tool_with_a_pattern_cannot_consume_the_deny(tmp_path, monkeypatch):
    _fixture(tmp_path)
    payload = _grep()
    payload["tool_name"] = "Bash"
    assert _invoke("search", payload, tmp_path, monkeypatch, strict=True) == ""
    assert _is_deny(_invoke("search", _grep(), tmp_path, monkeypatch, strict=True))


@pytest.mark.parametrize("command", ["grep -rn foo .", "rg foo .", "git grep foo"])
def test_bash_search_stays_advisory_without_consuming_deny(tmp_path, monkeypatch, command):
    _fixture(tmp_path)
    payload = {"session_id": "s1", "tool_name": "Bash", "tool_input": {"command": command}}
    out = _invoke("search", payload, tmp_path, monkeypatch, strict=True)
    assert not _is_deny(out) and "knowledge graph at" in out
    assert _is_deny(_invoke("search", _grep(), tmp_path, monkeypatch, strict=True))


def test_cli_search_passes_strict_flag(tmp_path, monkeypatch, capsys):
    import graphify.cli as cli
    import sys
    _fixture(tmp_path)
    monkeypatch.chdir(tmp_path)
    monkeypatch.setattr(sys, "argv", ["graphify", "hook-guard", "search", "--strict"])
    monkeypatch.setattr(sys, "stdin", io.TextIOWrapper(io.BytesIO(json.dumps(_grep()).encode())))
    with pytest.raises(SystemExit) as exc:
        cli.dispatch_command("hook-guard")
    assert exc.value.code == 0 and _is_deny(capsys.readouterr().out)