Files
vgui-cicd/abschnitte/tests.py
Adrian A. Baumann da66f2ddc6 Add comprehensive unit tests for rendering functions
- Test text rendering with markdown
- Test unordered and ordered lists
- Test table rendering
- Test diagram rendering (success and error cases)
- Test diagram with custom options
- Test code blocks
- Test edge cases (empty content, no type)
- Test multiple sections
- Test md_table_to_html function
- Integration tests for mixed content
2025-10-24 17:20:46 +00:00

378 lines
12 KiB
Python

from unittest.mock import Mock, patch, MagicMock
from django.test import TestCase, override_settings
from abschnitte.models import AbschnittTyp, Textabschnitt
from abschnitte.utils import render_textabschnitte, md_table_to_html
class MockTextabschnitt:
"""Mock object for Textabschnitt (since it's abstract)."""
def __init__(self, abschnitttyp, inhalt):
self.abschnitttyp = abschnitttyp
self.inhalt = inhalt
class RenderTextabschnitteTestCase(TestCase):
"""Test cases for render_textabschnitte function."""
def setUp(self):
"""Set up test fixtures."""
# Create mock AbschnittTyp objects
self.typ_text = Mock()
self.typ_text.abschnitttyp = "text"
self.typ_liste_ungeordnet = Mock()
self.typ_liste_ungeordnet.abschnitttyp = "liste ungeordnet"
self.typ_liste_geordnet = Mock()
self.typ_liste_geordnet.abschnitttyp = "liste geordnet"
self.typ_tabelle = Mock()
self.typ_tabelle.abschnitttyp = "tabelle"
self.typ_diagramm = Mock()
self.typ_diagramm.abschnitttyp = "diagramm"
self.typ_code = Mock()
self.typ_code.abschnitttyp = "code"
def test_render_basic_text(self):
"""Test rendering basic text content."""
abschnitt = MockTextabschnitt(
abschnitttyp=self.typ_text,
inhalt="This is **bold** text with *italic*."
)
result = render_textabschnitte([abschnitt])
self.assertEqual(len(result), 1)
typ, html = result[0]
self.assertEqual(typ, "text")
self.assertIn("<strong>bold</strong>", html)
self.assertIn("<em>italic</em>", html)
def test_render_unordered_list(self):
"""Test rendering unordered list."""
abschnitt = MockTextabschnitt(
abschnitttyp=self.typ_liste_ungeordnet,
inhalt="Item 1\nItem 2\nItem 3"
)
result = render_textabschnitte([abschnitt])
self.assertEqual(len(result), 1)
typ, html = result[0]
self.assertEqual(typ, "liste ungeordnet")
self.assertIn("<ul>", html)
self.assertIn("<li>Item 1</li>", html)
self.assertIn("<li>Item 2</li>", html)
self.assertIn("<li>Item 3</li>", html)
def test_render_ordered_list(self):
"""Test rendering ordered list."""
abschnitt = MockTextabschnitt(
abschnitttyp=self.typ_liste_geordnet,
inhalt="First item\nSecond item\nThird item"
)
result = render_textabschnitte([abschnitt])
self.assertEqual(len(result), 1)
typ, html = result[0]
self.assertEqual(typ, "liste geordnet")
self.assertIn("<ol>", html)
self.assertIn("<li>First item</li>", html)
def test_render_table(self):
"""Test rendering markdown table."""
table_content = """| Header 1 | Header 2 |
|----------|----------|
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |"""
abschnitt = MockTextabschnitt(
abschnitttyp=self.typ_tabelle,
inhalt=table_content
)
result = render_textabschnitte([abschnitt])
self.assertEqual(len(result), 1)
typ, html = result[0]
self.assertEqual(typ, "tabelle")
self.assertIn("<table", html)
self.assertIn("Header 1", html)
self.assertIn("Cell 1", html)
self.assertIn("table-bordered", html)
@override_settings(MEDIA_URL='/media/')
@patch('abschnitte.utils.get_cached_diagram')
def test_render_diagram_success(self, mock_get_cached):
"""Test rendering diagram with successful cache."""
mock_get_cached.return_value = 'diagram_cache/plantuml/abc123.svg'
diagram_content = """plantuml
@startuml
A -> B
@enduml"""
abschnitt = MockTextabschnitt(
abschnitttyp=self.typ_diagramm,
inhalt=diagram_content
)
result = render_textabschnitte([abschnitt])
self.assertEqual(len(result), 1)
typ, html = result[0]
self.assertEqual(typ, "diagramm")
# Verify diagram cache was called correctly
mock_get_cached.assert_called_once_with(
'plantuml',
'@startuml\nA -> B\n@enduml'
)
# Verify HTML contains correct image tag
self.assertIn('<img', html)
self.assertIn('/media/diagram_cache/plantuml/abc123.svg', html)
self.assertIn('width="100%"', html)
@patch('abschnitte.utils.get_cached_diagram')
def test_render_diagram_with_options(self, mock_get_cached):
"""Test rendering diagram with custom options."""
mock_get_cached.return_value = 'diagram_cache/mermaid/def456.svg'
diagram_content = """mermaid
option:width="50%" height="300px"
graph TD
A --> B"""
abschnitt = MockTextabschnitt(
abschnitttyp=self.typ_diagramm,
inhalt=diagram_content
)
result = render_textabschnitte([abschnitt])
typ, html = result[0]
# Verify custom options are used
self.assertIn('width="50%"', html)
self.assertIn('height="300px"', html)
# Verify diagram content doesn't include option line
call_args = mock_get_cached.call_args[0]
self.assertNotIn('option:', call_args[1])
@patch('abschnitte.utils.get_cached_diagram')
def test_render_diagram_error(self, mock_get_cached):
"""Test rendering diagram when caching fails."""
mock_get_cached.side_effect = Exception("Kroki server unavailable")
diagram_content = """plantuml
@startuml
A -> B
@enduml"""
abschnitt = MockTextabschnitt(
abschnitttyp=self.typ_diagramm,
inhalt=diagram_content
)
result = render_textabschnitte([abschnitt])
typ, html = result[0]
# Should show error message
self.assertIn('Error generating diagram', html)
self.assertIn('Kroki server unavailable', html)
self.assertIn('text-danger', html)
def test_render_code(self):
"""Test rendering code block."""
code_content = """def hello():
print("Hello, World!")"""
abschnitt = MockTextabschnitt(
abschnitttyp=self.typ_code,
inhalt=code_content
)
result = render_textabschnitte([abschnitt])
typ, html = result[0]
self.assertEqual(typ, "code")
self.assertIn("<pre>", html)
self.assertIn("<code>", html)
self.assertIn("def hello():", html)
def test_render_empty_content(self):
"""Test rendering with empty or None content."""
abschnitt = MockTextabschnitt(
abschnitttyp=self.typ_text,
inhalt=None
)
result = render_textabschnitte([abschnitt])
self.assertEqual(len(result), 1)
typ, html = result[0]
self.assertEqual(typ, "text")
# Should not crash, should return empty or minimal HTML
def test_render_multiple_abschnitte(self):
"""Test rendering multiple sections."""
abschnitte = [
MockTextabschnitt(self.typ_text, "First paragraph"),
MockTextabschnitt(self.typ_liste_ungeordnet, "Item 1\nItem 2"),
MockTextabschnitt(self.typ_text, "Second paragraph"),
]
result = render_textabschnitte(abschnitte)
self.assertEqual(len(result), 3)
self.assertEqual(result[0][0], "text")
self.assertEqual(result[1][0], "liste ungeordnet")
self.assertEqual(result[2][0], "text")
def test_render_no_abschnitttyp(self):
"""Test rendering when abschnitttyp is None."""
abschnitt = MockTextabschnitt(
abschnitttyp=None,
inhalt="Some content"
)
result = render_textabschnitte([abschnitt])
self.assertEqual(len(result), 1)
typ, html = result[0]
self.assertEqual(typ, "")
# Should still render as markdown
self.assertIn("Some content", html)
class MdTableToHtmlTestCase(TestCase):
"""Test cases for md_table_to_html function."""
def test_basic_table(self):
"""Test converting basic markdown table to HTML."""
md = """| Name | Age |
|------|-----|
| Alice | 30 |
| Bob | 25 |"""
html = md_table_to_html(md)
self.assertIn('<table', html)
self.assertIn('table-bordered', html)
self.assertIn('table-hover', html)
self.assertIn('<thead>', html)
self.assertIn('<tbody>', html)
self.assertIn('<th>Name</th>', html)
self.assertIn('<th>Age</th>', html)
self.assertIn('<td>Alice</td>', html)
self.assertIn('<td>30</td>', html)
def test_table_with_spaces(self):
"""Test table with various spacing."""
md = """| Name | Age |
|--------|-------|
| Alice | 30 |
| Bob | 25 |"""
html = md_table_to_html(md)
# Spaces should be trimmed
self.assertIn('<th>Name</th>', html)
self.assertIn('<th>Age</th>', html)
self.assertIn('<td>Alice</td>', html)
def test_table_no_outer_pipes(self):
"""Test table without outer pipe characters."""
md = """Name | Age
-----|-----
Alice | 30
Bob | 25"""
html = md_table_to_html(md)
self.assertIn('<th>Name</th>', html)
self.assertIn('<td>Alice</td>', html)
def test_table_insufficient_rows(self):
"""Test error handling for malformed table."""
md = """| Name |
|------|"""
with self.assertRaises(ValueError) as context:
md_table_to_html(md)
self.assertIn("at least header + separator", str(context.exception))
def test_table_with_empty_cells(self):
"""Test table with empty cells."""
md = """| Name | Age |
|------|-----|
| Alice | |
| | 25 |"""
html = md_table_to_html(md)
self.assertIn('<td>Alice</td>', html)
self.assertIn('<td></td>', html)
self.assertIn('<td>25</td>', html)
def test_table_three_columns(self):
"""Test table with more columns."""
md = """| First | Middle | Last |
|-------|--------|------|
| John | Q | Doe |"""
html = md_table_to_html(md)
self.assertIn('<th>First</th>', html)
self.assertIn('<th>Middle</th>', html)
self.assertIn('<th>Last</th>', html)
self.assertIn('<td>John</td>', html)
self.assertIn('<td>Q</td>', html)
self.assertIn('<td>Doe</td>', html)
class RenderIntegrationTestCase(TestCase):
"""Integration tests for rendering pipeline."""
@patch('abschnitte.utils.get_cached_diagram')
def test_mixed_content_rendering(self, mock_get_cached):
"""Test rendering a mix of different content types."""
mock_get_cached.return_value = 'diagram_cache/test.svg'
# Create various types of content
typ_text = Mock(abschnitttyp="text")
typ_liste = Mock(abschnitttyp="liste ungeordnet")
typ_diagram = Mock(abschnitttyp="diagramm")
abschnitte = [
MockTextabschnitt(typ_text, "Introduction paragraph"),
MockTextabschnitt(typ_liste, "Point 1\nPoint 2\nPoint 3"),
MockTextabschnitt(typ_diagram, "plantuml\n@startuml\nA -> B\n@enduml"),
MockTextabschnitt(typ_text, "Conclusion"),
]
result = render_textabschnitte(abschnitte)
# Should have 4 results
self.assertEqual(len(result), 4)
# Verify each type was rendered correctly
self.assertEqual(result[0][0], "text")
self.assertIn("Introduction", result[0][1])
self.assertEqual(result[1][0], "liste ungeordnet")
self.assertIn("<ul>", result[1][1])
self.assertEqual(result[2][0], "diagramm")
self.assertIn("<img", result[2][1])
self.assertEqual(result[3][0], "text")
self.assertIn("Conclusion", result[3][1])