mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
9515130b2a
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
245 lines
7.7 KiB
Python
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.
|