"""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