"""Security tests for session export generators. Validates that all user-supplied content is properly escaped in HTML exports to prevent XSS attacks, and that markdown/text exports handle edge cases. """ from datetime import datetime, timezone from unittest.mock import MagicMock import pytest from app.schemas.session import SessionExport from app.services.export_service import ( generate_html_export, generate_markdown_export, generate_text_export, ) def _make_session( tree_name="Test Tree", ticket_number=None, client_name=None, decisions=None, scratchpad="", ): """Create a mock session object for export testing.""" session = MagicMock() session.tree_snapshot = {"name": tree_name} session.ticket_number = ticket_number session.client_name = client_name session.decisions = decisions or [] session.scratchpad = scratchpad session.started_at = datetime(2026, 1, 15, 10, 30, tzinfo=timezone.utc) session.completed_at = datetime(2026, 1, 15, 11, 0, tzinfo=timezone.utc) return session def _default_options(**kwargs): """Create SessionExport options with defaults.""" return SessionExport( format=kwargs.get("format", "html"), include_timestamps=kwargs.get("include_timestamps", True), include_tree_info=kwargs.get("include_tree_info", True), ) # --- XSS Prevention Tests (HTML Export) --- class TestHtmlExportXssSecurity: """Verify all user-supplied fields are HTML-escaped in HTML export.""" def test_tree_name_script_tag_escaped(self): session = _make_session(tree_name='') result = generate_html_export(session, _default_options()) assert "' ) result = generate_html_export(session, _default_options()) assert "', "answer": "Yes"} ]) result = generate_html_export(session, _default_options()) assert "'} ]) result = generate_html_export(session, _default_options()) assert "'} ]) result = generate_html_export(session, _default_options()) assert "' ) result = generate_html_export(session, _default_options()) assert "', "answer": "Done"} ]) result = generate_html_export(session, _default_options()) assert "', ticket_number='', client_name='', scratchpad='', decisions=[ { "question": '', "answer": '', "notes": '', "timestamp": '', } ], ) result = generate_html_export(session, _default_options()) # Count: there should be zero raw