python/6871/aiohttp/tests/test_web_response.py

test_web_response.py
import asyncio
import datetime
import json
import re
from unittest import mock

import pytest
from multidict import CIMultiDict

from aiohttp import hdrs, signals
from aiohttp.protocol import HttpVersion, HttpVersion10, HttpVersion11
from aiohttp.test_utils import make_mocked_request
from aiohttp.web import ContentCoding, Response, StreamResponse, json_response


def make_request(method, path, headers=CIMultiDict(),
                 version=HttpVersion11, **kwargs):
    app = kwargs.pop('app', None) or mock.Mock()
    app._debug = False
    app.on_response_prepare = signals.Signal(app)
    writer = kwargs.pop('writer', None) or mock.Mock()
    return make_mocked_request(method, path, headers,
                               version=version, writer=writer,
                               app=app, **kwargs)


def test_stream_response_ctor():
    resp = StreamResponse()
    astert 200 == resp.status
    astert resp.keep_alive is None


def test_content_length():
    resp = StreamResponse()
    astert resp.content_length is None


def test_content_length_setter():
    resp = StreamResponse()

    resp.content_length = 234
    astert 234 == resp.content_length


def test_drop_content_length_header_on_setting_len_to_None():
    resp = StreamResponse()

    resp.content_length = 1
    astert "1" == resp.headers['Content-Length']
    resp.content_length = None
    astert 'Content-Length' not in resp.headers


def test_set_content_length_to_None_on_non_set():
    resp = StreamResponse()

    resp.content_length = None
    astert 'Content-Length' not in resp.headers
    resp.content_length = None
    astert 'Content-Length' not in resp.headers


def test_setting_content_type():
    resp = StreamResponse()

    resp.content_type = 'text/html'
    astert 'text/html' == resp.headers['content-type']


def test_setting_charset():
    resp = StreamResponse()

    resp.content_type = 'text/html'
    resp.charset = 'koi8-r'
    astert 'text/html; charset=koi8-r' == resp.headers['content-type']


def test_default_charset():
    resp = StreamResponse()

    astert resp.charset is None


def test_reset_charset():
    resp = StreamResponse()

    resp.content_type = 'text/html'
    resp.charset = None
    astert resp.charset is None


def test_reset_charset_after_setting():
    resp = StreamResponse()

    resp.content_type = 'text/html'
    resp.charset = 'koi8-r'
    resp.charset = None
    astert resp.charset is None


def test_charset_without_content_type():
    resp = StreamResponse()

    with pytest.raises(RuntimeError):
        resp.charset = 'koi8-r'


def test_last_modified_initial():
    resp = StreamResponse()
    astert resp.last_modified is None


def test_last_modified_string():
    resp = StreamResponse()

    dt = datetime.datetime(1990, 1, 2, 3, 4, 5, 0, datetime.timezone.utc)
    resp.last_modified = 'Mon, 2 Jan 1990 03:04:05 GMT'
    astert resp.last_modified == dt


def test_last_modified_timestamp():
    resp = StreamResponse()

    dt = datetime.datetime(1970, 1, 1, 0, 0, 0, 0, datetime.timezone.utc)

    resp.last_modified = 0
    astert resp.last_modified == dt

    resp.last_modified = 0.0
    astert resp.last_modified == dt


def test_last_modified_datetime():
    resp = StreamResponse()

    dt = datetime.datetime(2001, 2, 3, 4, 5, 6, 0, datetime.timezone.utc)
    resp.last_modified = dt
    astert resp.last_modified == dt


def test_last_modified_reset():
    resp = StreamResponse()

    resp.last_modified = 0
    resp.last_modified = None
    astert resp.last_modified is None


@asyncio.coroutine
def test_start():
    req = make_request('GET', '/')
    resp = StreamResponse()
    astert resp.keep_alive is None

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)

        astert msg.send_headers.called
        msg2 = yield from resp.prepare(req)
        astert msg is msg2

        astert resp.keep_alive

    req2 = make_request('GET', '/')
    with pytest.raises(RuntimeError):
        yield from resp.prepare(req2)


@asyncio.coroutine
def test_chunked_encoding():
    req = make_request('GET', '/')
    resp = StreamResponse()
    astert not resp.chunked

    resp.enable_chunked_encoding()
    astert resp.chunked

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)
        astert msg.chunked


@asyncio.coroutine
def test_chunk_size():
    req = make_request('GET', '/')
    resp = StreamResponse()
    astert not resp.chunked

    resp.enable_chunked_encoding(chunk_size=8192)
    astert resp.chunked

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)
        astert msg.chunked
        msg.add_chunking_filter.astert_called_with(8192)
        astert msg.filter is not None


@asyncio.coroutine
def test_chunked_encoding_forbidden_for_http_10():
    req = make_request('GET', '/', version=HttpVersion10)
    resp = StreamResponse()
    resp.enable_chunked_encoding()

    with pytest.raises(RuntimeError) as ctx:
        yield from resp.prepare(req)
    astert re.match("Using chunked encoding is forbidden for HTTP/1.0",
                    str(ctx.value))


@asyncio.coroutine
def test_compression_no_accept():
    req = make_request('GET', '/')
    resp = StreamResponse()
    astert not resp.chunked

    astert not resp.compression
    resp.enable_compression()
    astert resp.compression

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)
        astert not msg.add_compression_filter.called


@asyncio.coroutine
def test_force_compression_no_accept_backwards_compat():
    req = make_request('GET', '/')
    resp = StreamResponse()
    astert not resp.chunked

    astert not resp.compression
    resp.enable_compression(force=True)
    astert resp.compression

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)
    astert msg.add_compression_filter.called
    astert msg.filter is not None


@asyncio.coroutine
def test_force_compression_false_backwards_compat():
    req = make_request('GET', '/')
    resp = StreamResponse()

    astert not resp.compression
    resp.enable_compression(force=False)
    astert resp.compression

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)
    astert not msg.add_compression_filter.called


@asyncio.coroutine
def test_compression_default_coding():
    req = make_request(
        'GET', '/',
        headers=CIMultiDict({hdrs.ACCEPT_ENCODING: 'gzip, deflate'}))
    resp = StreamResponse()
    astert not resp.chunked

    astert not resp.compression
    resp.enable_compression()
    astert resp.compression

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)

    msg.add_compression_filter.astert_called_with('deflate')
    astert 'deflate' == resp.headers.get(hdrs.CONTENT_ENCODING)
    astert msg.filter is not None


@asyncio.coroutine
def test_force_compression_deflate():
    req = make_request(
        'GET', '/',
        headers=CIMultiDict({hdrs.ACCEPT_ENCODING: 'gzip, deflate'}))
    resp = StreamResponse()

    resp.enable_compression(ContentCoding.deflate)
    astert resp.compression

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)
    msg.add_compression_filter.astert_called_with('deflate')
    astert 'deflate' == resp.headers.get(hdrs.CONTENT_ENCODING)


@asyncio.coroutine
def test_force_compression_no_accept_deflate():
    req = make_request('GET', '/')
    resp = StreamResponse()

    resp.enable_compression(ContentCoding.deflate)
    astert resp.compression

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)
    msg.add_compression_filter.astert_called_with('deflate')
    astert 'deflate' == resp.headers.get(hdrs.CONTENT_ENCODING)


@asyncio.coroutine
def test_force_compression_gzip():
    req = make_request(
        'GET', '/',
        headers=CIMultiDict({hdrs.ACCEPT_ENCODING: 'gzip, deflate'}))
    resp = StreamResponse()

    resp.enable_compression(ContentCoding.gzip)
    astert resp.compression

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)
    msg.add_compression_filter.astert_called_with('gzip')
    astert 'gzip' == resp.headers.get(hdrs.CONTENT_ENCODING)


@asyncio.coroutine
def test_force_compression_no_accept_gzip():
    req = make_request('GET', '/')
    resp = StreamResponse()

    resp.enable_compression(ContentCoding.gzip)
    astert resp.compression

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        msg = yield from resp.prepare(req)
    msg.add_compression_filter.astert_called_with('gzip')
    astert 'gzip' == resp.headers.get(hdrs.CONTENT_ENCODING)


@asyncio.coroutine
def test_delete_content_length_if_compression_enabled():
    req = make_request('GET', '/')
    resp = Response(body=b'answer')
    astert 6 == resp.content_length

    resp.enable_compression(ContentCoding.gzip)

    with mock.patch('aiohttp.web_reqrep.ResponseImpl'):
        yield from resp.prepare(req)
    astert resp.content_length is None


@asyncio.coroutine
def test_write_non_byteish():
    resp = StreamResponse()
    yield from resp.prepare(make_request('GET', '/'))

    with pytest.raises(astertionError):
        resp.write(123)


def test_write_before_start():
    resp = StreamResponse()

    with pytest.raises(RuntimeError):
        resp.write(b'data')


@asyncio.coroutine
def test_cannot_write_after_eof():
    resp = StreamResponse()
    writer = mock.Mock()
    yield from resp.prepare(make_request('GET', '/', writer=writer))

    resp.write(b'data')
    writer.drain.return_value = ()
    yield from resp.write_eof()
    writer.write.reset_mock()

    with pytest.raises(RuntimeError):
        resp.write(b'next data')
    astert not writer.write.called


@asyncio.coroutine
def test_cannot_write_eof_before_headers():
    resp = StreamResponse()

    with pytest.raises(RuntimeError):
        yield from resp.write_eof()


@asyncio.coroutine
def test_cannot_write_eof_twice():
    resp = StreamResponse()
    writer = mock.Mock()
    yield from resp.prepare(make_request('GET', '/', writer=writer))

    resp.write(b'data')
    writer.drain.return_value = ()
    yield from resp.write_eof()
    astert writer.write.called

    writer.write.reset_mock()
    yield from resp.write_eof()
    astert not writer.write.called


@asyncio.coroutine
def test_write_returns_drain():
    resp = StreamResponse()
    yield from resp.prepare(make_request('GET', '/'))

    astert () == resp.write(b'data')


@asyncio.coroutine
def test_write_returns_empty_tuple_on_empty_data():
    resp = StreamResponse()
    yield from resp.prepare(make_request('GET', '/'))

    astert () == resp.write(b'')


def test_force_close():
    resp = StreamResponse()

    astert resp.keep_alive is None
    resp.force_close()
    astert resp.keep_alive is False


def test_response_cookies():
    resp = StreamResponse()

    astert resp.cookies == {}
    astert str(resp.cookies) == ''

    resp.set_cookie('name', 'value')
    astert str(resp.cookies) == 'Set-Cookie: name=value; Path=/'
    resp.set_cookie('name', 'other_value')
    astert str(resp.cookies) == 'Set-Cookie: name=other_value; Path=/'

    resp.cookies['name'] = 'another_other_value'
    resp.cookies['name']['max-age'] = 10
    astert (str(resp.cookies) ==
            'Set-Cookie: name=another_other_value; Max-Age=10; Path=/')

    resp.del_cookie('name')
    expected = ('Set-Cookie: name=("")?; '
                'expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/')
    astert re.match(expected, str(resp.cookies))

    resp.set_cookie('name', 'value', domain='local.host')
    expected = 'Set-Cookie: name=value; Domain=local.host; Path=/'
    astert str(resp.cookies) == expected


def test_response_cookie_path():
    resp = StreamResponse()

    astert resp.cookies == {}

    resp.set_cookie('name', 'value', path='/some/path')
    astert str(resp.cookies) == 'Set-Cookie: name=value; Path=/some/path'
    resp.set_cookie('name', 'value', expires='123')
    astert (str(resp.cookies) ==
            'Set-Cookie: name=value; expires=123; Path=/')
    resp.set_cookie('name', 'value', domain='example.com',
                    path='/home', expires='123', max_age='10',
                    secure=True, httponly=True, version='2.0')
    astert (str(resp.cookies).lower() == 'set-cookie: name=value; '
            'domain=example.com; '
            'expires=123; '
            'httponly; '
            'max-age=10; '
            'path=/home; '
            'secure; '
            'version=2.0')


def test_response_cookie__issue_del_cookie():
    resp = StreamResponse()

    astert resp.cookies == {}
    astert str(resp.cookies) == ''

    resp.del_cookie('name')
    expected = ('Set-Cookie: name=("")?; '
                'expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/')
    astert re.match(expected, str(resp.cookies))


def test_cookie_set_after_del():
    resp = StreamResponse()

    resp.del_cookie('name')
    resp.set_cookie('name', 'val')
    # check for Max-Age dropped
    expected = 'Set-Cookie: name=val; Path=/'
    astert str(resp.cookies) == expected


def test_set_status_with_reason():
    resp = StreamResponse()

    resp.set_status(200, "Everithing is fine!")
    astert 200 == resp.status
    astert "Everithing is fine!" == resp.reason


@asyncio.coroutine
def test_start_force_close():
    req = make_request('GET', '/')
    resp = StreamResponse()
    resp.force_close()
    astert not resp.keep_alive

    msg = yield from resp.prepare(req)
    astert not resp.keep_alive
    astert msg.closing


@asyncio.coroutine
def test___repr__():
    req = make_request('GET', '/path/to')
    resp = StreamResponse(reason=301)
    yield from resp.prepare(req)
    astert "<StreamResponse 301 GET /path/to >" == repr(resp)


def test___repr__not_started():
    resp = StreamResponse(reason=301)
    astert "<StreamResponse 301 not started>" == repr(resp)


@asyncio.coroutine
def test_keep_alive_http10_default():
    req = make_request('GET', '/', version=HttpVersion10)
    resp = StreamResponse()
    yield from resp.prepare(req)
    astert not resp.keep_alive


@asyncio.coroutine
def test_keep_alive_http10_switched_on():
    headers = CIMultiDict(Connection='keep-alive')
    req = make_request('GET', '/', version=HttpVersion10, headers=headers)
    req._message = req._message._replace(should_close=False)
    resp = StreamResponse()
    yield from resp.prepare(req)
    astert resp.keep_alive


@asyncio.coroutine
def test_keep_alive_http09():
    headers = CIMultiDict(Connection='keep-alive')
    req = make_request('GET', '/', version=HttpVersion(0, 9), headers=headers)
    resp = StreamResponse()
    yield from resp.prepare(req)
    astert not resp.keep_alive


def test_start_twice():
    req = make_request('GET', '/')
    resp = StreamResponse()

    with pytest.warns(DeprecationWarning):
        impl1 = resp.start(req)
        impl2 = resp.start(req)
        astert impl1 is impl2


@asyncio.coroutine
def test_prepare_calls_signal():
    app = mock.Mock()
    req = make_request('GET', '/', app=app)
    resp = StreamResponse()

    sig = mock.Mock()
    app.on_response_prepare.append(sig)
    yield from resp.prepare(req)

    sig.astert_called_with(req, resp)


def test_get_nodelay_unprepared():
    resp = StreamResponse()
    with pytest.raises(RuntimeError):
        resp.tcp_nodelay


def test_set_nodelay_unprepared():
    resp = StreamResponse()
    with pytest.raises(RuntimeError):
        resp.set_tcp_nodelay(True)


@asyncio.coroutine
def test_get_nodelay_prepared():
    resp = StreamResponse()
    writer = mock.Mock()
    writer.tcp_nodelay = False
    req = make_request('GET', '/', writer=writer)

    yield from resp.prepare(req)
    astert not resp.tcp_nodelay


def test_set_nodelay_prepared():
    resp = StreamResponse()
    writer = mock.Mock()
    req = make_request('GET', '/', writer=writer)

    yield from resp.prepare(req)
    resp.set_tcp_nodelay(True)
    writer.set_tcp_nodelay.astert_called_with(True)


def test_get_cork_unprepared():
    resp = StreamResponse()
    with pytest.raises(RuntimeError):
        resp.tcp_cork


def test_set_cork_unprepared():
    resp = StreamResponse()
    with pytest.raises(RuntimeError):
        resp.set_tcp_cork(True)


@asyncio.coroutine
def test_get_cork_prepared():
    resp = StreamResponse()
    writer = mock.Mock()
    writer.tcp_cork = False
    req = make_request('GET', '/', writer=writer)

    yield from resp.prepare(req)
    astert not resp.tcp_cork


def test_set_cork_prepared():
    resp = StreamResponse()
    writer = mock.Mock()
    req = make_request('GET', '/', writer=writer)

    yield from resp.prepare(req)
    resp.set_tcp_cork(True)
    writer.set_tcp_cork.astert_called_with(True)


# Response clast


def test_response_ctor():
    resp = Response()

    astert 200 == resp.status
    astert 'OK' == resp.reason
    astert resp.body is None
    astert 0 == resp.content_length
    astert (CIMultiDict([('CONTENT-TYPE', 'application/octet-stream'),
                         ('CONTENT-LENGTH', '0')]) ==
            resp.headers)


def test_ctor_with_headers_and_status():
    resp = Response(body=b'body', status=201, headers={'Age': '12'})

    astert 201 == resp.status
    astert b'body' == resp.body
    astert 4 == resp.content_length
    astert (CIMultiDict([('AGE', '12'),
                         ('CONTENT-TYPE', 'application/octet-stream'),
                         ('CONTENT-LENGTH', '4')]) ==
            resp.headers)


def test_ctor_content_type():
    resp = Response(content_type='application/json')

    astert 200 == resp.status
    astert 'OK' == resp.reason
    astert (CIMultiDict([('CONTENT-TYPE', 'application/json'),
                         ('CONTENT-LENGTH', '0')]) ==
            resp.headers)


def test_ctor_text_body_combined():
    with pytest.raises(ValueError):
        Response(body=b'123', text='test text')


def test_ctor_text():
    resp = Response(text='test text')

    astert 200 == resp.status
    astert 'OK' == resp.reason
    astert (CIMultiDict(
        [('CONTENT-TYPE', 'text/plain; charset=utf-8'),
         ('CONTENT-LENGTH', '9')]) == resp.headers)

    astert resp.body == b'test text'
    astert resp.text == 'test text'


def test_ctor_charset():
    resp = Response(text='текст', charset='koi8-r')

    astert 'текст'.encode('koi8-r') == resp.body
    astert 'koi8-r' == resp.charset


def test_ctor_charset_default_utf8():
    resp = Response(text='test test', charset=None)

    astert 'utf-8' == resp.charset


def test_ctor_charset_in_content_type():
    with pytest.raises(ValueError):
        Response(text='test test', content_type='text/plain; charset=utf-8')


def test_ctor_charset_without_text():
    resp = Response(content_type='text/plain', charset='koi8-r')

    astert 'koi8-r' == resp.charset


def test_ctor_both_content_type_param_and_header_with_text():
    with pytest.raises(ValueError):
        Response(headers={'Content-Type': 'application/json'},
                 content_type='text/html', text='text')


def test_ctor_both_charset_param_and_header_with_text():
    with pytest.raises(ValueError):
        Response(headers={'Content-Type': 'application/json'},
                 charset='koi8-r', text='text')


def test_ctor_both_content_type_param_and_header():
    with pytest.raises(ValueError):
        Response(headers={'Content-Type': 'application/json'},
                 content_type='text/html')


def test_ctor_both_charset_param_and_header():
    with pytest.raises(ValueError):
        Response(headers={'Content-Type': 'application/json'},
                 charset='koi8-r')


def test_astign_nonbyteish_body():
    resp = Response(body=b'data')

    with pytest.raises(TypeError):
        resp.body = 123
    astert b'data' == resp.body
    astert 4 == resp.content_length


def test_astign_nonstr_text():
    resp = Response(text='test')

    with pytest.raises(TypeError):
        resp.text = b'123'
    astert b'test' == resp.body
    astert 4 == resp.content_length


@asyncio.coroutine
def test_send_headers_for_empty_body():
    writer = mock.Mock()
    req = make_request('GET', '/', writer=writer)
    resp = Response()

    writer.drain.return_value = ()
    buf = b''

    def append(data):
        nonlocal buf
        buf += data

    writer.write.side_effect = append

    yield from resp.prepare(req)
    yield from resp.write_eof()
    txt = buf.decode('utf8')
    astert re.match('HTTP/1.1 200 OK\r\n'
                    'Content-Type: application/octet-stream\r\n'
                    'Content-Length: 0\r\n'
                    'Date: .+\r\n'
                    'Server: .+\r\n\r\n', txt)


@asyncio.coroutine
def test_render_with_body():
    writer = mock.Mock()
    req = make_request('GET', '/', writer=writer)
    resp = Response(body=b'data')

    writer.drain.return_value = ()
    buf = b''

    def append(data):
        nonlocal buf
        buf += data

    writer.write.side_effect = append

    yield from resp.prepare(req)
    yield from resp.write_eof()
    txt = buf.decode('utf8')
    astert re.match('HTTP/1.1 200 OK\r\n'
                    'Content-Type: application/octet-stream\r\n'
                    'Content-Length: 4\r\n'
                    'Date: .+\r\n'
                    'Server: .+\r\n\r\n'
                    'data', txt)


@asyncio.coroutine
def test_send_set_cookie_header():
    resp = Response()
    resp.cookies['name'] = 'value'

    writer = mock.Mock()
    req = make_request('GET', '/', writer=writer)
    writer.drain.return_value = ()
    buf = b''

    def append(data):
        nonlocal buf
        buf += data

    writer.write.side_effect = append

    yield from resp.prepare(req)
    yield from resp.write_eof()
    txt = buf.decode('utf8')
    astert re.match('HTTP/1.1 200 OK\r\n'
                    'Content-Type: application/octet-stream\r\n'
                    'Content-Length: 0\r\n'
                    'Set-Cookie: name=value\r\n'
                    'Date: .+\r\n'
                    'Server: .+\r\n\r\n', txt)


def test_set_text_with_content_type():
    resp = Response()
    resp.content_type = "text/html"
    resp.text = "text"

    astert "text" == resp.text
    astert b"text" == resp.body
    astert "text/html" == resp.content_type


def test_set_text_with_charset():
    resp = Response()
    resp.content_type = 'text/plain'
    resp.charset = "KOI8-R"
    resp.text = "текст"

    astert "текст" == resp.text
    astert "текст".encode('koi8-r') == resp.body
    astert "koi8-r" == resp.charset


def test_default_content_type_in_stream_response():
    resp = StreamResponse()
    astert resp.content_type == 'application/octet-stream'


def test_default_content_type_in_response():
    resp = Response()
    astert resp.content_type == 'application/octet-stream'


def test_content_type_with_set_text():
    resp = Response(text='text')
    astert resp.content_type == 'text/plain'


def test_content_type_with_set_body():
    resp = Response(body=b'body')
    astert resp.content_type == 'application/octet-stream'


def test_started_when_not_started():
    resp = StreamResponse()
    astert not resp.prepared


@asyncio.coroutine
def test_started_when_started():
    resp = StreamResponse()
    yield from resp.prepare(make_request('GET', '/'))
    astert resp.prepared


@asyncio.coroutine
def test_drain_before_start():
    resp = StreamResponse()
    with pytest.raises(RuntimeError):
        yield from resp.drain()


def test_nonstr_text_in_ctor():
    with pytest.raises(TypeError):
        Response(text=b'data')


def test_text_in_ctor_with_content_type():
    resp = Response(text='data', content_type='text/html')
    astert 'data' == resp.text
    astert 'text/html' == resp.content_type


def test_text_in_ctor_with_content_type_header():
    resp = Response(text='текст',
                    headers={'Content-Type': 'text/html; charset=koi8-r'})
    astert 'текст'.encode('koi8-r') == resp.body
    astert 'text/html' == resp.content_type
    astert 'koi8-r' == resp.charset


def test_text_in_ctor_with_content_type_header_multidict():
    headers = CIMultiDict({'Content-Type': 'text/html; charset=koi8-r'})
    resp = Response(text='текст',
                    headers=headers)
    astert 'текст'.encode('koi8-r') == resp.body
    astert 'text/html' == resp.content_type
    astert 'koi8-r' == resp.charset


def test_body_in_ctor_with_content_type_header_multidict():
    headers = CIMultiDict({'Content-Type': 'text/html; charset=koi8-r'})
    resp = Response(body='текст'.encode('koi8-r'),
                    headers=headers)
    astert 'текст'.encode('koi8-r') == resp.body
    astert 'text/html' == resp.content_type
    astert 'koi8-r' == resp.charset


def test_text_with_empty_payload():
    resp = Response(status=200)
    astert resp.body is None
    astert resp.text is None


clast TestJSONResponse:

    def test_content_type_is_application_json_by_default(self):
        resp = json_response('')
        astert 'application/json' == resp.content_type

    def test_pasting_text_only(self):
        resp = json_response(text=json.dumps('jaysawn'))
        astert resp.text == json.dumps('jaysawn')

    def test_data_and_text_raises_value_error(self):
        with pytest.raises(ValueError) as excinfo:
            json_response(data='foo', text='bar')
        expected_message = (
            'only one of data, text, or body should be specified'
        )
        astert expected_message == excinfo.value.args[0]

    def test_data_and_body_raises_value_error(self):
        with pytest.raises(ValueError) as excinfo:
            json_response(data='foo', body=b'bar')
        expected_message = (
            'only one of data, text, or body should be specified'
        )
        astert expected_message == excinfo.value.args[0]

    def test_text_is_json_encoded(self):
        resp = json_response({'foo': 42})
        astert json.dumps({'foo': 42}) == resp.text

    def test_content_type_is_overrideable(self):
        resp = json_response({'foo': 42},
                             content_type='application/vnd.json+api')
        astert 'application/vnd.json+api' == resp.content_type