mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
115 lines
4.2 KiB
Python
115 lines
4.2 KiB
Python
"""Definitive proof: a SettingsWrite from the REAL stdio MCP server, against a
|
|
REAL running backend, on a REAL session whose model is powered by a specific
|
|
API key, refuses to clear THAT key but clears a different provider's key.
|
|
|
|
Earlier coverage either drove the endpoint with an injected session (skipping the
|
|
stdio subprocess) or drove the stdio subprocess with no session (hitting the
|
|
fail-safe). This closes the gap: it boots uvicorn in-process (so the subprocess's
|
|
HTTP call lands on a backend whose `agent_manager.sessions` we can populate),
|
|
then runs `settings_meta_server.py` exactly as an agent would. No model required,
|
|
the guard is provider-routing logic, not an LLM call.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import secrets
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
import pytest
|
|
import uvicorn
|
|
|
|
from backend.main import app
|
|
|
|
SERVER = os.path.join(os.path.dirname(os.path.dirname(__file__)), "apps", "agents", "settings_meta_server.py")
|
|
|
|
|
|
def p_free_port() -> int:
|
|
s = socket.socket()
|
|
s.bind(("127.0.0.1", 0))
|
|
port = s.getsockname()[1]
|
|
s.close()
|
|
return port
|
|
|
|
|
|
@pytest.fixture
|
|
def live_backend():
|
|
import backend.auth as auth_mod
|
|
if not auth_mod.TOKEN:
|
|
auth_mod.TOKEN = secrets.token_urlsafe(32)
|
|
port = p_free_port()
|
|
server = uvicorn.Server(uvicorn.Config(app, host="127.0.0.1", port=port, log_level="error"))
|
|
thread = threading.Thread(target=server.run, daemon=True)
|
|
thread.start()
|
|
for _ in range(200):
|
|
if getattr(server, "started", False):
|
|
break
|
|
time.sleep(0.05)
|
|
assert getattr(server, "started", False), "uvicorn did not start"
|
|
yield port, auth_mod.TOKEN
|
|
server.should_exit = True
|
|
thread.join(timeout=5)
|
|
|
|
|
|
@pytest.fixture
|
|
def reset_settings():
|
|
from backend.apps.settings.settings import load_settings, save_settings
|
|
original = load_settings().model_copy(deep=True)
|
|
yield
|
|
save_settings(original)
|
|
|
|
|
|
def p_run_stdio(port: int, token: str, session_id: str, changes: dict) -> str:
|
|
env = {
|
|
**os.environ,
|
|
"OPENSWARM_PORT": str(port),
|
|
"OPENSWARM_AUTH_TOKEN": token,
|
|
"OPENSWARM_PARENT_SESSION_ID": session_id,
|
|
}
|
|
rpc = "\n".join([
|
|
json.dumps({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}),
|
|
json.dumps({"jsonrpc": "2.0", "id": 2, "method": "tools/call",
|
|
"params": {"name": "SettingsWrite", "arguments": {"changes": changes}}}),
|
|
]) + "\n"
|
|
proc = subprocess.run([sys.executable, SERVER], input=rpc, capture_output=True, text=True, env=env, timeout=30)
|
|
msgs = [json.loads(l) for l in proc.stdout.splitlines() if l.strip()]
|
|
resp = next(m for m in msgs if m.get("id") == 2)
|
|
return resp["result"]["content"][0]["text"]
|
|
|
|
|
|
def test_live_stdio_settingswrite_refuses_live_key_clears_other(live_backend, reset_settings):
|
|
port, token = live_backend
|
|
from backend.apps.settings.settings import load_settings, save_settings
|
|
from backend.apps.agents.agent_manager import agent_manager
|
|
from backend.apps.agents.core.models import AgentSession
|
|
|
|
# A real run on opus-4-8 in own_key mode: the Anthropic key powers it; an OpenAI key is also connected (the "other provider").
|
|
s = load_settings()
|
|
s.connection_mode = "own_key"
|
|
s.anthropic_api_key = "sk-ant-LIVE-do-not-clear"
|
|
s.openai_api_key = "sk-oai-OTHER-ok-to-clear"
|
|
save_settings(s)
|
|
agent_manager.sessions["live-stdio-test"] = AgentSession(id="live-stdio-test", name="t", model="opus-4-8")
|
|
|
|
try:
|
|
text = p_run_stdio(port, token, "live-stdio-test",
|
|
{"anthropic_api_key": "", "openai_api_key": "", "theme": "light"})
|
|
finally:
|
|
agent_manager.sessions.pop("live-stdio-test", None)
|
|
|
|
# The tool's own rendered result, exactly what the agent would read.
|
|
assert "Refused anthropic_api_key" in text, text
|
|
assert "powering this run" in text
|
|
assert "Applied" in text and "theme" in text
|
|
|
|
# And the truth on disk: live key kept, other cleared, benign applied.
|
|
final = load_settings()
|
|
assert final.anthropic_api_key == "sk-ant-LIVE-do-not-clear", "live key was cleared!"
|
|
assert not final.openai_api_key, "the other provider's key should have been cleared"
|
|
assert final.theme == "light"
|