Files
basicmachines-co-basic-memory/tests/mcp/clients/test_graph_clients.py
2026-03-05 10:00:35 -06:00

217 lines
7.9 KiB
Python

"""Tests for graph and FCM typed clients."""
from unittest.mock import MagicMock
import pytest
from basic_memory.mcp.clients import FCMClient, GraphClient
class TestGraphClient:
def test_init(self):
mock_http = MagicMock()
client = GraphClient(mock_http, "project-123")
assert client.http_client is mock_http
assert client.project_id == "project-123"
assert client._base_path == "/v2/projects/project-123/graph"
@pytest.mark.asyncio
async def test_lineage(self, monkeypatch):
from basic_memory.mcp.clients import graph as graph_mod
from basic_memory.schemas.graph_intelligence import GraphLineageRequest
mock_response = MagicMock()
mock_response.json.return_value = {
"root": {"id": "specs/search", "title": "specs/search", "permalink": "specs/search"},
"paths": [],
"generated_at": "2026-03-05T00:00:00+00:00",
}
async def mock_call_post(client, url, **kwargs):
assert "/v2/projects/proj-123/graph/lineage" in url
return mock_response
monkeypatch.setattr(graph_mod, "call_post", mock_call_post)
client = GraphClient(MagicMock(), "proj-123")
result = await client.lineage(GraphLineageRequest(start="memory://specs/search"))
assert result.root.id == "specs/search"
@pytest.mark.asyncio
async def test_impact(self, monkeypatch):
from basic_memory.mcp.clients import graph as graph_mod
from basic_memory.schemas.graph_intelligence import GraphImpactRequest
mock_response = MagicMock()
mock_response.json.return_value = {
"target": {"id": "specs/search", "title": "specs/search"},
"affected": [],
"summary": {"total_considered": 0, "total_returned": 0},
}
async def mock_call_post(client, url, **kwargs):
assert "/v2/projects/proj-123/graph/impact" in url
return mock_response
monkeypatch.setattr(graph_mod, "call_post", mock_call_post)
client = GraphClient(MagicMock(), "proj-123")
result = await client.impact(GraphImpactRequest(target="memory://specs/search", horizon=2))
assert result.summary.total_returned == 0
@pytest.mark.asyncio
async def test_health(self, monkeypatch):
from basic_memory.mcp.clients import graph as graph_mod
mock_response = MagicMock()
mock_response.json.return_value = {
"metrics": {
"orphan_rate": 0.0,
"stale_central_nodes": 0,
"overloaded_hubs": 0,
"contradiction_candidates": 0,
},
"issues": [],
"computed_at": "2026-03-05T00:00:00+00:00",
}
async def mock_call_get(client, url, **kwargs):
assert "/v2/projects/proj-123/graph/health" in url
assert kwargs["params"]["scope"] == "specs"
return mock_response
monkeypatch.setattr(graph_mod, "call_get", mock_call_get)
client = GraphClient(MagicMock(), "proj-123")
result = await client.health(scope="specs", timeframe="30d")
assert result.metrics.orphan_rate == 0.0
@pytest.mark.asyncio
async def test_reindex(self, monkeypatch):
from basic_memory.mcp.clients import graph as graph_mod
from basic_memory.schemas.graph_intelligence import GraphReindexRequest
mock_response = MagicMock()
mock_response.json.return_value = {
"job_id": "job-123",
"status": "queued",
"scheduled_at": "2026-03-05T00:00:00+00:00",
}
async def mock_call_post(client, url, **kwargs):
assert "/v2/projects/proj-123/graph/reindex" in url
return mock_response
monkeypatch.setattr(graph_mod, "call_post", mock_call_post)
client = GraphClient(MagicMock(), "proj-123")
result = await client.reindex(GraphReindexRequest(mode="full"))
assert result.status == "queued"
class TestFCMClient:
def test_init(self):
mock_http = MagicMock()
client = FCMClient(mock_http, "project-123")
assert client.http_client is mock_http
assert client.project_id == "project-123"
assert client._base_path == "/v2/projects/project-123/fcm"
@pytest.mark.asyncio
async def test_simulate(self, monkeypatch):
from basic_memory.mcp.clients import fcm as fcm_mod
from basic_memory.schemas.graph_intelligence import FCMSimulateRequest
mock_response = MagicMock()
mock_response.json.return_value = {
"baseline": [{"node_id": "n1", "state": 0.0}],
"projected": [{"node_id": "n1", "state": 0.2}],
"deltas": [{"node_id": "n1", "delta": 0.2}],
"stability": {"converged": True, "iterations_used": 3, "residual": 0.0},
"confidence": 0.5,
"explanations": [],
"evidence_refs": [],
}
async def mock_call_post(client, url, **kwargs):
assert "/v2/projects/proj-123/fcm/simulate" in url
return mock_response
monkeypatch.setattr(fcm_mod, "call_post", mock_call_post)
request = FCMSimulateRequest(actions=[{"node_id": "n1", "delta": 0.2}])
result = await FCMClient(MagicMock(), "proj-123").simulate(request)
assert result.stability.converged is True
@pytest.mark.asyncio
async def test_rank_actions(self, monkeypatch):
from basic_memory.mcp.clients import fcm as fcm_mod
from basic_memory.schemas.graph_intelligence import FCMRankActionsRequest
mock_response = MagicMock()
mock_response.json.return_value = {
"goal": {"node_id": "g1", "label": "g1"},
"recommendations": [],
}
async def mock_call_post(client, url, **kwargs):
assert "/v2/projects/proj-123/fcm/rank-actions" in url
return mock_response
monkeypatch.setattr(fcm_mod, "call_post", mock_call_post)
request = FCMRankActionsRequest(goal="g1")
result = await FCMClient(MagicMock(), "proj-123").rank_actions(request)
assert result.goal.node_id == "g1"
@pytest.mark.asyncio
async def test_import_model(self, monkeypatch):
from basic_memory.mcp.clients import fcm as fcm_mod
from basic_memory.schemas.graph_intelligence import FCMImportRequest
mock_response = MagicMock()
mock_response.json.return_value = {
"import_id": "imp-1",
"nodes_loaded": 0,
"edges_loaded": 0,
"warnings": [],
"errors": [],
}
async def mock_call_post(client, url, **kwargs):
assert "/v2/projects/proj-123/fcm/import" in url
return mock_response
monkeypatch.setattr(fcm_mod, "call_post", mock_call_post)
request = FCMImportRequest(source="/tmp/model.csv")
result = await FCMClient(MagicMock(), "proj-123").import_model(request)
assert result.import_id == "imp-1"
@pytest.mark.asyncio
async def test_export_model(self, monkeypatch):
from basic_memory.mcp.clients import fcm as fcm_mod
from basic_memory.schemas.graph_intelligence import FCMExportRequest
mock_response = MagicMock()
mock_response.json.return_value = {
"export_id": "exp-1",
"format": "csv_bundle_v1",
"files": [
{"name": "nodes.csv", "path": "/tmp/nodes.csv"},
{"name": "edges.csv", "path": "/tmp/edges.csv"},
],
"node_count": 0,
"edge_count": 0,
}
async def mock_call_post(client, url, **kwargs):
assert "/v2/projects/proj-123/fcm/export" in url
return mock_response
monkeypatch.setattr(fcm_mod, "call_post", mock_call_post)
request = FCMExportRequest()
result = await FCMClient(MagicMock(), "proj-123").export_model(request)
assert result.format == "csv_bundle_v1"