mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
55d675e278
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
189 lines
6.9 KiB
Python
189 lines
6.9 KiB
Python
"""Integration tests for build_context memory URL validation."""
|
|
|
|
import pytest
|
|
from fastmcp import Client
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_build_context_valid_urls(mcp_server, app, test_project):
|
|
"""Test that build_context works with valid memory URLs."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a test note to ensure we have something to find
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "URL Validation Test",
|
|
"directory": "testing",
|
|
"content": "# URL Validation Test\n\nThis note tests URL validation.",
|
|
"tags": "test,validation",
|
|
},
|
|
)
|
|
|
|
# Test various valid URL formats
|
|
valid_urls = [
|
|
"memory://testing/url-validation-test", # Full memory URL
|
|
"testing/url-validation-test", # Relative path
|
|
"testing/*", # Pattern matching
|
|
]
|
|
|
|
for url in valid_urls:
|
|
result = await client.call_tool(
|
|
"build_context", {"project": test_project.name, "url": url}
|
|
)
|
|
|
|
# Should return a valid GraphContext response
|
|
assert len(result.content) == 1
|
|
response = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
assert '"results"' in response # Should contain results structure
|
|
assert '"metadata"' in response # Should contain metadata
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_build_context_invalid_urls_fail_validation(mcp_server, app, test_project):
|
|
"""Test that build_context properly validates and rejects invalid memory URLs."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Test cases: (invalid_url, expected_error_fragment)
|
|
invalid_test_cases = [
|
|
("memory//test", "double slashes"),
|
|
("invalid://test", "protocol scheme"),
|
|
("notes<brackets>", "invalid characters"),
|
|
('notes"quotes"', "invalid characters"),
|
|
]
|
|
|
|
for invalid_url, expected_error in invalid_test_cases:
|
|
with pytest.raises(Exception) as exc_info:
|
|
await client.call_tool(
|
|
"build_context", {"project": test_project.name, "url": invalid_url}
|
|
)
|
|
|
|
error_message = str(exc_info.value).lower()
|
|
assert expected_error in error_message, (
|
|
f"URL '{invalid_url}' should fail with '{expected_error}' error"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_build_context_empty_urls_fail_validation(mcp_server, app, test_project):
|
|
"""Test that empty or whitespace-only URLs fail validation."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# These should fail validation
|
|
empty_urls = [
|
|
"", # Empty string
|
|
" ", # Whitespace only
|
|
]
|
|
|
|
for empty_url in empty_urls:
|
|
with pytest.raises(Exception) as exc_info:
|
|
await client.call_tool(
|
|
"build_context", {"project": test_project.name, "url": empty_url}
|
|
)
|
|
|
|
error_message = str(exc_info.value)
|
|
# Should fail with validation error
|
|
assert (
|
|
"cannot be empty" in error_message
|
|
or "empty or whitespace" in error_message
|
|
or "value_error" in error_message
|
|
or "should be non-empty" in error_message
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_build_context_nonexistent_urls_return_empty_results(mcp_server, app, test_project):
|
|
"""Test that valid but nonexistent URLs return empty results (not errors)."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# These are valid URL formats but don't exist in the system
|
|
nonexistent_valid_urls = [
|
|
"memory://nonexistent/note",
|
|
"nonexistent/note",
|
|
"missing/*",
|
|
]
|
|
|
|
for url in nonexistent_valid_urls:
|
|
result = await client.call_tool(
|
|
"build_context", {"project": test_project.name, "url": url}
|
|
)
|
|
|
|
# Should return valid response with empty results
|
|
assert len(result.content) == 1
|
|
response = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
assert '"results":[]' in response # Empty results
|
|
assert '"primary_count":0' in response # Zero count
|
|
assert '"metadata"' in response # But should have metadata
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_build_context_error_messages_are_helpful(mcp_server, app, test_project):
|
|
"""Test that validation error messages provide helpful guidance."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Test double slash error message
|
|
with pytest.raises(Exception) as exc_info:
|
|
await client.call_tool(
|
|
"build_context", {"project": test_project.name, "url": "memory//bad"}
|
|
)
|
|
|
|
error_msg = str(exc_info.value).lower()
|
|
# Should contain validation error info
|
|
assert (
|
|
"double slashes" in error_msg
|
|
or "value_error" in error_msg
|
|
or "validation error" in error_msg
|
|
)
|
|
|
|
# Test protocol scheme error message
|
|
with pytest.raises(Exception) as exc_info:
|
|
await client.call_tool(
|
|
"build_context", {"project": test_project.name, "url": "http://example.com"}
|
|
)
|
|
|
|
error_msg = str(exc_info.value).lower()
|
|
assert (
|
|
"protocol scheme" in error_msg
|
|
or "protocol" in error_msg
|
|
or "value_error" in error_msg
|
|
or "validation error" in error_msg
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_build_context_pattern_matching_works(mcp_server, app, test_project):
|
|
"""Test that valid pattern matching URLs work correctly."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create multiple test notes
|
|
test_notes = [
|
|
("Pattern Test One", "patterns", "# Pattern Test One\n\nFirst pattern test."),
|
|
("Pattern Test Two", "patterns", "# Pattern Test Two\n\nSecond pattern test."),
|
|
("Other Note", "other", "# Other Note\n\nNot a pattern match."),
|
|
]
|
|
|
|
for title, folder, content in test_notes:
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": title,
|
|
"directory": folder,
|
|
"content": content,
|
|
},
|
|
)
|
|
|
|
# Test pattern matching
|
|
result = await client.call_tool(
|
|
"build_context", {"project": test_project.name, "url": "patterns/*"}
|
|
)
|
|
|
|
assert len(result.content) == 1
|
|
response = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
|
|
# Should find the pattern matches but not the other note
|
|
assert '"primary_count":2' in response
|
|
assert "Pattern Test" in response
|
|
assert "Other Note" not in response
|