Skip to main content
Glama
test_document_lifecycle.py17.7 kB
""" Tests for document lifecycle tools. """ from unittest.mock import AsyncMock, patch import pytest from mcp_outline.features.documents.common import OutlineClientError # Mock FastMCP for registering tools class MockMCP: def __init__(self): self.tools = {} def tool(self, annotations=None): def decorator(func): self.tools[func.__name__] = func return func return decorator # Sample document data SAMPLE_DOCUMENT = { "id": "doc123", "title": "Test Document", "text": "Sample content", "updatedAt": "2023-01-01T12:00:00Z", } SAMPLE_DOCUMENTS_LIST = [ { "id": "doc1", "title": "Archived Doc 1", "updatedAt": "2023-01-01T12:00:00Z", }, { "id": "doc2", "title": "Archived Doc 2", "updatedAt": "2023-01-02T12:00:00Z", }, ] @pytest.fixture def mcp(): """Fixture to provide mock MCP instance.""" return MockMCP() @pytest.fixture def register_lifecycle_tools(mcp): """Fixture to register document lifecycle tools.""" from mcp_outline.features.documents.document_lifecycle import ( register_tools, ) register_tools(mcp) return mcp class TestArchiveDocument: """Tests for archive_document tool.""" @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_archive_document_success( self, mock_get_client, register_lifecycle_tools ): """Test archive_document tool success case.""" mock_client = AsyncMock() mock_client.archive_document.return_value = SAMPLE_DOCUMENT mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["archive_document"]( "doc123" ) mock_client.archive_document.assert_called_once_with("doc123") assert "Document archived successfully" in result assert "Test Document" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_archive_document_no_document_returned( self, mock_get_client, register_lifecycle_tools ): """Test archive_document when no document is returned.""" mock_client = AsyncMock() mock_client.archive_document.return_value = None mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["archive_document"]( "doc123" ) assert "Failed to archive document" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_archive_document_client_error( self, mock_get_client, register_lifecycle_tools ): """Test archive_document with client error.""" mock_client = AsyncMock() mock_client.archive_document.side_effect = OutlineClientError( "API error" ) mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["archive_document"]( "doc123" ) assert "Error archiving document" in result assert "API error" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_archive_document_unexpected_error( self, mock_get_client, register_lifecycle_tools ): """Test archive_document with unexpected error.""" mock_client = AsyncMock() mock_client.archive_document.side_effect = ValueError( "Unexpected error" ) mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["archive_document"]( "doc123" ) assert "Unexpected error" in result class TestUnarchiveDocument: """Tests for unarchive_document tool.""" @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_unarchive_document_success( self, mock_get_client, register_lifecycle_tools ): """Test unarchive_document tool success case.""" mock_client = AsyncMock() mock_client.unarchive_document.return_value = SAMPLE_DOCUMENT mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["unarchive_document"]( "doc123" ) mock_client.unarchive_document.assert_called_once_with("doc123") assert "Document unarchived successfully" in result assert "Test Document" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_unarchive_document_no_document_returned( self, mock_get_client, register_lifecycle_tools ): """Test unarchive_document when no document is returned.""" mock_client = AsyncMock() mock_client.unarchive_document.return_value = None mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["unarchive_document"]( "doc123" ) assert "Failed to unarchive document" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_unarchive_document_client_error( self, mock_get_client, register_lifecycle_tools ): """Test unarchive_document with client error.""" mock_client = AsyncMock() mock_client.unarchive_document.side_effect = OutlineClientError( "API error" ) mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["unarchive_document"]( "doc123" ) assert "Error unarchiving document" in result assert "API error" in result class TestDeleteDocument: """Tests for delete_document tool.""" @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_delete_document_to_trash_success( self, mock_get_client, register_lifecycle_tools ): """Test delete_document tool moving to trash (default).""" mock_client = AsyncMock() mock_client.get_document.return_value = SAMPLE_DOCUMENT mock_client.post.return_value = {"success": True} mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["delete_document"]( "doc123" ) mock_client.get_document.assert_called_once_with("doc123") mock_client.post.assert_called_once_with( "documents.delete", {"id": "doc123"} ) assert "Document moved to trash" in result assert "Test Document" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_delete_document_to_trash_failure( self, mock_get_client, register_lifecycle_tools ): """Test delete_document when move to trash fails.""" mock_client = AsyncMock() mock_client.get_document.return_value = SAMPLE_DOCUMENT mock_client.post.return_value = {"success": False} mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["delete_document"]( "doc123" ) assert "Failed to move document to trash" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_delete_document_permanently_success( self, mock_get_client, register_lifecycle_tools ): """Test delete_document tool with permanent deletion.""" mock_client = AsyncMock() mock_client.permanently_delete_document.return_value = True mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["delete_document"]( "doc123", permanent=True ) mock_client.permanently_delete_document.assert_called_once_with( "doc123" ) assert "Document permanently deleted" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_delete_document_permanently_failure( self, mock_get_client, register_lifecycle_tools ): """Test delete_document permanent deletion failure.""" mock_client = AsyncMock() mock_client.permanently_delete_document.return_value = False mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["delete_document"]( "doc123", permanent=True ) assert "Failed to permanently delete document" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_delete_document_client_error( self, mock_get_client, register_lifecycle_tools ): """Test delete_document with client error.""" mock_client = AsyncMock() mock_client.get_document.side_effect = OutlineClientError("API error") mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["delete_document"]( "doc123" ) assert "Error deleting document" in result assert "API error" in result class TestRestoreDocument: """Tests for restore_document tool.""" @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_restore_document_success( self, mock_get_client, register_lifecycle_tools ): """Test restore_document tool success case.""" mock_client = AsyncMock() mock_client.restore_document.return_value = SAMPLE_DOCUMENT mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["restore_document"]( "doc123" ) mock_client.restore_document.assert_called_once_with("doc123") assert "Document restored successfully" in result assert "Test Document" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_restore_document_no_document_returned( self, mock_get_client, register_lifecycle_tools ): """Test restore_document when no document is returned.""" mock_client = AsyncMock() mock_client.restore_document.return_value = None mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["restore_document"]( "doc123" ) assert "Failed to restore document from trash" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_restore_document_client_error( self, mock_get_client, register_lifecycle_tools ): """Test restore_document with client error.""" mock_client = AsyncMock() mock_client.restore_document.side_effect = OutlineClientError( "API error" ) mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["restore_document"]( "doc123" ) assert "Error restoring document" in result assert "API error" in result class TestListArchivedDocuments: """Tests for list_archived_documents tool.""" @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_list_archived_documents_success( self, mock_get_client, register_lifecycle_tools ): """Test list_archived_documents tool success case.""" mock_client = AsyncMock() mock_client.post.return_value = {"data": SAMPLE_DOCUMENTS_LIST} mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools[ "list_archived_documents" ]() mock_client.post.assert_called_once_with("documents.archived") assert "Archived Documents" in result assert "Archived Doc 1" in result assert "Archived Doc 2" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_list_archived_documents_empty( self, mock_get_client, register_lifecycle_tools ): """Test list_archived_documents with no archived documents.""" mock_client = AsyncMock() mock_client.post.return_value = {"data": []} mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools[ "list_archived_documents" ]() assert "archived documents" in result.lower() @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_list_archived_documents_client_error( self, mock_get_client, register_lifecycle_tools ): """Test list_archived_documents with client error.""" mock_client = AsyncMock() mock_client.post.side_effect = OutlineClientError("API error") mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools[ "list_archived_documents" ]() assert "Error listing archived documents" in result assert "API error" in result class TestListTrash: """Tests for list_trash tool.""" @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_list_trash_success( self, mock_get_client, register_lifecycle_tools ): """Test list_trash tool success case.""" mock_client = AsyncMock() mock_client.list_trash.return_value = SAMPLE_DOCUMENTS_LIST mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["list_trash"]() mock_client.list_trash.assert_called_once() assert "Documents in Trash" in result assert "Archived Doc 1" in result assert "Archived Doc 2" in result @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_list_trash_empty( self, mock_get_client, register_lifecycle_tools ): """Test list_trash with no documents in trash.""" mock_client = AsyncMock() mock_client.list_trash.return_value = [] mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["list_trash"]() assert "documents in trash" in result.lower() @pytest.mark.asyncio @patch( "mcp_outline.features.documents.document_lifecycle.get_outline_client" ) async def test_list_trash_client_error( self, mock_get_client, register_lifecycle_tools ): """Test list_trash with client error.""" mock_client = AsyncMock() mock_client.list_trash.side_effect = OutlineClientError("API error") mock_get_client.return_value = mock_client result = await register_lifecycle_tools.tools["list_trash"]() assert "Error listing trash" in result assert "API error" in result class TestConditionalRegistration: """Tests for conditional tool registration based on env vars.""" def test_delete_document_registered_by_default(self): """Test delete_document is registered by default.""" import os # Ensure environment variable is not set os.environ.pop("OUTLINE_DISABLE_DELETE", None) mock_mcp = MockMCP() # Re-import to get fresh registration import importlib from mcp_outline.features.documents import document_lifecycle importlib.reload(document_lifecycle) document_lifecycle.register_tools(mock_mcp) assert "delete_document" in mock_mcp.tools assert "archive_document" in mock_mcp.tools assert "unarchive_document" in mock_mcp.tools assert "restore_document" in mock_mcp.tools assert "list_archived_documents" in mock_mcp.tools assert "list_trash" in mock_mcp.tools def test_delete_document_not_registered_when_disabled(self): """Test delete_document not registered when disabled.""" import os # Set environment variable (new name) os.environ["OUTLINE_DISABLE_DELETE"] = "true" mock_mcp = MockMCP() # Re-import to get fresh registration import importlib from mcp_outline.features.documents import document_lifecycle importlib.reload(document_lifecycle) document_lifecycle.register_tools(mock_mcp) assert "delete_document" not in mock_mcp.tools assert "archive_document" in mock_mcp.tools assert "unarchive_document" in mock_mcp.tools assert "restore_document" in mock_mcp.tools assert "list_archived_documents" in mock_mcp.tools assert "list_trash" in mock_mcp.tools # Cleanup os.environ.pop("OUTLINE_DISABLE_DELETE", None)

Latest Blog Posts

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/Vortiago/mcp-outline'

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