Notion API MCP Server

by pbohannon
Verified
""" Integration tests for Notion blocks API. Tests block creation, formatting, and nesting with real API calls. """ import os import pytest import pytest_asyncio import httpx import structlog from datetime import datetime from notion_api_mcp.api.blocks import BlocksAPI from notion_api_mcp.api.pages import PagesAPI # Import common fixtures from ..common.conftest import ( full_access_client, readonly_client, strip_hyphens, ) logger = structlog.get_logger() @pytest_asyncio.fixture async def test_page(full_access_client): """Create a test page for block operations.""" parent_id = strip_hyphens(os.getenv("NOTION_PARENT_PAGE_ID")) pages_api = PagesAPI(full_access_client) # Create test page page = await pages_api.create_page( parent_id, properties={"title": {"title": [{"text": {"content": f"Test Page {os.urandom(4).hex()}"}}]}}, is_database=False ) page_id = page["id"] logger.info("test_page_created", page_id=page_id) yield page_id # Clean up try: await pages_api.archive_page(page_id) except Exception as e: logger.error("cleanup_error", page_id=page_id, error=str(e)) @pytest.mark.integration class TestBlockFormatting: """Test block formatting with real API calls.""" async def test_rich_text_blocks(self, full_access_client, test_page): """Test creating various rich text blocks.""" blocks_api = BlocksAPI(full_access_client) # Create different types of text blocks blocks = [ blocks_api.create_rich_text_block( "Heading 1 Test", block_type="heading_1" ), blocks_api.create_rich_text_block( "Heading 2 Test", block_type="heading_2" ), blocks_api.create_rich_text_block( "Regular paragraph", block_type="paragraph" ) ] response = await blocks_api.append_children(test_page, blocks) assert response is not None assert "results" in response assert len(response["results"]) == 3 # Verify block types results = response["results"] assert results[0]["type"] == "heading_1" assert results[1]["type"] == "heading_2" assert results[2]["type"] == "paragraph" async def test_formatted_text(self, full_access_client, test_page): """Test text with various formatting options.""" blocks_api = BlocksAPI(full_access_client) block = blocks_api.create_rich_text_block( "Formatted Text", annotations={ "bold": True, "italic": True, "color": "blue" } ) response = await blocks_api.append_children(test_page, [block]) assert response is not None assert "results" in response result = response["results"][0] annotations = result["paragraph"]["rich_text"][0]["annotations"] assert annotations["bold"] is True assert annotations["italic"] is True assert annotations["color"] == "blue" async def test_linked_text(self, full_access_client, test_page): """Test text with links.""" blocks_api = BlocksAPI(full_access_client) # Test URLs with and without trailing slashes test_urls = [ "https://example.com", "https://example.com/", "https://example.com/path", "https://example.com/path/" ] blocks = [ blocks_api.create_rich_text_block( f"Link {i+1}", link=url ) for i, url in enumerate(test_urls) ] response = await blocks_api.append_children(test_page, blocks) assert response is not None assert "results" in response assert len(response["results"]) == len(test_urls) # Verify all links are properly set for i, result in enumerate(response["results"]): link = result["paragraph"]["rich_text"][0]["text"]["link"] assert "url" in link # Verify URL is present, exact format handled by Notion API assert link["url"].startswith("https://example.com") @pytest.mark.integration class TestListOperations: """Test list creation and nesting.""" async def test_bulleted_list(self, full_access_client, test_page): """Test creating bulleted lists.""" blocks_api = BlocksAPI(full_access_client) blocks = [ blocks_api.create_bulleted_list_block("Item 1"), blocks_api.create_bulleted_list_block("Item 2"), blocks_api.create_bulleted_list_block( "Item 3", annotations={"bold": True} ) ] response = await blocks_api.append_children(test_page, blocks) assert response is not None assert "results" in response assert len(response["results"]) == 3 # Verify list items results = response["results"] for result in results: assert result["type"] == "bulleted_list_item" # Verify formatted item assert results[2]["bulleted_list_item"]["rich_text"][0]["annotations"]["bold"] is True async def test_todo_list(self, full_access_client, test_page): """Test creating todo lists.""" blocks_api = BlocksAPI(full_access_client) blocks = [ blocks_api.create_todo_block("Task 1"), blocks_api.create_todo_block("Task 2", checked=True), blocks_api.create_todo_block( "Task 3", annotations={"color": "red"} ) ] response = await blocks_api.append_children(test_page, blocks) assert response is not None assert "results" in response assert len(response["results"]) == 3 results = response["results"] # Verify todo states assert results[0]["to_do"]["checked"] is False assert results[1]["to_do"]["checked"] is True # Verify formatting assert results[2]["to_do"]["rich_text"][0]["annotations"]["color"] == "red" @pytest.mark.integration class TestBlockOperations: """Test block manipulation operations.""" async def test_block_children(self, full_access_client, test_page): """Test retrieving block children.""" blocks_api = BlocksAPI(full_access_client) # Add some blocks blocks = [ blocks_api.create_rich_text_block("Parent Block"), blocks_api.create_bulleted_list_block("Child 1"), blocks_api.create_bulleted_list_block("Child 2") ] await blocks_api.append_children(test_page, blocks) # Get children response = await blocks_api.get_block_children(test_page) assert response is not None assert "results" in response assert len(response["results"]) == 3 async def test_update_block(self, full_access_client, test_page): """Test updating block content.""" blocks_api = BlocksAPI(full_access_client) # Create initial block block = blocks_api.create_rich_text_block("Initial Text") response = await blocks_api.append_children(test_page, [block]) block_id = response["results"][0]["id"] # Update block updated_content = { "paragraph": { "rich_text": [{ "type": "text", "text": {"content": "Updated Text"} }] } } response = await blocks_api.update_block(block_id, updated_content) assert response is not None assert response["paragraph"]["rich_text"][0]["text"]["content"] == "Updated Text" @pytest.mark.integration class TestErrorHandling: """Test error handling with real API calls.""" async def test_invalid_block_id(self, full_access_client): """Test handling invalid block ID.""" blocks_api = BlocksAPI(full_access_client) with pytest.raises(httpx.HTTPError) as exc_info: await blocks_api.get_block("invalid-id") assert exc_info.value.response.status_code in (400, 404) async def test_invalid_parent(self, full_access_client): """Test handling invalid parent ID.""" blocks_api = BlocksAPI(full_access_client) block = blocks_api.create_rich_text_block("Test") with pytest.raises(httpx.HTTPError) as exc_info: await blocks_api.append_children("invalid-parent-id", [block]) assert exc_info.value.response.status_code in (400, 404) async def test_readonly_access(self, readonly_client, test_page): """Test operations with readonly access.""" blocks_api = BlocksAPI(readonly_client) block = blocks_api.create_rich_text_block("Test") with pytest.raises(httpx.HTTPError) as exc_info: await blocks_api.append_children(test_page, [block]) assert exc_info.value.response.status_code == 403