Skip to main content
Glama
test_tools.py24.2 kB
"""Unit tests for the tools module with mocked service.""" from unittest.mock import Mock, patch from mcp_kanka.tools import ( handle_create_entities, handle_create_posts, handle_delete_entities, handle_delete_posts, handle_find_entities, handle_get_entities, handle_update_entities, handle_update_posts, ) class TestFindEntities: """Test the handle_find_entities function.""" @patch("mcp_kanka.operations.get_service") async def test_find_with_search_query(self, mock_get_service): """Test finding entities with a search query.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock list results for content search mock_entities = [ Mock(id=1, entity_id=1, name="Alice", type="NPC"), Mock(id=2, entity_id=2, name="Bob", type="Player"), Mock(id=3, entity_id=3, name="Charlie", type="NPC"), ] mock_service.list_entities.return_value = mock_entities # Mock _entity_to_dict conversions mock_service._entity_to_dict.side_effect = [ { "id": 1, "entity_id": 1, "name": "Alice", "entity_type": "character", "type": "NPC", "entry": "A brave warrior test", "tags": ["hero"], "is_hidden": False, "created_at": "2023-01-01T10:00:00Z", "updated_at": "2023-01-01T10:00:00Z", }, { "id": 2, "entity_id": 2, "name": "Bob", "entity_type": "character", "type": "Player", "entry": "A cunning rogue", "tags": ["rogue"], "is_hidden": False, "created_at": "2023-01-01T10:00:00Z", "updated_at": "2023-01-01T10:00:00Z", }, { "id": 3, "entity_id": 3, "name": "Test Charlie", "entity_type": "character", "type": "NPC", "entry": "Another character", "tags": [], "is_hidden": False, "created_at": "2023-01-01T10:00:00Z", "updated_at": "2023-01-01T10:00:00Z", }, ] # Test search with full details result = await handle_find_entities( query="test", entity_type="character", include_full=True, limit=25, ) # Check response structure assert "entities" in result assert "sync_info" in result entities = result["entities"] # Alice has "test" in entry, Charlie has "Test" in name assert len(entities) == 2 assert entities[0]["name"] == "Alice" assert entities[1]["name"] == "Test Charlie" mock_service.list_entities.assert_called_once_with( "character", page=1, limit=0, last_sync=None, related=True ) @patch("mcp_kanka.operations.get_service") async def test_find_without_search_query(self, mock_get_service): """Test finding entities by listing when no search query.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock list results mock_entities = [ Mock(id=1, entity_id=1, name="Alice", type="NPC"), Mock(id=2, entity_id=2, name="Bob", type="Player"), ] mock_service.list_entities.return_value = mock_entities # Mock _entity_to_dict mock_service._entity_to_dict.side_effect = [ { "id": 1, "entity_id": 1, "name": "Alice", "entity_type": "character", "type": "NPC", "tags": [], "is_hidden": False, "entry": None, "created_at": "2023-01-01T10:00:00Z", "updated_at": "2023-01-01T10:00:00Z", }, { "id": 2, "entity_id": 2, "name": "Bob", "entity_type": "character", "type": "Player", "tags": [], "is_hidden": False, "entry": None, "created_at": "2023-01-01T10:00:00Z", "updated_at": "2023-01-01T10:00:00Z", }, ] # Test list without search result = await handle_find_entities( entity_type="character", include_full=True, ) entities = result["entities"] assert len(entities) == 2 assert entities[0]["name"] == "Alice" assert entities[1]["name"] == "Bob" mock_service.list_entities.assert_called_once_with( "character", page=1, limit=0, last_sync=None, related=True ) @patch("mcp_kanka.operations.filter_entities_by_name") @patch("mcp_kanka.operations.get_service") async def test_find_with_filters(self, mock_get_service, mock_filter_by_name): """Test finding entities with various filters.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock entities with various attributes mock_service.list_entities.return_value = [] mock_service._entity_to_dict.side_effect = lambda e, t: { "id": e.id, "entity_id": e.entity_id, "name": e.name, "entity_type": t, "type": e.type, "tags": e.tags, "is_hidden": False, "entry": e.entry, } entities = [ Mock( id=1, entity_id=1, name="Alice", type="NPC", tags=["hero", "warrior"], entry="Brave", ), Mock( id=2, entity_id=2, name="Bob", type="Player", tags=["rogue"], entry="Cunning", ), Mock( id=3, entity_id=3, name="Charlie", type="NPC", tags=["hero", "wizard"], entry="Wise", ), ] mock_service.list_entities.return_value = entities # Mock the filter function to return Alice mock_filter_by_name.return_value = [ { "id": 1, "entity_id": 1, "name": "Alice", "entity_type": "character", "type": "NPC", "tags": ["hero", "warrior"], "is_hidden": False, "entry": "Brave", } ] # Test with name filter result = await handle_find_entities( entity_type="character", name="Alice", include_full=True, ) # Should return results with proper structure assert "entities" in result assert "sync_info" in result assert len(result["entities"]) == 1 assert result["entities"][0]["name"] == "Alice" @patch("mcp_kanka.operations.get_service") async def test_find_minimal_results(self, mock_get_service): """Test finding entities with minimal results (include_full=False).""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock list results for all entity types # Since no entity_type is specified, it will search all types mock_service.list_entities.side_effect = [ # Characters [Mock(id=1, entity_id=1, name="Alice Test", type="NPC")], # Creatures - empty [], # Locations [Mock(id=2, entity_id=2, name="Test Location", type="City")], # Others empty [], [], [], [], [], ] # Mock _entity_to_dict for the entities that will be found mock_service._entity_to_dict.side_effect = [ { "id": 1, "entity_id": 1, "name": "Alice Test", "entity_type": "character", "type": "NPC", "entry": "A character", }, { "id": 2, "entity_id": 2, "name": "Test Location", "entity_type": "location", "type": "City", "entry": "A place", }, ] # Test search with minimal details result = await handle_find_entities( query="test", include_full=False, ) entities = result["entities"] assert len(entities) == 2 assert entities[0] == { "entity_id": 1, "name": "Alice Test", "entity_type": "character", } assert entities[1] == { "entity_id": 2, "name": "Test Location", "entity_type": "location", } class TestCreateEntities: """Test the handle_create_entities function.""" @patch("mcp_kanka.operations.get_service") async def test_create_single_entity(self, mock_get_service): """Test creating a single entity.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock creation result mock_service.create_entity.return_value = { "id": 1, "entity_id": 101, "name": "Test Character", "mention": "[entity:101]", } # Test create result = await handle_create_entities( entities=[ { "entity_type": "character", "name": "Test Character", "type": "NPC", "entry": "A test character", "tags": ["test"], "is_hidden": False, } ] ) assert len(result) == 1 assert result[0]["success"] is True assert result[0]["entity_id"] == 101 assert result[0]["name"] == "Test Character" assert result[0]["mention"] == "[entity:101]" assert result[0]["error"] is None @patch("mcp_kanka.operations.get_service") async def test_create_multiple_entities(self, mock_get_service): """Test creating multiple entities with partial failures.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock mixed results mock_service.create_entity.side_effect = [ { "id": 1, "entity_id": 101, "name": "Success Entity", "mention": "[entity:101]", }, Exception("API Error: Name already exists"), { "id": 3, "entity_id": 103, "name": "Another Success", "mention": "[entity:103]", }, ] # Test create multiple result = await handle_create_entities( entities=[ {"entity_type": "character", "name": "Success Entity"}, {"entity_type": "character", "name": "Duplicate Name"}, {"entity_type": "location", "name": "Another Success"}, ] ) assert len(result) == 3 # First entity succeeded assert result[0]["success"] is True assert result[0]["entity_id"] == 101 # Second entity failed assert result[1]["success"] is False assert result[1]["entity_id"] is None assert result[1]["name"] == "Duplicate Name" assert "Name already exists" in result[1]["error"] # Third entity succeeded assert result[2]["success"] is True assert result[2]["entity_id"] == 103 class TestUpdateEntities: """Test the handle_update_entities function.""" @patch("mcp_kanka.operations.get_service") async def test_update_entities(self, mock_get_service): """Test updating multiple entities.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock update results mock_service.update_entity.side_effect = [ True, # Success Exception("Entity not found"), # Failure True, # Success ] # Test update result = await handle_update_entities( updates=[ {"entity_id": 101, "name": "Updated Name 1"}, {"entity_id": 999, "name": "This will fail"}, {"entity_id": 103, "name": "Updated Name 3", "type": "Boss NPC"}, ] ) assert len(result) == 3 # Check results assert result[0]["entity_id"] == 101 assert result[0]["success"] is True assert result[0]["error"] is None assert result[1]["entity_id"] == 999 assert result[1]["success"] is False assert "not found" in result[1]["error"] assert result[2]["entity_id"] == 103 assert result[2]["success"] is True class TestGetEntities: """Test the handle_get_entities function.""" @patch("mcp_kanka.operations.get_service") async def test_get_entities_without_posts(self, mock_get_service): """Test getting entities without posts.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock get results mock_service.get_entity_by_id.side_effect = [ { "id": 1, "entity_id": 101, "name": "Alice", "entity_type": "character", "type": "NPC", "entry": "Description", "tags": ["hero"], "is_hidden": False, }, None, # Not found { "id": 3, "entity_id": 103, "name": "Cave", "entity_type": "location", "type": "Dungeon", "entry": None, "tags": [], "is_hidden": True, }, ] # Test get result = await handle_get_entities( entity_ids=[101, 102, 103], include_posts=False, ) assert len(result) == 3 # First entity found assert result[0]["success"] is True assert result[0]["entity_id"] == 101 assert result[0]["name"] == "Alice" assert "posts" not in result[0] # Second entity not found assert result[1]["success"] is False assert result[1]["entity_id"] == 102 assert "not found" in result[1]["error"] # Third entity found assert result[2]["success"] is True assert result[2]["entity_id"] == 103 @patch("mcp_kanka.operations.get_service") async def test_get_entities_with_posts(self, mock_get_service): """Test getting entities with posts.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock get results with posts mock_service.get_entity_by_id.return_value = { "id": 1, "entity_id": 101, "name": "Alice", "entity_type": "character", "posts": [ {"id": 1, "name": "Background", "entry": "Long ago..."}, {"id": 2, "name": "Recent Events", "entry": "Recently..."}, ], } # Test get with posts result = await handle_get_entities( entity_ids=[101], include_posts=True, ) assert len(result) == 1 assert result[0]["success"] is True assert "posts" in result[0] assert len(result[0]["posts"]) == 2 assert result[0]["posts"][0]["name"] == "Background" class TestDeleteEntities: """Test the handle_delete_entities function.""" @patch("mcp_kanka.operations.get_service") async def test_delete_entities(self, mock_get_service): """Test deleting multiple entities.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock delete results mock_service.delete_entity.side_effect = [ True, # Success Exception("Entity not found"), # Failure True, # Success ] # Test delete result = await handle_delete_entities(entity_ids=[101, 102, 103]) assert len(result) == 3 assert result[0]["entity_id"] == 101 assert result[0]["success"] is True assert result[0]["error"] is None assert result[1]["entity_id"] == 102 assert result[1]["success"] is False assert "not found" in result[1]["error"] assert result[2]["entity_id"] == 103 assert result[2]["success"] is True class TestInvalidParameters: """Test handling of invalid parameters.""" @patch("mcp_kanka.operations.get_service") async def test_find_entities_invalid_entity_type(self, mock_get_service): """Test find_entities with invalid entity type.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Should handle invalid entity type gracefully # The service will raise an AttributeError when trying to access invalid manager mock_service.list_entities.side_effect = AttributeError( "'KankaClient' object has no attribute 'dragons'" ) result = await handle_find_entities( entity_type="dragon", # Invalid - should be "creature" include_full=True, ) # Should return empty entities with empty sync_info assert result == {"entities": [], "sync_info": {}} @patch("mcp_kanka.operations.get_service") async def test_create_entities_invalid_entity_type(self, mock_get_service): """Test create_entities with invalid entity type.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Should handle invalid entity type result = await handle_create_entities( entities=[ { "entity_type": "invalid_type", "name": "Test Entity", } ] ) # Should return error for invalid entity assert len(result) == 1 assert result[0]["success"] is False assert "error" in result[0] @patch("mcp_kanka.operations.get_service") async def test_find_entities_invalid_date_format(self, mock_get_service): """Test find_entities with invalid date format.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock list results mock_entities = [Mock(id=1, entity_id=1, name="Test Journal", type="Session")] mock_service.list_entities.return_value = mock_entities # Mock _entity_to_dict mock_service._entity_to_dict.return_value = { "id": 1, "entity_id": 1, "name": "Test Journal", "entity_type": "journal", "entry": "Date: 2025-05-30", } # Test with invalid date format result = await handle_find_entities( entity_type="journal", date_range={ "start": "invalid-date", "end": "2025-12-31", }, include_full=True, ) # Should handle gracefully - return dict with entities assert isinstance(result, dict) assert "entities" in result assert isinstance(result["entities"], list) @patch("mcp_kanka.operations.get_service") async def test_update_entities_missing_required_fields(self, mock_get_service): """Test update_entities with missing required fields.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Test missing name (required by Kanka API) result = await handle_update_entities( updates=[ { "entity_id": 123, # Missing "name" field "entry": "Updated content", } ] ) # Should return error for missing required field assert len(result) == 1 assert result[0]["success"] is False assert ( "name" in result[0]["error"].lower() or "required" in result[0]["error"].lower() ) class TestPostOperations: """Test post-related operations.""" @patch("mcp_kanka.operations.get_service") async def test_create_posts(self, mock_get_service): """Test creating posts.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock create results mock_service.create_post.side_effect = [ {"post_id": 50, "entity_id": 101}, Exception("Entity not found"), ] # Test create posts result = await handle_create_posts( posts=[ { "entity_id": 101, "name": "Test Post", "entry": "Post content", "is_hidden": False, }, { "entity_id": 999, "name": "This will fail", }, ] ) assert len(result) == 2 assert result[0]["success"] is True assert result[0]["post_id"] == 50 assert result[0]["entity_id"] == 101 assert result[1]["success"] is False assert result[1]["post_id"] is None assert result[1]["entity_id"] == 999 @patch("mcp_kanka.operations.get_service") async def test_update_posts(self, mock_get_service): """Test updating posts.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock update results mock_service.update_post.side_effect = [True, Exception("Post not found")] # Test update posts result = await handle_update_posts( updates=[ { "entity_id": 101, "post_id": 50, "name": "Updated Post", "entry": "Updated content", }, { "entity_id": 101, "post_id": 999, "name": "This will fail", }, ] ) assert len(result) == 2 assert result[0]["success"] is True assert result[0]["entity_id"] == 101 assert result[0]["post_id"] == 50 assert result[1]["success"] is False assert result[1]["post_id"] == 999 @patch("mcp_kanka.operations.get_service") async def test_delete_posts(self, mock_get_service): """Test deleting posts.""" # Mock service mock_service = Mock() mock_get_service.return_value = mock_service # Mock delete results mock_service.delete_post.side_effect = [True, Exception("Post not found")] # Test delete posts result = await handle_delete_posts( deletions=[ {"entity_id": 101, "post_id": 50}, {"entity_id": 101, "post_id": 999}, ] ) assert len(result) == 2 assert result[0]["success"] is True assert result[0]["entity_id"] == 101 assert result[0]["post_id"] == 50 assert result[1]["success"] is False assert result[1]["post_id"] == 999

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/ervwalter/mcp-kanka'

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