From 7a5795a5476bf843b883b5983559a2fd5fd815f3 Mon Sep 17 00:00:00 2001 From: Matt Thomson Date: Sat, 6 Sep 2014 16:32:29 +0100 Subject: [PATCH 1/3] Add test to demonstrate Python 3 multiple headers bug. --- tests/integration/test_stubs.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/integration/test_stubs.py b/tests/integration/test_stubs.py index 941c6c5..15918d2 100644 --- a/tests/integration/test_stubs.py +++ b/tests/integration/test_stubs.py @@ -24,3 +24,19 @@ def test_case_insensitivity(tmpdir): # behavior should be the same both inside and outside assert outside == inside == inside2 + +def _multiple_header_value(httpserver): + conn = httplib.HTTPConnection('%s:%s' % httpserver.server_address) + conn.request('GET', "/") + r = conn.getresponse() + return r.getheader('foo') + +def test_multiple_headers(tmpdir, httpserver): + testfile = str(tmpdir.join('multiple_headers.yaml')) + httpserver.serve_content('Hello!', headers=[('foo', 'bar'), ('foo', 'baz')]) + outside = _multiple_header_value(httpserver) + + with vcr.use_cassette(testfile): + inside = _multiple_header_value(httpserver) + + assert outside == inside From 3c410b5f9d3539a8df9d5ee3315c4a084523a692 Mon Sep 17 00:00:00 2001 From: Matt Thomson Date: Sat, 6 Sep 2014 16:57:12 +0100 Subject: [PATCH 2/3] Don't write header values multiple times. On Python 3, response.msg.keys() contains the same value multiple times if there are multiple headers with the same value. Work around this by converting to a set before iterating over it. --- tests/integration/test_request.py | 11 +++++++++++ vcr/stubs/compat.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_request.py b/tests/integration/test_request.py index 0e01674..cbb6242 100644 --- a/tests/integration/test_request.py +++ b/tests/integration/test_request.py @@ -9,3 +9,14 @@ def test_recorded_request_uri_with_redirected_request(tmpdir): assert cass.requests[0].uri == 'http://httpbin.org/redirect/3' assert cass.requests[3].uri == 'http://httpbin.org/get' assert len(cass) == 4 + + +def test_records_multiple_header_values(tmpdir, httpserver): + httpserver.serve_content('Hello!', headers=[('foo', 'bar'), ('foo', 'baz')]) + + with vcr.use_cassette(str(tmpdir.join('test.yml'))) as cass: + assert len(cass) == 0 + + urlopen(httpserver.url) + assert len(cass) == 1 + assert cass.responses[0]['headers']['foo'] == ['bar', 'baz'] diff --git a/vcr/stubs/compat.py b/vcr/stubs/compat.py index 6b65ca0..13f30a0 100644 --- a/vcr/stubs/compat.py +++ b/vcr/stubs/compat.py @@ -28,7 +28,7 @@ def get_header_items(message): def get_headers(response): - for key in response.msg.keys(): + for key in set(response.msg.keys()): if six.PY3: yield key, response.msg.get_all(key) else: From f719f90e63d026aec98ad7408089cf73e175db7e Mon Sep 17 00:00:00 2001 From: Matt Thomson Date: Sat, 6 Sep 2014 17:15:15 +0100 Subject: [PATCH 3/3] Fix multiple header behaviour. Join multiple header values together, rather than losing duplicates with a dict. --- vcr/stubs/__init__.py | 12 ++++++++---- vcr/stubs/compat.py | 15 +++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/vcr/stubs/__init__.py b/vcr/stubs/__init__.py index 0922e33..45af7ea 100644 --- a/vcr/stubs/__init__.py +++ b/vcr/stubs/__init__.py @@ -57,7 +57,7 @@ def parse_headers(header_list): def serialize_headers(response): out = {} - for key, values in compat.get_headers(response): + for key, values in compat.get_headers(response.msg): out.setdefault(key, []) out[key].extend(values) return out @@ -108,11 +108,15 @@ class VCRHTTPResponse(HTTPResponse): def getheaders(self): message = parse_headers(self.recorded_response['headers']) - return compat.get_header_items(message) + return list(compat.get_header_items(message)) def getheader(self, header, default=None): - headers = dict(((k.lower(), v) for k, v in self.getheaders())) - return headers.get(header.lower(), default) + values = [v for (k, v) in self.getheaders() if k.lower() == header.lower()] + + if values: + return ', '.join(values) + else: + return default class VCRConnection: diff --git a/vcr/stubs/compat.py b/vcr/stubs/compat.py index 13f30a0..d640ac4 100644 --- a/vcr/stubs/compat.py +++ b/vcr/stubs/compat.py @@ -21,18 +21,17 @@ def get_header(message, name): def get_header_items(message): - if six.PY3: - return dict(message._headers).items() - else: - return message.dict.items() + for (key, values) in get_headers(message): + for value in values: + yield key, value -def get_headers(response): - for key in set(response.msg.keys()): +def get_headers(message): + for key in set(message.keys()): if six.PY3: - yield key, response.msg.get_all(key) + yield key, message.get_all(key) else: - yield key, response.msg.getheaders(key) + yield key, message.getheaders(key) def get_httpmessage(headers):