1
0
mirror of https://github.com/kevin1024/vcrpy.git synced 2025-12-09 01:03:24 +00:00

Compare commits

..

12 Commits

Author SHA1 Message Date
Ivan Malison
efe6744eda v1.7.3 2015-08-23 18:10:01 -07:00
Ivan 'Goat' Malison
58f4b98f7f Merge pull request #191 from agriffis/trivial-fixes
Trivial cleanups and one bugfix
2015-08-23 13:33:34 -07:00
Aron Griffis
3305f0ca7d Repair a docstring 2015-08-23 16:33:25 -04:00
Aron Griffis
7f02d65dd9 Make hosts_to_ignore a set() earlier for clarity. 2015-08-23 16:00:52 -04:00
Aron Griffis
3e5553c56a Don't drop passed-in before_record_response
This is an actual bugfix. If before_record_response is passed into VCR as
an iterable then it won't be included in filter_functions. This commit
repairs the logic to separate the tests (just as it's already done for
before_record_request).

Also use .extend() rather than looping on .append()
2015-08-23 16:00:52 -04:00
Aron Griffis
a569dd4dc8 Raise KeyError with message instead of print, just like in get_matchers below 2015-08-23 12:37:01 -04:00
Aron Griffis
eb1cdad03a self._before_record_response can never be falsy in Cassette (just like self._before_record_request above it) 2015-08-23 12:37:01 -04:00
Aron Griffis
08bb3bd187 Remove an extra space 2015-08-23 12:37:01 -04:00
Ivan Malison
ae5580c8f9 style changes in test_vcr.py 2015-08-22 18:59:59 -07:00
Ivan Malison
f342f92f03 additional_matchers test 2015-08-22 18:58:12 -07:00
Ivan Malison
be3bf39161 Style changes in vcr/config.py 2015-08-22 18:48:04 -07:00
Ivan Malison
29d37e410a Add additional_matchers to use_cassette
Closes #188.
2015-08-22 18:06:13 -07:00
7 changed files with 131 additions and 50 deletions

View File

@@ -14,17 +14,18 @@ library <https://github.com/vcr/vcr>`__.
What it does
------------
VCR.py simplifies and speeds up tests that make HTTP requests. The first
time you run code that is inside a VCR.py context manager or decorated
function, VCR.py records all HTTP interactions that take place through
the libraries it supports and serializes and writes them to a flat file
(in yaml format by default). This flat file is called a cassette. When
the relevant peice of code is executed again, VCR.py will read the
serialized requests and responses from the aforementioned cassette file,
and intercept any HTTP requests that it recognizes from the original
test run and return responses that corresponded to those requests. This
means that the requests will not actually result in HTTP traffic, which
confers several benefits including:
VCR.py simplifies and speeds up tests that make HTTP requests. The
first time you run code that is inside a VCR.py context manager or
decorated function, VCR.py records all HTTP interactions that take
place through the libraries it supports and serializes and writes them
to a flat file (in yaml format by default). This flat file is called a
cassette. When the relevant peice of code is executed again, VCR.py
will read the serialized requests and responses from the
aforementioned cassette file, and intercept any HTTP requests that it
recognizes from the original test run and return the responses that
corresponded to those requests. This means that the requests will not
actually result in HTTP traffic, which confers several benefits
including:
- The ability to work offline
- Completely deterministic tests
@@ -608,6 +609,9 @@ new API in version 1.0.x
Changelog
---------
- 1.7.3 [#188] ``additional_matchers`` kwarg on ``use_casstte``.
[#191] Actually support passing multiple before_record_request
functions (thanks @agriffis).
- 1.7.2 [#186] Get effective_url in tornado (thanks @mvschaik), [#187]
Set request_time on Response object in tornado (thanks @abhinav).
- 1.7.1 [#183] Patch ``fetch_impl`` instead of the entire HTTPClient

View File

@@ -51,7 +51,7 @@ except Exception:
setup(
name='vcrpy',
version='1.7.2',
version='1.7.3',
description=(
"Automatically mock your HTTP interactions to simplify and "
"speed up testing"

View File

@@ -15,9 +15,11 @@ def test_vcr_use_cassette():
'vcr.cassette.Cassette.load',
return_value=mock.MagicMock(inject=False)
) as mock_cassette_load:
@test_vcr.use_cassette('test')
def function():
pass
assert mock_cassette_load.call_count == 0
function()
assert mock_cassette_load.call_args[1]['record_mode'] is record_mode
@@ -38,9 +40,11 @@ def test_vcr_use_cassette():
def test_vcr_before_record_request_params():
base_path = 'http://httpbin.org/'
def before_record_cb(request):
if request.path != '/get':
return request
test_vcr = VCR(filter_headers=('cookie',), before_record_request=before_record_cb,
ignore_hosts=('www.test.com',), ignore_localhost=True,
filter_query_parameters=('foo',))
@@ -53,8 +57,12 @@ def test_vcr_before_record_request_params():
assert cassette.filter_request(
Request('GET', base_path + '?foo=bar', '',
{'cookie': 'test', 'other': 'fun'})).headers == {'other': 'fun'}
assert cassette.filter_request(Request('GET', base_path + '?foo=bar', '',
{'cookie': 'test', 'other': 'fun'})).headers == {'other': 'fun'}
assert cassette.filter_request(
Request(
'GET', base_path + '?foo=bar', '',
{'cookie': 'test', 'other': 'fun'}
)
).headers == {'other': 'fun'}
assert cassette.filter_request(Request('GET', 'http://www.test.com' + '?foo=bar', '',
{'cookie': 'test', 'other': 'fun'})) is None
@@ -64,6 +72,32 @@ def test_vcr_before_record_request_params():
assert cassette.filter_request(Request('GET', base_path + 'get', '', {})) is not None
def test_vcr_before_record_response_iterable():
# Regression test for #191
request = Request('GET', '/', '', {})
response = object() # just can't be None
# Prevent actually saving the cassette
with mock.patch('vcr.cassette.save_cassette'):
# Baseline: non-iterable before_record_response should work
mock_filter = mock.Mock()
vcr = VCR(before_record_response=mock_filter)
with vcr.use_cassette('test') as cassette:
assert mock_filter.call_count == 0
cassette.append(request, response)
assert mock_filter.call_count == 1
# Regression test: iterable before_record_response should work too
mock_filter = mock.Mock()
vcr = VCR(before_record_response=(mock_filter,))
with vcr.use_cassette('test') as cassette:
assert mock_filter.call_count == 0
cassette.append(request, response)
assert mock_filter.call_count == 1
@pytest.fixture
def random_fixture():
return 1
@@ -103,6 +137,7 @@ def test_custom_patchers():
def test_inject_cassette():
vcr = VCR(inject_cassette=True)
@vcr.use_cassette('test', record_mode='once')
def with_cassette_injected(cassette):
assert cassette.record_mode == 'once'
@@ -117,9 +152,11 @@ def test_inject_cassette():
def test_with_current_defaults():
vcr = VCR(inject_cassette=True, record_mode='once')
@vcr.use_cassette('test', with_current_defaults=False)
def changing_defaults(cassette, checks):
checks(cassette)
@vcr.use_cassette('test', with_current_defaults=True)
def current_defaults(cassette, checks):
checks(cassette)
@@ -141,27 +178,33 @@ def test_with_current_defaults():
def test_cassette_library_dir_with_decoration_and_no_explicit_path():
library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir)
@vcr.use_cassette()
def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'function_name')
function_name()
def test_cassette_library_dir_with_decoration_and_explicit_path():
library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir)
@vcr.use_cassette(path='custom_name')
def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'custom_name')
function_name()
def test_cassette_library_dir_with_decoration_and_super_explicit_path():
library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir)
@vcr.use_cassette(path=os.path.join(library_dir, 'custom_name'))
def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'custom_name')
function_name()
@@ -169,26 +212,32 @@ def test_cassette_library_dir_with_path_transformer():
library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir,
path_transformer=lambda path: path + '.json')
@vcr.use_cassette()
def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'function_name.json')
function_name()
def test_use_cassette_with_no_extra_invocation():
vcr = VCR(inject_cassette=True, cassette_library_dir='/')
@vcr.use_cassette
def function_name(cassette):
assert cassette._path == os.path.join('/', 'function_name')
function_name()
def test_path_transformer():
vcr = VCR(inject_cassette=True, cassette_library_dir='/',
path_transformer=lambda x: x + '_test')
@vcr.use_cassette
def function_name(cassette):
assert cassette._path == os.path.join('/', 'function_name_test')
function_name()
@@ -203,8 +252,25 @@ def test_cassette_name_generator_defaults_to_using_module_function_defined_in():
def test_ensure_suffix():
vcr = VCR(inject_cassette=True, path_transformer=VCR.ensure_suffix('.yaml'))
@vcr.use_cassette
def function_name(cassette):
assert cassette._path == os.path.join(os.path.dirname(__file__),
'function_name.yaml')
function_name()
def test_additional_matchers():
vcr = VCR(match_on=('uri',), inject_cassette=True)
@vcr.use_cassette
def function_defaults(cassette):
assert set(cassette._match_on) == set([vcr.matchers['uri']])
@vcr.use_cassette(additional_matchers=('body',))
def function_additional(cassette):
assert set(cassette._match_on) == set([vcr.matchers['uri'], vcr.matchers['body']])
function_defaults()
function_additional()

View File

@@ -164,7 +164,7 @@ class Cassette(object):
return CassetteContextDecorator.from_args(cls, **kwargs)
def __init__(self, path, serializer=yamlserializer, record_mode='once',
match_on=(uri, method), before_record_request=None,
match_on=(uri, method), before_record_request=None,
before_record_response=None, custom_patches=(),
inject=False):
@@ -210,8 +210,7 @@ class Cassette(object):
request = self._before_record_request(request)
if not request:
return
if self._before_record_response:
response = self._before_record_response(response)
response = self._before_record_response(response)
self.data.append((request, response))
self.dirty = True

View File

@@ -67,10 +67,11 @@ class VCR(object):
try:
serializer = self.serializers[serializer_name]
except KeyError:
print("Serializer {0} doesn't exist or isn't registered".format(
serializer_name
))
raise KeyError
raise KeyError(
"Serializer {0} doesn't exist or isn't registered".format(
serializer_name
)
)
return serializer
def _get_matchers(self, matcher_names):
@@ -117,12 +118,16 @@ class VCR(object):
'cassette_library_dir',
self.cassette_library_dir
)
additional_matchers = kwargs.get('additional_matchers', ())
if cassette_library_dir:
def add_cassette_library_dir(path):
if not path.startswith(cassette_library_dir):
return os.path.join(cassette_library_dir, path)
return path
path_transformer = compose(add_cassette_library_dir, path_transformer)
path_transformer = compose(
add_cassette_library_dir, path_transformer
)
elif not func_path_generator:
# If we don't have a library dir, use the functions
# location to build a full path for cassettes.
@@ -130,12 +135,12 @@ class VCR(object):
merged_config = {
'serializer': self._get_serializer(serializer_name),
'match_on': self._get_matchers(matcher_names),
'match_on': self._get_matchers(
tuple(matcher_names) + tuple(additional_matchers)
),
'record_mode': kwargs.get('record_mode', self.record_mode),
'before_record_request': self._build_before_record_request(kwargs),
'before_record_response': self._build_before_record_response(
kwargs
),
'before_record_response': self._build_before_record_response(kwargs),
'custom_patches': self._custom_patches + kwargs.get(
'custom_patches', ()
),
@@ -153,11 +158,11 @@ class VCR(object):
'before_record_response', self.before_record_response
)
filter_functions = []
if before_record_response and not isinstance(before_record_response,
collections.Iterable):
before_record_response = (before_record_response,)
for function in before_record_response:
filter_functions.append(function)
if before_record_response:
if not isinstance(before_record_response, collections.Iterable):
before_record_response = (before_record_response,)
filter_functions.extend(before_record_response)
def before_record_response(response):
for function in filter_functions:
if response is None:
@@ -178,7 +183,8 @@ class VCR(object):
'filter_post_data_parameters', self.filter_post_data_parameters
)
before_record_request = options.get(
"before_record_request", options.get("before_record", self.before_record_request)
"before_record_request",
options.get("before_record", self.before_record_request)
)
ignore_hosts = options.get(
'ignore_hosts', self.ignore_hosts
@@ -187,28 +193,36 @@ class VCR(object):
'ignore_localhost', self.ignore_localhost
)
if filter_headers:
filter_functions.append(functools.partial(filters.remove_headers,
headers_to_remove=filter_headers))
filter_functions.append(
functools.partial(
filters.remove_headers,
headers_to_remove=filter_headers
)
)
if filter_query_parameters:
filter_functions.append(functools.partial(filters.remove_query_parameters,
query_parameters_to_remove=filter_query_parameters))
filter_functions.append(functools.partial(
filters.remove_query_parameters,
query_parameters_to_remove=filter_query_parameters
))
if filter_post_data_parameters:
filter_functions.append(functools.partial(filters.remove_post_data_parameters,
post_data_parameters_to_remove=filter_post_data_parameters))
filter_functions.append(
functools.partial(
filters.remove_post_data_parameters,
post_data_parameters_to_remove=filter_post_data_parameters
)
)
hosts_to_ignore = list(ignore_hosts)
hosts_to_ignore = set(ignore_hosts)
if ignore_localhost:
hosts_to_ignore.extend(('localhost', '0.0.0.0', '127.0.0.1'))
hosts_to_ignore.update(('localhost', '0.0.0.0', '127.0.0.1'))
if hosts_to_ignore:
hosts_to_ignore = set(hosts_to_ignore)
filter_functions.append(self._build_ignore_hosts(hosts_to_ignore))
if before_record_request:
if not isinstance(before_record_request, collections.Iterable):
before_record_request = (before_record_request,)
for function in before_record_request:
filter_functions.append(function)
filter_functions.extend(before_record_request)
def before_record_request(request):
request = copy.copy(request)
for function in filter_functions:
@@ -216,7 +230,6 @@ class VCR(object):
break
request = function(request)
return request
return before_record_request
@staticmethod
@@ -230,7 +243,7 @@ class VCR(object):
@staticmethod
def _build_path_from_func_using_module(function):
return os.path.join(os.path.dirname(inspect.getfile(function)),
function.__name__)
function.__name__)
def register_serializer(self, name, serializer):
self.serializers[name] = serializer

View File

@@ -3,8 +3,5 @@ class CannotOverwriteExistingCassetteException(Exception):
class UnhandledHTTPRequestError(KeyError):
'''
Raised when a cassette does not c
ontain the request we want
'''
"""Raised when a cassette does not contain the request we want."""
pass

View File

@@ -2,6 +2,8 @@ import json
from six.moves import urllib, xmlrpc_client
from .util import CaseInsensitiveDict, read_body
import logging
log = logging.getLogger(__name__)