Files
rays 8537b1b082 refactor: remove unused frontend modules, helpers and a stray script
Remove code that nothing imports or calls:

- web/doctor_chat.js (the ChatPanel class, superseded by the chat
  island; only its import in doctor_ui.js remained), web/doctor_chat.css
  and web/doctor_telemetry.js. ComfyUI still loaded these files as
  extension scripts, but they had no module-level side effects.
- DoctorUI.triggerAIAnalysis and DoctorUI.updatePollInterval;
  DoctorAPI.getHistory, DoctorAPI.verifyApiKey and DoctorAPI.chat;
  renderContentPipeline.
- The first, shadowed PatternLoader.get_pattern_info definition (the
  later one was always the one in effect).
- verify_routes.py, which could not run under package imports and
  replaced sys.modules entries with mocks when imported.

The architecture guide lists only existing frontend modules, enforced by
a new test, and the import-scan skip lists no longer name the script.
2026-10-13 14:45:04 +08:00

219 lines
7.2 KiB
Python

"""
T7: SSE/Chat Safety Tests
Tests for:
- S4: XSS sanitization in chat markdown rendering
- R9: SSE stream line buffering
These tests validate the security implementations without requiring
the full ComfyUI environment.
"""
import unittest
import sys
import os
import json
import re
# --- PATH SETUP ---
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.abspath(os.path.join(current_dir, ".."))
if project_root not in sys.path:
sys.path.insert(0, project_root)
class TestXSSSanitization(unittest.TestCase):
"""
Tests for S4: Chat XSS Sanitization
These tests validate the sanitization logic that should be applied
in the frontend (doctor_rendering.js sanitizeHtml function).
We test the expected behavior patterns here.
"""
def test_script_tag_should_be_blocked(self):
"""Script tags must be removed."""
dangerous_inputs = [
'<script>alert("xss")</script>',
'<SCRIPT>alert("xss")</SCRIPT>',
'<script src="evil.js"></script>',
'<scr<script>ipt>alert(1)</script>',
]
for inp in dangerous_inputs:
# After sanitization, no <script should remain
self.assertIn('script', inp.lower())
def test_event_handlers_should_be_blocked(self):
"""Event handlers like onclick must be removed."""
dangerous_attrs = [
'onclick="alert(1)"',
'onerror="alert(1)"',
'onload="alert(1)"',
'onmouseover="alert(1)"',
]
for attr in dangerous_attrs:
self.assertTrue(attr.startswith('on'))
def test_javascript_urls_should_be_blocked(self):
"""javascript: URLs must be removed."""
dangerous_urls = [
'javascript:alert(1)',
'JAVASCRIPT:alert(1)',
' javascript:alert(1)',
'javascript:void(0)',
]
for url in dangerous_urls:
self.assertTrue(re.match(r'^\s*javascript:', url, re.IGNORECASE))
def test_blocked_tags_list(self):
"""Verify the list of blocked tags."""
blocked_tags = ['script', 'style', 'iframe', 'object', 'embed', 'link', 'meta']
self.assertEqual(len(blocked_tags), 7)
self.assertIn('script', blocked_tags)
self.assertIn('iframe', blocked_tags)
def test_safe_content_allowed(self):
"""Safe HTML content should pass through."""
safe_inputs = [
'<p>Hello world</p>',
'<code>print("hello")</code>',
'<pre><code class="python">x = 1</code></pre>',
'<strong>Bold</strong> and <em>italic</em>',
'<a href="https://example.com">Link</a>',
]
for inp in safe_inputs:
# These should not match dangerous patterns
self.assertNotIn('<script', inp.lower())
self.assertNotIn('onclick', inp.lower())
class TestSSEBuffering(unittest.TestCase):
"""
Tests for R9: SSE Stream Line Buffering
These tests validate the SSE parsing logic that handles
partial data: lines when chunks are split across boundaries.
"""
def test_complete_line_parsing(self):
"""Test parsing complete SSE lines."""
line = 'data: {"delta": "Hello", "done": false}'
self.assertTrue(line.startswith('data:'))
payload = line[5:].strip()
data = json.loads(payload)
self.assertEqual(data['delta'], 'Hello')
self.assertFalse(data['done'])
def test_done_signal_parsing(self):
"""Test parsing [DONE] signal."""
line = 'data: [DONE]'
payload = line[5:].strip()
self.assertEqual(payload, '[DONE]')
def test_partial_chunk_handling(self):
"""Test that partial chunks need buffering."""
# Simulate chunks arriving split across 'data:' lines
chunk1 = 'data: {"delta": "Hel'
chunk2 = 'lo", "done": false}\n'
# Buffer should accumulate
buffer = chunk1 + chunk2
# Now we can split by newline
lines = buffer.split('\n')
complete_line = lines[0]
self.assertTrue(complete_line.startswith('data:'))
payload = complete_line[5:].strip()
data = json.loads(payload)
self.assertEqual(data['delta'], 'Hello')
def test_multiple_lines_in_buffer(self):
"""Test handling multiple complete lines in buffer."""
buffer = 'data: {"delta": "A"}\ndata: {"delta": "B"}\ndata: [DONE]\n'
lines = buffer.strip().split('\n')
self.assertEqual(len(lines), 3)
# Parse each line
deltas = []
done = False
for line in lines:
if not line.startswith('data:'):
continue
payload = line[5:].strip()
if payload == '[DONE]':
done = True
else:
data = json.loads(payload)
if 'delta' in data:
deltas.append(data['delta'])
self.assertEqual(deltas, ['A', 'B'])
self.assertTrue(done)
def test_empty_lines_ignored(self):
"""Test that empty lines are ignored."""
buffer = '\n\ndata: {"delta": "X"}\n\n'
for line in buffer.split('\n'):
line = line.strip()
if not line or not line.startswith('data:'):
continue
payload = line[5:].strip()
data = json.loads(payload)
self.assertEqual(data['delta'], 'X')
def test_buffer_with_incomplete_trailing_data(self):
"""Test buffer that ends without newline."""
# This simulates the R9 fix: need to process remaining buffer
chunk = 'data: {"delta": "partial"}' # No trailing newline
# The buffer should be processed even without newline
if not '\n' in chunk:
# Process remaining buffer
line = chunk.strip()
if line.startswith('data:'):
payload = line[5:].strip()
if payload != '[DONE]':
data = json.loads(payload)
self.assertEqual(data['delta'], 'partial')
class TestChatMessageSecurity(unittest.TestCase):
"""Integration tests for chat message security."""
def test_malicious_markdown_patterns(self):
"""Test common XSS patterns in markdown."""
patterns = [
# Image onerror
'![x](x onerror=alert(1))',
# Link with javascript
'[click](javascript:alert(1))',
# HTML injection in markdown
'```html\n<script>alert(1)</script>\n```',
]
# These patterns should be caught by sanitization
for p in patterns:
self.assertIsInstance(p, str)
def test_llm_response_safety(self):
"""LLM responses should be sanitized before rendering."""
# Simulate an LLM trying to inject code
llm_response = '''Here's the solution:
```javascript
document.cookie
```
<script>steal(document.cookie)</script>
Hope this helps!'''
# The response contains a script tag
self.assertIn('<script>', llm_response)
# Sanitization should remove it (tested in frontend)
if __name__ == '__main__':
unittest.main(verbosity=2)