259 lines
8.1 KiB
Python
Executable File
259 lines
8.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""Test integrazione Gitea MCP — mock server + opzionale live API."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import threading
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
from urllib.parse import parse_qs, urlparse
|
|
|
|
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
if ROOT not in sys.path:
|
|
sys.path.insert(0, ROOT)
|
|
|
|
MOCK_TOKEN = "test-gitea-token"
|
|
MOCK_PORT = 18799
|
|
|
|
os.environ["GITEA_URL"] = "https://git.loogle.it"
|
|
os.environ["GITEA_API_URL"] = f"http://127.0.0.1:{MOCK_PORT}"
|
|
os.environ["GITEA_API_TOKEN_DANIELE"] = MOCK_TOKEN
|
|
os.environ["MCP_DB"] = "/tmp/loogle_mcp_test.db"
|
|
|
|
|
|
class GiteaMockHandler(BaseHTTPRequestHandler):
|
|
def log_message(self, fmt, *args):
|
|
return
|
|
|
|
def _auth_ok(self) -> bool:
|
|
auth = self.headers.get("Authorization", "")
|
|
return auth == f"token {MOCK_TOKEN}"
|
|
|
|
def _json(self, code: int, payload):
|
|
body = json.dumps(payload).encode()
|
|
self.send_response(code)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def do_GET(self):
|
|
if not self._auth_ok():
|
|
self._json(401, {"message": "invalid token"})
|
|
return
|
|
path = urlparse(self.path).path
|
|
qs = parse_qs(urlparse(self.path).query)
|
|
|
|
if path == "/api/v1/user/repos":
|
|
self._json(200, [
|
|
{
|
|
"full_name": "daniele/rete",
|
|
"description": "Infra HA",
|
|
"private": True,
|
|
"html_url": "https://git.loogle.it/daniele/rete",
|
|
"default_branch": "main",
|
|
"updated_at": "2026-08-22T10:00:00Z",
|
|
}
|
|
])
|
|
return
|
|
|
|
if path == "/api/v1/repos/daniele/rete/contents/ha/RUNBOOK-failover.md":
|
|
content = "# Failover tier-b\n\nloogle-mcp nel pivot tier-b.\n"
|
|
import base64
|
|
|
|
self._json(
|
|
200,
|
|
{
|
|
"type": "file",
|
|
"path": "ha/RUNBOOK-failover.md",
|
|
"content": base64.b64encode(content.encode()).decode(),
|
|
"encoding": "base64",
|
|
"size": len(content),
|
|
"sha": "abc123",
|
|
"html_url": "https://git.loogle.it/daniele/rete/src/branch/main/ha/RUNBOOK-failover.md",
|
|
},
|
|
)
|
|
return
|
|
|
|
if path == "/api/v1/search/code":
|
|
q = (qs.get("q") or [""])[0]
|
|
self._json(
|
|
200,
|
|
{
|
|
"data": [
|
|
{
|
|
"repository": {"full_name": "daniele/rete"},
|
|
"path": "ha/RUNBOOK-failover.md",
|
|
"sha": "abc123",
|
|
"content": f"...{q}...",
|
|
}
|
|
]
|
|
},
|
|
)
|
|
return
|
|
|
|
if path == "/api/v1/repos/daniele/rete/issues":
|
|
self._json(
|
|
200,
|
|
[
|
|
{
|
|
"number": 1,
|
|
"title": "Test issue",
|
|
"state": "open",
|
|
"user": {"login": "daniele"},
|
|
"html_url": "https://git.loogle.it/daniele/rete/issues/1",
|
|
"created_at": "2026-08-22T10:00:00Z",
|
|
"updated_at": "2026-08-22T10:00:00Z",
|
|
"labels": [],
|
|
}
|
|
],
|
|
)
|
|
return
|
|
|
|
if path == "/api/v1/repos/daniele/rete/issues/1":
|
|
self._json(
|
|
200,
|
|
{
|
|
"number": 1,
|
|
"title": "Test issue",
|
|
"state": "open",
|
|
"body": "Corpo issue di test",
|
|
"user": {"login": "daniele"},
|
|
"html_url": "https://git.loogle.it/daniele/rete/issues/1",
|
|
"created_at": "2026-08-22T10:00:00Z",
|
|
"updated_at": "2026-08-22T10:00:00Z",
|
|
"labels": [],
|
|
},
|
|
)
|
|
return
|
|
|
|
self._json(404, {"message": f"not found: {path}"})
|
|
|
|
def do_POST(self):
|
|
if not self._auth_ok():
|
|
self._json(401, {"message": "invalid token"})
|
|
return
|
|
path = urlparse(self.path).path
|
|
length = int(self.headers.get("Content-Length", 0))
|
|
body = json.loads(self.rfile.read(length).decode() or "{}")
|
|
if path == "/api/v1/repos/daniele/rete/issues":
|
|
self._json(
|
|
201,
|
|
{
|
|
"number": 42,
|
|
"title": body.get("title"),
|
|
"state": "open",
|
|
"html_url": "https://git.loogle.it/daniele/rete/issues/42",
|
|
},
|
|
)
|
|
return
|
|
self._json(404, {"message": f"not found: {path}"})
|
|
|
|
|
|
def run_mock_tests() -> None:
|
|
from app.db import init_db
|
|
from app.knowledge import gitea as gitea_mod
|
|
from app.mcp import tools
|
|
|
|
init_db()
|
|
gitea_mod._tokens_cache = None
|
|
|
|
server = HTTPServer(("127.0.0.1", MOCK_PORT), GiteaMockHandler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
|
|
claims = {"sub": "daniele", "scope": "gitea:read gitea:write"}
|
|
|
|
repos = tools.call_tool("list_repos", {}, claims)
|
|
assert "daniele/rete" in repos["content"][0]["text"]
|
|
|
|
file_data = tools.call_tool(
|
|
"get_file",
|
|
{"repo": "daniele/rete", "path": "ha/RUNBOOK-failover.md"},
|
|
claims,
|
|
)
|
|
assert "tier-b" in file_data["content"][0]["text"]
|
|
|
|
search = tools.call_tool(
|
|
"search_code",
|
|
{"query": "tier-b", "repo": "daniele/rete"},
|
|
claims,
|
|
)
|
|
assert "RUNBOOK-failover" in search["content"][0]["text"]
|
|
|
|
issues = tools.call_tool("list_issues", {"repo": "daniele/rete"}, claims)
|
|
assert "Test issue" in issues["content"][0]["text"]
|
|
|
|
issue = tools.call_tool(
|
|
"get_issue",
|
|
{"repo": "daniele/rete", "number": 1},
|
|
claims,
|
|
)
|
|
assert "Corpo issue" in issue["content"][0]["text"]
|
|
|
|
created = tools.call_tool(
|
|
"create_issue",
|
|
{"repo": "daniele/rete", "title": "Da MCP", "body": "Test"},
|
|
claims,
|
|
)
|
|
assert "42" in created["content"][0]["text"]
|
|
|
|
server.shutdown()
|
|
print("OK mock: list_repos, get_file, search_code, list_issues, get_issue, create_issue")
|
|
|
|
|
|
def _load_dotenv() -> None:
|
|
env_path = os.path.join(ROOT, ".env")
|
|
if not os.path.isfile(env_path):
|
|
return
|
|
with open(env_path, encoding="utf-8") as fh:
|
|
for line in fh:
|
|
line = line.strip()
|
|
if not line or line.startswith("#") or "=" not in line:
|
|
continue
|
|
key, _, value = line.partition("=")
|
|
key = key.strip()
|
|
value = value.strip().strip("'").strip('"')
|
|
if key.startswith("GITEA_"):
|
|
os.environ[key] = value
|
|
|
|
|
|
def run_live_tests() -> None:
|
|
_load_dotenv()
|
|
from app.knowledge import gitea as gitea_mod
|
|
from app.mcp import tools
|
|
|
|
token = os.environ.get("GITEA_API_TOKEN_DANIELE", "").strip()
|
|
if not token or token == MOCK_TOKEN:
|
|
print("SKIP live: GITEA_API_TOKEN_DANIELE non impostato (token reale in .env)")
|
|
return
|
|
|
|
api_url = gitea_mod.api_base_url()
|
|
if str(MOCK_PORT) in api_url:
|
|
print("SKIP live: GITEA_API_URL punta al mock")
|
|
return
|
|
|
|
gitea_mod._tokens_cache = None
|
|
claims = {"sub": "daniele", "scope": "gitea:read gitea:write"}
|
|
|
|
repos = gitea_mod.list_repos(username="daniele")
|
|
if not repos.get("repos"):
|
|
raise RuntimeError("live list_repos: nessun repository")
|
|
|
|
first = repos["repos"][0]["full_name"]
|
|
tools.call_tool("list_repos", {}, claims)
|
|
print(f"OK live list_repos: {len(repos['repos'])} repo, primo={first}")
|
|
|
|
|
|
def main() -> int:
|
|
run_mock_tests()
|
|
run_live_tests()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|