Files
Paul Hernandez 9515130b2a feat: upgrade fastmcp 2.12.3 to 3.0.1 with tool annotations (#598)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:28:30 -06:00

245 lines
7.7 KiB
Python

"""Tests for resource tools that exercise the full stack with SQLite."""
import io
import base64
from PIL import Image as PILImage
import pytest
from mcp.server.fastmcp.exceptions import ToolError
from basic_memory.mcp.tools import read_content, write_note
from basic_memory.mcp.tools.read_content import (
calculate_target_params,
resize_image,
optimize_image,
)
@pytest.mark.asyncio
async def test_read_file_text_file(app, synced_files, test_project):
"""Test reading a text file.
Should:
- Correctly identify text content
- Return the content as text
- Include correct metadata
"""
# First create a text file via notes
result = await write_note(
project=test_project.name,
title="Text Resource",
directory="test",
content="This is a test text resource",
tags=["test", "resource"],
)
assert result is not None
# Now read it as a resource
response = await read_content("test/text-resource", project=test_project.name)
assert response["type"] == "text"
assert "This is a test text resource" in response["text"]
assert response["content_type"].startswith("text/")
assert response["encoding"] == "utf-8"
@pytest.mark.asyncio
async def test_read_content_file_path(app, synced_files, test_project):
"""Test reading a text file.
Should:
- Correctly identify text content
- Return the content as text
- Include correct metadata
"""
# First create a text file via notes
result = await write_note(
project=test_project.name,
title="Text Resource",
directory="test",
content="This is a test text resource",
tags=["test", "resource"],
)
assert result is not None
# Now read it as a resource
response = await read_content("test/Text Resource.md", project=test_project.name)
assert response["type"] == "text"
assert "This is a test text resource" in response["text"]
assert response["content_type"].startswith("text/")
assert response["encoding"] == "utf-8"
@pytest.mark.asyncio
async def test_read_file_image_file(app, synced_files, test_project):
"""Test reading an image file.
Should:
- Correctly identify image content
- Optimize the image
- Return base64 encoded image data
"""
# Get the path to the synced image file
image_path = synced_files["image"].name
# Read it as a resource
response = await read_content(image_path, project=test_project.name)
assert response["type"] == "image"
assert response["source"]["type"] == "base64"
assert response["source"]["media_type"] == "image/jpeg"
# Verify the image data is valid base64 that can be decoded
img_data = base64.b64decode(response["source"]["data"])
assert len(img_data) > 0
# Should be able to open as an image
img = PILImage.open(io.BytesIO(img_data))
assert img.width > 0
assert img.height > 0
@pytest.mark.asyncio
async def test_read_file_pdf_file(app, synced_files, test_project):
"""Test reading a PDF file.
Should:
- Correctly identify PDF content
- Return base64 encoded PDF data
"""
# Get the path to the synced PDF file
pdf_path = synced_files["pdf"].name
# Read it as a resource
response = await read_content(pdf_path, project=test_project.name)
assert response["type"] == "document"
assert response["source"]["type"] == "base64"
assert response["source"]["media_type"] == "application/pdf"
# Verify the PDF data is valid base64 that can be decoded
pdf_data = base64.b64decode(response["source"]["data"])
assert len(pdf_data) > 0
assert pdf_data.startswith(b"%PDF") # PDF signature
@pytest.mark.asyncio
async def test_read_file_not_found(app, test_project):
"""Test trying to read a non-existent"""
with pytest.raises(ToolError, match="Resource not found"):
await read_content("does-not-exist", project=test_project.name)
@pytest.mark.asyncio
async def test_read_file_memory_url(app, synced_files, test_project):
"""Test reading a resource using a memory:// URL."""
# Create a text file via notes
await write_note(
project=test_project.name,
title="Memory URL Test",
directory="test",
content="Testing memory:// URL handling for resources",
)
# Read it with a memory:// URL
memory_url = "memory://test/memory-url-test"
response = await read_content(memory_url, project=test_project.name)
assert response["type"] == "text"
assert "Testing memory:// URL handling for resources" in response["text"]
@pytest.mark.asyncio
async def test_read_file_memory_url_with_project_prefix(app, synced_files, test_project):
"""Test reading a resource using a memory:// URL with explicit project prefix."""
await write_note(
project=test_project.name,
title="Project Prefixed Resource URL Test",
directory="test",
content="Testing memory:// URL handling for resources with project prefix",
)
memory_url = f"memory://{test_project.name}/test/project-prefixed-resource-url-test"
response = await read_content(memory_url)
assert response["type"] == "text"
assert "Testing memory:// URL handling for resources with project prefix" in response["text"]
@pytest.mark.asyncio
async def test_image_optimization_functions(app):
"""Test the image optimization helper functions."""
# Create a test image
img = PILImage.new("RGB", (1000, 800), color="white")
# Test calculate_target_params function
# Small image
quality, size = calculate_target_params(100000)
assert quality == 70
assert size == 1000
# Medium image
quality, size = calculate_target_params(800000)
assert quality == 60
assert size == 800
# Large image
quality, size = calculate_target_params(2000000)
assert quality == 50
assert size == 600
# Test resize_image function
# Image that needs resizing
resized = resize_image(img, 500)
assert resized.width <= 500
assert resized.height <= 500
# Image that doesn't need resizing
small_img = PILImage.new("RGB", (300, 200), color="white")
resized = resize_image(small_img, 500)
assert resized.width == 300
assert resized.height == 200
# Test optimize_image function
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")
img_bytes.seek(0)
content_length = len(img_bytes.getvalue())
# In a small test image, optimization might make the image larger
# because of JPEG overhead. Let's just test that it returns something
optimized = optimize_image(img, content_length)
assert len(optimized) > 0
@pytest.mark.asyncio
async def test_image_conversion(app, synced_files, test_project):
"""Test reading an image and verify conversion works.
Should:
- Handle image content correctly
- Return optimized image data
"""
# Use the synced image file that's already part of our test fixtures
image_path = synced_files["image"].name
# Test reading the resource
response = await read_content(image_path, project=test_project.name)
assert response["type"] == "image"
assert response["source"]["media_type"] == "image/jpeg"
# Verify the image data is valid
img_data = base64.b64decode(response["source"]["data"])
img = PILImage.open(io.BytesIO(img_data))
assert img.width > 0
assert img.height > 0
assert img.mode == "RGB" # Should be in RGB mode
# Skip testing the large document size handling since it would require
# complex mocking of internal logic. We've already tested the happy path
# with the PDF file, and the error handling with our updated tool_utils tests.
# We have 100% coverage of this code in read_file.py according to the coverage report.