Files
parentzone_downloader/tests/test_html_rendering.py

387 lines
15 KiB
Python
Raw Permalink Normal View History

2025-10-07 14:52:04 +01:00
#!/usr/bin/env python3
"""
Test HTML Rendering in Notes Field
This script tests that the notes field HTML content is properly rendered
in the output HTML file instead of being escaped.
"""
import asyncio
import json
import logging
import sys
import tempfile
from pathlib import Path
import os
# Add the current directory to the path so we can import modules
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from snapshot_downloader import SnapshotDownloader
class HTMLRenderingTester:
"""Test class for HTML rendering functionality."""
def __init__(self):
"""Initialize the tester."""
self.logger = logging.getLogger(__name__)
def test_notes_html_rendering(self):
"""Test that HTML in notes field is properly rendered."""
print("=" * 60)
print("TEST: HTML Rendering in Notes Field")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
downloader = SnapshotDownloader(output_dir=temp_dir)
print("1. Testing snapshot with HTML content in notes...")
# Create mock snapshot with HTML content in notes
mock_snapshot = {
"id": "test_html_rendering",
"type": "Snapshot",
"code": "Snapshot",
"child": {
"forename": "Test",
"surname": "Child"
},
"author": {
"forename": "Test",
"surname": "Teacher"
},
"startTime": "2024-01-15T10:30:00",
"notes": """<p>This is a <strong>bold</strong> statement about the child's progress.</p>
<p><br></p>
<p>The child demonstrated <em>excellent</em> skills in:</p>
<p> Communication</p>
<p> Problem solving</p>
<p><br></p>
<p><span style="color: rgb(255, 0, 0);">Important note:</span> Continue encouraging creative play.</p>
<p><span style="font-size: 14px;">Next steps: Focus on fine motor skills development.</span></p>""",
"frameworkIndicatorCount": 15,
"signed": False
}
# Generate HTML for the snapshot
html_content = downloader.format_snapshot_html(mock_snapshot)
print("2. Checking HTML content rendering...")
# Check that HTML tags are NOT escaped (should be rendered) within notes-content
if 'notes-content"><p>' in html_content or 'notes-content"><strong>' in html_content:
print(" ✅ HTML paragraph tags are rendered (not escaped)")
else:
print(" ❌ HTML paragraph tags are escaped instead of rendered")
# Debug output to see what we actually got
start = html_content.find('notes-content')
if start != -1:
sample = html_content[start:start+150]
print(f" Debug - Found: {sample}")
return False
if "<strong>bold</strong>" in html_content:
print(" ✅ HTML strong tags are rendered (not escaped)")
else:
print(" ❌ HTML strong tags are escaped instead of rendered")
return False
if "<em>excellent</em>" in html_content:
print(" ✅ HTML emphasis tags are rendered (not escaped)")
else:
print(" ❌ HTML emphasis tags are escaped instead of rendered")
return False
if 'style="color: rgb(255, 0, 0);"' in html_content:
print(" ✅ Inline CSS styles are preserved")
else:
print(" ❌ Inline CSS styles are not preserved")
return False
print("\n3. Testing complete HTML file generation...")
# Generate complete HTML file
mock_snapshots = [mock_snapshot]
html_file = downloader.generate_html_file(
mock_snapshots, "2024-01-01", "2024-01-31"
)
if html_file.exists():
print(" ✅ HTML file created successfully")
# Read and check file content
with open(html_file, 'r', encoding='utf-8') as f:
file_content = f.read()
# Check for proper HTML structure
if 'class="notes-content"' in file_content:
print(" ✅ Notes content wrapper class present")
else:
print(" ❌ Notes content wrapper class missing")
return False
# Check that HTML content is rendered in the file
if "<p>This is a <strong>bold</strong> statement" in file_content:
print(" ✅ HTML content properly rendered in file")
else:
print(" ❌ HTML content not properly rendered in file")
print(" Debug: Looking for HTML content in file...")
# Show a sample of the content for debugging
start = file_content.find('notes-content')
if start != -1:
sample = file_content[start:start+200]
print(f" Sample content: {sample}")
return False
# Check for CSS styles that handle HTML content
if ".notes-content" in file_content:
print(" ✅ CSS styles for notes content included")
else:
print(" ❌ CSS styles for notes content missing")
return False
else:
print(" ❌ HTML file was not created")
return False
print("\n4. Testing XSS safety with potentially dangerous content...")
# Test with potentially dangerous content to ensure basic safety
dangerous_snapshot = {
"id": "test_xss_safety",
"type": "Snapshot",
"startTime": "2024-01-15T10:30:00",
"notes": '<p>Safe content</p><script>alert("xss")</script><p>More safe content</p>',
}
dangerous_html = downloader.format_snapshot_html(dangerous_snapshot)
# The script tag should still be present (we're not sanitizing, just rendering)
# But we should document this as a security consideration
if '<script>' in dangerous_html:
print(" ⚠️ Script tags are rendered (consider content sanitization for production)")
else:
print(" ✅ Script tags are filtered/escaped")
print("\n✅ HTML rendering test completed!")
return True
def test_complex_html_scenarios(self):
"""Test various complex HTML scenarios."""
print("\n" + "=" * 60)
print("TEST: Complex HTML Scenarios")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
downloader = SnapshotDownloader(output_dir=temp_dir)
test_cases = [
{
"name": "Nested HTML Tags",
"notes": '<p>Child showed <strong>excellent <em>progress</em></strong> today.</p>',
"should_contain": ['<strong>', '<em>', 'excellent', 'progress']
},
{
"name": "Line Breaks and Paragraphs",
"notes": '<p>First paragraph.</p><p><br></p><p>Second paragraph after break.</p>',
"should_contain": ['<p>First paragraph.</p>', '<p><br></p>', '<p>Second paragraph']
},
{
"name": "Styled Text",
"notes": '<p><span style="color: rgb(0, 0, 255); font-size: 16px;">Blue text</span></p>',
"should_contain": ['style="color: rgb(0, 0, 255)', 'font-size: 16px', 'Blue text']
},
{
"name": "Mixed Content",
"notes": '<p>Normal text</p><p>• Bullet point 1</p><p>• Bullet point 2</p><p><strong>Next steps:</strong> Continue activities.</p>',
"should_contain": ['Normal text', '• Bullet', '<strong>Next steps:</strong>']
}
]
for i, test_case in enumerate(test_cases, 1):
print(f"\n{i}. Testing: {test_case['name']}")
mock_snapshot = {
"id": f"test_case_{i}",
"type": "Snapshot",
"startTime": "2024-01-15T10:30:00",
"notes": test_case['notes']
}
html_content = downloader.format_snapshot_html(mock_snapshot)
# Check that all expected content is present and rendered
all_found = True
for expected in test_case['should_contain']:
if expected in html_content:
print(f" ✅ Found: {expected[:30]}...")
else:
print(f" ❌ Missing: {expected[:30]}...")
all_found = False
if not all_found:
print(f" ❌ Test case '{test_case['name']}' failed")
return False
else:
print(f" ✅ Test case '{test_case['name']}' passed")
print("\n✅ Complex HTML scenarios test completed!")
return True
def test_empty_and_edge_cases(self):
"""Test edge cases for notes field."""
print("\n" + "=" * 60)
print("TEST: Edge Cases")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
downloader = SnapshotDownloader(output_dir=temp_dir)
edge_cases = [
{
"name": "Empty notes",
"notes": "",
"expected": "No description provided"
},
{
"name": "None notes",
"notes": None,
"expected": "No description provided"
},
{
"name": "Only whitespace",
"notes": " \n\t ",
"expected": " \n\t " # Should preserve whitespace
},
{
"name": "Plain text (no HTML)",
"notes": "Just plain text without HTML tags.",
"expected": "Just plain text without HTML tags."
}
]
for i, test_case in enumerate(edge_cases, 1):
print(f"\n{i}. Testing: {test_case['name']}")
mock_snapshot = {
"id": f"edge_case_{i}",
"type": "Snapshot",
"startTime": "2024-01-15T10:30:00"
}
if test_case['notes'] is not None:
mock_snapshot['notes'] = test_case['notes']
html_content = downloader.format_snapshot_html(mock_snapshot)
if test_case['expected'] in html_content:
print(f" ✅ Correctly handled: {test_case['name']}")
else:
print(f" ❌ Failed: {test_case['name']}")
print(f" Expected: {test_case['expected']}")
# Show relevant part of HTML for debugging
start = html_content.find('notes-content')
if start != -1:
sample = html_content[start:start+100]
print(f" Found: {sample}")
return False
print("\n✅ Edge cases test completed!")
return True
def run_all_tests(self):
"""Run all HTML rendering tests."""
print("🚀 Starting HTML Rendering Tests")
print("=" * 80)
try:
success = True
success &= self.test_notes_html_rendering()
success &= self.test_complex_html_scenarios()
success &= self.test_empty_and_edge_cases()
if success:
print("\n" + "=" * 80)
print("🎉 ALL HTML RENDERING TESTS PASSED!")
print("=" * 80)
print("✅ HTML content in notes field is properly rendered")
print("✅ Complex HTML scenarios work correctly")
print("✅ Edge cases are handled appropriately")
print("✅ CSS styles support HTML content rendering")
print("\n⚠️ Security Note:")
print(" HTML content is rendered as-is for rich formatting.")
print(" Consider content sanitization if accepting user input.")
else:
print("\n❌ SOME HTML RENDERING TESTS FAILED")
return success
except Exception as e:
print(f"\n❌ HTML RENDERING TESTS FAILED: {e}")
import traceback
traceback.print_exc()
return False
def show_html_rendering_info():
"""Show information about HTML rendering in notes."""
print("\n" + "=" * 80)
print("📝 HTML RENDERING IN NOTES FIELD")
print("=" * 80)
print("\n🎨 What's Rendered:")
print("• <p> tags for paragraphs")
print("• <strong> and <em> for bold/italic text")
print("• <br> tags for line breaks")
print("• <span> with style attributes for colors/fonts")
print("• Bullet points and lists")
print("• All inline CSS styles")
print("\n💡 Example HTML Content:")
print('<p>Child showed <strong>excellent</strong> progress today.</p>')
print('<p><br></p>')
print('<p><span style="color: rgb(255, 0, 0);">Important:</span> Continue activities.</p>')
print("\n📋 Becomes:")
print("Child showed excellent progress today.")
print("")
print("Important: Continue activities. (in red)")
print("\n🔒 Security Considerations:")
print("• HTML content is rendered as-is from the API")
print("• Content comes from trusted ParentZone staff")
print("• Script tags and other content are preserved")
print("• Consider sanitization for untrusted input")
print("\n🎯 Benefits:")
print("• Rich text formatting preserved")
print("• Professional-looking reports")
print("• Colors and styling from original content")
print("• Better readability and presentation")
def main():
"""Main test function."""
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
tester = HTMLRenderingTester()
# Run tests
success = tester.run_all_tests()
# Show information
if success:
show_html_rendering_info()
return 0 if success else 1
if __name__ == "__main__":
exit(main())