Skip to main content
Glama

Obsidian MCP Server

by suhailnajeeb
test_move_note_enhanced.py9.32 kB
"""Tests for enhanced move_note functionality with rename support.""" import pytest import asyncio from unittest.mock import AsyncMock, MagicMock, patch from obsidian_mcp.tools.organization import move_note from obsidian_mcp.models import Note, NoteMetadata @pytest.fixture def mock_vault(): """Create a mock vault for testing.""" vault = AsyncMock() return vault @pytest.fixture def mock_get_vault(mock_vault): """Patch get_vault to return our mock.""" with patch('obsidian_mcp.tools.organization.get_vault', return_value=mock_vault): with patch('obsidian_mcp.tools.link_management.get_vault', return_value=mock_vault): yield mock_vault @pytest.mark.asyncio async def test_move_note_simple_folder_change(mock_get_vault): """Test moving a note to a different folder without renaming.""" vault = mock_get_vault # Setup mock note note = Note( path="Inbox/Note.md", content="# My Note\n\nContent here.", metadata=NoteMetadata() ) vault.read_note.side_effect = [note, FileNotFoundError()] vault.write_note.return_value = None vault.delete_note.return_value = None # Test move without rename result = await move_note( "Inbox/Note.md", "Archive/Note.md" ) assert result["success"] is True assert result["source"] == "Inbox/Note.md" assert result["destination"] == "Archive/Note.md" assert result["renamed"] is False assert result["details"]["links_updated"] == 0 assert result["details"]["notes_updated"] == 0 # Verify operations vault.write_note.assert_called_once_with("Archive/Note.md", note.content, overwrite=False) vault.delete_note.assert_called_once_with("Inbox/Note.md") @pytest.mark.asyncio async def test_move_note_with_rename(mock_get_vault): """Test moving a note with a name change, triggering link updates.""" vault = mock_get_vault # Setup mock notes moving_note = Note( path="Inbox/Old Name.md", content="# Old Name\n\nThis is the note being moved.", metadata=NoteMetadata() ) linking_note1 = Note( path="Daily/2024-01-15.md", content="Today I worked on [[Old Name]] and [[Old Name|the project]].", metadata=NoteMetadata() ) linking_note2 = Note( path="Projects/Overview.md", content="Related: [[Old Name.md]] and [[Other Note]].", metadata=NoteMetadata() ) # Mock get_backlinks with patch('obsidian_mcp.tools.organization.get_backlinks') as mock_backlinks: mock_backlinks.return_value = { 'findings': [ {'source_path': 'Daily/2024-01-15.md', 'link_text': 'Old Name', 'link_type': 'wiki'}, {'source_path': 'Daily/2024-01-15.md', 'link_text': 'the project', 'link_type': 'wiki'}, {'source_path': 'Projects/Overview.md', 'link_text': 'Old Name.md', 'link_type': 'wiki'} ] } # Setup vault mocks vault.read_note.side_effect = [ moving_note, # First read of source note FileNotFoundError(), # Check destination doesn't exist linking_note1, # Read first linking note linking_note2, # Read second linking note ] vault.write_note.return_value = None vault.delete_note.return_value = None # Test move with rename result = await move_note( "Inbox/Old Name.md", "Archive/New Name.md", update_links=True ) assert result["success"] is True assert result["source"] == "Inbox/Old Name.md" assert result["destination"] == "Archive/New Name.md" assert result["renamed"] is True assert result["details"]["links_updated"] == 3 assert result["details"]["notes_updated"] == 2 assert len(result["details"]["link_update_details"]) == 2 # Verify link updates write_calls = vault.write_note.call_args_list assert len(write_calls) == 3 # 2 link updates + 1 moved note # Check that links were updated correctly updated_content1 = write_calls[0][0][1] assert "[[New Name]]" in updated_content1 assert "[[New Name|the project]]" in updated_content1 assert "[[Old Name" not in updated_content1 updated_content2 = write_calls[1][0][1] assert "[[New Name.md]]" in updated_content2 assert "[[Old Name.md]]" not in updated_content2 @pytest.mark.asyncio async def test_move_note_with_rename_links_disabled(mock_get_vault): """Test moving with rename but update_links=False.""" vault = mock_get_vault note = Note( path="Inbox/Old.md", content="Content", metadata=NoteMetadata() ) vault.read_note.side_effect = [note, FileNotFoundError()] vault.write_note.return_value = None vault.delete_note.return_value = None # Test move with rename but links disabled result = await move_note( "Inbox/Old.md", "Archive/New.md", update_links=False ) assert result["success"] is True assert result["renamed"] is True assert result["details"]["links_updated"] == 0 assert result["details"]["notes_updated"] == 0 # Should not have called get_backlinks with patch('obsidian_mcp.tools.organization.get_backlinks') as mock_backlinks: mock_backlinks.assert_not_called() @pytest.mark.asyncio async def test_move_note_preserve_aliases(mock_get_vault): """Test that link aliases are preserved during move with rename.""" vault = mock_get_vault moving_note = Note( path="Technical Name.md", content="# Technical Name", metadata=NoteMetadata() ) linking_note = Note( path="Index.md", content="See [[Technical Name|User Friendly Display]] for details.", metadata=NoteMetadata() ) with patch('obsidian_mcp.tools.organization.get_backlinks') as mock_backlinks: mock_backlinks.return_value = { 'findings': [ {'source_path': 'Index.md', 'link_text': 'User Friendly Display', 'link_type': 'wiki'} ] } vault.read_note.side_effect = [moving_note, FileNotFoundError(), linking_note] vault.write_note.return_value = None vault.delete_note.return_value = None result = await move_note( "Technical Name.md", "Archive/Better Name.md", update_links=True ) # Check that alias was preserved write_calls = vault.write_note.call_args_list updated_content = write_calls[0][0][1] assert "[[Better Name|User Friendly Display]]" in updated_content assert result["details"]["links_updated"] == 1 @pytest.mark.asyncio async def test_move_note_complex_path_changes(mock_get_vault): """Test moving between nested directories with rename.""" vault = mock_get_vault note = Note( path="Projects/Active/2024/Q1/Sprint Planning.md", content="Sprint planning notes", metadata=NoteMetadata() ) linking_note = Note( path="Daily/2024-01-15.md", content="Review [[Sprint Planning]] today", metadata=NoteMetadata() ) with patch('obsidian_mcp.tools.organization.get_backlinks') as mock_backlinks: mock_backlinks.return_value = { 'findings': [ {'source_path': 'Daily/2024-01-15.md', 'link_text': 'Sprint Planning', 'link_type': 'wiki'} ] } vault.read_note.side_effect = [note, FileNotFoundError(), linking_note] vault.write_note.return_value = None vault.delete_note.return_value = None result = await move_note( "Projects/Active/2024/Q1/Sprint Planning.md", "Archive/2024/Q1/Completed Sprints.md" ) assert result["renamed"] is True assert result["details"]["links_updated"] == 1 # Verify the link was updated correctly write_calls = vault.write_note.call_args_list updated_content = write_calls[0][0][1] assert "[[Completed Sprints]]" in updated_content @pytest.mark.asyncio async def test_move_note_same_name_different_folder(mock_get_vault): """Test that moving to different folder with same name doesn't update links.""" vault = mock_get_vault note = Note( path="Inbox/Project.md", content="Project content", metadata=NoteMetadata() ) vault.read_note.side_effect = [note, FileNotFoundError()] vault.write_note.return_value = None vault.delete_note.return_value = None # Should not call get_backlinks since name didn't change with patch('obsidian_mcp.tools.organization.get_backlinks') as mock_backlinks: result = await move_note( "Inbox/Project.md", "Projects/Active/Project.md" ) assert result["renamed"] is False assert result["details"]["links_updated"] == 0 mock_backlinks.assert_not_called() if __name__ == "__main__": pytest.main([__file__, "-v"])

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/suhailnajeeb/obsidian-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server