Add Hypothesis tests for HTTP responses

Similarly to what I did for HTTP requests, this commit adds a couple
test that try to check different parts of the ASGI spec. Because going
from message to HTTP response is more straightforward than going from
HTTP request to channel message, there's not a whole lot going on here.
This commit is contained in:
Maik Hoepfel 2017-03-22 16:59:23 +08:00
parent f35ad20748
commit e7490dcda6

View File

@ -7,9 +7,84 @@ from __future__ import unicode_literals
from unittest import TestCase from unittest import TestCase
from asgiref.inmemory import ChannelLayer from asgiref.inmemory import ChannelLayer
from hypothesis import given
from twisted.test import proto_helpers from twisted.test import proto_helpers
from ..http_protocol import HTTPFactory from daphne.http_protocol import HTTPFactory
from . import factories, http_strategies, testcases
class TestHTTPResponseSpec(testcases.ASGITestCase):
def test_minimal_response(self):
"""
Smallest viable example. Mostly verifies that our response building works.
"""
message = {'status': 200}
response = factories.response_for_message(message)
self.assert_valid_http_response_message(message, response)
self.assertIn(b'200 OK', response)
# Assert that the response is the last of the chunks.
# N.b. at the time of writing, Daphne did not support multiple response chunks,
# but still sends with Transfer-Encoding: chunked if no Content-Length header
# is specified (and maybe even if specified).
self.assertTrue(response.endswith(b'0\r\n\r\n'))
def test_status_code_required(self):
"""
Asserts that passing in the 'status' key is required.
Previous versions of Daphne did not enforce this, so this test is here
to make sure it stays required.
"""
with self.assertRaises(ValueError):
factories.response_for_message({})
def test_status_code_is_transmitted(self):
"""
Tests that a custom status code is present in the response.
We can't really use hypothesis to test all sorts of status codes, because a lot
of them have meaning that is respected by Twisted. E.g. setting 204 (No Content)
as a status code results in Twisted discarding the body.
"""
message = {'status': 201} # 'Created'
response = factories.response_for_message(message)
self.assert_valid_http_response_message(message, response)
self.assertIn(b'201 Created', response)
@given(body=http_strategies.http_body())
def test_body_is_transmitted(self, body):
message = {'status': 200, 'content': body.encode('ascii')}
response = factories.response_for_message(message)
self.assert_valid_http_response_message(message, response)
@given(headers=http_strategies.headers())
def test_headers(self, headers):
# The ASGI spec requires us to lowercase our header names
message = {'status': 200, 'headers': [(name.lower(), value) for name, value in headers]}
response = factories.response_for_message(message)
# The assert_ method does the heavy lifting of checking that headers are
# as expected.
self.assert_valid_http_response_message(message, response)
@given(
headers=http_strategies.headers(),
body=http_strategies.http_body()
)
def test_kitchen_sink(self, headers, body):
"""
This tests tries to let Hypothesis find combinations of variables that result
in breaking our assumptions. But responses are less exciting than responses,
so there's not a lot going on here.
"""
message = {
'status': 202, # 'Accepted'
'headers': [(name.lower(), value) for name, value in headers],
'content': body.encode('ascii')
}
response = factories.response_for_message(message)
self.assert_valid_http_response_message(message, response)
class TestHTTPResponse(TestCase): class TestHTTPResponse(TestCase):
@ -24,39 +99,6 @@ class TestHTTPResponse(TestCase):
self.tr = proto_helpers.StringTransport() self.tr = proto_helpers.StringTransport()
self.proto.makeConnection(self.tr) self.proto.makeConnection(self.tr)
def test_basic(self):
"""
Tests basic HTTP parsing
"""
# Send a simple request to the protocol
self.proto.dataReceived(
b"GET /te%20st-%C3%A0/?foo=+bar HTTP/1.1\r\n" +
b"Host: somewhere.com\r\n" +
b"\r\n"
)
# Get the resulting message off of the channel layer
_, message = self.channel_layer.receive(["http.request"])
self.assertEqual(message['http_version'], "1.1")
self.assertEqual(message['method'], "GET")
self.assertEqual(message['scheme'], "http")
self.assertEqual(message['path'], "/te st-à/")
self.assertEqual(message['query_string'], b"foo=+bar")
self.assertEqual(message['headers'], [(b"host", b"somewhere.com")])
self.assertFalse(message.get("body", None))
self.assertTrue(message['reply_channel'])
# Send back an example response
self.factory.dispatch_reply(
message['reply_channel'],
{
"status": 201,
"status_text": b"Created",
"content": b"OH HAI",
"headers": [[b"X-Test", b"Boom!"]],
}
)
# Make sure that comes back right on the protocol
self.assertEqual(self.tr.value(), b"HTTP/1.1 201 Created\r\nTransfer-Encoding: chunked\r\nX-Test: Boom!\r\n\r\n6\r\nOH HAI\r\n0\r\n\r\n")
def test_http_disconnect_sets_path_key(self): def test_http_disconnect_sets_path_key(self):
""" """
Tests http disconnect has the path key set, see https://channels.readthedocs.io/en/latest/asgi.html#disconnect Tests http disconnect has the path key set, see https://channels.readthedocs.io/en/latest/asgi.html#disconnect