Files
loogle-scripts/services/loogle-mcp/scripts/test_gitea_integration.py
T

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())