|
| 1 | +import itertools |
| 2 | +import pytest |
1 | 3 | import requests
|
2 | 4 |
|
3 | 5 | from pytest_localserver import http, plugin
|
|
8 | 10 | httpserver = plugin.httpserver
|
9 | 11 |
|
10 | 12 |
|
| 13 | +transfer_encoded = pytest.mark.parametrize('transfer_encoding_header', ['Transfer-encoding', 'Transfer-Encoding', 'transfer-encoding', 'TRANSFER-ENCODING']) |
| 14 | + |
| 15 | + |
11 | 16 | def test_httpserver_funcarg(httpserver):
|
12 | 17 | assert isinstance(httpserver, http.ContentServer)
|
13 | 18 | assert httpserver.is_alive()
|
@@ -76,3 +81,206 @@ def test_HEAD_request(httpserver):
|
76 | 81 | # resp = requests.post(httpserver.url, data={'data': 'value'}, headers=headers)
|
77 | 82 | # assert resp.json() == {'data': 'value'}
|
78 | 83 | # assert resp.status_code == 200
|
| 84 | + |
| 85 | + |
| 86 | +@pytest.mark.parametrize('chunked_flag', [http.Chunked.YES, http.Chunked.AUTO, http.Chunked.NO]) |
| 87 | +def test_chunked_attribute_without_header(httpserver, chunked_flag): |
| 88 | + """ |
| 89 | + Test that passing the chunked attribute to serve_content() properly sets |
| 90 | + the chunked property of the server. |
| 91 | + """ |
| 92 | + httpserver.serve_content( |
| 93 | + ('TEST!', 'test'), |
| 94 | + headers={'Content-type': 'text/plain'}, |
| 95 | + chunked=chunked_flag |
| 96 | + ) |
| 97 | + assert httpserver.chunked == chunked_flag |
| 98 | + |
| 99 | + |
| 100 | +@pytest.mark.parametrize('chunked_flag', [http.Chunked.YES, http.Chunked.AUTO, http.Chunked.NO]) |
| 101 | +def test_chunked_attribute_with_header(httpserver, chunked_flag): |
| 102 | + """ |
| 103 | + Test that passing the chunked attribute to serve_content() properly sets |
| 104 | + the chunked property of the server even when the transfer-encoding header is |
| 105 | + also set. |
| 106 | + """ |
| 107 | + httpserver.serve_content( |
| 108 | + ('TEST!', 'test'), |
| 109 | + headers={'Content-type': 'text/plain', 'Transfer-encoding': 'chunked'}, |
| 110 | + chunked=chunked_flag |
| 111 | + ) |
| 112 | + assert httpserver.chunked == chunked_flag |
| 113 | + |
| 114 | + |
| 115 | +@transfer_encoded |
| 116 | +@pytest.mark.parametrize('chunked_flag', [http.Chunked.YES, http.Chunked.AUTO]) |
| 117 | +def test_GET_request_chunked_parameter(httpserver, transfer_encoding_header, chunked_flag): |
| 118 | + """ |
| 119 | + Test that passing YES or AUTO as the chunked parameter to serve_content() |
| 120 | + causes the response to be sent using chunking when the Transfer-encoding |
| 121 | + header is also set. |
| 122 | + """ |
| 123 | + httpserver.serve_content( |
| 124 | + ('TEST!', 'test'), |
| 125 | + headers={'Content-type': 'text/plain', transfer_encoding_header: 'chunked'}, |
| 126 | + chunked=chunked_flag |
| 127 | + ) |
| 128 | + resp = requests.get(httpserver.url, headers={'User-Agent': 'Test method'}) |
| 129 | + assert resp.text == 'TEST!test' |
| 130 | + assert resp.status_code == 200 |
| 131 | + assert 'text/plain' in resp.headers['Content-type'] |
| 132 | + assert 'chunked' in resp.headers['Transfer-encoding'] |
| 133 | + |
| 134 | + |
| 135 | +@transfer_encoded |
| 136 | +@pytest.mark.parametrize('chunked_flag', [http.Chunked.YES, http.Chunked.AUTO]) |
| 137 | +def test_GET_request_chunked_attribute(httpserver, transfer_encoding_header, chunked_flag): |
| 138 | + """ |
| 139 | + Test that setting the chunked attribute of httpserver to YES or AUTO |
| 140 | + causes the response to be sent using chunking when the Transfer-encoding |
| 141 | + header is also set. |
| 142 | + """ |
| 143 | + httpserver.serve_content( |
| 144 | + ('TEST!', 'test'), |
| 145 | + headers={'Content-type': 'text/plain', transfer_encoding_header: 'chunked'} |
| 146 | + ) |
| 147 | + httpserver.chunked = chunked_flag |
| 148 | + resp = requests.get(httpserver.url, headers={'User-Agent': 'Test method'}) |
| 149 | + assert resp.text == 'TEST!test' |
| 150 | + assert resp.status_code == 200 |
| 151 | + assert 'text/plain' in resp.headers['Content-type'] |
| 152 | + assert 'chunked' in resp.headers['Transfer-encoding'] |
| 153 | + |
| 154 | + |
| 155 | +@transfer_encoded |
| 156 | +def test_GET_request_not_chunked(httpserver, transfer_encoding_header): |
| 157 | + """ |
| 158 | + Test that setting the chunked attribute of httpserver to NO causes |
| 159 | + the response not to be sent using chunking even if the Transfer-encoding |
| 160 | + header is set. |
| 161 | + """ |
| 162 | + httpserver.serve_content( |
| 163 | + ('TEST!', 'test'), |
| 164 | + headers={'Content-type': 'text/plain', transfer_encoding_header: 'chunked'}, |
| 165 | + chunked=http.Chunked.NO |
| 166 | + ) |
| 167 | + with pytest.raises(requests.exceptions.ChunkedEncodingError): |
| 168 | + resp = requests.get(httpserver.url, headers={'User-Agent': 'Test method'}) |
| 169 | + |
| 170 | + |
| 171 | +@pytest.mark.parametrize('chunked_flag', [http.Chunked.NO, http.Chunked.AUTO]) |
| 172 | +def test_GET_request_chunked_parameter_no_header(httpserver, chunked_flag): |
| 173 | + """ |
| 174 | + Test that passing NO or AUTO as the chunked parameter to serve_content() |
| 175 | + causes the response not to be sent using chunking when the Transfer-encoding |
| 176 | + header is not set. |
| 177 | + """ |
| 178 | + httpserver.serve_content( |
| 179 | + ('TEST!', 'test'), |
| 180 | + headers={'Content-type': 'text/plain'}, |
| 181 | + chunked=chunked_flag |
| 182 | + ) |
| 183 | + resp = requests.get(httpserver.url, headers={'User-Agent': 'Test method'}) |
| 184 | + assert resp.text == 'TEST!test' |
| 185 | + assert resp.status_code == 200 |
| 186 | + assert 'text/plain' in resp.headers['Content-type'] |
| 187 | + assert 'Transfer-encoding' not in resp.headers |
| 188 | + |
| 189 | + |
| 190 | +@pytest.mark.parametrize('chunked_flag', [http.Chunked.NO, http.Chunked.AUTO]) |
| 191 | +def test_GET_request_chunked_attribute_no_header(httpserver, chunked_flag): |
| 192 | + """ |
| 193 | + Test that setting the chunked attribute of httpserver to NO or AUTO |
| 194 | + causes the response not to be sent using chunking when the Transfer-encoding |
| 195 | + header is not set. |
| 196 | + """ |
| 197 | + httpserver.serve_content( |
| 198 | + ('TEST!', 'test'), |
| 199 | + headers={'Content-type': 'text/plain'} |
| 200 | + ) |
| 201 | + httpserver.chunked = chunked_flag |
| 202 | + resp = requests.get(httpserver.url, headers={'User-Agent': 'Test method'}) |
| 203 | + assert resp.text == 'TEST!test' |
| 204 | + assert resp.status_code == 200 |
| 205 | + assert 'text/plain' in resp.headers['Content-type'] |
| 206 | + assert 'Transfer-encoding' not in resp.headers |
| 207 | + |
| 208 | + |
| 209 | +def test_GET_request_chunked_no_header(httpserver): |
| 210 | + """ |
| 211 | + Test that setting the chunked attribute of httpserver to YES causes |
| 212 | + the response to be sent using chunking even if the Transfer-encoding |
| 213 | + header is not set. |
| 214 | + """ |
| 215 | + httpserver.serve_content( |
| 216 | + ('TEST!', 'test'), |
| 217 | + headers={'Content-type': 'text/plain'}, |
| 218 | + chunked=http.Chunked.YES |
| 219 | + ) |
| 220 | + resp = requests.get(httpserver.url, headers={'User-Agent': 'Test method'}) |
| 221 | + # Without the Transfer-encoding header set, requests does not undo the chunk |
| 222 | + # encoding so it comes through as "raw" chunks |
| 223 | + assert resp.text == '5\r\nTEST!\r\n4\r\ntest\r\n0\r\n\r\n' |
| 224 | + |
| 225 | + |
| 226 | +def _format_chunk(chunk): |
| 227 | + r = repr(chunk) |
| 228 | + if len(r) <= 40: |
| 229 | + return r |
| 230 | + else: |
| 231 | + return r[:13] + '...' + r[-14:] + ' (length {0})'.format(len(chunk)) |
| 232 | + |
| 233 | + |
| 234 | +def _compare_chunks(expected, actual): |
| 235 | + __tracebackhide__ = True |
| 236 | + if expected != actual: |
| 237 | + message = [_format_chunk(expected) + ' != ' + _format_chunk(actual)] |
| 238 | + if type(expected) == type(actual): |
| 239 | + for i, (e, a) in enumerate(itertools.zip_longest(expected, actual, fillvalue='<end>')): |
| 240 | + if e != a: |
| 241 | + message += [ |
| 242 | + ' Chunks differ at index {}:'.format(i), |
| 243 | + ' Expected: ' + (repr(expected[i:i+5]) + '...' if e != '<end>' else '<end>'), |
| 244 | + ' Found: ' + (repr(actual[i:i+5]) + '...' if a != '<end>' else '<end>') |
| 245 | + ] |
| 246 | + break |
| 247 | + pytest.fail('\n'.join(message)) |
| 248 | + |
| 249 | + |
| 250 | +@pytest.mark.parametrize('chunk_size', [400, 499, 500, 512, 750, 1024, 4096, 8192]) |
| 251 | +def test_GET_request_large_chunks(httpserver, chunk_size): |
| 252 | + """ |
| 253 | + Test that a response with large chunks comes through correctly |
| 254 | + """ |
| 255 | + body = b'0123456789abcdef' * 1024 # 16 kb total |
| 256 | + # Split body into fixed-size chunks, from https://stackoverflow.com/a/18854817/56541 |
| 257 | + chunks = [body[0 + i:chunk_size + i] for i in range(0, len(body), chunk_size)] |
| 258 | + httpserver.serve_content( |
| 259 | + chunks, |
| 260 | + headers={'Content-type': 'text/plain', 'Transfer-encoding': 'chunked'}, |
| 261 | + chunked=http.Chunked.YES |
| 262 | + ) |
| 263 | + resp = requests.get(httpserver.url, headers={'User-Agent': 'Test method'}, stream=True) |
| 264 | + assert resp.status_code == 200 |
| 265 | + text = b'' |
| 266 | + for original_chunk, received_chunk in itertools.zip_longest(chunks, resp.iter_content(chunk_size=None)): |
| 267 | + _compare_chunks(original_chunk, received_chunk) |
| 268 | + text += received_chunk |
| 269 | + assert text == body |
| 270 | + assert 'chunked' in resp.headers['Transfer-encoding'] |
| 271 | + |
| 272 | + |
| 273 | +@pytest.mark.parametrize('chunked_flag', [http.Chunked.YES, http.Chunked.AUTO]) |
| 274 | +def test_GET_request_chunked_no_content_length(httpserver, chunked_flag): |
| 275 | + """ |
| 276 | + Test that a chunked response does not include a Content-length header |
| 277 | + """ |
| 278 | + httpserver.serve_content( |
| 279 | + ('TEST!', 'test'), |
| 280 | + headers={'Content-type': 'text/plain', 'Transfer-encoding': 'chunked'}, |
| 281 | + chunked=chunked_flag |
| 282 | + ) |
| 283 | + resp = requests.get(httpserver.url, headers={'User-Agent': 'Test method'}) |
| 284 | + assert resp.status_code == 200 |
| 285 | + assert 'Transfer-encoding' in resp.headers |
| 286 | + assert 'Content-length' not in resp.headers |
0 commit comments