mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
9809b469c6
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
475 lines
15 KiB
Python
475 lines
15 KiB
Python
"""
|
|
Integration tests for delete_note MCP tool.
|
|
|
|
Tests the complete delete note workflow: MCP client -> MCP server -> FastAPI -> database
|
|
"""
|
|
|
|
import pytest
|
|
from fastmcp import Client
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_note_by_title(mcp_server, app, test_project):
|
|
"""Test deleting a note by its title."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# First create a note
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Note to Delete",
|
|
"directory": "test",
|
|
"content": "# Note to Delete\n\nThis note will be deleted.",
|
|
"tags": "test,delete",
|
|
},
|
|
)
|
|
|
|
# Verify the note exists by reading it
|
|
read_result = await client.call_tool(
|
|
"read_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "Note to Delete",
|
|
},
|
|
)
|
|
assert len(read_result.content) == 1
|
|
assert "Note to Delete" in read_result.content[0].text
|
|
|
|
# Delete the note by title
|
|
delete_result = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "Note to Delete",
|
|
},
|
|
)
|
|
|
|
# Should return True for successful deletion
|
|
assert len(delete_result.content) == 1
|
|
assert delete_result.content[0].type == "text"
|
|
assert "true" in delete_result.content[0].text.lower()
|
|
|
|
# Verify the note no longer exists
|
|
read_after_delete = await client.call_tool(
|
|
"read_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "Note to Delete",
|
|
},
|
|
)
|
|
|
|
# Should return helpful "Note Not Found" message instead of the actual note
|
|
assert len(read_after_delete.content) == 1
|
|
result_text = read_after_delete.content[0].text
|
|
assert "Note Not Found" in result_text
|
|
assert "Note to Delete" in result_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_note_by_permalink(mcp_server, app, test_project):
|
|
"""Test deleting a note by its permalink."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Permalink Delete Test",
|
|
"directory": "tests",
|
|
"content": "# Permalink Delete Test\n\nTesting deletion by permalink.",
|
|
"tags": "test,permalink",
|
|
},
|
|
)
|
|
|
|
# Delete the note by permalink
|
|
delete_result = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "tests/permalink-delete-test",
|
|
},
|
|
)
|
|
|
|
# Should return True for successful deletion
|
|
assert len(delete_result.content) == 1
|
|
assert "true" in delete_result.content[0].text.lower()
|
|
|
|
# Verify the note no longer exists by searching
|
|
search_result = await client.call_tool(
|
|
"search_notes",
|
|
{
|
|
"project": test_project.name,
|
|
"query": "Permalink Delete Test",
|
|
},
|
|
)
|
|
|
|
# Default text format returns "No results found" when empty
|
|
assert "No results found" in search_result.content[0].text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_note_with_observations_and_relations(mcp_server, app, test_project):
|
|
"""Test deleting a note that has observations and relations."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a complex note with observations and relations
|
|
complex_content = """# Project Management System
|
|
|
|
This is a comprehensive project management system.
|
|
|
|
## Observations
|
|
- [feature] Task tracking functionality
|
|
- [feature] User authentication system
|
|
- [tech] Built with Python and Flask
|
|
- [status] Currently in development
|
|
|
|
## Relations
|
|
- depends_on [[Database Schema]]
|
|
- implements [[User Stories]]
|
|
- part_of [[Main Application]]
|
|
|
|
The system handles multiple projects and users."""
|
|
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Project Management System",
|
|
"directory": "projects",
|
|
"content": complex_content,
|
|
"tags": "project,management,system",
|
|
},
|
|
)
|
|
|
|
# Verify the note exists and has content
|
|
read_result = await client.call_tool(
|
|
"read_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "Project Management System",
|
|
},
|
|
)
|
|
assert len(read_result.content) == 1
|
|
result_text = read_result.content[0].text
|
|
assert "Task tracking functionality" in result_text
|
|
assert "depends_on" in result_text
|
|
|
|
# Delete the complex note
|
|
delete_result = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "projects/project-management-system",
|
|
},
|
|
)
|
|
|
|
# Should return True for successful deletion
|
|
assert "true" in delete_result.content[0].text.lower()
|
|
|
|
# Verify the note and all its components are deleted
|
|
read_after_delete_2 = await client.call_tool(
|
|
"read_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "Project Management System",
|
|
},
|
|
)
|
|
|
|
# Should return "Note Not Found" message
|
|
assert len(read_after_delete_2.content) == 1
|
|
result_text = read_after_delete_2.content[0].text
|
|
assert "Note Not Found" in result_text
|
|
assert "Project Management System" in result_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_note_special_characters_in_title(mcp_server, app, test_project):
|
|
"""Test deleting notes with special characters in the title."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create notes with special characters
|
|
special_titles = [
|
|
"Note with spaces",
|
|
"Note-with-dashes",
|
|
"Note_with_underscores",
|
|
"Note (with parentheses)",
|
|
"Note & Symbols!",
|
|
]
|
|
|
|
# Create all the notes
|
|
for title in special_titles:
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": title,
|
|
"directory": "special",
|
|
"content": f"# {title}\n\nContent for {title}",
|
|
"tags": "special,characters",
|
|
},
|
|
)
|
|
|
|
# Delete each note by title
|
|
for title in special_titles:
|
|
delete_result = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": title,
|
|
},
|
|
)
|
|
|
|
# Should return True for successful deletion
|
|
assert "true" in delete_result.content[0].text.lower(), (
|
|
f"Failed to delete note: {title}"
|
|
)
|
|
|
|
# Verify the note is deleted
|
|
read_after_delete = await client.call_tool(
|
|
"read_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": title,
|
|
},
|
|
)
|
|
|
|
# Should return "Note Not Found" message
|
|
assert len(read_after_delete.content) == 1
|
|
result_text = read_after_delete.content[0].text
|
|
assert "Note Not Found" in result_text
|
|
assert title in result_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_nonexistent_note(mcp_server, app, test_project):
|
|
"""Test attempting to delete a note that doesn't exist."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Try to delete a note that doesn't exist
|
|
delete_result = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "Nonexistent Note",
|
|
},
|
|
)
|
|
|
|
# Should return False for unsuccessful deletion
|
|
assert len(delete_result.content) == 1
|
|
assert "false" in delete_result.content[0].text.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_note_by_file_path(mcp_server, app, test_project):
|
|
"""Test deleting a note using its file path."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "File Path Delete",
|
|
"directory": "docs",
|
|
"content": "# File Path Delete\n\nTesting deletion by file path.",
|
|
"tags": "test,filepath",
|
|
},
|
|
)
|
|
|
|
# Try to delete using the file path (should work as an identifier)
|
|
delete_result = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "docs/File Path Delete.md",
|
|
},
|
|
)
|
|
|
|
# Should return True for successful deletion
|
|
assert "true" in delete_result.content[0].text.lower()
|
|
|
|
# Verify deletion
|
|
read_after_delete = await client.call_tool(
|
|
"read_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "File Path Delete",
|
|
},
|
|
)
|
|
|
|
# Should return "Note Not Found" message
|
|
assert len(read_after_delete.content) == 1
|
|
result_text = read_after_delete.content[0].text
|
|
assert "Note Not Found" in result_text
|
|
assert "File Path Delete" in result_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_note_rejects_case_mismatch(mcp_server, app, test_project):
|
|
"""Test that delete_note with wrong case does not fuzzy-match to an existing note.
|
|
|
|
Strict resolution (#649) prevents destructive operations from silently
|
|
resolving to a different note via fuzzy search. Case-mismatched titles
|
|
should be rejected, not resolved to the nearest match.
|
|
"""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note with mixed case
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "CamelCase Note Title",
|
|
"directory": "test",
|
|
"content": "# CamelCase Note Title\n\nTesting case sensitivity.",
|
|
"tags": "test,case",
|
|
},
|
|
)
|
|
|
|
# Try to delete with different case — should NOT find the note
|
|
delete_result = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "camelcase note title",
|
|
},
|
|
)
|
|
|
|
# Should return False (not found) — strict mode rejects fuzzy matches
|
|
assert "false" in delete_result.content[0].text.lower()
|
|
|
|
# Verify the note still exists using the exact title
|
|
read_result = await client.call_tool(
|
|
"read_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "CamelCase Note Title",
|
|
},
|
|
)
|
|
assert "Testing case sensitivity" in read_result.content[0].text
|
|
|
|
# Delete with exact title should succeed
|
|
delete_result2 = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "CamelCase Note Title",
|
|
},
|
|
)
|
|
assert "true" in delete_result2.content[0].text.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_multiple_notes_sequentially(mcp_server, app, test_project):
|
|
"""Test deleting multiple notes in sequence."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create multiple notes
|
|
note_titles = [
|
|
"First Note",
|
|
"Second Note",
|
|
"Third Note",
|
|
"Fourth Note",
|
|
"Fifth Note",
|
|
]
|
|
|
|
for title in note_titles:
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": title,
|
|
"directory": "batch",
|
|
"content": f"# {title}\n\nContent for {title}",
|
|
"tags": "batch,test",
|
|
},
|
|
)
|
|
|
|
# Delete all notes sequentially
|
|
for title in note_titles:
|
|
delete_result = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": title,
|
|
},
|
|
)
|
|
|
|
# Each deletion should be successful
|
|
assert "true" in delete_result.content[0].text.lower(), f"Failed to delete {title}"
|
|
|
|
# Verify all notes are deleted by searching
|
|
search_result = await client.call_tool(
|
|
"search_notes",
|
|
{
|
|
"project": test_project.name,
|
|
"query": "batch",
|
|
},
|
|
)
|
|
|
|
# Default text format returns "No results found" when empty
|
|
assert "No results found" in search_result.content[0].text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_note_with_unicode_content(mcp_server, app, test_project):
|
|
"""Test deleting notes with Unicode content."""
|
|
|
|
async with Client(mcp_server) as client:
|
|
# Create a note with Unicode content
|
|
unicode_content = """# Unicode Test Note 🚀
|
|
|
|
This note contains various Unicode characters:
|
|
- Emojis: 🎉 🔥 ⚡ 💡
|
|
- Languages: 测试中文 Tëst Übër
|
|
- Symbols: ♠♣♥♦ ←→↑↓ ∞≠≤≥
|
|
- Math: ∑∏∂∇∆Ω
|
|
|
|
## Observations
|
|
- [test] Unicode characters preserved ✓
|
|
- [note] Emoji support working 🎯
|
|
|
|
## Relations
|
|
- supports [[Unicode Standards]]
|
|
- tested_with [[Various Languages]]"""
|
|
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"project": test_project.name,
|
|
"title": "Unicode Test Note",
|
|
"directory": "unicode",
|
|
"content": unicode_content,
|
|
"tags": "unicode,test,emoji",
|
|
},
|
|
)
|
|
|
|
# Delete the Unicode note
|
|
delete_result = await client.call_tool(
|
|
"delete_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "Unicode Test Note",
|
|
},
|
|
)
|
|
|
|
# Should return True for successful deletion
|
|
assert "true" in delete_result.content[0].text.lower()
|
|
|
|
# Verify deletion
|
|
read_after_delete = await client.call_tool(
|
|
"read_note",
|
|
{
|
|
"project": test_project.name,
|
|
"identifier": "Unicode Test Note",
|
|
},
|
|
)
|
|
|
|
# Should return "Note Not Found" message
|
|
assert len(read_after_delete.content) == 1
|
|
result_text = read_after_delete.content[0].text
|
|
assert "Note Not Found" in result_text
|
|
assert "Unicode Test Note" in result_text
|