mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
545804f194
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
385 lines
12 KiB
Python
385 lines
12 KiB
Python
"""
|
|
Integration tests for read_content MCP tool.
|
|
|
|
Comprehensive tests covering text files, binary files, images, error cases,
|
|
and memory:// URL handling via the complete MCP client-server flow.
|
|
"""
|
|
|
|
import json
|
|
import pytest
|
|
from fastmcp import Client
|
|
from fastmcp.exceptions import ToolError
|
|
|
|
|
|
def parse_read_content_response(mcp_result):
|
|
"""Helper function to parse read_content MCP response."""
|
|
assert len(mcp_result.content) == 1
|
|
assert mcp_result.content[0].type == "text"
|
|
return json.loads(mcp_result.content[0].text)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_content_markdown_file(mcp_server, app, test_project):
|
|
"""Test reading a markdown file created by write_note."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# First create a note
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Content Test",
|
|
"directory": "test",
|
|
"content": "# Content Test\n\nThis is test content with **markdown**.",
|
|
"tags": "test,content",
|
|
},
|
|
)
|
|
|
|
# Then read the raw file content
|
|
read_result = await client.call_tool(
|
|
"read_content",
|
|
{
|
|
"project": test_project.name,
|
|
"path": "test/Content Test.md",
|
|
},
|
|
)
|
|
|
|
# Parse the response
|
|
response_data = parse_read_content_response(read_result)
|
|
|
|
assert response_data["type"] == "text"
|
|
assert response_data["content_type"] == "text/markdown; charset=utf-8"
|
|
assert response_data["encoding"] == "utf-8"
|
|
|
|
content = response_data["text"]
|
|
|
|
# Should contain the raw markdown with frontmatter
|
|
assert "# Content Test" in content
|
|
assert "This is test content with **markdown**." in content
|
|
assert "tags:" in content # frontmatter
|
|
assert "- test" in content # tags are in YAML list format
|
|
assert "- content" in content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_content_by_permalink(mcp_server, app, test_project):
|
|
"""Test reading content using permalink instead of file path."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Permalink Test",
|
|
"directory": "docs",
|
|
"content": "# Permalink Test\n\nTesting permalink-based content reading.",
|
|
},
|
|
)
|
|
|
|
# Read by permalink (without .md extension)
|
|
read_result = await client.call_tool(
|
|
"read_content",
|
|
{
|
|
"project": test_project.name,
|
|
"path": "docs/permalink-test",
|
|
},
|
|
)
|
|
|
|
# Parse the response
|
|
response_data = parse_read_content_response(read_result)
|
|
content = response_data["text"]
|
|
|
|
assert "# Permalink Test" in content
|
|
assert "Testing permalink-based content reading." in content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_content_memory_url(mcp_server, app, test_project):
|
|
"""Test reading content using memory:// URL format."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Memory URL Test",
|
|
"directory": "test",
|
|
"content": "# Memory URL Test\n\nTesting memory:// URL handling.",
|
|
"tags": "memory,url",
|
|
},
|
|
)
|
|
|
|
# Read using memory:// URL
|
|
read_result = await client.call_tool(
|
|
"read_content",
|
|
{
|
|
"project": test_project.name,
|
|
"path": "memory://test/memory-url-test",
|
|
},
|
|
)
|
|
|
|
# Parse the response
|
|
response_data = parse_read_content_response(read_result)
|
|
content = response_data["text"]
|
|
|
|
assert "# Memory URL Test" in content
|
|
assert "Testing memory:// URL handling." in content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_content_unicode_file(mcp_server, app, test_project):
|
|
"""Test reading content with unicode characters and emojis."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note with unicode content
|
|
unicode_content = (
|
|
"# Unicode Test 🚀\n\nThis note has emoji 🎉 and unicode ♠♣♥♦\n\n测试中文内容"
|
|
)
|
|
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Unicode Content Test",
|
|
"directory": "test",
|
|
"content": unicode_content,
|
|
"tags": "unicode,emoji",
|
|
},
|
|
)
|
|
|
|
# Read the content back
|
|
read_result = await client.call_tool(
|
|
"read_content",
|
|
{
|
|
"project": test_project.name,
|
|
"path": "test/Unicode Content Test.md",
|
|
},
|
|
)
|
|
|
|
# Parse the response
|
|
response_data = parse_read_content_response(read_result)
|
|
content = response_data["text"]
|
|
|
|
# All unicode content should be preserved
|
|
assert "🚀" in content
|
|
assert "🎉" in content
|
|
assert "♠♣♥♦" in content
|
|
assert "测试中文内容" in content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_content_complex_frontmatter(mcp_server, app, test_project):
|
|
"""Test reading content with complex frontmatter and markdown."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note with complex content
|
|
complex_content = """---
|
|
title: Complex Note
|
|
type: document
|
|
version: 1.0
|
|
author: Test Author
|
|
metadata:
|
|
status: draft
|
|
priority: high
|
|
---
|
|
|
|
# Complex Note
|
|
|
|
This note has complex frontmatter and various markdown elements.
|
|
|
|
## Observations
|
|
- [tech] Uses YAML frontmatter
|
|
- [design] Structured content format
|
|
|
|
## Relations
|
|
- related_to [[Other Note]]
|
|
- depends_on [[Framework]]
|
|
|
|
Regular markdown content continues here."""
|
|
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Complex Note",
|
|
"directory": "docs",
|
|
"content": complex_content,
|
|
"tags": "complex,frontmatter",
|
|
},
|
|
)
|
|
|
|
# Read the content back
|
|
read_result = await client.call_tool(
|
|
"read_content",
|
|
{
|
|
"project": test_project.name,
|
|
"path": "docs/Complex Note.md",
|
|
},
|
|
)
|
|
|
|
# Parse the response
|
|
response_data = parse_read_content_response(read_result)
|
|
content = response_data["text"]
|
|
|
|
# Should preserve all frontmatter and content structure
|
|
assert "version: 1.0" in content
|
|
assert "author: Test Author" in content
|
|
assert "status: draft" in content
|
|
assert "[tech] Uses YAML frontmatter" in content
|
|
assert "[[Other Note]]" in content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_content_missing_file(mcp_server, app, test_project):
|
|
"""Test reading a file that doesn't exist."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
try:
|
|
await client.call_tool(
|
|
"read_content",
|
|
{
|
|
"project": test_project.name,
|
|
"path": "nonexistent/file.md",
|
|
},
|
|
)
|
|
# Should not reach here - expecting an error
|
|
assert False, "Expected error for missing file"
|
|
except ToolError as e:
|
|
# Should get an appropriate error message
|
|
error_msg = str(e).lower()
|
|
assert "not found" in error_msg or "does not exist" in error_msg
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_content_empty_file(mcp_server, app, test_project):
|
|
"""Test reading an empty file."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note with minimal content
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Empty Test",
|
|
"directory": "test",
|
|
"content": "", # Empty content
|
|
},
|
|
)
|
|
|
|
# Read the content back
|
|
read_result = await client.call_tool(
|
|
"read_content",
|
|
{
|
|
"project": test_project.name,
|
|
"path": "test/Empty Test.md",
|
|
},
|
|
)
|
|
|
|
# Parse the response
|
|
response_data = parse_read_content_response(read_result)
|
|
content = response_data["text"]
|
|
|
|
# Should still have frontmatter even with empty content
|
|
assert "title: Empty Test" in content
|
|
assert f"permalink: {test_project.name}/test/empty-test" in content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_content_large_file(mcp_server, app, test_project):
|
|
"""Test reading a file with substantial content."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note with substantial content
|
|
large_content = "# Large Content Test\n\n"
|
|
|
|
# Add multiple sections with substantial text
|
|
for i in range(10):
|
|
large_content += f"""
|
|
## Section {i + 1}
|
|
|
|
This is section {i + 1} with substantial content. Lorem ipsum dolor sit amet,
|
|
consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
|
|
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.
|
|
|
|
- [note] This is observation {i + 1}
|
|
- related_to [[Section {i}]]
|
|
|
|
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
|
|
eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.
|
|
|
|
"""
|
|
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Large Content Note",
|
|
"directory": "test",
|
|
"content": large_content,
|
|
"tags": "large,content,test",
|
|
},
|
|
)
|
|
|
|
# Read the content back
|
|
read_result = await client.call_tool(
|
|
"read_content",
|
|
{
|
|
"project": test_project.name,
|
|
"path": "test/Large Content Note.md",
|
|
},
|
|
)
|
|
|
|
# Parse the response
|
|
response_data = parse_read_content_response(read_result)
|
|
content = response_data["text"]
|
|
|
|
# Should contain all sections
|
|
assert "Section 1" in content
|
|
assert "Section 10" in content
|
|
assert "Lorem ipsum" in content
|
|
assert len(content) > 1000 # Should be substantial
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_content_special_characters_in_filename(mcp_server, app, test_project):
|
|
"""Test reading files with special characters in the filename."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create notes with special characters in titles
|
|
test_cases = [
|
|
("File with spaces", "test"),
|
|
("File-with-dashes", "test"),
|
|
("File_with_underscores", "test"),
|
|
("File (with parentheses)", "test"),
|
|
("File & Symbols!", "test"),
|
|
]
|
|
|
|
for title, folder in test_cases:
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": title,
|
|
"directory": folder,
|
|
"content": f"# {title}\n\nContent for {title}",
|
|
},
|
|
)
|
|
|
|
# Read the content back using the exact filename
|
|
read_result = await client.call_tool(
|
|
"read_content",
|
|
{
|
|
"project": test_project.name,
|
|
"path": f"{folder}/{title}.md",
|
|
},
|
|
)
|
|
|
|
assert len(read_result.content) == 1
|
|
assert read_result.content[0].type == "text"
|
|
content = read_result.content[0].text
|
|
|
|
assert f"# {title}" in content
|
|
assert f"Content for {title}" in content
|