Skip to main content
Glama

LinkedIn Content Creation MCP Server

by chrishayuk
test_composition_tools_coverage.py26.6 kB
"""Comprehensive tests for composition_tools.py coverage. Tests all error handling paths and edge cases. """ from unittest.mock import MagicMock, patch import pytest from chuk_mcp_linkedin.manager import Draft from chuk_mcp_linkedin.tools import composition_tools @pytest.fixture def mock_mcp(): """Create a mock MCP server""" mcp = MagicMock() mcp.registered_tools = {} def tool_decorator(func): mcp.registered_tools[func.__name__] = func return func mcp.tool = tool_decorator return mcp @pytest.fixture def mock_manager(): """Create a mock manager""" manager = MagicMock() manager.current_draft_id = "draft-123" return manager class TestCompositionToolsErrorPaths: """Test error handling paths in composition tools""" @pytest.fixture(autouse=True) def patch_manager(self, mock_manager): """Automatically patch get_current_manager for all tests in this class""" with patch( "chuk_mcp_linkedin.tools.composition_tools.get_current_manager", return_value=mock_manager, ): yield @pytest.mark.asyncio async def test_add_hook_exception_handling(self, mock_mcp, mock_manager): """Test hook with exception from ComposablePost""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) # Trigger validation error by using invalid hook type with ComposablePost with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_hook.side_effect = ValueError("Invalid hook type") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_hook"]("invalid_type", "content") assert "Invalid hook type" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_body_exception_handling(self, mock_mcp, mock_manager): """Test body with exception from ComposablePost""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_body.side_effect = ValueError("Content too long") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_body"]("x" * 10000, "linear") assert "Content too long" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_cta_exception_handling(self, mock_mcp, mock_manager): """Test CTA with exception from ComposablePost""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_cta.side_effect = ValueError("Invalid CTA type") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_cta"]("invalid", "text") assert "Invalid CTA type" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_bar_chart_exception(self, mock_mcp, mock_manager): """Test bar chart with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_bar_chart.side_effect = ValueError("Invalid data") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_bar_chart"]({"A": "invalid"}, "Title") assert "Invalid data" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_metrics_chart_exception(self, mock_mcp, mock_manager): """Test metrics chart with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_metrics_chart.side_effect = ValueError("Invalid metrics") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_metrics_chart"]({"A": 123}, "Title") assert "Invalid metrics" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_comparison_chart_exception(self, mock_mcp, mock_manager): """Test comparison chart with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_comparison_chart.side_effect = ValueError("Need 2 options") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_comparison_chart"]({"A": "only one"}, "Title") assert "Need 2 options" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_progress_chart_exception(self, mock_mcp, mock_manager): """Test progress chart with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_progress_chart.side_effect = ValueError("Invalid percentage") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_progress_chart"]({"A": 150}, "Title") assert "Invalid percentage" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_ranking_chart_exception(self, mock_mcp, mock_manager): """Test ranking chart with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_ranking_chart.side_effect = ValueError("Invalid ranking data") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_ranking_chart"]({}, "Title") assert "Invalid ranking data" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_quote_exception(self, mock_mcp, mock_manager): """Test quote with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_quote.side_effect = ValueError("Quote too long") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_quote"]("x" * 1000, "Author") assert "Quote too long" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_big_stat_exception(self, mock_mcp, mock_manager): """Test big stat with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_big_stat.side_effect = ValueError("Invalid stat") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_big_stat"]("", "label") assert "Invalid stat" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_timeline_exception(self, mock_mcp, mock_manager): """Test timeline with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_timeline.side_effect = ValueError("Empty timeline") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_timeline"]({}, "Title") assert "Empty timeline" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_key_takeaway_exception(self, mock_mcp, mock_manager): """Test key takeaway with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_key_takeaway.side_effect = ValueError("Message too long") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_key_takeaway"]("x" * 1000) assert "Message too long" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_pro_con_exception(self, mock_mcp, mock_manager): """Test pro/con with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_pro_con.side_effect = ValueError("Empty lists") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_pro_con"]([], []) assert "Empty lists" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_separator_exception(self, mock_mcp, mock_manager): """Test separator with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_separator.side_effect = ValueError("Invalid style") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_separator"]("invalid") assert "Invalid style" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_checklist_exception(self, mock_mcp, mock_manager): """Test checklist with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_checklist.side_effect = ValueError("Invalid checklist items") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_checklist"]([]) assert "Invalid checklist items" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_before_after_exception(self, mock_mcp, mock_manager): """Test before/after with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_before_after.side_effect = ValueError("Mismatched lists") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_before_after"]([], []) assert "Mismatched lists" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_tip_box_exception(self, mock_mcp, mock_manager): """Test tip box with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_tip_box.side_effect = ValueError("Message empty") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_tip_box"]("") assert "Message empty" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_stats_grid_exception(self, mock_mcp, mock_manager): """Test stats grid with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_stats_grid.side_effect = ValueError("Invalid columns") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_stats_grid"]({"A": "1"}, columns=10) assert "Invalid columns" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_poll_preview_exception(self, mock_mcp, mock_manager): """Test poll preview with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_poll_preview.side_effect = ValueError("Not enough options") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_poll_preview"]("Question?", ["A"]) assert "Not enough options" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_feature_list_exception(self, mock_mcp, mock_manager): """Test feature list with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_feature_list.side_effect = ValueError("Missing title") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_feature_list"]([{"no_title": "oops"}]) assert "Missing title" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_numbered_list_exception(self, mock_mcp, mock_manager): """Test numbered list with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_numbered_list.side_effect = ValueError("Empty list") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_numbered_list"]([]) assert "Empty list" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_add_hashtags_exception(self, mock_mcp, mock_manager): """Test hashtags with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.add_hashtags.side_effect = ValueError("No tags") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_add_hashtags"]([]) assert "No tags" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_compose_post_exception(self, mock_mcp, mock_manager): """Test compose post with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.compose.side_effect = ValueError("Content exceeds limit") mock_post.optimize_for_engagement.return_value = None # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_compose_post"](optimize=True) assert "Content exceeds limit" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_get_preview_exception(self, mock_mcp, mock_manager): """Test get preview with exception""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) with patch("chuk_mcp_linkedin.tools.composition_tools.ComposablePost") as _: mock_post = MagicMock() mock_post.get_preview.side_effect = ValueError("No content") # Add mock to cache composition_tools._post_cache["draft-123"] = mock_post result = await tools["linkedin_get_preview"]() assert "No content" in result # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_get_or_create_post_with_theme(self, mock_mcp, mock_manager): """Test _get_or_create_post creates post with theme""" from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme="thought_leader" ) mock_manager.get_current_draft.return_value = mock_draft tools = register_composition_tools(mock_mcp) # Call any tool to trigger _get_or_create_post with theme result = await tools["linkedin_add_body"]("Test content") assert "Added body" in result # Verify ComposablePost was created in cache with theme assert "draft-123" in composition_tools._post_cache # Cleanup cache composition_tools._post_cache.clear() @pytest.mark.asyncio async def test_preview_html_failed_generation(self, mock_mcp, mock_manager): """Test HTML preview when generation fails""" from unittest.mock import AsyncMock from chuk_mcp_linkedin.tools.composition_tools import register_composition_tools mock_draft = Draft( draft_id="draft-123", name="Test", post_type="text", content={}, theme=None ) mock_manager.get_current_draft.return_value = mock_draft mock_manager.generate_preview_url = AsyncMock(return_value=None) # Failure tools = register_composition_tools(mock_mcp) result = await tools["linkedin_preview_html"](open_browser=False) assert "Failed to generate preview" in result

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/chrishayuk/chuk-mcp-linkedin'

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