From f35ad20748a6f2e84122c45dfae6046a71541a9d Mon Sep 17 00:00:00 2001 From: Maik Hoepfel Date: Wed, 22 Mar 2017 16:57:43 +0800 Subject: [PATCH] Add assert method to check a response for spec conformance Similarly to the method for checking HTTP requests for spec conformance, we're adding a method to do the same for HTTP responses. This one is a bit less exciting because we're testing raw HTTP responses. --- daphne/tests/factories.py | 11 +++++++++-- daphne/tests/testcases.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/daphne/tests/factories.py b/daphne/tests/factories.py index e7ab6cc..fd9013a 100644 --- a/daphne/tests/factories.py +++ b/daphne/tests/factories.py @@ -73,8 +73,8 @@ def _build_request(method, path, params=None, headers=None, body=None): quoted_path += b'?' + parse.urlencode(params) request = method.encode('ascii') + b' ' + quoted_path + b" HTTP/1.1\r\n" - for k, v in headers: - request += k.encode('ascii') + b': ' + v.encode('ascii') + b"\r\n" + for name, value in headers: + request += header_line(name, value) request += b'\r\n' @@ -84,6 +84,13 @@ def _build_request(method, path, params=None, headers=None, body=None): return request +def header_line(name, value): + """ + Given a header name and value, returns the line to use in a HTTP request or response. + """ + return name.encode('ascii') + b': ' + value.encode('ascii') + b"\r\n" + + def _run_through_daphne(request, channel_name): """ Returns Daphne's channel message for a given request. diff --git a/daphne/tests/testcases.py b/daphne/tests/testcases.py index 6e2e7a8..df1750b 100644 --- a/daphne/tests/testcases.py +++ b/daphne/tests/testcases.py @@ -4,12 +4,13 @@ Contains a test case class to allow verifying ASGI messages from __future__ import unicode_literals from collections import defaultdict - import six -import socket from six.moves.urllib import parse +import socket import unittest +from . import factories + class ASGITestCase(unittest.TestCase): """ @@ -122,3 +123,27 @@ class ASGITestCase(unittest.TestCase): self.assertIsInstance(server_host, six.text_type) self.assert_is_ip_address(server_host) self.assertIsInstance(server_port, int) + + def assert_valid_http_response_message(self, message, response): + self.assertTrue(message) + self.assertTrue(response.startswith(b'HTTP')) + + status_code_bytes = six.text_type(message['status']).encode('ascii') + self.assertIn(status_code_bytes, response) + + if 'content' in message: + self.assertIn(message['content'], response) + + # Check that headers are in the given order. + # N.b. HTTP spec only enforces that the order of header values is kept, but + # the ASGI spec requires that order of all headers is kept. This code + # checks conformance with the stricter ASGI spec. + if 'headers' in message: + for name, value in message['headers']: + expected_header = factories.header_line(name, value) + # Daphne or Twisted turn our lower cased header names ('foo-bar') into title + # case ('Foo-Bar'). So technically we want to to match that the header name is + # present while ignoring casing, and want to ensure the value is present without + # altered casing. The approach below does this well enough. + self.assertIn(expected_header.lower(), response.lower()) + self.assertIn(value.encode('ascii'), response)