#!/usr/bin/env python3 from __future__ import annotations import json import os import sys import urllib.error import urllib.request from dataclasses import dataclass from pathlib import Path from typing import Any PLUGIN_ROOT = Path(__file__).resolve().parents[1] if str(PLUGIN_ROOT) not in sys.path: sys.path.insert(0, str(PLUGIN_ROOT)) from memory_gateway_plugin.client import MemoryGatewayClient from memory_gateway_plugin.config import PluginConfig from memory_gateway_plugin.output import debug_raw_enabled, dumps_safe, redact, short_id, summarize_data, summarize_result from memory_gateway_plugin.tools import ( memory_append_episode, memory_commit_session, memory_feedback, memory_search, memory_upsert, ) USER_ID = "test_user_memory_gateway_plugin" AGENT_ID = "test_hermes_memory_gateway_plugin" WORKSPACE_ID = "test_workspace_memory_gateway_plugin" SESSION_ID = "test_session_memory_gateway_plugin_001" def _short(value: Any, max_chars: int = 700) -> str: text = json.dumps(redact(value), ensure_ascii=False, default=str) return text[:max_chars] def _summary_data(data: dict[str, Any] | None) -> dict[str, Any]: return summarize_data(data) if isinstance(summarize_data(data), dict) else {} def _result_detail(result: dict[str, Any]) -> str: return _short(summarize_result(result)) @dataclass class Step: name: str ok: bool endpoint: str = "" status_code: int | None = None detail: str = "" data: dict[str, Any] | None = None def to_dict(self) -> dict[str, Any]: return { "name": self.name, "ok": self.ok, "endpoint": self.endpoint, "status_code": self.status_code, "detail": self.detail, "data": _summary_data(self.data), } def _request(method: str, url: str, payload: dict[str, Any] | None = None, api_key: str = "") -> dict[str, Any]: headers = {"Content-Type": "application/json"} if api_key: headers["X-API-Key"] = api_key body = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8") req = urllib.request.Request(url, data=body, headers=headers, method=method) try: with urllib.request.urlopen(req, timeout=10) as response: raw = response.read().decode("utf-8") return {"ok": True, "status_code": getattr(response, "status", 200), "data": json.loads(raw) if raw else {}} except urllib.error.HTTPError as exc: try: body_text = exc.read().decode("utf-8") except Exception: body_text = str(exc.reason) return {"ok": False, "status_code": exc.code, "error": body_text[:700]} except Exception as exc: return {"ok": False, "status_code": None, "error": str(exc)[:700]} def _health(config: PluginConfig) -> Step: endpoint = "/health" result = _request("GET", config.gateway_url.rstrip("/") + endpoint, api_key=config.api_key) return Step("health", bool(result.get("ok")), endpoint, result.get("status_code"), _result_detail(result), result.get("data")) def _ensure_user(config: PluginConfig) -> Step: endpoint = "/v1/users" result = _request( "POST", config.gateway_url.rstrip("/") + endpoint, {"user_id": USER_ID, "display_name": "Memory Gateway Plugin Integration Test", "preferences": {"purpose": "integration_test"}}, api_key=config.api_key, ) ok = bool(result.get("ok")) or result.get("status_code") in {200, 201, 409} return Step("ensure_user", ok, endpoint, result.get("status_code"), _result_detail(result), result.get("data")) def _client(config: PluginConfig) -> MemoryGatewayClient: return MemoryGatewayClient(config) def run() -> dict[str, Any]: config = PluginConfig.from_env() client = _client(config) steps: list[Step] = [] steps.append(_health(config)) if not steps[-1].ok: return {"ok": False, "steps": [s.to_dict() for s in steps]} steps.append(_ensure_user(config)) search_1 = memory_search( query="Memory Gateway plugin integration test", user_id=USER_ID, agent_id=AGENT_ID, workspace_id=WORKSPACE_ID, session_id=SESSION_ID, limit=5, client=client, ) steps.append(Step("memory_search_initial", bool(search_1.get("ok")), "/v1/memory/search", search_1.get("status_code"), _result_detail(search_1), search_1.get("data"))) episode = memory_append_episode( user_id=USER_ID, agent_id=AGENT_ID, workspace_id=WORKSPACE_ID, session_id=SESSION_ID, content="Integration test: user prefers Memory Gateway plugin to store only summarized episodes, not raw transcripts.", tags=["integration_test", "plugin"], source="agent", importance=0.2, confidence=0.5, client=client, ) steps.append(Step("memory_append_episode", bool(episode.get("ok")), "/v1/episodes", episode.get("status_code"), _result_detail(episode), episode.get("data"))) commit = memory_commit_session( user_id=USER_ID, agent_id=AGENT_ID, workspace_id=WORKSPACE_ID, session_id=SESSION_ID, promote=True, min_importance=0.1, client=client, ) commit_detail = _result_detail(commit) if commit.get("ok") and not (commit.get("data") or {}).get("promoted"): commit_detail += " | promotion may be empty while commit endpoint succeeded" steps.append(Step("memory_commit_session", bool(commit.get("ok")), f"/v1/sessions/{SESSION_ID}/commit", commit.get("status_code"), commit_detail, commit.get("data"))) upsert = memory_upsert( user_id=USER_ID, agent_id=AGENT_ID, workspace_id=WORKSPACE_ID, namespace=f"user/{USER_ID}/long_term", memory_type="preference", content="Integration test memory: this should be removable or clearly tagged as test data.", tags=["integration_test", "plugin", "safe_to_delete"], importance=0.1, confidence=0.5, source="agent", client=client, ) steps.append(Step("memory_upsert", bool(upsert.get("ok")), "/v1/memory", upsert.get("status_code"), _result_detail(upsert), upsert.get("data"))) search_2 = memory_search( query="Integration test memory summarized episodes raw transcripts", user_id=USER_ID, agent_id=AGENT_ID, workspace_id=WORKSPACE_ID, session_id=SESSION_ID, limit=10, client=client, ) result_count = len((search_2.get("data") or {}).get("results", [])) detail = _result_detail(search_2) if search_2.get("ok") and result_count == 0: detail += " | search succeeded but returned no results; indexing or OpenViking sync may be asynchronous" steps.append(Step("memory_search_after_write", bool(search_2.get("ok")), "/v1/memory/search", search_2.get("status_code"), detail, search_2.get("data"))) memory_id = ((upsert.get("data") or {}).get("memory") or upsert.get("data") or {}).get("id") if memory_id: feedback = memory_feedback( user_id=USER_ID, agent_id=AGENT_ID, workspace_id=WORKSPACE_ID, session_id=SESSION_ID, memory_id=memory_id, feedback="reject", comment="Integration test cleanup marker; safe to ignore/delete.", client=client, ) steps.append(Step("memory_feedback", bool(feedback.get("ok")), f"/v1/memory/{memory_id}/feedback", feedback.get("status_code"), _result_detail(feedback), feedback.get("data"))) else: steps.append(Step("memory_feedback", False, "/v1/memory/{memory_id}/feedback", None, "skipped because memory_upsert did not return memory id")) required = {"health", "memory_search_initial", "memory_append_episode", "memory_commit_session", "memory_upsert", "memory_search_after_write", "memory_feedback"} ok = all(step.ok for step in steps if step.name in required) return {"ok": ok, "gateway_url": config.gateway_url, "debug_raw": debug_raw_enabled(), "test_identity": {"user_id": USER_ID, "agent_id": AGENT_ID, "workspace_id": WORKSPACE_ID, "session_id": SESSION_ID}, "steps": [s.to_dict() for s in steps]} def main() -> int: result = run() print(dumps_safe(result)) return 0 if result.get("ok") else 1 if __name__ == "__main__": sys.exit(main())