fix(gemini): implement official hook contract
This commit is contained in:
1 parent
202aa1dff7
commit
653e644e4f
16 files changed
+2360
-204
No files matched your search
@@ -0,0 +1,165 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from gemini_support import (
|
||||
ElydoraApiHandler,
|
||||
managed_handler,
|
||||
prepare_fixture,
|
||||
run_handler,
|
||||
server_base_url,
|
||||
start_api_server,
|
||||
)
|
||||
|
||||
|
||||
GUARD_NAME = "elydora-guard"
|
||||
AUDIT_NAME = "elydora-audit"
|
||||
|
||||
|
||||
def official_payload(event: str, **overrides: object) -> dict[str, object]:
|
||||
value: dict[str, object] = {
|
||||
"session_id": "session-1",
|
||||
"transcript_path": "/tmp/session-1.jsonl",
|
||||
"cwd": "/tmp/project",
|
||||
"hook_event_name": event,
|
||||
"timestamp": "2026-07-19T00:00:00.000Z",
|
||||
"tool_name": "run_shell_command",
|
||||
"tool_input": {"command": "pytest"},
|
||||
}
|
||||
if event == "AfterTool":
|
||||
value["tool_response"] = {"output": "passed", "error": None}
|
||||
value.update(overrides)
|
||||
return value
|
||||
|
||||
|
||||
def encoded(value: object) -> bytes:
|
||||
return json.dumps(value, separators=(",", ":")).encode()
|
||||
|
||||
|
||||
def test_guard_accepts_active_agents_with_valid_json_stdout(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
server = start_api_server()
|
||||
try:
|
||||
fixture = prepare_fixture(
|
||||
monkeypatch, tmp_path, base_url=server_base_url(server)
|
||||
)
|
||||
fixture.install()
|
||||
result = run_handler(
|
||||
managed_handler(fixture.settings(), "BeforeTool", GUARD_NAME),
|
||||
encoded(official_payload("BeforeTool")),
|
||||
fixture,
|
||||
)
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {}
|
||||
assert [request["method"] for request in ElydoraApiHandler.requests] == ["GET"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", ["frozen", "revoked"])
|
||||
def test_guard_propagates_official_exit_code_two(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
status: str,
|
||||
) -> None:
|
||||
server = start_api_server(status=status)
|
||||
try:
|
||||
fixture = prepare_fixture(
|
||||
monkeypatch, tmp_path, base_url=server_base_url(server)
|
||||
)
|
||||
fixture.install()
|
||||
result = run_handler(
|
||||
managed_handler(fixture.settings(), "BeforeTool", GUARD_NAME),
|
||||
encoded(official_payload("BeforeTool")),
|
||||
fixture,
|
||||
)
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
assert result.returncode == 2
|
||||
assert result.stdout == b""
|
||||
assert status.encode() in result.stderr.lower()
|
||||
|
||||
|
||||
def test_audit_preserves_complete_native_after_tool_payload(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
server = start_api_server()
|
||||
try:
|
||||
fixture = prepare_fixture(
|
||||
monkeypatch, tmp_path, base_url=server_base_url(server)
|
||||
)
|
||||
fixture.install()
|
||||
payload = official_payload(
|
||||
"AfterTool",
|
||||
mcp_context={"server_name": "filesystem", "tool_name": "write_file"},
|
||||
original_request_name="write_file",
|
||||
future_provider_field={"preserved": True},
|
||||
)
|
||||
result = run_handler(
|
||||
managed_handler(fixture.settings(), "AfterTool", AUDIT_NAME),
|
||||
encoded(payload),
|
||||
fixture,
|
||||
)
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {}
|
||||
operation = next(
|
||||
request["json"]
|
||||
for request in ElydoraApiHandler.requests
|
||||
if request["method"] == "POST"
|
||||
)
|
||||
assert operation["payload"] == payload
|
||||
assert operation["subject"] == {"session_id": "session-1"}
|
||||
assert operation["action"] == {"tool": "run_shell_command"}
|
||||
|
||||
|
||||
def test_runtime_failures_stay_observable_and_fail_open(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
fixture = prepare_fixture(monkeypatch, tmp_path)
|
||||
fixture.install()
|
||||
settings = fixture.settings()
|
||||
guard = managed_handler(settings, "BeforeTool", GUARD_NAME)
|
||||
audit = managed_handler(settings, "AfterTool", AUDIT_NAME)
|
||||
|
||||
invalid_guard = run_handler(guard, b"{ malformed", fixture)
|
||||
audit_result = run_handler(
|
||||
audit, encoded(official_payload("AfterTool")), fixture
|
||||
)
|
||||
|
||||
assert invalid_guard.returncode == 0
|
||||
assert json.loads(invalid_guard.stdout) == {}
|
||||
assert b"invalid JSON" in invalid_guard.stderr
|
||||
assert audit_result.returncode == 0
|
||||
assert json.loads(audit_result.stdout) == {}
|
||||
log = (fixture.agent_dir / "error.log").read_text(encoding="utf-8")
|
||||
assert "[elydora-hook]" in log
|
||||
assert "refused" in log.lower() or "urlerror" in log.lower()
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "nt", reason="POSIX mode bits apply on POSIX")
|
||||
def test_runtime_artifacts_use_private_modes(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
fixture = prepare_fixture(monkeypatch, tmp_path)
|
||||
fixture.install()
|
||||
for path, expected in (
|
||||
(fixture.runtime_config_path, 0o600),
|
||||
(fixture.private_key_path, 0o600),
|
||||
(fixture.guard_path, 0o700),
|
||||
(fixture.hook_path, 0o700),
|
||||
(fixture.config_path, 0o600),
|
||||
):
|
||||
assert path.stat().st_mode & 0o777 == expected
|
||||
Reference in new issue
Block a user