Skip to content

Commit e194542

Browse files
committed
Add tests for chunked encoding
This commit adds several test cases that confirm the server is able to send data using the chunked transfer encoding, that it correctly does or doesn't apply chunking depending on the value of the chunked attribute and the Transfer-encoding header, that the Content-length header is correctly omitted when sending a chunked response, and that various combinations of content size and chunk size work correctly.
1 parent 5a54e66 commit e194542

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed

tests/test_http.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import itertools
2+
import pytest
13
import requests
24

35
from pytest_localserver import http, plugin
@@ -8,6 +10,9 @@
810
httpserver = plugin.httpserver
911

1012

13+
transfer_encoded = pytest.mark.parametrize('transfer_encoding_header', ['Transfer-encoding', 'Transfer-Encoding', 'transfer-encoding', 'TRANSFER-ENCODING'])
14+
15+
1116
def test_httpserver_funcarg(httpserver):
1217
assert isinstance(httpserver, http.ContentServer)
1318
assert httpserver.is_alive()
@@ -76,3 +81,206 @@ def test_HEAD_request(httpserver):
7681
# resp = requests.post(httpserver.url, data={'data': 'value'}, headers=headers)
7782
# assert resp.json() == {'data': 'value'}
7883
# 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

Comments
 (0)