mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
617e60bda4
- Avoiding "useless permalink values" for files without metadata - Enable permalinks to be updated on move via config setting
333 lines
8.0 KiB
Python
333 lines
8.0 KiB
Python
"""Tests for note tools that exercise the full stack with SQLite."""
|
|
|
|
from textwrap import dedent
|
|
import pytest
|
|
|
|
from basic_memory.mcp.tools import write_note, read_note, delete_note
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_write_note(app):
|
|
"""Test creating a new note.
|
|
|
|
Should:
|
|
- Create entity with correct type and content
|
|
- Save markdown content
|
|
- Handle tags correctly
|
|
- Return valid permalink
|
|
"""
|
|
result = await write_note(
|
|
title="Test Note",
|
|
folder="test",
|
|
content="# Test\nThis is a test note",
|
|
tags=["test", "documentation"],
|
|
)
|
|
|
|
assert result
|
|
assert (
|
|
dedent("""
|
|
# Created note
|
|
file_path: test/Test Note.md
|
|
permalink: test/test-note
|
|
checksum: 159f2168
|
|
|
|
## Tags
|
|
- test, documentation
|
|
""").strip()
|
|
in result
|
|
)
|
|
|
|
# Try reading it back via permalink
|
|
content = await read_note("test/test-note")
|
|
assert (
|
|
dedent("""
|
|
---
|
|
title: Test Note
|
|
type: note
|
|
permalink: test/test-note
|
|
tags:
|
|
- '#test'
|
|
- '#documentation'
|
|
---
|
|
|
|
# Test
|
|
This is a test note
|
|
""").strip()
|
|
in content
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_write_note_no_tags(app):
|
|
"""Test creating a note without tags."""
|
|
result = await write_note(title="Simple Note", folder="test", content="Just some text")
|
|
|
|
assert result
|
|
assert (
|
|
dedent("""
|
|
# Created note
|
|
file_path: test/Simple Note.md
|
|
permalink: test/simple-note
|
|
checksum: 9a1ff079
|
|
""").strip()
|
|
in result
|
|
)
|
|
# Should be able to read it back
|
|
content = await read_note("test/simple-note")
|
|
assert (
|
|
dedent("""
|
|
--
|
|
title: Simple Note
|
|
type: note
|
|
permalink: test/simple-note
|
|
---
|
|
|
|
Just some text
|
|
""").strip()
|
|
in content
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_write_note_update_existing(app):
|
|
"""Test creating a new note.
|
|
|
|
Should:
|
|
- Create entity with correct type and content
|
|
- Save markdown content
|
|
- Handle tags correctly
|
|
- Return valid permalink
|
|
"""
|
|
result = await write_note(
|
|
title="Test Note",
|
|
folder="test",
|
|
content="# Test\nThis is a test note",
|
|
tags=["test", "documentation"],
|
|
)
|
|
|
|
assert result # Got a valid permalink
|
|
assert (
|
|
dedent("""
|
|
# Created note
|
|
file_path: test/Test Note.md
|
|
permalink: test/test-note
|
|
checksum: 159f2168
|
|
|
|
## Tags
|
|
- test, documentation
|
|
""").strip()
|
|
in result
|
|
)
|
|
|
|
result = await write_note(
|
|
title="Test Note",
|
|
folder="test",
|
|
content="# Test\nThis is an updated note",
|
|
tags=["test", "documentation"],
|
|
)
|
|
assert (
|
|
dedent("""
|
|
# Updated note
|
|
file_path: test/Test Note.md
|
|
permalink: test/test-note
|
|
checksum: a8eb4d44
|
|
|
|
## Tags
|
|
- test, documentation
|
|
""").strip()
|
|
in result
|
|
)
|
|
|
|
# Try reading it back
|
|
content = await read_note("test/test-note")
|
|
assert (
|
|
dedent(
|
|
"""
|
|
---
|
|
title: Test Note
|
|
type: note
|
|
permalink: test/test-note
|
|
tags:
|
|
- '#test'
|
|
- '#documentation'
|
|
---
|
|
|
|
# Test
|
|
This is an updated note
|
|
"""
|
|
).strip()
|
|
== content
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_note_existing(app):
|
|
"""Test deleting a new note.
|
|
|
|
Should:
|
|
- Create entity with correct type and content
|
|
- Return valid permalink
|
|
- Delete the note
|
|
"""
|
|
result = await write_note(
|
|
title="Test Note",
|
|
folder="test",
|
|
content="# Test\nThis is a test note",
|
|
tags=["test", "documentation"],
|
|
)
|
|
|
|
assert result
|
|
|
|
deleted = await delete_note("test/test-note")
|
|
assert deleted is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_note_doesnt_exist(app):
|
|
"""Test deleting a new note.
|
|
|
|
Should:
|
|
- Delete the note
|
|
- verify returns false
|
|
"""
|
|
deleted = await delete_note("doesnt-exist")
|
|
assert deleted is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_write_note_with_tag_array_from_bug_report(app):
|
|
"""Test creating a note with a tag array as reported in issue #38.
|
|
|
|
This reproduces the exact payload from the bug report where Cursor
|
|
was passing an array of tags and getting a type mismatch error.
|
|
"""
|
|
# This is the exact payload from the bug report
|
|
bug_payload = {
|
|
"title": "Title",
|
|
"folder": "folder",
|
|
"content": "CONTENT",
|
|
"tags": ["hipporag", "search", "fallback", "symfony", "error-handling"],
|
|
}
|
|
|
|
# Try to call the function with this data directly
|
|
result = await write_note(**bug_payload)
|
|
|
|
assert result
|
|
assert "permalink: folder/title" in result
|
|
assert "Tags" in result
|
|
assert "hipporag" in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_write_note_verbose(app):
|
|
"""Test creating a new note.
|
|
|
|
Should:
|
|
- Create entity with correct type and content
|
|
- Save markdown content
|
|
- Handle tags correctly
|
|
- Return valid permalink
|
|
"""
|
|
result = await write_note(
|
|
title="Test Note",
|
|
folder="test",
|
|
content="""
|
|
# Test\nThis is a test note
|
|
|
|
- [note] First observation
|
|
- relates to [[Knowledge]]
|
|
|
|
""",
|
|
tags=["test", "documentation"],
|
|
)
|
|
|
|
assert (
|
|
dedent("""
|
|
# Created note
|
|
file_path: test/Test Note.md
|
|
permalink: test/test-note
|
|
checksum: 06873a7a
|
|
|
|
## Observations
|
|
- note: 1
|
|
|
|
## Relations
|
|
- Resolved: 0
|
|
- Unresolved: 1
|
|
|
|
Unresolved relations will be retried on next sync.
|
|
|
|
## Tags
|
|
- test, documentation
|
|
""").strip()
|
|
in result
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_write_note_preserves_custom_metadata(app, test_config):
|
|
"""Test that updating a note preserves custom metadata fields.
|
|
|
|
Reproduces issue #36 where custom frontmatter fields like Status
|
|
were being lost when updating notes with the write_note tool.
|
|
|
|
Should:
|
|
- Create a note with custom frontmatter
|
|
- Update the note with new content
|
|
- Verify custom frontmatter is preserved
|
|
"""
|
|
# First, create a note with custom metadata using write_note
|
|
await write_note(
|
|
title="Custom Metadata Note",
|
|
folder="test",
|
|
content="# Initial content",
|
|
tags=["test"],
|
|
)
|
|
|
|
# Read the note to get its permalink
|
|
content = await read_note("test/custom-metadata-note")
|
|
|
|
# Now directly update the file with custom frontmatter
|
|
# We need to use a direct file update to add custom frontmatter
|
|
import frontmatter
|
|
|
|
file_path = test_config.home / "test" / "Custom Metadata Note.md"
|
|
post = frontmatter.load(file_path)
|
|
|
|
# Add custom frontmatter
|
|
post["Status"] = "In Progress"
|
|
post["Priority"] = "High"
|
|
post["Version"] = "1.0"
|
|
|
|
# Write the file back
|
|
with open(file_path, "w") as f:
|
|
f.write(frontmatter.dumps(post))
|
|
|
|
# Now update the note using write_note
|
|
result = await write_note(
|
|
title="Custom Metadata Note",
|
|
folder="test",
|
|
content="# Updated content",
|
|
tags=["test", "updated"],
|
|
)
|
|
|
|
# Verify the update was successful
|
|
assert ("Updated note\nfile_path: test/Custom Metadata Note.md") in result
|
|
|
|
# Read the note back and check if custom frontmatter is preserved
|
|
content = await read_note("test/custom-metadata-note")
|
|
|
|
# Custom frontmatter should be preserved
|
|
assert "Status: In Progress" in content
|
|
assert "Priority: High" in content
|
|
# Version might be quoted as '1.0' due to YAML serialization
|
|
assert "Version:" in content # Just check that the field exists
|
|
assert "1.0" in content # And that the value exists somewhere
|
|
|
|
# And new content should be there
|
|
assert "# Updated content" in content
|
|
|
|
# And tags should be updated
|
|
assert "'#test'" in content
|
|
assert "'#updated'" in content
|