Files
basicmachines-co-basic-memory/tests/mcp/test_tool_canvas.py
T
Paul Hernandez 093dab5f03 feat: Add enhanced prompts and resources (#15)
## Summary
- Add comprehensive documentation to all MCP prompt modules
- Enhance search prompt with detailed contextual output formatting
- Implement consistent logging and docstring patterns across prompt
utilities
- Fix type checking in prompt modules

## Prompts Added/Enhanced
- `search.py`: New formatted output with relevance scores, excerpts, and
next steps
- `recent_activity.py`: Enhanced with better metadata handling and
documentation
- `continue_conversation.py`: Improved context management

## Resources Added/Enhanced
- `ai_assistant_guide`: Resource with description to give to LLM to
understand how to use the tools

## Technical improvements
- Added detailed docstrings to all prompt modules explaining their
purpose and usage
- Enhanced the search prompt with rich contextual output that helps LLMs
understand results
- Created a consistent pattern for formatting output across prompts
- Improved error handling in metadata extraction
- Standardized import organization and naming conventions
- Fixed various type checking issues across the codebase

This PR is part of our ongoing effort to improve the MCP's interaction
quality with LLMs, making the system more helpful and intuitive for AI
assistants to navigate knowledge bases.

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: phernandez <phernandez@basicmachines.co>
2025-02-27 20:48:56 -06:00

274 lines
7.7 KiB
Python

"""Tests for canvas tool that exercise the full stack with SQLite."""
import json
from pathlib import Path
import pytest
from basic_memory.mcp.tools import canvas
@pytest.mark.asyncio
async def test_create_canvas(app, test_config):
"""Test creating a new canvas file.
Should:
- Create canvas file with correct content
- Create entity in database
- Return successful status
"""
# Test data
nodes = [
{
"id": "node1",
"type": "text",
"text": "Test Node",
"x": 100,
"y": 200,
"width": 400,
"height": 300,
}
]
edges = [{"id": "edge1", "fromNode": "node1", "toNode": "node2", "label": "connects to"}]
title = "test-canvas"
folder = "visualizations"
# Execute
result = await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
# Verify result message
assert result
assert "Created: visualizations/test-canvas" in result
assert "The canvas is ready to open in Obsidian" in result
# Verify file was created
file_path = Path(test_config.home) / folder / f"{title}.canvas"
assert file_path.exists()
# Verify content is correct
content = json.loads(file_path.read_text())
assert content["nodes"] == nodes
assert content["edges"] == edges
@pytest.mark.asyncio
async def test_create_canvas_with_extension(app, test_config):
"""Test creating a canvas file with .canvas extension already in the title."""
# Test data
nodes = [
{
"id": "node1",
"type": "text",
"text": "Extension Test",
"x": 100,
"y": 200,
"width": 400,
"height": 300,
}
]
edges = []
title = "extension-test.canvas" # Already has extension
folder = "visualizations"
# Execute
result = await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
# Verify
assert "Created: visualizations/extension-test.canvas" in result
# Verify file exists with correct name (shouldn't have double extension)
file_path = Path(test_config.home) / folder / title
assert file_path.exists()
# Verify content
content = json.loads(file_path.read_text())
assert content["nodes"] == nodes
@pytest.mark.asyncio
async def test_update_existing_canvas(app, test_config):
"""Test updating an existing canvas file."""
# First create a canvas
nodes = [
{
"id": "initial",
"type": "text",
"text": "Initial content",
"x": 0,
"y": 0,
"width": 200,
"height": 100,
}
]
edges = []
title = "update-test"
folder = "visualizations"
# Create initial canvas
await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
# Verify file exists
file_path = Path(test_config.home) / folder / f"{title}.canvas"
assert file_path.exists()
# Now update with new content
updated_nodes = [
{
"id": "updated",
"type": "text",
"text": "Updated content",
"x": 100,
"y": 100,
"width": 300,
"height": 200,
}
]
updated_edges = [
{"id": "new-edge", "fromNode": "updated", "toNode": "other", "label": "new connection"}
]
# Execute update
result = await canvas(nodes=updated_nodes, edges=updated_edges, title=title, folder=folder)
# Verify result indicates update
assert "Updated: visualizations/update-test.canvas" in result
# Verify content was updated
content = json.loads(file_path.read_text())
assert content["nodes"] == updated_nodes
assert content["edges"] == updated_edges
@pytest.mark.asyncio
async def test_create_canvas_with_nested_folders(app, test_config):
"""Test creating a canvas in nested folders that don't exist yet."""
# Test data
nodes = [
{
"id": "test",
"type": "text",
"text": "Nested folder test",
"x": 0,
"y": 0,
"width": 200,
"height": 100,
}
]
edges = []
title = "nested-test"
folder = "visualizations/nested/folders" # Deep path
# Execute
result = await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
# Verify
assert "Created: visualizations/nested/folders/nested-test.canvas" in result
# Verify folders and file were created
file_path = Path(test_config.home) / folder / f"{title}.canvas"
assert file_path.exists()
assert file_path.parent.exists()
@pytest.mark.asyncio
async def test_create_canvas_complex_content(app, test_config):
"""Test creating a canvas with complex content structures."""
# Test data - more complex structure with all node types
nodes = [
{
"id": "text-node",
"type": "text",
"text": "# Heading\n\nThis is a test with *markdown* formatting",
"x": 100,
"y": 100,
"width": 400,
"height": 300,
"color": "4", # Using a preset color
},
{
"id": "file-node",
"type": "file",
"file": "test/test-file.md", # Reference a file
"x": 600,
"y": 100,
"width": 400,
"height": 300,
"color": "#FF5500", # Using hex color
},
{
"id": "link-node",
"type": "link",
"url": "https://example.com",
"x": 100,
"y": 500,
"width": 400,
"height": 200,
},
{
"id": "group-node",
"type": "group",
"label": "Group Label",
"x": 600,
"y": 500,
"width": 600,
"height": 400,
},
]
edges = [
{
"id": "edge1",
"fromNode": "text-node",
"toNode": "file-node",
"label": "references",
"fromSide": "right",
"toSide": "left",
},
{
"id": "edge2",
"fromNode": "link-node",
"toNode": "group-node",
"label": "belongs to",
"color": "6",
},
]
title = "complex-test"
folder = "visualizations"
# Create a test file that we're referencing
test_file_path = Path(test_config.home) / "test/test-file.md"
test_file_path.parent.mkdir(parents=True, exist_ok=True)
test_file_path.write_text("# Test File\nThis is referenced by the canvas")
# Execute
result = await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
# Verify
assert "Created: visualizations/complex-test.canvas" in result
# Verify file was created
file_path = Path(test_config.home) / folder / f"{title}.canvas"
assert file_path.exists()
# Verify content is correct with all complex structures
content = json.loads(file_path.read_text())
assert len(content["nodes"]) == 4
assert len(content["edges"]) == 2
# Verify specific content elements are preserved
assert any(node["type"] == "text" and "#" in node["text"] for node in content["nodes"])
assert any(
node["type"] == "file" and "test-file.md" in node["file"] for node in content["nodes"]
)
assert any(node["type"] == "link" and "example.com" in node["url"] for node in content["nodes"])
assert any(
node["type"] == "group" and "Group Label" == node["label"] for node in content["nodes"]
)
# Verify edge properties
assert any(
edge["fromSide"] == "right" and edge["toSide"] == "left" for edge in content["edges"]
)
assert any(edge["label"] == "belongs to" and edge["color"] == "6" for edge in content["edges"])