Skip to main content
Glama

LinkedIn Content Creation MCP Server

by chrishayuk
test_composition.pyโ€ข19.1 kB
"""Tests for posts/composition module.""" import pytest from chuk_mcp_linkedin.posts.composition import ComposablePost, PostBuilder class TestComposablePostInit: """Test ComposablePost initialization""" def test_init_basic(self): """Test basic initialization""" post = ComposablePost("text") assert post.post_type == "text" assert post.theme is None assert post.variant_config == {} assert post.components == [] assert post.metadata == {} def test_init_with_theme(self): """Test initialization with theme""" theme = type("Theme", (), {"name": "professional"})() post = ComposablePost("text", theme=theme) assert post.theme == theme def test_init_with_variant_config(self): """Test initialization with variant config""" config = {"structure": "listicle"} post = ComposablePost("text", variant_config=config) assert post.variant_config == config class TestComposablePostContentComponents: """Test adding content components""" def test_add_hook(self): """Test adding hook""" post = ComposablePost("text") result = post.add_hook("question", "Why is this important?") assert result is post # Fluent interface assert len(post.components) == 1 def test_add_body(self): """Test adding body""" post = ComposablePost("text") result = post.add_body("Content here") assert result is post assert len(post.components) == 1 def test_add_body_with_structure(self): """Test adding body with structure""" post = ComposablePost("text") post.add_body("Content", structure="listicle") assert len(post.components) == 1 def test_add_body_uses_variant_config(self): """Test body uses variant config structure""" post = ComposablePost("text", variant_config={"structure": "framework"}) post.add_body("Content") # Structure should come from variant_config assert len(post.components) == 1 def test_add_cta(self): """Test adding CTA""" post = ComposablePost("text") result = post.add_cta("direct", "Click here") assert result is post assert len(post.components) == 1 def test_add_hashtags(self): """Test adding hashtags""" post = ComposablePost("text") result = post.add_hashtags(["AI", "Tech"]) assert result is post assert len(post.components) == 1 def test_add_hashtags_with_placement(self): """Test adding hashtags with placement""" post = ComposablePost("text") post.add_hashtags(["AI", "Tech"], placement="inline") assert len(post.components) == 1 class TestComposablePostDataVizComponents: """Test adding data visualization components""" def test_add_bar_chart(self): """Test adding bar chart""" post = ComposablePost("text") result = post.add_bar_chart({"A": 10, "B": 20}) assert result is post assert len(post.components) == 1 def test_add_bar_chart_with_title(self): """Test adding bar chart with title and unit""" post = ComposablePost("text") post.add_bar_chart({"A": 10}, title="Chart", unit="hours") assert len(post.components) == 1 def test_add_metrics_chart(self): """Test adding metrics chart""" post = ComposablePost("text") result = post.add_metrics_chart({"Speed": "67%"}) assert result is post assert len(post.components) == 1 def test_add_comparison_chart(self): """Test adding comparison chart""" post = ComposablePost("text") result = post.add_comparison_chart({"A": "Point", "B": "Point"}) assert result is post assert len(post.components) == 1 def test_add_progress_chart(self): """Test adding progress chart""" post = ComposablePost("text") result = post.add_progress_chart({"Task": 50}) assert result is post assert len(post.components) == 1 def test_add_ranking_chart(self): """Test adding ranking chart""" post = ComposablePost("text") result = post.add_ranking_chart({"First": "100"}) assert result is post assert len(post.components) == 1 def test_add_ranking_chart_without_medals(self): """Test adding ranking chart without medals""" post = ComposablePost("text") post.add_ranking_chart({"First": "100"}, show_medals=False) assert len(post.components) == 1 class TestComposablePostFeatureComponents: """Test adding feature components""" def test_add_quote(self): """Test adding quote""" post = ComposablePost("text") result = post.add_quote("Quote text", "Author") assert result is post assert len(post.components) == 1 def test_add_quote_with_source(self): """Test adding quote with source""" post = ComposablePost("text") post.add_quote("Quote", "Author", source="CEO") assert len(post.components) == 1 def test_add_big_stat(self): """Test adding big stat""" post = ComposablePost("text") result = post.add_big_stat("2.5M", "users") assert result is post assert len(post.components) == 1 def test_add_big_stat_with_context(self): """Test adding big stat with context""" post = ComposablePost("text") post.add_big_stat("10x", "faster", context="growth") assert len(post.components) == 1 def test_add_timeline(self): """Test adding timeline""" post = ComposablePost("text") result = post.add_timeline({"2024": "Launch", "2025": "Scale"}) assert result is post assert len(post.components) == 1 def test_add_timeline_with_style(self): """Test adding timeline with style""" post = ComposablePost("text") post.add_timeline({"2024": "Launch"}, style="numbered") assert len(post.components) == 1 def test_add_key_takeaway(self): """Test adding key takeaway""" post = ComposablePost("text") result = post.add_key_takeaway("Important message") assert result is post assert len(post.components) == 1 def test_add_key_takeaway_custom(self): """Test adding key takeaway with custom title and style""" post = ComposablePost("text") post.add_key_takeaway("Message", title="Note", style="highlight") assert len(post.components) == 1 def test_add_pro_con(self): """Test adding pro/con""" post = ComposablePost("text") result = post.add_pro_con(["Pro1"], ["Con1"]) assert result is post assert len(post.components) == 1 def test_add_pro_con_with_title(self): """Test adding pro/con with title""" post = ComposablePost("text") post.add_pro_con(["Pro"], ["Con"], title="Analysis") assert len(post.components) == 1 def test_add_checklist(self): """Test adding checklist""" post = ComposablePost("text") result = post.add_checklist([{"text": "Task 1"}]) assert result is post assert len(post.components) == 1 def test_add_checklist_with_progress(self): """Test adding checklist with progress""" post = ComposablePost("text") post.add_checklist([{"text": "Task"}], show_progress=True) assert len(post.components) == 1 def test_add_before_after(self): """Test adding before/after""" post = ComposablePost("text") result = post.add_before_after(["Old"], ["New"]) assert result is post assert len(post.components) == 1 def test_add_before_after_with_labels(self): """Test adding before/after with labels""" post = ComposablePost("text") post.add_before_after(["Old"], ["New"], labels={"before": "Before", "after": "After"}) assert len(post.components) == 1 def test_add_tip_box(self): """Test adding tip box""" post = ComposablePost("text") result = post.add_tip_box("Helpful tip") assert result is post assert len(post.components) == 1 def test_add_tip_box_custom(self): """Test adding tip box with custom style""" post = ComposablePost("text") post.add_tip_box("Tip", title="Note", style="warning") assert len(post.components) == 1 def test_add_stats_grid(self): """Test adding stats grid""" post = ComposablePost("text") result = post.add_stats_grid({"A": "1", "B": "2"}) assert result is post assert len(post.components) == 1 def test_add_stats_grid_custom(self): """Test adding stats grid with custom columns""" post = ComposablePost("text") post.add_stats_grid({"A": "1", "B": "2"}, columns=3) assert len(post.components) == 1 def test_add_poll_preview(self): """Test adding poll preview""" post = ComposablePost("text") result = post.add_poll_preview("Question?", ["A", "B"]) assert result is post assert len(post.components) == 1 def test_add_feature_list(self): """Test adding feature list""" post = ComposablePost("text") result = post.add_feature_list([{"title": "Feature 1"}]) assert result is post assert len(post.components) == 1 def test_add_numbered_list(self): """Test adding numbered list""" post = ComposablePost("text") result = post.add_numbered_list(["Item 1", "Item 2"]) assert result is post assert len(post.components) == 1 def test_add_numbered_list_custom(self): """Test adding numbered list with custom style""" post = ComposablePost("text") post.add_numbered_list(["Item"], style="emoji_numbers", start=5) assert len(post.components) == 1 class TestComposablePostLayoutComponents: """Test adding layout components""" def test_add_separator(self): """Test adding separator""" post = ComposablePost("text") result = post.add_separator() assert result is post assert len(post.components) == 1 def test_add_separator_custom(self): """Test adding separator with custom style""" post = ComposablePost("text") post.add_separator(style="dots") assert len(post.components) == 1 class TestComposablePostComposition: """Test post composition methods""" def test_compose_empty(self): """Test composing empty post""" post = ComposablePost("text") result = post.compose() assert result == "" def test_compose_single_component(self): """Test composing with single component""" post = ComposablePost("text") post.add_hook("question", "Why?") result = post.compose() assert "Why?" in result def test_compose_multiple_components(self): """Test composing with multiple components""" post = ComposablePost("text") post.add_hook("question", "Why?") post.add_body("Because.") post.add_cta("direct", "Click here") result = post.compose() assert "Why?" in result assert "Because." in result assert "Click here" in result def test_compose_with_separator(self): """Test composing with separator""" post = ComposablePost("text") post.add_body("Part 1") post.add_separator() post.add_body("Part 2") result = post.compose() assert "Part 1" in result assert "Part 2" in result def test_compose_too_long_raises_error(self): """Test compose raises error if too long""" post = ComposablePost("text") # Add content that exceeds max length (LinkedIn limit is 3000) # Need to add multiple components to exceed limit since Body has its own validation for i in range(6): post.add_body("x" * 500) # 6 * 500 = 3000 chars + separators with pytest.raises(ValueError, match="exceeds.*character limit"): post.compose() def test_get_preview_short(self): """Test get_preview with short content""" post = ComposablePost("text") post.add_body("Short content") preview = post.get_preview(210) assert preview == "Short content" def test_get_preview_long(self): """Test get_preview with long content""" post = ComposablePost("text") post.add_body("A" * 300) preview = post.get_preview(210) assert len(preview) == 213 # 210 + "..." assert preview.endswith("...") class TestComposablePostOptimization: """Test engagement optimization""" def test_optimize_adds_hook_if_missing(self): """Test optimize adds hook if missing""" theme = type("Theme", (), {"name": "professional", "hook_style": "question"})() post = ComposablePost("text", theme=theme) post.add_body("Content") post.optimize_for_engagement() # Should have hook + body + cta (optimize adds both) assert len(post.components) == 3 def test_optimize_adds_cta_if_missing(self): """Test optimize adds CTA if missing""" theme = type("Theme", (), {"name": "professional", "cta_style": "curiosity"})() post = ComposablePost("text", theme=theme) post.add_body("Content") post.optimize_for_engagement() # Should have hook + body + cta assert len(post.components) == 3 def test_optimize_does_not_add_if_present(self): """Test optimize does not add if hook/cta already present""" theme = type("Theme", (), {"name": "professional"})() post = ComposablePost("text", theme=theme) post.add_hook("question", "Why?") post.add_body("Content") post.add_cta("direct", "Act") post.optimize_for_engagement() # Should still have just 3 components assert len(post.components) == 3 def test_optimize_without_theme(self): """Test optimize without theme does nothing""" post = ComposablePost("text") post.add_body("Content") post.optimize_for_engagement() # Should still have just 1 component assert len(post.components) == 1 class TestComposablePostToDict: """Test to_dict export""" def test_to_dict_basic(self): """Test basic to_dict""" post = ComposablePost("text") post.add_hook("question", "Why?") result = post.to_dict() assert result["post_type"] == "text" assert result["theme"] is None assert len(result["components"]) == 1 assert "final_text" in result assert "character_count" in result assert "preview" in result def test_to_dict_with_theme(self): """Test to_dict with theme""" theme = type( "Theme", (), { "name": "professional", "line_break_style": "moderate", "emoji_level": "minimal", "controversy_level": "safe", }, )() post = ComposablePost("text", theme=theme) post.add_hook("question", "Test") result = post.to_dict() assert result["theme"] == "professional" class TestPostBuilder: """Test PostBuilder helper patterns""" def test_thought_leadership_post(self): """Test thought leadership pattern""" theme = type( "Theme", (), { "name": "professional", "controversy_level": "safe", "emoji_level": "minimal", "line_break_style": "moderate", "hashtag_strategy": "minimal", }, )() post = PostBuilder.thought_leadership_post( hook_stat="95% agree", framework_name="3-Part Framework", framework_parts=["Part 1", "Part 2", "Part 3"], conclusion="Apply this today", theme=theme, ) assert len(post.components) > 0 result = post.compose() assert "95% agree" in result assert "3-Part Framework" in result def test_story_post(self): """Test story pattern""" theme = type( "Theme", (), { "name": "storyteller", "controversy_level": "safe", "emoji_level": "moderate", "line_break_style": "generous", }, )() post = PostBuilder.story_post( hook="Let me tell you", problem="The problem was", journey="The journey", solution="The solution", lesson="The lesson learned", theme=theme, ) assert len(post.components) > 0 result = post.compose() assert "Let me tell you" in result assert "lesson learned" in result def test_listicle_post(self): """Test listicle pattern""" theme = type( "Theme", (), { "name": "personal_brand", "controversy_level": "safe", "emoji_level": "moderate", "line_break_style": "moderate", }, )() post = PostBuilder.listicle_post( hook="5 key points", items=["Point 1", "Point 2", "Point 3"], conclusion="That's it", theme=theme, ) assert len(post.components) > 0 result = post.compose() assert "5 key points" in result assert "Point 1" in result def test_comparison_post(self): """Test comparison pattern""" theme = type( "Theme", (), { "name": "technical_expert", "controversy_level": "safe", "emoji_level": "none", "line_break_style": "tight", }, )() post = PostBuilder.comparison_post( hook="Which is better?", option_a="Option A features", option_b="Option B features", recommendation="I recommend A", theme=theme, ) assert len(post.components) > 0 result = post.compose() assert "Which is better?" in result assert "recommend" in result class TestComposablePostFluent: """Test fluent interface chaining""" def test_fluent_chaining(self): """Test method chaining""" post = ( ComposablePost("text") .add_hook("question", "Why?") .add_body("Content") .add_separator() .add_cta("direct", "Act") .add_hashtags(["Test"]) ) assert len(post.components) == 5 result = post.compose() assert "Why?" in result assert "Content" in result assert "Act" in result

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

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