Notion API MCP Server

by pbohannon
Verified
""" Tests for Notion block formatting and specialized block types. Tests rich text formatting, lists, and todo blocks. """ import pytest import pytest_asyncio from unittest.mock import AsyncMock, MagicMock import httpx import structlog from notion_api_mcp.api.blocks import BlocksAPI # Import common fixtures from ..common.conftest import ( full_access_client, readonly_client, strip_hyphens, ) logger = structlog.get_logger() # Test data constants RICH_TEXT_BLOCKS = { "paragraph": "Basic paragraph text", "heading_1": "Main heading", "heading_2": "Sub heading", "heading_3": "Sub-sub heading", } TEXT_ANNOTATIONS = { "bold": True, "italic": True, "strikethrough": False, "underline": False, "code": False, "color": "blue" } @pytest_asyncio.fixture async def mock_client(): """Mock httpx client for testing.""" client = AsyncMock(spec=httpx.AsyncClient) # Create a mock response mock_response = MagicMock(spec=httpx.Response) mock_response.status_code = 200 mock_response.raise_for_status = MagicMock() mock_response.json = MagicMock(return_value={"object": "block"}) # Make client methods return the mock response client.post.return_value = mock_response client.patch.return_value = mock_response client.get.return_value = mock_response return client @pytest_asyncio.fixture async def blocks_api(mock_client): """BlocksAPI instance with mocked client.""" return BlocksAPI(mock_client) class TestRichTextBlocks: """Test rich text block creation and formatting.""" def test_create_basic_paragraph(self, blocks_api): """Test creating a basic paragraph block.""" block = blocks_api.create_rich_text_block("Test paragraph") assert "paragraph" in block assert block["paragraph"]["rich_text"][0]["text"]["content"] == "Test paragraph" assert block["paragraph"]["rich_text"][0]["type"] == "text" def test_create_heading_blocks(self, blocks_api): """Test creating different heading blocks.""" for block_type, content in RICH_TEXT_BLOCKS.items(): block = blocks_api.create_rich_text_block(content, block_type=block_type) assert block_type in block assert block[block_type]["rich_text"][0]["text"]["content"] == content def test_create_formatted_text(self, blocks_api): """Test creating text with formatting annotations.""" block = blocks_api.create_rich_text_block( "Formatted text", annotations=TEXT_ANNOTATIONS ) rich_text = block["paragraph"]["rich_text"][0] assert rich_text["annotations"]["bold"] is True assert rich_text["annotations"]["italic"] is True assert rich_text["annotations"]["color"] == "blue" def test_create_linked_text(self, blocks_api): """Test creating text with a link.""" block = blocks_api.create_rich_text_block( "Link text", link="https://example.com" ) rich_text = block["paragraph"]["rich_text"][0] assert rich_text["text"]["link"]["url"] == "https://example.com" class TestListBlocks: """Test list block creation and formatting.""" def test_create_basic_list(self, blocks_api): """Test creating a basic bulleted list item.""" block = blocks_api.create_bulleted_list_block("List item") assert "bulleted_list_item" in block assert block["bulleted_list_item"]["rich_text"][0]["text"]["content"] == "List item" def test_create_formatted_list(self, blocks_api): """Test creating a formatted list item.""" block = blocks_api.create_bulleted_list_block( "Formatted item", annotations=TEXT_ANNOTATIONS ) rich_text = block["bulleted_list_item"]["rich_text"][0] assert rich_text["annotations"]["bold"] is True assert rich_text["annotations"]["italic"] is True assert rich_text["annotations"]["color"] == "blue" @pytest.mark.asyncio async def test_nested_list(self, blocks_api): """Test creating nested list items.""" parent = blocks_api.create_bulleted_list_block("Parent item") child = blocks_api.create_bulleted_list_block("Child item") result = await blocks_api.append_children( "test-block-id", [parent, child] ) assert result == {"object": "block"} blocks_api._client.patch.assert_called_once_with( "blocks/test-block-id/children", json={"children": [parent, child]} ) class TestTodoBlocks: """Test todo block creation and formatting.""" def test_create_basic_todo(self, blocks_api): """Test creating a basic todo block.""" block = blocks_api.create_todo_block("Todo item") assert "to_do" in block assert block["to_do"]["rich_text"][0]["text"]["content"] == "Todo item" assert block["to_do"]["checked"] is False def test_create_checked_todo(self, blocks_api): """Test creating a completed todo block.""" block = blocks_api.create_todo_block("Done item", checked=True) assert block["to_do"]["checked"] is True def test_create_formatted_todo(self, blocks_api): """Test creating a formatted todo block.""" block = blocks_api.create_todo_block( "Important todo", annotations=TEXT_ANNOTATIONS ) rich_text = block["to_do"]["rich_text"][0] assert rich_text["annotations"]["bold"] is True assert rich_text["annotations"]["italic"] is True assert rich_text["annotations"]["color"] == "blue" @pytest.mark.asyncio class TestErrorHandling: """Test error handling in block formatting operations.""" async def test_invalid_block_type(self, blocks_api): """Test error handling with invalid block type.""" block = blocks_api.create_rich_text_block( "Test", block_type="invalid_type" ) blocks_api._client.patch.side_effect = httpx.HTTPError("Invalid block type") with pytest.raises(httpx.HTTPError): await blocks_api.append_children("test-id", [block]) async def test_invalid_annotations(self, blocks_api): """Test error handling with invalid annotations.""" block = blocks_api.create_rich_text_block( "Test", annotations={"invalid": "annotation"} ) blocks_api._client.patch.side_effect = httpx.HTTPError("Invalid annotations") with pytest.raises(httpx.HTTPError): await blocks_api.append_children("test-id", [block]) async def test_invalid_color(self, blocks_api): """Test error handling with invalid color.""" block = blocks_api.create_rich_text_block( "Test", annotations={"color": "invalid_color"} ) blocks_api._client.patch.side_effect = httpx.HTTPError("Invalid color") with pytest.raises(httpx.HTTPError): await blocks_api.append_children("test-id", [block])