Files
basicmachines-co-basic-memory/test-int/mcp/test_read_note_integration.py
2026-05-01 05:15:44 -05:00

171 lines
6.1 KiB
Python

"""
Integration tests for read_note MCP tool.
Tests the full flow: MCP client -> MCP server -> FastAPI -> database
"""
import pytest
from fastmcp import Client
@pytest.mark.asyncio
async def test_read_note_after_write(mcp_server, app, test_project):
"""Test read_note after write_note using real database."""
async with Client(mcp_server) as client:
# First write a note
write_result = await client.call_tool(
"write_note",
{
"project": test_project.name,
"title": "Test Note",
"directory": "test",
"content": "# Test Note\n\nThis is test content.",
"tags": "test,integration",
},
)
assert len(write_result.content) == 1
assert write_result.content[0].type == "text"
assert "Test Note.md" in write_result.content[0].text
# Then read it back
read_result = await client.call_tool(
"read_note",
{
"project": test_project.name,
"identifier": "Test Note",
},
)
assert len(read_result.content) == 1
assert read_result.content[0].type == "text"
result_text = read_result.content[0].text
# Should contain the note content and metadata
assert "# Test Note" in result_text
assert "This is test content." in result_text
assert f"{test_project.name}/test/test-note" in result_text # permalink
@pytest.mark.asyncio
async def test_read_note_underscored_folder_by_permalink(mcp_server, app, test_project):
"""Test read_note with permalink from underscored folder.
Reproduces bug #416: read_note fails to find notes when given permalinks
from underscored folder names (e.g., _archive/, _drafts/), even though
the permalink is copied directly from the note's YAML frontmatter.
"""
async with Client(mcp_server) as client:
# Create a note in an underscored folder
write_result = await client.call_tool(
"write_note",
{
"project": test_project.name,
"title": "Example Note",
"directory": "_archive/articles",
"content": "# Example Note\n\nThis is a test note in an underscored folder.",
"tags": "test,archive",
},
)
assert len(write_result.content) == 1
assert write_result.content[0].type == "text"
write_text = write_result.content[0].text
# Verify the file path includes the underscore
assert "_archive/articles/Example Note.md" in write_text
# Verify the permalink has underscores stripped (this is the expected behavior)
assert f"{test_project.name}/archive/articles/example-note" in write_text
# Now try to read the note using the permalink (without underscores)
# This is the exact scenario from the bug report - using the permalink
# that was generated in the YAML frontmatter
read_result = await client.call_tool(
"read_note",
{
"project": test_project.name,
"identifier": "archive/articles/example-note", # permalink without underscores
},
)
# This should succeed - the note should be found by its permalink
assert len(read_result.content) == 1
assert read_result.content[0].type == "text"
result_text = read_result.content[0].text
# Should contain the note content
assert "# Example Note" in result_text
assert "This is a test note in an underscored folder." in result_text
assert f"{test_project.name}/archive/articles/example-note" in result_text # permalink
@pytest.mark.asyncio
async def test_read_note_by_project_id(mcp_server, app, test_project):
"""Read a note by passing project_id (UUID) instead of project name.
Verifies the project_id parameter routes through get_project_client correctly
in pure local mode (no cloud creds), where get_project_mode() would otherwise
default unknown identifiers to CLOUD and break routing.
"""
async with Client(mcp_server) as client:
await client.call_tool(
"write_note",
{
"project": test_project.name,
"title": "By ID Note",
"directory": "test",
"content": "# By ID Note\n\nLooked up by external_id.",
},
)
# Read by external_id (UUID) instead of project name
read_result = await client.call_tool(
"read_note",
{
"project_id": test_project.external_id,
"identifier": "By ID Note",
},
)
assert len(read_result.content) == 1
assert read_result.content[0].type == "text"
result_text = read_result.content[0].text
assert "# By ID Note" in result_text
assert "Looked up by external_id." in result_text
@pytest.mark.asyncio
async def test_read_note_project_id_takes_precedence_over_name(mcp_server, app, test_project):
"""When project_id is passed alongside a wrong project name, project_id wins."""
async with Client(mcp_server) as client:
await client.call_tool(
"write_note",
{
"project": test_project.name,
"title": "Precedence Note",
"directory": "test",
"content": "# Precedence Note\n\nproject_id wins.",
},
)
# Pass an obviously-wrong project name alongside the correct project_id.
# If project_id takes precedence (as documented), the read still succeeds.
read_result = await client.call_tool(
"read_note",
{
"project": "this-project-does-not-exist",
"project_id": test_project.external_id,
"identifier": "Precedence Note",
},
)
assert len(read_result.content) == 1
result_text = read_result.content[0].text
assert "# Precedence Note" in result_text
assert "project_id wins." in result_text