Skip to main content
Glama
test_safe_editor_comprehensive.py•21.4 kB
""" Corrected comprehensive tests for SafeMarkdownEditor with better coverage. """ import pytest import threading from quantalogic_markdown_mcp import ( SafeMarkdownEditor, ValidationLevel, EditOperation, ErrorCategory, SectionReference, EditResult, SafeParseError ) class TestSafeMarkdownEditorFixed: """Fixed test suite for SafeMarkdownEditor.""" @pytest.fixture def sample_markdown(self): """Sample markdown for testing.""" return """# Main Document This is the main document content. ## Section A Content for section A. ### Subsection A1 Content for subsection A1. ### Subsection A2 Content for subsection A2. ## Section B Content for section B. ## Section C Content for section C. """ @pytest.fixture def editor(self, sample_markdown): """Create SafeMarkdownEditor instance.""" return SafeMarkdownEditor(sample_markdown, ValidationLevel.NORMAL) def test_constructor_all_validation_levels(self, sample_markdown): """Test constructor with all validation levels.""" # Test all validation levels for level in [ValidationLevel.STRICT, ValidationLevel.NORMAL, ValidationLevel.PERMISSIVE]: editor = SafeMarkdownEditor(sample_markdown, level) assert editor._validation_level == level def test_constructor_with_empty_content(self): """Test constructor with empty content.""" editor = SafeMarkdownEditor("", ValidationLevel.NORMAL) sections = editor.get_sections() assert len(sections) == 0 def test_get_sections_comprehensive(self, editor): """Test get_sections method thoroughly.""" sections = editor.get_sections() # Should have 6 sections total assert len(sections) == 6 # Check all sections have valid human-readable IDs for section in sections: assert section.id # Should not be empty assert isinstance(section.id, str) # Should be a string # Human-readable IDs should not contain spaces and should be reasonable length assert ' ' not in section.id assert len(section.id) > 0 assert len(section.id) <= 60 assert len(section.id) > 8 # Should have hash part def test_get_section_by_id(self, editor): """Test get_section_by_id method.""" sections = editor.get_sections() if sections: first_section = sections[0] # Should find existing section found_section = editor.get_section_by_id(first_section.id) assert found_section is not None assert found_section.id == first_section.id # Should return None for non-existent ID not_found = editor.get_section_by_id("nonexistent_id") assert not_found is None def test_get_sections_by_level(self, editor): """Test get_sections_by_level method.""" # Test level 1 level_1 = editor.get_sections_by_level(1) assert len(level_1) >= 1 # Test invalid level (should raise error) with pytest.raises(ValueError): editor.get_sections_by_level(7) with pytest.raises(ValueError): editor.get_sections_by_level(0) def test_get_child_sections(self, editor): """Test get_child_sections method.""" sections = editor.get_sections() if len(sections) > 0: main_doc = sections[0] children = editor.get_child_sections(main_doc) assert isinstance(children, list) def test_preview_operation_update_section(self, editor): """Test preview_operation for UPDATE_SECTION.""" sections = editor.get_sections() if len(sections) > 1: section_a = sections[1] # "Section A" # Test valid preview result = editor.preview_operation( EditOperation.UPDATE_SECTION, section_ref=section_a, content="New content for section A" ) assert result.success is True assert result.operation == EditOperation.UPDATE_SECTION def test_update_section_content(self, editor): """Test update_section_content method.""" sections = editor.get_sections() if len(sections) > 4: section_b = sections[4] # "Section B" original_version = editor._version # Update content result = editor.update_section_content( section_b, "Updated content for section B with **formatting**." ) # Should succeed or have clear error assert isinstance(result, EditResult) assert result.operation == EditOperation.UPDATE_SECTION if result.success: # Check version incremented assert editor._version == original_version + 1 def test_insert_section_after(self, editor): """Test insert_section_after method.""" sections = editor.get_sections() if len(sections) > 1: section_a = sections[1] original_count = len(sections) result = editor.insert_section_after( section_a, level=3, title="New Subsection", content="Content for the new subsection." ) assert isinstance(result, EditResult) assert result.operation == EditOperation.INSERT_SECTION def test_delete_section(self, editor): """Test delete_section method.""" sections = editor.get_sections() if len(sections) > 2: subsection = sections[2] # Should be a subsection result = editor.delete_section(subsection) assert isinstance(result, EditResult) assert result.operation == EditOperation.DELETE_SECTION def test_change_heading_level(self, editor): """Test change_heading_level method.""" sections = editor.get_sections() if len(sections) > 4: section_b = sections[4] # "Section B" result = editor.change_heading_level(section_b, 3) assert isinstance(result, EditResult) assert result.operation == EditOperation.CHANGE_HEADING_LEVEL def test_change_heading_level_invalid_level(self, editor): """Test change_heading_level with invalid level.""" sections = editor.get_sections() if sections: section = sections[0] # Test level too high result = editor.change_heading_level(section, 7) assert result.success is False # Test level too low result = editor.change_heading_level(section, 0) assert result.success is False def test_move_section(self, editor): """Test move_section method.""" sections = editor.get_sections() if len(sections) > 5: section_c = sections[5] section_a = sections[1] result = editor.move_section(section_c, section_a, "before") assert isinstance(result, EditResult) assert result.operation == EditOperation.MOVE_SECTION def test_get_transaction_history(self, editor): """Test get_transaction_history method.""" # Should return a list history = editor.get_transaction_history() assert isinstance(history, list) # Test with limit limited_history = editor.get_transaction_history(limit=1) assert isinstance(limited_history, list) assert len(limited_history) <= 1 def test_rollback_transaction_no_history(self): """Test rollback_transaction with no history.""" editor = SafeMarkdownEditor("# Test", ValidationLevel.NORMAL) result = editor.rollback_transaction() assert result.success is False assert len(result.errors) > 0 def test_validate_document(self, editor): """Test validate_document method.""" errors = editor.validate_document() assert isinstance(errors, list) def test_to_markdown(self, editor): """Test to_markdown export.""" markdown = editor.to_markdown() assert isinstance(markdown, str) assert len(markdown) > 0 def test_to_html(self, editor): """Test to_html export.""" html = editor.to_html() assert isinstance(html, str) assert len(html) > 0 def test_to_json(self, editor): """Test to_json export.""" json_str = editor.to_json() assert isinstance(json_str, str) assert len(json_str) > 0 def test_get_statistics(self, editor): """Test get_statistics method.""" stats = editor.get_statistics() assert stats.total_sections >= 0 assert stats.word_count >= 0 assert stats.character_count >= 0 assert stats.line_count >= 0 assert stats.max_heading_depth >= 0 assert stats.edit_count >= 0 assert isinstance(stats.section_distribution, dict) def test_thread_safety_concurrent_reads(self, editor): """Test thread safety with concurrent reads.""" results = [] errors = [] def read_sections(thread_id): try: sections = editor.get_sections() stats = editor.get_statistics() results.append({ 'thread_id': thread_id, 'section_count': len(sections), 'word_count': stats.word_count }) except Exception as e: errors.append(f"Thread {thread_id}: {e}") # Create multiple threads threads = [] for i in range(3): thread = threading.Thread(target=read_sections, args=(i,)) threads.append(thread) # Start all threads for thread in threads: thread.start() # Wait for completion for thread in threads: thread.join() # Verify results assert len(errors) == 0, f"Thread errors: {errors}" assert len(results) == 3 def test_error_handling_edge_cases(self, editor): """Test various error handling scenarios.""" # Test with empty strings result = editor.get_section_by_id("") assert result is None # Test update with empty content - should succeed sections = editor.get_sections() if sections: result = editor.update_section_content(sections[0], "") assert isinstance(result, EditResult) def test_document_state_consistency(self, editor): """Test that document state remains consistent across operations.""" # Get initial state initial_sections = editor.get_sections() initial_stats = editor.get_statistics() # Perform operation if len(initial_sections) > 1: section = initial_sections[1] result = editor.update_section_content(section, "Modified content") if result.success: # Try rollback if we have transaction history rollback_result = editor.rollback_transaction() if rollback_result.success: # State should be restored restored_sections = editor.get_sections() assert len(restored_sections) == len(initial_sections) class TestSafeMarkdownEditorTypesFixed: """Test SafeMarkdownEditor data types.""" def test_section_reference_immutability(self): """Test that SectionReference is properly immutable.""" section = SectionReference( id="test_id", title="Test Section", level=2, line_start=1, line_end=5, path=["parent"] ) # Should not be able to modify fields with pytest.raises((AttributeError, TypeError)): section.title = "Modified" def test_edit_result_creation(self): """Test EditResult creation and properties.""" result = EditResult( success=True, operation=EditOperation.UPDATE_SECTION, modified_sections=[], errors=[], warnings=[] ) assert result.success is True assert result.operation == EditOperation.UPDATE_SECTION assert isinstance(result.modified_sections, list) assert isinstance(result.errors, list) assert isinstance(result.warnings, list) def test_safe_parse_error_with_suggestions(self): """Test SafeParseError with suggestions.""" error = SafeParseError( message="Test error", error_code="TEST_ERROR", category=ErrorCategory.VALIDATION, suggestions=["Try this", "Or this"] ) assert error.message == "Test error" assert error.error_code == "TEST_ERROR" assert error.category == ErrorCategory.VALIDATION assert len(error.suggestions) == 2 class TestSafeMarkdownEditorEdgeCasesFixed: """Edge case tests for SafeMarkdownEditor.""" def test_empty_document_operations(self): """Test operations on empty document.""" editor = SafeMarkdownEditor("", ValidationLevel.NORMAL) # Should handle empty document gracefully sections = editor.get_sections() assert len(sections) == 0 stats = editor.get_statistics() assert stats.total_sections == 0 assert stats.word_count == 0 # Validation should work on empty document errors = editor.validate_document() assert isinstance(errors, list) def test_single_heading_document(self): """Test document with single heading.""" editor = SafeMarkdownEditor("# Single Heading", ValidationLevel.NORMAL) sections = editor.get_sections() assert len(sections) == 1 assert sections[0].title == "Single Heading" assert sections[0].level == 1 def test_document_with_only_content_no_headings(self): """Test document with content but no headings.""" editor = SafeMarkdownEditor("Just some content\nwith multiple lines.", ValidationLevel.NORMAL) sections = editor.get_sections() assert len(sections) == 0 stats = editor.get_statistics() assert stats.total_sections == 0 assert stats.word_count > 0 # Should still count words def test_very_deep_heading_hierarchy(self): """Test with maximum heading depth.""" deep_markdown = """# Level 1 ## Level 2 ### Level 3 #### Level 4 ##### Level 5 ###### Level 6""" editor = SafeMarkdownEditor(deep_markdown, ValidationLevel.NORMAL) sections = editor.get_sections() assert len(sections) == 6 levels = [s.level for s in sections] assert levels == [1, 2, 3, 4, 5, 6] stats = editor.get_statistics() assert stats.max_heading_depth == 6 def test_sections_with_special_characters(self): """Test sections with special characters in titles.""" special_chars = """# Section with "quotes" ## Section with *asterisks* ### Section with [brackets]""" editor = SafeMarkdownEditor(special_chars, ValidationLevel.NORMAL) sections = editor.get_sections() assert len(sections) == 3 # Should have sections with special characters titles = [s.title for s in sections] assert len(titles) == 3 def test_transaction_history_operations(self): """Test transaction history functionality.""" editor = SafeMarkdownEditor("# Test\n\nContent.", ValidationLevel.NORMAL) # Get initial history initial_history = editor.get_transaction_history() initial_count = len(initial_history) # Make a change sections = editor.get_sections() if sections: result = editor.update_section_content(sections[0], "Modified content") if result.success: # Should have more transactions new_history = editor.get_transaction_history() assert len(new_history) == initial_count + 1 def test_validation_permissive_vs_strict(self): """Test different validation levels.""" # Content with potential issues content = """# Heading 1 ## Heading 2 #### Heading 4""" # Skips heading 3 # Permissive should allow it editor_permissive = SafeMarkdownEditor(content, ValidationLevel.PERMISSIVE) assert editor_permissive is not None # Normal should allow it editor_normal = SafeMarkdownEditor(content, ValidationLevel.NORMAL) assert editor_normal is not None # Strict might reject it try: editor_strict = SafeMarkdownEditor(content, ValidationLevel.STRICT) assert editor_strict is not None except Exception: # Expected for strict validation pass def test_statistics_after_modifications(self): """Test that statistics update correctly after modifications.""" editor = SafeMarkdownEditor("# Test\n\nInitial content.", ValidationLevel.NORMAL) initial_stats = editor.get_statistics() assert initial_stats.edit_count == 0 # Make an edit sections = editor.get_sections() if sections: result = editor.update_section_content(sections[0], "Modified content with more words.") if result.success: updated_stats = editor.get_statistics() assert updated_stats.edit_count == 1 def test_export_methods_with_modified_content(self): """Test export methods after content modifications.""" editor = SafeMarkdownEditor("# Original\n\nOriginal content.", ValidationLevel.NORMAL) # Test all export methods work markdown = editor.to_markdown() assert isinstance(markdown, str) html = editor.to_html() assert isinstance(html, str) json_str = editor.to_json() assert isinstance(json_str, str) def test_helper_method_coverage(self): """Test various helper methods indirectly.""" markdown = """# Main Document Content here. ## Section A ### Subsection A1 Content for A1.""" editor = SafeMarkdownEditor(markdown, ValidationLevel.NORMAL) # Test _build_section_references through get_sections sections = editor.get_sections() assert len(sections) > 0 # All sections should have proper human-readable IDs for section in sections: assert section.id # Should not be empty assert isinstance(section.id, str) # Should be a string assert ' ' not in section.id # No spaces assert len(section.id) <= 60 # Reasonable length assert len(section.id) > 8 def test_section_boundary_detection(self): """Test section boundary detection in complex documents.""" complex_markdown = """# Main Content before section A. ## Section A Content for section A. ### Subsection A1 Content for A1.""" editor = SafeMarkdownEditor(complex_markdown, ValidationLevel.NORMAL) sections = editor.get_sections() # Verify line boundaries make sense for i, section in enumerate(sections): assert section.line_start >= 0 # Allow 0-based indexing assert section.line_end >= section.line_start def test_concurrent_access_safety(self): """Test concurrent access safety.""" editor = SafeMarkdownEditor("# Test\n\nContent.", ValidationLevel.NORMAL) results = [] errors = [] def concurrent_operations(thread_id): try: # Different threads do different operations if thread_id % 2 == 0: sections = editor.get_sections() results.append(('read', len(sections))) else: stats = editor.get_statistics() results.append(('stats', stats.word_count)) except Exception as e: errors.append(f"Thread {thread_id}: {e}") # Run concurrent operations threads = [] for i in range(4): thread = threading.Thread(target=concurrent_operations, args=(i,)) threads.append(thread) for thread in threads: thread.start() for thread in threads: thread.join() # Should not have any errors assert len(errors) == 0 assert len(results) == 4

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/quantalogic/quantalogic_markdown_mcp'

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