fix: import unpublished exam rooms and add opt-in school HTML feedback
This commit is contained in:
@@ -4,6 +4,7 @@ import threading
|
||||
import tempfile
|
||||
import time
|
||||
import smtplib
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
import feedback_service
|
||||
|
||||
@@ -15,6 +16,66 @@ from feedback_service import Settings, SendFailure, UncertainFailure, SmtpSender
|
||||
|
||||
ID = "cbaaaace-60b2-4871-8a95-ae316444a2fb"
|
||||
PNG = b"\x89PNG\r\n\x1a\n" + b"safe fixture"
|
||||
HTML = (b'<!doctype html><!-- TPSchedule school diagnostic --><html><head><meta charset="utf-8">'
|
||||
b'<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; base-uri \'none\'; form-action \'none\'">'
|
||||
b'</head><body><table></table></body></html>')
|
||||
|
||||
|
||||
def test_real_client_generated_synthetic_html_is_accepted_as_attachment(service):
|
||||
client, sender, _ = service
|
||||
page = (Path(__file__).parent / 'fixtures/client-school-diagnostic.html').read_bytes()
|
||||
fields = [('submission_id', (None, ID)), ('type', (None, 'problem')), ('message', (None, 'Cannot import'))]
|
||||
response = client.post('/v1/feedback', files=fields + [('diagnostic_html', ('page.html', page, 'text/html'))])
|
||||
assert response.status_code == 200
|
||||
attachment = list(sender.messages[0].iter_attachments())[0]
|
||||
assert attachment.get_payload(decode=True) == page
|
||||
|
||||
|
||||
@pytest.mark.parametrize('extra', [
|
||||
'<script>alert(1)</script>', '<img src="https://evil.test/">',
|
||||
'<table onclick="alert(1)"></table>', '<meta http-equiv="refresh" content="0;url=https://evil.test/">',
|
||||
'<iframe srcdoc="x"></iframe>',
|
||||
])
|
||||
def test_html_diagnostic_rejects_active_or_network_content_even_with_marker(service, extra):
|
||||
client, sender, _ = service
|
||||
data = HTML.replace(b'</body>', extra.encode() + b'</body>')
|
||||
fields = [('submission_id', (None, ID)), ('type', (None, 'problem')), ('message', (None, 'Cannot import'))]
|
||||
response = client.post('/v1/feedback', files=fields + [('diagnostic_html', ('page.html', data, 'text/html'))])
|
||||
assert response.status_code == 415
|
||||
assert not sender.messages
|
||||
|
||||
|
||||
def test_html_diagnostic_is_fifth_attachment_and_retry_uses_identical_snapshot(service):
|
||||
client, sender, _ = service
|
||||
files = [('submission_id', (None, ID)), ('type', (None, 'problem')), ('message', (None, 'Cannot import'))]
|
||||
files += [('attachments', (f'{i}.png', PNG, 'image/png')) for i in range(3)]
|
||||
files += [('diagnostics', ('log.txt', b'TPSchedule diagnostic log\n', 'text/plain')),
|
||||
('diagnostic_html', ('page.html', HTML, 'text/html; charset=utf-8'))]
|
||||
assert client.post('/v1/feedback', files=files).status_code == 200
|
||||
attachments = list(sender.messages[0].iter_attachments())
|
||||
assert len(attachments) == 5
|
||||
assert attachments[-1].get_filename() == 'TPSchedule-school-page.html'
|
||||
assert attachments[-1].get_payload(decode=True) == HTML
|
||||
assert client.post('/v1/feedback', files=files).status_code == 200
|
||||
assert len(sender.messages) == 1
|
||||
files[-1] = ('diagnostic_html', ('page.html', HTML.replace(b'<table>', b'<table><tr><td>changed</td></tr>'), 'text/html'))
|
||||
assert client.post('/v1/feedback', files=files).status_code == 409
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data,mime,kind,status', [
|
||||
(b'not HTML', 'text/html', 'problem', 415),
|
||||
(HTML, 'image/png', 'problem', 415),
|
||||
(HTML + b'\xff', 'text/html', 'problem', 415),
|
||||
(HTML + b'x' * (2 * 1024 * 1024), 'text/html', 'problem', 413),
|
||||
(HTML, 'text/html', 'development', 422),
|
||||
], ids=['header', 'mime', 'utf8', 'oversize', 'development'])
|
||||
def test_invalid_html_diagnostics_never_send(service, data, mime, kind, status):
|
||||
client, sender, _ = service
|
||||
fields = [('submission_id', (None, ID)), ('type', (None, kind))]
|
||||
fields += [('message', (None, 'Cannot import'))] if kind == 'problem' else [('school', (None, 'School')), ('contact', (None, 'Contact'))]
|
||||
response = client.post('/v1/feedback', files=fields + [('diagnostic_html', ('page.html', data, mime))])
|
||||
assert response.status_code == status
|
||||
assert not sender.messages
|
||||
|
||||
|
||||
class RecordingSender:
|
||||
@@ -251,8 +312,8 @@ def test_aggregate_attachments_are_limited(service):
|
||||
def test_chunked_body_without_content_length_is_limited(service):
|
||||
client, sender, _ = service
|
||||
events, output = iter([
|
||||
{"type": "http.request", "body": b"x" * (11 * 1024 * 1024), "more_body": True},
|
||||
{"type": "http.request", "body": b"x" * (11 * 1024 * 1024), "more_body": False},
|
||||
{"type": "http.request", "body": b"x" * (12 * 1024 * 1024), "more_body": True},
|
||||
{"type": "http.request", "body": b"x" * (12 * 1024 * 1024), "more_body": False},
|
||||
]), []
|
||||
|
||||
async def receive():
|
||||
|
||||
Reference in New Issue
Block a user