diff --git a/tests/unit/test_matchers.py b/tests/unit/test_matchers.py
index d4b32de..5efc507 100644
--- a/tests/unit/test_matchers.py
+++ b/tests/unit/test_matchers.py
@@ -1,5 +1,7 @@
import itertools
+import pytest
+
from vcr import matchers
from vcr import request
@@ -35,36 +37,105 @@ def test_uri_matcher():
assert matched
-def test_body_matcher():
- # raw
- req1 = request.Request('POST', 'http://host.com/', '123', {})
- req2 = request.Request('POST', 'http://another-host.com/', '123', {'Some-Header': 'value'})
- assert matchers.body(req1, req2)
+req1_body = (b"test"
+ b""
+ b"a1"
+ b"b2"
+ b"")
+req2_body = (b"test"
+ b""
+ b"b2"
+ b"a1"
+ b"")
- # application/x-www-form-urlencoded
- req1 = request.Request('POST', 'http://host.com/', 'a=1&b=2', {'Content-Type': 'application/x-www-form-urlencoded'})
- req2 = request.Request('POST', 'http://host.com/', 'b=2&a=1', {'Content-Type': 'application/x-www-form-urlencoded'})
- assert matchers.body(req1, req2)
- # application/json
- req1 = request.Request('POST', 'http://host.com/', '{"a": 1, "b": 2}', {'Content-Type': 'application/json'})
- req2 = request.Request('POST', 'http://host.com/', '{"b": 2, "a": 1}', {'content-type': 'application/json'})
- assert matchers.body(req1, req2)
+@pytest.mark.parametrize("r1, r2", [
+ (
+ request.Request('POST', 'http://host.com/', '123', {}),
+ request.Request('POST', 'http://another-host.com/',
+ '123', {'Some-Header': 'value'})
+ ),
+ (
+ request.Request('POST', 'http://host.com/', 'a=1&b=2',
+ {'Content-Type': 'application/x-www-form-urlencoded'}),
+ request.Request('POST', 'http://host.com/', 'b=2&a=1',
+ {'Content-Type': 'application/x-www-form-urlencoded'})
+ ),
+ (
+ request.Request('POST', 'http://host.com/', '123', {}),
+ request.Request('POST', 'http://another-host.com/', '123', {'Some-Header': 'value'})
+ ),
+ (
+ request.Request(
+ 'POST', 'http://host.com/', 'a=1&b=2',
+ {'Content-Type': 'application/x-www-form-urlencoded'}
+ ),
+ request.Request(
+ 'POST', 'http://host.com/', 'b=2&a=1',
+ {'Content-Type': 'application/x-www-form-urlencoded'}
+ )
+ ),
+ (
+ request.Request(
+ 'POST', 'http://host.com/', '{"a": 1, "b": 2}',
+ {'Content-Type': 'application/json'}
+ ),
+ request.Request(
+ 'POST', 'http://host.com/', '{"b": 2, "a": 1}',
+ {'content-type': 'application/json'}
+ )
+ ),
+ (
+ request.Request(
+ 'POST', 'http://host.com/', req1_body,
+ {'User-Agent': 'xmlrpclib', 'Content-Type': 'text/xml'}
+ ),
+ request.Request(
+ 'POST', 'http://host.com/', req2_body,
+ {'user-agent': 'somexmlrpc', 'content-type': 'text/xml'}
+ )
+ ),
+ (
+ request.Request(
+ 'POST', 'http://host.com/',
+ '{"a": 1, "b": 2}', {'Content-Type': 'application/json'}
+ ),
+ request.Request(
+ 'POST', 'http://host.com/',
+ '{"b": 2, "a": 1}', {'content-type': 'application/json'}
+ )
+ )
+])
+def test_body_matcher_does_match(r1, r2):
+ assert matchers.body(r1, r2)
- # xmlrpc
- req1_body = (b"test"
- b""
- b"a1"
- b"b2"
- b"")
- req2_body = (b"test"
- b""
- b"b2"
- b"a1"
- b"")
- req1 = request.Request('POST', 'http://host.com/', req1_body, {'User-Agent': 'xmlrpclib', 'Content-Type': 'text/xml'})
- req2 = request.Request('POST', 'http://host.com/', req2_body, {'user-agent': 'somexmlrpc', 'content-type': 'text/xml'})
- assert matchers.body(req1, req2)
+
+@pytest.mark.parametrize("r1, r2", [
+ (
+ request.Request('POST', 'http://host.com/', '{"a": 1, "b": 2}', {}),
+ request.Request('POST', 'http://host.com/', '{"b": 2, "a": 1}', {}),
+ ),
+ (
+ request.Request(
+ 'POST', 'http://host.com/',
+ '{"a": 1, "b": 3}', {'Content-Type': 'application/json'}
+ ),
+ request.Request(
+ 'POST', 'http://host.com/',
+ '{"b": 2, "a": 1}', {'content-type': 'application/json'}
+ )
+ ),
+ (
+ request.Request(
+ 'POST', 'http://host.com/', req1_body, {'Content-Type': 'text/xml'}
+ ),
+ request.Request(
+ 'POST', 'http://host.com/', req2_body, {'content-type': 'text/xml'}
+ )
+ )
+])
+def test_body_match_does_not_match(r1, r2):
+ assert not matchers.body(r1, r2)
def test_query_matcher():
diff --git a/vcr/matchers.py b/vcr/matchers.py
index 39c5949..34bd4e6 100644
--- a/vcr/matchers.py
+++ b/vcr/matchers.py
@@ -37,19 +37,39 @@ def raw_body(r1, r2):
return read_body(r1) == read_body(r2)
+def _header_checker(value, header='Content-Type'):
+ def checker(headers):
+ return value in headers.get(header, '').lower()
+ return checker
+
+
+_xml_header_checker = _header_checker('text/xml')
+_xmlrpc_header_checker = _header_checker('xmlrpc', header='User-Agent')
+_checker_transformer_pairs = (
+ (_header_checker('application/x-www-form-urlencoded'), urllib.parse.parse_qs),
+ (_header_checker('application/json'), json.loads),
+ (lambda request: _xml_header_checker(request) and _xmlrpc_header_checker(request), xmlrpc_client.loads),
+)
+
+
+def _identity(x):
+ return x
+
+
+def _get_transformer(request):
+ headers = CaseInsensitiveDict(request.headers)
+ for checker, transformer in _checker_transformer_pairs:
+ if checker(headers): return transformer
+ else:
+ return _identity
+
+
def body(r1, r2):
- r1_body = read_body(r1)
- r2_body = read_body(r2)
- r1_headers = CaseInsensitiveDict(r1.headers)
- r2_headers = CaseInsensitiveDict(r2.headers)
- if r1_headers.get('Content-Type') == r2_headers.get('Content-Type') == 'application/x-www-form-urlencoded':
- return urllib.parse.parse_qs(r1_body) == urllib.parse.parse_qs(r2_body)
- if r1_headers.get('Content-Type') == r2_headers.get('Content-Type') == 'application/json':
- return json.loads(r1_body) == json.loads(r2_body)
- if ('xmlrpc' in r1_headers.get('User-Agent', '') and 'xmlrpc' in r2_headers.get('User-Agent', '') and
- r1_headers.get('Content-Type') == r2_headers.get('Content-Type') == 'text/xml'):
- return xmlrpc_client.loads(r1_body) == xmlrpc_client.loads(r2_body)
- return r1_body == r2_body
+ transformer = _get_transformer(r1)
+ r2_transformer = _get_transformer(r2)
+ if transformer != r2_transformer:
+ transformer = _identity
+ return transformer(read_body(r1)) == transformer(read_body(r2))
def headers(r1, r2):