Skip to main content
Glama

LinkedIn Content Creation MCP Server

by chrishayuk
test_manager.py34.3 kB
""" Tests for LinkedInManager and Draft. """ import json import tempfile from pathlib import Path import pytest from chuk_mcp_linkedin.manager import Draft, LinkedInManager class TestDraft: """Test Draft class""" def test_draft_creation(self): """Test creating a draft""" draft = Draft( draft_id="draft_1", name="My Post", post_type="text", content={"text": "Hello world"}, theme="thought_leader", ) assert draft.draft_id == "draft_1" assert draft.name == "My Post" assert draft.post_type == "text" assert draft.theme == "thought_leader" def test_draft_update_content(self): """Test updating draft content""" import time draft = Draft( draft_id="draft_1", name="My Post", post_type="text", content={"text": "Hello"} ) original_updated = draft.updated_at # Small delay to ensure timestamp changes on all platforms (including Windows) time.sleep(0.01) draft.update_content({"text": "Hello world"}) assert draft.content["text"] == "Hello world" assert draft.updated_at != original_updated def test_draft_to_dict(self): """Test converting draft to dictionary""" draft = Draft( draft_id="draft_1", name="My Post", post_type="text", content={"text": "Hello"}, theme="thought_leader", ) draft_dict = draft.to_dict() assert draft_dict["draft_id"] == "draft_1" assert draft_dict["name"] == "My Post" assert draft_dict["theme"] == "thought_leader" assert "created_at" in draft_dict assert "updated_at" in draft_dict def test_draft_from_dict(self): """Test creating draft from dictionary""" data = { "draft_id": "draft_1", "name": "My Post", "post_type": "text", "content": {"text": "Hello"}, "theme": "thought_leader", } draft = Draft.from_dict(data) assert draft.draft_id == "draft_1" assert draft.name == "My Post" assert draft.theme == "thought_leader" def test_draft_name_setter(self): """Test setting draft name updates timestamp""" import time draft = Draft(draft_id="draft_1", name="Original", post_type="text", content={}) original_updated = draft.updated_at time.sleep(0.01) draft.name = "Updated Name" assert draft.name == "Updated Name" assert draft.updated_at != original_updated def test_draft_created_at_setter(self): """Test setting created_at timestamp""" draft = Draft(draft_id="draft_1", name="Test", post_type="text", content={}) custom_timestamp = "2024-01-01T12:00:00" draft.created_at = custom_timestamp assert draft.created_at == custom_timestamp def test_draft_updated_at_setter(self): """Test setting updated_at timestamp""" draft = Draft(draft_id="draft_1", name="Test", post_type="text", content={}) custom_timestamp = "2024-01-01T13:00:00" draft.updated_at = custom_timestamp assert draft.updated_at == custom_timestamp def test_draft_metadata_property(self): """Test accessing draft metadata""" draft = Draft(draft_id="draft_1", name="Test", post_type="text", content={}) # Metadata should be accessible assert isinstance(draft.metadata, dict) def test_draft_preview_token_property(self): """Test accessing preview token""" draft = Draft(draft_id="draft_1", name="Test", post_type="text", content={}) # Preview token should be accessible and non-empty assert draft.preview_token assert isinstance(draft.preview_token, str) class TestLinkedInManager: """Test LinkedInManager class""" @pytest.fixture def temp_storage(self): """Create temporary storage for tests""" with tempfile.TemporaryDirectory() as tmpdir: yield tmpdir @pytest.fixture def manager(self, temp_storage): """Create manager with temporary storage""" return LinkedInManager(storage_path=temp_storage) def test_manager_initialization(self, temp_storage): """Test manager initializes correctly""" manager = LinkedInManager(storage_path=temp_storage) assert manager.storage_path == Path(temp_storage) assert len(manager.drafts) == 0 assert manager.current_draft_id is None def test_create_draft(self, manager): """Test creating a draft""" draft = manager.create_draft( name="My Post", post_type="text", content={"text": "Hello world"}, theme="thought_leader", ) assert draft.name == "My Post" assert draft.post_type == "text" assert draft.theme == "thought_leader" assert draft.draft_id in manager.drafts assert manager.current_draft_id == draft.draft_id def test_create_multiple_drafts(self, manager): """Test creating multiple drafts""" manager.create_draft("Post 1", "text") draft2 = manager.create_draft("Post 2", "poll") assert len(manager.drafts) == 2 assert manager.current_draft_id == draft2.draft_id def test_get_draft(self, manager): """Test getting a draft by ID""" draft = manager.create_draft("My Post", "text") retrieved = manager.get_draft(draft.draft_id) assert retrieved is not None assert retrieved.draft_id == draft.draft_id def test_get_draft_not_found(self, manager): """Test getting non-existent draft""" draft = manager.get_draft("nonexistent") assert draft is None def test_get_current_draft(self, manager): """Test getting current draft""" draft = manager.create_draft("My Post", "text") current = manager.get_current_draft() assert current is not None assert current.draft_id == draft.draft_id def test_get_current_draft_none(self, manager): """Test getting current draft when none exists""" current = manager.get_current_draft() assert current is None def test_list_drafts(self, manager): """Test listing all drafts""" manager.create_draft("Post 1", "text") manager.create_draft("Post 2", "poll") drafts = manager.list_drafts() assert len(drafts) == 2 assert drafts[0]["name"] == "Post 1" assert drafts[1]["name"] == "Post 2" assert drafts[1]["is_current"] is True def test_switch_draft(self, manager): """Test switching to a different draft""" draft1 = manager.create_draft("Post 1", "text") manager.create_draft("Post 2", "poll") # Switch back to draft1 success = manager.switch_draft(draft1.draft_id) assert success is True assert manager.current_draft_id == draft1.draft_id def test_switch_draft_invalid(self, manager): """Test switching to invalid draft""" success = manager.switch_draft("nonexistent") assert success is False def test_update_draft(self, manager): """Test updating a draft""" draft = manager.create_draft("My Post", "text", content={"text": "Hello"}) success = manager.update_draft( draft.draft_id, content={"text": "Hello world"}, theme="personal_brand" ) assert success is True updated_draft = manager.get_draft(draft.draft_id) assert updated_draft.content["text"] == "Hello world" assert updated_draft.theme == "personal_brand" def test_update_draft_not_found(self, manager): """Test updating non-existent draft""" success = manager.update_draft("nonexistent", content={"text": "Hello"}) assert success is False def test_delete_draft(self, manager): """Test deleting a draft""" draft = manager.create_draft("My Post", "text") success = manager.delete_draft(draft.draft_id) assert success is True assert draft.draft_id not in manager.drafts def test_delete_draft_updates_current(self, manager): """Test deleting current draft updates current_draft_id""" draft1 = manager.create_draft("Post 1", "text") draft2 = manager.create_draft("Post 2", "poll") # Delete current draft manager.delete_draft(draft2.draft_id) assert manager.current_draft_id == draft1.draft_id def test_delete_draft_not_found(self, manager): """Test deleting non-existent draft""" success = manager.delete_draft("nonexistent") assert success is False def test_clear_all_drafts(self, manager): """Test clearing all drafts""" manager.create_draft("Post 1", "text") manager.create_draft("Post 2", "poll") manager.create_draft("Post 3", "document") count = manager.clear_all_drafts() assert count == 3 assert len(manager.drafts) == 0 assert manager.current_draft_id is None def test_export_draft(self, manager): """Test exporting draft as JSON""" draft = manager.create_draft( "My Post", "text", content={"text": "Hello"}, theme="thought_leader" ) json_str = manager.export_draft(draft.draft_id) assert json_str is not None # Parse and verify data = json.loads(json_str) assert data["name"] == "My Post" assert data["theme"] == "thought_leader" def test_export_draft_not_found(self, manager): """Test exporting non-existent draft""" json_str = manager.export_draft("nonexistent") assert json_str is None def test_import_draft(self, manager): """Test importing draft from JSON""" draft_json = json.dumps( { "draft_id": "imported_1", "name": "Imported Post", "post_type": "text", "content": {"text": "Imported content"}, "theme": "storyteller", } ) draft = manager.import_draft(draft_json) assert draft is not None assert draft.name == "Imported Post" assert draft.theme == "storyteller" assert draft.draft_id in manager.drafts def test_import_draft_duplicate_id(self, manager): """Test importing draft with duplicate ID gets new ID""" draft1 = manager.create_draft("Post 1", "text") draft_json = json.dumps( { "draft_id": draft1.draft_id, # Same ID "name": "Duplicate", "post_type": "text", "content": {"text": "Content"}, } ) draft2 = manager.import_draft(draft_json) assert draft2.draft_id != draft1.draft_id def test_import_draft_invalid_json(self, manager): """Test importing invalid JSON""" draft = manager.import_draft("invalid json{") assert draft is None def test_get_draft_preview(self, manager): """Test getting draft preview""" draft = manager.create_draft("My Post", "text", content={"commentary": "x" * 300}) preview = manager.get_draft_preview(draft.draft_id, chars=210) assert len(preview) <= 213 # 210 + "..." def test_get_draft_preview_not_found(self, manager): """Test getting preview of non-existent draft""" preview = manager.get_draft_preview("nonexistent") assert preview is None def test_get_draft_stats(self, manager): """Test getting draft statistics""" draft = manager.create_draft( "My Post", "text", content={ "commentary": "Hello world this is my post", "hook": "Attention grabber", "cta": "What do you think?", "hashtags": ["Marketing", "LinkedIn"], }, ) stats = manager.get_draft_stats(draft.draft_id) assert stats is not None assert stats["word_count"] > 0 assert stats["char_count"] > 0 assert stats["has_hook"] is True assert stats["has_cta"] is True assert stats["hashtag_count"] == 2 def test_get_draft_stats_not_found(self, manager): """Test getting stats of non-existent draft""" stats = manager.get_draft_stats("nonexistent") assert stats is None def test_get_info(self, manager): """Test getting manager info""" manager.create_draft("Post 1", "text") manager.create_draft("Post 2", "poll") info = manager.get_info() assert info["total_drafts"] == 2 assert info["current_draft_id"] is not None assert "text" in info["draft_types"] assert "poll" in info["draft_types"] def test_persistence(self, temp_storage): """Test drafts persist across manager instances""" # Create drafts with first manager manager1 = LinkedInManager(storage_path=temp_storage) draft1 = manager1.create_draft("Post 1", "text") draft2 = manager1.create_draft("Post 2", "poll") # Create new manager instance with same storage manager2 = LinkedInManager(storage_path=temp_storage) # Drafts should be loaded assert len(manager2.drafts) == 2 assert draft1.draft_id in manager2.drafts assert draft2.draft_id in manager2.drafts def test_save_and_load_preserves_data(self, temp_storage): """Test save and load preserves all draft data""" manager1 = LinkedInManager(storage_path=temp_storage) draft = manager1.create_draft( "My Post", "text", content={"text": "Hello", "hook": "Stat"}, theme="thought_leader", variant_config={"style": "insight"}, ) # Load in new manager manager2 = LinkedInManager(storage_path=temp_storage) loaded_draft = manager2.get_draft(draft.draft_id) assert loaded_draft.name == "My Post" assert loaded_draft.post_type == "text" assert loaded_draft.theme == "thought_leader" assert loaded_draft.content["text"] == "Hello" assert loaded_draft.variant_config["style"] == "insight" def test_update_draft_with_variant_config(self, manager): """Test updating draft with variant config""" draft = manager.create_draft("My Post", "text", variant_config={"style": "insight"}) # Update variant config manager.update_draft(draft.draft_id, variant_config={"tone": "professional"}) updated_draft = manager.get_draft(draft.draft_id) assert updated_draft.variant_config["style"] == "insight" assert updated_draft.variant_config["tone"] == "professional" def test_get_draft_preview_with_text_fallback(self, manager): """Test getting draft preview falls back to text field""" draft = manager.create_draft("My Post", "text", content={"text": "x" * 300}) preview = manager.get_draft_preview(draft.draft_id, chars=210) assert len(preview) <= 213 # 210 + "..." def test_get_draft_preview_short_content(self, manager): """Test getting draft preview with short content""" draft = manager.create_draft("My Post", "text", content={"commentary": "Short"}) preview = manager.get_draft_preview(draft.draft_id, chars=210) assert preview == "Short" def test_load_drafts_with_corrupted_file(self, temp_storage): """Test loading drafts skips corrupted files""" manager1 = LinkedInManager(storage_path=temp_storage) manager1.create_draft("Valid Post", "text") # Create a corrupted JSON file corrupted_file = Path(temp_storage) / "corrupted.json" with open(corrupted_file, "w") as f: f.write("invalid json{") # Load in new manager - should skip corrupted file manager2 = LinkedInManager(storage_path=temp_storage) assert len(manager2.drafts) == 1 # Only the valid draft loaded def test_user_id_generation(self, temp_storage): """Test that manager has a user ID""" manager = LinkedInManager(storage_path=temp_storage) # Default user_id is None (used for backward compatibility) assert manager.user_id is None or isinstance(manager.user_id, str) def test_custom_user_id(self, temp_storage): """Test manager with custom user ID""" manager = LinkedInManager(storage_path=temp_storage, user_id="user123") assert manager.user_id == "user123" def test_get_draft_by_preview_token(self, manager): """Test getting draft by preview token""" draft1 = manager.create_draft("Post 1", "text") draft2 = manager.create_draft("Post 2", "text") # Get draft by its preview token found_draft = manager.get_draft_by_preview_token(draft1.preview_token) assert found_draft is not None assert found_draft.draft_id == draft1.draft_id assert found_draft.name == "Post 1" # Get second draft by its token found_draft2 = manager.get_draft_by_preview_token(draft2.preview_token) assert found_draft2 is not None assert found_draft2.draft_id == draft2.draft_id def test_get_draft_by_preview_token_not_found(self, manager): """Test getting draft with invalid preview token""" manager.create_draft("Post 1", "text") # Non-existent token found_draft = manager.get_draft_by_preview_token("invalid-token-123") assert found_draft is None def test_user_isolation(self, temp_storage): """Test that different users have isolated drafts""" # User 1 creates drafts manager1 = LinkedInManager(storage_path=temp_storage, user_id="user1") draft1 = manager1.create_draft("User 1 Draft", "text") assert draft1 is not None # User 2 should have independent storage manager2 = LinkedInManager(storage_path=temp_storage, user_id="user2") # User 2 shouldn't see user 1's drafts (when user_id is used for isolation) # Note: With current filesystem implementation, drafts are shared unless # using artifacts with user_id scoping assert len(manager2.drafts) >= 0 # May see shared drafts in filesystem mode def test_draft_creation_with_user_id(self, temp_storage): """Test creating drafts with user ID""" manager = LinkedInManager(storage_path=temp_storage, user_id="testuser") draft = manager.create_draft("Test", "text") assert draft is not None assert draft.draft_id in manager.drafts def test_list_drafts_for_user(self, temp_storage): """Test listing drafts for a specific user""" manager = LinkedInManager(storage_path=temp_storage, user_id="user1") # Create drafts manager.create_draft("Draft 1", "text") manager.create_draft("Draft 2", "text") # List drafts drafts = manager.list_drafts() assert len(drafts) == 2 def test_list_drafts_includes_metadata(self, manager): """Test draft list includes metadata""" manager.create_draft("Test", "text") drafts = manager.list_drafts() assert len(drafts) == 1 draft_info = drafts[0] # Should include basic draft metadata assert "draft_id" in draft_info assert "name" in draft_info assert "post_type" in draft_info assert "is_current" in draft_info class TestLinkedInManagerWithArtifacts: """Test LinkedInManager with artifact storage""" @pytest.mark.asyncio async def test_artifact_storage_initialization(self): """Test artifact storage initialization""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: assert manager.use_artifacts is True assert manager.artifact_provider == "memory" @pytest.mark.asyncio async def test_store_draft_as_artifact(self): """Test storing draft as artifact""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: draft = manager.create_draft( name="Test Draft", post_type="text", content={"commentary": "Test"} ) artifact_id = await manager.store_draft_as_artifact(draft.draft_id) assert artifact_id is not None @pytest.mark.asyncio async def test_store_nonexistent_draft_as_artifact(self): """Test storing non-existent draft as artifact""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: artifact_id = await manager.store_draft_as_artifact("nonexistent") assert artifact_id is None @pytest.mark.asyncio async def test_retrieve_draft_from_artifact(self): """Test retrieving draft from artifact""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: # Create and store draft original = manager.create_draft( name="Test Draft", post_type="text", content={"commentary": "Test content"} ) artifact_id = await manager.store_draft_as_artifact(original.draft_id) # Retrieve from artifact retrieved = await manager.retrieve_draft_from_artifact(artifact_id) assert retrieved is not None assert retrieved.name == "Test Draft" assert retrieved.content["commentary"] == "Test content" @pytest.mark.asyncio async def test_retrieve_nonexistent_artifact(self): """Test retrieving non-existent artifact""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: retrieved = await manager.retrieve_draft_from_artifact("nonexistent") assert retrieved is None @pytest.mark.asyncio async def test_artifact_storage_disabled(self): """Test that artifact storage is disabled by default""" manager = LinkedInManager(use_artifacts=False) assert manager.use_artifacts is False @pytest.mark.asyncio async def test_artifact_provider_filesystem(self): """Test artifact provider with filesystem""" async with LinkedInManager(use_artifacts=True, artifact_provider="filesystem") as manager: assert manager.artifact_provider == "filesystem" draft = manager.create_draft("FS Test", "text") artifact_id = await manager.store_draft_as_artifact(draft.draft_id) assert artifact_id is not None @pytest.mark.asyncio async def test_store_draft_artifact_store_none(self): """Test storing draft when artifact store is None""" manager = LinkedInManager(use_artifacts=True, artifact_provider="memory") draft = manager.create_draft("Test", "text") # Access private attribute to set it to None (edge case testing) manager._artifact_store = None manager._artifact_initialized = True artifact_id = await manager.store_draft_as_artifact(draft.draft_id) assert artifact_id is None @pytest.mark.asyncio async def test_retrieve_draft_when_disabled(self): """Test retrieving draft from artifact when artifacts are disabled""" manager = LinkedInManager(use_artifacts=False) result = await manager.retrieve_draft_from_artifact("any-id") assert result is None @pytest.mark.asyncio async def test_retrieve_draft_artifact_store_none(self): """Test retrieving draft when artifact store is None""" manager = LinkedInManager(use_artifacts=True, artifact_provider="memory") # Set store to None after initialization (edge case) manager._artifact_store = None manager._artifact_initialized = True result = await manager.retrieve_draft_from_artifact("any-id") assert result is None @pytest.mark.asyncio async def test_retrieve_draft_artifact_not_found(self): """Test retrieving draft when artifact data is None""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: # Try to retrieve with ID that doesn't exist result = await manager.retrieve_draft_from_artifact("missing-id") assert result is None @pytest.mark.asyncio async def test_generate_html_preview_draft_not_found(self): """Test HTML preview generation when draft doesn't exist""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: result = await manager.generate_html_preview_async("nonexistent-draft") assert result is None @pytest.mark.asyncio async def test_generate_html_preview_artifact_store_none(self): """Test HTML preview generation when artifact store is None""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: draft = manager.create_draft("Test", "text") # Set store to None (edge case) manager._artifact_store = None manager._artifact_initialized = True result = await manager.generate_html_preview_async(draft.draft_id) assert result is None @pytest.mark.asyncio async def test_read_preview_html_draft_not_found(self): """Test reading preview HTML when draft doesn't exist""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: result = await manager.read_preview_html_async("nonexistent-draft") assert result is None @pytest.mark.asyncio async def test_read_preview_html_artifact_store_none(self): """Test reading preview HTML when artifact store is None""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: draft = manager.create_draft("Test", "text") # Set store to None (edge case) manager._artifact_store = None manager._artifact_initialized = True result = await manager.read_preview_html_async(draft.draft_id) assert result is None @pytest.mark.asyncio async def test_read_preview_html_artifact_metadata_no_match(self): """Test reading preview HTML when metadata doesn't match""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: draft = manager.create_draft("Test", "text") # Generate preview first await manager.generate_html_preview_async(draft.draft_id) # Now try to read with a different draft_id result = await manager.read_preview_html_async("different-draft-id") # Should generate new preview since no matching artifact found assert result is None @pytest.mark.asyncio async def test_read_preview_html_exception_handling(self): """Test reading preview HTML handles exceptions gracefully""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: draft = manager.create_draft("Test", "text") # Mock the artifact store to raise an exception from unittest.mock import AsyncMock manager._artifact_store.list_by_session = AsyncMock(side_effect=Exception("Test error")) result = await manager.read_preview_html_async(draft.draft_id) assert result is None @pytest.mark.asyncio async def test_generate_preview_url_draft_not_found(self): """Test generating preview URL when draft doesn't exist""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: result = await manager.generate_preview_url("nonexistent-draft") assert result is None @pytest.mark.asyncio async def test_generate_preview_url_s3(self): """Test generating preview URL with S3 storage""" async with LinkedInManager(use_artifacts=True, artifact_provider="s3") as manager: from unittest.mock import AsyncMock draft = manager.create_draft("Test", "text") # Mock the get_shareable_url method if manager._artifact_store: manager._artifact_store.get_shareable_url = AsyncMock( return_value="https://s3.amazonaws.com/signed-url" ) result = await manager.generate_preview_url(draft.draft_id) assert result == "https://s3.amazonaws.com/signed-url" @pytest.mark.asyncio async def test_generate_preview_url_s3_generation_failed(self): """Test generating preview URL with S3 when preview generation fails""" async with LinkedInManager(use_artifacts=True, artifact_provider="s3") as manager: draft = manager.create_draft("Test", "text") # Mock generate_html_preview_async to return None from unittest.mock import AsyncMock manager.generate_html_preview_async = AsyncMock(return_value=None) result = await manager.generate_preview_url(draft.draft_id) assert result is None @pytest.mark.asyncio async def test_generate_preview_url_s3_store_none(self): """Test generating preview URL with S3 when store is None""" async with LinkedInManager(use_artifacts=True, artifact_provider="s3") as manager: draft = manager.create_draft("Test", "text") # Set store to None manager._artifact_store = None manager._artifact_initialized = True result = await manager.generate_preview_url(draft.draft_id) assert result is None @pytest.mark.asyncio async def test_generate_preview_url_s3_no_signed_url(self): """Test generating preview URL with S3 when signed URL returns None""" async with LinkedInManager(use_artifacts=True, artifact_provider="s3") as manager: from unittest.mock import AsyncMock draft = manager.create_draft("Test", "text") # Mock get_shareable_url to return None if manager._artifact_store: manager._artifact_store.get_shareable_url = AsyncMock(return_value=None) result = await manager.generate_preview_url(draft.draft_id) assert result is None @pytest.mark.asyncio async def test_generate_preview_url_memory_provider(self): """Test generating preview URL with memory provider""" async with LinkedInManager(use_artifacts=True, artifact_provider="memory") as manager: draft = manager.create_draft("Test", "text") result = await manager.generate_preview_url( draft.draft_id, base_url="http://localhost:8000" ) assert result is not None assert "http://localhost:8000/preview/" in result assert draft.preview_token in result @pytest.mark.asyncio async def test_preview_cross_user_isolation(self): """Test that preview HTML respects user isolation - no cross-user leakage""" import tempfile with tempfile.TemporaryDirectory() as tmpdir: # User 1: Create draft and generate preview async with LinkedInManager( storage_path=tmpdir, user_id="user1", use_artifacts=True, artifact_provider="memory", ) as manager1: draft1 = manager1.create_draft( "User 1 Draft", "text", content={"commentary": "User 1 content"} ) # Generate preview for user 1 draft artifact_id1 = await manager1.generate_html_preview_async(draft1.draft_id) assert artifact_id1 is not None # User 1 can read their own preview html1 = await manager1.read_preview_html_async(draft1.draft_id) assert html1 is not None assert "User 1 content" in html1 # User 2: Each user has their own artifact store (memory provider is per-manager) async with LinkedInManager( storage_path=tmpdir, user_id="user2", use_artifacts=True, artifact_provider="memory", ) as manager2: # User 2 can see draft1 exists (filesystem-based draft storage is shared in this test) _ = manager2.get_draft(draft1.draft_id) # In production with artifacts, user2 wouldn't see user1's drafts # But in this test with shared filesystem, they might # User 2 creates their own draft draft2 = manager2.create_draft( "User 2 Draft", "text", content={"commentary": "User 2 content"} ) # User 2 can generate/read their own preview artifact_id2 = await manager2.generate_html_preview_async(draft2.draft_id) assert artifact_id2 is not None html2 = await manager2.read_preview_html_async(draft2.draft_id) assert html2 is not None assert "User 2 content" in html2 # SECURITY VERIFICATION: Verify user1 and user2 artifacts are isolated # Re-open user1 and verify they can only access their own data async with LinkedInManager( storage_path=tmpdir, user_id="user1", use_artifacts=True, artifact_provider="memory", ) as manager1_reopen: draft1_reloaded = manager1_reopen.get_draft(draft1.draft_id) assert draft1_reloaded is not None # User 1 needs to regenerate preview (memory artifact store was cleared) html1_new = await manager1_reopen.read_preview_html_async(draft1.draft_id) assert html1_new is not None assert "User 1 content" in html1_new

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