mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
5da97e4820
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude <noreply@anthropic.com>
229 lines
7.4 KiB
Python
229 lines
7.4 KiB
Python
"""Tests for ChatGPT-compatible MCP tools."""
|
|
|
|
import json
|
|
import pytest
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
from basic_memory.schemas.search import SearchResponse, SearchResult, SearchItemType
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_search_successful_results():
|
|
"""Test search with successful results returns proper MCP content array format."""
|
|
# Mock successful search results
|
|
mock_results = SearchResponse(
|
|
results=[
|
|
SearchResult(
|
|
title="Test Document 1",
|
|
permalink="docs/test-doc-1",
|
|
content="This is test content for document 1",
|
|
type=SearchItemType.ENTITY,
|
|
score=1.0,
|
|
file_path="/test/docs/test-doc-1.md",
|
|
),
|
|
SearchResult(
|
|
title="Test Document 2",
|
|
permalink="docs/test-doc-2",
|
|
content="This is test content for document 2",
|
|
type=SearchItemType.ENTITY,
|
|
score=0.9,
|
|
file_path="/test/docs/test-doc-2.md",
|
|
),
|
|
],
|
|
current_page=1,
|
|
page_size=10,
|
|
)
|
|
|
|
with patch(
|
|
"basic_memory.mcp.tools.chatgpt_tools.search_notes.fn", new_callable=AsyncMock
|
|
) as mock_search:
|
|
mock_search.return_value = mock_results
|
|
|
|
# Import and call the actual function
|
|
from basic_memory.mcp.tools.chatgpt_tools import search
|
|
|
|
result = await search.fn("test query")
|
|
|
|
# Verify MCP content array format
|
|
assert isinstance(result, list)
|
|
assert len(result) == 1
|
|
assert result[0]["type"] == "text"
|
|
|
|
# Parse the JSON content
|
|
content = json.loads(result[0]["text"])
|
|
assert "results" in content
|
|
assert "query" in content
|
|
|
|
# Verify result structure
|
|
assert len(content["results"]) == 2
|
|
assert content["query"] == "test query"
|
|
|
|
# Verify individual result format
|
|
result_item = content["results"][0]
|
|
assert result_item["id"] == "docs/test-doc-1"
|
|
assert result_item["title"] == "Test Document 1"
|
|
assert result_item["url"] == "docs/test-doc-1"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_search_with_error_response():
|
|
"""Test search when underlying search_notes returns error string."""
|
|
error_message = "# Search Failed - Invalid Syntax\n\nThe search query contains errors..."
|
|
|
|
with patch(
|
|
"basic_memory.mcp.tools.chatgpt_tools.search_notes.fn", new_callable=AsyncMock
|
|
) as mock_search:
|
|
mock_search.return_value = error_message
|
|
|
|
from basic_memory.mcp.tools.chatgpt_tools import search
|
|
|
|
result = await search.fn("invalid query")
|
|
|
|
# Verify MCP content array format
|
|
assert isinstance(result, list)
|
|
assert len(result) == 1
|
|
assert result[0]["type"] == "text"
|
|
|
|
# Parse the JSON content
|
|
content = json.loads(result[0]["text"])
|
|
assert content["results"] == []
|
|
assert content["error"] == "Search failed"
|
|
assert "error_details" in content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_successful_document():
|
|
"""Test fetch with successful document retrieval."""
|
|
document_content = """# Test Document
|
|
|
|
This is the content of a test document.
|
|
|
|
## Section 1
|
|
Some content here.
|
|
|
|
## Observations
|
|
- [observation] This is a test observation
|
|
|
|
## Relations
|
|
- relates_to [[Another Document]]
|
|
"""
|
|
|
|
with patch(
|
|
"basic_memory.mcp.tools.chatgpt_tools.read_note.fn", new_callable=AsyncMock
|
|
) as mock_read:
|
|
mock_read.return_value = document_content
|
|
|
|
from basic_memory.mcp.tools.chatgpt_tools import fetch
|
|
|
|
result = await fetch.fn("docs/test-document")
|
|
|
|
# Verify MCP content array format
|
|
assert isinstance(result, list)
|
|
assert len(result) == 1
|
|
assert result[0]["type"] == "text"
|
|
|
|
# Parse the JSON content
|
|
content = json.loads(result[0]["text"])
|
|
assert content["id"] == "docs/test-document"
|
|
assert content["title"] == "Test Document" # Extracted from markdown
|
|
assert content["text"] == document_content
|
|
assert content["url"] == "docs/test-document"
|
|
assert content["metadata"]["format"] == "markdown"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_document_not_found():
|
|
"""Test fetch when document is not found."""
|
|
error_content = """# Note Not Found: "nonexistent-doc"
|
|
|
|
I couldn't find any notes matching "nonexistent-doc". Here are some suggestions:
|
|
|
|
## Check Identifier Type
|
|
- If you provided a title, try using the exact permalink instead
|
|
"""
|
|
|
|
with patch(
|
|
"basic_memory.mcp.tools.chatgpt_tools.read_note.fn", new_callable=AsyncMock
|
|
) as mock_read:
|
|
mock_read.return_value = error_content
|
|
|
|
from basic_memory.mcp.tools.chatgpt_tools import fetch
|
|
|
|
result = await fetch.fn("nonexistent-doc")
|
|
|
|
# Verify MCP content array format
|
|
assert isinstance(result, list)
|
|
assert len(result) == 1
|
|
assert result[0]["type"] == "text"
|
|
|
|
# Parse the JSON content
|
|
content = json.loads(result[0]["text"])
|
|
assert content["id"] == "nonexistent-doc"
|
|
assert content["text"] == error_content
|
|
assert content["metadata"]["error"] == "Document not found"
|
|
|
|
|
|
def test_format_search_results_for_chatgpt():
|
|
"""Test search results formatting."""
|
|
from basic_memory.mcp.tools.chatgpt_tools import _format_search_results_for_chatgpt
|
|
|
|
mock_results = SearchResponse(
|
|
results=[
|
|
SearchResult(
|
|
title="Document One",
|
|
permalink="docs/doc-one",
|
|
content="Content for document one",
|
|
type=SearchItemType.ENTITY,
|
|
score=1.0,
|
|
file_path="/test/docs/doc-one.md",
|
|
),
|
|
SearchResult(
|
|
title="", # Test empty title handling
|
|
permalink="docs/untitled",
|
|
content="Content without title",
|
|
type=SearchItemType.ENTITY,
|
|
score=0.8,
|
|
file_path="/test/docs/untitled.md",
|
|
),
|
|
],
|
|
current_page=1,
|
|
page_size=10,
|
|
)
|
|
|
|
formatted = _format_search_results_for_chatgpt(mock_results)
|
|
|
|
assert len(formatted) == 2
|
|
assert formatted[0]["id"] == "docs/doc-one"
|
|
assert formatted[0]["title"] == "Document One"
|
|
assert formatted[0]["url"] == "docs/doc-one"
|
|
|
|
# Test empty title handling
|
|
assert formatted[1]["title"] == "Untitled"
|
|
|
|
|
|
def test_format_document_for_chatgpt():
|
|
"""Test document formatting."""
|
|
from basic_memory.mcp.tools.chatgpt_tools import _format_document_for_chatgpt
|
|
|
|
content = "# Test Document\n\nThis is test content."
|
|
result = _format_document_for_chatgpt(content, "docs/test")
|
|
|
|
assert result["id"] == "docs/test"
|
|
assert result["title"] == "Test Document"
|
|
assert result["text"] == content
|
|
assert result["url"] == "docs/test"
|
|
assert result["metadata"]["format"] == "markdown"
|
|
|
|
|
|
def test_format_document_error_handling():
|
|
"""Test document formatting with error content."""
|
|
from basic_memory.mcp.tools.chatgpt_tools import _format_document_for_chatgpt
|
|
|
|
error_content = '# Note Not Found: "missing-doc"\n\nDocument not found.'
|
|
result = _format_document_for_chatgpt(error_content, "missing-doc", "Missing Doc")
|
|
|
|
assert result["id"] == "missing-doc"
|
|
assert result["title"] == "Missing Doc"
|
|
assert result["text"] == error_content
|
|
assert result["metadata"]["error"] == "Document not found"
|