daphne/daphne/tests/test_http_response.py
2017-11-25 13:41:38 -08:00

129 lines
4.8 KiB
Python

# coding: utf8
"""
Tests for the HTTP response section of the ASGI spec
"""
from __future__ import unicode_literals
from unittest import TestCase
from asgiref.inmemory import ChannelLayer
from hypothesis import given, settings
from twisted.test import proto_helpers
from daphne.http_protocol import HTTPFactory
from . import factories, http_strategies, testcases
class TestHTTPResponseSpec(testcases.ASGIHTTPTestCase):
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(),
)
@settings(perform_health_check=False)
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):
"""
Tests that the HTTP protocol class correctly generates and parses messages.
"""
def setUp(self):
self.channel_layer = ChannelLayer()
self.factory = HTTPFactory(self.channel_layer, send_channel="test!")
self.proto = self.factory.buildProtocol(("127.0.0.1", 0))
self.tr = proto_helpers.StringTransport()
self.proto.makeConnection(self.tr)
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
"""
# 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: anywhere.com\r\n" +
b"\r\n"
)
# Get the request message
_, message = self.channel_layer.receive(["http.request"])
# Send back an example response
self.factory.dispatch_reply(
message["reply_channel"],
{
"status": 200,
"status_text": b"OK",
"content": b"DISCO",
}
)
# Get the disconnection notification
_, disconnect_message = self.channel_layer.receive(["http.disconnect"])
self.assertEqual(disconnect_message["path"], "/te st-à/")