Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""Tests for the mcp-oauth-remote-gateway optional skill.
|
||||
|
||||
Covers the diagnose-oauth-mcp.py decision tree (TOKEN_OK / REFRESH_FIXED /
|
||||
SESSION_REVOKED / REFRESH_DEAD), the HERMES_HOME resolution fallback, the
|
||||
atomic --write persistence path, and SKILL.md frontmatter invariants.
|
||||
No live network calls — urllib is mocked throughout.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
SKILL_DIR = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "optional-skills"
|
||||
/ "mcp"
|
||||
/ "mcp-oauth-remote-gateway"
|
||||
)
|
||||
SCRIPT_PATH = SKILL_DIR / "scripts" / "diagnose-oauth-mcp.py"
|
||||
SKILL_MD = SKILL_DIR / "SKILL.md"
|
||||
|
||||
|
||||
def load_module():
|
||||
spec = importlib.util.spec_from_file_location("diagnose_oauth_mcp", SCRIPT_PATH)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader is not None
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, status=200, body=b"{}", headers=None):
|
||||
self.status = status
|
||||
self._body = body
|
||||
self.headers = headers or {}
|
||||
|
||||
def read(self):
|
||||
return self._body
|
||||
|
||||
|
||||
def _write_token_files(tokens_dir: Path, server="stripe", resource="https://mcp.example.com",
|
||||
refresh_token="rt-1"):
|
||||
tokens_dir.mkdir(parents=True, exist_ok=True)
|
||||
tok = {
|
||||
"access_token": "at-stored",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": refresh_token,
|
||||
"scope": "read",
|
||||
"resource": resource,
|
||||
"expires_at": 0,
|
||||
}
|
||||
if refresh_token is None:
|
||||
del tok["refresh_token"]
|
||||
(tokens_dir / f"{server}.json").write_text(json.dumps(tok))
|
||||
(tokens_dir / f"{server}.client.json").write_text(
|
||||
json.dumps({"client_id": "cid-1", "token_endpoint_auth_method": "none"})
|
||||
)
|
||||
return tok
|
||||
|
||||
|
||||
def _run_main(mod, tokens_dir, argv, responses):
|
||||
"""Run mod.main() with urlopen mocked; returns captured stdout.
|
||||
|
||||
``responses`` is a list consumed in call order; each item is either a
|
||||
FakeResponse or an Exception to raise.
|
||||
"""
|
||||
calls = []
|
||||
|
||||
def fake_urlopen(req, timeout=None):
|
||||
calls.append(req)
|
||||
item = responses.pop(0)
|
||||
if isinstance(item, Exception):
|
||||
raise item
|
||||
return item
|
||||
|
||||
with patch.object(mod.os, "environ", dict(mod.os.environ, HERMES_HOME=str(tokens_dir.parent))), \
|
||||
patch.object(mod.urllib.request, "urlopen", side_effect=fake_urlopen), \
|
||||
patch.object(sys, "argv", ["diagnose-oauth-mcp.py", *argv]):
|
||||
# Force the env-var fallback path (ignore any importable hermes_constants).
|
||||
with patch.object(mod, "_hermes_home", lambda: str(tokens_dir.parent)):
|
||||
buf = io.StringIO()
|
||||
from contextlib import redirect_stdout
|
||||
with redirect_stdout(buf):
|
||||
mod.main()
|
||||
return buf.getvalue(), calls
|
||||
|
||||
|
||||
def _init_ok_body():
|
||||
return json.dumps({"jsonrpc": "2.0", "id": 1,
|
||||
"result": {"serverInfo": {"name": "x"}, "capabilities": {}}}).encode()
|
||||
|
||||
|
||||
def _init_revoked_error(code=401):
|
||||
body = json.dumps({"error": {"code": -32002, "message": "Session expired. Please re-authenticate."}}).encode()
|
||||
return urllib.error.HTTPError("https://mcp.example.com", code, "Unauthorized",
|
||||
{"WWW-Authenticate": 'Bearer error="invalid_token"'},
|
||||
io.BytesIO(body))
|
||||
|
||||
|
||||
def test_token_ok_branch(tmp_path):
|
||||
mod = load_module()
|
||||
tokens_dir = tmp_path / "mcp-tokens"
|
||||
_write_token_files(tokens_dir)
|
||||
out, calls = _run_main(mod, tokens_dir, ["stripe"], [FakeResponse(200, _init_ok_body())])
|
||||
assert "BRANCH=TOKEN_OK" in out
|
||||
assert len(calls) == 1 # never touched the token endpoint
|
||||
|
||||
|
||||
def test_refresh_dead_no_refresh_token(tmp_path):
|
||||
mod = load_module()
|
||||
tokens_dir = tmp_path / "mcp-tokens"
|
||||
_write_token_files(tokens_dir, refresh_token=None)
|
||||
out, _ = _run_main(mod, tokens_dir, ["stripe"], [_init_revoked_error()])
|
||||
assert "BRANCH=REFRESH_DEAD" in out
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test_requests_send_httpx_user_agent(tmp_path):
|
||||
"""Cloudflare 403s bare urllib UAs — every request must carry the httpx UA."""
|
||||
mod = load_module()
|
||||
tokens_dir = tmp_path / "mcp-tokens"
|
||||
_write_token_files(tokens_dir)
|
||||
_, calls = _run_main(mod, tokens_dir, ["stripe"], [FakeResponse(200, _init_ok_body())])
|
||||
for req in calls:
|
||||
assert req.get_header("User-agent") == mod.UA
|
||||
|
||||
|
||||
def test_skill_md_frontmatter_invariants():
|
||||
yaml = pytest.importorskip("yaml")
|
||||
content = SKILL_MD.read_text()
|
||||
assert content.startswith("---\n")
|
||||
fm = yaml.safe_load(re.search(r"^---\n(.*?)\n---", content, re.DOTALL).group(1))
|
||||
assert len(fm["description"]) <= 60
|
||||
assert fm["description"].endswith(".")
|
||||
assert "platforms" in fm and len(fm["platforms"]) >= 1
|
||||
assert fm["author"].split(",")[0].strip() != "Hermes Agent" # human credited first
|
||||
Reference in New Issue
Block a user