agent/tests/test_openbrain_memory.py
Snider 5a851c2f4a feat(hermes): add openbrain_memory.py MemoryProvider plugin
Python plugin implementing Hermes MemoryProvider ABC backed by OpenBrain
(Qdrant + Postgres + PHP BrainService HTTP API). Exposes is_available,
initialize, get_tool_schemas for the four brain_* MCP tools,
handle_tool_call dispatch, sync_turn non-blocking writes, Librarian-stance
system_prompt_block, on_session_end flush.

Closes tasks.lthn.sh/view.php?id=73
Co-authored-by: Codex <noreply@openai.com>

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-23 17:12:10 +01:00

129 lines
4.1 KiB
Python

# SPDX-License-Identifier: EUPL-1.2
from __future__ import annotations
from contextlib import nullcontext
from unittest.mock import patch
from hermes.plugins.openbrain_memory import OpenBrainMemoryProvider
def make_provider() -> OpenBrainMemoryProvider:
return OpenBrainMemoryProvider(
brain_url="https://brain.example",
api_key="test-key",
qdrant_url="https://qdrant.example",
pg_dsn="postgresql://brain:secret@postgres.example:5432/openbrain",
workspace_id=73,
org="lthn",
)
def test_is_available_happy() -> None:
provider = make_provider()
with patch.object(provider, "_request_status", return_value=200), patch(
"hermes.plugins.openbrain_memory.socket.create_connection",
return_value=nullcontext(object()),
):
assert provider.is_available() is True
def test_is_available_qdrant_down() -> None:
provider = make_provider()
with patch.object(provider, "_request_status", side_effect=OSError("down")), patch(
"hermes.plugins.openbrain_memory.socket.create_connection",
return_value=nullcontext(object()),
):
assert provider.is_available() is False
def test_is_available_postgres_down() -> None:
provider = make_provider()
with patch.object(provider, "_request_status", return_value=200), patch(
"hermes.plugins.openbrain_memory.socket.create_connection",
side_effect=OSError("down"),
):
assert provider.is_available() is False
def test_get_tool_schemas_returns_four_mcp_tools() -> None:
provider = make_provider()
schemas = provider.get_tool_schemas()
assert [schema["name"] for schema in schemas] == [
"brain_remember",
"brain_recall",
"brain_forget",
"brain_list",
]
for schema in schemas:
assert isinstance(schema["description"], str)
assert schema["inputSchema"]["type"] == "object"
assert isinstance(schema["inputSchema"]["properties"], dict)
if "required" in schema["inputSchema"]:
assert isinstance(schema["inputSchema"]["required"], list)
@patch.object(OpenBrainMemoryProvider, "initialize", autospec=True)
def test_handle_tool_call_maps_tool_names_to_endpoints(mock_initialize) -> None:
provider = make_provider()
cases = [
(
"brain_remember",
{"content": "remember this", "type": "fact"},
"POST",
"https://brain.example/v1/brain/remember",
None,
),
(
"brain_recall",
{"query": "find this", "limit": 3},
"POST",
"https://brain.example/v1/brain/recall",
None,
),
(
"brain_forget",
{"id": "123e4567-e89b-12d3-a456-426614174000"},
"DELETE",
"https://brain.example/v1/brain/forget/123e4567-e89b-12d3-a456-426614174000",
None,
),
(
"brain_list",
{"project": "corepy"},
"GET",
"https://brain.example/v1/brain/list",
{"project": "corepy", "workspace_id": 73, "org": "lthn"},
),
]
for name, args, method, url, params in cases:
with patch.object(provider, "_request_json", return_value={"ok": True}) as mock_request:
result = provider.handle_tool_call(name, args)
assert result == {"ok": True}
called_method, called_url = mock_request.call_args.args[:2]
assert called_method == method
assert called_url == url
if params is None:
assert mock_request.call_args.kwargs.get("params") is None
else:
assert mock_request.call_args.kwargs["params"] == params
@patch.object(OpenBrainMemoryProvider, "initialize", autospec=True)
def test_sync_turn_does_not_raise_on_non_200(mock_initialize) -> None:
provider = make_provider()
with patch.object(provider, "_dispatch_pending_write", return_value=False), patch.object(
provider,
"_request_json",
return_value={"status": 500, "error": "service_unavailable"},
):
provider.sync_turn({"content": "turn content", "project": "corepy"})