Markdown support for description fields added; Tests updated
All checks were successful
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/labhelper) (push) Successful in 1m44s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/labhelper-data-loader) (push) Successful in 6s

This commit is contained in:
2026-01-05 11:00:16 +01:00
parent 5c0b09f78e
commit ca50832b54
7 changed files with 439 additions and 34 deletions

View File

@@ -577,7 +577,8 @@ class SearchApiTests(AuthTestCase):
self.assertEqual(response.status_code, 200)
results = response.json()['results']
self.assertEqual(len(results), 1)
self.assertLessEqual(len(results[0]['description']), 100)
# Truncated text + '...' should be around 100 chars
self.assertLessEqual(len(results[0]['description']), 105)
def test_search_api_limits_results(self):
"""Search API should limit results to 50."""
@@ -1708,3 +1709,178 @@ class ThingTagTests(AuthTestCase):
results = response.json()['results']
self.assertEqual(len(results), 1)
self.assertEqual(results[0]['name'], 'Arduino Uno')
class MarkdownRenderingTests(TestCase):
"""Tests for Markdown rendering in templates."""
def test_render_markdown_basic(self):
"""render_markdown filter should convert basic Markdown to HTML."""
from boxes.templatetags.dict_extras import render_markdown
result = render_markdown('**bold** and *italic*')
self.assertIn('<strong>bold</strong>', result)
self.assertIn('<em>italic</em>', result)
def test_render_markdown_links(self):
"""render_markdown filter should convert links."""
from boxes.templatetags.dict_extras import render_markdown
result = render_markdown('[Example](https://example.com)')
self.assertIn('href="https://example.com"', result)
self.assertIn('target="_blank"', result)
self.assertIn('rel="noopener noreferrer"', result)
def test_render_markdown_code(self):
"""render_markdown filter should convert code blocks."""
from boxes.templatetags.dict_extras import render_markdown
result = render_markdown('`inline code`')
self.assertIn('<code>inline code</code>', result)
def test_render_markdown_lists(self):
"""render_markdown filter should convert lists."""
from boxes.templatetags.dict_extras import render_markdown
result = render_markdown('- item 1\n- item 2')
self.assertIn('<ul>', result)
self.assertIn('<li>item 1</li>', result)
def test_render_markdown_sanitizes_script(self):
"""render_markdown filter should sanitize script tags."""
from boxes.templatetags.dict_extras import render_markdown
result = render_markdown('<script>alert("xss")</script>')
self.assertNotIn('<script>', result)
self.assertNotIn('</script>', result)
def test_render_markdown_empty_string(self):
"""render_markdown filter should handle empty strings."""
from boxes.templatetags.dict_extras import render_markdown
result = render_markdown('')
self.assertEqual(result, '')
def test_render_markdown_none(self):
"""render_markdown filter should handle None."""
from boxes.templatetags.dict_extras import render_markdown
result = render_markdown(None)
self.assertEqual(result, '')
def test_render_markdown_tables(self):
"""render_markdown filter should convert tables."""
from boxes.templatetags.dict_extras import render_markdown
md = '| A | B |\n|---|---|\n| 1 | 2 |'
result = render_markdown(md)
self.assertIn('<table>', result)
self.assertIn('<th>', result)
self.assertIn('<td>', result)
def test_truncate_markdown_basic(self):
"""truncate_markdown filter should truncate and strip Markdown."""
from boxes.templatetags.dict_extras import truncate_markdown
result = truncate_markdown('**bold** text here', 10)
self.assertNotIn('**', result)
self.assertNotIn('<strong>', result)
def test_truncate_markdown_long_text(self):
"""truncate_markdown filter should add ellipsis for long text."""
from boxes.templatetags.dict_extras import truncate_markdown
long_text = 'This is a very long text that should be truncated'
result = truncate_markdown(long_text, 20)
self.assertTrue(result.endswith('...'))
self.assertLessEqual(len(result), 25)
def test_truncate_markdown_empty(self):
"""truncate_markdown filter should handle empty strings."""
from boxes.templatetags.dict_extras import truncate_markdown
result = truncate_markdown('')
self.assertEqual(result, '')
def test_truncate_markdown_short_text(self):
"""truncate_markdown filter should not truncate short text."""
from boxes.templatetags.dict_extras import truncate_markdown
result = truncate_markdown('Short', 100)
self.assertEqual(result, 'Short')
self.assertNotIn('...', result)
class MarkdownInViewsTests(AuthTestCase):
"""Tests for Markdown rendering in views."""
def setUp(self):
"""Set up test fixtures."""
super().setUp()
self.box_type = BoxType.objects.create(
name='Standard Box',
width=200,
height=100,
length=300
)
self.box = Box.objects.create(
id='BOX001',
box_type=self.box_type
)
def test_thing_detail_renders_markdown(self):
"""Thing detail page should render Markdown in description."""
thing = Thing.objects.create(
name='Test Item',
box=self.box,
description='**Bold text** and *italic*'
)
response = self.client.get(f'/thing/{thing.id}/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, '<strong>Bold text</strong>')
self.assertContains(response, '<em>italic</em>')
def test_thing_detail_renders_markdown_links(self):
"""Thing detail page should render Markdown links with target blank."""
thing = Thing.objects.create(
name='Test Item',
box=self.box,
description='Check [this link](https://example.com)'
)
response = self.client.get(f'/thing/{thing.id}/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'href="https://example.com"')
self.assertContains(response, 'target="_blank"')
def test_thing_detail_sanitizes_html(self):
"""Thing detail page should sanitize dangerous HTML in description."""
thing = Thing.objects.create(
name='Test Item',
box=self.box,
description='<script>alert("xss")</script>Normal text'
)
response = self.client.get(f'/thing/{thing.id}/')
self.assertEqual(response.status_code, 200)
# The description should not contain script tags (they are stripped)
# Note: The base template has a <script> tag for jQuery, so we check
# specifically that the malicious script content is not executed
self.assertContains(response, 'Normal text')
# Check the markdown-content div doesn't have script tags
self.assertNotContains(response, '<script>alert')
def test_box_detail_truncates_markdown(self):
"""Box detail page should show truncated plain text description."""
thing = Thing.objects.create(
name='Test Item',
box=self.box,
description='**Bold** and a very long description that should be truncated in the box detail view'
)
response = self.client.get(f'/box/{self.box.id}/')
self.assertEqual(response.status_code, 200)
# Should not contain raw Markdown syntax
self.assertNotContains(response, '**Bold**')
# Should contain the plain text (truncated)
self.assertContains(response, 'Bold')
def test_search_api_strips_markdown(self):
"""Search API should return plain text description."""
thing = Thing.objects.create(
name='Searchable Item',
box=self.box,
description='**Bold text** in description'
)
response = self.client.get('/search/api/?q=Searchable')
self.assertEqual(response.status_code, 200)
results = response.json()['results']
self.assertEqual(len(results), 1)
# Description should be plain text without Markdown
self.assertNotIn('**', results[0]['description'])
self.assertIn('Bold text', results[0]['description'])