ReMe/tests/integration/test_codex_plugin_install.py

102 lines
3.7 KiB
Python

"""Opt-in native plugin discovery regression, without model calls or hook execution.
Run with REME_CODEX_BIN=/path/to/codex pytest tests/integration/test_codex_plugin_install.py.
The selected runtime must support the hooks/list app-server API.
"""
# pylint: disable=missing-function-docstring
import json
import os
import queue
import subprocess
import threading
from pathlib import Path
import pytest
MARKETPLACE = Path(__file__).resolve().parents[2] / "integrations/codex"
EVENTS = {"sessionStart", "sessionEnd", "userPromptSubmit", "stop", "subagentStop"}
def test_installed_plugin_exposes_all_hooks_before_trust(tmp_path):
binary = os.environ.get("REME_CODEX_BIN")
if not binary:
pytest.skip("Set REME_CODEX_BIN to run the native Codex installation check")
home = tmp_path / "codex-home"
home.mkdir()
env = {**os.environ, "CODEX_HOME": str(home)}
for args in (
["plugin", "marketplace", "add", str(MARKETPLACE)],
["plugin", "add", "reme@reme-codex"],
):
result = subprocess.run([binary, *args], env=env, capture_output=True, text=True, check=False, timeout=60)
assert result.returncode == 0, result.stderr
with (
(tmp_path / "app-server.log").open("w", encoding="utf-8") as stderr,
subprocess.Popen(
[binary, "app-server"],
env=env,
cwd=tmp_path,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=stderr,
text=True,
) as process,
):
responses = queue.Queue()
def receive():
for line in process.stdout:
responses.put(json.loads(line))
reader = threading.Thread(target=receive, daemon=True)
reader.start()
def request(request_id, method, params):
process.stdin.write(json.dumps({"id": request_id, "method": method, "params": params}) + "\n")
process.stdin.flush()
while True:
response = responses.get(timeout=30)
if response.get("id") == request_id:
assert "error" not in response, response
return response["result"]
try:
request(
0,
"initialize",
{
"clientInfo": {"name": "reme-install-test", "version": "1.0"},
"capabilities": {"experimentalApi": True},
},
)
process.stdin.write(json.dumps({"method": "initialized", "params": {}}) + "\n")
process.stdin.flush()
details = request(
1,
"plugin/read",
{"marketplacePath": str(MARKETPLACE / ".agents/plugins/marketplace.json"), "pluginName": "reme"},
)["plugin"]
assert details["summary"]["installed"] and details["summary"]["enabled"]
assert details["mcpServers"] == ["reme"]
assert {hook["eventName"] for hook in details["hooks"]} == EVENTS
entries = request(2, "hooks/list", {"cwds": [str(tmp_path)]})["data"]
assert len(entries) == 1
assert not entries[0]["errors"]
hooks = [hook for hook in entries[0]["hooks"] if hook.get("pluginId") == "reme@reme-codex"]
assert len(hooks) == 7
assert {hook["eventName"] for hook in hooks} == EVENTS
assert all(hook["enabled"] and hook["trustStatus"] == "untrusted" for hook in hooks)
finally:
process.stdin.close()
process.terminate()
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=10)
reader.join(timeout=5)
process.stdout.close()