mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-08 16:53:23 +00:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffd2142d86 | ||
|
|
8e78666b8a | ||
|
|
d843d2a6c1 | ||
|
|
0d623aead5 | ||
|
|
f263c60d78 | ||
|
|
2eba71092d | ||
|
|
dd277d7a30 | ||
|
|
dc9fbef3f0 | ||
|
|
f31be550bd | ||
|
|
baadf913ef | ||
|
|
1eec0f657d | ||
|
|
c5c120e91b | ||
|
|
7caf29735a | ||
|
|
75969de601 | ||
|
|
78e21aa220 | ||
|
|
1b565d3f88 | ||
|
|
cc752cf790 | ||
|
|
caa3a67bde | ||
|
|
e3b7116564 | ||
|
|
9d37210fc8 | ||
|
|
857488ee3a | ||
|
|
9fb8a7b0ba | ||
|
|
77581437f7 | ||
|
|
759423a75a | ||
|
|
988ac335d4 | ||
|
|
200262fb5c | ||
|
|
8c54ae9826 | ||
|
|
2d96d35621 | ||
|
|
6ca7cf3cc6 | ||
|
|
5e76e4733d | ||
|
|
14d1454bbf | ||
|
|
4daafcc687 | ||
|
|
347026f42c | ||
|
|
8297d65038 | ||
|
|
792c5c4558 | ||
|
|
2fa2ca2072 | ||
|
|
26a4b8df55 | ||
|
|
41e174ccb6 | ||
|
|
a17624a464 | ||
|
|
bbab27ed1b | ||
|
|
b92be4e9e5 | ||
|
|
c6e7cb12c6 | ||
|
|
d07915ccf6 | ||
|
|
7c14d81ab1 | ||
|
|
d682e7b19a | ||
|
|
2b9498e009 | ||
|
|
eb99a3e36f | ||
|
|
6c877a1749 | ||
|
|
6be6f0236b | ||
|
|
95c7898b65 | ||
|
|
d4b706334c | ||
|
|
e8a9a65bef | ||
|
|
67b03b45c3 | ||
|
|
6c41b8b723 | ||
|
|
cc55ef5b35 | ||
|
|
1b6f304421 | ||
|
|
9039eab916 | ||
|
|
92e03603ea | ||
|
|
f8e8b85790 | ||
|
|
09ed0e911e | ||
|
|
0830f6052b | ||
|
|
829e9b2f1c | ||
|
|
b203fd4113 | ||
|
|
f414e04f49 | ||
|
|
28d9899b9b | ||
|
|
396c4354e8 | ||
|
|
0a01f0fb51 | ||
|
|
46f5b8a187 | ||
|
|
940dec1dd6 | ||
|
|
de244a968f | ||
|
|
728dc71a35 | ||
|
|
bdb74b9841 | ||
|
|
34f0417dc9 | ||
|
|
86586e8cd9 | ||
|
|
7724b364aa | ||
|
|
c4803dbc4d | ||
|
|
a53121b645 | ||
|
|
78a0a52bd9 | ||
|
|
18977a85d1 | ||
|
|
fb84928ef6 | ||
|
|
0b4d92c277 | ||
|
|
8f4e089200 | ||
|
|
7670e10bc2 | ||
|
|
dc174c3250 | ||
|
|
114fcd29b4 | ||
|
|
20e8f4ad41 | ||
|
|
4e990db32e | ||
|
|
472bc3aea1 | ||
|
|
c74a857aa4 | ||
|
|
c3705dae9f | ||
|
|
6c166482d9 | ||
|
|
cc9fabf2d9 | ||
|
|
f77442d87b | ||
|
|
602112cd87 | ||
|
|
4ef5205094 | ||
|
|
0d2f49fe8a | ||
|
|
8fdc6dbb68 | ||
|
|
ffc4dca502 | ||
|
|
302ea35d9a | ||
|
|
895850b197 | ||
|
|
a9e75a545e |
7
.codecov.yml
Normal file
7
.codecov.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 75
|
||||
# Allow 0% coverage regression
|
||||
threshold: 0
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,8 +6,12 @@ build/
|
||||
dist/
|
||||
*.egg/
|
||||
.coverage
|
||||
coverage.xml
|
||||
htmlcov/
|
||||
*.egg-info/
|
||||
pytestdebug.log
|
||||
pip-wheel-metadata/
|
||||
.python-version
|
||||
|
||||
fixtures/
|
||||
/docs/_build
|
||||
|
||||
81
.travis.yml
81
.travis.yml
@@ -1,73 +1,44 @@
|
||||
dist: xenial
|
||||
language: python
|
||||
sudo: false
|
||||
before_install: openssl version
|
||||
env:
|
||||
global:
|
||||
- secure: AifoKzwhjV94cmcQZrdQmqRu/9rkZZvWpwBv1daeAQpLOKFPGsOm3D+x2cSw9+iCfkgDZDfqQVv1kCaFVxTll8v8jTq5SJdqEY0NmGWbj/UkNtShh609oRDsuzLxAEwtVKYjf/h8K2BRea+bl1tGkwZ2vtmYS6dxNlAijjWOfds=
|
||||
- secure: LBSEg/gMj4u4Hrpo3zs6Y/1mTpd2RtcN49mZIFgTdbJ9IhpiNPqcEt647Lz94F9Eses2x2WbNuKqZKZZReY7QLbEzU1m0nN5jlaKrjcG5NR5clNABfFFyhgc0jBikyS4abAG8jc2efeaTrFuQwdoF4sE8YiVrkiVj2X5Xoi6sBk=
|
||||
# global:
|
||||
# - secure: AifoKzwhjV94cmcQZrdQmqRu/9rkZZvWpwBv1daeAQpLOKFPGsOm3D+x2cSw9+iCfkgDZDfqQVv1kCaFVxTll8v8jTq5SJdqEY0NmGWbj/UkNtShh609oRDsuzLxAEwtVKYjf/h8K2BRea+bl1tGkwZ2vtmYS6dxNlAijjWOfds=
|
||||
# - secure: LBSEg/gMj4u4Hrpo3zs6Y/1mTpd2RtcN49mZIFgTdbJ9IhpiNPqcEt647Lz94F9Eses2x2WbNuKqZKZZReY7QLbEzU1m0nN5jlaKrjcG5NR5clNABfFFyhgc0jBikyS4abAG8jc2efeaTrFuQwdoF4sE8YiVrkiVj2X5Xoi6sBk=
|
||||
matrix:
|
||||
- TOX_SUFFIX="flakes"
|
||||
- TOX_SUFFIX="requests27"
|
||||
- TOX_SUFFIX="requests"
|
||||
- TOX_SUFFIX="httplib2"
|
||||
- TOX_SUFFIX="boto3"
|
||||
- TOX_SUFFIX="urllib3121"
|
||||
- TOX_SUFFIX="urllib3"
|
||||
- TOX_SUFFIX="tornado4"
|
||||
- TOX_SUFFIX="aiohttp"
|
||||
matrix:
|
||||
include:
|
||||
- env: TOX_SUFFIX="flakes"
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- env: TOX_SUFFIX="requests27"
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- env: TOX_SUFFIX="httplib2"
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- env: TOX_SUFFIX="urllib3121"
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- env: TOX_SUFFIX="tornado4"
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- env: TOX_SUFFIX="aiohttp"
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
# Only run lint on a single 3.x
|
||||
- env: TOX_SUFFIX="lint"
|
||||
python: "3.7"
|
||||
allow_failures:
|
||||
- env: TOX_SUFFIX="boto3"
|
||||
- env: TOX_SUFFIX="aiohttp"
|
||||
python: "pypy3.5-5.9.0"
|
||||
- env: TOX_SUFFIX="aiohttp"
|
||||
python: 3.4
|
||||
python: "pypy3"
|
||||
- python: "3.8-dev"
|
||||
exclude:
|
||||
# Only run flakes on a single Python 2.x and a single 3.x
|
||||
- env: TOX_SUFFIX="flakes"
|
||||
python: 3.4
|
||||
- env: TOX_SUFFIX="flakes"
|
||||
python: 3.5
|
||||
- env: TOX_SUFFIX="flakes"
|
||||
python: pypy
|
||||
- env: TOX_SUFFIX="flakes"
|
||||
python: "pypy3.5-5.9.0"
|
||||
# Exclude aiohttp support
|
||||
- env: TOX_SUFFIX="aiohttp"
|
||||
python: 2.7
|
||||
python: "2.7"
|
||||
- env: TOX_SUFFIX="aiohttp"
|
||||
python: pypy
|
||||
python: "pypy"
|
||||
python:
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- pypy
|
||||
- "pypy3.5-5.9.0"
|
||||
- "2.7"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8-dev"
|
||||
- "pypy"
|
||||
- "pypy3"
|
||||
install:
|
||||
- pip install tox-travis
|
||||
- if [[ $TOX_SUFFIX != 'flakes' ]]; then python setup.py install ; fi
|
||||
- pip install tox-travis codecov
|
||||
- if [[ $TOX_SUFFIX != 'lint' ]]; then python setup.py install ; fi
|
||||
script:
|
||||
- tox -e "${TOX_SUFFIX}"
|
||||
- tox -e "${TOX_SUFFIX}"
|
||||
after_success:
|
||||
- codecov
|
||||
|
||||
10
README.rst
10
README.rst
@@ -1,4 +1,4 @@
|
||||
|PyPI| |Python versions| |Build Status| |Waffle Ready| |Gitter|
|
||||
|PyPI| |Python versions| |Build Status| |CodeCov| |Gitter| |CodeStyleBlack|
|
||||
|
||||
VCR.py
|
||||
======
|
||||
@@ -53,8 +53,12 @@ more details
|
||||
:target: https://pypi.python.org/pypi/vcrpy
|
||||
.. |Build Status| image:: https://secure.travis-ci.org/kevin1024/vcrpy.svg?branch=master
|
||||
:target: http://travis-ci.org/kevin1024/vcrpy
|
||||
.. |Waffle Ready| image:: https://badge.waffle.io/kevin1024/vcrpy.svg?label=ready&title=waffle
|
||||
:target: https://waffle.io/kevin1024/vcrpy
|
||||
.. |Gitter| image:: https://badges.gitter.im/Join%20Chat.svg
|
||||
:alt: Join the chat at https://gitter.im/kevin1024/vcrpy
|
||||
:target: https://gitter.im/kevin1024/vcrpy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
.. |CodeCov| image:: https://codecov.io/gh/kevin1024/vcrpy/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/kevin1024/vcrpy
|
||||
:alt: Code Coverage Status
|
||||
.. |CodeStyleBlack| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
:alt: Code Style: black
|
||||
|
||||
@@ -97,8 +97,12 @@ Create your own method with the following signature
|
||||
|
||||
def my_matcher(r1, r2):
|
||||
|
||||
Your method receives the two requests and must return ``True`` if they
|
||||
match, ``False`` if they don't.
|
||||
Your method receives the two requests and can either:
|
||||
|
||||
- Use an ``assert`` statement: return None if they match and raise ``AssertionError`` if not.
|
||||
- Return a boolean: ``True`` if they match, ``False`` if not.
|
||||
|
||||
Note: in order to have good feedback when a matcher fails, we recommend using an ``assert`` statement with a clear error message.
|
||||
|
||||
Finally, register your method with VCR to use your new request matcher.
|
||||
|
||||
@@ -107,7 +111,8 @@ Finally, register your method with VCR to use your new request matcher.
|
||||
import vcr
|
||||
|
||||
def jurassic_matcher(r1, r2):
|
||||
return r1.uri == r2.uri and 'JURASSIC PARK' in r1.body
|
||||
assert r1.uri == r2.uri and 'JURASSIC PARK' in r1.body, \
|
||||
'required string (JURASSIC PARK) not found in request body'
|
||||
|
||||
my_vcr = vcr.VCR()
|
||||
my_vcr.register_matcher('jurassic', jurassic_matcher)
|
||||
@@ -221,24 +226,25 @@ Custom Request filtering
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If none of these covers your request filtering needs, you can register a
|
||||
callback that will manipulate the HTTP request before adding it to the
|
||||
cassette. Use the ``before_record_request`` configuration option to so this.
|
||||
Here is an example that will never record requests to the /login
|
||||
endpoint.
|
||||
callback with the ``before_record_request`` configuration option to
|
||||
manipulate the HTTP request before adding it to the cassette, or return
|
||||
``None`` to ignore it entirely. Here is an example that will never record
|
||||
requests to the ``'/login'`` path:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def before_record_cb(request):
|
||||
if request.path != '/login':
|
||||
return request
|
||||
if request.path == '/login':
|
||||
return None
|
||||
return request
|
||||
|
||||
my_vcr = vcr.VCR(
|
||||
before_record_request = before_record_cb,
|
||||
before_record_request=before_record_cb,
|
||||
)
|
||||
with my_vcr.use_cassette('test.yml'):
|
||||
# your http code here
|
||||
|
||||
You can also mutate the response using this callback. For example, you
|
||||
You can also mutate the request using this callback. For example, you
|
||||
could remove all query parameters from any requests to the ``'/login'``
|
||||
path.
|
||||
|
||||
@@ -246,7 +252,7 @@ path.
|
||||
|
||||
def scrub_login_request(request):
|
||||
if request.path == '/login':
|
||||
request.uri, _ = urllib.splitquery(response.uri)
|
||||
request.uri, _ = urllib.splitquery(request.uri)
|
||||
return request
|
||||
|
||||
my_vcr = vcr.VCR(
|
||||
@@ -258,9 +264,12 @@ path.
|
||||
Custom Response Filtering
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
VCR.py also suports response filtering with the
|
||||
``before_record_response`` keyword argument. It's usage is similar to
|
||||
that of ``before_record``:
|
||||
You can also do response filtering with the
|
||||
``before_record_response`` configuration option. Its usage is
|
||||
similar to the above ``before_record_request`` - you can
|
||||
mutate the response, or return ``None`` to avoid recording
|
||||
the request and response altogether. For example to hide
|
||||
sensitive data from the request body:
|
||||
|
||||
.. code:: python
|
||||
|
||||
@@ -302,8 +311,8 @@ in a few ways:
|
||||
or 0.0.0.0.
|
||||
- Set the ``ignore_hosts`` configuration option to a list of hosts to
|
||||
ignore
|
||||
- Add a ``before_record`` callback that returns None for requests you
|
||||
want to ignore
|
||||
- Add a ``before_record_request`` or ``before_record_response`` callback
|
||||
that returns ``None`` for requests you want to ignore (see above).
|
||||
|
||||
Requests that are ignored by VCR will not be saved in a cassette, nor
|
||||
played back from a cassette. VCR will completely ignore those requests
|
||||
@@ -364,3 +373,16 @@ cassette names, use ``VCR.ensure_suffix`` as follows:
|
||||
|
||||
@my_vcr.use_cassette
|
||||
def my_test_function():
|
||||
|
||||
Rewind Cassette
|
||||
---------------
|
||||
|
||||
VCR.py allows to rewind a cassette in order to replay it inside the same function/test.
|
||||
|
||||
.. code:: python
|
||||
|
||||
with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml') as cass:
|
||||
response = urllib2.urlopen('http://www.zombo.com/').read()
|
||||
assert cass.all_played
|
||||
a.rewind()
|
||||
assert not cass.all_played
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
Changelog
|
||||
---------
|
||||
- 2.1.x (UNRELEASED)
|
||||
- ....
|
||||
- 2.1.1
|
||||
- Format code with black (@neozenith)
|
||||
- Use latest pypy3 in Travis (@hugovk)
|
||||
- Improve documentation about custom matchers (@gward)
|
||||
- Fix exception when body is empty (@keithprickett)
|
||||
- Add `pytest-recording` to the documentation as an alternative Pytest plugin (@Stranger6667)
|
||||
- Fix yarl and python3.5 version issue (@neozenith)
|
||||
- Fix header matcher for boto3 - fixes #474 (@simahawk)
|
||||
- 2.1.0 - Add a `rewind` method to reset a cassette (thanks @khamidou)
|
||||
New error message with more details on why the cassette failed to play a request (thanks @arthurHamon2, @neozenith)
|
||||
Handle connect tunnel URI (thanks @jeking3)
|
||||
Add code coverage to the project (thanks @neozenith)
|
||||
Drop support to python 3.4
|
||||
Add deprecation warning on python 2.7, next major release will drop python 2.7 support
|
||||
Fix build problems on requests tests (thanks to @dunossauro)
|
||||
Fix matching on 'body' failing when Unicode symbols are present in them (thanks @valgur)
|
||||
Fix bugs on aiohttp integration (thanks @graingert, @steinnes, @stj, @lamenezes, @lmazuel)
|
||||
Fix Biopython incompatibility (thanks @rishab121)
|
||||
Fix Boto3 integration (thanks @1oglop1, @arthurHamon2)
|
||||
- 2.0.1 - Fix bug when using vcrpy with python 3.4
|
||||
- 2.0.0 - Support python 3.7 (fix httplib2 and urllib2, thanks @felixonmars)
|
||||
[#356] Fixes `before_record_response` so the original response isn't changed (thanks @kgraves)
|
||||
|
||||
165
docs/conf.py
165
docs/conf.py
@@ -17,50 +17,45 @@ import os
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.viewcode',
|
||||
]
|
||||
extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.coverage", "sphinx.ext.viewcode"]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u'vcrpy'
|
||||
copyright = u'2015, Kevin McCarthy'
|
||||
author = u'Kevin McCarthy'
|
||||
project = u"vcrpy"
|
||||
copyright = u"2015, Kevin McCarthy"
|
||||
author = u"Kevin McCarthy"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.7.4'
|
||||
version = "1.7.4"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.7.4'
|
||||
release = "1.7.4"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -71,37 +66,37 @@ language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
# keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
@@ -111,156 +106,148 @@ todo_include_todos = False
|
||||
|
||||
# The theme to use for HTML and HTML Help pages.
|
||||
# https://read-the-docs.readthedocs.io/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs
|
||||
if 'READTHEDOCS' not in os.environ:
|
||||
if "READTHEDOCS" not in os.environ:
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
# html_theme_options = {}
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
# html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
# html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
#html_search_language = 'en'
|
||||
# html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
#html_search_options = {'type': 'default'}
|
||||
# html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#html_search_scorer = 'scorer.js'
|
||||
# html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'vcrpydoc'
|
||||
htmlhelp_basename = "vcrpydoc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'vcrpy.tex', u'vcrpy Documentation',
|
||||
u'Kevin McCarthy', 'manual'),
|
||||
]
|
||||
latex_documents = [(master_doc, "vcrpy.tex", u"vcrpy Documentation", u"Kevin McCarthy", "manual")]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'vcrpy', u'vcrpy Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
man_pages = [(master_doc, "vcrpy", u"vcrpy Documentation", [author], 1)]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
@@ -269,23 +256,29 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'vcrpy', u'vcrpy Documentation',
|
||||
author, 'vcrpy', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
master_doc,
|
||||
"vcrpy",
|
||||
u"vcrpy Documentation",
|
||||
author,
|
||||
"vcrpy",
|
||||
"One line description of project.",
|
||||
"Miscellaneous",
|
||||
)
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
# texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'https://docs.python.org/': None}
|
||||
intersphinx_mapping = {"https://docs.python.org/": None}
|
||||
|
||||
@@ -11,7 +11,10 @@ yourself using `py.test <http://pytest.org/>`__ and
|
||||
all environments VCR.py supports. The test suite is pretty big and slow,
|
||||
but you can tell tox to only run specific tests like this::
|
||||
|
||||
tox -e py27requests -- -v -k "'test_status_code or test_gzip'"
|
||||
tox -e {pyNN}-{HTTP_LIBRARY} -- <pytest flags passed through>
|
||||
|
||||
tox -e py27-requests -- -v -k "'test_status_code or test_gzip'"
|
||||
tox -e py37-requests -- -v --last-failed
|
||||
|
||||
This will run only tests that look like ``test_status_code`` or
|
||||
``test_gzip`` in the test suite, and only in the python 2.7 environment
|
||||
@@ -23,3 +26,56 @@ documentation <https://boto.readthedocs.io/en/latest/getting_started.html>`__
|
||||
for how to set this up. I have marked the boto tests as optional in
|
||||
Travis so you don't have to worry about them failing if you submit a
|
||||
pull request.
|
||||
|
||||
Using PyEnv with VCR's test suite
|
||||
---------------------------------
|
||||
|
||||
PyEnv is a tool for managing multiple installation of python on your system.
|
||||
See the full documentation at their `github <https://github.com/pyenv/pyenv>`
|
||||
but we are also going to use `tox-pyenv <https://pypi.org/project/tox-pyenv/>`
|
||||
in this example::
|
||||
|
||||
git clone https://github.com/pyenv/pyenv ~/.pyenv
|
||||
|
||||
# Add ~/.pyenv/bin to your PATH
|
||||
export PATH="$PATH:~/.pyenv/bin"
|
||||
|
||||
# Setup shim paths
|
||||
eval "$(pyenv init -)"
|
||||
|
||||
# Setup your local system tox tooling
|
||||
pip install tox tox-pyenv
|
||||
|
||||
# Install supported versions (at time of writing), this does not activate them
|
||||
pyenv install 2.7.10 3.5.7 3.6.9 3.7.4 3.8-dev pypy2.6-7.1.1 pypy3.6-7.1.1
|
||||
|
||||
# This activates them
|
||||
pyenv local 2.7.10 3.5.7 3.6.9 3.7.4 3.8-dev pypy2.6-7.1.1 pypy3.6-7.1.1
|
||||
|
||||
# Run the whole test suite
|
||||
tox
|
||||
|
||||
# Run the whole test suite or just part of it
|
||||
tox -e lint
|
||||
tox -e py37-requests
|
||||
|
||||
|
||||
Troubleshooting on MacOSX
|
||||
-------------------------
|
||||
|
||||
If you have this kind of error when running tox :
|
||||
|
||||
.. code:: python
|
||||
|
||||
__main__.ConfigurationError: Curl is configured to use SSL, but we have
|
||||
not been able to determine which SSL backend it is using. Please see PycURL documentation for how to specify the SSL backend manually.
|
||||
|
||||
Then you need to define some environment variables:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
export PYCURL_SSL_LIBRARY=openssl
|
||||
export LDFLAGS=-L/usr/local/opt/openssl/lib
|
||||
export CPPFLAGS=-I/usr/local/opt/openssl/include
|
||||
|
||||
Reference : `stackoverflow issue <https://stackoverflow.com/questions/51019622/curl-is-configured-to-use-ssl-but-we-have-not-been-able-to-determine-which-ssl>`__
|
||||
|
||||
@@ -29,3 +29,29 @@ The second time, you will see::
|
||||
If you set the loglevel to DEBUG, you will also get information about
|
||||
which matchers didn't match. This can help you with debugging custom
|
||||
matchers.
|
||||
|
||||
CannotOverwriteExistingCassetteException
|
||||
========================================
|
||||
|
||||
When a request failed to be found in an existing cassette,
|
||||
VCR.py tries to get the request(s) that may be similar to the one being searched.
|
||||
The goal is to see which matcher(s) failed and understand what part of the failed request may have changed.
|
||||
It can return multiple similar requests with :
|
||||
|
||||
- the matchers that have succeeded
|
||||
- the matchers that have failed
|
||||
- for each failed matchers, why it has failed with an assertion message
|
||||
|
||||
CannotOverwriteExistingCassetteException message example :
|
||||
|
||||
.. code:: python
|
||||
|
||||
CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('cassette.yaml') in your current record mode ('once').
|
||||
No match for the request (<Request (GET) https://www.googleapis.com/?alt=json&maxResults=200>) was found.
|
||||
Found 1 similar requests with 1 different matchers :
|
||||
|
||||
1 - (<Request (GET) https://www.googleapis.com/?alt=json&maxResults=500>).
|
||||
Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
|
||||
Matchers failed :
|
||||
query - assertion failure :
|
||||
[('alt', 'json'), ('maxResults', '200')] != [('alt', 'json'), ('maxResults', '500')]
|
||||
@@ -9,7 +9,7 @@ with pip::
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
VCR.py supports Python 2.7 and 3.4+, and
|
||||
VCR.py supports Python 2.7 and 3.5+, and
|
||||
`pypy <http://pypy.org>`__.
|
||||
|
||||
The following HTTP libraries are supported:
|
||||
|
||||
@@ -11,7 +11,7 @@ Usage
|
||||
assert 'Example domains' in response
|
||||
|
||||
Run this test once, and VCR.py will record the HTTP request to
|
||||
``fixtures/vcr_cassettes/synopsis.yml``. Run it again, and VCR.py will
|
||||
``fixtures/vcr_cassettes/synopsis.yaml``. Run it again, and VCR.py will
|
||||
replay the response from iana.org when the http request is made. This
|
||||
test is now fast (no real HTTP requests are made anymore), deterministic
|
||||
(the test will continue to pass, even if you are offline, or iana.org
|
||||
@@ -95,3 +95,12 @@ Unittest Integration
|
||||
While it's possible to use the context manager or decorator forms with unittest,
|
||||
there's also a ``VCRTestCase`` provided separately by `vcrpy-unittest
|
||||
<https://github.com/agriffis/vcrpy-unittest>`__.
|
||||
|
||||
Pytest Integration
|
||||
------------------
|
||||
|
||||
A Pytest plugin is available here : `pytest-vcr
|
||||
<https://github.com/ktosiek/pytest-vcr>`__.
|
||||
|
||||
Alternative plugin, that also provides network access blocking: `pytest-recording
|
||||
<https://github.com/kiwicom/pytest-recording>`__.
|
||||
|
||||
2
pyproject.toml
Normal file
2
pyproject.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[tool.black]
|
||||
line-length=110
|
||||
@@ -1,3 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# https://blog.ionelmc.ro/2015/04/14/tox-tricks-and-patterns/#when-it-inevitably-leads-to-shell-scripts
|
||||
# If you are getting an INVOCATION ERROR for this script then there is
|
||||
# a good chance you are running on Windows.
|
||||
# You can and should use WSL for running tox on Windows when it calls bash scripts.
|
||||
REQUESTS_CA_BUNDLE=`python -m pytest_httpbin.certs` py.test $*
|
||||
|
||||
67
setup.py
67
setup.py
@@ -5,11 +5,10 @@ import sys
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
long_description = open('README.rst', 'r').read()
|
||||
long_description = open("README.rst", "r").read()
|
||||
|
||||
|
||||
class PyTest(TestCommand):
|
||||
|
||||
def finalize_options(self):
|
||||
TestCommand.finalize_options(self)
|
||||
self.test_args = []
|
||||
@@ -18,17 +17,19 @@ class PyTest(TestCommand):
|
||||
def run_tests(self):
|
||||
# import here, cause outside the eggs aren't loaded
|
||||
import pytest
|
||||
|
||||
errno = pytest.main(self.test_args)
|
||||
sys.exit(errno)
|
||||
|
||||
|
||||
install_requires = [
|
||||
'PyYAML',
|
||||
'wrapt',
|
||||
'six>=1.5',
|
||||
"PyYAML",
|
||||
"wrapt",
|
||||
"six>=1.5",
|
||||
'contextlib2; python_version=="2.7"',
|
||||
'mock; python_version=="2.7"',
|
||||
'yarl; python_version>="3.4"',
|
||||
'yarl; python_version>="3.6"',
|
||||
'yarl<1.4; python_version=="3.5"',
|
||||
]
|
||||
|
||||
excluded_packages = ["tests*"]
|
||||
@@ -36,37 +37,33 @@ if sys.version_info[0] == 2:
|
||||
excluded_packages.append("vcr.stubs.aiohttp_stubs")
|
||||
|
||||
setup(
|
||||
name='vcrpy',
|
||||
version='2.0.1',
|
||||
description=(
|
||||
"Automatically mock your HTTP interactions to simplify and "
|
||||
"speed up testing"
|
||||
),
|
||||
name="vcrpy",
|
||||
version="2.1.1",
|
||||
description=("Automatically mock your HTTP interactions to simplify and " "speed up testing"),
|
||||
long_description=long_description,
|
||||
author='Kevin McCarthy',
|
||||
author_email='me@kevinmccarthy.org',
|
||||
url='https://github.com/kevin1024/vcrpy',
|
||||
author="Kevin McCarthy",
|
||||
author_email="me@kevinmccarthy.org",
|
||||
url="https://github.com/kevin1024/vcrpy",
|
||||
packages=find_packages(exclude=excluded_packages),
|
||||
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
||||
install_requires=install_requires,
|
||||
license='MIT',
|
||||
tests_require=['pytest', 'mock', 'pytest-httpbin'],
|
||||
license="MIT",
|
||||
tests_require=["pytest", "mock", "pytest-httpbin"],
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
'Topic :: Software Development :: Testing',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
]
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Topic :: Software Development :: Testing",
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ def assert_cassette_has_one_response(cass):
|
||||
|
||||
def assert_is_json(a_string):
|
||||
try:
|
||||
json.loads(a_string.decode('utf-8'))
|
||||
json.loads(a_string.decode("utf-8"))
|
||||
except Exception:
|
||||
assert False
|
||||
assert True
|
||||
|
||||
2
tests/fixtures/migration/new_cassette.yaml
vendored
2
tests/fixtures/migration/new_cassette.yaml
vendored
@@ -9,7 +9,7 @@ interactions:
|
||||
method: GET
|
||||
uri: http://httpbin.org/ip
|
||||
response:
|
||||
body: {string: !!python/unicode "{\n \"origin\": \"217.122.164.194\"\n}"}
|
||||
body: {string: "{\n \"origin\": \"217.122.164.194\"\n}"}
|
||||
headers:
|
||||
access-control-allow-origin: ['*']
|
||||
content-type: [application/json]
|
||||
|
||||
@@ -5,18 +5,20 @@ import aiohttp
|
||||
from aiohttp.test_utils import TestClient
|
||||
|
||||
|
||||
async def aiohttp_request(loop, method, url, output='text', encoding='utf-8', content_type=None, **kwargs):
|
||||
async def aiohttp_request(loop, method, url, output="text", encoding="utf-8", content_type=None, **kwargs):
|
||||
session = aiohttp.ClientSession(loop=loop)
|
||||
response_ctx = session.request(method, url, **kwargs)
|
||||
|
||||
response = await response_ctx.__aenter__()
|
||||
if output == 'text':
|
||||
if output == "text":
|
||||
content = await response.text()
|
||||
elif output == 'json':
|
||||
content_type = content_type or 'application/json'
|
||||
elif output == "json":
|
||||
content_type = content_type or "application/json"
|
||||
content = await response.json(encoding=encoding, content_type=content_type)
|
||||
elif output == 'raw':
|
||||
elif output == "raw":
|
||||
content = await response.read()
|
||||
elif output == "stream":
|
||||
content = await response.content.read()
|
||||
|
||||
response_ctx._resp.close()
|
||||
await session.close()
|
||||
@@ -26,8 +28,16 @@ async def aiohttp_request(loop, method, url, output='text', encoding='utf-8', co
|
||||
|
||||
def aiohttp_app():
|
||||
async def hello(request):
|
||||
return aiohttp.web.Response(text='hello')
|
||||
return aiohttp.web.Response(text="hello")
|
||||
|
||||
async def json(request):
|
||||
return aiohttp.web.json_response({})
|
||||
|
||||
async def json_empty_body(request):
|
||||
return aiohttp.web.json_response()
|
||||
|
||||
app = aiohttp.web.Application()
|
||||
app.router.add_get('/', hello)
|
||||
app.router.add_get("/", hello)
|
||||
app.router.add_get("/json", json)
|
||||
app.router.add_get("/json/empty", json_empty_body)
|
||||
return app
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
asyncio = pytest.importorskip("asyncio")
|
||||
aiohttp = pytest.importorskip("aiohttp")
|
||||
|
||||
@@ -15,141 +17,184 @@ def run_in_loop(fn):
|
||||
return loop.run_until_complete(task)
|
||||
|
||||
|
||||
def request(method, url, output='text', **kwargs):
|
||||
def request(method, url, output="text", **kwargs):
|
||||
def run(loop):
|
||||
return aiohttp_request(loop, method, url, output=output, **kwargs)
|
||||
|
||||
return run_in_loop(run)
|
||||
|
||||
|
||||
def get(url, output='text', **kwargs):
|
||||
return request('GET', url, output=output, **kwargs)
|
||||
def get(url, output="text", **kwargs):
|
||||
return request("GET", url, output=output, **kwargs)
|
||||
|
||||
|
||||
def post(url, output='text', **kwargs):
|
||||
return request('POST', url, output='text', **kwargs)
|
||||
def post(url, output="text", **kwargs):
|
||||
return request("POST", url, output="text", **kwargs)
|
||||
|
||||
|
||||
@pytest.fixture(params=["https", "http"])
|
||||
def scheme(request):
|
||||
'''Fixture that returns both http and https.'''
|
||||
"""Fixture that returns both http and https."""
|
||||
return request.param
|
||||
|
||||
|
||||
def test_status(tmpdir, scheme):
|
||||
url = scheme + '://httpbin.org'
|
||||
with vcr.use_cassette(str(tmpdir.join('status.yaml'))):
|
||||
url = scheme + "://httpbin.org"
|
||||
with vcr.use_cassette(str(tmpdir.join("status.yaml"))):
|
||||
response, _ = get(url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('status.yaml'))) as cassette:
|
||||
with vcr.use_cassette(str(tmpdir.join("status.yaml"))) as cassette:
|
||||
cassette_response, _ = get(url)
|
||||
assert cassette_response.status == response.status
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_headers(tmpdir, scheme):
|
||||
url = scheme + '://httpbin.org'
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
response, _ = get(url)
|
||||
@pytest.mark.parametrize("auth", [None, aiohttp.BasicAuth("vcrpy", "test")])
|
||||
def test_headers(tmpdir, scheme, auth):
|
||||
url = scheme + "://httpbin.org"
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
response, _ = get(url, auth=auth)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cassette:
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))) as cassette:
|
||||
if auth is not None:
|
||||
request = cassette.requests[0]
|
||||
assert "AUTHORIZATION" in request.headers
|
||||
cassette_response, _ = get(url, auth=auth)
|
||||
assert dict(cassette_response.headers) == dict(response.headers)
|
||||
assert cassette.play_count == 1
|
||||
assert "istr" not in cassette.data[0]
|
||||
assert "yarl.URL" not in cassette.data[0]
|
||||
|
||||
|
||||
def test_case_insensitive_headers(tmpdir, scheme):
|
||||
url = scheme + "://httpbin.org"
|
||||
with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))):
|
||||
_, _ = get(url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))) as cassette:
|
||||
cassette_response, _ = get(url)
|
||||
assert cassette_response.headers == response.headers
|
||||
assert "Content-Type" in cassette_response.headers
|
||||
assert "content-type" in cassette_response.headers
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_text(tmpdir, scheme):
|
||||
url = scheme + '://httpbin.org'
|
||||
with vcr.use_cassette(str(tmpdir.join('text.yaml'))):
|
||||
url = scheme + "://httpbin.org"
|
||||
with vcr.use_cassette(str(tmpdir.join("text.yaml"))):
|
||||
_, response_text = get(url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('text.yaml'))) as cassette:
|
||||
with vcr.use_cassette(str(tmpdir.join("text.yaml"))) as cassette:
|
||||
_, cassette_response_text = get(url)
|
||||
assert cassette_response_text == response_text
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_json(tmpdir, scheme):
|
||||
url = scheme + '://httpbin.org/get'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
url = scheme + "://httpbin.org/get"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('json.yaml'))):
|
||||
_, response_json = get(url, output='json', headers=headers)
|
||||
with vcr.use_cassette(str(tmpdir.join("json.yaml"))):
|
||||
_, response_json = get(url, output="json", headers=headers)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('json.yaml'))) as cassette:
|
||||
_, cassette_response_json = get(url, output='json', headers=headers)
|
||||
with vcr.use_cassette(str(tmpdir.join("json.yaml"))) as cassette:
|
||||
_, cassette_response_json = get(url, output="json", headers=headers)
|
||||
assert cassette_response_json == response_json
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_binary(tmpdir, scheme):
|
||||
url = scheme + '://httpbin.org/image/png'
|
||||
with vcr.use_cassette(str(tmpdir.join('binary.yaml'))):
|
||||
_, response_binary = get(url, output='raw')
|
||||
url = scheme + "://httpbin.org/image/png"
|
||||
with vcr.use_cassette(str(tmpdir.join("binary.yaml"))):
|
||||
_, response_binary = get(url, output="raw")
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('binary.yaml'))) as cassette:
|
||||
_, cassette_response_binary = get(url, output='raw')
|
||||
with vcr.use_cassette(str(tmpdir.join("binary.yaml"))) as cassette:
|
||||
_, cassette_response_binary = get(url, output="raw")
|
||||
assert cassette_response_binary == response_binary
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_post(tmpdir, scheme):
|
||||
data = {'key1': 'value1', 'key2': 'value2'}
|
||||
url = scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post.yaml'))):
|
||||
_, response_json = post(url, data=data)
|
||||
def test_stream(tmpdir, scheme):
|
||||
url = scheme + "://httpbin.org/get"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('post.yaml'))) as cassette:
|
||||
_, cassette_response_json = post(url, data=data)
|
||||
assert cassette_response_json == response_json
|
||||
with vcr.use_cassette(str(tmpdir.join("stream.yaml"))):
|
||||
resp, body = get(url, output="raw") # Do not use stream here, as the stream is exhausted by vcr
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("stream.yaml"))) as cassette:
|
||||
cassette_resp, cassette_body = get(url, output="stream")
|
||||
assert cassette_body == body
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body", ["data", "json"])
|
||||
def test_post(tmpdir, scheme, body, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
url = scheme + "://httpbin.org/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("post.yaml"))):
|
||||
_, response_json = post(url, **{body: data})
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("post.yaml"))) as cassette:
|
||||
request = cassette.requests[0]
|
||||
assert request.body == data
|
||||
_, cassette_response_json = post(url, **{body: data})
|
||||
assert cassette_response_json == response_json
|
||||
assert cassette.play_count == 1
|
||||
|
||||
assert next(
|
||||
(
|
||||
log
|
||||
for log in caplog.records
|
||||
if log.getMessage() == "<Request (POST) {}> not in cassette, sending to real server".format(url)
|
||||
),
|
||||
None,
|
||||
), "Log message not found."
|
||||
|
||||
|
||||
def test_params(tmpdir, scheme):
|
||||
url = scheme + '://httpbin.org/get'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
params = {'a': 1, 'b': False, 'c': 'c'}
|
||||
url = scheme + "://httpbin.org/get"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
params = {"a": 1, "b": False, "c": "c"}
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
|
||||
_, response_json = get(url, output='json', params=params, headers=headers)
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
_, response_json = get(url, output="json", params=params, headers=headers)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
|
||||
_, cassette_response_json = get(url, output='json', params=params, headers=headers)
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
_, cassette_response_json = get(url, output="json", params=params, headers=headers)
|
||||
assert cassette_response_json == response_json
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_params_same_url_distinct_params(tmpdir, scheme):
|
||||
url = scheme + '://httpbin.org/get'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
params = {'a': 1, 'b': False, 'c': 'c'}
|
||||
url = scheme + "://httpbin.org/get"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
params = {"a": 1, "b": False, "c": "c"}
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
|
||||
_, response_json = get(url, output='json', params=params, headers=headers)
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
_, response_json = get(url, output="json", params=params, headers=headers)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
|
||||
_, cassette_response_json = get(url, output='json', params=params, headers=headers)
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
_, cassette_response_json = get(url, output="json", params=params, headers=headers)
|
||||
assert cassette_response_json == response_json
|
||||
assert cassette.play_count == 1
|
||||
|
||||
other_params = {'other': 'params'}
|
||||
with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
|
||||
response, cassette_response_text = get(url, output='text', params=other_params)
|
||||
assert 'No match for the request' in cassette_response_text
|
||||
other_params = {"other": "params"}
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
response, cassette_response_text = get(url, output="text", params=other_params)
|
||||
assert "No match for the request" in cassette_response_text
|
||||
assert response.status == 599
|
||||
|
||||
|
||||
def test_params_on_url(tmpdir, scheme):
|
||||
url = scheme + '://httpbin.org/get?a=1&b=foo'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
url = scheme + "://httpbin.org/get?a=1&b=foo"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
|
||||
_, response_json = get(url, output='json', headers=headers)
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
_, response_json = get(url, output="json", headers=headers)
|
||||
request = cassette.requests[0]
|
||||
assert request.url == url
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
|
||||
_, cassette_response_json = get(url, output='json', headers=headers)
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
_, cassette_response_json = get(url, output="json", headers=headers)
|
||||
request = cassette.requests[0]
|
||||
assert request.url == url
|
||||
assert cassette_response_json == response_json
|
||||
@@ -159,21 +204,61 @@ def test_params_on_url(tmpdir, scheme):
|
||||
def test_aiohttp_test_client(aiohttp_client, tmpdir):
|
||||
loop = asyncio.get_event_loop()
|
||||
app = aiohttp_app()
|
||||
url = '/'
|
||||
url = "/"
|
||||
client = loop.run_until_complete(aiohttp_client(app))
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))):
|
||||
response = loop.run_until_complete(client.get(url))
|
||||
|
||||
assert response.status == 200
|
||||
response_text = loop.run_until_complete(response.text())
|
||||
assert response_text == 'hello'
|
||||
assert response_text == "hello"
|
||||
response_text = loop.run_until_complete(response.text(errors="replace"))
|
||||
assert response_text == "hello"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
response = loop.run_until_complete(client.get(url))
|
||||
|
||||
request = cassette.requests[0]
|
||||
assert request.url == str(client.make_url(url))
|
||||
response_text = loop.run_until_complete(response.text())
|
||||
assert response_text == 'hello'
|
||||
assert response_text == "hello"
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_aiohttp_test_client_json(aiohttp_client, tmpdir):
|
||||
loop = asyncio.get_event_loop()
|
||||
app = aiohttp_app()
|
||||
url = "/json/empty"
|
||||
client = loop.run_until_complete(aiohttp_client(app))
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))):
|
||||
response = loop.run_until_complete(client.get(url))
|
||||
|
||||
assert response.status == 200
|
||||
response_json = loop.run_until_complete(response.json())
|
||||
assert response_json is None
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
response = loop.run_until_complete(client.get(url))
|
||||
|
||||
request = cassette.requests[0]
|
||||
assert request.url == str(client.make_url(url))
|
||||
response_json = loop.run_until_complete(response.json())
|
||||
assert response_json is None
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_redirect(aiohttp_client, tmpdir):
|
||||
url = "https://httpbin.org/redirect/2"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("redirect.yaml"))):
|
||||
response, _ = get(url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("redirect.yaml"))) as cassette:
|
||||
cassette_response, _ = get(url)
|
||||
|
||||
assert cassette_response.status == response.status
|
||||
assert len(cassette_response.history) == len(response.history)
|
||||
assert len(cassette) == 3
|
||||
assert cassette.play_count == 3
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''Basic tests for cassettes'''
|
||||
"""Basic tests for cassettes"""
|
||||
|
||||
# External imports
|
||||
import os
|
||||
@@ -10,21 +10,21 @@ import vcr
|
||||
|
||||
|
||||
def test_nonexistent_directory(tmpdir, httpbin):
|
||||
'''If we load a cassette in a nonexistent directory, it can save ok'''
|
||||
"""If we load a cassette in a nonexistent directory, it can save ok"""
|
||||
# Check to make sure directory doesnt exist
|
||||
assert not os.path.exists(str(tmpdir.join('nonexistent')))
|
||||
assert not os.path.exists(str(tmpdir.join("nonexistent")))
|
||||
|
||||
# Run VCR to create dir and cassette file
|
||||
with vcr.use_cassette(str(tmpdir.join('nonexistent', 'cassette.yml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("nonexistent", "cassette.yml"))):
|
||||
urlopen(httpbin.url).read()
|
||||
|
||||
# This should have made the file and the directory
|
||||
assert os.path.exists(str(tmpdir.join('nonexistent', 'cassette.yml')))
|
||||
assert os.path.exists(str(tmpdir.join("nonexistent", "cassette.yml")))
|
||||
|
||||
|
||||
def test_unpatch(tmpdir, httpbin):
|
||||
'''Ensure that our cassette gets unpatched when we're done'''
|
||||
with vcr.use_cassette(str(tmpdir.join('unpatch.yaml'))) as cass:
|
||||
"""Ensure that our cassette gets unpatched when we're done"""
|
||||
with vcr.use_cassette(str(tmpdir.join("unpatch.yaml"))) as cass:
|
||||
urlopen(httpbin.url).read()
|
||||
|
||||
# Make the same request, and assert that we haven't served any more
|
||||
@@ -34,30 +34,30 @@ def test_unpatch(tmpdir, httpbin):
|
||||
|
||||
|
||||
def test_basic_json_use(tmpdir, httpbin):
|
||||
'''
|
||||
"""
|
||||
Ensure you can load a json serialized cassette
|
||||
'''
|
||||
test_fixture = str(tmpdir.join('synopsis.json'))
|
||||
with vcr.use_cassette(test_fixture, serializer='json'):
|
||||
"""
|
||||
test_fixture = str(tmpdir.join("synopsis.json"))
|
||||
with vcr.use_cassette(test_fixture, serializer="json"):
|
||||
response = urlopen(httpbin.url).read()
|
||||
assert b'difficult sometimes' in response
|
||||
assert b"difficult sometimes" in response
|
||||
|
||||
|
||||
def test_patched_content(tmpdir, httpbin):
|
||||
'''
|
||||
"""
|
||||
Ensure that what you pull from a cassette is what came from the
|
||||
request
|
||||
'''
|
||||
with vcr.use_cassette(str(tmpdir.join('synopsis.yaml'))) as cass:
|
||||
"""
|
||||
with vcr.use_cassette(str(tmpdir.join("synopsis.yaml"))) as cass:
|
||||
response = urlopen(httpbin.url).read()
|
||||
assert cass.play_count == 0
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('synopsis.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("synopsis.yaml"))) as cass:
|
||||
response2 = urlopen(httpbin.url).read()
|
||||
assert cass.play_count == 1
|
||||
cass._save(force=True)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('synopsis.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("synopsis.yaml"))) as cass:
|
||||
response3 = urlopen(httpbin.url).read()
|
||||
assert cass.play_count == 1
|
||||
|
||||
@@ -66,12 +66,12 @@ def test_patched_content(tmpdir, httpbin):
|
||||
|
||||
|
||||
def test_patched_content_json(tmpdir, httpbin):
|
||||
'''
|
||||
"""
|
||||
Ensure that what you pull from a json cassette is what came from the
|
||||
request
|
||||
'''
|
||||
"""
|
||||
|
||||
testfile = str(tmpdir.join('synopsis.json'))
|
||||
testfile = str(tmpdir.join("synopsis.json"))
|
||||
|
||||
with vcr.use_cassette(testfile) as cass:
|
||||
response = urlopen(httpbin.url).read()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pytest
|
||||
|
||||
boto = pytest.importorskip("boto")
|
||||
|
||||
import boto # NOQA
|
||||
@@ -6,6 +7,7 @@ import boto.iam # NOQA
|
||||
from boto.s3.connection import S3Connection # NOQA
|
||||
from boto.s3.key import Key # NOQA
|
||||
import vcr # NOQA
|
||||
|
||||
try: # NOQA
|
||||
from ConfigParser import DuplicateSectionError # NOQA
|
||||
except ImportError: # NOQA
|
||||
@@ -14,68 +16,69 @@ except ImportError: # NOQA
|
||||
|
||||
|
||||
def test_boto_stubs(tmpdir):
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-stubs.yml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("boto-stubs.yml"))):
|
||||
# Perform the imports within the patched context so that
|
||||
# CertValidatingHTTPSConnection refers to the patched version.
|
||||
from boto.https_connection import CertValidatingHTTPSConnection
|
||||
from vcr.stubs.boto_stubs import VCRCertValidatingHTTPSConnection
|
||||
|
||||
# Prove that the class was patched by the stub and that we can instantiate it.
|
||||
assert issubclass(CertValidatingHTTPSConnection, VCRCertValidatingHTTPSConnection)
|
||||
CertValidatingHTTPSConnection('hostname.does.not.matter')
|
||||
CertValidatingHTTPSConnection("hostname.does.not.matter")
|
||||
|
||||
|
||||
def test_boto_without_vcr():
|
||||
s3_conn = S3Connection()
|
||||
s3_bucket = s3_conn.get_bucket('boto-demo-1394171994') # a bucket you can access
|
||||
s3_bucket = s3_conn.get_bucket("boto-demo-1394171994") # a bucket you can access
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
k.key = "test.txt"
|
||||
k.set_contents_from_string("hello world i am a string")
|
||||
|
||||
|
||||
def test_boto_medium_difficulty(tmpdir):
|
||||
s3_conn = S3Connection()
|
||||
s3_bucket = s3_conn.get_bucket('boto-demo-1394171994') # a bucket you can access
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-medium.yml'))):
|
||||
s3_bucket = s3_conn.get_bucket("boto-demo-1394171994") # a bucket you can access
|
||||
with vcr.use_cassette(str(tmpdir.join("boto-medium.yml"))):
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
k.key = "test.txt"
|
||||
k.set_contents_from_string("hello world i am a string")
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-medium.yml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("boto-medium.yml"))):
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
k.key = "test.txt"
|
||||
k.set_contents_from_string("hello world i am a string")
|
||||
|
||||
|
||||
def test_boto_hardcore_mode(tmpdir):
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-hardcore.yml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("boto-hardcore.yml"))):
|
||||
s3_conn = S3Connection()
|
||||
s3_bucket = s3_conn.get_bucket('boto-demo-1394171994') # a bucket you can access
|
||||
s3_bucket = s3_conn.get_bucket("boto-demo-1394171994") # a bucket you can access
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
k.key = "test.txt"
|
||||
k.set_contents_from_string("hello world i am a string")
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-hardcore.yml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("boto-hardcore.yml"))):
|
||||
s3_conn = S3Connection()
|
||||
s3_bucket = s3_conn.get_bucket('boto-demo-1394171994') # a bucket you can access
|
||||
s3_bucket = s3_conn.get_bucket("boto-demo-1394171994") # a bucket you can access
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
k.key = "test.txt"
|
||||
k.set_contents_from_string("hello world i am a string")
|
||||
|
||||
|
||||
def test_boto_iam(tmpdir):
|
||||
try:
|
||||
boto.config.add_section('Boto')
|
||||
boto.config.add_section("Boto")
|
||||
except DuplicateSectionError:
|
||||
pass
|
||||
# Ensure that boto uses HTTPS
|
||||
boto.config.set('Boto', 'is_secure', 'true')
|
||||
boto.config.set("Boto", "is_secure", "true")
|
||||
# Ensure that boto uses CertValidatingHTTPSConnection
|
||||
boto.config.set('Boto', 'https_validate_certificates', 'true')
|
||||
boto.config.set("Boto", "https_validate_certificates", "true")
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-iam.yml'))):
|
||||
iam_conn = boto.iam.connect_to_region('universal')
|
||||
with vcr.use_cassette(str(tmpdir.join("boto-iam.yml"))):
|
||||
iam_conn = boto.iam.connect_to_region("universal")
|
||||
iam_conn.get_all_users()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-iam.yml'))):
|
||||
iam_conn = boto.iam.connect_to_region('universal')
|
||||
with vcr.use_cassette(str(tmpdir.join("boto-iam.yml"))):
|
||||
iam_conn = boto.iam.connect_to_region("universal")
|
||||
iam_conn.get_all_users()
|
||||
|
||||
@@ -1,67 +1,122 @@
|
||||
import pytest
|
||||
import os
|
||||
|
||||
boto3 = pytest.importorskip("boto3")
|
||||
|
||||
import boto3 # NOQA
|
||||
import botocore # NOQA
|
||||
import vcr # NOQA
|
||||
|
||||
bucket = 'boto3-demo-1337' # a bucket you can access
|
||||
key = 'test/my_test.txt' # key with r+w access
|
||||
content = 'hello world i am a string' # content to put in the test file
|
||||
try:
|
||||
from botocore import awsrequest # NOQA
|
||||
|
||||
botocore_awsrequest = True
|
||||
except ImportError:
|
||||
botocore_awsrequest = False
|
||||
|
||||
# skip tests if boto does not use vendored requests anymore
|
||||
# https://github.com/boto/botocore/pull/1495
|
||||
boto3_skip_vendored_requests = pytest.mark.skipif(
|
||||
botocore_awsrequest,
|
||||
reason="botocore version {ver} does not use vendored requests anymore.".format(ver=botocore.__version__),
|
||||
)
|
||||
|
||||
boto3_skip_awsrequest = pytest.mark.skipif(
|
||||
not botocore_awsrequest,
|
||||
reason="botocore version {ver} still uses vendored requests.".format(ver=botocore.__version__),
|
||||
)
|
||||
|
||||
IAM_USER_NAME = "vcrpy"
|
||||
|
||||
|
||||
def test_boto_stubs(tmpdir):
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-stubs.yml'))):
|
||||
@pytest.fixture
|
||||
def iam_client():
|
||||
def _iam_client(boto3_session=None):
|
||||
if boto3_session is None:
|
||||
boto3_session = boto3.Session(
|
||||
aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID", "default"),
|
||||
aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY", "default"),
|
||||
aws_session_token=None,
|
||||
region_name=os.environ.get("AWS_DEFAULT_REGION", "default"),
|
||||
)
|
||||
return boto3_session.client("iam")
|
||||
|
||||
return _iam_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_user(iam_client):
|
||||
def _get_user(client=None, user_name=IAM_USER_NAME):
|
||||
if client is None:
|
||||
# Default client set with fixture `iam_client`
|
||||
client = iam_client()
|
||||
return client.get_user(UserName=user_name)
|
||||
|
||||
return _get_user
|
||||
|
||||
|
||||
@boto3_skip_vendored_requests
|
||||
def test_boto_vendored_stubs(tmpdir):
|
||||
with vcr.use_cassette(str(tmpdir.join("boto3-stubs.yml"))):
|
||||
# Perform the imports within the patched context so that
|
||||
# HTTPConnection, VerifiedHTTPSConnection refers to the patched version.
|
||||
from botocore.vendored.requests.packages.urllib3.connectionpool import \
|
||||
HTTPConnection, VerifiedHTTPSConnection
|
||||
from botocore.vendored.requests.packages.urllib3.connectionpool import (
|
||||
HTTPConnection,
|
||||
VerifiedHTTPSConnection,
|
||||
)
|
||||
from vcr.stubs.boto3_stubs import VCRRequestsHTTPConnection, VCRRequestsHTTPSConnection
|
||||
|
||||
# Prove that the class was patched by the stub and that we can instantiate it.
|
||||
assert issubclass(HTTPConnection, VCRRequestsHTTPConnection)
|
||||
assert issubclass(VerifiedHTTPSConnection, VCRRequestsHTTPSConnection)
|
||||
HTTPConnection('hostname.does.not.matter')
|
||||
VerifiedHTTPSConnection('hostname.does.not.matter')
|
||||
HTTPConnection("hostname.does.not.matter")
|
||||
VerifiedHTTPSConnection("hostname.does.not.matter")
|
||||
|
||||
|
||||
def test_boto3_without_vcr():
|
||||
s3_resource = boto3.resource('s3')
|
||||
b = s3_resource.Bucket(bucket)
|
||||
b.put_object(Key=key, Body=content)
|
||||
@pytest.mark.skipif(
|
||||
os.environ.get("TRAVIS_PULL_REQUEST") != "false",
|
||||
reason="Encrypted Environment Variables from Travis Repository Settings"
|
||||
" are disabled on PRs from forks. "
|
||||
"https://docs.travis-ci.com/user/pull-requests/#pull-requests-and-security-restrictions",
|
||||
)
|
||||
def test_boto_medium_difficulty(tmpdir, get_user):
|
||||
|
||||
# retrieve content to check it
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
with vcr.use_cassette(str(tmpdir.join("boto3-medium.yml"))):
|
||||
response = get_user()
|
||||
assert response["User"]["UserName"] == IAM_USER_NAME
|
||||
|
||||
# decode for python3
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
|
||||
|
||||
def test_boto_medium_difficulty(tmpdir):
|
||||
s3_resource = boto3.resource('s3')
|
||||
b = s3_resource.Bucket(bucket)
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-medium.yml'))):
|
||||
b.put_object(Key=key, Body=content)
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-medium.yml'))) as cass:
|
||||
b.put_object(Key=key, Body=content)
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
with vcr.use_cassette(str(tmpdir.join("boto3-medium.yml"))) as cass:
|
||||
response = get_user()
|
||||
assert response["User"]["UserName"] == IAM_USER_NAME
|
||||
assert cass.all_played
|
||||
|
||||
|
||||
def test_boto_hardcore_mode(tmpdir):
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-hardcore.yml'))):
|
||||
s3_resource = boto3.resource('s3')
|
||||
b = s3_resource.Bucket(bucket)
|
||||
b.put_object(Key=key, Body=content)
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
@pytest.mark.skipif(
|
||||
os.environ.get("TRAVIS_PULL_REQUEST") != "false",
|
||||
reason="Encrypted Environment Variables from Travis Repository Settings"
|
||||
" are disabled on PRs from forks. "
|
||||
"https://docs.travis-ci.com/user/pull-requests/#pull-requests-and-security-restrictions",
|
||||
)
|
||||
def test_boto_hardcore_mode(tmpdir, iam_client, get_user):
|
||||
with vcr.use_cassette(str(tmpdir.join("boto3-hardcore.yml"))):
|
||||
ses = boto3.Session(
|
||||
aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
|
||||
aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
|
||||
region_name=os.environ.get("AWS_DEFAULT_REGION"),
|
||||
)
|
||||
client = iam_client(ses)
|
||||
response = get_user(client=client)
|
||||
assert response["User"]["UserName"] == IAM_USER_NAME
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-hardcore.yml'))) as cass:
|
||||
s3_resource = boto3.resource('s3')
|
||||
b = s3_resource.Bucket(bucket)
|
||||
b.put_object(Key=key, Body=content)
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
with vcr.use_cassette(str(tmpdir.join("boto3-hardcore.yml"))) as cass:
|
||||
ses = boto3.Session(
|
||||
aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
|
||||
aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
|
||||
aws_session_token=None,
|
||||
region_name=os.environ.get("AWS_DEFAULT_REGION"),
|
||||
)
|
||||
|
||||
client = iam_client(ses)
|
||||
response = get_user(client=client)
|
||||
assert response["User"]["UserName"] == IAM_USER_NAME
|
||||
assert cass.all_played
|
||||
|
||||
@@ -6,45 +6,45 @@ from six.moves.urllib.request import urlopen
|
||||
|
||||
|
||||
def test_set_serializer_default_config(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR(serializer='json')
|
||||
my_vcr = vcr.VCR(serializer="json")
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))):
|
||||
assert my_vcr.serializer == 'json'
|
||||
urlopen(httpbin.url + '/get')
|
||||
with my_vcr.use_cassette(str(tmpdir.join("test.json"))):
|
||||
assert my_vcr.serializer == "json"
|
||||
urlopen(httpbin.url + "/get")
|
||||
|
||||
with open(str(tmpdir.join('test.json'))) as f:
|
||||
with open(str(tmpdir.join("test.json"))) as f:
|
||||
assert json.loads(f.read())
|
||||
|
||||
|
||||
def test_default_set_cassette_library_dir(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join('subdir')))
|
||||
my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join("subdir")))
|
||||
|
||||
with my_vcr.use_cassette('test.json'):
|
||||
urlopen(httpbin.url + '/get')
|
||||
with my_vcr.use_cassette("test.json"):
|
||||
urlopen(httpbin.url + "/get")
|
||||
|
||||
assert os.path.exists(str(tmpdir.join('subdir').join('test.json')))
|
||||
assert os.path.exists(str(tmpdir.join("subdir").join("test.json")))
|
||||
|
||||
|
||||
def test_override_set_cassette_library_dir(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join('subdir')))
|
||||
my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join("subdir")))
|
||||
|
||||
cld = str(tmpdir.join('subdir2'))
|
||||
cld = str(tmpdir.join("subdir2"))
|
||||
|
||||
with my_vcr.use_cassette('test.json', cassette_library_dir=cld):
|
||||
urlopen(httpbin.url + '/get')
|
||||
with my_vcr.use_cassette("test.json", cassette_library_dir=cld):
|
||||
urlopen(httpbin.url + "/get")
|
||||
|
||||
assert os.path.exists(str(tmpdir.join('subdir2').join('test.json')))
|
||||
assert not os.path.exists(str(tmpdir.join('subdir').join('test.json')))
|
||||
assert os.path.exists(str(tmpdir.join("subdir2").join("test.json")))
|
||||
assert not os.path.exists(str(tmpdir.join("subdir").join("test.json")))
|
||||
|
||||
|
||||
def test_override_match_on(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR(match_on=['method'])
|
||||
my_vcr = vcr.VCR(match_on=["method"])
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))):
|
||||
with my_vcr.use_cassette(str(tmpdir.join("test.json"))):
|
||||
urlopen(httpbin.url)
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))) as cass:
|
||||
urlopen(httpbin.url + '/get')
|
||||
with my_vcr.use_cassette(str(tmpdir.join("test.json"))) as cass:
|
||||
urlopen(httpbin.url + "/get")
|
||||
|
||||
assert len(cass) == 1
|
||||
assert cass.play_count == 1
|
||||
@@ -54,5 +54,5 @@ def test_missing_matcher():
|
||||
my_vcr = vcr.VCR()
|
||||
my_vcr.register_matcher("awesome", object)
|
||||
with pytest.raises(KeyError):
|
||||
with my_vcr.use_cassette("test.yaml", match_on=['notawesome']):
|
||||
with my_vcr.use_cassette("test.yaml", match_on=["notawesome"]):
|
||||
pass
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''Basic tests about save behavior'''
|
||||
"""Basic tests about save behavior"""
|
||||
|
||||
# External imports
|
||||
import os
|
||||
@@ -11,11 +11,11 @@ import vcr
|
||||
|
||||
|
||||
def test_disk_saver_nowrite(tmpdir, httpbin):
|
||||
'''
|
||||
"""
|
||||
Ensure that when you close a cassette without changing it it doesn't
|
||||
rewrite the file
|
||||
'''
|
||||
fname = str(tmpdir.join('synopsis.yaml'))
|
||||
"""
|
||||
fname = str(tmpdir.join("synopsis.yaml"))
|
||||
with vcr.use_cassette(fname) as cass:
|
||||
urlopen(httpbin.url).read()
|
||||
assert cass.play_count == 0
|
||||
@@ -31,11 +31,11 @@ def test_disk_saver_nowrite(tmpdir, httpbin):
|
||||
|
||||
|
||||
def test_disk_saver_write(tmpdir, httpbin):
|
||||
'''
|
||||
"""
|
||||
Ensure that when you close a cassette after changing it it does
|
||||
rewrite the file
|
||||
'''
|
||||
fname = str(tmpdir.join('synopsis.yaml'))
|
||||
"""
|
||||
fname = str(tmpdir.join("synopsis.yaml"))
|
||||
with vcr.use_cassette(fname) as cass:
|
||||
urlopen(httpbin.url).read()
|
||||
assert cass.play_count == 0
|
||||
@@ -45,9 +45,9 @@ def test_disk_saver_write(tmpdir, httpbin):
|
||||
# the mtime doesn't change
|
||||
time.sleep(1)
|
||||
|
||||
with vcr.use_cassette(fname, record_mode='any') as cass:
|
||||
with vcr.use_cassette(fname, record_mode="any") as cass:
|
||||
urlopen(httpbin.url).read()
|
||||
urlopen(httpbin.url + '/get').read()
|
||||
urlopen(httpbin.url + "/get").read()
|
||||
assert cass.play_count == 1
|
||||
assert cass.dirty
|
||||
last_mod2 = os.path.getmtime(fname)
|
||||
|
||||
@@ -10,9 +10,7 @@ from assertions import assert_cassette_has_one_response, assert_is_json
|
||||
|
||||
def _request_with_auth(url, username, password):
|
||||
request = Request(url)
|
||||
base64string = base64.b64encode(
|
||||
username.encode('ascii') + b':' + password.encode('ascii')
|
||||
)
|
||||
base64string = base64.b64encode(username.encode("ascii") + b":" + password.encode("ascii"))
|
||||
request.add_header(b"Authorization", b"Basic " + base64string)
|
||||
return urlopen(request)
|
||||
|
||||
@@ -22,84 +20,84 @@ def _find_header(cassette, header):
|
||||
|
||||
|
||||
def test_filter_basic_auth(tmpdir, httpbin):
|
||||
url = httpbin.url + '/basic-auth/user/passwd'
|
||||
cass_file = str(tmpdir.join('basic_auth_filter.yaml'))
|
||||
my_vcr = vcr.VCR(match_on=['uri', 'method', 'headers'])
|
||||
url = httpbin.url + "/basic-auth/user/passwd"
|
||||
cass_file = str(tmpdir.join("basic_auth_filter.yaml"))
|
||||
my_vcr = vcr.VCR(match_on=["uri", "method", "headers"])
|
||||
# 2 requests, one with auth failure and one with auth success
|
||||
with my_vcr.use_cassette(cass_file, filter_headers=['authorization']):
|
||||
with my_vcr.use_cassette(cass_file, filter_headers=["authorization"]):
|
||||
with pytest.raises(HTTPError):
|
||||
resp = _request_with_auth(url, 'user', 'wrongpasswd')
|
||||
resp = _request_with_auth(url, "user", "wrongpasswd")
|
||||
assert resp.getcode() == 401
|
||||
resp = _request_with_auth(url, 'user', 'passwd')
|
||||
resp = _request_with_auth(url, "user", "passwd")
|
||||
assert resp.getcode() == 200
|
||||
# make same 2 requests, this time both served from cassette.
|
||||
with my_vcr.use_cassette(cass_file, filter_headers=['authorization']) as cass:
|
||||
with my_vcr.use_cassette(cass_file, filter_headers=["authorization"]) as cass:
|
||||
with pytest.raises(HTTPError):
|
||||
resp = _request_with_auth(url, 'user', 'wrongpasswd')
|
||||
resp = _request_with_auth(url, "user", "wrongpasswd")
|
||||
assert resp.getcode() == 401
|
||||
resp = _request_with_auth(url, 'user', 'passwd')
|
||||
resp = _request_with_auth(url, "user", "passwd")
|
||||
assert resp.getcode() == 200
|
||||
# authorization header should not have been recorded
|
||||
assert not _find_header(cass, 'authorization')
|
||||
assert not _find_header(cass, "authorization")
|
||||
assert len(cass) == 2
|
||||
|
||||
|
||||
def test_filter_querystring(tmpdir, httpbin):
|
||||
url = httpbin.url + '/?foo=bar'
|
||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
||||
with vcr.use_cassette(cass_file, filter_query_parameters=['foo']):
|
||||
url = httpbin.url + "/?foo=bar"
|
||||
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||
with vcr.use_cassette(cass_file, filter_query_parameters=["foo"]):
|
||||
urlopen(url)
|
||||
with vcr.use_cassette(cass_file, filter_query_parameters=['foo']) as cass:
|
||||
with vcr.use_cassette(cass_file, filter_query_parameters=["foo"]) as cass:
|
||||
urlopen(url)
|
||||
assert 'foo' not in cass.requests[0].url
|
||||
assert "foo" not in cass.requests[0].url
|
||||
|
||||
|
||||
def test_filter_post_data(tmpdir, httpbin):
|
||||
url = httpbin.url + '/post'
|
||||
data = urlencode({'id': 'secret', 'foo': 'bar'}).encode('utf-8')
|
||||
cass_file = str(tmpdir.join('filter_pd.yaml'))
|
||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=['id']):
|
||||
url = httpbin.url + "/post"
|
||||
data = urlencode({"id": "secret", "foo": "bar"}).encode("utf-8")
|
||||
cass_file = str(tmpdir.join("filter_pd.yaml"))
|
||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=["id"]):
|
||||
urlopen(url, data)
|
||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=['id']) as cass:
|
||||
assert b'id=secret' not in cass.requests[0].body
|
||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=["id"]) as cass:
|
||||
assert b"id=secret" not in cass.requests[0].body
|
||||
|
||||
|
||||
def test_filter_json_post_data(tmpdir, httpbin):
|
||||
data = json.dumps({'id': 'secret', 'foo': 'bar'}).encode('utf-8')
|
||||
request = Request(httpbin.url + '/post', data=data)
|
||||
request.add_header('Content-Type', 'application/json')
|
||||
data = json.dumps({"id": "secret", "foo": "bar"}).encode("utf-8")
|
||||
request = Request(httpbin.url + "/post", data=data)
|
||||
request.add_header("Content-Type", "application/json")
|
||||
|
||||
cass_file = str(tmpdir.join('filter_jpd.yaml'))
|
||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=['id']):
|
||||
cass_file = str(tmpdir.join("filter_jpd.yaml"))
|
||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=["id"]):
|
||||
urlopen(request)
|
||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=['id']) as cass:
|
||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=["id"]) as cass:
|
||||
assert b'"id": "secret"' not in cass.requests[0].body
|
||||
|
||||
|
||||
def test_filter_callback(tmpdir, httpbin):
|
||||
url = httpbin.url + '/get'
|
||||
cass_file = str(tmpdir.join('basic_auth_filter.yaml'))
|
||||
url = httpbin.url + "/get"
|
||||
cass_file = str(tmpdir.join("basic_auth_filter.yaml"))
|
||||
|
||||
def before_record_cb(request):
|
||||
if request.path != '/get':
|
||||
if request.path != "/get":
|
||||
return request
|
||||
|
||||
# Test the legacy keyword.
|
||||
my_vcr = vcr.VCR(before_record=before_record_cb)
|
||||
with my_vcr.use_cassette(cass_file, filter_headers=['authorization']) as cass:
|
||||
with my_vcr.use_cassette(cass_file, filter_headers=["authorization"]) as cass:
|
||||
urlopen(url)
|
||||
assert len(cass) == 0
|
||||
|
||||
my_vcr = vcr.VCR(before_record_request=before_record_cb)
|
||||
with my_vcr.use_cassette(cass_file, filter_headers=['authorization']) as cass:
|
||||
with my_vcr.use_cassette(cass_file, filter_headers=["authorization"]) as cass:
|
||||
urlopen(url)
|
||||
assert len(cass) == 0
|
||||
|
||||
|
||||
def test_decompress_gzip(tmpdir, httpbin):
|
||||
url = httpbin.url + '/gzip'
|
||||
request = Request(url, headers={'Accept-Encoding': ['gzip, deflate']})
|
||||
cass_file = str(tmpdir.join('gzip_response.yaml'))
|
||||
url = httpbin.url + "/gzip"
|
||||
request = Request(url, headers={"Accept-Encoding": ["gzip, deflate"]})
|
||||
cass_file = str(tmpdir.join("gzip_response.yaml"))
|
||||
with vcr.use_cassette(cass_file, decode_compressed_response=True):
|
||||
urlopen(request)
|
||||
with vcr.use_cassette(cass_file) as cass:
|
||||
@@ -109,9 +107,9 @@ def test_decompress_gzip(tmpdir, httpbin):
|
||||
|
||||
|
||||
def test_decompress_deflate(tmpdir, httpbin):
|
||||
url = httpbin.url + '/deflate'
|
||||
request = Request(url, headers={'Accept-Encoding': ['gzip, deflate']})
|
||||
cass_file = str(tmpdir.join('deflate_response.yaml'))
|
||||
url = httpbin.url + "/deflate"
|
||||
request = Request(url, headers={"Accept-Encoding": ["gzip, deflate"]})
|
||||
cass_file = str(tmpdir.join("deflate_response.yaml"))
|
||||
with vcr.use_cassette(cass_file, decode_compressed_response=True):
|
||||
urlopen(request)
|
||||
with vcr.use_cassette(cass_file) as cass:
|
||||
@@ -122,8 +120,8 @@ def test_decompress_deflate(tmpdir, httpbin):
|
||||
|
||||
def test_decompress_regular(tmpdir, httpbin):
|
||||
"""Test that it doesn't try to decompress content that isn't compressed"""
|
||||
url = httpbin.url + '/get'
|
||||
cass_file = str(tmpdir.join('noncompressed_response.yaml'))
|
||||
url = httpbin.url + "/get"
|
||||
cass_file = str(tmpdir.join("noncompressed_response.yaml"))
|
||||
with vcr.use_cassette(cass_file, decode_compressed_response=True):
|
||||
urlopen(url)
|
||||
with vcr.use_cassette(cass_file) as cass:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''Integration tests with httplib2'''
|
||||
"""Integration tests with httplib2"""
|
||||
|
||||
import sys
|
||||
|
||||
@@ -19,97 +19,90 @@ def http():
|
||||
Returns an httplib2 HTTP instance
|
||||
with the certificate replaced by the httpbin one.
|
||||
"""
|
||||
kwargs = {
|
||||
'ca_certs': pytest_httpbin.certs.where()
|
||||
}
|
||||
if sys.version_info[:2] == (3, 7):
|
||||
kwargs['disable_ssl_certificate_validation'] = True
|
||||
kwargs = {"ca_certs": pytest_httpbin.certs.where()}
|
||||
if sys.version_info[:2] in [(2, 7), (3, 7)]:
|
||||
kwargs["disable_ssl_certificate_validation"] = True
|
||||
return httplib2.Http(**kwargs)
|
||||
|
||||
|
||||
def test_response_code(tmpdir, httpbin_both):
|
||||
'''Ensure we can read a response code from a fetch'''
|
||||
"""Ensure we can read a response code from a fetch"""
|
||||
url = httpbin_both.url
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
resp, _ = http().request(url)
|
||||
code = resp.status
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
resp, _ = http().request(url)
|
||||
assert code == resp.status
|
||||
|
||||
|
||||
def test_random_body(httpbin_both, tmpdir):
|
||||
'''Ensure we can read the content, and that it's served from cache'''
|
||||
url = httpbin_both.url + '/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
||||
"""Ensure we can read the content, and that it's served from cache"""
|
||||
url = httpbin_both.url + "/bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
_, content = http().request(url)
|
||||
body = content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
_, content = http().request(url)
|
||||
assert body == content
|
||||
|
||||
|
||||
def test_response_headers(tmpdir, httpbin_both):
|
||||
'''Ensure we can get information from the response'''
|
||||
"""Ensure we can get information from the response"""
|
||||
url = httpbin_both.url
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
resp, _ = http().request(url)
|
||||
headers = resp.items()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
resp, _ = http().request(url)
|
||||
assert set(headers) == set(resp.items())
|
||||
|
||||
|
||||
def test_effective_url(tmpdir, httpbin_both):
|
||||
'''Ensure that the effective_url is captured'''
|
||||
url = httpbin_both.url + '/redirect-to?url=/html'
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
"""Ensure that the effective_url is captured"""
|
||||
url = httpbin_both.url + "/redirect-to?url=/html"
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
resp, _ = http().request(url)
|
||||
effective_url = resp['content-location']
|
||||
assert effective_url == httpbin_both + '/html'
|
||||
effective_url = resp["content-location"]
|
||||
assert effective_url == httpbin_both + "/html"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
resp, _ = http().request(url)
|
||||
assert effective_url == resp['content-location']
|
||||
assert effective_url == resp["content-location"]
|
||||
|
||||
|
||||
def test_multiple_requests(tmpdir, httpbin_both):
|
||||
'''Ensure that we can cache multiple requests'''
|
||||
urls = [
|
||||
httpbin_both.url,
|
||||
httpbin_both.url,
|
||||
httpbin_both.url + '/get',
|
||||
httpbin_both.url + '/bytes/1024',
|
||||
]
|
||||
with vcr.use_cassette(str(tmpdir.join('multiple.yaml'))) as cass:
|
||||
"""Ensure that we can cache multiple requests"""
|
||||
urls = [httpbin_both.url, httpbin_both.url, httpbin_both.url + "/get", httpbin_both.url + "/bytes/1024"]
|
||||
with vcr.use_cassette(str(tmpdir.join("multiple.yaml"))) as cass:
|
||||
[http().request(url) for url in urls]
|
||||
assert len(cass) == len(urls)
|
||||
|
||||
|
||||
def test_get_data(tmpdir, httpbin_both):
|
||||
'''Ensure that it works with query data'''
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = httpbin_both.url + '/get?' + data
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))):
|
||||
"""Ensure that it works with query data"""
|
||||
data = urlencode({"some": 1, "data": "here"})
|
||||
url = httpbin_both.url + "/get?" + data
|
||||
with vcr.use_cassette(str(tmpdir.join("get_data.yaml"))):
|
||||
_, res1 = http().request(url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("get_data.yaml"))):
|
||||
_, res2 = http().request(url)
|
||||
|
||||
assert res1 == res2
|
||||
|
||||
|
||||
def test_post_data(tmpdir, httpbin_both):
|
||||
'''Ensure that it works when posting data'''
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = httpbin_both.url + '/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))):
|
||||
"""Ensure that it works when posting data"""
|
||||
data = urlencode({"some": 1, "data": "here"})
|
||||
url = httpbin_both.url + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||
_, res1 = http().request(url, "POST", data)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))) as cass:
|
||||
_, res2 = http().request(url, "POST", data)
|
||||
|
||||
assert res1 == res2
|
||||
@@ -117,13 +110,13 @@ def test_post_data(tmpdir, httpbin_both):
|
||||
|
||||
|
||||
def test_post_unicode_data(tmpdir, httpbin_both):
|
||||
'''Ensure that it works when posting unicode data'''
|
||||
data = urlencode({'snowman': u'☃'.encode('utf-8')})
|
||||
url = httpbin_both.url + '/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))):
|
||||
"""Ensure that it works when posting unicode data"""
|
||||
data = urlencode({"snowman": u"☃".encode("utf-8")})
|
||||
url = httpbin_both.url + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||
_, res1 = http().request(url, "POST", data)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))) as cass:
|
||||
_, res2 = http().request(url, "POST", data)
|
||||
|
||||
assert res1 == res2
|
||||
@@ -131,11 +124,11 @@ def test_post_unicode_data(tmpdir, httpbin_both):
|
||||
|
||||
|
||||
def test_cross_scheme(tmpdir, httpbin, httpbin_secure):
|
||||
'''Ensure that requests between schemes are treated separately'''
|
||||
"""Ensure that requests between schemes are treated separately"""
|
||||
# First fetch a url under https, and then again under https and then
|
||||
# ensure that we haven't served anything out of cache, and we have two
|
||||
# requests / response pairs in the cassette
|
||||
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("cross_scheme.yaml"))) as cass:
|
||||
http().request(httpbin_secure.url)
|
||||
http().request(httpbin.url)
|
||||
assert len(cass) == 2
|
||||
@@ -143,17 +136,17 @@ def test_cross_scheme(tmpdir, httpbin, httpbin_secure):
|
||||
|
||||
|
||||
def test_decorator(tmpdir, httpbin_both):
|
||||
'''Test the decorator version of VCR.py'''
|
||||
"""Test the decorator version of VCR.py"""
|
||||
url = httpbin_both.url
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
@vcr.use_cassette(str(tmpdir.join("atts.yaml")))
|
||||
def inner1():
|
||||
resp, _ = http().request(url)
|
||||
return resp['status']
|
||||
return resp["status"]
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
@vcr.use_cassette(str(tmpdir.join("atts.yaml")))
|
||||
def inner2():
|
||||
resp, _ = http().request(url)
|
||||
return resp['status']
|
||||
return resp["status"]
|
||||
|
||||
assert inner1() == inner2()
|
||||
|
||||
@@ -15,59 +15,53 @@ def overridden_dns(overrides):
|
||||
def fake_getaddrinfo(*args, **kwargs):
|
||||
if args[0] in overrides:
|
||||
address = overrides[args[0]]
|
||||
return [(2, 1, 6, '', (address, args[1]))]
|
||||
return [(2, 1, 6, "", (address, args[1]))]
|
||||
return real_getaddrinfo(*args, **kwargs)
|
||||
|
||||
socket.getaddrinfo = fake_getaddrinfo
|
||||
yield
|
||||
socket.getaddrinfo = real_getaddrinfo
|
||||
|
||||
|
||||
def test_ignore_localhost(tmpdir, httpbin):
|
||||
with overridden_dns({'httpbin.org': '127.0.0.1'}):
|
||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
||||
with overridden_dns({"httpbin.org": "127.0.0.1"}):
|
||||
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
|
||||
urlopen('http://localhost:{}/'.format(httpbin.port))
|
||||
urlopen("http://localhost:{}/".format(httpbin.port))
|
||||
assert len(cass) == 0
|
||||
urlopen('http://httpbin.org:{}/'.format(httpbin.port))
|
||||
urlopen("http://httpbin.org:{}/".format(httpbin.port))
|
||||
assert len(cass) == 1
|
||||
|
||||
|
||||
def test_ignore_httpbin(tmpdir, httpbin):
|
||||
with overridden_dns({'httpbin.org': '127.0.0.1'}):
|
||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
||||
with vcr.use_cassette(
|
||||
cass_file,
|
||||
ignore_hosts=['httpbin.org']
|
||||
) as cass:
|
||||
urlopen('http://httpbin.org:{}/'.format(httpbin.port))
|
||||
with overridden_dns({"httpbin.org": "127.0.0.1"}):
|
||||
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||
with vcr.use_cassette(cass_file, ignore_hosts=["httpbin.org"]) as cass:
|
||||
urlopen("http://httpbin.org:{}/".format(httpbin.port))
|
||||
assert len(cass) == 0
|
||||
urlopen('http://localhost:{}/'.format(httpbin.port))
|
||||
urlopen("http://localhost:{}/".format(httpbin.port))
|
||||
assert len(cass) == 1
|
||||
|
||||
|
||||
def test_ignore_localhost_and_httpbin(tmpdir, httpbin):
|
||||
with overridden_dns({'httpbin.org': '127.0.0.1'}):
|
||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
||||
with vcr.use_cassette(
|
||||
cass_file,
|
||||
ignore_hosts=['httpbin.org'],
|
||||
ignore_localhost=True
|
||||
) as cass:
|
||||
urlopen('http://httpbin.org:{}'.format(httpbin.port))
|
||||
urlopen('http://localhost:{}'.format(httpbin.port))
|
||||
with overridden_dns({"httpbin.org": "127.0.0.1"}):
|
||||
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||
with vcr.use_cassette(cass_file, ignore_hosts=["httpbin.org"], ignore_localhost=True) as cass:
|
||||
urlopen("http://httpbin.org:{}".format(httpbin.port))
|
||||
urlopen("http://localhost:{}".format(httpbin.port))
|
||||
assert len(cass) == 0
|
||||
|
||||
|
||||
def test_ignore_localhost_twice(tmpdir, httpbin):
|
||||
with overridden_dns({'httpbin.org': '127.0.0.1'}):
|
||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
||||
with overridden_dns({"httpbin.org": "127.0.0.1"}):
|
||||
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
|
||||
urlopen('http://localhost:{}'.format(httpbin.port))
|
||||
urlopen("http://localhost:{}".format(httpbin.port))
|
||||
assert len(cass) == 0
|
||||
urlopen('http://httpbin.org:{}'.format(httpbin.port))
|
||||
urlopen("http://httpbin.org:{}".format(httpbin.port))
|
||||
assert len(cass) == 1
|
||||
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
|
||||
assert len(cass) == 1
|
||||
urlopen('http://localhost:{}'.format(httpbin.port))
|
||||
urlopen('http://httpbin.org:{}'.format(httpbin.port))
|
||||
urlopen("http://localhost:{}".format(httpbin.port))
|
||||
urlopen("http://httpbin.org:{}".format(httpbin.port))
|
||||
assert len(cass) == 1
|
||||
|
||||
@@ -3,11 +3,11 @@ import pytest
|
||||
from six.moves.urllib.request import urlopen
|
||||
|
||||
|
||||
DEFAULT_URI = 'http://httpbin.org/get?p1=q1&p2=q2' # base uri for testing
|
||||
DEFAULT_URI = "http://httpbin.org/get?p1=q1&p2=q2" # base uri for testing
|
||||
|
||||
|
||||
def _replace_httpbin(uri, httpbin, httpbin_secure):
|
||||
return uri.replace('http://httpbin.org', httpbin.url).replace('https://httpbin.org', httpbin_secure.url)
|
||||
return uri.replace("http://httpbin.org", httpbin.url).replace("https://httpbin.org", httpbin_secure.url)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -18,29 +18,22 @@ def cassette(tmpdir, httpbin, httpbin_secure):
|
||||
"""
|
||||
default_uri = _replace_httpbin(DEFAULT_URI, httpbin, httpbin_secure)
|
||||
|
||||
cassette_path = str(tmpdir.join('test.yml'))
|
||||
with vcr.use_cassette(cassette_path, record_mode='all'):
|
||||
cassette_path = str(tmpdir.join("test.yml"))
|
||||
with vcr.use_cassette(cassette_path, record_mode="all"):
|
||||
urlopen(default_uri)
|
||||
return cassette_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize("matcher, matching_uri, not_matching_uri", [
|
||||
('uri',
|
||||
'http://httpbin.org/get?p1=q1&p2=q2',
|
||||
'http://httpbin.org/get?p2=q2&p1=q1'),
|
||||
('scheme',
|
||||
'http://google.com/post?a=b',
|
||||
'https://httpbin.org/get?p1=q1&p2=q2'),
|
||||
('host',
|
||||
'https://httpbin.org/post?a=b',
|
||||
'http://google.com/get?p1=q1&p2=q2'),
|
||||
('path',
|
||||
'https://google.com/get?a=b',
|
||||
'http://httpbin.org/post?p1=q1&p2=q2'),
|
||||
('query',
|
||||
'https://google.com/get?p2=q2&p1=q1',
|
||||
'http://httpbin.org/get?p1=q1&a=b')
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"matcher, matching_uri, not_matching_uri",
|
||||
[
|
||||
("uri", "http://httpbin.org/get?p1=q1&p2=q2", "http://httpbin.org/get?p2=q2&p1=q1"),
|
||||
("scheme", "http://google.com/post?a=b", "https://httpbin.org/get?p1=q1&p2=q2"),
|
||||
("host", "https://httpbin.org/post?a=b", "http://google.com/get?p1=q1&p2=q2"),
|
||||
("path", "https://google.com/get?a=b", "http://httpbin.org/post?p1=q1&p2=q2"),
|
||||
("query", "https://google.com/get?p2=q2&p1=q1", "http://httpbin.org/get?p1=q1&a=b"),
|
||||
],
|
||||
)
|
||||
def test_matchers(httpbin, httpbin_secure, cassette, matcher, matching_uri, not_matching_uri):
|
||||
|
||||
matching_uri = _replace_httpbin(matching_uri, httpbin, httpbin_secure)
|
||||
@@ -67,22 +60,20 @@ def test_method_matcher(cassette, httpbin, httpbin_secure):
|
||||
default_uri = _replace_httpbin(DEFAULT_URI, httpbin, httpbin_secure)
|
||||
|
||||
# play cassette with matching on method
|
||||
with vcr.use_cassette(cassette, match_on=['method']) as cass:
|
||||
urlopen('https://google.com/get?a=b')
|
||||
with vcr.use_cassette(cassette, match_on=["method"]) as cass:
|
||||
urlopen("https://google.com/get?a=b")
|
||||
assert cass.play_count == 1
|
||||
|
||||
# should fail if method does not match
|
||||
with pytest.raises(vcr.errors.CannotOverwriteExistingCassetteException):
|
||||
with vcr.use_cassette(cassette, match_on=['method']) as cass:
|
||||
with vcr.use_cassette(cassette, match_on=["method"]) as cass:
|
||||
# is a POST request
|
||||
urlopen(default_uri, data=b'')
|
||||
urlopen(default_uri, data=b"")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("uri", [
|
||||
DEFAULT_URI,
|
||||
'http://httpbin.org/get?p2=q2&p1=q1',
|
||||
'http://httpbin.org/get?p2=q2&p1=q1',
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"uri", [DEFAULT_URI, "http://httpbin.org/get?p2=q2&p1=q1", "http://httpbin.org/get?p2=q2&p1=q1"]
|
||||
)
|
||||
def test_default_matcher_matches(cassette, uri, httpbin, httpbin_secure):
|
||||
|
||||
uri = _replace_httpbin(uri, httpbin, httpbin_secure)
|
||||
@@ -92,12 +83,15 @@ def test_default_matcher_matches(cassette, uri, httpbin, httpbin_secure):
|
||||
assert cass.play_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("uri", [
|
||||
'https://httpbin.org/get?p1=q1&p2=q2',
|
||||
'http://google.com/get?p1=q1&p2=q2',
|
||||
'http://httpbin.org/post?p1=q1&p2=q2',
|
||||
'http://httpbin.org/get?p1=q1&a=b'
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"uri",
|
||||
[
|
||||
"https://httpbin.org/get?p1=q1&p2=q2",
|
||||
"http://google.com/get?p1=q1&p2=q2",
|
||||
"http://httpbin.org/post?p1=q1&p2=q2",
|
||||
"http://httpbin.org/get?p1=q1&a=b",
|
||||
],
|
||||
)
|
||||
def test_default_matcher_does_not_match(cassette, uri, httpbin, httpbin_secure):
|
||||
uri = _replace_httpbin(uri, httpbin, httpbin_secure)
|
||||
with pytest.raises(vcr.errors.CannotOverwriteExistingCassetteException):
|
||||
@@ -110,4 +104,4 @@ def test_default_matcher_does_not_match_on_method(cassette, httpbin, httpbin_sec
|
||||
with pytest.raises(vcr.errors.CannotOverwriteExistingCassetteException):
|
||||
with vcr.use_cassette(cassette):
|
||||
# is a POST request
|
||||
urlopen(default_uri, data=b'')
|
||||
urlopen(default_uri, data=b"")
|
||||
|
||||
@@ -6,15 +6,15 @@ from six.moves.urllib.request import urlopen
|
||||
def test_making_extra_request_raises_exception(tmpdir, httpbin):
|
||||
# make two requests in the first request that are considered
|
||||
# identical (since the match is based on method)
|
||||
with vcr.use_cassette(str(tmpdir.join('test.json')), match_on=['method']):
|
||||
urlopen(httpbin.url + '/status/200')
|
||||
urlopen(httpbin.url + '/status/201')
|
||||
with vcr.use_cassette(str(tmpdir.join("test.json")), match_on=["method"]):
|
||||
urlopen(httpbin.url + "/status/200")
|
||||
urlopen(httpbin.url + "/status/201")
|
||||
|
||||
# Now, try to make three requests. The first two should return the
|
||||
# correct status codes in order, and the third should raise an
|
||||
# exception.
|
||||
with vcr.use_cassette(str(tmpdir.join('test.json')), match_on=['method']):
|
||||
assert urlopen(httpbin.url + '/status/200').getcode() == 200
|
||||
assert urlopen(httpbin.url + '/status/201').getcode() == 201
|
||||
with vcr.use_cassette(str(tmpdir.join("test.json")), match_on=["method"]):
|
||||
assert urlopen(httpbin.url + "/status/200").getcode() == 200
|
||||
assert urlopen(httpbin.url + "/status/201").getcode() == 201
|
||||
with pytest.raises(Exception):
|
||||
urlopen(httpbin.url + '/status/200')
|
||||
urlopen(httpbin.url + "/status/200")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''Test using a proxy.'''
|
||||
"""Test using a proxy."""
|
||||
|
||||
# External imports
|
||||
import multiprocessing
|
||||
@@ -16,11 +16,12 @@ requests = pytest.importorskip("requests")
|
||||
|
||||
|
||||
class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
'''
|
||||
"""
|
||||
Simple proxy server.
|
||||
|
||||
(Inspired by: http://effbot.org/librarybook/simplehttpserver.htm).
|
||||
'''
|
||||
"""
|
||||
|
||||
def do_GET(self):
|
||||
upstream_response = urlopen(self.path)
|
||||
try:
|
||||
@@ -37,24 +38,22 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
self.copyfile(upstream_response, self.wfile)
|
||||
|
||||
|
||||
@pytest.yield_fixture(scope='session')
|
||||
@pytest.yield_fixture(scope="session")
|
||||
def proxy_server():
|
||||
httpd = socketserver.ThreadingTCPServer(('', 0), Proxy)
|
||||
proxy_process = multiprocessing.Process(
|
||||
target=httpd.serve_forever,
|
||||
)
|
||||
httpd = socketserver.ThreadingTCPServer(("", 0), Proxy)
|
||||
proxy_process = multiprocessing.Process(target=httpd.serve_forever)
|
||||
proxy_process.start()
|
||||
yield 'http://{0}:{1}'.format(*httpd.server_address)
|
||||
yield "http://{}:{}".format(*httpd.server_address)
|
||||
proxy_process.terminate()
|
||||
|
||||
|
||||
def test_use_proxy(tmpdir, httpbin, proxy_server):
|
||||
'''Ensure that it works with a proxy.'''
|
||||
with vcr.use_cassette(str(tmpdir.join('proxy.yaml'))):
|
||||
response = requests.get(httpbin.url, proxies={'http': proxy_server})
|
||||
"""Ensure that it works with a proxy."""
|
||||
with vcr.use_cassette(str(tmpdir.join("proxy.yaml"))):
|
||||
response = requests.get(httpbin.url, proxies={"http": proxy_server})
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('proxy.yaml'))) as cassette:
|
||||
cassette_response = requests.get(httpbin.url, proxies={'http': proxy_server})
|
||||
with vcr.use_cassette(str(tmpdir.join("proxy.yaml"))) as cassette:
|
||||
cassette_response = requests.get(httpbin.url, proxies={"http": proxy_server})
|
||||
|
||||
assert cassette_response.headers == response.headers
|
||||
assert cassette.play_count == 1
|
||||
|
||||
@@ -4,7 +4,7 @@ from six.moves.urllib.request import urlopen
|
||||
|
||||
|
||||
def test_once_record_mode(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
testfile = str(tmpdir.join("recordmode.yml"))
|
||||
with vcr.use_cassette(testfile, record_mode="once"):
|
||||
# cassette file doesn't exist, so create.
|
||||
urlopen(httpbin.url).read()
|
||||
@@ -17,11 +17,11 @@ def test_once_record_mode(tmpdir, httpbin):
|
||||
# but, try to access something else from the same cassette, and an
|
||||
# exception is raised.
|
||||
with pytest.raises(Exception):
|
||||
urlopen(httpbin.url + '/get').read()
|
||||
urlopen(httpbin.url + "/get").read()
|
||||
|
||||
|
||||
def test_once_record_mode_two_times(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
testfile = str(tmpdir.join("recordmode.yml"))
|
||||
with vcr.use_cassette(testfile, record_mode="once"):
|
||||
# get two of the same file
|
||||
urlopen(httpbin.url).read()
|
||||
@@ -34,7 +34,7 @@ def test_once_record_mode_two_times(tmpdir, httpbin):
|
||||
|
||||
|
||||
def test_once_mode_three_times(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
testfile = str(tmpdir.join("recordmode.yml"))
|
||||
with vcr.use_cassette(testfile, record_mode="once"):
|
||||
# get three of the same file
|
||||
urlopen(httpbin.url).read()
|
||||
@@ -43,7 +43,7 @@ def test_once_mode_three_times(tmpdir, httpbin):
|
||||
|
||||
|
||||
def test_new_episodes_record_mode(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
testfile = str(tmpdir.join("recordmode.yml"))
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="new_episodes"):
|
||||
# cassette file doesn't exist, so create.
|
||||
@@ -58,7 +58,7 @@ def test_new_episodes_record_mode(tmpdir, httpbin):
|
||||
|
||||
# in the "new_episodes" record mode, we can add more requests to
|
||||
# a cassette without repurcussions.
|
||||
urlopen(httpbin.url + '/get').read()
|
||||
urlopen(httpbin.url + "/get").read()
|
||||
|
||||
# one of the responses has been played
|
||||
assert cass.play_count == 1
|
||||
@@ -72,8 +72,8 @@ def test_new_episodes_record_mode(tmpdir, httpbin):
|
||||
|
||||
|
||||
def test_new_episodes_record_mode_two_times(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
url = httpbin.url + '/bytes/1024'
|
||||
testfile = str(tmpdir.join("recordmode.yml"))
|
||||
url = httpbin.url + "/bytes/1024"
|
||||
with vcr.use_cassette(testfile, record_mode="new_episodes"):
|
||||
# cassette file doesn't exist, so create.
|
||||
original_first_response = urlopen(url).read()
|
||||
@@ -97,7 +97,7 @@ def test_new_episodes_record_mode_two_times(tmpdir, httpbin):
|
||||
|
||||
|
||||
def test_all_record_mode(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
testfile = str(tmpdir.join("recordmode.yml"))
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="all"):
|
||||
# cassette file doesn't exist, so create.
|
||||
@@ -109,7 +109,7 @@ def test_all_record_mode(tmpdir, httpbin):
|
||||
|
||||
# in the "all" record mode, we can add more requests to
|
||||
# a cassette without repurcussions.
|
||||
urlopen(httpbin.url + '/get').read()
|
||||
urlopen(httpbin.url + "/get").read()
|
||||
|
||||
# The cassette was never actually played, even though it existed.
|
||||
# that's because, in "all" mode, the requests all go directly to
|
||||
@@ -120,7 +120,7 @@ def test_all_record_mode(tmpdir, httpbin):
|
||||
def test_none_record_mode(tmpdir, httpbin):
|
||||
# Cassette file doesn't exist, yet we are trying to make a request.
|
||||
# raise hell.
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
testfile = str(tmpdir.join("recordmode.yml"))
|
||||
with vcr.use_cassette(testfile, record_mode="none"):
|
||||
with pytest.raises(Exception):
|
||||
urlopen(httpbin.url).read()
|
||||
@@ -128,7 +128,7 @@ def test_none_record_mode(tmpdir, httpbin):
|
||||
|
||||
def test_none_record_mode_with_existing_cassette(tmpdir, httpbin):
|
||||
# create a cassette file
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
testfile = str(tmpdir.join("recordmode.yml"))
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="all"):
|
||||
urlopen(httpbin.url).read()
|
||||
@@ -139,4 +139,4 @@ def test_none_record_mode_with_existing_cassette(tmpdir, httpbin):
|
||||
assert cass.play_count == 1
|
||||
# but if I try to hit the net, raise an exception.
|
||||
with pytest.raises(Exception):
|
||||
urlopen(httpbin.url + '/get').read()
|
||||
urlopen(httpbin.url + "/get").read()
|
||||
|
||||
@@ -12,25 +12,25 @@ def false_matcher(r1, r2):
|
||||
|
||||
def test_registered_true_matcher(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR()
|
||||
my_vcr.register_matcher('true', true_matcher)
|
||||
testfile = str(tmpdir.join('test.yml'))
|
||||
with my_vcr.use_cassette(testfile, match_on=['true']):
|
||||
my_vcr.register_matcher("true", true_matcher)
|
||||
testfile = str(tmpdir.join("test.yml"))
|
||||
with my_vcr.use_cassette(testfile, match_on=["true"]):
|
||||
# These 2 different urls are stored as the same request
|
||||
urlopen(httpbin.url)
|
||||
urlopen(httpbin.url + '/get')
|
||||
urlopen(httpbin.url + "/get")
|
||||
|
||||
with my_vcr.use_cassette(testfile, match_on=['true']):
|
||||
with my_vcr.use_cassette(testfile, match_on=["true"]):
|
||||
# I can get the response twice even though I only asked for it once
|
||||
urlopen(httpbin.url + '/get')
|
||||
urlopen(httpbin.url + '/get')
|
||||
urlopen(httpbin.url + "/get")
|
||||
urlopen(httpbin.url + "/get")
|
||||
|
||||
|
||||
def test_registered_false_matcher(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR()
|
||||
my_vcr.register_matcher('false', false_matcher)
|
||||
testfile = str(tmpdir.join('test.yml'))
|
||||
with my_vcr.use_cassette(testfile, match_on=['false']) as cass:
|
||||
my_vcr.register_matcher("false", false_matcher)
|
||||
testfile = str(tmpdir.join("test.yml"))
|
||||
with my_vcr.use_cassette(testfile, match_on=["false"]) as cass:
|
||||
# These 2 different urls are stored as different requests
|
||||
urlopen(httpbin.url)
|
||||
urlopen(httpbin.url + '/get')
|
||||
urlopen(httpbin.url + "/get")
|
||||
assert len(cass) == 2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''Tests for cassettes with custom persistence'''
|
||||
"""Tests for cassettes with custom persistence"""
|
||||
|
||||
# External imports
|
||||
import os
|
||||
@@ -11,45 +11,45 @@ from vcr.persisters.filesystem import FilesystemPersister
|
||||
|
||||
|
||||
class CustomFilesystemPersister(object):
|
||||
'''Behaves just like default FilesystemPersister but adds .test extension
|
||||
to the cassette file'''
|
||||
"""Behaves just like default FilesystemPersister but adds .test extension
|
||||
to the cassette file"""
|
||||
|
||||
@staticmethod
|
||||
def load_cassette(cassette_path, serializer):
|
||||
cassette_path += '.test'
|
||||
cassette_path += ".test"
|
||||
return FilesystemPersister.load_cassette(cassette_path, serializer)
|
||||
|
||||
@staticmethod
|
||||
def save_cassette(cassette_path, cassette_dict, serializer):
|
||||
cassette_path += '.test'
|
||||
FilesystemPersister.save_cassette(cassette_path, cassette_dict,
|
||||
serializer)
|
||||
cassette_path += ".test"
|
||||
FilesystemPersister.save_cassette(cassette_path, cassette_dict, serializer)
|
||||
|
||||
|
||||
def test_save_cassette_with_custom_persister(tmpdir, httpbin):
|
||||
'''Ensure you can save a cassette using custom persister'''
|
||||
"""Ensure you can save a cassette using custom persister"""
|
||||
my_vcr = vcr.VCR()
|
||||
my_vcr.register_persister(CustomFilesystemPersister)
|
||||
|
||||
# Check to make sure directory doesnt exist
|
||||
assert not os.path.exists(str(tmpdir.join('nonexistent')))
|
||||
assert not os.path.exists(str(tmpdir.join("nonexistent")))
|
||||
|
||||
# Run VCR to create dir and cassette file using new save_cassette callback
|
||||
with my_vcr.use_cassette(str(tmpdir.join('nonexistent', 'cassette.yml'))):
|
||||
with my_vcr.use_cassette(str(tmpdir.join("nonexistent", "cassette.yml"))):
|
||||
urlopen(httpbin.url).read()
|
||||
|
||||
# Callback should have made the file and the directory
|
||||
assert os.path.exists(str(tmpdir.join('nonexistent', 'cassette.yml.test')))
|
||||
assert os.path.exists(str(tmpdir.join("nonexistent", "cassette.yml.test")))
|
||||
|
||||
|
||||
def test_load_cassette_with_custom_persister(tmpdir, httpbin):
|
||||
'''
|
||||
"""
|
||||
Ensure you can load a cassette using custom persister
|
||||
'''
|
||||
"""
|
||||
my_vcr = vcr.VCR()
|
||||
my_vcr.register_persister(CustomFilesystemPersister)
|
||||
|
||||
test_fixture = str(tmpdir.join('synopsis.json.test'))
|
||||
test_fixture = str(tmpdir.join("synopsis.json.test"))
|
||||
|
||||
with my_vcr.use_cassette(test_fixture, serializer='json'):
|
||||
with my_vcr.use_cassette(test_fixture, serializer="json"):
|
||||
response = urlopen(httpbin.url).read()
|
||||
assert b'difficult sometimes' in response
|
||||
assert b"difficult sometimes" in response
|
||||
|
||||
@@ -10,7 +10,7 @@ class MockSerializer(object):
|
||||
def deserialize(self, cassette_string):
|
||||
self.serialize_count += 1
|
||||
self.cassette_string = cassette_string
|
||||
return {'interactions': []}
|
||||
return {"interactions": []}
|
||||
|
||||
def serialize(self, cassette_dict):
|
||||
self.deserialize_count += 1
|
||||
@@ -20,13 +20,13 @@ class MockSerializer(object):
|
||||
def test_registered_serializer(tmpdir):
|
||||
ms = MockSerializer()
|
||||
my_vcr = vcr.VCR()
|
||||
my_vcr.register_serializer('mock', ms)
|
||||
tmpdir.join('test.mock').write('test_data')
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.mock')), serializer='mock'):
|
||||
my_vcr.register_serializer("mock", ms)
|
||||
tmpdir.join("test.mock").write("test_data")
|
||||
with my_vcr.use_cassette(str(tmpdir.join("test.mock")), serializer="mock"):
|
||||
# Serializer deserialized once
|
||||
assert ms.serialize_count == 1
|
||||
# and serialized the test data string
|
||||
assert ms.cassette_string == 'test_data'
|
||||
assert ms.cassette_string == "test_data"
|
||||
# and hasn't serialized yet
|
||||
assert ms.deserialize_count == 0
|
||||
|
||||
|
||||
@@ -3,17 +3,17 @@ from six.moves.urllib.request import urlopen
|
||||
|
||||
|
||||
def test_recorded_request_uri_with_redirected_request(tmpdir, httpbin):
|
||||
with vcr.use_cassette(str(tmpdir.join('test.yml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("test.yml"))) as cass:
|
||||
assert len(cass) == 0
|
||||
urlopen(httpbin.url + '/redirect/3')
|
||||
assert cass.requests[0].uri == httpbin.url + '/redirect/3'
|
||||
assert cass.requests[3].uri == httpbin.url + '/get'
|
||||
urlopen(httpbin.url + "/redirect/3")
|
||||
assert cass.requests[0].uri == httpbin.url + "/redirect/3"
|
||||
assert cass.requests[3].uri == httpbin.url + "/get"
|
||||
assert len(cass) == 4
|
||||
|
||||
|
||||
def test_records_multiple_header_values(tmpdir, httpbin):
|
||||
with vcr.use_cassette(str(tmpdir.join('test.yml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("test.yml"))) as cass:
|
||||
assert len(cass) == 0
|
||||
urlopen(httpbin.url + '/response-headers?foo=bar&foo=baz')
|
||||
urlopen(httpbin.url + "/response-headers?foo=bar&foo=baz")
|
||||
assert len(cass) == 1
|
||||
assert cass.responses[0]['headers']['foo'] == ['bar', 'baz']
|
||||
assert cass.responses[0]["headers"]["foo"] == ["bar", "baz"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''Test requests' interaction with vcr'''
|
||||
"""Test requests' interaction with vcr"""
|
||||
import platform
|
||||
import pytest
|
||||
import sys
|
||||
@@ -11,76 +11,76 @@ from requests.exceptions import ConnectionError # noqa E402
|
||||
|
||||
|
||||
def test_status_code(httpbin_both, tmpdir):
|
||||
'''Ensure that we can read the status code'''
|
||||
url = httpbin_both.url + '/'
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
||||
"""Ensure that we can read the status code"""
|
||||
url = httpbin_both.url + "/"
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
status_code = requests.get(url).status_code
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
assert status_code == requests.get(url).status_code
|
||||
|
||||
|
||||
def test_headers(httpbin_both, tmpdir):
|
||||
'''Ensure that we can read the headers back'''
|
||||
url = httpbin_both + '/'
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
"""Ensure that we can read the headers back"""
|
||||
url = httpbin_both + "/"
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
headers = requests.get(url).headers
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
assert headers == requests.get(url).headers
|
||||
|
||||
|
||||
def test_body(tmpdir, httpbin_both):
|
||||
'''Ensure the responses are all identical enough'''
|
||||
url = httpbin_both + '/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
||||
"""Ensure the responses are all identical enough"""
|
||||
url = httpbin_both + "/bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
content = requests.get(url).content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
assert content == requests.get(url).content
|
||||
|
||||
|
||||
def test_get_empty_content_type_json(tmpdir, httpbin_both):
|
||||
'''Ensure GET with application/json content-type and empty request body doesn't crash'''
|
||||
url = httpbin_both + '/status/200'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
"""Ensure GET with application/json content-type and empty request body doesn't crash"""
|
||||
url = httpbin_both + "/status/200"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get_empty_json.yaml')), match_on=('body',)):
|
||||
with vcr.use_cassette(str(tmpdir.join("get_empty_json.yaml")), match_on=("body",)):
|
||||
status = requests.get(url, headers=headers).status_code
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get_empty_json.yaml')), match_on=('body',)):
|
||||
with vcr.use_cassette(str(tmpdir.join("get_empty_json.yaml")), match_on=("body",)):
|
||||
assert status == requests.get(url, headers=headers).status_code
|
||||
|
||||
|
||||
def test_effective_url(tmpdir, httpbin_both):
|
||||
'''Ensure that the effective_url is captured'''
|
||||
url = httpbin_both.url + '/redirect-to?url=/html'
|
||||
with vcr.use_cassette(str(tmpdir.join('url.yaml'))):
|
||||
"""Ensure that the effective_url is captured"""
|
||||
url = httpbin_both.url + "/redirect-to?url=/html"
|
||||
with vcr.use_cassette(str(tmpdir.join("url.yaml"))):
|
||||
effective_url = requests.get(url).url
|
||||
assert effective_url == httpbin_both.url + '/html'
|
||||
assert effective_url == httpbin_both.url + "/html"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('url.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("url.yaml"))):
|
||||
assert effective_url == requests.get(url).url
|
||||
|
||||
|
||||
def test_auth(tmpdir, httpbin_both):
|
||||
'''Ensure that we can handle basic auth'''
|
||||
auth = ('user', 'passwd')
|
||||
url = httpbin_both + '/basic-auth/user/passwd'
|
||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
|
||||
"""Ensure that we can handle basic auth"""
|
||||
auth = ("user", "passwd")
|
||||
url = httpbin_both + "/basic-auth/user/passwd"
|
||||
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))):
|
||||
one = requests.get(url, auth=auth)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))):
|
||||
two = requests.get(url, auth=auth)
|
||||
assert one.content == two.content
|
||||
assert one.status_code == two.status_code
|
||||
|
||||
|
||||
def test_auth_failed(tmpdir, httpbin_both):
|
||||
'''Ensure that we can save failed auth statuses'''
|
||||
auth = ('user', 'wrongwrongwrong')
|
||||
url = httpbin_both + '/basic-auth/user/passwd'
|
||||
with vcr.use_cassette(str(tmpdir.join('auth-failed.yaml'))) as cass:
|
||||
"""Ensure that we can save failed auth statuses"""
|
||||
auth = ("user", "wrongwrongwrong")
|
||||
url = httpbin_both + "/basic-auth/user/passwd"
|
||||
with vcr.use_cassette(str(tmpdir.join("auth-failed.yaml"))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
one = requests.get(url, auth=auth)
|
||||
@@ -90,58 +90,59 @@ def test_auth_failed(tmpdir, httpbin_both):
|
||||
|
||||
|
||||
def test_post(tmpdir, httpbin_both):
|
||||
'''Ensure that we can post and cache the results'''
|
||||
data = {'key1': 'value1', 'key2': 'value2'}
|
||||
url = httpbin_both + '/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
||||
"""Ensure that we can post and cache the results"""
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
url = httpbin_both + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||
req1 = requests.post(url, data).content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||
req2 = requests.post(url, data).content
|
||||
|
||||
assert req1 == req2
|
||||
|
||||
|
||||
def test_post_chunked_binary(tmpdir, httpbin):
|
||||
'''Ensure that we can send chunked binary without breaking while trying to concatenate bytes with str.'''
|
||||
data1 = iter([b'data', b'to', b'send'])
|
||||
data2 = iter([b'data', b'to', b'send'])
|
||||
url = httpbin.url + '/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
||||
"""Ensure that we can send chunked binary without breaking while trying to concatenate bytes with str."""
|
||||
data1 = iter([b"data", b"to", b"send"])
|
||||
data2 = iter([b"data", b"to", b"send"])
|
||||
url = httpbin.url + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||
req1 = requests.post(url, data1).content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||
req2 = requests.post(url, data2).content
|
||||
|
||||
assert req1 == req2
|
||||
|
||||
|
||||
@pytest.mark.xskip('sys.version_info >= (3, 6)', strict=True, raises=ConnectionError)
|
||||
@pytest.mark.xskip((3, 5) < sys.version_info < (3, 6) and
|
||||
platform.python_implementation() == 'CPython',
|
||||
reason='Fails on CPython 3.5')
|
||||
@pytest.mark.skipif("sys.version_info >= (3, 6)", strict=True, raises=ConnectionError)
|
||||
@pytest.mark.skipif(
|
||||
(3, 5) < sys.version_info < (3, 6) and platform.python_implementation() == "CPython",
|
||||
reason="Fails on CPython 3.5",
|
||||
)
|
||||
def test_post_chunked_binary_secure(tmpdir, httpbin_secure):
|
||||
'''Ensure that we can send chunked binary without breaking while trying to concatenate bytes with str.'''
|
||||
data1 = iter([b'data', b'to', b'send'])
|
||||
data2 = iter([b'data', b'to', b'send'])
|
||||
url = httpbin_secure.url + '/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
||||
"""Ensure that we can send chunked binary without breaking while trying to concatenate bytes with str."""
|
||||
data1 = iter([b"data", b"to", b"send"])
|
||||
data2 = iter([b"data", b"to", b"send"])
|
||||
url = httpbin_secure.url + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||
req1 = requests.post(url, data1).content
|
||||
print(req1)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||
req2 = requests.post(url, data2).content
|
||||
|
||||
assert req1 == req2
|
||||
|
||||
|
||||
def test_redirects(tmpdir, httpbin_both):
|
||||
'''Ensure that we can handle redirects'''
|
||||
url = httpbin_both + '/redirect-to?url=bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
||||
"""Ensure that we can handle redirects"""
|
||||
url = httpbin_both + "/redirect-to?url=bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||
content = requests.get(url).content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))) as cass:
|
||||
assert content == requests.get(url).content
|
||||
# Ensure that we've now cached *two* responses. One for the redirect
|
||||
# and one for the final fetch
|
||||
@@ -150,135 +151,151 @@ def test_redirects(tmpdir, httpbin_both):
|
||||
|
||||
|
||||
def test_cross_scheme(tmpdir, httpbin_secure, httpbin):
|
||||
'''Ensure that requests between schemes are treated separately'''
|
||||
"""Ensure that requests between schemes are treated separately"""
|
||||
# First fetch a url under http, and then again under https and then
|
||||
# ensure that we haven't served anything out of cache, and we have two
|
||||
# requests / response pairs in the cassette
|
||||
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
|
||||
requests.get(httpbin_secure + '/')
|
||||
requests.get(httpbin + '/')
|
||||
with vcr.use_cassette(str(tmpdir.join("cross_scheme.yaml"))) as cass:
|
||||
requests.get(httpbin_secure + "/")
|
||||
requests.get(httpbin + "/")
|
||||
assert cass.play_count == 0
|
||||
assert len(cass) == 2
|
||||
|
||||
|
||||
def test_gzip(tmpdir, httpbin_both):
|
||||
'''
|
||||
"""
|
||||
Ensure that requests (actually urllib3) is able to automatically decompress
|
||||
the response body
|
||||
'''
|
||||
url = httpbin_both + '/gzip'
|
||||
"""
|
||||
url = httpbin_both + "/gzip"
|
||||
response = requests.get(url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("gzip.yaml"))):
|
||||
response = requests.get(url)
|
||||
assert_is_json(response.content)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("gzip.yaml"))):
|
||||
assert_is_json(response.content)
|
||||
|
||||
|
||||
def test_session_and_connection_close(tmpdir, httpbin):
|
||||
'''
|
||||
"""
|
||||
This tests the issue in https://github.com/kevin1024/vcrpy/issues/48
|
||||
|
||||
If you use a requests.session and the connection is closed, then an
|
||||
exception is raised in the urllib3 module vendored into requests:
|
||||
`AttributeError: 'NoneType' object has no attribute 'settimeout'`
|
||||
'''
|
||||
with vcr.use_cassette(str(tmpdir.join('session_connection_closed.yaml'))):
|
||||
"""
|
||||
with vcr.use_cassette(str(tmpdir.join("session_connection_closed.yaml"))):
|
||||
session = requests.session()
|
||||
|
||||
session.get(httpbin + '/get', headers={'Connection': 'close'})
|
||||
session.get(httpbin + '/get', headers={'Connection': 'close'})
|
||||
session.get(httpbin + "/get", headers={"Connection": "close"})
|
||||
session.get(httpbin + "/get", headers={"Connection": "close"})
|
||||
|
||||
|
||||
def test_https_with_cert_validation_disabled(tmpdir, httpbin_secure):
|
||||
with vcr.use_cassette(str(tmpdir.join('cert_validation_disabled.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("cert_validation_disabled.yaml"))):
|
||||
requests.get(httpbin_secure.url, verify=False)
|
||||
|
||||
|
||||
def test_session_can_make_requests_after_requests_unpatched(tmpdir, httpbin):
|
||||
with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("test_session_after_unpatched.yaml"))):
|
||||
session = requests.session()
|
||||
session.get(httpbin + '/get')
|
||||
session.get(httpbin + "/get")
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("test_session_after_unpatched.yaml"))):
|
||||
session = requests.session()
|
||||
session.get(httpbin + '/get')
|
||||
session.get(httpbin + "/get")
|
||||
|
||||
session.get(httpbin + '/status/200')
|
||||
session.get(httpbin + "/status/200")
|
||||
|
||||
|
||||
def test_session_created_before_use_cassette_is_patched(tmpdir, httpbin_both):
|
||||
url = httpbin_both + '/bytes/1024'
|
||||
url = httpbin_both + "/bytes/1024"
|
||||
# Record arbitrary, random data to the cassette
|
||||
with vcr.use_cassette(str(tmpdir.join('session_created_outside.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("session_created_outside.yaml"))):
|
||||
session = requests.session()
|
||||
body = session.get(url).content
|
||||
|
||||
# Create a session outside of any cassette context manager
|
||||
session = requests.session()
|
||||
# Make a request to make sure that a connectionpool is instantiated
|
||||
session.get(httpbin_both + '/get')
|
||||
session.get(httpbin_both + "/get")
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('session_created_outside.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("session_created_outside.yaml"))):
|
||||
# These should only be the same if the patching succeeded.
|
||||
assert session.get(url).content == body
|
||||
|
||||
|
||||
def test_nested_cassettes_with_session_created_before_nesting(httpbin_both, tmpdir):
|
||||
'''
|
||||
"""
|
||||
This tests ensures that a session that was created while one cassette was
|
||||
active is patched to the use the responses of a second cassette when it
|
||||
is enabled.
|
||||
'''
|
||||
url = httpbin_both + '/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('first_nested.yaml'))):
|
||||
"""
|
||||
url = httpbin_both + "/bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("first_nested.yaml"))):
|
||||
session = requests.session()
|
||||
first_body = session.get(url).content
|
||||
with vcr.use_cassette(str(tmpdir.join('second_nested.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("second_nested.yaml"))):
|
||||
second_body = session.get(url).content
|
||||
third_body = requests.get(url).content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('second_nested.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("second_nested.yaml"))):
|
||||
session = requests.session()
|
||||
assert session.get(url).content == second_body
|
||||
with vcr.use_cassette(str(tmpdir.join('first_nested.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("first_nested.yaml"))):
|
||||
assert session.get(url).content == first_body
|
||||
assert session.get(url).content == third_body
|
||||
|
||||
# Make sure that the session can now get content normally.
|
||||
assert 'User-agent' in session.get(httpbin_both.url + '/robots.txt').text
|
||||
assert "User-agent" in session.get(httpbin_both.url + "/robots.txt").text
|
||||
|
||||
|
||||
def test_post_file(tmpdir, httpbin_both):
|
||||
'''Ensure that we handle posting a file.'''
|
||||
url = httpbin_both + '/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_file.yaml'))) as cass, open('tox.ini') as f:
|
||||
"""Ensure that we handle posting a file."""
|
||||
url = httpbin_both + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("post_file.yaml"))) as cass, open("tox.ini", "rb") as f:
|
||||
original_response = requests.post(url, f).content
|
||||
|
||||
# This also tests that we do the right thing with matching the body when they are files.
|
||||
with vcr.use_cassette(str(tmpdir.join('post_file.yaml')),
|
||||
match_on=('method', 'scheme', 'host', 'port', 'path', 'query', 'body')) as cass:
|
||||
with open('tox.ini', 'rb') as f:
|
||||
with vcr.use_cassette(
|
||||
str(tmpdir.join("post_file.yaml")),
|
||||
match_on=("method", "scheme", "host", "port", "path", "query", "body"),
|
||||
) as cass:
|
||||
with open("tox.ini", "rb") as f:
|
||||
tox_content = f.read()
|
||||
assert cass.requests[0].body.read() == tox_content
|
||||
with open('tox.ini', 'rb') as f:
|
||||
with open("tox.ini", "rb") as f:
|
||||
new_response = requests.post(url, f).content
|
||||
assert original_response == new_response
|
||||
|
||||
|
||||
def test_filter_post_params(tmpdir, httpbin_both):
|
||||
'''
|
||||
"""
|
||||
This tests the issue in https://github.com/kevin1024/vcrpy/issues/158
|
||||
|
||||
Ensure that a post request made through requests can still be filtered.
|
||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=['id']) as cass:
|
||||
assert b'id=secret' not in cass.requests[0].body
|
||||
'''
|
||||
url = httpbin_both.url + '/post'
|
||||
cass_loc = str(tmpdir.join('filter_post_params.yaml'))
|
||||
with vcr.use_cassette(cass_loc, filter_post_data_parameters=['key']) as cass:
|
||||
requests.post(url, data={'key': 'value'})
|
||||
with vcr.use_cassette(cass_loc, filter_post_data_parameters=['key']) as cass:
|
||||
assert b'key=value' not in cass.requests[0].body
|
||||
"""
|
||||
url = httpbin_both.url + "/post"
|
||||
cass_loc = str(tmpdir.join("filter_post_params.yaml"))
|
||||
with vcr.use_cassette(cass_loc, filter_post_data_parameters=["key"]) as cass:
|
||||
requests.post(url, data={"key": "value"})
|
||||
with vcr.use_cassette(cass_loc, filter_post_data_parameters=["key"]) as cass:
|
||||
assert b"key=value" not in cass.requests[0].body
|
||||
|
||||
|
||||
def test_post_unicode_match_on_body(tmpdir, httpbin_both):
|
||||
"""Ensure that matching on POST body that contains Unicode characters works."""
|
||||
data = {"key1": "value1", "●‿●": "٩(●̮̮̃•̃)۶"}
|
||||
url = httpbin_both + "/post"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml")), additional_matchers=("body",)):
|
||||
req1 = requests.post(url, data).content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml")), additional_matchers=("body",)):
|
||||
req2 = requests.post(url, data).content
|
||||
|
||||
assert req1 == req2
|
||||
|
||||
@@ -8,18 +8,18 @@ from assertions import assert_is_json
|
||||
|
||||
def _headers_are_case_insensitive(host, port):
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', "/cookies/set?k1=v1")
|
||||
conn.request("GET", "/cookies/set?k1=v1")
|
||||
r1 = conn.getresponse()
|
||||
cookie_data1 = r1.getheader('set-cookie')
|
||||
cookie_data1 = r1.getheader("set-cookie")
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', "/cookies/set?k1=v1")
|
||||
conn.request("GET", "/cookies/set?k1=v1")
|
||||
r2 = conn.getresponse()
|
||||
cookie_data2 = r2.getheader('Set-Cookie')
|
||||
cookie_data2 = r2.getheader("Set-Cookie")
|
||||
return cookie_data1 == cookie_data2
|
||||
|
||||
|
||||
def test_case_insensitivity(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('case_insensitivity.yml'))
|
||||
testfile = str(tmpdir.join("case_insensitivity.yml"))
|
||||
# check if headers are case insensitive outside of vcrpy
|
||||
host, port = httpbin.host, httpbin.port
|
||||
outside = _headers_are_case_insensitive(host, port)
|
||||
@@ -35,13 +35,13 @@ def test_case_insensitivity(tmpdir, httpbin):
|
||||
|
||||
def _multiple_header_value(httpbin):
|
||||
conn = httplib.HTTPConnection(httpbin.host, httpbin.port)
|
||||
conn.request('GET', "/response-headers?foo=bar&foo=baz")
|
||||
conn.request("GET", "/response-headers?foo=bar&foo=baz")
|
||||
r = conn.getresponse()
|
||||
return r.getheader('foo')
|
||||
return r.getheader("foo")
|
||||
|
||||
|
||||
def test_multiple_headers(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('multiple_headers.yaml'))
|
||||
testfile = str(tmpdir.join("multiple_headers.yaml"))
|
||||
outside = _multiple_header_value(httpbin)
|
||||
|
||||
with vcr.use_cassette(testfile):
|
||||
@@ -51,83 +51,84 @@ def test_multiple_headers(tmpdir, httpbin):
|
||||
|
||||
|
||||
def test_original_decoded_response_is_not_modified(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('decoded_response.yml'))
|
||||
testfile = str(tmpdir.join("decoded_response.yml"))
|
||||
host, port = httpbin.host, httpbin.port
|
||||
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', '/gzip')
|
||||
conn.request("GET", "/gzip")
|
||||
outside = conn.getresponse()
|
||||
|
||||
with vcr.use_cassette(testfile, decode_compressed_response=True):
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', '/gzip')
|
||||
conn.request("GET", "/gzip")
|
||||
inside = conn.getresponse()
|
||||
|
||||
# Assert that we do not modify the original response while appending
|
||||
# to the casssette.
|
||||
assert 'gzip' == inside.headers['content-encoding']
|
||||
assert "gzip" == inside.headers["content-encoding"]
|
||||
|
||||
# They should effectively be the same response.
|
||||
inside_headers = (h for h in inside.headers.items() if h[0].lower() != 'date')
|
||||
outside_headers = (h for h in outside.getheaders() if h[0].lower() != 'date')
|
||||
inside_headers = (h for h in inside.headers.items() if h[0].lower() != "date")
|
||||
outside_headers = (h for h in outside.getheaders() if h[0].lower() != "date")
|
||||
assert set(inside_headers) == set(outside_headers)
|
||||
inside = zlib.decompress(inside.read(), 16+zlib.MAX_WBITS)
|
||||
outside = zlib.decompress(outside.read(), 16+zlib.MAX_WBITS)
|
||||
inside = zlib.decompress(inside.read(), 16 + zlib.MAX_WBITS)
|
||||
outside = zlib.decompress(outside.read(), 16 + zlib.MAX_WBITS)
|
||||
assert inside == outside
|
||||
|
||||
# Even though the above are raw bytes, the JSON data should have been
|
||||
# decoded and saved to the cassette.
|
||||
with vcr.use_cassette(testfile):
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', '/gzip')
|
||||
conn.request("GET", "/gzip")
|
||||
inside = conn.getresponse()
|
||||
|
||||
assert 'content-encoding' not in inside.headers
|
||||
assert "content-encoding" not in inside.headers
|
||||
assert_is_json(inside.read())
|
||||
|
||||
|
||||
def _make_before_record_response(fields, replacement='[REDACTED]'):
|
||||
def _make_before_record_response(fields, replacement="[REDACTED]"):
|
||||
def before_record_response(response):
|
||||
string_body = response['body']['string'].decode('utf8')
|
||||
string_body = response["body"]["string"].decode("utf8")
|
||||
body = json.loads(string_body)
|
||||
|
||||
for field in fields:
|
||||
if field in body:
|
||||
body[field] = replacement
|
||||
|
||||
response['body']['string'] = json.dumps(body).encode()
|
||||
response["body"]["string"] = json.dumps(body).encode()
|
||||
return response
|
||||
|
||||
return before_record_response
|
||||
|
||||
|
||||
def test_original_response_is_not_modified_by_before_filter(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('sensitive_data_scrubbed_response.yml'))
|
||||
testfile = str(tmpdir.join("sensitive_data_scrubbed_response.yml"))
|
||||
host, port = httpbin.host, httpbin.port
|
||||
field_to_scrub = 'url'
|
||||
replacement = '[YOU_CANT_HAVE_THE_MANGO]'
|
||||
field_to_scrub = "url"
|
||||
replacement = "[YOU_CANT_HAVE_THE_MANGO]"
|
||||
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', '/get')
|
||||
conn.request("GET", "/get")
|
||||
outside = conn.getresponse()
|
||||
|
||||
callback = _make_before_record_response([field_to_scrub], replacement)
|
||||
with vcr.use_cassette(testfile, before_record_response=callback):
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', '/get')
|
||||
conn.request("GET", "/get")
|
||||
inside = conn.getresponse()
|
||||
|
||||
# The scrubbed field should be the same, because no cassette existed.
|
||||
# Furthermore, the responses should be identical.
|
||||
inside_body = json.loads(inside.read().decode('utf-8'))
|
||||
outside_body = json.loads(outside.read().decode('utf-8'))
|
||||
inside_body = json.loads(inside.read().decode("utf-8"))
|
||||
outside_body = json.loads(outside.read().decode("utf-8"))
|
||||
assert not inside_body[field_to_scrub] == replacement
|
||||
assert inside_body[field_to_scrub] == outside_body[field_to_scrub]
|
||||
|
||||
# Ensure that when a cassette exists, the scrubbed response is returned.
|
||||
with vcr.use_cassette(testfile, before_record_response=callback):
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', '/get')
|
||||
conn.request("GET", "/get")
|
||||
inside = conn.getresponse()
|
||||
|
||||
inside_body = json.loads(inside.read().decode('utf-8'))
|
||||
inside_body = json.loads(inside.read().decode("utf-8"))
|
||||
assert inside_body[field_to_scrub] == replacement
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''Test requests' interaction with vcr'''
|
||||
"""Test requests' interaction with vcr"""
|
||||
|
||||
import json
|
||||
|
||||
@@ -17,105 +17,99 @@ http = pytest.importorskip("tornado.httpclient")
|
||||
supports_raise_error = tornado.version_info >= (4,)
|
||||
|
||||
|
||||
@pytest.fixture(params=['simple', 'curl', 'default'])
|
||||
@pytest.fixture(params=["simple", "curl", "default"])
|
||||
def get_client(request):
|
||||
if request.param == 'simple':
|
||||
if request.param == "simple":
|
||||
from tornado import simple_httpclient as simple
|
||||
return (lambda: simple.SimpleAsyncHTTPClient())
|
||||
elif request.param == 'curl':
|
||||
|
||||
return lambda: simple.SimpleAsyncHTTPClient()
|
||||
elif request.param == "curl":
|
||||
curl = pytest.importorskip("tornado.curl_httpclient")
|
||||
return (lambda: curl.CurlAsyncHTTPClient())
|
||||
return lambda: curl.CurlAsyncHTTPClient()
|
||||
else:
|
||||
return (lambda: http.AsyncHTTPClient())
|
||||
return lambda: http.AsyncHTTPClient()
|
||||
|
||||
|
||||
def get(client, url, **kwargs):
|
||||
fetch_kwargs = {}
|
||||
if supports_raise_error:
|
||||
fetch_kwargs['raise_error'] = kwargs.pop('raise_error', True)
|
||||
fetch_kwargs["raise_error"] = kwargs.pop("raise_error", True)
|
||||
|
||||
return client.fetch(
|
||||
http.HTTPRequest(url, method='GET', **kwargs),
|
||||
**fetch_kwargs
|
||||
)
|
||||
return client.fetch(http.HTTPRequest(url, method="GET", **kwargs), **fetch_kwargs)
|
||||
|
||||
|
||||
def post(client, url, data=None, **kwargs):
|
||||
if data:
|
||||
kwargs['body'] = json.dumps(data)
|
||||
return client.fetch(http.HTTPRequest(url, method='POST', **kwargs))
|
||||
kwargs["body"] = json.dumps(data)
|
||||
return client.fetch(http.HTTPRequest(url, method="POST", **kwargs))
|
||||
|
||||
|
||||
@pytest.fixture(params=["https", "http"])
|
||||
def scheme(request):
|
||||
'''Fixture that returns both http and https.'''
|
||||
"""Fixture that returns both http and https."""
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_status_code(get_client, scheme, tmpdir):
|
||||
'''Ensure that we can read the status code'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
||||
"""Ensure that we can read the status code"""
|
||||
url = scheme + "://httpbin.org/"
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
status_code = (yield get(get_client(), url)).code
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))) as cass:
|
||||
assert status_code == (yield get(get_client(), url)).code
|
||||
assert 1 == cass.play_count
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_headers(get_client, scheme, tmpdir):
|
||||
'''Ensure that we can read the headers back'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
"""Ensure that we can read the headers back"""
|
||||
url = scheme + "://httpbin.org/"
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
headers = (yield get(get_client(), url)).headers
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))) as cass:
|
||||
assert headers == (yield get(get_client(), url)).headers
|
||||
assert 1 == cass.play_count
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_body(get_client, tmpdir, scheme):
|
||||
'''Ensure the responses are all identical enough'''
|
||||
"""Ensure the responses are all identical enough"""
|
||||
|
||||
url = scheme + '://httpbin.org/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
||||
url = scheme + "://httpbin.org/bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
content = (yield get(get_client(), url)).body
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))) as cass:
|
||||
assert content == (yield get(get_client(), url)).body
|
||||
assert 1 == cass.play_count
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_effective_url(get_client, scheme, tmpdir):
|
||||
'''Ensure that the effective_url is captured'''
|
||||
url = scheme + '://httpbin.org/redirect-to?url=/html'
|
||||
with vcr.use_cassette(str(tmpdir.join('url.yaml'))):
|
||||
"""Ensure that the effective_url is captured"""
|
||||
url = scheme + "://httpbin.org/redirect-to?url=/html"
|
||||
with vcr.use_cassette(str(tmpdir.join("url.yaml"))):
|
||||
effective_url = (yield get(get_client(), url)).effective_url
|
||||
assert effective_url == scheme + '://httpbin.org/html'
|
||||
assert effective_url == scheme + "://httpbin.org/html"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('url.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("url.yaml"))) as cass:
|
||||
assert effective_url == (yield get(get_client(), url)).effective_url
|
||||
assert 1 == cass.play_count
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auth(get_client, tmpdir, scheme):
|
||||
'''Ensure that we can handle basic auth'''
|
||||
auth = ('user', 'passwd')
|
||||
url = scheme + '://httpbin.org/basic-auth/user/passwd'
|
||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
|
||||
one = yield get(
|
||||
get_client(), url, auth_username=auth[0], auth_password=auth[1]
|
||||
)
|
||||
"""Ensure that we can handle basic auth"""
|
||||
auth = ("user", "passwd")
|
||||
url = scheme + "://httpbin.org/basic-auth/user/passwd"
|
||||
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))):
|
||||
one = yield get(get_client(), url, auth_username=auth[0], auth_password=auth[1])
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))) as cass:
|
||||
two = yield get(
|
||||
get_client(), url, auth_username=auth[0], auth_password=auth[1]
|
||||
)
|
||||
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))) as cass:
|
||||
two = yield get(get_client(), url, auth_username=auth[0], auth_password=auth[1])
|
||||
assert one.body == two.body
|
||||
assert one.code == two.code
|
||||
assert 1 == cass.play_count
|
||||
@@ -123,30 +117,20 @@ def test_auth(get_client, tmpdir, scheme):
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auth_failed(get_client, tmpdir, scheme):
|
||||
'''Ensure that we can save failed auth statuses'''
|
||||
auth = ('user', 'wrongwrongwrong')
|
||||
url = scheme + '://httpbin.org/basic-auth/user/passwd'
|
||||
with vcr.use_cassette(str(tmpdir.join('auth-failed.yaml'))) as cass:
|
||||
"""Ensure that we can save failed auth statuses"""
|
||||
auth = ("user", "wrongwrongwrong")
|
||||
url = scheme + "://httpbin.org/basic-auth/user/passwd"
|
||||
with vcr.use_cassette(str(tmpdir.join("auth-failed.yaml"))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
with pytest.raises(http.HTTPError) as exc_info:
|
||||
yield get(
|
||||
get_client(),
|
||||
url,
|
||||
auth_username=auth[0],
|
||||
auth_password=auth[1],
|
||||
)
|
||||
yield get(get_client(), url, auth_username=auth[0], auth_password=auth[1])
|
||||
one = exc_info.value.response
|
||||
assert exc_info.value.code == 401
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('auth-failed.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("auth-failed.yaml"))) as cass:
|
||||
with pytest.raises(http.HTTPError) as exc_info:
|
||||
two = yield get(
|
||||
get_client(),
|
||||
url,
|
||||
auth_username=auth[0],
|
||||
auth_password=auth[1],
|
||||
)
|
||||
two = yield get(get_client(), url, auth_username=auth[0], auth_password=auth[1])
|
||||
two = exc_info.value.response
|
||||
assert exc_info.value.code == 401
|
||||
assert one.body == two.body
|
||||
@@ -156,13 +140,13 @@ def test_auth_failed(get_client, tmpdir, scheme):
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_post(get_client, tmpdir, scheme):
|
||||
'''Ensure that we can post and cache the results'''
|
||||
data = {'key1': 'value1', 'key2': 'value2'}
|
||||
url = scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
||||
"""Ensure that we can post and cache the results"""
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
url = scheme + "://httpbin.org/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||
req1 = (yield post(get_client(), url, data)).body
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))) as cass:
|
||||
req2 = (yield post(get_client(), url, data)).body
|
||||
|
||||
assert req1 == req2
|
||||
@@ -171,55 +155,55 @@ def test_post(get_client, tmpdir, scheme):
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_redirects(get_client, tmpdir, scheme):
|
||||
'''Ensure that we can handle redirects'''
|
||||
url = scheme + '://httpbin.org/redirect-to?url=bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
||||
"""Ensure that we can handle redirects"""
|
||||
url = scheme + "://httpbin.org/redirect-to?url=bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||
content = (yield get(get_client(), url)).body
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))) as cass:
|
||||
assert content == (yield get(get_client(), url)).body
|
||||
assert cass.play_count == 1
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_cross_scheme(get_client, tmpdir, scheme):
|
||||
'''Ensure that requests between schemes are treated separately'''
|
||||
"""Ensure that requests between schemes are treated separately"""
|
||||
# First fetch a url under http, and then again under https and then
|
||||
# ensure that we haven't served anything out of cache, and we have two
|
||||
# requests / response pairs in the cassette
|
||||
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
|
||||
yield get(get_client(), 'https://httpbin.org/')
|
||||
yield get(get_client(), 'http://httpbin.org/')
|
||||
with vcr.use_cassette(str(tmpdir.join("cross_scheme.yaml"))) as cass:
|
||||
yield get(get_client(), "https://httpbin.org/")
|
||||
yield get(get_client(), "http://httpbin.org/")
|
||||
assert cass.play_count == 0
|
||||
assert len(cass) == 2
|
||||
|
||||
# Then repeat the same requests and ensure both were replayed.
|
||||
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
|
||||
yield get(get_client(), 'https://httpbin.org/')
|
||||
yield get(get_client(), 'http://httpbin.org/')
|
||||
with vcr.use_cassette(str(tmpdir.join("cross_scheme.yaml"))) as cass:
|
||||
yield get(get_client(), "https://httpbin.org/")
|
||||
yield get(get_client(), "http://httpbin.org/")
|
||||
assert cass.play_count == 2
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_gzip(get_client, tmpdir, scheme):
|
||||
'''
|
||||
"""
|
||||
Ensure that httpclient is able to automatically decompress the response
|
||||
body
|
||||
'''
|
||||
url = scheme + '://httpbin.org/gzip'
|
||||
"""
|
||||
url = scheme + "://httpbin.org/gzip"
|
||||
|
||||
# use_gzip was renamed to decompress_response in 4.0
|
||||
kwargs = {}
|
||||
if tornado.version_info < (4,):
|
||||
kwargs['use_gzip'] = True
|
||||
kwargs["use_gzip"] = True
|
||||
else:
|
||||
kwargs['decompress_response'] = True
|
||||
kwargs["decompress_response"] = True
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("gzip.yaml"))):
|
||||
response = yield get(get_client(), url, **kwargs)
|
||||
assert_is_json(response.body)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("gzip.yaml"))) as cass:
|
||||
response = yield get(get_client(), url, **kwargs)
|
||||
assert_is_json(response.body)
|
||||
assert 1 == cass.play_count
|
||||
@@ -227,28 +211,26 @@ def test_gzip(get_client, tmpdir, scheme):
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_https_with_cert_validation_disabled(get_client, tmpdir):
|
||||
cass_path = str(tmpdir.join('cert_validation_disabled.yaml'))
|
||||
cass_path = str(tmpdir.join("cert_validation_disabled.yaml"))
|
||||
|
||||
with vcr.use_cassette(cass_path):
|
||||
yield get(get_client(), 'https://httpbin.org', validate_cert=False)
|
||||
yield get(get_client(), "https://httpbin.org", validate_cert=False)
|
||||
|
||||
with vcr.use_cassette(cass_path) as cass:
|
||||
yield get(get_client(), 'https://httpbin.org', validate_cert=False)
|
||||
yield get(get_client(), "https://httpbin.org", validate_cert=False)
|
||||
assert 1 == cass.play_count
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_unsupported_features_raises_in_future(get_client, tmpdir):
|
||||
'''Ensure that the exception for an AsyncHTTPClient feature not being
|
||||
supported is raised inside the future.'''
|
||||
"""Ensure that the exception for an AsyncHTTPClient feature not being
|
||||
supported is raised inside the future."""
|
||||
|
||||
def callback(chunk):
|
||||
assert False, "Did not expect to be called."
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('invalid.yaml'))):
|
||||
future = get(
|
||||
get_client(), 'http://httpbin.org', streaming_callback=callback
|
||||
)
|
||||
with vcr.use_cassette(str(tmpdir.join("invalid.yaml"))):
|
||||
future = get(get_client(), "http://httpbin.org", streaming_callback=callback)
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
yield future
|
||||
@@ -256,24 +238,18 @@ def test_unsupported_features_raises_in_future(get_client, tmpdir):
|
||||
assert "not yet supported by VCR" in str(excinfo)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not supports_raise_error,
|
||||
reason='raise_error unavailable in tornado <= 3',
|
||||
)
|
||||
@pytest.mark.skipif(not supports_raise_error, reason="raise_error unavailable in tornado <= 3")
|
||||
@pytest.mark.gen_test
|
||||
def test_unsupported_features_raise_error_disabled(get_client, tmpdir):
|
||||
'''Ensure that the exception for an AsyncHTTPClient feature not being
|
||||
supported is not raised if raise_error=False.'''
|
||||
"""Ensure that the exception for an AsyncHTTPClient feature not being
|
||||
supported is not raised if raise_error=False."""
|
||||
|
||||
def callback(chunk):
|
||||
assert False, "Did not expect to be called."
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('invalid.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("invalid.yaml"))):
|
||||
response = yield get(
|
||||
get_client(),
|
||||
'http://httpbin.org',
|
||||
streaming_callback=callback,
|
||||
raise_error=False,
|
||||
get_client(), "http://httpbin.org", streaming_callback=callback, raise_error=False
|
||||
)
|
||||
|
||||
assert "not yet supported by VCR" in str(response.error)
|
||||
@@ -281,60 +257,51 @@ def test_unsupported_features_raise_error_disabled(get_client, tmpdir):
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_cannot_overwrite_cassette_raises_in_future(get_client, tmpdir):
|
||||
'''Ensure that CannotOverwriteExistingCassetteException is raised inside
|
||||
the future.'''
|
||||
"""Ensure that CannotOverwriteExistingCassetteException is raised inside
|
||||
the future."""
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('overwrite.yaml'))):
|
||||
yield get(get_client(), 'http://httpbin.org/get')
|
||||
with vcr.use_cassette(str(tmpdir.join("overwrite.yaml"))):
|
||||
yield get(get_client(), "http://httpbin.org/get")
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('overwrite.yaml'))):
|
||||
future = get(get_client(), 'http://httpbin.org/headers')
|
||||
with vcr.use_cassette(str(tmpdir.join("overwrite.yaml"))):
|
||||
future = get(get_client(), "http://httpbin.org/headers")
|
||||
|
||||
with pytest.raises(CannotOverwriteExistingCassetteException):
|
||||
yield future
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not supports_raise_error,
|
||||
reason='raise_error unavailable in tornado <= 3',
|
||||
)
|
||||
@pytest.mark.skipif(not supports_raise_error, reason="raise_error unavailable in tornado <= 3")
|
||||
@pytest.mark.gen_test
|
||||
def test_cannot_overwrite_cassette_raise_error_disabled(get_client, tmpdir):
|
||||
'''Ensure that CannotOverwriteExistingCassetteException is not raised if
|
||||
raise_error=False in the fetch() call.'''
|
||||
"""Ensure that CannotOverwriteExistingCassetteException is not raised if
|
||||
raise_error=False in the fetch() call."""
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('overwrite.yaml'))):
|
||||
yield get(
|
||||
get_client(), 'http://httpbin.org/get', raise_error=False
|
||||
)
|
||||
with vcr.use_cassette(str(tmpdir.join("overwrite.yaml"))):
|
||||
yield get(get_client(), "http://httpbin.org/get", raise_error=False)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('overwrite.yaml'))):
|
||||
response = yield get(
|
||||
get_client(), 'http://httpbin.org/headers', raise_error=False
|
||||
)
|
||||
with vcr.use_cassette(str(tmpdir.join("overwrite.yaml"))):
|
||||
response = yield get(get_client(), "http://httpbin.org/headers", raise_error=False)
|
||||
|
||||
assert isinstance(response.error, CannotOverwriteExistingCassetteException)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
@vcr.use_cassette(path_transformer=vcr.default_vcr.ensure_suffix('.yaml'))
|
||||
@vcr.use_cassette(path_transformer=vcr.default_vcr.ensure_suffix(".yaml"))
|
||||
def test_tornado_with_decorator_use_cassette(get_client):
|
||||
response = yield get_client().fetch(
|
||||
http.HTTPRequest('http://www.google.com/', method='GET')
|
||||
)
|
||||
assert response.body.decode('utf-8') == "not actually google"
|
||||
response = yield get_client().fetch(http.HTTPRequest("http://www.google.com/", method="GET"))
|
||||
assert response.body.decode("utf-8") == "not actually google"
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
@vcr.use_cassette(path_transformer=vcr.default_vcr.ensure_suffix('.yaml'))
|
||||
@vcr.use_cassette(path_transformer=vcr.default_vcr.ensure_suffix(".yaml"))
|
||||
def test_tornado_exception_can_be_caught(get_client):
|
||||
try:
|
||||
yield get(get_client(), 'http://httpbin.org/status/500')
|
||||
yield get(get_client(), "http://httpbin.org/status/500")
|
||||
except http.HTTPError as e:
|
||||
assert e.code == 500
|
||||
|
||||
try:
|
||||
yield get(get_client(), 'http://httpbin.org/status/404')
|
||||
yield get(get_client(), "http://httpbin.org/status/404")
|
||||
except http.HTTPError as e:
|
||||
assert e.code == 404
|
||||
|
||||
@@ -343,41 +310,41 @@ def test_tornado_exception_can_be_caught(get_client):
|
||||
def test_existing_references_get_patched(tmpdir):
|
||||
from tornado.httpclient import AsyncHTTPClient
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('data.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("data.yaml"))):
|
||||
client = AsyncHTTPClient()
|
||||
yield get(client, 'http://httpbin.org/get')
|
||||
yield get(client, "http://httpbin.org/get")
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('data.yaml'))) as cass:
|
||||
yield get(client, 'http://httpbin.org/get')
|
||||
with vcr.use_cassette(str(tmpdir.join("data.yaml"))) as cass:
|
||||
yield get(client, "http://httpbin.org/get")
|
||||
assert cass.play_count == 1
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_existing_instances_get_patched(get_client, tmpdir):
|
||||
'''Ensure that existing instances of AsyncHTTPClient get patched upon
|
||||
entering VCR context.'''
|
||||
"""Ensure that existing instances of AsyncHTTPClient get patched upon
|
||||
entering VCR context."""
|
||||
|
||||
client = get_client()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('data.yaml'))):
|
||||
yield get(client, 'http://httpbin.org/get')
|
||||
with vcr.use_cassette(str(tmpdir.join("data.yaml"))):
|
||||
yield get(client, "http://httpbin.org/get")
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('data.yaml'))) as cass:
|
||||
yield get(client, 'http://httpbin.org/get')
|
||||
with vcr.use_cassette(str(tmpdir.join("data.yaml"))) as cass:
|
||||
yield get(client, "http://httpbin.org/get")
|
||||
assert cass.play_count == 1
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_request_time_is_set(get_client, tmpdir):
|
||||
'''Ensures that the request_time on HTTPResponses is set.'''
|
||||
"""Ensures that the request_time on HTTPResponses is set."""
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('data.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("data.yaml"))):
|
||||
client = get_client()
|
||||
response = yield get(client, 'http://httpbin.org/get')
|
||||
response = yield get(client, "http://httpbin.org/get")
|
||||
assert response.request_time is not None
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('data.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("data.yaml"))) as cass:
|
||||
client = get_client()
|
||||
response = yield get(client, 'http://httpbin.org/get')
|
||||
response = yield get(client, "http://httpbin.org/get")
|
||||
assert response.request_time is not None
|
||||
assert cass.play_count == 1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''Integration tests with urllib2'''
|
||||
"""Integration tests with urllib2"""
|
||||
|
||||
import ssl
|
||||
from six.moves.urllib.request import urlopen
|
||||
@@ -15,91 +15,86 @@ from assertions import assert_cassette_has_one_response
|
||||
def urlopen_with_cafile(*args, **kwargs):
|
||||
context = ssl.create_default_context(cafile=pytest_httpbin.certs.where())
|
||||
context.check_hostname = False
|
||||
kwargs['context'] = context
|
||||
kwargs["context"] = context
|
||||
try:
|
||||
return urlopen(*args, **kwargs)
|
||||
except TypeError:
|
||||
# python2/pypi don't let us override this
|
||||
del kwargs['cafile']
|
||||
del kwargs["cafile"]
|
||||
return urlopen(*args, **kwargs)
|
||||
|
||||
|
||||
def test_response_code(httpbin_both, tmpdir):
|
||||
'''Ensure we can read a response code from a fetch'''
|
||||
"""Ensure we can read a response code from a fetch"""
|
||||
url = httpbin_both.url
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
code = urlopen_with_cafile(url).getcode()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
assert code == urlopen_with_cafile(url).getcode()
|
||||
|
||||
|
||||
def test_random_body(httpbin_both, tmpdir):
|
||||
'''Ensure we can read the content, and that it's served from cache'''
|
||||
url = httpbin_both.url + '/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
||||
"""Ensure we can read the content, and that it's served from cache"""
|
||||
url = httpbin_both.url + "/bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
body = urlopen_with_cafile(url).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
assert body == urlopen_with_cafile(url).read()
|
||||
|
||||
|
||||
def test_response_headers(httpbin_both, tmpdir):
|
||||
'''Ensure we can get information from the response'''
|
||||
"""Ensure we can get information from the response"""
|
||||
url = httpbin_both.url
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
open1 = urlopen_with_cafile(url).info().items()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
open2 = urlopen_with_cafile(url).info().items()
|
||||
|
||||
assert sorted(open1) == sorted(open2)
|
||||
|
||||
|
||||
def test_effective_url(httpbin_both, tmpdir):
|
||||
'''Ensure that the effective_url is captured'''
|
||||
url = httpbin_both.url + '/redirect-to?url=/html'
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
"""Ensure that the effective_url is captured"""
|
||||
url = httpbin_both.url + "/redirect-to?url=/html"
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
effective_url = urlopen_with_cafile(url).geturl()
|
||||
assert effective_url == httpbin_both.url + '/html'
|
||||
assert effective_url == httpbin_both.url + "/html"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
assert effective_url == urlopen_with_cafile(url).geturl()
|
||||
|
||||
|
||||
def test_multiple_requests(httpbin_both, tmpdir):
|
||||
'''Ensure that we can cache multiple requests'''
|
||||
urls = [
|
||||
httpbin_both.url,
|
||||
httpbin_both.url,
|
||||
httpbin_both.url + '/get',
|
||||
httpbin_both.url + '/bytes/1024',
|
||||
]
|
||||
with vcr.use_cassette(str(tmpdir.join('multiple.yaml'))) as cass:
|
||||
"""Ensure that we can cache multiple requests"""
|
||||
urls = [httpbin_both.url, httpbin_both.url, httpbin_both.url + "/get", httpbin_both.url + "/bytes/1024"]
|
||||
with vcr.use_cassette(str(tmpdir.join("multiple.yaml"))) as cass:
|
||||
[urlopen_with_cafile(url) for url in urls]
|
||||
assert len(cass) == len(urls)
|
||||
|
||||
|
||||
def test_get_data(httpbin_both, tmpdir):
|
||||
'''Ensure that it works with query data'''
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = httpbin_both.url + '/get?' + data
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))):
|
||||
"""Ensure that it works with query data"""
|
||||
data = urlencode({"some": 1, "data": "here"})
|
||||
url = httpbin_both.url + "/get?" + data
|
||||
with vcr.use_cassette(str(tmpdir.join("get_data.yaml"))):
|
||||
res1 = urlopen_with_cafile(url).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("get_data.yaml"))):
|
||||
res2 = urlopen_with_cafile(url).read()
|
||||
assert res1 == res2
|
||||
|
||||
|
||||
def test_post_data(httpbin_both, tmpdir):
|
||||
'''Ensure that it works when posting data'''
|
||||
data = urlencode({'some': 1, 'data': 'here'}).encode('utf-8')
|
||||
url = httpbin_both.url + '/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))):
|
||||
"""Ensure that it works when posting data"""
|
||||
data = urlencode({"some": 1, "data": "here"}).encode("utf-8")
|
||||
url = httpbin_both.url + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||
res1 = urlopen_with_cafile(url, data).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))) as cass:
|
||||
res2 = urlopen_with_cafile(url, data).read()
|
||||
assert len(cass) == 1
|
||||
|
||||
@@ -108,13 +103,13 @@ def test_post_data(httpbin_both, tmpdir):
|
||||
|
||||
|
||||
def test_post_unicode_data(httpbin_both, tmpdir):
|
||||
'''Ensure that it works when posting unicode data'''
|
||||
data = urlencode({'snowman': u'☃'.encode('utf-8')}).encode('utf-8')
|
||||
url = httpbin_both.url + '/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))):
|
||||
"""Ensure that it works when posting unicode data"""
|
||||
data = urlencode({"snowman": u"☃".encode("utf-8")}).encode("utf-8")
|
||||
url = httpbin_both.url + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||
res1 = urlopen_with_cafile(url, data).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))) as cass:
|
||||
res2 = urlopen_with_cafile(url, data).read()
|
||||
assert len(cass) == 1
|
||||
|
||||
@@ -123,11 +118,11 @@ def test_post_unicode_data(httpbin_both, tmpdir):
|
||||
|
||||
|
||||
def test_cross_scheme(tmpdir, httpbin_secure, httpbin):
|
||||
'''Ensure that requests between schemes are treated separately'''
|
||||
"""Ensure that requests between schemes are treated separately"""
|
||||
# First fetch a url under https, and then again under https and then
|
||||
# ensure that we haven't served anything out of cache, and we have two
|
||||
# requests / response pairs in the cassette
|
||||
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
|
||||
with vcr.use_cassette(str(tmpdir.join("cross_scheme.yaml"))) as cass:
|
||||
urlopen_with_cafile(httpbin_secure.url)
|
||||
urlopen_with_cafile(httpbin.url)
|
||||
assert len(cass) == 2
|
||||
@@ -135,14 +130,14 @@ def test_cross_scheme(tmpdir, httpbin_secure, httpbin):
|
||||
|
||||
|
||||
def test_decorator(httpbin_both, tmpdir):
|
||||
'''Test the decorator version of VCR.py'''
|
||||
"""Test the decorator version of VCR.py"""
|
||||
url = httpbin_both.url
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
@vcr.use_cassette(str(tmpdir.join("atts.yaml")))
|
||||
def inner1():
|
||||
return urlopen_with_cafile(url).getcode()
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
@vcr.use_cassette(str(tmpdir.join("atts.yaml")))
|
||||
def inner2():
|
||||
return urlopen_with_cafile(url).getcode()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'''Integration tests with urllib3'''
|
||||
"""Integration tests with urllib3"""
|
||||
|
||||
# coding=utf-8
|
||||
|
||||
@@ -7,101 +7,101 @@ import pytest_httpbin
|
||||
import vcr
|
||||
from vcr.patch import force_reset
|
||||
from assertions import assert_cassette_empty, assert_is_json
|
||||
|
||||
urllib3 = pytest.importorskip("urllib3")
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture(scope="module")
|
||||
def verify_pool_mgr():
|
||||
return urllib3.PoolManager(
|
||||
cert_reqs='CERT_REQUIRED', # Force certificate check.
|
||||
ca_certs=pytest_httpbin.certs.where()
|
||||
cert_reqs="CERT_REQUIRED", ca_certs=pytest_httpbin.certs.where() # Force certificate check.
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture(scope="module")
|
||||
def pool_mgr():
|
||||
return urllib3.PoolManager()
|
||||
return urllib3.PoolManager(cert_reqs="CERT_NONE")
|
||||
|
||||
|
||||
def test_status_code(httpbin_both, tmpdir, verify_pool_mgr):
|
||||
'''Ensure that we can read the status code'''
|
||||
"""Ensure that we can read the status code"""
|
||||
url = httpbin_both.url
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
||||
status_code = verify_pool_mgr.request('GET', url).status
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
status_code = verify_pool_mgr.request("GET", url).status
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
||||
assert status_code == verify_pool_mgr.request('GET', url).status
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
assert status_code == verify_pool_mgr.request("GET", url).status
|
||||
|
||||
|
||||
def test_headers(tmpdir, httpbin_both, verify_pool_mgr):
|
||||
'''Ensure that we can read the headers back'''
|
||||
"""Ensure that we can read the headers back"""
|
||||
url = httpbin_both.url
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
headers = verify_pool_mgr.request('GET', url).headers
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
headers = verify_pool_mgr.request("GET", url).headers
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
||||
assert headers == verify_pool_mgr.request('GET', url).headers
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
assert headers == verify_pool_mgr.request("GET", url).headers
|
||||
|
||||
|
||||
def test_body(tmpdir, httpbin_both, verify_pool_mgr):
|
||||
'''Ensure the responses are all identical enough'''
|
||||
url = httpbin_both.url + '/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
||||
content = verify_pool_mgr.request('GET', url).data
|
||||
"""Ensure the responses are all identical enough"""
|
||||
url = httpbin_both.url + "/bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
content = verify_pool_mgr.request("GET", url).data
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
||||
assert content == verify_pool_mgr.request('GET', url).data
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
assert content == verify_pool_mgr.request("GET", url).data
|
||||
|
||||
|
||||
def test_auth(tmpdir, httpbin_both, verify_pool_mgr):
|
||||
'''Ensure that we can handle basic auth'''
|
||||
auth = ('user', 'passwd')
|
||||
headers = urllib3.util.make_headers(basic_auth='{}:{}'.format(*auth))
|
||||
url = httpbin_both.url + '/basic-auth/user/passwd'
|
||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
|
||||
one = verify_pool_mgr.request('GET', url, headers=headers)
|
||||
"""Ensure that we can handle basic auth"""
|
||||
auth = ("user", "passwd")
|
||||
headers = urllib3.util.make_headers(basic_auth="{}:{}".format(*auth))
|
||||
url = httpbin_both.url + "/basic-auth/user/passwd"
|
||||
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))):
|
||||
one = verify_pool_mgr.request("GET", url, headers=headers)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
|
||||
two = verify_pool_mgr.request('GET', url, headers=headers)
|
||||
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))):
|
||||
two = verify_pool_mgr.request("GET", url, headers=headers)
|
||||
assert one.data == two.data
|
||||
assert one.status == two.status
|
||||
|
||||
|
||||
def test_auth_failed(tmpdir, httpbin_both, verify_pool_mgr):
|
||||
'''Ensure that we can save failed auth statuses'''
|
||||
auth = ('user', 'wrongwrongwrong')
|
||||
headers = urllib3.util.make_headers(basic_auth='{}:{}'.format(*auth))
|
||||
url = httpbin_both.url + '/basic-auth/user/passwd'
|
||||
with vcr.use_cassette(str(tmpdir.join('auth-failed.yaml'))) as cass:
|
||||
"""Ensure that we can save failed auth statuses"""
|
||||
auth = ("user", "wrongwrongwrong")
|
||||
headers = urllib3.util.make_headers(basic_auth="{}:{}".format(*auth))
|
||||
url = httpbin_both.url + "/basic-auth/user/passwd"
|
||||
with vcr.use_cassette(str(tmpdir.join("auth-failed.yaml"))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
one = verify_pool_mgr.request('GET', url, headers=headers)
|
||||
two = verify_pool_mgr.request('GET', url, headers=headers)
|
||||
one = verify_pool_mgr.request("GET", url, headers=headers)
|
||||
two = verify_pool_mgr.request("GET", url, headers=headers)
|
||||
assert one.data == two.data
|
||||
assert one.status == two.status == 401
|
||||
|
||||
|
||||
def test_post(tmpdir, httpbin_both, verify_pool_mgr):
|
||||
'''Ensure that we can post and cache the results'''
|
||||
data = {'key1': 'value1', 'key2': 'value2'}
|
||||
url = httpbin_both.url + '/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))):
|
||||
req1 = verify_pool_mgr.request('POST', url, data).data
|
||||
"""Ensure that we can post and cache the results"""
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
url = httpbin_both.url + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))):
|
||||
req1 = verify_pool_mgr.request("POST", url, data).data
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))):
|
||||
req2 = verify_pool_mgr.request('POST', url, data).data
|
||||
with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))):
|
||||
req2 = verify_pool_mgr.request("POST", url, data).data
|
||||
|
||||
assert req1 == req2
|
||||
|
||||
|
||||
def test_redirects(tmpdir, httpbin_both, verify_pool_mgr):
|
||||
'''Ensure that we can handle redirects'''
|
||||
url = httpbin_both.url + '/redirect-to?url=bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))):
|
||||
content = verify_pool_mgr.request('GET', url).data
|
||||
"""Ensure that we can handle redirects"""
|
||||
url = httpbin_both.url + "/redirect-to?url=bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))):
|
||||
content = verify_pool_mgr.request("GET", url).data
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))) as cass:
|
||||
assert content == verify_pool_mgr.request('GET', url).data
|
||||
with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))) as cass:
|
||||
assert content == verify_pool_mgr.request("GET", url).data
|
||||
# Ensure that we've now cached *two* responses. One for the redirect
|
||||
# and one for the final fetch
|
||||
assert len(cass) == 2
|
||||
@@ -109,36 +109,36 @@ def test_redirects(tmpdir, httpbin_both, verify_pool_mgr):
|
||||
|
||||
|
||||
def test_cross_scheme(tmpdir, httpbin, httpbin_secure, verify_pool_mgr):
|
||||
'''Ensure that requests between schemes are treated separately'''
|
||||
"""Ensure that requests between schemes are treated separately"""
|
||||
# First fetch a url under http, and then again under https and then
|
||||
# ensure that we haven't served anything out of cache, and we have two
|
||||
# requests / response pairs in the cassette
|
||||
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
|
||||
verify_pool_mgr.request('GET', httpbin_secure.url)
|
||||
verify_pool_mgr.request('GET', httpbin.url)
|
||||
with vcr.use_cassette(str(tmpdir.join("cross_scheme.yaml"))) as cass:
|
||||
verify_pool_mgr.request("GET", httpbin_secure.url)
|
||||
verify_pool_mgr.request("GET", httpbin.url)
|
||||
assert cass.play_count == 0
|
||||
assert len(cass) == 2
|
||||
|
||||
|
||||
def test_gzip(tmpdir, httpbin_both, verify_pool_mgr):
|
||||
'''
|
||||
"""
|
||||
Ensure that requests (actually urllib3) is able to automatically decompress
|
||||
the response body
|
||||
'''
|
||||
url = httpbin_both.url + '/gzip'
|
||||
response = verify_pool_mgr.request('GET', url)
|
||||
"""
|
||||
url = httpbin_both.url + "/gzip"
|
||||
response = verify_pool_mgr.request("GET", url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
|
||||
response = verify_pool_mgr.request('GET', url)
|
||||
with vcr.use_cassette(str(tmpdir.join("gzip.yaml"))):
|
||||
response = verify_pool_mgr.request("GET", url)
|
||||
assert_is_json(response.data)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("gzip.yaml"))):
|
||||
assert_is_json(response.data)
|
||||
|
||||
|
||||
def test_https_with_cert_validation_disabled(tmpdir, httpbin_secure, pool_mgr):
|
||||
with vcr.use_cassette(str(tmpdir.join('cert_validation_disabled.yaml'))):
|
||||
pool_mgr.request('GET', httpbin_secure.url)
|
||||
with vcr.use_cassette(str(tmpdir.join("cert_validation_disabled.yaml"))):
|
||||
pool_mgr.request("GET", httpbin_secure.url)
|
||||
|
||||
|
||||
def test_urllib3_force_reset():
|
||||
@@ -146,7 +146,7 @@ def test_urllib3_force_reset():
|
||||
http_original = cpool.HTTPConnection
|
||||
https_original = cpool.HTTPSConnection
|
||||
verified_https_original = cpool.VerifiedHTTPSConnection
|
||||
with vcr.use_cassette(path='test'):
|
||||
with vcr.use_cassette(path="test"):
|
||||
first_cassette_HTTPConnection = cpool.HTTPConnection
|
||||
first_cassette_HTTPSConnection = cpool.HTTPSConnection
|
||||
first_cassette_VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
|
||||
|
||||
@@ -13,13 +13,13 @@ except ImportError:
|
||||
|
||||
|
||||
def test_domain_redirect():
|
||||
'''Ensure that redirects across domains are considered unique'''
|
||||
"""Ensure that redirects across domains are considered unique"""
|
||||
# In this example, seomoz.org redirects to moz.com, and if those
|
||||
# requests are considered identical, then we'll be stuck in a redirect
|
||||
# loop.
|
||||
url = 'http://seomoz.org/'
|
||||
with vcr.use_cassette('tests/fixtures/wild/domain_redirect.yaml') as cass:
|
||||
requests.get(url, headers={'User-Agent': 'vcrpy-test'})
|
||||
url = "http://seomoz.org/"
|
||||
with vcr.use_cassette("tests/fixtures/wild/domain_redirect.yaml") as cass:
|
||||
requests.get(url, headers={"User-Agent": "vcrpy-test"})
|
||||
# Ensure that we've now served two responses. One for the original
|
||||
# redirect, and a second for the actual fetch
|
||||
assert len(cass) == 2
|
||||
@@ -30,13 +30,11 @@ def test_flickr_multipart_upload(httpbin, tmpdir):
|
||||
The python-flickr-api project does a multipart
|
||||
upload that confuses vcrpy
|
||||
"""
|
||||
|
||||
def _pretend_to_be_flickr_library():
|
||||
content_type, body = "text/plain", "HELLO WORLD"
|
||||
h = httplib.HTTPConnection(httpbin.host, httpbin.port)
|
||||
headers = {
|
||||
"Content-Type": content_type,
|
||||
"content-length": str(len(body))
|
||||
}
|
||||
headers = {"Content-Type": content_type, "content-length": str(len(body))}
|
||||
h.request("POST", "/post/", headers=headers)
|
||||
h.send(body)
|
||||
r = h.getresponse()
|
||||
@@ -45,7 +43,7 @@ def test_flickr_multipart_upload(httpbin, tmpdir):
|
||||
|
||||
return data
|
||||
|
||||
testfile = str(tmpdir.join('flickr.yml'))
|
||||
testfile = str(tmpdir.join("flickr.yml"))
|
||||
with vcr.use_cassette(testfile) as cass:
|
||||
_pretend_to_be_flickr_library()
|
||||
assert len(cass) == 1
|
||||
@@ -57,50 +55,54 @@ def test_flickr_multipart_upload(httpbin, tmpdir):
|
||||
|
||||
|
||||
def test_flickr_should_respond_with_200(tmpdir):
|
||||
testfile = str(tmpdir.join('flickr.yml'))
|
||||
testfile = str(tmpdir.join("flickr.yml"))
|
||||
with vcr.use_cassette(testfile):
|
||||
r = requests.post("http://api.flickr.com/services/upload")
|
||||
r = requests.post("https://api.flickr.com/services/upload", verify=False)
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
def test_cookies(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('cookies.yml'))
|
||||
testfile = str(tmpdir.join("cookies.yml"))
|
||||
with vcr.use_cassette(testfile):
|
||||
s = requests.Session()
|
||||
s.get(httpbin.url + "/cookies/set?k1=v1&k2=v2")
|
||||
|
||||
r2 = s.get(httpbin.url + "/cookies")
|
||||
assert len(r2.json()['cookies']) == 2
|
||||
assert len(r2.json()["cookies"]) == 2
|
||||
|
||||
|
||||
def test_amazon_doctype(tmpdir):
|
||||
# amazon gzips its homepage. For some reason, in requests 2.7, it's not
|
||||
# getting gunzipped.
|
||||
with vcr.use_cassette(str(tmpdir.join('amz.yml'))):
|
||||
r = requests.get('http://www.amazon.com', verify=False)
|
||||
assert 'html' in r.text
|
||||
with vcr.use_cassette(str(tmpdir.join("amz.yml"))):
|
||||
r = requests.get("http://www.amazon.com", verify=False)
|
||||
assert "html" in r.text
|
||||
|
||||
|
||||
@pytest.yield_fixture(scope='session')
|
||||
def rpc_server():
|
||||
httpd = xmlrpc_server.SimpleXMLRPCServer(('', 0))
|
||||
def start_rpc_server(q):
|
||||
httpd = xmlrpc_server.SimpleXMLRPCServer(("127.0.0.1", 0))
|
||||
httpd.register_function(pow)
|
||||
proxy_process = multiprocessing.Process(
|
||||
target=httpd.serve_forever,
|
||||
)
|
||||
q.put("http://{}:{}".format(*httpd.server_address))
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
@pytest.yield_fixture(scope="session")
|
||||
def rpc_server():
|
||||
q = multiprocessing.Queue()
|
||||
proxy_process = multiprocessing.Process(target=start_rpc_server, args=(q,))
|
||||
try:
|
||||
proxy_process.start()
|
||||
yield 'http://{}:{}'.format(*httpd.server_address)
|
||||
yield q.get()
|
||||
finally:
|
||||
proxy_process.terminate()
|
||||
|
||||
|
||||
def test_xmlrpclib(tmpdir, rpc_server):
|
||||
with vcr.use_cassette(str(tmpdir.join('xmlrpcvideo.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("xmlrpcvideo.yaml"))):
|
||||
roundup_server = xmlrpc_client.ServerProxy(rpc_server, allow_none=True)
|
||||
original_schema = roundup_server.pow(2, 4)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('xmlrpcvideo.yaml'))):
|
||||
with vcr.use_cassette(str(tmpdir.join("xmlrpcvideo.yaml"))):
|
||||
roundup_server = xmlrpc_client.ServerProxy(rpc_server, allow_none=True)
|
||||
second_schema = roundup_server.pow(2, 4)
|
||||
|
||||
|
||||
@@ -14,31 +14,36 @@ from vcr.stubs import VCRHTTPSConnection
|
||||
|
||||
|
||||
def test_cassette_load(tmpdir):
|
||||
a_file = tmpdir.join('test_cassette.yml')
|
||||
a_file.write(yaml.dump({'interactions': [
|
||||
{'request': {'body': '', 'uri': 'foo', 'method': 'GET', 'headers': {}},
|
||||
'response': 'bar'}
|
||||
]}))
|
||||
a_file = tmpdir.join("test_cassette.yml")
|
||||
a_file.write(
|
||||
yaml.dump(
|
||||
{
|
||||
"interactions": [
|
||||
{"request": {"body": "", "uri": "foo", "method": "GET", "headers": {}}, "response": "bar"}
|
||||
]
|
||||
}
|
||||
)
|
||||
)
|
||||
a_cassette = Cassette.load(path=str(a_file))
|
||||
assert len(a_cassette) == 1
|
||||
|
||||
|
||||
def test_cassette_not_played():
|
||||
a = Cassette('test')
|
||||
a = Cassette("test")
|
||||
assert not a.play_count
|
||||
|
||||
|
||||
def test_cassette_append():
|
||||
a = Cassette('test')
|
||||
a.append('foo', 'bar')
|
||||
assert a.requests == ['foo']
|
||||
assert a.responses == ['bar']
|
||||
a = Cassette("test")
|
||||
a.append("foo", "bar")
|
||||
assert a.requests == ["foo"]
|
||||
assert a.responses == ["bar"]
|
||||
|
||||
|
||||
def test_cassette_len():
|
||||
a = Cassette('test')
|
||||
a.append('foo', 'bar')
|
||||
a.append('foo2', 'bar2')
|
||||
a = Cassette("test")
|
||||
a.append("foo", "bar")
|
||||
a.append("foo2", "bar2")
|
||||
assert len(a) == 2
|
||||
|
||||
|
||||
@@ -46,34 +51,34 @@ def _mock_requests_match(request1, request2, matchers):
|
||||
return request1 == request2
|
||||
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', _mock_requests_match)
|
||||
@mock.patch("vcr.cassette.requests_match", _mock_requests_match)
|
||||
def test_cassette_contains():
|
||||
a = Cassette('test')
|
||||
a.append('foo', 'bar')
|
||||
assert 'foo' in a
|
||||
a = Cassette("test")
|
||||
a.append("foo", "bar")
|
||||
assert "foo" in a
|
||||
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', _mock_requests_match)
|
||||
@mock.patch("vcr.cassette.requests_match", _mock_requests_match)
|
||||
def test_cassette_responses_of():
|
||||
a = Cassette('test')
|
||||
a.append('foo', 'bar')
|
||||
assert a.responses_of('foo') == ['bar']
|
||||
a = Cassette("test")
|
||||
a.append("foo", "bar")
|
||||
assert a.responses_of("foo") == ["bar"]
|
||||
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', _mock_requests_match)
|
||||
@mock.patch("vcr.cassette.requests_match", _mock_requests_match)
|
||||
def test_cassette_get_missing_response():
|
||||
a = Cassette('test')
|
||||
a = Cassette("test")
|
||||
with pytest.raises(UnhandledHTTPRequestError):
|
||||
a.responses_of('foo')
|
||||
a.responses_of("foo")
|
||||
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', _mock_requests_match)
|
||||
@mock.patch("vcr.cassette.requests_match", _mock_requests_match)
|
||||
def test_cassette_cant_read_same_request_twice():
|
||||
a = Cassette('test')
|
||||
a.append('foo', 'bar')
|
||||
a.play_response('foo')
|
||||
a = Cassette("test")
|
||||
a.append("foo", "bar")
|
||||
a.play_response("foo")
|
||||
with pytest.raises(UnhandledHTTPRequestError):
|
||||
a.play_response('foo')
|
||||
a.play_response("foo")
|
||||
|
||||
|
||||
def make_get_request():
|
||||
@@ -82,105 +87,119 @@ def make_get_request():
|
||||
return conn.getresponse()
|
||||
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', return_value=True)
|
||||
@mock.patch('vcr.cassette.FilesystemPersister.load_cassette',
|
||||
classmethod(lambda *args, **kwargs: (('foo',), (mock.MagicMock(),))))
|
||||
@mock.patch('vcr.cassette.Cassette.can_play_response_for', return_value=True)
|
||||
@mock.patch('vcr.stubs.VCRHTTPResponse')
|
||||
@mock.patch("vcr.cassette.requests_match", return_value=True)
|
||||
@mock.patch(
|
||||
"vcr.cassette.FilesystemPersister.load_cassette",
|
||||
classmethod(lambda *args, **kwargs: (("foo",), (mock.MagicMock(),))),
|
||||
)
|
||||
@mock.patch("vcr.cassette.Cassette.can_play_response_for", return_value=True)
|
||||
@mock.patch("vcr.stubs.VCRHTTPResponse")
|
||||
def test_function_decorated_with_use_cassette_can_be_invoked_multiple_times(*args):
|
||||
decorated_function = Cassette.use(path='test')(make_get_request)
|
||||
decorated_function = Cassette.use(path="test")(make_get_request)
|
||||
for i in range(4):
|
||||
decorated_function()
|
||||
|
||||
|
||||
def test_arg_getter_functionality():
|
||||
arg_getter = mock.Mock(return_value={'path': 'test'})
|
||||
arg_getter = mock.Mock(return_value={"path": "test"})
|
||||
context_decorator = Cassette.use_arg_getter(arg_getter)
|
||||
|
||||
with context_decorator as cassette:
|
||||
assert cassette._path == 'test'
|
||||
assert cassette._path == "test"
|
||||
|
||||
arg_getter.return_value = {'path': 'other'}
|
||||
arg_getter.return_value = {"path": "other"}
|
||||
|
||||
with context_decorator as cassette:
|
||||
assert cassette._path == 'other'
|
||||
assert cassette._path == "other"
|
||||
|
||||
arg_getter.return_value = {'path': 'other', 'filter_headers': ('header_name',)}
|
||||
arg_getter.return_value = {"path": "other", "filter_headers": ("header_name",)}
|
||||
|
||||
@context_decorator
|
||||
def function():
|
||||
pass
|
||||
|
||||
with mock.patch.object(
|
||||
Cassette, 'load',
|
||||
return_value=mock.MagicMock(inject=False)
|
||||
) as cassette_load:
|
||||
with mock.patch.object(Cassette, "load", return_value=mock.MagicMock(inject=False)) as cassette_load:
|
||||
function()
|
||||
cassette_load.assert_called_once_with(**arg_getter.return_value)
|
||||
|
||||
|
||||
def test_cassette_not_all_played():
|
||||
a = Cassette('test')
|
||||
a.append('foo', 'bar')
|
||||
a = Cassette("test")
|
||||
a.append("foo", "bar")
|
||||
assert not a.all_played
|
||||
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', _mock_requests_match)
|
||||
@mock.patch("vcr.cassette.requests_match", _mock_requests_match)
|
||||
def test_cassette_all_played():
|
||||
a = Cassette('test')
|
||||
a.append('foo', 'bar')
|
||||
a.play_response('foo')
|
||||
a = Cassette("test")
|
||||
a.append("foo", "bar")
|
||||
a.play_response("foo")
|
||||
assert a.all_played
|
||||
|
||||
|
||||
def test_before_record_response():
|
||||
before_record_response = mock.Mock(return_value='mutated')
|
||||
cassette = Cassette('test', before_record_response=before_record_response)
|
||||
cassette.append('req', 'res')
|
||||
@mock.patch("vcr.cassette.requests_match", _mock_requests_match)
|
||||
def test_cassette_rewound():
|
||||
a = Cassette("test")
|
||||
a.append("foo", "bar")
|
||||
a.play_response("foo")
|
||||
assert a.all_played
|
||||
|
||||
before_record_response.assert_called_once_with('res')
|
||||
assert cassette.responses[0] == 'mutated'
|
||||
a.rewind()
|
||||
assert not a.all_played
|
||||
|
||||
|
||||
def test_before_record_response():
|
||||
before_record_response = mock.Mock(return_value="mutated")
|
||||
cassette = Cassette("test", before_record_response=before_record_response)
|
||||
cassette.append("req", "res")
|
||||
|
||||
before_record_response.assert_called_once_with("res")
|
||||
assert cassette.responses[0] == "mutated"
|
||||
|
||||
|
||||
def assert_get_response_body_is(value):
|
||||
conn = httplib.HTTPConnection("www.python.org")
|
||||
conn.request("GET", "/index.html")
|
||||
assert conn.getresponse().read().decode('utf8') == value
|
||||
assert conn.getresponse().read().decode("utf8") == value
|
||||
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', _mock_requests_match)
|
||||
@mock.patch('vcr.cassette.Cassette.can_play_response_for', return_value=True)
|
||||
@mock.patch('vcr.cassette.Cassette._save', return_value=True)
|
||||
@mock.patch("vcr.cassette.requests_match", _mock_requests_match)
|
||||
@mock.patch("vcr.cassette.Cassette.can_play_response_for", return_value=True)
|
||||
@mock.patch("vcr.cassette.Cassette._save", return_value=True)
|
||||
def test_nesting_cassette_context_managers(*args):
|
||||
first_response = {'body': {'string': b'first_response'}, 'headers': {},
|
||||
'status': {'message': 'm', 'code': 200}}
|
||||
first_response = {
|
||||
"body": {"string": b"first_response"},
|
||||
"headers": {},
|
||||
"status": {"message": "m", "code": 200},
|
||||
}
|
||||
|
||||
second_response = copy.deepcopy(first_response)
|
||||
second_response['body']['string'] = b'second_response'
|
||||
second_response["body"]["string"] = b"second_response"
|
||||
|
||||
with contextlib.ExitStack() as exit_stack:
|
||||
first_cassette = exit_stack.enter_context(Cassette.use(path='test'))
|
||||
exit_stack.enter_context(mock.patch.object(first_cassette, 'play_response',
|
||||
return_value=first_response))
|
||||
assert_get_response_body_is('first_response')
|
||||
first_cassette = exit_stack.enter_context(Cassette.use(path="test"))
|
||||
exit_stack.enter_context(
|
||||
mock.patch.object(first_cassette, "play_response", return_value=first_response)
|
||||
)
|
||||
assert_get_response_body_is("first_response")
|
||||
|
||||
# Make sure a second cassette can supercede the first
|
||||
with Cassette.use(path='test') as second_cassette:
|
||||
with mock.patch.object(second_cassette, 'play_response', return_value=second_response):
|
||||
assert_get_response_body_is('second_response')
|
||||
with Cassette.use(path="test") as second_cassette:
|
||||
with mock.patch.object(second_cassette, "play_response", return_value=second_response):
|
||||
assert_get_response_body_is("second_response")
|
||||
|
||||
# Now the first cassette should be back in effect
|
||||
assert_get_response_body_is('first_response')
|
||||
assert_get_response_body_is("first_response")
|
||||
|
||||
|
||||
def test_nesting_context_managers_by_checking_references_of_http_connection():
|
||||
original = httplib.HTTPConnection
|
||||
with Cassette.use(path='test'):
|
||||
with Cassette.use(path="test"):
|
||||
first_cassette_HTTPConnection = httplib.HTTPConnection
|
||||
with Cassette.use(path='test'):
|
||||
with Cassette.use(path="test"):
|
||||
second_cassette_HTTPConnection = httplib.HTTPConnection
|
||||
assert second_cassette_HTTPConnection is not first_cassette_HTTPConnection
|
||||
with Cassette.use(path='test'):
|
||||
with Cassette.use(path="test"):
|
||||
assert httplib.HTTPConnection is not second_cassette_HTTPConnection
|
||||
with force_reset():
|
||||
assert httplib.HTTPConnection is original
|
||||
@@ -191,14 +210,13 @@ def test_nesting_context_managers_by_checking_references_of_http_connection():
|
||||
def test_custom_patchers():
|
||||
class Test(object):
|
||||
attribute = None
|
||||
with Cassette.use(path='custom_patches',
|
||||
custom_patches=((Test, 'attribute', VCRHTTPSConnection),)):
|
||||
|
||||
with Cassette.use(path="custom_patches", custom_patches=((Test, "attribute", VCRHTTPSConnection),)):
|
||||
assert issubclass(Test.attribute, VCRHTTPSConnection)
|
||||
assert VCRHTTPSConnection is not Test.attribute
|
||||
old_attribute = Test.attribute
|
||||
|
||||
with Cassette.use(path='custom_patches',
|
||||
custom_patches=((Test, 'attribute', VCRHTTPSConnection),)):
|
||||
with Cassette.use(path="custom_patches", custom_patches=((Test, "attribute", VCRHTTPSConnection),)):
|
||||
assert issubclass(Test.attribute, VCRHTTPSConnection)
|
||||
assert VCRHTTPSConnection is not Test.attribute
|
||||
assert Test.attribute is not old_attribute
|
||||
@@ -212,15 +230,15 @@ def test_decorated_functions_are_reentrant():
|
||||
info = {"second": False}
|
||||
original_conn = httplib.HTTPConnection
|
||||
|
||||
@Cassette.use(path='whatever', inject=True)
|
||||
@Cassette.use(path="whatever", inject=True)
|
||||
def test_function(cassette):
|
||||
if info['second']:
|
||||
assert httplib.HTTPConnection is not info['first_conn']
|
||||
if info["second"]:
|
||||
assert httplib.HTTPConnection is not info["first_conn"]
|
||||
else:
|
||||
info['first_conn'] = httplib.HTTPConnection
|
||||
info['second'] = True
|
||||
info["first_conn"] = httplib.HTTPConnection
|
||||
info["second"] = True
|
||||
test_function()
|
||||
assert httplib.HTTPConnection is info['first_conn']
|
||||
assert httplib.HTTPConnection is info["first_conn"]
|
||||
|
||||
test_function()
|
||||
assert httplib.HTTPConnection is original_conn
|
||||
@@ -229,43 +247,39 @@ def test_decorated_functions_are_reentrant():
|
||||
def test_cassette_use_called_without_path_uses_function_to_generate_path():
|
||||
@Cassette.use(inject=True)
|
||||
def function_name(cassette):
|
||||
assert cassette._path == 'function_name'
|
||||
assert cassette._path == "function_name"
|
||||
|
||||
function_name()
|
||||
|
||||
|
||||
def test_path_transformer_with_function_path():
|
||||
def path_transformer(path):
|
||||
return os.path.join('a', path)
|
||||
return os.path.join("a", path)
|
||||
|
||||
@Cassette.use(inject=True, path_transformer=path_transformer)
|
||||
def function_name(cassette):
|
||||
assert cassette._path == os.path.join('a', 'function_name')
|
||||
assert cassette._path == os.path.join("a", "function_name")
|
||||
|
||||
function_name()
|
||||
|
||||
|
||||
def test_path_transformer_with_context_manager():
|
||||
with Cassette.use(
|
||||
path='b', path_transformer=lambda *args: 'a'
|
||||
) as cassette:
|
||||
assert cassette._path == 'a'
|
||||
with Cassette.use(path="b", path_transformer=lambda *args: "a") as cassette:
|
||||
assert cassette._path == "a"
|
||||
|
||||
|
||||
def test_path_transformer_None():
|
||||
with Cassette.use(
|
||||
path='a', path_transformer=None,
|
||||
) as cassette:
|
||||
assert cassette._path == 'a'
|
||||
with Cassette.use(path="a", path_transformer=None) as cassette:
|
||||
assert cassette._path == "a"
|
||||
|
||||
|
||||
def test_func_path_generator():
|
||||
def generator(function):
|
||||
return os.path.join(os.path.dirname(inspect.getfile(function)),
|
||||
function.__name__)
|
||||
return os.path.join(os.path.dirname(inspect.getfile(function)), function.__name__)
|
||||
|
||||
@Cassette.use(inject=True, func_path_generator=generator)
|
||||
def function_name(cassette):
|
||||
assert cassette._path == os.path.join(os.path.dirname(__file__), 'function_name')
|
||||
assert cassette._path == os.path.join(os.path.dirname(__file__), "function_name")
|
||||
|
||||
function_name()
|
||||
|
||||
@@ -306,3 +320,51 @@ def test_use_as_decorator_on_generator():
|
||||
yield 2
|
||||
|
||||
assert list(test_function()) == [1, 2]
|
||||
|
||||
|
||||
@mock.patch("vcr.cassette.get_matchers_results")
|
||||
def test_find_requests_with_most_matches_one_similar_request(mock_get_matchers_results):
|
||||
mock_get_matchers_results.side_effect = [
|
||||
(["method"], [("path", "failed : path"), ("query", "failed : query")]),
|
||||
(["method", "path"], [("query", "failed : query")]),
|
||||
([], [("method", "failed : method"), ("path", "failed : path"), ("query", "failed : query")]),
|
||||
]
|
||||
|
||||
cassette = Cassette("test")
|
||||
for request in range(1, 4):
|
||||
cassette.append(request, "response")
|
||||
result = cassette.find_requests_with_most_matches("fake request")
|
||||
assert result == [(2, ["method", "path"], [("query", "failed : query")])]
|
||||
|
||||
|
||||
@mock.patch("vcr.cassette.get_matchers_results")
|
||||
def test_find_requests_with_most_matches_no_similar_requests(mock_get_matchers_results):
|
||||
mock_get_matchers_results.side_effect = [
|
||||
([], [("path", "failed : path"), ("query", "failed : query")]),
|
||||
([], [("path", "failed : path"), ("query", "failed : query")]),
|
||||
([], [("path", "failed : path"), ("query", "failed : query")]),
|
||||
]
|
||||
|
||||
cassette = Cassette("test")
|
||||
for request in range(1, 4):
|
||||
cassette.append(request, "response")
|
||||
result = cassette.find_requests_with_most_matches("fake request")
|
||||
assert result == []
|
||||
|
||||
|
||||
@mock.patch("vcr.cassette.get_matchers_results")
|
||||
def test_find_requests_with_most_matches_many_similar_requests(mock_get_matchers_results):
|
||||
mock_get_matchers_results.side_effect = [
|
||||
(["method", "path"], [("query", "failed : query")]),
|
||||
(["method"], [("path", "failed : path"), ("query", "failed : query")]),
|
||||
(["method", "path"], [("query", "failed : query")]),
|
||||
]
|
||||
|
||||
cassette = Cassette("test")
|
||||
for request in range(1, 4):
|
||||
cassette.append(request, "response")
|
||||
result = cassette.find_requests_with_most_matches("fake request")
|
||||
assert result == [
|
||||
(1, ["method", "path"], [("query", "failed : query")]),
|
||||
(3, ["method", "path"], [("query", "failed : query")]),
|
||||
]
|
||||
|
||||
68
tests/unit/test_errors.py
Normal file
68
tests/unit/test_errors.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import pytest
|
||||
|
||||
from vcr.compat import mock
|
||||
from vcr import errors
|
||||
from vcr.cassette import Cassette
|
||||
|
||||
|
||||
@mock.patch("vcr.cassette.Cassette.find_requests_with_most_matches")
|
||||
@pytest.mark.parametrize(
|
||||
"most_matches, expected_message",
|
||||
[
|
||||
# No request match found
|
||||
([], "No similar requests, that have not been played, found."),
|
||||
# One matcher failed
|
||||
(
|
||||
[("similar request", ["method", "path"], [("query", "failed : query")])],
|
||||
"Found 1 similar requests with 1 different matcher(s) :\n"
|
||||
"\n1 - ('similar request').\n"
|
||||
"Matchers succeeded : ['method', 'path']\n"
|
||||
"Matchers failed :\n"
|
||||
"query - assertion failure :\n"
|
||||
"failed : query\n",
|
||||
),
|
||||
# Multiple failed matchers
|
||||
(
|
||||
[("similar request", ["method"], [("query", "failed : query"), ("path", "failed : path")])],
|
||||
"Found 1 similar requests with 2 different matcher(s) :\n"
|
||||
"\n1 - ('similar request').\n"
|
||||
"Matchers succeeded : ['method']\n"
|
||||
"Matchers failed :\n"
|
||||
"query - assertion failure :\n"
|
||||
"failed : query\n"
|
||||
"path - assertion failure :\n"
|
||||
"failed : path\n",
|
||||
),
|
||||
# Multiple similar requests
|
||||
(
|
||||
[
|
||||
("similar request", ["method"], [("query", "failed : query")]),
|
||||
("similar request 2", ["method"], [("query", "failed : query 2")]),
|
||||
],
|
||||
"Found 2 similar requests with 1 different matcher(s) :\n"
|
||||
"\n1 - ('similar request').\n"
|
||||
"Matchers succeeded : ['method']\n"
|
||||
"Matchers failed :\n"
|
||||
"query - assertion failure :\n"
|
||||
"failed : query\n"
|
||||
"\n2 - ('similar request 2').\n"
|
||||
"Matchers succeeded : ['method']\n"
|
||||
"Matchers failed :\n"
|
||||
"query - assertion failure :\n"
|
||||
"failed : query 2\n",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_CannotOverwriteExistingCassetteException_get_message(
|
||||
mock_find_requests_with_most_matches, most_matches, expected_message
|
||||
):
|
||||
mock_find_requests_with_most_matches.return_value = most_matches
|
||||
cassette = Cassette("path")
|
||||
failed_request = "request"
|
||||
exception_message = errors.CannotOverwriteExistingCassetteException._get_message(cassette, "request")
|
||||
expected = (
|
||||
"Can't overwrite existing cassette (%r) in your current record mode (%r).\n"
|
||||
"No match for the request (%r) was found.\n"
|
||||
"%s" % (cassette._path, cassette.record_mode, failed_request, expected_message)
|
||||
)
|
||||
assert exception_message == expected
|
||||
@@ -1,9 +1,12 @@
|
||||
from six import BytesIO
|
||||
from vcr.filters import (
|
||||
remove_headers, replace_headers,
|
||||
remove_query_parameters, replace_query_parameters,
|
||||
remove_post_data_parameters, replace_post_data_parameters,
|
||||
decode_response
|
||||
remove_headers,
|
||||
replace_headers,
|
||||
remove_query_parameters,
|
||||
replace_query_parameters,
|
||||
remove_post_data_parameters,
|
||||
replace_post_data_parameters,
|
||||
decode_response,
|
||||
)
|
||||
from vcr.compat import mock
|
||||
from vcr.request import Request
|
||||
@@ -20,31 +23,24 @@ def test_replace_headers():
|
||||
# 4. replacing a header using a callable
|
||||
# 5. removing a header using a callable
|
||||
# 6. replacing a header that doesn't exist
|
||||
headers = {
|
||||
'one': ['keep'],
|
||||
'two': ['lose'],
|
||||
'three': ['change'],
|
||||
'four': ['shout'],
|
||||
'five': ['whisper'],
|
||||
}
|
||||
request = Request('GET', 'http://google.com', '', headers)
|
||||
replace_headers(request, [
|
||||
('two', None),
|
||||
('three', 'tada'),
|
||||
('four', lambda key, value, request: value.upper()),
|
||||
('five', lambda key, value, request: None),
|
||||
('six', 'doesntexist'),
|
||||
])
|
||||
assert request.headers == {
|
||||
'one': 'keep',
|
||||
'three': 'tada',
|
||||
'four': 'SHOUT',
|
||||
}
|
||||
headers = {"one": ["keep"], "two": ["lose"], "three": ["change"], "four": ["shout"], "five": ["whisper"]}
|
||||
request = Request("GET", "http://google.com", "", headers)
|
||||
replace_headers(
|
||||
request,
|
||||
[
|
||||
("two", None),
|
||||
("three", "tada"),
|
||||
("four", lambda key, value, request: value.upper()),
|
||||
("five", lambda key, value, request: None),
|
||||
("six", "doesntexist"),
|
||||
],
|
||||
)
|
||||
assert request.headers == {"one": "keep", "three": "tada", "four": "SHOUT"}
|
||||
|
||||
|
||||
def test_replace_headers_empty():
|
||||
headers = {'hello': 'goodbye', 'secret': 'header'}
|
||||
request = Request('GET', 'http://google.com', '', headers)
|
||||
headers = {"hello": "goodbye", "secret": "header"}
|
||||
request = Request("GET", "http://google.com", "", headers)
|
||||
replace_headers(request, [])
|
||||
assert request.headers == headers
|
||||
|
||||
@@ -52,22 +48,20 @@ def test_replace_headers_empty():
|
||||
def test_replace_headers_callable():
|
||||
# This goes beyond test_replace_headers() to ensure that the callable
|
||||
# receives the expected arguments.
|
||||
headers = {'hey': 'there'}
|
||||
request = Request('GET', 'http://google.com', '', headers)
|
||||
callme = mock.Mock(return_value='ho')
|
||||
replace_headers(request, [('hey', callme)])
|
||||
assert request.headers == {'hey': 'ho'}
|
||||
assert callme.call_args == ((), {'request': request,
|
||||
'key': 'hey',
|
||||
'value': 'there'})
|
||||
headers = {"hey": "there"}
|
||||
request = Request("GET", "http://google.com", "", headers)
|
||||
callme = mock.Mock(return_value="ho")
|
||||
replace_headers(request, [("hey", callme)])
|
||||
assert request.headers == {"hey": "ho"}
|
||||
assert callme.call_args == ((), {"request": request, "key": "hey", "value": "there"})
|
||||
|
||||
|
||||
def test_remove_headers():
|
||||
# Test the backward-compatible API wrapper.
|
||||
headers = {'hello': ['goodbye'], 'secret': ['header']}
|
||||
request = Request('GET', 'http://google.com', '', headers)
|
||||
remove_headers(request, ['secret'])
|
||||
assert request.headers == {'hello': 'goodbye'}
|
||||
headers = {"hello": ["goodbye"], "secret": ["header"]}
|
||||
request = Request("GET", "http://google.com", "", headers)
|
||||
remove_headers(request, ["secret"])
|
||||
assert request.headers == {"hello": "goodbye"}
|
||||
|
||||
|
||||
def test_replace_query_parameters():
|
||||
@@ -78,48 +72,45 @@ def test_replace_query_parameters():
|
||||
# 4. replacing a parameter using a callable
|
||||
# 5. removing a parameter using a callable
|
||||
# 6. replacing a parameter that doesn't exist
|
||||
uri = 'http://g.com/?one=keep&two=lose&three=change&four=shout&five=whisper'
|
||||
request = Request('GET', uri, '', {})
|
||||
replace_query_parameters(request, [
|
||||
('two', None),
|
||||
('three', 'tada'),
|
||||
('four', lambda key, value, request: value.upper()),
|
||||
('five', lambda key, value, request: None),
|
||||
('six', 'doesntexist'),
|
||||
])
|
||||
assert request.query == [
|
||||
('four', 'SHOUT'),
|
||||
('one', 'keep'),
|
||||
('three', 'tada'),
|
||||
]
|
||||
uri = "http://g.com/?one=keep&two=lose&three=change&four=shout&five=whisper"
|
||||
request = Request("GET", uri, "", {})
|
||||
replace_query_parameters(
|
||||
request,
|
||||
[
|
||||
("two", None),
|
||||
("three", "tada"),
|
||||
("four", lambda key, value, request: value.upper()),
|
||||
("five", lambda key, value, request: None),
|
||||
("six", "doesntexist"),
|
||||
],
|
||||
)
|
||||
assert request.query == [("four", "SHOUT"), ("one", "keep"), ("three", "tada")]
|
||||
|
||||
|
||||
def test_remove_all_query_parameters():
|
||||
uri = 'http://g.com/?q=cowboys&w=1'
|
||||
request = Request('GET', uri, '', {})
|
||||
replace_query_parameters(request, [('w', None), ('q', None)])
|
||||
assert request.uri == 'http://g.com/'
|
||||
uri = "http://g.com/?q=cowboys&w=1"
|
||||
request = Request("GET", uri, "", {})
|
||||
replace_query_parameters(request, [("w", None), ("q", None)])
|
||||
assert request.uri == "http://g.com/"
|
||||
|
||||
|
||||
def test_replace_query_parameters_callable():
|
||||
# This goes beyond test_replace_query_parameters() to ensure that the
|
||||
# callable receives the expected arguments.
|
||||
uri = 'http://g.com/?hey=there'
|
||||
request = Request('GET', uri, '', {})
|
||||
callme = mock.Mock(return_value='ho')
|
||||
replace_query_parameters(request, [('hey', callme)])
|
||||
assert request.uri == 'http://g.com/?hey=ho'
|
||||
assert callme.call_args == ((), {'request': request,
|
||||
'key': 'hey',
|
||||
'value': 'there'})
|
||||
uri = "http://g.com/?hey=there"
|
||||
request = Request("GET", uri, "", {})
|
||||
callme = mock.Mock(return_value="ho")
|
||||
replace_query_parameters(request, [("hey", callme)])
|
||||
assert request.uri == "http://g.com/?hey=ho"
|
||||
assert callme.call_args == ((), {"request": request, "key": "hey", "value": "there"})
|
||||
|
||||
|
||||
def test_remove_query_parameters():
|
||||
# Test the backward-compatible API wrapper.
|
||||
uri = 'http://g.com/?q=cowboys&w=1'
|
||||
request = Request('GET', uri, '', {})
|
||||
remove_query_parameters(request, ['w'])
|
||||
assert request.uri == 'http://g.com/?q=cowboys'
|
||||
uri = "http://g.com/?q=cowboys&w=1"
|
||||
request = Request("GET", uri, "", {})
|
||||
remove_query_parameters(request, ["w"])
|
||||
assert request.uri == "http://g.com/?q=cowboys"
|
||||
|
||||
|
||||
def test_replace_post_data_parameters():
|
||||
@@ -130,38 +121,58 @@ def test_replace_post_data_parameters():
|
||||
# 4. replacing a parameter using a callable
|
||||
# 5. removing a parameter using a callable
|
||||
# 6. replacing a parameter that doesn't exist
|
||||
body = b'one=keep&two=lose&three=change&four=shout&five=whisper'
|
||||
request = Request('POST', 'http://google.com', body, {})
|
||||
replace_post_data_parameters(request, [
|
||||
('two', None),
|
||||
('three', 'tada'),
|
||||
('four', lambda key, value, request: value.upper()),
|
||||
('five', lambda key, value, request: None),
|
||||
('six', 'doesntexist'),
|
||||
])
|
||||
assert request.body == b'one=keep&three=tada&four=SHOUT'
|
||||
body = b"one=keep&two=lose&three=change&four=shout&five=whisper"
|
||||
request = Request("POST", "http://google.com", body, {})
|
||||
replace_post_data_parameters(
|
||||
request,
|
||||
[
|
||||
("two", None),
|
||||
("three", "tada"),
|
||||
("four", lambda key, value, request: value.upper()),
|
||||
("five", lambda key, value, request: None),
|
||||
("six", "doesntexist"),
|
||||
],
|
||||
)
|
||||
assert request.body == b"one=keep&three=tada&four=SHOUT"
|
||||
|
||||
|
||||
def test_replace_post_data_parameters_empty_body():
|
||||
# This test ensures replace_post_data_parameters doesn't throw exception when body is empty.
|
||||
body = None
|
||||
request = Request("POST", "http://google.com", body, {})
|
||||
replace_post_data_parameters(
|
||||
request,
|
||||
[
|
||||
("two", None),
|
||||
("three", "tada"),
|
||||
("four", lambda key, value, request: value.upper()),
|
||||
("five", lambda key, value, request: None),
|
||||
("six", "doesntexist"),
|
||||
],
|
||||
)
|
||||
assert request.body is None
|
||||
|
||||
|
||||
def test_remove_post_data_parameters():
|
||||
# Test the backward-compatible API wrapper.
|
||||
body = b'id=secret&foo=bar'
|
||||
request = Request('POST', 'http://google.com', body, {})
|
||||
remove_post_data_parameters(request, ['id'])
|
||||
assert request.body == b'foo=bar'
|
||||
body = b"id=secret&foo=bar"
|
||||
request = Request("POST", "http://google.com", body, {})
|
||||
remove_post_data_parameters(request, ["id"])
|
||||
assert request.body == b"foo=bar"
|
||||
|
||||
|
||||
def test_preserve_multiple_post_data_parameters():
|
||||
body = b'id=secret&foo=bar&foo=baz'
|
||||
request = Request('POST', 'http://google.com', body, {})
|
||||
replace_post_data_parameters(request, [('id', None)])
|
||||
assert request.body == b'foo=bar&foo=baz'
|
||||
body = b"id=secret&foo=bar&foo=baz"
|
||||
request = Request("POST", "http://google.com", body, {})
|
||||
replace_post_data_parameters(request, [("id", None)])
|
||||
assert request.body == b"foo=bar&foo=baz"
|
||||
|
||||
|
||||
def test_remove_all_post_data_parameters():
|
||||
body = b'id=secret&foo=bar'
|
||||
request = Request('POST', 'http://google.com', body, {})
|
||||
replace_post_data_parameters(request, [('id', None), ('foo', None)])
|
||||
assert request.body == b''
|
||||
body = b"id=secret&foo=bar"
|
||||
request = Request("POST", "http://google.com", body, {})
|
||||
replace_post_data_parameters(request, [("id", None), ("foo", None)])
|
||||
assert request.body == b""
|
||||
|
||||
|
||||
def test_replace_json_post_data_parameters():
|
||||
@@ -173,16 +184,19 @@ def test_replace_json_post_data_parameters():
|
||||
# 5. removing a parameter using a callable
|
||||
# 6. replacing a parameter that doesn't exist
|
||||
body = b'{"one": "keep", "two": "lose", "three": "change", "four": "shout", "five": "whisper"}'
|
||||
request = Request('POST', 'http://google.com', body, {})
|
||||
request.headers['Content-Type'] = 'application/json'
|
||||
replace_post_data_parameters(request, [
|
||||
('two', None),
|
||||
('three', 'tada'),
|
||||
('four', lambda key, value, request: value.upper()),
|
||||
('five', lambda key, value, request: None),
|
||||
('six', 'doesntexist'),
|
||||
])
|
||||
request_data = json.loads(request.body.decode('utf-8'))
|
||||
request = Request("POST", "http://google.com", body, {})
|
||||
request.headers["Content-Type"] = "application/json"
|
||||
replace_post_data_parameters(
|
||||
request,
|
||||
[
|
||||
("two", None),
|
||||
("three", "tada"),
|
||||
("four", lambda key, value, request: value.upper()),
|
||||
("five", lambda key, value, request: None),
|
||||
("six", "doesntexist"),
|
||||
],
|
||||
)
|
||||
request_data = json.loads(request.body.decode("utf-8"))
|
||||
expected_data = json.loads('{"one": "keep", "three": "tada", "four": "SHOUT"}')
|
||||
assert request_data == expected_data
|
||||
|
||||
@@ -190,85 +204,80 @@ def test_replace_json_post_data_parameters():
|
||||
def test_remove_json_post_data_parameters():
|
||||
# Test the backward-compatible API wrapper.
|
||||
body = b'{"id": "secret", "foo": "bar", "baz": "qux"}'
|
||||
request = Request('POST', 'http://google.com', body, {})
|
||||
request.headers['Content-Type'] = 'application/json'
|
||||
remove_post_data_parameters(request, ['id'])
|
||||
request_body_json = json.loads(request.body.decode('utf-8'))
|
||||
expected_json = json.loads(b'{"foo": "bar", "baz": "qux"}'.decode('utf-8'))
|
||||
request = Request("POST", "http://google.com", body, {})
|
||||
request.headers["Content-Type"] = "application/json"
|
||||
remove_post_data_parameters(request, ["id"])
|
||||
request_body_json = json.loads(request.body.decode("utf-8"))
|
||||
expected_json = json.loads(b'{"foo": "bar", "baz": "qux"}'.decode("utf-8"))
|
||||
assert request_body_json == expected_json
|
||||
|
||||
|
||||
def test_remove_all_json_post_data_parameters():
|
||||
body = b'{"id": "secret", "foo": "bar"}'
|
||||
request = Request('POST', 'http://google.com', body, {})
|
||||
request.headers['Content-Type'] = 'application/json'
|
||||
replace_post_data_parameters(request, [('id', None), ('foo', None)])
|
||||
assert request.body == b'{}'
|
||||
request = Request("POST", "http://google.com", body, {})
|
||||
request.headers["Content-Type"] = "application/json"
|
||||
replace_post_data_parameters(request, [("id", None), ("foo", None)])
|
||||
assert request.body == b"{}"
|
||||
|
||||
|
||||
def test_decode_response_uncompressed():
|
||||
recorded_response = {
|
||||
"status": {
|
||||
"message": "OK",
|
||||
"code": 200
|
||||
},
|
||||
"status": {"message": "OK", "code": 200},
|
||||
"headers": {
|
||||
"content-length": ["10806"],
|
||||
"date": ["Fri, 24 Oct 2014 18:35:37 GMT"],
|
||||
"content-type": ["text/html; charset=utf-8"],
|
||||
},
|
||||
"body": {
|
||||
"string": b""
|
||||
}
|
||||
"body": {"string": b""},
|
||||
}
|
||||
assert decode_response(recorded_response) == recorded_response
|
||||
|
||||
|
||||
def test_decode_response_deflate():
|
||||
body = b'deflate message'
|
||||
body = b"deflate message"
|
||||
deflate_response = {
|
||||
'body': {'string': zlib.compress(body)},
|
||||
'headers': {
|
||||
'access-control-allow-credentials': ['true'],
|
||||
'access-control-allow-origin': ['*'],
|
||||
'connection': ['keep-alive'],
|
||||
'content-encoding': ['deflate'],
|
||||
'content-length': ['177'],
|
||||
'content-type': ['application/json'],
|
||||
'date': ['Wed, 02 Dec 2015 19:44:32 GMT'],
|
||||
'server': ['nginx']
|
||||
"body": {"string": zlib.compress(body)},
|
||||
"headers": {
|
||||
"access-control-allow-credentials": ["true"],
|
||||
"access-control-allow-origin": ["*"],
|
||||
"connection": ["keep-alive"],
|
||||
"content-encoding": ["deflate"],
|
||||
"content-length": ["177"],
|
||||
"content-type": ["application/json"],
|
||||
"date": ["Wed, 02 Dec 2015 19:44:32 GMT"],
|
||||
"server": ["nginx"],
|
||||
},
|
||||
'status': {'code': 200, 'message': 'OK'}
|
||||
"status": {"code": 200, "message": "OK"},
|
||||
}
|
||||
decoded_response = decode_response(deflate_response)
|
||||
assert decoded_response['body']['string'] == body
|
||||
assert decoded_response['headers']['content-length'] == [str(len(body))]
|
||||
assert decoded_response["body"]["string"] == body
|
||||
assert decoded_response["headers"]["content-length"] == [str(len(body))]
|
||||
|
||||
|
||||
def test_decode_response_gzip():
|
||||
body = b'gzip message'
|
||||
body = b"gzip message"
|
||||
|
||||
buf = BytesIO()
|
||||
f = gzip.GzipFile('a', fileobj=buf, mode='wb')
|
||||
f = gzip.GzipFile("a", fileobj=buf, mode="wb")
|
||||
f.write(body)
|
||||
f.close()
|
||||
|
||||
compressed_body = buf.getvalue()
|
||||
buf.close()
|
||||
gzip_response = {
|
||||
'body': {'string': compressed_body},
|
||||
'headers': {
|
||||
'access-control-allow-credentials': ['true'],
|
||||
'access-control-allow-origin': ['*'],
|
||||
'connection': ['keep-alive'],
|
||||
'content-encoding': ['gzip'],
|
||||
'content-length': ['177'],
|
||||
'content-type': ['application/json'],
|
||||
'date': ['Wed, 02 Dec 2015 19:44:32 GMT'],
|
||||
'server': ['nginx']
|
||||
"body": {"string": compressed_body},
|
||||
"headers": {
|
||||
"access-control-allow-credentials": ["true"],
|
||||
"access-control-allow-origin": ["*"],
|
||||
"connection": ["keep-alive"],
|
||||
"content-encoding": ["gzip"],
|
||||
"content-length": ["177"],
|
||||
"content-type": ["application/json"],
|
||||
"date": ["Wed, 02 Dec 2015 19:44:32 GMT"],
|
||||
"server": ["nginx"],
|
||||
},
|
||||
'status': {'code': 200, 'message': 'OK'}
|
||||
"status": {"code": 200, "message": "OK"},
|
||||
}
|
||||
decoded_response = decode_response(gzip_response)
|
||||
assert decoded_response['body']['string'] == body
|
||||
assert decoded_response['headers']['content-length'] == [str(len(body))]
|
||||
assert decoded_response["body"]["string"] == body
|
||||
assert decoded_response["headers"]["content-length"] == [str(len(body))]
|
||||
|
||||
@@ -4,16 +4,14 @@ from vcr.request import Request
|
||||
|
||||
|
||||
def test_serialize_binary():
|
||||
request = Request(
|
||||
method='GET',
|
||||
uri='http://localhost/',
|
||||
body='',
|
||||
headers={},
|
||||
)
|
||||
cassette = {'requests': [request], 'responses': [{'body': b'\x8c'}]}
|
||||
request = Request(method="GET", uri="http://localhost/", body="", headers={})
|
||||
cassette = {"requests": [request], "responses": [{"body": b"\x8c"}]}
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
serialize(cassette)
|
||||
assert e.message == "Error serializing cassette to JSON. Does this \
|
||||
assert (
|
||||
e.message
|
||||
== "Error serializing cassette to JSON. Does this \
|
||||
HTTP interaction contain binary data? If so, use a different \
|
||||
serializer (like the yaml serializer) for this request"
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import itertools
|
||||
from vcr.compat import mock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -8,152 +9,266 @@ from vcr import request
|
||||
# the dict contains requests with corresponding to its key difference
|
||||
# with 'base' request.
|
||||
REQUESTS = {
|
||||
'base': request.Request('GET', 'http://host.com/p?a=b', '', {}),
|
||||
'method': request.Request('POST', 'http://host.com/p?a=b', '', {}),
|
||||
'scheme': request.Request('GET', 'https://host.com:80/p?a=b', '', {}),
|
||||
'host': request.Request('GET', 'http://another-host.com/p?a=b', '', {}),
|
||||
'port': request.Request('GET', 'http://host.com:90/p?a=b', '', {}),
|
||||
'path': request.Request('GET', 'http://host.com/x?a=b', '', {}),
|
||||
'query': request.Request('GET', 'http://host.com/p?c=d', '', {}),
|
||||
"base": request.Request("GET", "http://host.com/p?a=b", "", {}),
|
||||
"method": request.Request("POST", "http://host.com/p?a=b", "", {}),
|
||||
"scheme": request.Request("GET", "https://host.com:80/p?a=b", "", {}),
|
||||
"host": request.Request("GET", "http://another-host.com/p?a=b", "", {}),
|
||||
"port": request.Request("GET", "http://host.com:90/p?a=b", "", {}),
|
||||
"path": request.Request("GET", "http://host.com/x?a=b", "", {}),
|
||||
"query": request.Request("GET", "http://host.com/p?c=d", "", {}),
|
||||
}
|
||||
|
||||
|
||||
def assert_matcher(matcher_name):
|
||||
matcher = getattr(matchers, matcher_name)
|
||||
for k1, k2 in itertools.permutations(REQUESTS, 2):
|
||||
matched = matcher(REQUESTS[k1], REQUESTS[k2])
|
||||
if matcher_name in {k1, k2}:
|
||||
assert not matched
|
||||
expecting_assertion_error = matcher_name in {k1, k2}
|
||||
if expecting_assertion_error:
|
||||
with pytest.raises(AssertionError):
|
||||
matcher(REQUESTS[k1], REQUESTS[k2])
|
||||
else:
|
||||
assert matched
|
||||
assert matcher(REQUESTS[k1], REQUESTS[k2]) is None
|
||||
|
||||
|
||||
def test_uri_matcher():
|
||||
for k1, k2 in itertools.permutations(REQUESTS, 2):
|
||||
matched = matchers.uri(REQUESTS[k1], REQUESTS[k2])
|
||||
if {k1, k2} != {'base', 'method'}:
|
||||
assert not matched
|
||||
expecting_assertion_error = {k1, k2} != {"base", "method"}
|
||||
if expecting_assertion_error:
|
||||
with pytest.raises(AssertionError):
|
||||
matchers.uri(REQUESTS[k1], REQUESTS[k2])
|
||||
else:
|
||||
assert matched
|
||||
assert matchers.uri(REQUESTS[k1], REQUESTS[k2]) is None
|
||||
|
||||
|
||||
req1_body = (b"<?xml version='1.0'?><methodCall><methodName>test</methodName>"
|
||||
b"<params><param><value><array><data><value><struct>"
|
||||
b"<member><name>a</name><value><string>1</string></value></member>"
|
||||
b"<member><name>b</name><value><string>2</string></value></member>"
|
||||
b"</struct></value></data></array></value></param></params></methodCall>")
|
||||
req2_body = (b"<?xml version='1.0'?><methodCall><methodName>test</methodName>"
|
||||
b"<params><param><value><array><data><value><struct>"
|
||||
b"<member><name>b</name><value><string>2</string></value></member>"
|
||||
b"<member><name>a</name><value><string>1</string></value></member>"
|
||||
b"</struct></value></data></array></value></param></params></methodCall>")
|
||||
req1_body = (
|
||||
b"<?xml version='1.0'?><methodCall><methodName>test</methodName>"
|
||||
b"<params><param><value><array><data><value><struct>"
|
||||
b"<member><name>a</name><value><string>1</string></value></member>"
|
||||
b"<member><name>b</name><value><string>2</string></value></member>"
|
||||
b"</struct></value></data></array></value></param></params></methodCall>"
|
||||
)
|
||||
req2_body = (
|
||||
b"<?xml version='1.0'?><methodCall><methodName>test</methodName>"
|
||||
b"<params><param><value><array><data><value><struct>"
|
||||
b"<member><name>b</name><value><string>2</string></value></member>"
|
||||
b"<member><name>a</name><value><string>1</string></value></member>"
|
||||
b"</struct></value></data></array></value></param></params></methodCall>"
|
||||
)
|
||||
boto3_bytes_headers = {
|
||||
"X-Amz-Content-SHA256": b"UNSIGNED-PAYLOAD",
|
||||
"Cache-Control": b"max-age=31536000, public",
|
||||
"X-Amz-Date": b"20191102T143910Z",
|
||||
"User-Agent": b"Boto3/1.9.102 Python/3.5.3 Linux/4.15.0-54-generic Botocore/1.12.253 Resource",
|
||||
"Content-MD5": b"GQqjEXsRqrPyxfTl99nkAg==",
|
||||
"Content-Type": b"text/plain",
|
||||
"Expect": b"100-continue",
|
||||
"Content-Length": "21",
|
||||
}
|
||||
|
||||
|
||||
@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'}
|
||||
@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/', '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/", "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/', '{"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/", "123", {}),
|
||||
request.Request("POST", "http://another-host.com/", "123", {"Some-Header": "value"}),
|
||||
),
|
||||
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/", "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/',
|
||||
'{"b": 2, "a": 1}', {'content-type': 'application/json'}
|
||||
)
|
||||
)
|
||||
])
|
||||
(
|
||||
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"}
|
||||
),
|
||||
),
|
||||
(
|
||||
# special case for boto3 bytes headers
|
||||
request.Request("POST", "http://aws.custom.com/", b"123", boto3_bytes_headers),
|
||||
request.Request("POST", "http://aws.custom.com/", b"123", boto3_bytes_headers),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_body_matcher_does_match(r1, r2):
|
||||
assert matchers.body(r1, r2)
|
||||
assert matchers.body(r1, r2) is None
|
||||
|
||||
|
||||
@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'}
|
||||
@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/',
|
||||
'{"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/", '{"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/', req2_body, {'content-type': 'text/xml'}
|
||||
)
|
||||
)
|
||||
])
|
||||
(
|
||||
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)
|
||||
with pytest.raises(AssertionError):
|
||||
matchers.body(r1, r2)
|
||||
|
||||
|
||||
def test_query_matcher():
|
||||
req1 = request.Request('GET', 'http://host.com/?a=b&c=d', '', {})
|
||||
req2 = request.Request('GET', 'http://host.com/?c=d&a=b', '', {})
|
||||
assert matchers.query(req1, req2)
|
||||
req1 = request.Request("GET", "http://host.com/?a=b&c=d", "", {})
|
||||
req2 = request.Request("GET", "http://host.com/?c=d&a=b", "", {})
|
||||
assert matchers.query(req1, req2) is None
|
||||
|
||||
req1 = request.Request('GET', 'http://host.com/?a=b&a=b&c=d', '', {})
|
||||
req2 = request.Request('GET', 'http://host.com/?a=b&c=d&a=b', '', {})
|
||||
req3 = request.Request('GET', 'http://host.com/?c=d&a=b&a=b', '', {})
|
||||
assert matchers.query(req1, req2)
|
||||
assert matchers.query(req1, req3)
|
||||
req1 = request.Request("GET", "http://host.com/?a=b&a=b&c=d", "", {})
|
||||
req2 = request.Request("GET", "http://host.com/?a=b&c=d&a=b", "", {})
|
||||
req3 = request.Request("GET", "http://host.com/?c=d&a=b&a=b", "", {})
|
||||
assert matchers.query(req1, req2) is None
|
||||
assert matchers.query(req1, req3) is None
|
||||
|
||||
|
||||
def test_metchers():
|
||||
assert_matcher('method')
|
||||
assert_matcher('scheme')
|
||||
assert_matcher('host')
|
||||
assert_matcher('port')
|
||||
assert_matcher('path')
|
||||
assert_matcher('query')
|
||||
def test_matchers():
|
||||
assert_matcher("method")
|
||||
assert_matcher("scheme")
|
||||
assert_matcher("host")
|
||||
assert_matcher("port")
|
||||
assert_matcher("path")
|
||||
assert_matcher("query")
|
||||
|
||||
|
||||
def test_evaluate_matcher_does_match():
|
||||
def bool_matcher(r1, r2):
|
||||
return True
|
||||
|
||||
def assertion_matcher(r1, r2):
|
||||
assert 1 == 1
|
||||
|
||||
r1, r2 = None, None
|
||||
for matcher in [bool_matcher, assertion_matcher]:
|
||||
match, assertion_msg = matchers._evaluate_matcher(matcher, r1, r2)
|
||||
assert match is True
|
||||
assert assertion_msg is None
|
||||
|
||||
|
||||
def test_evaluate_matcher_does_not_match():
|
||||
def bool_matcher(r1, r2):
|
||||
return False
|
||||
|
||||
def assertion_matcher(r1, r2):
|
||||
# This is like the "assert" statement preventing pytest to recompile it
|
||||
raise AssertionError()
|
||||
|
||||
r1, r2 = None, None
|
||||
for matcher in [bool_matcher, assertion_matcher]:
|
||||
match, assertion_msg = matchers._evaluate_matcher(matcher, r1, r2)
|
||||
assert match is False
|
||||
assert not assertion_msg
|
||||
|
||||
|
||||
def test_evaluate_matcher_does_not_match_with_assert_message():
|
||||
def assertion_matcher(r1, r2):
|
||||
# This is like the "assert" statement preventing pytest to recompile it
|
||||
raise AssertionError("Failing matcher")
|
||||
|
||||
r1, r2 = None, None
|
||||
match, assertion_msg = matchers._evaluate_matcher(assertion_matcher, r1, r2)
|
||||
assert match is False
|
||||
assert assertion_msg == "Failing matcher"
|
||||
|
||||
|
||||
def test_get_assertion_message():
|
||||
assert matchers.get_assertion_message(None) is None
|
||||
assert matchers.get_assertion_message("") == ""
|
||||
|
||||
|
||||
def test_get_assertion_message_with_details():
|
||||
assertion_msg = "q1=1 != q2=1"
|
||||
expected = assertion_msg
|
||||
assert matchers.get_assertion_message(assertion_msg) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"r1, r2, expected_successes, expected_failures",
|
||||
[
|
||||
(
|
||||
request.Request("GET", "http://host.com/p?a=b", "", {}),
|
||||
request.Request("GET", "http://host.com/p?a=b", "", {}),
|
||||
["method", "path"],
|
||||
[],
|
||||
),
|
||||
(
|
||||
request.Request("GET", "http://host.com/p?a=b", "", {}),
|
||||
request.Request("POST", "http://host.com/p?a=b", "", {}),
|
||||
["path"],
|
||||
["method"],
|
||||
),
|
||||
(
|
||||
request.Request("GET", "http://host.com/p?a=b", "", {}),
|
||||
request.Request("POST", "http://host.com/path?a=b", "", {}),
|
||||
[],
|
||||
["method", "path"],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_matchers_results(r1, r2, expected_successes, expected_failures):
|
||||
successes, failures = matchers.get_matchers_results(r1, r2, [matchers.method, matchers.path])
|
||||
assert successes == expected_successes
|
||||
assert len(failures) == len(expected_failures)
|
||||
for i, expected_failure in enumerate(expected_failures):
|
||||
assert failures[i][0] == expected_failure
|
||||
assert failures[i][1] is not None
|
||||
|
||||
|
||||
@mock.patch("vcr.matchers.get_matchers_results")
|
||||
@pytest.mark.parametrize(
|
||||
"successes, failures, expected_match",
|
||||
[(["method", "path"], [], True), (["method"], ["path"], False), ([], ["method", "path"], False)],
|
||||
)
|
||||
def test_requests_match(mock_get_matchers_results, successes, failures, expected_match):
|
||||
mock_get_matchers_results.return_value = (successes, failures)
|
||||
r1 = request.Request("GET", "http://host.com/p?a=b", "", {})
|
||||
r2 = request.Request("GET", "http://host.com/p?a=b", "", {})
|
||||
match = matchers.requests_match(r1, r2, [matchers.method, matchers.path])
|
||||
assert match is expected_match
|
||||
|
||||
@@ -5,35 +5,41 @@ import yaml
|
||||
|
||||
import vcr.migration
|
||||
|
||||
# Use the libYAML versions if possible
|
||||
try:
|
||||
from yaml import CLoader as Loader
|
||||
except ImportError:
|
||||
from yaml import Loader
|
||||
|
||||
|
||||
def test_try_migrate_with_json(tmpdir):
|
||||
cassette = tmpdir.join('cassette.json').strpath
|
||||
shutil.copy('tests/fixtures/migration/old_cassette.json', cassette)
|
||||
cassette = tmpdir.join("cassette.json").strpath
|
||||
shutil.copy("tests/fixtures/migration/old_cassette.json", cassette)
|
||||
assert vcr.migration.try_migrate(cassette)
|
||||
with open('tests/fixtures/migration/new_cassette.json', 'r') as f:
|
||||
with open("tests/fixtures/migration/new_cassette.json", "r") as f:
|
||||
expected_json = json.load(f)
|
||||
with open(cassette, 'r') as f:
|
||||
with open(cassette, "r") as f:
|
||||
actual_json = json.load(f)
|
||||
assert actual_json == expected_json
|
||||
|
||||
|
||||
def test_try_migrate_with_yaml(tmpdir):
|
||||
cassette = tmpdir.join('cassette.yaml').strpath
|
||||
shutil.copy('tests/fixtures/migration/old_cassette.yaml', cassette)
|
||||
cassette = tmpdir.join("cassette.yaml").strpath
|
||||
shutil.copy("tests/fixtures/migration/old_cassette.yaml", cassette)
|
||||
assert vcr.migration.try_migrate(cassette)
|
||||
with open('tests/fixtures/migration/new_cassette.yaml', 'r') as f:
|
||||
expected_yaml = yaml.load(f)
|
||||
with open(cassette, 'r') as f:
|
||||
actual_yaml = yaml.load(f)
|
||||
with open("tests/fixtures/migration/new_cassette.yaml", "r") as f:
|
||||
expected_yaml = yaml.load(f, Loader=Loader)
|
||||
with open(cassette, "r") as f:
|
||||
actual_yaml = yaml.load(f, Loader=Loader)
|
||||
assert actual_yaml == expected_yaml
|
||||
|
||||
|
||||
def test_try_migrate_with_invalid_or_new_cassettes(tmpdir):
|
||||
cassette = tmpdir.join('cassette').strpath
|
||||
cassette = tmpdir.join("cassette").strpath
|
||||
files = [
|
||||
'tests/fixtures/migration/not_cassette.txt',
|
||||
'tests/fixtures/migration/new_cassette.yaml',
|
||||
'tests/fixtures/migration/new_cassette.json',
|
||||
"tests/fixtures/migration/not_cassette.txt",
|
||||
"tests/fixtures/migration/new_cassette.yaml",
|
||||
"tests/fixtures/migration/new_cassette.json",
|
||||
]
|
||||
for file_path in files:
|
||||
shutil.copy(file_path, cassette)
|
||||
|
||||
@@ -4,20 +4,26 @@ from vcr.persisters.filesystem import FilesystemPersister
|
||||
from vcr.serializers import jsonserializer, yamlserializer
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cassette_path, serializer", [
|
||||
('tests/fixtures/migration/old_cassette.json', jsonserializer),
|
||||
('tests/fixtures/migration/old_cassette.yaml', yamlserializer),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"cassette_path, serializer",
|
||||
[
|
||||
("tests/fixtures/migration/old_cassette.json", jsonserializer),
|
||||
("tests/fixtures/migration/old_cassette.yaml", yamlserializer),
|
||||
],
|
||||
)
|
||||
def test_load_cassette_with_old_cassettes(cassette_path, serializer):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
FilesystemPersister.load_cassette(cassette_path, serializer)
|
||||
assert "run the migration script" in excinfo.exconly()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cassette_path, serializer", [
|
||||
('tests/fixtures/migration/not_cassette.txt', jsonserializer),
|
||||
('tests/fixtures/migration/not_cassette.txt', yamlserializer),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"cassette_path, serializer",
|
||||
[
|
||||
("tests/fixtures/migration/not_cassette.txt", jsonserializer),
|
||||
("tests/fixtures/migration/not_cassette.txt", yamlserializer),
|
||||
],
|
||||
)
|
||||
def test_load_cassette_with_invalid_cassettes(cassette_path, serializer):
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
FilesystemPersister.load_cassette(cassette_path, serializer)
|
||||
|
||||
@@ -3,44 +3,60 @@ import pytest
|
||||
from vcr.request import Request, HeadersDict
|
||||
|
||||
|
||||
def test_str():
|
||||
req = Request('GET', 'http://www.google.com/', '', {})
|
||||
str(req) == '<Request (GET) http://www.google.com/>'
|
||||
@pytest.mark.parametrize(
|
||||
"method, uri, expected_str",
|
||||
[
|
||||
("GET", "http://www.google.com/", "<Request (GET) http://www.google.com/>"),
|
||||
("OPTIONS", "*", "<Request (OPTIONS) *>"),
|
||||
("CONNECT", "host.some.where:1234", "<Request (CONNECT) host.some.where:1234>"),
|
||||
],
|
||||
)
|
||||
def test_str(method, uri, expected_str):
|
||||
assert str(Request(method, uri, "", {})) == expected_str
|
||||
|
||||
|
||||
def test_headers():
|
||||
headers = {'X-Header1': ['h1'], 'X-Header2': 'h2'}
|
||||
req = Request('GET', 'http://go.com/', '', headers)
|
||||
assert req.headers == {'X-Header1': 'h1', 'X-Header2': 'h2'}
|
||||
req.headers['X-Header1'] = 'h11'
|
||||
assert req.headers == {'X-Header1': 'h11', 'X-Header2': 'h2'}
|
||||
headers = {"X-Header1": ["h1"], "X-Header2": "h2"}
|
||||
req = Request("GET", "http://go.com/", "", headers)
|
||||
assert req.headers == {"X-Header1": "h1", "X-Header2": "h2"}
|
||||
req.headers["X-Header1"] = "h11"
|
||||
assert req.headers == {"X-Header1": "h11", "X-Header2": "h2"}
|
||||
|
||||
|
||||
def test_add_header_deprecated():
|
||||
req = Request('GET', 'http://go.com/', '', {})
|
||||
pytest.deprecated_call(req.add_header, 'foo', 'bar')
|
||||
assert req.headers == {'foo': 'bar'}
|
||||
req = Request("GET", "http://go.com/", "", {})
|
||||
pytest.deprecated_call(req.add_header, "foo", "bar")
|
||||
assert req.headers == {"foo": "bar"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("uri, expected_port", [
|
||||
('http://go.com/', 80),
|
||||
('http://go.com:80/', 80),
|
||||
('http://go.com:3000/', 3000),
|
||||
('https://go.com/', 443),
|
||||
('https://go.com:443/', 443),
|
||||
('https://go.com:3000/', 3000),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"uri, expected_port",
|
||||
[
|
||||
("http://go.com/", 80),
|
||||
("http://go.com:80/", 80),
|
||||
("http://go.com:3000/", 3000),
|
||||
("https://go.com/", 443),
|
||||
("https://go.com:443/", 443),
|
||||
("https://go.com:3000/", 3000),
|
||||
("*", None),
|
||||
],
|
||||
)
|
||||
def test_port(uri, expected_port):
|
||||
req = Request('GET', uri, '', {})
|
||||
req = Request("GET", uri, "", {})
|
||||
assert req.port == expected_port
|
||||
|
||||
|
||||
def test_uri():
|
||||
req = Request('GET', 'http://go.com/', '', {})
|
||||
assert req.uri == 'http://go.com/'
|
||||
|
||||
req = Request('GET', 'http://go.com:80/', '', {})
|
||||
assert req.uri == 'http://go.com:80/'
|
||||
@pytest.mark.parametrize(
|
||||
"method, uri",
|
||||
[
|
||||
("GET", "http://go.com/"),
|
||||
("GET", "http://go.com:80/"),
|
||||
("CONNECT", "localhost:1234"),
|
||||
("OPTIONS", "*"),
|
||||
],
|
||||
)
|
||||
def test_uri(method, uri):
|
||||
assert Request(method, uri, "", {}).uri == uri
|
||||
|
||||
|
||||
def test_HeadersDict():
|
||||
@@ -48,23 +64,23 @@ def test_HeadersDict():
|
||||
# Simple test of CaseInsensitiveDict
|
||||
h = HeadersDict()
|
||||
assert h == {}
|
||||
h['Content-Type'] = 'application/json'
|
||||
assert h == {'Content-Type': 'application/json'}
|
||||
assert h['content-type'] == 'application/json'
|
||||
assert h['CONTENT-TYPE'] == 'application/json'
|
||||
h["Content-Type"] = "application/json"
|
||||
assert h == {"Content-Type": "application/json"}
|
||||
assert h["content-type"] == "application/json"
|
||||
assert h["CONTENT-TYPE"] == "application/json"
|
||||
|
||||
# Test feature of HeadersDict: devolve list to first element
|
||||
h = HeadersDict()
|
||||
assert h == {}
|
||||
h['x'] = ['foo', 'bar']
|
||||
assert h == {'x': 'foo'}
|
||||
h["x"] = ["foo", "bar"]
|
||||
assert h == {"x": "foo"}
|
||||
|
||||
# Test feature of HeadersDict: preserve original key case
|
||||
h = HeadersDict()
|
||||
assert h == {}
|
||||
h['Content-Type'] = 'application/json'
|
||||
assert h == {'Content-Type': 'application/json'}
|
||||
h['content-type'] = 'text/plain'
|
||||
assert h == {'Content-Type': 'text/plain'}
|
||||
h['CONtent-tyPE'] = 'whoa'
|
||||
assert h == {'Content-Type': 'whoa'}
|
||||
h["Content-Type"] = "application/json"
|
||||
assert h == {"Content-Type": "application/json"}
|
||||
h["content-type"] = "text/plain"
|
||||
assert h == {"Content-Type": "text/plain"}
|
||||
h["CONtent-tyPE"] = "whoa"
|
||||
assert h == {"Content-Type": "whoa"}
|
||||
|
||||
@@ -1,13 +1,71 @@
|
||||
# coding: UTF-8
|
||||
import io
|
||||
import unittest
|
||||
|
||||
import six
|
||||
|
||||
from vcr.stubs import VCRHTTPResponse
|
||||
|
||||
|
||||
def test_response_should_have_headers_field():
|
||||
recorded_response = {
|
||||
"status": {
|
||||
"message": "OK",
|
||||
"code": 200
|
||||
"status": {"message": "OK", "code": 200},
|
||||
"headers": {
|
||||
"content-length": ["0"],
|
||||
"server": ["gunicorn/18.0"],
|
||||
"connection": ["Close"],
|
||||
"access-control-allow-credentials": ["true"],
|
||||
"date": ["Fri, 24 Oct 2014 18:35:37 GMT"],
|
||||
"access-control-allow-origin": ["*"],
|
||||
"content-type": ["text/html; charset=utf-8"],
|
||||
},
|
||||
"body": {"string": b""},
|
||||
}
|
||||
response = VCRHTTPResponse(recorded_response)
|
||||
|
||||
assert response.headers is not None
|
||||
|
||||
|
||||
def test_response_headers_should_be_equal_to_msg():
|
||||
recorded_response = {
|
||||
"status": {"message": b"OK", "code": 200},
|
||||
"headers": {
|
||||
"content-length": ["0"],
|
||||
"server": ["gunicorn/18.0"],
|
||||
"connection": ["Close"],
|
||||
"content-type": ["text/html; charset=utf-8"],
|
||||
},
|
||||
"body": {"string": b""},
|
||||
}
|
||||
response = VCRHTTPResponse(recorded_response)
|
||||
|
||||
assert response.headers == response.msg
|
||||
|
||||
|
||||
def test_response_headers_should_have_correct_values():
|
||||
recorded_response = {
|
||||
"status": {"message": "OK", "code": 200},
|
||||
"headers": {
|
||||
"content-length": ["10806"],
|
||||
"date": ["Fri, 24 Oct 2014 18:35:37 GMT"],
|
||||
"content-type": ["text/html; charset=utf-8"],
|
||||
},
|
||||
"body": {"string": b""},
|
||||
}
|
||||
response = VCRHTTPResponse(recorded_response)
|
||||
|
||||
assert response.headers.get("content-length") == "10806"
|
||||
assert response.headers.get("date") == "Fri, 24 Oct 2014 18:35:37 GMT"
|
||||
|
||||
|
||||
@unittest.skipIf(six.PY2, "Regression test for Python3 only")
|
||||
def test_response_parses_correctly_and_fp_attribute_error_is_not_thrown():
|
||||
"""
|
||||
Regression test for https://github.com/kevin1024/vcrpy/issues/440
|
||||
:return:
|
||||
"""
|
||||
recorded_response = {
|
||||
"status": {"message": "OK", "code": 200},
|
||||
"headers": {
|
||||
"content-length": ["0"],
|
||||
"server": ["gunicorn/18.0"],
|
||||
@@ -18,51 +76,28 @@ def test_response_should_have_headers_field():
|
||||
"content-type": ["text/html; charset=utf-8"],
|
||||
},
|
||||
"body": {
|
||||
"string": b""
|
||||
}
|
||||
"string": b"\nPMID- 19416910\nOWN - NLM\nSTAT- MEDLINE\nDA - 20090513\nDCOM- "
|
||||
b"20090622\nLR - "
|
||||
b"20141209\nIS - 1091-6490 (Electronic)\nIS - 0027-8424 (Linking)\nVI - "
|
||||
b"106\nIP - "
|
||||
b"19\nDP - 2009 May 12\nTI - Genetic dissection of histone deacetylase "
|
||||
b"requirement in "
|
||||
b"tumor cells.\nPG - 7751-5\nLID - 10.1073/pnas.0903139106 [doi]\nAB - "
|
||||
b"Histone "
|
||||
b"deacetylase inhibitors (HDACi) represent a new group of drugs currently\n "
|
||||
b" being "
|
||||
b"tested in a wide variety of clinical applications. They are especially\n "
|
||||
b" effective "
|
||||
b"in preclinical models of cancer where they show antiproliferative\n "
|
||||
b"action in many "
|
||||
b"different types of cancer cells. Recently, the first HDACi was\n "
|
||||
b"approved for the "
|
||||
b"treatment of cutaneous T cell lymphomas. Most HDACi currently in\n "
|
||||
b"clinical "
|
||||
},
|
||||
}
|
||||
response = VCRHTTPResponse(recorded_response)
|
||||
|
||||
assert response.headers is not None
|
||||
|
||||
|
||||
def test_response_headers_should_be_equal_to_msg():
|
||||
recorded_response = {
|
||||
"status": {
|
||||
"message": b"OK",
|
||||
"code": 200
|
||||
},
|
||||
"headers": {
|
||||
"content-length": ["0"],
|
||||
"server": ["gunicorn/18.0"],
|
||||
"connection": ["Close"],
|
||||
"content-type": ["text/html; charset=utf-8"],
|
||||
},
|
||||
"body": {
|
||||
"string": b""
|
||||
}
|
||||
}
|
||||
response = VCRHTTPResponse(recorded_response)
|
||||
|
||||
assert response.headers == response.msg
|
||||
|
||||
|
||||
def test_response_headers_should_have_correct_values():
|
||||
recorded_response = {
|
||||
"status": {
|
||||
"message": "OK",
|
||||
"code": 200
|
||||
},
|
||||
"headers": {
|
||||
"content-length": ["10806"],
|
||||
"date": ["Fri, 24 Oct 2014 18:35:37 GMT"],
|
||||
"content-type": ["text/html; charset=utf-8"],
|
||||
},
|
||||
"body": {
|
||||
"string": b""
|
||||
}
|
||||
}
|
||||
response = VCRHTTPResponse(recorded_response)
|
||||
|
||||
assert response.headers.get('content-length') == "10806"
|
||||
assert response.headers.get('date') == "Fri, 24 Oct 2014 18:35:37 GMT"
|
||||
vcr_response = VCRHTTPResponse(recorded_response)
|
||||
handle = io.TextIOWrapper(io.BufferedReader(vcr_response), encoding="utf-8")
|
||||
handle = iter(handle)
|
||||
articles = [line for line in handle]
|
||||
assert len(articles) > 1
|
||||
|
||||
@@ -8,28 +8,28 @@ from vcr.serializers import yamlserializer, jsonserializer, compat
|
||||
|
||||
|
||||
def test_deserialize_old_yaml_cassette():
|
||||
with open('tests/fixtures/migration/old_cassette.yaml', 'r') as f:
|
||||
with open("tests/fixtures/migration/old_cassette.yaml", "r") as f:
|
||||
with pytest.raises(ValueError):
|
||||
deserialize(f.read(), yamlserializer)
|
||||
|
||||
|
||||
def test_deserialize_old_json_cassette():
|
||||
with open('tests/fixtures/migration/old_cassette.json', 'r') as f:
|
||||
with open("tests/fixtures/migration/old_cassette.json", "r") as f:
|
||||
with pytest.raises(ValueError):
|
||||
deserialize(f.read(), jsonserializer)
|
||||
|
||||
|
||||
def test_deserialize_new_yaml_cassette():
|
||||
with open('tests/fixtures/migration/new_cassette.yaml', 'r') as f:
|
||||
with open("tests/fixtures/migration/new_cassette.yaml", "r") as f:
|
||||
deserialize(f.read(), yamlserializer)
|
||||
|
||||
|
||||
def test_deserialize_new_json_cassette():
|
||||
with open('tests/fixtures/migration/new_cassette.json', 'r') as f:
|
||||
with open("tests/fixtures/migration/new_cassette.json", "r") as f:
|
||||
deserialize(f.read(), jsonserializer)
|
||||
|
||||
|
||||
REQBODY_TEMPLATE = u'''\
|
||||
REQBODY_TEMPLATE = u"""\
|
||||
interactions:
|
||||
- request:
|
||||
body: {req_body}
|
||||
@@ -44,96 +44,76 @@ interactions:
|
||||
content-length: ['0']
|
||||
content-type: [application/json]
|
||||
status: {{code: 200, message: OK}}
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
# A cassette generated under Python 2 stores the request body as a string,
|
||||
# but the same cassette generated under Python 3 stores it as "!!binary".
|
||||
# Make sure we accept both forms, regardless of whether we're running under
|
||||
# Python 2 or 3.
|
||||
@pytest.mark.parametrize("req_body, expect", [
|
||||
# Cassette written under Python 2 (pure ASCII body)
|
||||
('x=5&y=2', b'x=5&y=2'),
|
||||
# Cassette written under Python 3 (pure ASCII body)
|
||||
('!!binary |\n eD01Jnk9Mg==', b'x=5&y=2'),
|
||||
|
||||
# Request body has non-ASCII chars (x=föo&y=2), encoded in UTF-8.
|
||||
('!!python/str "x=f\\xF6o&y=2"', b'x=f\xc3\xb6o&y=2'),
|
||||
('!!binary |\n eD1mw7ZvJnk9Mg==', b'x=f\xc3\xb6o&y=2'),
|
||||
|
||||
# Same request body, this time encoded in UTF-16. In this case, we
|
||||
# write the same YAML file under both Python 2 and 3, so there's only
|
||||
# one test case here.
|
||||
('!!binary |\n //54AD0AZgD2AG8AJgB5AD0AMgA=',
|
||||
b'\xff\xfex\x00=\x00f\x00\xf6\x00o\x00&\x00y\x00=\x002\x00'),
|
||||
|
||||
# Same again, this time encoded in ISO-8859-1.
|
||||
('!!binary |\n eD1m9m8meT0y', b'x=f\xf6o&y=2'),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"req_body, expect",
|
||||
[
|
||||
# Cassette written under Python 2 (pure ASCII body)
|
||||
("x=5&y=2", b"x=5&y=2"),
|
||||
# Cassette written under Python 3 (pure ASCII body)
|
||||
("!!binary |\n eD01Jnk9Mg==", b"x=5&y=2"),
|
||||
# Request body has non-ASCII chars (x=föo&y=2), encoded in UTF-8.
|
||||
('!!python/str "x=f\\xF6o&y=2"', b"x=f\xc3\xb6o&y=2"),
|
||||
("!!binary |\n eD1mw7ZvJnk9Mg==", b"x=f\xc3\xb6o&y=2"),
|
||||
# Same request body, this time encoded in UTF-16. In this case, we
|
||||
# write the same YAML file under both Python 2 and 3, so there's only
|
||||
# one test case here.
|
||||
(
|
||||
"!!binary |\n //54AD0AZgD2AG8AJgB5AD0AMgA=",
|
||||
b"\xff\xfex\x00=\x00f\x00\xf6\x00o\x00&\x00y\x00=\x002\x00",
|
||||
),
|
||||
# Same again, this time encoded in ISO-8859-1.
|
||||
("!!binary |\n eD1m9m8meT0y", b"x=f\xf6o&y=2"),
|
||||
],
|
||||
)
|
||||
def test_deserialize_py2py3_yaml_cassette(tmpdir, req_body, expect):
|
||||
cfile = tmpdir.join('test_cassette.yaml')
|
||||
cfile = tmpdir.join("test_cassette.yaml")
|
||||
cfile.write(REQBODY_TEMPLATE.format(req_body=req_body))
|
||||
with open(str(cfile)) as f:
|
||||
(requests, responses) = deserialize(f.read(), yamlserializer)
|
||||
assert requests[0].body == expect
|
||||
|
||||
|
||||
@mock.patch.object(jsonserializer.json, 'dumps',
|
||||
side_effect=UnicodeDecodeError('utf-8', b'unicode error in serialization',
|
||||
0, 10, 'blew up'))
|
||||
@mock.patch.object(
|
||||
jsonserializer.json,
|
||||
"dumps",
|
||||
side_effect=UnicodeDecodeError("utf-8", b"unicode error in serialization", 0, 10, "blew up"),
|
||||
)
|
||||
def test_serialize_constructs_UnicodeDecodeError(mock_dumps):
|
||||
with pytest.raises(UnicodeDecodeError):
|
||||
jsonserializer.serialize({})
|
||||
|
||||
|
||||
def test_serialize_empty_request():
|
||||
request = Request(
|
||||
method='POST',
|
||||
uri='http://localhost/',
|
||||
body='',
|
||||
headers={},
|
||||
)
|
||||
request = Request(method="POST", uri="http://localhost/", body="", headers={})
|
||||
|
||||
serialize(
|
||||
{'requests': [request], 'responses': [{}]},
|
||||
jsonserializer
|
||||
)
|
||||
serialize({"requests": [request], "responses": [{}]}, jsonserializer)
|
||||
|
||||
|
||||
def test_serialize_json_request():
|
||||
request = Request(
|
||||
method='POST',
|
||||
uri='http://localhost/',
|
||||
body="{'hello': 'world'}",
|
||||
headers={},
|
||||
)
|
||||
request = Request(method="POST", uri="http://localhost/", body="{'hello': 'world'}", headers={})
|
||||
|
||||
serialize(
|
||||
{'requests': [request], 'responses': [{}]},
|
||||
jsonserializer
|
||||
)
|
||||
serialize({"requests": [request], "responses": [{}]}, jsonserializer)
|
||||
|
||||
|
||||
def test_serialize_binary_request():
|
||||
msg = "Does this HTTP interaction contain binary data?"
|
||||
|
||||
request = Request(
|
||||
method='POST',
|
||||
uri='http://localhost/',
|
||||
body=b'\x8c',
|
||||
headers={},
|
||||
)
|
||||
request = Request(method="POST", uri="http://localhost/", body=b"\x8c", headers={})
|
||||
|
||||
try:
|
||||
serialize(
|
||||
{'requests': [request], 'responses': [{}]},
|
||||
jsonserializer
|
||||
)
|
||||
serialize({"requests": [request], "responses": [{}]}, jsonserializer)
|
||||
except (UnicodeDecodeError, TypeError) as exc:
|
||||
assert msg in str(exc)
|
||||
|
||||
|
||||
def test_deserialize_no_body_string():
|
||||
data = {'body': {'string': None}}
|
||||
data = {"body": {"string": None}}
|
||||
output = compat.convert_to_bytes(data)
|
||||
assert data == output
|
||||
|
||||
@@ -4,15 +4,14 @@ from vcr.cassette import Cassette
|
||||
|
||||
|
||||
class TestVCRConnection(object):
|
||||
|
||||
def test_setting_of_attributes_get_propogated_to_real_connection(self):
|
||||
vcr_connection = VCRHTTPSConnection('www.examplehost.com')
|
||||
vcr_connection.ssl_version = 'example_ssl_version'
|
||||
assert vcr_connection.real_connection.ssl_version == 'example_ssl_version'
|
||||
vcr_connection = VCRHTTPSConnection("www.examplehost.com")
|
||||
vcr_connection.ssl_version = "example_ssl_version"
|
||||
assert vcr_connection.real_connection.ssl_version == "example_ssl_version"
|
||||
|
||||
@mock.patch('vcr.cassette.Cassette.can_play_response_for', return_value=False)
|
||||
@mock.patch("vcr.cassette.Cassette.can_play_response_for", return_value=False)
|
||||
def testing_connect(*args):
|
||||
vcr_connection = VCRHTTPSConnection('www.google.com')
|
||||
vcr_connection.cassette = Cassette('test', record_mode='all')
|
||||
vcr_connection = VCRHTTPSConnection("www.google.com")
|
||||
vcr_connection.cassette = Cassette("test", record_mode="all")
|
||||
vcr_connection.real_connection.connect()
|
||||
assert vcr_connection.real_connection.sock is not None
|
||||
|
||||
@@ -14,75 +14,75 @@ def test_vcr_use_cassette():
|
||||
record_mode = mock.Mock()
|
||||
test_vcr = VCR(record_mode=record_mode)
|
||||
with mock.patch(
|
||||
'vcr.cassette.Cassette.load',
|
||||
return_value=mock.MagicMock(inject=False)
|
||||
"vcr.cassette.Cassette.load", return_value=mock.MagicMock(inject=False)
|
||||
) as mock_cassette_load:
|
||||
|
||||
@test_vcr.use_cassette('test')
|
||||
@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
|
||||
assert mock_cassette_load.call_args[1]["record_mode"] is record_mode
|
||||
|
||||
# Make sure that calls to function now use cassettes with the
|
||||
# new filter_header_settings
|
||||
test_vcr.record_mode = mock.Mock()
|
||||
function()
|
||||
assert mock_cassette_load.call_args[1]['record_mode'] == test_vcr.record_mode
|
||||
assert mock_cassette_load.call_args[1]["record_mode"] == test_vcr.record_mode
|
||||
|
||||
# Ensure that explicitly provided arguments still supercede
|
||||
# those on the vcr.
|
||||
new_record_mode = mock.Mock()
|
||||
|
||||
with test_vcr.use_cassette('test', record_mode=new_record_mode) as cassette:
|
||||
with test_vcr.use_cassette("test", record_mode=new_record_mode) as cassette:
|
||||
assert cassette.record_mode == new_record_mode
|
||||
|
||||
|
||||
def test_vcr_before_record_request_params():
|
||||
base_path = 'http://httpbin.org/'
|
||||
base_path = "http://httpbin.org/"
|
||||
|
||||
def before_record_cb(request):
|
||||
if request.path != '/get':
|
||||
if request.path != "/get":
|
||||
return request
|
||||
|
||||
test_vcr = VCR(filter_headers=('cookie', ('bert', 'ernie')),
|
||||
before_record_request=before_record_cb,
|
||||
ignore_hosts=('www.test.com',), ignore_localhost=True,
|
||||
filter_query_parameters=('foo', ('tom', 'jerry')),
|
||||
filter_post_data_parameters=('posted', ('no', 'trespassing')))
|
||||
test_vcr = VCR(
|
||||
filter_headers=("cookie", ("bert", "ernie")),
|
||||
before_record_request=before_record_cb,
|
||||
ignore_hosts=("www.test.com",),
|
||||
ignore_localhost=True,
|
||||
filter_query_parameters=("foo", ("tom", "jerry")),
|
||||
filter_post_data_parameters=("posted", ("no", "trespassing")),
|
||||
)
|
||||
|
||||
with test_vcr.use_cassette('test') as cassette:
|
||||
with test_vcr.use_cassette("test") as cassette:
|
||||
# Test explicit before_record_cb
|
||||
request_get = Request('GET', base_path + 'get', '', {})
|
||||
request_get = Request("GET", base_path + "get", "", {})
|
||||
assert cassette.filter_request(request_get) is None
|
||||
request = Request('GET', base_path + 'get2', '', {})
|
||||
request = Request("GET", base_path + "get2", "", {})
|
||||
assert cassette.filter_request(request) is not None
|
||||
|
||||
# Test filter_query_parameters
|
||||
request = Request('GET', base_path + '?foo=bar', '', {})
|
||||
request = Request("GET", base_path + "?foo=bar", "", {})
|
||||
assert cassette.filter_request(request).query == []
|
||||
request = Request('GET', base_path + '?tom=nobody', '', {})
|
||||
assert cassette.filter_request(request).query == [('tom', 'jerry')]
|
||||
request = Request("GET", base_path + "?tom=nobody", "", {})
|
||||
assert cassette.filter_request(request).query == [("tom", "jerry")]
|
||||
|
||||
# Test filter_headers
|
||||
request = Request('GET', base_path + '?foo=bar', '',
|
||||
{'cookie': 'test', 'other': 'fun', 'bert': 'nobody'})
|
||||
assert (cassette.filter_request(request).headers ==
|
||||
{'other': 'fun', 'bert': 'ernie'})
|
||||
request = Request(
|
||||
"GET", base_path + "?foo=bar", "", {"cookie": "test", "other": "fun", "bert": "nobody"}
|
||||
)
|
||||
assert cassette.filter_request(request).headers == {"other": "fun", "bert": "ernie"}
|
||||
|
||||
# Test ignore_hosts
|
||||
request = Request('GET', 'http://www.test.com' + '?foo=bar', '',
|
||||
{'cookie': 'test', 'other': 'fun'})
|
||||
request = Request("GET", "http://www.test.com" + "?foo=bar", "", {"cookie": "test", "other": "fun"})
|
||||
assert cassette.filter_request(request) is None
|
||||
|
||||
# Test ignore_localhost
|
||||
request = Request('GET', 'http://localhost:8000' + '?foo=bar', '',
|
||||
{'cookie': 'test', 'other': 'fun'})
|
||||
request = Request("GET", "http://localhost:8000" + "?foo=bar", "", {"cookie": "test", "other": "fun"})
|
||||
assert cassette.filter_request(request) is None
|
||||
|
||||
with test_vcr.use_cassette('test', before_record_request=None) as cassette:
|
||||
with test_vcr.use_cassette("test", before_record_request=None) as cassette:
|
||||
# Test that before_record can be overwritten in context manager.
|
||||
assert cassette.filter_request(request_get) is not None
|
||||
|
||||
@@ -90,16 +90,16 @@ def test_vcr_before_record_request_params():
|
||||
def test_vcr_before_record_response_iterable():
|
||||
# Regression test for #191
|
||||
|
||||
request = Request('GET', '/', '', {})
|
||||
request = Request("GET", "/", "", {})
|
||||
response = object() # just can't be None
|
||||
|
||||
# Prevent actually saving the cassette
|
||||
with mock.patch('vcr.cassette.FilesystemPersister.save_cassette'):
|
||||
with mock.patch("vcr.cassette.FilesystemPersister.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:
|
||||
with vcr.use_cassette("test") as cassette:
|
||||
assert mock_filter.call_count == 0
|
||||
cassette.append(request, response)
|
||||
assert mock_filter.call_count == 1
|
||||
@@ -107,22 +107,22 @@ def test_vcr_before_record_response_iterable():
|
||||
# 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:
|
||||
with vcr.use_cassette("test") as cassette:
|
||||
assert mock_filter.call_count == 0
|
||||
cassette.append(request, response)
|
||||
assert mock_filter.call_count == 1
|
||||
|
||||
|
||||
def test_before_record_response_as_filter():
|
||||
request = Request('GET', '/', '', {})
|
||||
request = Request("GET", "/", "", {})
|
||||
response = object() # just can't be None
|
||||
|
||||
# Prevent actually saving the cassette
|
||||
with mock.patch('vcr.cassette.FilesystemPersister.save_cassette'):
|
||||
with mock.patch("vcr.cassette.FilesystemPersister.save_cassette"):
|
||||
|
||||
filter_all = mock.Mock(return_value=None)
|
||||
vcr = VCR(before_record_response=filter_all)
|
||||
with vcr.use_cassette('test') as cassette:
|
||||
with vcr.use_cassette("test") as cassette:
|
||||
cassette.append(request, response)
|
||||
assert cassette.data == []
|
||||
assert not cassette.dirty
|
||||
@@ -132,22 +132,22 @@ def test_vcr_path_transformer():
|
||||
# Regression test for #199
|
||||
|
||||
# Prevent actually saving the cassette
|
||||
with mock.patch('vcr.cassette.FilesystemPersister.save_cassette'):
|
||||
with mock.patch("vcr.cassette.FilesystemPersister.save_cassette"):
|
||||
|
||||
# Baseline: path should be unchanged
|
||||
vcr = VCR()
|
||||
with vcr.use_cassette('test') as cassette:
|
||||
assert cassette._path == 'test'
|
||||
with vcr.use_cassette("test") as cassette:
|
||||
assert cassette._path == "test"
|
||||
|
||||
# Regression test: path_transformer=None should do the same.
|
||||
vcr = VCR(path_transformer=None)
|
||||
with vcr.use_cassette('test') as cassette:
|
||||
assert cassette._path == 'test'
|
||||
with vcr.use_cassette("test") as cassette:
|
||||
assert cassette._path == "test"
|
||||
|
||||
# and it should still work with cassette_library_dir
|
||||
vcr = VCR(cassette_library_dir='/foo')
|
||||
with vcr.use_cassette('test') as cassette:
|
||||
assert cassette._path == '/foo/test'
|
||||
vcr = VCR(cassette_library_dir="/foo")
|
||||
with vcr.use_cassette("test") as cassette:
|
||||
assert os.path.abspath(cassette._path) == os.path.abspath("/foo/test")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -155,7 +155,7 @@ def random_fixture():
|
||||
return 1
|
||||
|
||||
|
||||
@use_cassette('test')
|
||||
@use_cassette("test")
|
||||
def test_fixtures_with_use_cassette(random_fixture):
|
||||
# Applying a decorator to a test function that requests features can cause
|
||||
# problems if the decorator does not preserve the signature of the original
|
||||
@@ -173,15 +173,13 @@ def test_custom_patchers():
|
||||
class Test(object):
|
||||
attribute = None
|
||||
attribute2 = None
|
||||
test_vcr = VCR(custom_patches=((Test, 'attribute', VCRHTTPSConnection),))
|
||||
with test_vcr.use_cassette('custom_patches'):
|
||||
|
||||
test_vcr = VCR(custom_patches=((Test, "attribute", VCRHTTPSConnection),))
|
||||
with test_vcr.use_cassette("custom_patches"):
|
||||
assert issubclass(Test.attribute, VCRHTTPSConnection)
|
||||
assert VCRHTTPSConnection is not Test.attribute
|
||||
|
||||
with test_vcr.use_cassette(
|
||||
'custom_patches',
|
||||
custom_patches=((Test, 'attribute2', VCRHTTPSConnection),)
|
||||
):
|
||||
with test_vcr.use_cassette("custom_patches", custom_patches=((Test, "attribute2", VCRHTTPSConnection),)):
|
||||
assert issubclass(Test.attribute, VCRHTTPSConnection)
|
||||
assert VCRHTTPSConnection is not Test.attribute
|
||||
assert Test.attribute is Test.attribute2
|
||||
@@ -190,11 +188,11 @@ def test_custom_patchers():
|
||||
def test_inject_cassette():
|
||||
vcr = VCR(inject_cassette=True)
|
||||
|
||||
@vcr.use_cassette('test', record_mode='once')
|
||||
@vcr.use_cassette("test", record_mode="once")
|
||||
def with_cassette_injected(cassette):
|
||||
assert cassette.record_mode == 'once'
|
||||
assert cassette.record_mode == "once"
|
||||
|
||||
@vcr.use_cassette('test', record_mode='once', inject_cassette=False)
|
||||
@vcr.use_cassette("test", record_mode="once", inject_cassette=False)
|
||||
def without_cassette_injected():
|
||||
pass
|
||||
|
||||
@@ -203,92 +201,92 @@ def test_inject_cassette():
|
||||
|
||||
|
||||
def test_with_current_defaults():
|
||||
vcr = VCR(inject_cassette=True, record_mode='once')
|
||||
vcr = VCR(inject_cassette=True, record_mode="once")
|
||||
|
||||
@vcr.use_cassette('test', with_current_defaults=False)
|
||||
@vcr.use_cassette("test", with_current_defaults=False)
|
||||
def changing_defaults(cassette, checks):
|
||||
checks(cassette)
|
||||
|
||||
@vcr.use_cassette('test', with_current_defaults=True)
|
||||
@vcr.use_cassette("test", with_current_defaults=True)
|
||||
def current_defaults(cassette, checks):
|
||||
checks(cassette)
|
||||
|
||||
def assert_record_mode_once(cassette):
|
||||
assert cassette.record_mode == 'once'
|
||||
assert cassette.record_mode == "once"
|
||||
|
||||
def assert_record_mode_all(cassette):
|
||||
assert cassette.record_mode == 'all'
|
||||
assert cassette.record_mode == "all"
|
||||
|
||||
changing_defaults(assert_record_mode_once)
|
||||
current_defaults(assert_record_mode_once)
|
||||
|
||||
vcr.record_mode = 'all'
|
||||
vcr.record_mode = "all"
|
||||
changing_defaults(assert_record_mode_all)
|
||||
current_defaults(assert_record_mode_once)
|
||||
|
||||
|
||||
def test_cassette_library_dir_with_decoration_and_no_explicit_path():
|
||||
library_dir = '/libary_dir'
|
||||
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')
|
||||
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'
|
||||
library_dir = "/libary_dir"
|
||||
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir)
|
||||
|
||||
@vcr.use_cassette(path='custom_name')
|
||||
@vcr.use_cassette(path="custom_name")
|
||||
def function_name(cassette):
|
||||
assert cassette._path == os.path.join(library_dir, 'custom_name')
|
||||
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'
|
||||
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'))
|
||||
@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')
|
||||
assert cassette._path == os.path.join(library_dir, "custom_name")
|
||||
|
||||
function_name()
|
||||
|
||||
|
||||
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')
|
||||
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')
|
||||
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 = VCR(inject_cassette=True, cassette_library_dir="/")
|
||||
|
||||
@vcr.use_cassette
|
||||
def function_name(cassette):
|
||||
assert cassette._path == os.path.join('/', 'function_name')
|
||||
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 = 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')
|
||||
assert cassette._path == os.path.join("/", "function_name_test")
|
||||
|
||||
function_name()
|
||||
|
||||
@@ -298,32 +296,31 @@ def test_cassette_name_generator_defaults_to_using_module_function_defined_in():
|
||||
|
||||
@vcr.use_cassette
|
||||
def function_name(cassette):
|
||||
assert cassette._path == os.path.join(os.path.dirname(__file__),
|
||||
'function_name')
|
||||
assert cassette._path == os.path.join(os.path.dirname(__file__), "function_name")
|
||||
|
||||
function_name()
|
||||
|
||||
|
||||
def test_ensure_suffix():
|
||||
vcr = VCR(inject_cassette=True, path_transformer=VCR.ensure_suffix('.yaml'))
|
||||
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')
|
||||
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 = VCR(match_on=("uri",), inject_cassette=True)
|
||||
|
||||
@vcr.use_cassette
|
||||
def function_defaults(cassette):
|
||||
assert set(cassette._match_on) == {vcr.matchers['uri']}
|
||||
assert set(cassette._match_on) == {vcr.matchers["uri"]}
|
||||
|
||||
@vcr.use_cassette(additional_matchers=('body',))
|
||||
@vcr.use_cassette(additional_matchers=("body",))
|
||||
def function_additional(cassette):
|
||||
assert set(cassette._match_on) == {vcr.matchers['uri'], vcr.matchers['body']}
|
||||
assert set(cassette._match_on) == {vcr.matchers["uri"], vcr.matchers["body"]}
|
||||
|
||||
function_defaults()
|
||||
function_additional()
|
||||
@@ -331,7 +328,7 @@ def test_additional_matchers():
|
||||
|
||||
def test_decoration_should_respect_function_return_value():
|
||||
vcr = VCR()
|
||||
ret = 'a-return-value'
|
||||
ret = "a-return-value"
|
||||
|
||||
@vcr.use_cassette
|
||||
def function_with_return():
|
||||
@@ -341,7 +338,6 @@ def test_decoration_should_respect_function_return_value():
|
||||
|
||||
|
||||
class TestVCRClass(VCR().test_case()):
|
||||
|
||||
def no_decoration(self):
|
||||
assert httplib.HTTPConnection == _HTTPConnection
|
||||
self.test_dynamically_added()
|
||||
|
||||
16
tests/unit/test_vcr_import.py
Normal file
16
tests/unit/test_vcr_import.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import sys
|
||||
|
||||
|
||||
def test_vcr_import_deprecation(recwarn):
|
||||
|
||||
if "vcr" in sys.modules:
|
||||
# Remove imported module entry if already loaded in another test
|
||||
del sys.modules["vcr"]
|
||||
|
||||
import vcr # noqa: F401
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
assert len(recwarn) == 1
|
||||
assert issubclass(recwarn[0].category, DeprecationWarning)
|
||||
else:
|
||||
assert len(recwarn) == 0
|
||||
52
tox.ini
52
tox.ini
@@ -1,33 +1,71 @@
|
||||
[tox]
|
||||
envlist = {py27,py34,py35,py36,py37,pypy}-{flakes,requests27,httplib2,urllib3121,tornado4,boto3,aiohttp}
|
||||
skip_missing_interpreters=true
|
||||
envlist =
|
||||
cov-clean,
|
||||
lint,
|
||||
{py27,py35,py36,py37,py38,pypy,pypy3}-{requests,httplib2,urllib3,tornado4,boto3},
|
||||
{py35,py36,py37,py38}-{aiohttp},
|
||||
cov-report
|
||||
|
||||
[testenv:flakes]
|
||||
|
||||
# Coverage environment tasks: cov-clean and cov-report
|
||||
# https://pytest-cov.readthedocs.io/en/latest/tox.html
|
||||
[testenv:cov-clean]
|
||||
deps = coverage
|
||||
skip_install=true
|
||||
commands = coverage erase
|
||||
|
||||
[testenv:cov-report]
|
||||
deps = coverage
|
||||
skip_install=true
|
||||
commands =
|
||||
coverage html
|
||||
coverage report --fail-under=90
|
||||
|
||||
[testenv:lint]
|
||||
skipsdist = True
|
||||
commands =
|
||||
black --version
|
||||
black --check --diff .
|
||||
flake8 --version
|
||||
flake8 --exclude=./docs/conf.py,./.tox/
|
||||
pyflakes ./docs/conf.py
|
||||
deps = flake8
|
||||
deps =
|
||||
flake8
|
||||
black
|
||||
|
||||
[testenv]
|
||||
# Need to use develop install so that paths
|
||||
# for aggregate code coverage combine
|
||||
usedevelop=true
|
||||
commands =
|
||||
./runtests.sh {posargs}
|
||||
./runtests.sh --cov=./vcr --cov-branch --cov-report=xml --cov-append {posargs}
|
||||
deps =
|
||||
Flask<1
|
||||
Flask
|
||||
mock
|
||||
pytest
|
||||
pytest-httpbin
|
||||
pytest-cov
|
||||
PyYAML
|
||||
requests27: requests==2.7.0
|
||||
ipaddress
|
||||
requests: requests>=2.22.0
|
||||
httplib2: httplib2
|
||||
urllib3121: urllib3==1.21.1
|
||||
urllib3: urllib3
|
||||
{py27,py35,py36,pypy}-tornado4: tornado>=4,<5
|
||||
{py27,py35,py36,pypy}-tornado4: pytest-tornado
|
||||
{py27,py35,py36}-tornado4: pycurl
|
||||
boto3: boto3
|
||||
boto3: urllib3
|
||||
aiohttp: aiohttp
|
||||
aiohttp: pytest-asyncio
|
||||
aiohttp: pytest-aiohttp
|
||||
depends =
|
||||
{py27,py35,py36,py37,pypy}-{lint,requests,httplib2,urllib3,tornado4,boto3},{py35,py36,py37}-{aiohttp}: cov-clean
|
||||
cov-report: {py27,py35,py36,py37,pypy}-{lint,requests,httplib2,urllib3,tornado4,boto3},{py35,py36,py37}-{aiohttp}
|
||||
passenv =
|
||||
AWS_ACCESS_KEY_ID
|
||||
AWS_DEFAULT_REGION
|
||||
AWS_SECRET_ACCESS_KEY
|
||||
|
||||
[flake8]
|
||||
max_line_length = 110
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import logging
|
||||
import warnings
|
||||
import sys
|
||||
from .config import VCR
|
||||
|
||||
# Set default logging handler to avoid "No handler found" warnings.
|
||||
try: # Python 2.7+
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
warnings.warn(
|
||||
"Python 2.x support of vcrpy is deprecated and will be removed in an upcoming major release.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
async def handle_coroutine(vcr, fn): # noqa: E999
|
||||
with vcr as cassette:
|
||||
return (await fn(cassette)) # noqa: E999
|
||||
return await fn(cassette) # noqa: E999
|
||||
|
||||
125
vcr/cassette.py
125
vcr/cassette.py
@@ -8,7 +8,7 @@ import wrapt
|
||||
|
||||
from .compat import contextlib
|
||||
from .errors import UnhandledHTTPRequestError
|
||||
from .matchers import requests_match, uri, method
|
||||
from .matchers import requests_match, uri, method, get_matchers_results
|
||||
from .patch import CassettePatcherBuilder
|
||||
from .serializers import yamlserializer
|
||||
from .persisters.filesystem import FilesystemPersister
|
||||
@@ -17,14 +17,17 @@ from .util import partition_dict
|
||||
try:
|
||||
from asyncio import iscoroutinefunction
|
||||
except ImportError:
|
||||
|
||||
def iscoroutinefunction(*args, **kwargs):
|
||||
return False
|
||||
|
||||
|
||||
if sys.version_info[:2] >= (3, 5):
|
||||
from ._handle_coroutine import handle_coroutine
|
||||
else:
|
||||
|
||||
def handle_coroutine(*args, **kwags):
|
||||
raise NotImplementedError('Not implemented on Python 2')
|
||||
raise NotImplementedError("Not implemented on Python 2")
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -48,7 +51,7 @@ class CassetteContextDecorator(object):
|
||||
this class as a context manager in ``__exit__``.
|
||||
"""
|
||||
|
||||
_non_cassette_arguments = ('path_transformer', 'func_path_generator')
|
||||
_non_cassette_arguments = ("path_transformer", "func_path_generator")
|
||||
|
||||
@classmethod
|
||||
def from_args(cls, cassette_class, **kwargs):
|
||||
@@ -63,14 +66,10 @@ class CassetteContextDecorator(object):
|
||||
with contextlib.ExitStack() as exit_stack:
|
||||
for patcher in CassettePatcherBuilder(cassette).build():
|
||||
exit_stack.enter_context(patcher)
|
||||
log_format = '{action} context for cassette at {path}.'
|
||||
log.debug(log_format.format(
|
||||
action="Entering", path=cassette._path
|
||||
))
|
||||
log_format = "{action} context for cassette at {path}."
|
||||
log.debug(log_format.format(action="Entering", path=cassette._path))
|
||||
yield cassette
|
||||
log.debug(log_format.format(
|
||||
action="Exiting", path=cassette._path
|
||||
))
|
||||
log.debug(log_format.format(action="Exiting", path=cassette._path))
|
||||
# TODO(@IvanMalison): Hmmm. it kind of feels like this should be
|
||||
# somewhere else.
|
||||
cassette._save()
|
||||
@@ -86,12 +85,11 @@ class CassetteContextDecorator(object):
|
||||
# pass
|
||||
assert self.__finish is None, "Cassette already open."
|
||||
other_kwargs, cassette_kwargs = partition_dict(
|
||||
lambda key, _: key in self._non_cassette_arguments,
|
||||
self._args_getter()
|
||||
lambda key, _: key in self._non_cassette_arguments, self._args_getter()
|
||||
)
|
||||
if other_kwargs.get('path_transformer'):
|
||||
transformer = other_kwargs['path_transformer']
|
||||
cassette_kwargs['path'] = transformer(cassette_kwargs['path'])
|
||||
if other_kwargs.get("path_transformer"):
|
||||
transformer = other_kwargs["path_transformer"]
|
||||
cassette_kwargs["path"] = transformer(cassette_kwargs["path"])
|
||||
self.__finish = self._patch_generator(self.cls.load(**cassette_kwargs))
|
||||
return next(self.__finish)
|
||||
|
||||
@@ -105,9 +103,7 @@ class CassetteContextDecorator(object):
|
||||
# functions are reentrant. This is required for thread
|
||||
# safety and the correct operation of recursive functions.
|
||||
args_getter = self._build_args_getter_for_decorator(function)
|
||||
return type(self)(self.cls, args_getter)._execute_function(
|
||||
function, args, kwargs
|
||||
)
|
||||
return type(self)(self.cls, args_getter)._execute_function(function, args, kwargs)
|
||||
|
||||
def _execute_function(self, function, args, kwargs):
|
||||
def handle_function(cassette):
|
||||
@@ -154,12 +150,12 @@ class CassetteContextDecorator(object):
|
||||
def _build_args_getter_for_decorator(self, function):
|
||||
def new_args_getter():
|
||||
kwargs = self._args_getter()
|
||||
if 'path' not in kwargs:
|
||||
name_generator = (kwargs.get('func_path_generator') or
|
||||
self.get_function_name)
|
||||
if "path" not in kwargs:
|
||||
name_generator = kwargs.get("func_path_generator") or self.get_function_name
|
||||
path = name_generator(function)
|
||||
kwargs['path'] = path
|
||||
kwargs["path"] = path
|
||||
return kwargs
|
||||
|
||||
return new_args_getter
|
||||
|
||||
|
||||
@@ -181,15 +177,24 @@ class Cassette(object):
|
||||
def use(cls, **kwargs):
|
||||
return CassetteContextDecorator.from_args(cls, **kwargs)
|
||||
|
||||
def __init__(self, path, serializer=None, persister=None, record_mode='once',
|
||||
match_on=(uri, method), before_record_request=None,
|
||||
before_record_response=None, custom_patches=(),
|
||||
inject=False):
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
serializer=None,
|
||||
persister=None,
|
||||
record_mode="once",
|
||||
match_on=(uri, method),
|
||||
before_record_request=None,
|
||||
before_record_response=None,
|
||||
custom_patches=(),
|
||||
inject=False,
|
||||
):
|
||||
self._persister = persister or FilesystemPersister
|
||||
self._path = path
|
||||
self._serializer = serializer or yamlserializer
|
||||
self._match_on = match_on
|
||||
self._before_record_request = before_record_request or (lambda x: x)
|
||||
log.info(self._before_record_request)
|
||||
self._before_record_response = before_record_response or (lambda x: x)
|
||||
self.inject = inject
|
||||
self.record_mode = record_mode
|
||||
@@ -220,11 +225,11 @@ class Cassette(object):
|
||||
|
||||
@property
|
||||
def write_protected(self):
|
||||
return self.rewound and self.record_mode == 'once' or \
|
||||
self.record_mode == 'none'
|
||||
return self.rewound and self.record_mode == "once" or self.record_mode == "none"
|
||||
|
||||
def append(self, request, response):
|
||||
"""Add a request, response pair to this cassette"""
|
||||
log.info("Appending request %s and response %s", request, response)
|
||||
request = self._before_record_request(request)
|
||||
if not request:
|
||||
return
|
||||
@@ -252,9 +257,7 @@ class Cassette(object):
|
||||
|
||||
def can_play_response_for(self, request):
|
||||
request = self._before_record_request(request)
|
||||
return request and request in self and \
|
||||
self.record_mode != 'all' and \
|
||||
self.rewound
|
||||
return request and request in self and self.record_mode != "all" and self.rewound
|
||||
|
||||
def play_response(self, request):
|
||||
"""
|
||||
@@ -267,8 +270,7 @@ class Cassette(object):
|
||||
return response
|
||||
# The cassette doesn't contain the request asked for.
|
||||
raise UnhandledHTTPRequestError(
|
||||
"The cassette (%r) doesn't contain the request (%r) asked for"
|
||||
% (self._path, request)
|
||||
"The cassette (%r) doesn't contain the request (%r) asked for" % (self._path, request)
|
||||
)
|
||||
|
||||
def responses_of(self, request):
|
||||
@@ -283,28 +285,59 @@ class Cassette(object):
|
||||
return responses
|
||||
# The cassette doesn't contain the request asked for.
|
||||
raise UnhandledHTTPRequestError(
|
||||
"The cassette (%r) doesn't contain the request (%r) asked for"
|
||||
% (self._path, request)
|
||||
"The cassette (%r) doesn't contain the request (%r) asked for" % (self._path, request)
|
||||
)
|
||||
|
||||
def rewind(self):
|
||||
self.play_counts = collections.Counter()
|
||||
|
||||
def find_requests_with_most_matches(self, request):
|
||||
"""
|
||||
Get the most similar request(s) stored in the cassette
|
||||
of a given request as a list of tuples like this:
|
||||
- the request object
|
||||
- the successful matchers as string
|
||||
- the failed matchers and the related assertion message with the difference details as strings tuple
|
||||
|
||||
This is useful when a request failed to be found,
|
||||
we can get the similar request(s) in order to know what have changed in the request parts.
|
||||
"""
|
||||
best_matches = []
|
||||
request = self._before_record_request(request)
|
||||
for index, (stored_request, response) in enumerate(self.data):
|
||||
successes, fails = get_matchers_results(request, stored_request, self._match_on)
|
||||
best_matches.append((len(successes), stored_request, successes, fails))
|
||||
best_matches.sort(key=lambda t: t[0], reverse=True)
|
||||
# Get the first best matches (multiple if equal matches)
|
||||
final_best_matches = []
|
||||
|
||||
if not best_matches:
|
||||
return final_best_matches
|
||||
|
||||
previous_nb_success = best_matches[0][0]
|
||||
for best_match in best_matches:
|
||||
nb_success = best_match[0]
|
||||
# Do not keep matches that have 0 successes,
|
||||
# it means that the request is totally different from
|
||||
# the ones stored in the cassette
|
||||
if nb_success < 1 or previous_nb_success != nb_success:
|
||||
break
|
||||
previous_nb_success = nb_success
|
||||
final_best_matches.append(best_match[1:])
|
||||
|
||||
return final_best_matches
|
||||
|
||||
def _as_dict(self):
|
||||
return {"requests": self.requests, "responses": self.responses}
|
||||
|
||||
def _save(self, force=False):
|
||||
if force or self.dirty:
|
||||
self._persister.save_cassette(
|
||||
self._path,
|
||||
self._as_dict(),
|
||||
serializer=self._serializer,
|
||||
)
|
||||
self._persister.save_cassette(self._path, self._as_dict(), serializer=self._serializer)
|
||||
self.dirty = False
|
||||
|
||||
def _load(self):
|
||||
try:
|
||||
requests, responses = self._persister.load_cassette(
|
||||
self._path,
|
||||
serializer=self._serializer,
|
||||
)
|
||||
requests, responses = self._persister.load_cassette(self._path, serializer=self._serializer)
|
||||
for request, response in zip(requests, responses):
|
||||
self.append(request, response)
|
||||
self.dirty = False
|
||||
@@ -313,9 +346,7 @@ class Cassette(object):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return "<Cassette containing {} recorded response(s)>".format(
|
||||
len(self)
|
||||
)
|
||||
return "<Cassette containing {} recorded response(s)>".format(len(self))
|
||||
|
||||
def __len__(self):
|
||||
"""Return the number of request,response pairs stored in here"""
|
||||
|
||||
@@ -8,7 +8,7 @@ try:
|
||||
except ImportError:
|
||||
import contextlib2 as contextlib
|
||||
else:
|
||||
if not hasattr(contextlib, 'ExitStack'):
|
||||
if not hasattr(contextlib, "ExitStack"):
|
||||
import contextlib2 as contextlib
|
||||
|
||||
__all__ = ['mock', 'contextlib']
|
||||
__all__ = ["mock", "contextlib"]
|
||||
|
||||
202
vcr/config.py
202
vcr/config.py
@@ -1,5 +1,9 @@
|
||||
import copy
|
||||
import collections
|
||||
|
||||
try:
|
||||
from collections import abc as collections_abc # only works on python 3.3+
|
||||
except ImportError:
|
||||
import collections as collections_abc
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
@@ -16,11 +20,9 @@ from . import filters
|
||||
|
||||
|
||||
class VCR(object):
|
||||
|
||||
@staticmethod
|
||||
def is_test_method(method_name, function):
|
||||
return method_name.startswith('test') and \
|
||||
isinstance(function, types.FunctionType)
|
||||
return method_name.startswith("test") and isinstance(function, types.FunctionType)
|
||||
|
||||
@staticmethod
|
||||
def ensure_suffix(suffix):
|
||||
@@ -28,35 +30,45 @@ class VCR(object):
|
||||
if not path.endswith(suffix):
|
||||
return path + suffix
|
||||
return path
|
||||
|
||||
return ensure
|
||||
|
||||
def __init__(self, path_transformer=None, before_record_request=None,
|
||||
custom_patches=(), filter_query_parameters=(), ignore_hosts=(),
|
||||
record_mode="once", ignore_localhost=False, filter_headers=(),
|
||||
before_record_response=None, filter_post_data_parameters=(),
|
||||
match_on=('method', 'scheme', 'host', 'port', 'path', 'query'),
|
||||
before_record=None, inject_cassette=False, serializer='yaml',
|
||||
cassette_library_dir=None, func_path_generator=None,
|
||||
decode_compressed_response=False):
|
||||
def __init__(
|
||||
self,
|
||||
path_transformer=None,
|
||||
before_record_request=None,
|
||||
custom_patches=(),
|
||||
filter_query_parameters=(),
|
||||
ignore_hosts=(),
|
||||
record_mode="once",
|
||||
ignore_localhost=False,
|
||||
filter_headers=(),
|
||||
before_record_response=None,
|
||||
filter_post_data_parameters=(),
|
||||
match_on=("method", "scheme", "host", "port", "path", "query"),
|
||||
before_record=None,
|
||||
inject_cassette=False,
|
||||
serializer="yaml",
|
||||
cassette_library_dir=None,
|
||||
func_path_generator=None,
|
||||
decode_compressed_response=False,
|
||||
):
|
||||
self.serializer = serializer
|
||||
self.match_on = match_on
|
||||
self.cassette_library_dir = cassette_library_dir
|
||||
self.serializers = {
|
||||
'yaml': yamlserializer,
|
||||
'json': jsonserializer,
|
||||
}
|
||||
self.serializers = {"yaml": yamlserializer, "json": jsonserializer}
|
||||
self.matchers = {
|
||||
'method': matchers.method,
|
||||
'uri': matchers.uri,
|
||||
'url': matchers.uri, # matcher for backwards compatibility
|
||||
'scheme': matchers.scheme,
|
||||
'host': matchers.host,
|
||||
'port': matchers.port,
|
||||
'path': matchers.path,
|
||||
'query': matchers.query,
|
||||
'headers': matchers.headers,
|
||||
'raw_body': matchers.raw_body,
|
||||
'body': matchers.body,
|
||||
"method": matchers.method,
|
||||
"uri": matchers.uri,
|
||||
"url": matchers.uri, # matcher for backwards compatibility
|
||||
"scheme": matchers.scheme,
|
||||
"host": matchers.host,
|
||||
"port": matchers.port,
|
||||
"path": matchers.path,
|
||||
"query": matchers.query,
|
||||
"headers": matchers.headers,
|
||||
"raw_body": matchers.raw_body,
|
||||
"body": matchers.body,
|
||||
}
|
||||
self.persister = FilesystemPersister
|
||||
self.record_mode = record_mode
|
||||
@@ -77,11 +89,7 @@ class VCR(object):
|
||||
try:
|
||||
serializer = self.serializers[serializer_name]
|
||||
except KeyError:
|
||||
raise KeyError(
|
||||
"Serializer {} doesn't exist or isn't registered".format(
|
||||
serializer_name
|
||||
)
|
||||
)
|
||||
raise KeyError("Serializer {} doesn't exist or isn't registered".format(serializer_name))
|
||||
return serializer
|
||||
|
||||
def _get_matchers(self, matcher_names):
|
||||
@@ -90,9 +98,7 @@ class VCR(object):
|
||||
for m in matcher_names:
|
||||
matchers.append(self.matchers[m])
|
||||
except KeyError:
|
||||
raise KeyError(
|
||||
"Matcher {} doesn't exist or isn't registered".format(m)
|
||||
)
|
||||
raise KeyError("Matcher {} doesn't exist or isn't registered".format(m))
|
||||
return matchers
|
||||
|
||||
def use_cassette(self, path=None, **kwargs):
|
||||
@@ -114,68 +120,53 @@ class VCR(object):
|
||||
return Cassette.use_arg_getter(args_getter)
|
||||
|
||||
def get_merged_config(self, **kwargs):
|
||||
serializer_name = kwargs.get('serializer', self.serializer)
|
||||
matcher_names = kwargs.get('match_on', self.match_on)
|
||||
path_transformer = kwargs.get(
|
||||
'path_transformer',
|
||||
self.path_transformer
|
||||
)
|
||||
func_path_generator = kwargs.get(
|
||||
'func_path_generator',
|
||||
self.func_path_generator
|
||||
)
|
||||
cassette_library_dir = kwargs.get(
|
||||
'cassette_library_dir',
|
||||
self.cassette_library_dir
|
||||
)
|
||||
additional_matchers = kwargs.get('additional_matchers', ())
|
||||
serializer_name = kwargs.get("serializer", self.serializer)
|
||||
matcher_names = kwargs.get("match_on", self.match_on)
|
||||
path_transformer = kwargs.get("path_transformer", self.path_transformer)
|
||||
func_path_generator = kwargs.get("func_path_generator", self.func_path_generator)
|
||||
cassette_library_dir = kwargs.get("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.
|
||||
func_path_generator = self._build_path_from_func_using_module
|
||||
|
||||
merged_config = {
|
||||
'serializer': self._get_serializer(serializer_name),
|
||||
'persister': self.persister,
|
||||
'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),
|
||||
'custom_patches': self._custom_patches + kwargs.get(
|
||||
'custom_patches', ()
|
||||
),
|
||||
'inject': kwargs.get('inject_cassette', self.inject_cassette),
|
||||
'path_transformer': path_transformer,
|
||||
'func_path_generator': func_path_generator
|
||||
"serializer": self._get_serializer(serializer_name),
|
||||
"persister": self.persister,
|
||||
"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),
|
||||
"custom_patches": self._custom_patches + kwargs.get("custom_patches", ()),
|
||||
"inject": kwargs.get("inject_cassette", self.inject_cassette),
|
||||
"path_transformer": path_transformer,
|
||||
"func_path_generator": func_path_generator,
|
||||
}
|
||||
path = kwargs.get('path')
|
||||
path = kwargs.get("path")
|
||||
if path:
|
||||
merged_config['path'] = path
|
||||
merged_config["path"] = path
|
||||
return merged_config
|
||||
|
||||
def _build_before_record_response(self, options):
|
||||
before_record_response = options.get(
|
||||
'before_record_response', self.before_record_response
|
||||
)
|
||||
before_record_response = options.get("before_record_response", self.before_record_response)
|
||||
decode_compressed_response = options.get(
|
||||
'decode_compressed_response', self.decode_compressed_response
|
||||
"decode_compressed_response", self.decode_compressed_response
|
||||
)
|
||||
filter_functions = []
|
||||
if decode_compressed_response:
|
||||
filter_functions.append(filters.decode_response)
|
||||
if before_record_response:
|
||||
if not isinstance(before_record_response, collections.Iterable):
|
||||
if not isinstance(before_record_response, collections_abc.Iterable):
|
||||
before_record_response = (before_record_response,)
|
||||
filter_functions.extend(before_record_response)
|
||||
|
||||
@@ -185,63 +176,43 @@ class VCR(object):
|
||||
break
|
||||
response = function(response)
|
||||
return response
|
||||
|
||||
return before_record_response
|
||||
|
||||
def _build_before_record_request(self, options):
|
||||
filter_functions = []
|
||||
filter_headers = options.get(
|
||||
'filter_headers', self.filter_headers
|
||||
)
|
||||
filter_query_parameters = options.get(
|
||||
'filter_query_parameters', self.filter_query_parameters
|
||||
)
|
||||
filter_headers = options.get("filter_headers", self.filter_headers)
|
||||
filter_query_parameters = options.get("filter_query_parameters", self.filter_query_parameters)
|
||||
filter_post_data_parameters = options.get(
|
||||
'filter_post_data_parameters', self.filter_post_data_parameters
|
||||
"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)
|
||||
)
|
||||
ignore_hosts = options.get(
|
||||
'ignore_hosts', self.ignore_hosts
|
||||
)
|
||||
ignore_localhost = options.get(
|
||||
'ignore_localhost', self.ignore_localhost
|
||||
"before_record_request", options.get("before_record", self.before_record_request)
|
||||
)
|
||||
ignore_hosts = options.get("ignore_hosts", self.ignore_hosts)
|
||||
ignore_localhost = options.get("ignore_localhost", self.ignore_localhost)
|
||||
if filter_headers:
|
||||
replacements = [h if isinstance(h, tuple) else (h, None)
|
||||
for h in filter_headers]
|
||||
filter_functions.append(
|
||||
functools.partial(
|
||||
filters.replace_headers,
|
||||
replacements=replacements,
|
||||
)
|
||||
)
|
||||
replacements = [h if isinstance(h, tuple) else (h, None) for h in filter_headers]
|
||||
filter_functions.append(functools.partial(filters.replace_headers, replacements=replacements))
|
||||
if filter_query_parameters:
|
||||
replacements = [p if isinstance(p, tuple) else (p, None)
|
||||
for p in filter_query_parameters]
|
||||
filter_functions.append(functools.partial(
|
||||
filters.replace_query_parameters,
|
||||
replacements=replacements,
|
||||
))
|
||||
if filter_post_data_parameters:
|
||||
replacements = [p if isinstance(p, tuple) else (p, None)
|
||||
for p in filter_post_data_parameters]
|
||||
replacements = [p if isinstance(p, tuple) else (p, None) for p in filter_query_parameters]
|
||||
filter_functions.append(
|
||||
functools.partial(
|
||||
filters.replace_post_data_parameters,
|
||||
replacements=replacements,
|
||||
)
|
||||
functools.partial(filters.replace_query_parameters, replacements=replacements)
|
||||
)
|
||||
if filter_post_data_parameters:
|
||||
replacements = [p if isinstance(p, tuple) else (p, None) for p in filter_post_data_parameters]
|
||||
filter_functions.append(
|
||||
functools.partial(filters.replace_post_data_parameters, replacements=replacements)
|
||||
)
|
||||
|
||||
hosts_to_ignore = set(ignore_hosts)
|
||||
if ignore_localhost:
|
||||
hosts_to_ignore.update(('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:
|
||||
filter_functions.append(self._build_ignore_hosts(hosts_to_ignore))
|
||||
|
||||
if before_record_request:
|
||||
if not isinstance(before_record_request, collections.Iterable):
|
||||
if not isinstance(before_record_request, collections_abc.Iterable):
|
||||
before_record_request = (before_record_request,)
|
||||
filter_functions.extend(before_record_request)
|
||||
|
||||
@@ -252,20 +223,21 @@ class VCR(object):
|
||||
break
|
||||
request = function(request)
|
||||
return request
|
||||
|
||||
return before_record_request
|
||||
|
||||
@staticmethod
|
||||
def _build_ignore_hosts(hosts_to_ignore):
|
||||
def filter_ignored_hosts(request):
|
||||
if hasattr(request, 'host') and request.host in hosts_to_ignore:
|
||||
if hasattr(request, "host") and request.host in hosts_to_ignore:
|
||||
return
|
||||
return request
|
||||
|
||||
return filter_ignored_hosts
|
||||
|
||||
@staticmethod
|
||||
def _build_path_from_func_using_module(function):
|
||||
return os.path.join(os.path.dirname(inspect.getfile(function)),
|
||||
function.__name__)
|
||||
return os.path.join(os.path.dirname(inspect.getfile(function)), function.__name__)
|
||||
|
||||
def register_serializer(self, name, serializer):
|
||||
self.serializers[name] = serializer
|
||||
|
||||
@@ -1,7 +1,42 @@
|
||||
class CannotOverwriteExistingCassetteException(Exception):
|
||||
pass
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.cassette = kwargs["cassette"]
|
||||
self.failed_request = kwargs["failed_request"]
|
||||
message = self._get_message(kwargs["cassette"], kwargs["failed_request"])
|
||||
super(CannotOverwriteExistingCassetteException, self).__init__(message)
|
||||
|
||||
@staticmethod
|
||||
def _get_message(cassette, failed_request):
|
||||
"""Get the final message related to the exception"""
|
||||
# Get the similar requests in the cassette that
|
||||
# have match the most with the request.
|
||||
best_matches = cassette.find_requests_with_most_matches(failed_request)
|
||||
if best_matches:
|
||||
# Build a comprehensible message to put in the exception.
|
||||
best_matches_msg = "Found {} similar requests with {} different matcher(s) :\n".format(
|
||||
len(best_matches), len(best_matches[0][2])
|
||||
)
|
||||
|
||||
for idx, best_match in enumerate(best_matches, start=1):
|
||||
request, succeeded_matchers, failed_matchers_assertion_msgs = best_match
|
||||
best_matches_msg += (
|
||||
"\n%s - (%r).\n"
|
||||
"Matchers succeeded : %s\n"
|
||||
"Matchers failed :\n" % (idx, request, succeeded_matchers)
|
||||
)
|
||||
for failed_matcher, assertion_msg in failed_matchers_assertion_msgs:
|
||||
best_matches_msg += "%s - assertion failure :\n" "%s\n" % (failed_matcher, assertion_msg)
|
||||
else:
|
||||
best_matches_msg = "No similar requests, that have not been played, found."
|
||||
return (
|
||||
"Can't overwrite existing cassette (%r) in "
|
||||
"your current record mode (%r).\n"
|
||||
"No match for the request (%r) was found.\n"
|
||||
"%s" % (cassette._path, cassette.record_mode, failed_request, best_matches_msg)
|
||||
)
|
||||
|
||||
|
||||
class UnhandledHTTPRequestError(KeyError):
|
||||
"""Raised when a cassette does not contain the request we want."""
|
||||
|
||||
pass
|
||||
|
||||
@@ -82,10 +82,14 @@ def replace_post_data_parameters(request, replacements):
|
||||
3. A callable which accepts (key, value, request) and returns a string
|
||||
value or None.
|
||||
"""
|
||||
if not request.body:
|
||||
# Nothing to replace
|
||||
return request
|
||||
|
||||
replacements = dict(replacements)
|
||||
if request.method == 'POST' and not isinstance(request.body, BytesIO):
|
||||
if request.headers.get('Content-Type') == 'application/json':
|
||||
json_data = json.loads(request.body.decode('utf-8'))
|
||||
if request.method == "POST" and not isinstance(request.body, BytesIO):
|
||||
if request.headers.get("Content-Type") == "application/json":
|
||||
json_data = json.loads(request.body.decode("utf-8"))
|
||||
for k, rv in replacements.items():
|
||||
if k in json_data:
|
||||
ov = json_data.pop(k)
|
||||
@@ -93,28 +97,26 @@ def replace_post_data_parameters(request, replacements):
|
||||
rv = rv(key=k, value=ov, request=request)
|
||||
if rv is not None:
|
||||
json_data[k] = rv
|
||||
request.body = json.dumps(json_data).encode('utf-8')
|
||||
request.body = json.dumps(json_data).encode("utf-8")
|
||||
else:
|
||||
if isinstance(request.body, text_type):
|
||||
request.body = request.body.encode('utf-8')
|
||||
splits = [p.partition(b'=') for p in request.body.split(b'&')]
|
||||
request.body = request.body.encode("utf-8")
|
||||
splits = [p.partition(b"=") for p in request.body.split(b"&")]
|
||||
new_splits = []
|
||||
for k, sep, ov in splits:
|
||||
if sep is None:
|
||||
new_splits.append((k, sep, ov))
|
||||
else:
|
||||
rk = k.decode('utf-8')
|
||||
rk = k.decode("utf-8")
|
||||
if rk not in replacements:
|
||||
new_splits.append((k, sep, ov))
|
||||
else:
|
||||
rv = replacements[rk]
|
||||
if callable(rv):
|
||||
rv = rv(key=rk, value=ov.decode('utf-8'),
|
||||
request=request)
|
||||
rv = rv(key=rk, value=ov.decode("utf-8"), request=request)
|
||||
if rv is not None:
|
||||
new_splits.append((k, sep, rv.encode('utf-8')))
|
||||
request.body = b'&'.join(k if sep is None else b''.join([k, sep, v])
|
||||
for k, sep, v in new_splits)
|
||||
new_splits.append((k, sep, rv.encode("utf-8")))
|
||||
request.body = b"&".join(k if sep is None else b"".join([k, sep, v]) for k, sep, v in new_splits)
|
||||
return request
|
||||
|
||||
|
||||
@@ -133,15 +135,16 @@ def decode_response(response):
|
||||
2. delete the content-encoding header
|
||||
3. update content-length header to decompressed length
|
||||
"""
|
||||
|
||||
def is_compressed(headers):
|
||||
encoding = headers.get('content-encoding', [])
|
||||
return encoding and encoding[0] in ('gzip', 'deflate')
|
||||
encoding = headers.get("content-encoding", [])
|
||||
return encoding and encoding[0] in ("gzip", "deflate")
|
||||
|
||||
def decompress_body(body, encoding):
|
||||
"""Returns decompressed body according to encoding using zlib.
|
||||
to (de-)compress gzip format, use wbits = zlib.MAX_WBITS | 16
|
||||
"""
|
||||
if encoding == 'gzip':
|
||||
if encoding == "gzip":
|
||||
return zlib.decompress(body, zlib.MAX_WBITS | 16)
|
||||
else: # encoding == 'deflate'
|
||||
return zlib.decompress(body)
|
||||
@@ -149,15 +152,15 @@ def decode_response(response):
|
||||
# Deepcopy here in case `headers` contain objects that could
|
||||
# be mutated by a shallow copy and corrupt the real response.
|
||||
response = copy.deepcopy(response)
|
||||
headers = CaseInsensitiveDict(response['headers'])
|
||||
headers = CaseInsensitiveDict(response["headers"])
|
||||
if is_compressed(headers):
|
||||
encoding = headers['content-encoding'][0]
|
||||
headers['content-encoding'].remove(encoding)
|
||||
if not headers['content-encoding']:
|
||||
del headers['content-encoding']
|
||||
encoding = headers["content-encoding"][0]
|
||||
headers["content-encoding"].remove(encoding)
|
||||
if not headers["content-encoding"]:
|
||||
del headers["content-encoding"]
|
||||
|
||||
new_body = decompress_body(response['body']['string'], encoding)
|
||||
response['body']['string'] = new_body
|
||||
headers['content-length'] = [str(len(new_body))]
|
||||
response['headers'] = dict(headers)
|
||||
new_body = decompress_body(response["body"]["string"], encoding)
|
||||
response["body"]["string"] = new_body
|
||||
headers["content-length"] = [str(len(new_body))]
|
||||
response["headers"] = dict(headers)
|
||||
return response
|
||||
|
||||
119
vcr/matchers.py
119
vcr/matchers.py
@@ -8,40 +8,56 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def method(r1, r2):
|
||||
return r1.method == r2.method
|
||||
assert r1.method == r2.method, "{} != {}".format(r1.method, r2.method)
|
||||
|
||||
|
||||
def uri(r1, r2):
|
||||
return r1.uri == r2.uri
|
||||
assert r1.uri == r2.uri, "{} != {}".format(r1.uri, r2.uri)
|
||||
|
||||
|
||||
def host(r1, r2):
|
||||
return r1.host == r2.host
|
||||
assert r1.host == r2.host, "{} != {}".format(r1.host, r2.host)
|
||||
|
||||
|
||||
def scheme(r1, r2):
|
||||
return r1.scheme == r2.scheme
|
||||
assert r1.scheme == r2.scheme, "{} != {}".format(r1.scheme, r2.scheme)
|
||||
|
||||
|
||||
def port(r1, r2):
|
||||
return r1.port == r2.port
|
||||
assert r1.port == r2.port, "{} != {}".format(r1.port, r2.port)
|
||||
|
||||
|
||||
def path(r1, r2):
|
||||
return r1.path == r2.path
|
||||
assert r1.path == r2.path, "{} != {}".format(r1.path, r2.path)
|
||||
|
||||
|
||||
def query(r1, r2):
|
||||
return r1.query == r2.query
|
||||
assert r1.query == r2.query, "{} != {}".format(r1.query, r2.query)
|
||||
|
||||
|
||||
def raw_body(r1, r2):
|
||||
return read_body(r1) == read_body(r2)
|
||||
assert read_body(r1) == read_body(r2)
|
||||
|
||||
|
||||
def _header_checker(value, header='Content-Type'):
|
||||
def body(r1, r2):
|
||||
transformer = _get_transformer(r1)
|
||||
r2_transformer = _get_transformer(r2)
|
||||
if transformer != r2_transformer:
|
||||
transformer = _identity
|
||||
assert transformer(read_body(r1)) == transformer(read_body(r2))
|
||||
|
||||
|
||||
def headers(r1, r2):
|
||||
assert r1.headers == r2.headers, "{} != {}".format(r1.headers, r2.headers)
|
||||
|
||||
|
||||
def _header_checker(value, header="Content-Type"):
|
||||
def checker(headers):
|
||||
return value in headers.get(header, '').lower()
|
||||
_header = headers.get(header, "")
|
||||
if isinstance(_header, bytes):
|
||||
_header = _header.decode("utf-8")
|
||||
return value in _header.lower()
|
||||
|
||||
return checker
|
||||
|
||||
|
||||
@@ -50,14 +66,17 @@ def _transform_json(body):
|
||||
# string. RFC 7159 says the default encoding is UTF-8 (although UTF-16
|
||||
# and UTF-32 are also allowed: hmmmmm).
|
||||
if body:
|
||||
return json.loads(body.decode('utf-8'))
|
||||
return json.loads(body.decode("utf-8"))
|
||||
|
||||
|
||||
_xml_header_checker = _header_checker('text/xml')
|
||||
_xmlrpc_header_checker = _header_checker('xmlrpc', header='User-Agent')
|
||||
_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'), _transform_json),
|
||||
(
|
||||
_header_checker("application/x-www-form-urlencoded"),
|
||||
lambda body: urllib.parse.parse_qs(body.decode("ascii")),
|
||||
),
|
||||
(_header_checker("application/json"), _transform_json),
|
||||
(lambda request: _xml_header_checker(request) and _xmlrpc_header_checker(request), xmlrpc_client.loads),
|
||||
)
|
||||
|
||||
@@ -74,28 +93,50 @@ def _get_transformer(request):
|
||||
return _identity
|
||||
|
||||
|
||||
def body(r1, r2):
|
||||
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):
|
||||
return r1.headers == r2.headers
|
||||
|
||||
|
||||
def _log_matches(r1, r2, matches):
|
||||
differences = [m for m in matches if not m[0]]
|
||||
if differences:
|
||||
log.debug(
|
||||
"Requests {} and {} differ according to "
|
||||
"the following matchers: {}".format(r1, r2, differences)
|
||||
)
|
||||
|
||||
|
||||
def requests_match(r1, r2, matchers):
|
||||
matches = [(m(r1, r2), m) for m in matchers]
|
||||
_log_matches(r1, r2, matches)
|
||||
return all(m[0] for m in matches)
|
||||
successes, failures = get_matchers_results(r1, r2, matchers)
|
||||
if failures:
|
||||
log.debug("Requests {} and {} differ.\n" "Failure details:\n" "{}".format(r1, r2, failures))
|
||||
return len(failures) == 0
|
||||
|
||||
|
||||
def _evaluate_matcher(matcher_function, *args):
|
||||
"""
|
||||
Evaluate the result of a given matcher as a boolean with an assertion error message if any.
|
||||
It handles two types of matcher :
|
||||
- a matcher returning a boolean value.
|
||||
- a matcher that only makes an assert, returning None or raises an assertion error.
|
||||
"""
|
||||
assertion_message = None
|
||||
try:
|
||||
match = matcher_function(*args)
|
||||
match = True if match is None else match
|
||||
except AssertionError as e:
|
||||
match = False
|
||||
assertion_message = str(e)
|
||||
return match, assertion_message
|
||||
|
||||
|
||||
def get_matchers_results(r1, r2, matchers):
|
||||
"""
|
||||
Get the comparison results of two requests as two list.
|
||||
The first returned list represents the matchers names that passed.
|
||||
The second list is the failed matchers as a string with failed assertion details if any.
|
||||
"""
|
||||
matches_success, matches_fails = [], []
|
||||
for m in matchers:
|
||||
matcher_name = m.__name__
|
||||
match, assertion_message = _evaluate_matcher(m, r1, r2)
|
||||
if match:
|
||||
matches_success.append(matcher_name)
|
||||
else:
|
||||
assertion_message = get_assertion_message(assertion_message)
|
||||
matches_fails.append((matcher_name, assertion_message))
|
||||
return matches_success, matches_fails
|
||||
|
||||
|
||||
def get_assertion_message(assertion_details):
|
||||
"""
|
||||
Get a detailed message about the failing matcher.
|
||||
"""
|
||||
return assertion_details
|
||||
|
||||
@@ -38,55 +38,46 @@ def preprocess_yaml(cassette):
|
||||
# versions. So this just strips the tags before deserializing.
|
||||
|
||||
STRINGS_TO_NUKE = [
|
||||
'!!python/object:vcr.request.Request',
|
||||
'!!python/object/apply:__builtin__.frozenset',
|
||||
'!!python/object/apply:builtins.frozenset',
|
||||
"!!python/object:vcr.request.Request",
|
||||
"!!python/object/apply:__builtin__.frozenset",
|
||||
"!!python/object/apply:builtins.frozenset",
|
||||
]
|
||||
for s in STRINGS_TO_NUKE:
|
||||
cassette = cassette.replace(s, '')
|
||||
cassette = cassette.replace(s, "")
|
||||
return cassette
|
||||
|
||||
|
||||
PARTS = [
|
||||
'protocol',
|
||||
'host',
|
||||
'port',
|
||||
'path',
|
||||
]
|
||||
PARTS = ["protocol", "host", "port", "path"]
|
||||
|
||||
|
||||
def build_uri(**parts):
|
||||
port = parts['port']
|
||||
scheme = parts['protocol']
|
||||
default_port = {'https': 443, 'http': 80}[scheme]
|
||||
parts['port'] = ':{}'.format(port) if port != default_port else ''
|
||||
port = parts["port"]
|
||||
scheme = parts["protocol"]
|
||||
default_port = {"https": 443, "http": 80}[scheme]
|
||||
parts["port"] = ":{}".format(port) if port != default_port else ""
|
||||
return "{protocol}://{host}{port}{path}".format(**parts)
|
||||
|
||||
|
||||
def _migrate(data):
|
||||
interactions = []
|
||||
for item in data:
|
||||
req = item['request']
|
||||
res = item['response']
|
||||
uri = dict((k, req.pop(k)) for k in PARTS)
|
||||
req['uri'] = build_uri(**uri)
|
||||
req = item["request"]
|
||||
res = item["response"]
|
||||
uri = {k: req.pop(k) for k in PARTS}
|
||||
req["uri"] = build_uri(**uri)
|
||||
# convert headers to dict of lists
|
||||
headers = req['headers']
|
||||
headers = req["headers"]
|
||||
for k in headers:
|
||||
headers[k] = [headers[k]]
|
||||
response_headers = {}
|
||||
for k, v in get_httpmessage(
|
||||
b"".join(h.encode('utf-8') for h in res['headers'])
|
||||
).items():
|
||||
for k, v in get_httpmessage(b"".join(h.encode("utf-8") for h in res["headers"])).items():
|
||||
response_headers.setdefault(k, [])
|
||||
response_headers[k].append(v)
|
||||
res['headers'] = response_headers
|
||||
interactions.append({'request': req, 'response': res})
|
||||
res["headers"] = response_headers
|
||||
interactions.append({"request": req, "response": res})
|
||||
return {
|
||||
'requests': [
|
||||
request.Request._from_dict(i['request']) for i in interactions
|
||||
],
|
||||
'responses': [i['response'] for i in interactions],
|
||||
"requests": [request.Request._from_dict(i["request"]) for i in interactions],
|
||||
"responses": [i["response"] for i in interactions],
|
||||
}
|
||||
|
||||
|
||||
@@ -100,12 +91,12 @@ def migrate_json(in_fp, out_fp):
|
||||
|
||||
|
||||
def _list_of_tuples_to_dict(fs):
|
||||
return dict((k, v) for k, v in fs[0])
|
||||
return {k: v for k, v in fs[0]}
|
||||
|
||||
|
||||
def _already_migrated(data):
|
||||
try:
|
||||
if data.get('version') == 1:
|
||||
if data.get("version") == 1:
|
||||
return True
|
||||
except AttributeError:
|
||||
return False
|
||||
@@ -116,9 +107,7 @@ def migrate_yml(in_fp, out_fp):
|
||||
if _already_migrated(data):
|
||||
return False
|
||||
for i in range(len(data)):
|
||||
data[i]['request']['headers'] = _list_of_tuples_to_dict(
|
||||
data[i]['request']['headers']
|
||||
)
|
||||
data[i]["request"]["headers"] = _list_of_tuples_to_dict(data[i]["request"]["headers"])
|
||||
interactions = _migrate(data)
|
||||
out_fp.write(serialize(interactions, yamlserializer))
|
||||
return True
|
||||
@@ -127,43 +116,42 @@ def migrate_yml(in_fp, out_fp):
|
||||
def migrate(file_path, migration_fn):
|
||||
# because we assume that original files can be reverted
|
||||
# we will try to copy the content. (os.rename not needed)
|
||||
with tempfile.TemporaryFile(mode='w+') as out_fp:
|
||||
with open(file_path, 'r') as in_fp:
|
||||
with tempfile.TemporaryFile(mode="w+") as out_fp:
|
||||
with open(file_path, "r") as in_fp:
|
||||
if not migration_fn(in_fp, out_fp):
|
||||
return False
|
||||
with open(file_path, 'w') as in_fp:
|
||||
with open(file_path, "w") as in_fp:
|
||||
out_fp.seek(0)
|
||||
shutil.copyfileobj(out_fp, in_fp)
|
||||
return True
|
||||
|
||||
|
||||
def try_migrate(path):
|
||||
if path.endswith('.json'):
|
||||
if path.endswith(".json"):
|
||||
return migrate(path, migrate_json)
|
||||
elif path.endswith('.yaml') or path.endswith('.yml'):
|
||||
elif path.endswith(".yaml") or path.endswith(".yml"):
|
||||
return migrate(path, migrate_yml)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("Please provide path to cassettes directory or file. "
|
||||
"Usage: python -m vcr.migration PATH")
|
||||
raise SystemExit(
|
||||
"Please provide path to cassettes directory or file. " "Usage: python -m vcr.migration PATH"
|
||||
)
|
||||
|
||||
path = sys.argv[1]
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.abspath(path)
|
||||
files = [path]
|
||||
if os.path.isdir(path):
|
||||
files = (os.path.join(root, name)
|
||||
for (root, dirs, files) in os.walk(path)
|
||||
for name in files)
|
||||
files = (os.path.join(root, name) for (root, dirs, files) in os.walk(path) for name in files)
|
||||
for file_path in files:
|
||||
migrated = try_migrate(file_path)
|
||||
status = 'OK' if migrated else 'FAIL'
|
||||
sys.stderr.write("[{}] {}\n".format(status, file_path))
|
||||
migrated = try_migrate(file_path)
|
||||
status = "OK" if migrated else "FAIL"
|
||||
sys.stderr.write("[{}] {}\n".format(status, file_path))
|
||||
sys.stderr.write("Done.\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
284
vcr/patch.py
284
vcr/patch.py
@@ -1,4 +1,4 @@
|
||||
'''Utilities for patching in cassettes'''
|
||||
"""Utilities for patching in cassettes"""
|
||||
import functools
|
||||
import itertools
|
||||
|
||||
@@ -6,21 +6,29 @@ from .compat import contextlib, mock
|
||||
from .stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||
from six.moves import http_client as httplib
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
# Save some of the original types for the purposes of unpatching
|
||||
_HTTPConnection = httplib.HTTPConnection
|
||||
_HTTPSConnection = httplib.HTTPSConnection
|
||||
|
||||
|
||||
# Try to save the original types for boto3
|
||||
try:
|
||||
import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
from botocore.awsrequest import AWSHTTPSConnection, AWSHTTPConnection
|
||||
except ImportError:
|
||||
try:
|
||||
import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
_Boto3VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
|
||||
_cpoolBoto3HTTPConnection = cpool.HTTPConnection
|
||||
_cpoolBoto3HTTPSConnection = cpool.HTTPSConnection
|
||||
else:
|
||||
_Boto3VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
|
||||
_cpoolBoto3HTTPConnection = cpool.HTTPConnection
|
||||
_cpoolBoto3HTTPSConnection = cpool.HTTPSConnection
|
||||
_Boto3VerifiedHTTPSConnection = AWSHTTPSConnection
|
||||
_cpoolBoto3HTTPConnection = AWSHTTPConnection
|
||||
_cpoolBoto3HTTPSConnection = AWSHTTPSConnection
|
||||
|
||||
cpool = None
|
||||
# Try to save the original types for urllib3
|
||||
@@ -44,7 +52,6 @@ else:
|
||||
_cpoolHTTPConnection = cpool.HTTPConnection
|
||||
_cpoolHTTPSConnection = cpool.HTTPSConnection
|
||||
|
||||
|
||||
# Try to save the original types for httplib2
|
||||
try:
|
||||
import httplib2
|
||||
@@ -55,7 +62,6 @@ else:
|
||||
_HTTPSConnectionWithTimeout = httplib2.HTTPSConnectionWithTimeout
|
||||
_SCHEME_TO_CONNECTION = httplib2.SCHEME_TO_CONNECTION
|
||||
|
||||
|
||||
# Try to save the original types for boto
|
||||
try:
|
||||
import boto.https_connection
|
||||
@@ -64,24 +70,20 @@ except ImportError: # pragma: no cover
|
||||
else:
|
||||
_CertValidatingHTTPSConnection = boto.https_connection.CertValidatingHTTPSConnection
|
||||
|
||||
|
||||
# Try to save the original types for Tornado
|
||||
try:
|
||||
import tornado.simple_httpclient
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
_SimpleAsyncHTTPClient_fetch_impl = \
|
||||
tornado.simple_httpclient.SimpleAsyncHTTPClient.fetch_impl
|
||||
|
||||
_SimpleAsyncHTTPClient_fetch_impl = tornado.simple_httpclient.SimpleAsyncHTTPClient.fetch_impl
|
||||
|
||||
try:
|
||||
import tornado.curl_httpclient
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
_CurlAsyncHTTPClient_fetch_impl = \
|
||||
tornado.curl_httpclient.CurlAsyncHTTPClient.fetch_impl
|
||||
_CurlAsyncHTTPClient_fetch_impl = tornado.curl_httpclient.CurlAsyncHTTPClient.fetch_impl
|
||||
|
||||
try:
|
||||
import aiohttp.client
|
||||
@@ -92,13 +94,11 @@ else:
|
||||
|
||||
|
||||
class CassettePatcherBuilder(object):
|
||||
|
||||
def _build_patchers_from_mock_triples_decorator(function):
|
||||
@functools.wraps(function)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
return self._build_patchers_from_mock_triples(
|
||||
function(self, *args, **kwargs)
|
||||
)
|
||||
return self._build_patchers_from_mock_triples(function(self, *args, **kwargs))
|
||||
|
||||
return wrapped
|
||||
|
||||
def __init__(self, cassette):
|
||||
@@ -107,11 +107,15 @@ class CassettePatcherBuilder(object):
|
||||
|
||||
def build(self):
|
||||
return itertools.chain(
|
||||
self._httplib(), self._requests(), self._boto3(), self._urllib3(),
|
||||
self._httplib2(), self._boto(), self._tornado(), self._aiohttp(),
|
||||
self._build_patchers_from_mock_triples(
|
||||
self._cassette.custom_patches
|
||||
),
|
||||
self._httplib(),
|
||||
self._requests(),
|
||||
self._boto3(),
|
||||
self._urllib3(),
|
||||
self._httplib2(),
|
||||
self._boto(),
|
||||
self._tornado(),
|
||||
self._aiohttp(),
|
||||
self._build_patchers_from_mock_triples(self._cassette.custom_patches),
|
||||
)
|
||||
|
||||
def _build_patchers_from_mock_triples(self, mock_triples):
|
||||
@@ -124,9 +128,9 @@ class CassettePatcherBuilder(object):
|
||||
if not hasattr(obj, patched_attribute):
|
||||
return
|
||||
|
||||
return mock.patch.object(obj, patched_attribute,
|
||||
self._recursively_apply_get_cassette_subclass(
|
||||
replacement_class))
|
||||
return mock.patch.object(
|
||||
obj, patched_attribute, self._recursively_apply_get_cassette_subclass(replacement_class)
|
||||
)
|
||||
|
||||
def _recursively_apply_get_cassette_subclass(self, replacement_dict_or_obj):
|
||||
"""One of the subtleties of this class is that it does not directly
|
||||
@@ -148,13 +152,11 @@ class CassettePatcherBuilder(object):
|
||||
"""
|
||||
if isinstance(replacement_dict_or_obj, dict):
|
||||
for key, replacement_obj in replacement_dict_or_obj.items():
|
||||
replacement_obj = self._recursively_apply_get_cassette_subclass(
|
||||
replacement_obj)
|
||||
replacement_obj = self._recursively_apply_get_cassette_subclass(replacement_obj)
|
||||
replacement_dict_or_obj[key] = replacement_obj
|
||||
return replacement_dict_or_obj
|
||||
if hasattr(replacement_dict_or_obj, 'cassette'):
|
||||
replacement_dict_or_obj = self._get_cassette_subclass(
|
||||
replacement_dict_or_obj)
|
||||
if hasattr(replacement_dict_or_obj, "cassette"):
|
||||
replacement_dict_or_obj = self._get_cassette_subclass(replacement_dict_or_obj)
|
||||
return replacement_dict_or_obj
|
||||
|
||||
def _get_cassette_subclass(self, klass):
|
||||
@@ -169,13 +171,14 @@ class CassettePatcherBuilder(object):
|
||||
bases = (base_class,)
|
||||
if not issubclass(base_class, object): # Check for old style class
|
||||
bases += (object,)
|
||||
return type('{}{}'.format(base_class.__name__, self._cassette._path),
|
||||
bases, dict(cassette=self._cassette))
|
||||
return type(
|
||||
"{}{}".format(base_class.__name__, self._cassette._path), bases, dict(cassette=self._cassette)
|
||||
)
|
||||
|
||||
@_build_patchers_from_mock_triples_decorator
|
||||
def _httplib(self):
|
||||
yield httplib, 'HTTPConnection', VCRHTTPConnection
|
||||
yield httplib, 'HTTPSConnection', VCRHTTPSConnection
|
||||
yield httplib, "HTTPConnection", VCRHTTPConnection
|
||||
yield httplib, "HTTPSConnection", VCRHTTPSConnection
|
||||
|
||||
def _requests(self):
|
||||
try:
|
||||
@@ -184,13 +187,28 @@ class CassettePatcherBuilder(object):
|
||||
return ()
|
||||
return self._urllib3_patchers(cpool, requests_stubs)
|
||||
|
||||
@_build_patchers_from_mock_triples_decorator
|
||||
def _boto3(self):
|
||||
|
||||
try:
|
||||
import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
|
||||
# botocore using awsrequest
|
||||
import botocore.awsrequest as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
return ()
|
||||
from .stubs import boto3_stubs
|
||||
return self._urllib3_patchers(cpool, boto3_stubs)
|
||||
try:
|
||||
# botocore using vendored requests
|
||||
import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
from .stubs import boto3_stubs
|
||||
|
||||
yield self._urllib3_patchers(cpool, boto3_stubs)
|
||||
else:
|
||||
from .stubs import boto3_stubs
|
||||
|
||||
log.debug("Patching boto3 cpool with %s", cpool)
|
||||
yield cpool.AWSHTTPConnectionPool, "ConnectionCls", boto3_stubs.VCRRequestsHTTPConnection
|
||||
yield cpool.AWSHTTPSConnectionPool, "ConnectionCls", boto3_stubs.VCRRequestsHTTPSConnection
|
||||
|
||||
def _patched_get_conn(self, connection_pool_class, connection_class_getter):
|
||||
get_conn = connection_pool_class._get_conn
|
||||
@@ -199,8 +217,8 @@ class CassettePatcherBuilder(object):
|
||||
def patched_get_conn(pool, timeout=None):
|
||||
connection = get_conn(pool, timeout)
|
||||
connection_class = (
|
||||
pool.ConnectionCls if hasattr(pool, 'ConnectionCls')
|
||||
else connection_class_getter())
|
||||
pool.ConnectionCls if hasattr(pool, "ConnectionCls") else connection_class_getter()
|
||||
)
|
||||
# We need to make sure that we are actually providing a
|
||||
# patched version of the connection class. This might not
|
||||
# always be the case because the pool keeps previously
|
||||
@@ -230,6 +248,7 @@ class CassettePatcherBuilder(object):
|
||||
except ImportError: # pragma: no cover
|
||||
return ()
|
||||
from .stubs import urllib3_stubs
|
||||
|
||||
return self._urllib3_patchers(cpool, urllib3_stubs)
|
||||
|
||||
@_build_patchers_from_mock_triples_decorator
|
||||
@@ -242,10 +261,12 @@ class CassettePatcherBuilder(object):
|
||||
from .stubs.httplib2_stubs import VCRHTTPConnectionWithTimeout
|
||||
from .stubs.httplib2_stubs import VCRHTTPSConnectionWithTimeout
|
||||
|
||||
yield cpool, 'HTTPConnectionWithTimeout', VCRHTTPConnectionWithTimeout
|
||||
yield cpool, 'HTTPSConnectionWithTimeout', VCRHTTPSConnectionWithTimeout
|
||||
yield cpool, 'SCHEME_TO_CONNECTION', {'http': VCRHTTPConnectionWithTimeout,
|
||||
'https': VCRHTTPSConnectionWithTimeout}
|
||||
yield cpool, "HTTPConnectionWithTimeout", VCRHTTPConnectionWithTimeout
|
||||
yield cpool, "HTTPSConnectionWithTimeout", VCRHTTPSConnectionWithTimeout
|
||||
yield cpool, "SCHEME_TO_CONNECTION", {
|
||||
"http": VCRHTTPConnectionWithTimeout,
|
||||
"https": VCRHTTPSConnectionWithTimeout,
|
||||
}
|
||||
|
||||
@_build_patchers_from_mock_triples_decorator
|
||||
def _boto(self):
|
||||
@@ -255,7 +276,8 @@ class CassettePatcherBuilder(object):
|
||||
pass
|
||||
else:
|
||||
from .stubs.boto_stubs import VCRCertValidatingHTTPSConnection
|
||||
yield cpool, 'CertValidatingHTTPSConnection', VCRCertValidatingHTTPSConnection
|
||||
|
||||
yield cpool, "CertValidatingHTTPSConnection", VCRCertValidatingHTTPSConnection
|
||||
|
||||
@_build_patchers_from_mock_triples_decorator
|
||||
def _tornado(self):
|
||||
@@ -266,10 +288,8 @@ class CassettePatcherBuilder(object):
|
||||
else:
|
||||
from .stubs.tornado_stubs import vcr_fetch_impl
|
||||
|
||||
new_fetch_impl = vcr_fetch_impl(
|
||||
self._cassette, _SimpleAsyncHTTPClient_fetch_impl
|
||||
)
|
||||
yield simple.SimpleAsyncHTTPClient, 'fetch_impl', new_fetch_impl
|
||||
new_fetch_impl = vcr_fetch_impl(self._cassette, _SimpleAsyncHTTPClient_fetch_impl)
|
||||
yield simple.SimpleAsyncHTTPClient, "fetch_impl", new_fetch_impl
|
||||
try:
|
||||
import tornado.curl_httpclient as curl
|
||||
except ImportError: # pragma: no cover
|
||||
@@ -277,10 +297,8 @@ class CassettePatcherBuilder(object):
|
||||
else:
|
||||
from .stubs.tornado_stubs import vcr_fetch_impl
|
||||
|
||||
new_fetch_impl = vcr_fetch_impl(
|
||||
self._cassette, _CurlAsyncHTTPClient_fetch_impl
|
||||
)
|
||||
yield curl.CurlAsyncHTTPClient, 'fetch_impl', new_fetch_impl
|
||||
new_fetch_impl = vcr_fetch_impl(self._cassette, _CurlAsyncHTTPClient_fetch_impl)
|
||||
yield curl.CurlAsyncHTTPClient, "fetch_impl", new_fetch_impl
|
||||
|
||||
@_build_patchers_from_mock_triples_decorator
|
||||
def _aiohttp(self):
|
||||
@@ -290,10 +308,9 @@ class CassettePatcherBuilder(object):
|
||||
pass
|
||||
else:
|
||||
from .stubs.aiohttp_stubs import vcr_request
|
||||
new_request = vcr_request(
|
||||
self._cassette, _AiohttpClientSessionRequest
|
||||
)
|
||||
yield client.ClientSession, '_request', new_request
|
||||
|
||||
new_request = vcr_request(self._cassette, _AiohttpClientSessionRequest)
|
||||
yield client.ClientSession, "_request", new_request
|
||||
|
||||
def _urllib3_patchers(self, cpool, stubs):
|
||||
http_connection_remover = ConnectionRemover(
|
||||
@@ -303,34 +320,45 @@ class CassettePatcherBuilder(object):
|
||||
self._get_cassette_subclass(stubs.VCRRequestsHTTPSConnection)
|
||||
)
|
||||
mock_triples = (
|
||||
(cpool, 'VerifiedHTTPSConnection', stubs.VCRRequestsHTTPSConnection),
|
||||
(cpool, 'HTTPConnection', stubs.VCRRequestsHTTPConnection),
|
||||
(cpool, 'HTTPSConnection', stubs.VCRRequestsHTTPSConnection),
|
||||
(cpool, 'is_connection_dropped', mock.Mock(return_value=False)), # Needed on Windows only
|
||||
(cpool.HTTPConnectionPool, 'ConnectionCls', stubs.VCRRequestsHTTPConnection),
|
||||
(cpool.HTTPSConnectionPool, 'ConnectionCls', stubs.VCRRequestsHTTPSConnection),
|
||||
(cpool, "VerifiedHTTPSConnection", stubs.VCRRequestsHTTPSConnection),
|
||||
(cpool, "HTTPConnection", stubs.VCRRequestsHTTPConnection),
|
||||
(cpool, "HTTPSConnection", stubs.VCRRequestsHTTPSConnection),
|
||||
(cpool, "is_connection_dropped", mock.Mock(return_value=False)), # Needed on Windows only
|
||||
(cpool.HTTPConnectionPool, "ConnectionCls", stubs.VCRRequestsHTTPConnection),
|
||||
(cpool.HTTPSConnectionPool, "ConnectionCls", stubs.VCRRequestsHTTPSConnection),
|
||||
)
|
||||
# These handle making sure that sessions only use the
|
||||
# connections of the appropriate type.
|
||||
mock_triples += ((cpool.HTTPConnectionPool, '_get_conn',
|
||||
self._patched_get_conn(cpool.HTTPConnectionPool,
|
||||
lambda: cpool.HTTPConnection)),
|
||||
(cpool.HTTPSConnectionPool, '_get_conn',
|
||||
self._patched_get_conn(cpool.HTTPSConnectionPool,
|
||||
lambda: cpool.HTTPSConnection)),
|
||||
(cpool.HTTPConnectionPool, '_new_conn',
|
||||
self._patched_new_conn(cpool.HTTPConnectionPool,
|
||||
http_connection_remover)),
|
||||
(cpool.HTTPSConnectionPool, '_new_conn',
|
||||
self._patched_new_conn(cpool.HTTPSConnectionPool,
|
||||
https_connection_remover)))
|
||||
mock_triples += (
|
||||
(
|
||||
cpool.HTTPConnectionPool,
|
||||
"_get_conn",
|
||||
self._patched_get_conn(cpool.HTTPConnectionPool, lambda: cpool.HTTPConnection),
|
||||
),
|
||||
(
|
||||
cpool.HTTPSConnectionPool,
|
||||
"_get_conn",
|
||||
self._patched_get_conn(cpool.HTTPSConnectionPool, lambda: cpool.HTTPSConnection),
|
||||
),
|
||||
(
|
||||
cpool.HTTPConnectionPool,
|
||||
"_new_conn",
|
||||
self._patched_new_conn(cpool.HTTPConnectionPool, http_connection_remover),
|
||||
),
|
||||
(
|
||||
cpool.HTTPSConnectionPool,
|
||||
"_new_conn",
|
||||
self._patched_new_conn(cpool.HTTPSConnectionPool, https_connection_remover),
|
||||
),
|
||||
)
|
||||
|
||||
return itertools.chain(self._build_patchers_from_mock_triples(mock_triples),
|
||||
(http_connection_remover, https_connection_remover))
|
||||
return itertools.chain(
|
||||
self._build_patchers_from_mock_triples(mock_triples),
|
||||
(http_connection_remover, https_connection_remover),
|
||||
)
|
||||
|
||||
|
||||
class ConnectionRemover(object):
|
||||
|
||||
def __init__(self, connection_class):
|
||||
self._connection_class = connection_class
|
||||
self._connection_pool_to_connections = {}
|
||||
@@ -360,11 +388,12 @@ class ConnectionRemover(object):
|
||||
|
||||
|
||||
def reset_patchers():
|
||||
yield mock.patch.object(httplib, 'HTTPConnection', _HTTPConnection)
|
||||
yield mock.patch.object(httplib, 'HTTPSConnection', _HTTPSConnection)
|
||||
yield mock.patch.object(httplib, "HTTPConnection", _HTTPConnection)
|
||||
yield mock.patch.object(httplib, "HTTPSConnection", _HTTPSConnection)
|
||||
|
||||
try:
|
||||
import requests
|
||||
|
||||
if requests.__build__ < 0x021603:
|
||||
# Avoid double unmock if requests 2.16.3
|
||||
# First, this is pointless, requests.packages.urllib3 *IS* urllib3 (see packages.py)
|
||||
@@ -382,85 +411,86 @@ def reset_patchers():
|
||||
pass
|
||||
else:
|
||||
# unpatch requests v1.x
|
||||
yield mock.patch.object(cpool, 'VerifiedHTTPSConnection', _VerifiedHTTPSConnection)
|
||||
yield mock.patch.object(cpool, 'HTTPConnection', _cpoolHTTPConnection)
|
||||
yield mock.patch.object(cpool, "VerifiedHTTPSConnection", _VerifiedHTTPSConnection)
|
||||
yield mock.patch.object(cpool, "HTTPConnection", _cpoolHTTPConnection)
|
||||
# unpatch requests v2.x
|
||||
if hasattr(cpool.HTTPConnectionPool, 'ConnectionCls'):
|
||||
yield mock.patch.object(cpool.HTTPConnectionPool, 'ConnectionCls',
|
||||
_cpoolHTTPConnection)
|
||||
yield mock.patch.object(cpool.HTTPSConnectionPool, 'ConnectionCls',
|
||||
_cpoolHTTPSConnection)
|
||||
if hasattr(cpool.HTTPConnectionPool, "ConnectionCls"):
|
||||
yield mock.patch.object(cpool.HTTPConnectionPool, "ConnectionCls", _cpoolHTTPConnection)
|
||||
yield mock.patch.object(cpool.HTTPSConnectionPool, "ConnectionCls", _cpoolHTTPSConnection)
|
||||
|
||||
if hasattr(cpool, 'HTTPSConnection'):
|
||||
yield mock.patch.object(cpool, 'HTTPSConnection', _cpoolHTTPSConnection)
|
||||
if hasattr(cpool, "HTTPSConnection"):
|
||||
yield mock.patch.object(cpool, "HTTPSConnection", _cpoolHTTPSConnection)
|
||||
|
||||
try:
|
||||
import urllib3.connectionpool as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
yield mock.patch.object(cpool, 'VerifiedHTTPSConnection', _VerifiedHTTPSConnection)
|
||||
yield mock.patch.object(cpool, 'HTTPConnection', _cpoolHTTPConnection)
|
||||
yield mock.patch.object(cpool, 'HTTPSConnection', _cpoolHTTPSConnection)
|
||||
if hasattr(cpool.HTTPConnectionPool, 'ConnectionCls'):
|
||||
yield mock.patch.object(cpool.HTTPConnectionPool, 'ConnectionCls', _cpoolHTTPConnection)
|
||||
yield mock.patch.object(cpool.HTTPSConnectionPool, 'ConnectionCls', _cpoolHTTPSConnection)
|
||||
yield mock.patch.object(cpool, "VerifiedHTTPSConnection", _VerifiedHTTPSConnection)
|
||||
yield mock.patch.object(cpool, "HTTPConnection", _cpoolHTTPConnection)
|
||||
yield mock.patch.object(cpool, "HTTPSConnection", _cpoolHTTPSConnection)
|
||||
if hasattr(cpool.HTTPConnectionPool, "ConnectionCls"):
|
||||
yield mock.patch.object(cpool.HTTPConnectionPool, "ConnectionCls", _cpoolHTTPConnection)
|
||||
yield mock.patch.object(cpool.HTTPSConnectionPool, "ConnectionCls", _cpoolHTTPSConnection)
|
||||
|
||||
try:
|
||||
import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
|
||||
# unpatch botocore with awsrequest
|
||||
import botocore.awsrequest as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
# unpatch requests v1.x
|
||||
yield mock.patch.object(cpool, 'VerifiedHTTPSConnection', _Boto3VerifiedHTTPSConnection)
|
||||
yield mock.patch.object(cpool, 'HTTPConnection', _cpoolBoto3HTTPConnection)
|
||||
# unpatch requests v2.x
|
||||
if hasattr(cpool.HTTPConnectionPool, 'ConnectionCls'):
|
||||
yield mock.patch.object(cpool.HTTPConnectionPool, 'ConnectionCls',
|
||||
_cpoolBoto3HTTPConnection)
|
||||
yield mock.patch.object(cpool.HTTPSConnectionPool, 'ConnectionCls',
|
||||
_cpoolBoto3HTTPSConnection)
|
||||
try:
|
||||
# unpatch botocore with vendored requests
|
||||
import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
# unpatch requests v1.x
|
||||
yield mock.patch.object(cpool, "VerifiedHTTPSConnection", _Boto3VerifiedHTTPSConnection)
|
||||
yield mock.patch.object(cpool, "HTTPConnection", _cpoolBoto3HTTPConnection)
|
||||
# unpatch requests v2.x
|
||||
if hasattr(cpool.HTTPConnectionPool, "ConnectionCls"):
|
||||
yield mock.patch.object(cpool.HTTPConnectionPool, "ConnectionCls", _cpoolBoto3HTTPConnection)
|
||||
yield mock.patch.object(
|
||||
cpool.HTTPSConnectionPool, "ConnectionCls", _cpoolBoto3HTTPSConnection
|
||||
)
|
||||
|
||||
if hasattr(cpool, 'HTTPSConnection'):
|
||||
yield mock.patch.object(cpool, 'HTTPSConnection', _cpoolBoto3HTTPSConnection)
|
||||
if hasattr(cpool, "HTTPSConnection"):
|
||||
yield mock.patch.object(cpool, "HTTPSConnection", _cpoolBoto3HTTPSConnection)
|
||||
else:
|
||||
if hasattr(cpool.AWSHTTPConnectionPool, "ConnectionCls"):
|
||||
yield mock.patch.object(cpool.AWSHTTPConnectionPool, "ConnectionCls", _cpoolBoto3HTTPConnection)
|
||||
yield mock.patch.object(cpool.AWSHTTPSConnectionPool, "ConnectionCls", _cpoolBoto3HTTPSConnection)
|
||||
|
||||
if hasattr(cpool, "AWSHTTPSConnection"):
|
||||
yield mock.patch.object(cpool, "AWSHTTPSConnection", _cpoolBoto3HTTPSConnection)
|
||||
|
||||
try:
|
||||
import httplib2 as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
yield mock.patch.object(cpool, 'HTTPConnectionWithTimeout', _HTTPConnectionWithTimeout)
|
||||
yield mock.patch.object(cpool, 'HTTPSConnectionWithTimeout', _HTTPSConnectionWithTimeout)
|
||||
yield mock.patch.object(cpool, 'SCHEME_TO_CONNECTION', _SCHEME_TO_CONNECTION)
|
||||
yield mock.patch.object(cpool, "HTTPConnectionWithTimeout", _HTTPConnectionWithTimeout)
|
||||
yield mock.patch.object(cpool, "HTTPSConnectionWithTimeout", _HTTPSConnectionWithTimeout)
|
||||
yield mock.patch.object(cpool, "SCHEME_TO_CONNECTION", _SCHEME_TO_CONNECTION)
|
||||
|
||||
try:
|
||||
import boto.https_connection as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
yield mock.patch.object(cpool, 'CertValidatingHTTPSConnection',
|
||||
_CertValidatingHTTPSConnection)
|
||||
yield mock.patch.object(cpool, "CertValidatingHTTPSConnection", _CertValidatingHTTPSConnection)
|
||||
|
||||
try:
|
||||
import tornado.simple_httpclient as simple
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
yield mock.patch.object(
|
||||
simple.SimpleAsyncHTTPClient,
|
||||
'fetch_impl',
|
||||
_SimpleAsyncHTTPClient_fetch_impl,
|
||||
)
|
||||
yield mock.patch.object(simple.SimpleAsyncHTTPClient, "fetch_impl", _SimpleAsyncHTTPClient_fetch_impl)
|
||||
try:
|
||||
import tornado.curl_httpclient as curl
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
yield mock.patch.object(
|
||||
curl.CurlAsyncHTTPClient,
|
||||
'fetch_impl',
|
||||
_CurlAsyncHTTPClient_fetch_impl,
|
||||
)
|
||||
yield mock.patch.object(curl.CurlAsyncHTTPClient, "fetch_impl", _CurlAsyncHTTPClient_fetch_impl)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
||||
@@ -5,14 +5,13 @@ from ..serialize import serialize, deserialize
|
||||
|
||||
|
||||
class FilesystemPersister(object):
|
||||
|
||||
@classmethod
|
||||
def load_cassette(cls, cassette_path, serializer):
|
||||
try:
|
||||
with open(cassette_path) as f:
|
||||
cassette_content = f.read()
|
||||
except IOError:
|
||||
raise ValueError('Cassette not found.')
|
||||
raise ValueError("Cassette not found.")
|
||||
cassette = deserialize(cassette_content, serializer)
|
||||
return cassette
|
||||
|
||||
@@ -22,5 +21,5 @@ class FilesystemPersister(object):
|
||||
dirname, filename = os.path.split(cassette_path)
|
||||
if dirname and not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
with open(cassette_path, 'w') as f:
|
||||
with open(cassette_path, "w") as f:
|
||||
f.write(data)
|
||||
|
||||
@@ -2,6 +2,9 @@ import warnings
|
||||
from six import BytesIO, text_type
|
||||
from six.moves.urllib.parse import urlparse, parse_qsl
|
||||
from .util import CaseInsensitiveDict
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Request(object):
|
||||
@@ -12,12 +15,13 @@ class Request(object):
|
||||
def __init__(self, method, uri, body, headers):
|
||||
self.method = method
|
||||
self.uri = uri
|
||||
self._was_file = hasattr(body, 'read')
|
||||
self._was_file = hasattr(body, "read")
|
||||
if self._was_file:
|
||||
self.body = body.read()
|
||||
else:
|
||||
self.body = body
|
||||
self.headers = headers
|
||||
log.debug("Invoking Request %s", self.uri)
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
@@ -36,13 +40,14 @@ class Request(object):
|
||||
@body.setter
|
||||
def body(self, value):
|
||||
if isinstance(value, text_type):
|
||||
value = value.encode('utf-8')
|
||||
value = value.encode("utf-8")
|
||||
self._body = value
|
||||
|
||||
def add_header(self, key, value):
|
||||
warnings.warn("Request.add_header is deprecated. "
|
||||
"Please assign to request.headers instead.",
|
||||
DeprecationWarning)
|
||||
warnings.warn(
|
||||
"Request.add_header is deprecated. " "Please assign to request.headers instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
self.headers[key] = value
|
||||
|
||||
@property
|
||||
@@ -58,7 +63,10 @@ class Request(object):
|
||||
parse_uri = urlparse(self.uri)
|
||||
port = parse_uri.port
|
||||
if port is None:
|
||||
port = {'https': 443, 'http': 80}[parse_uri.scheme]
|
||||
try:
|
||||
port = {"https": 443, "http": 80}[parse_uri.scheme]
|
||||
except KeyError:
|
||||
pass
|
||||
return port
|
||||
|
||||
@property
|
||||
@@ -88,10 +96,10 @@ class Request(object):
|
||||
|
||||
def _to_dict(self):
|
||||
return {
|
||||
'method': self.method,
|
||||
'uri': self.uri,
|
||||
'body': self.body,
|
||||
'headers': dict(((k, [v]) for k, v in self.headers.items())),
|
||||
"method": self.method,
|
||||
"uri": self.uri,
|
||||
"body": self.body,
|
||||
"headers": {k: [v] for k, v in self.headers.items()},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -112,7 +120,7 @@ class HeadersDict(CaseInsensitiveDict):
|
||||
In addition, some servers sometimes send the same header more than once,
|
||||
and httplib *can* deal with this situation.
|
||||
|
||||
Futhermore, I wanted to keep the request and response cassette format as
|
||||
Furthermore, I wanted to keep the request and response cassette format as
|
||||
similar as possible.
|
||||
|
||||
For this reason, in cassettes I keep a dict with lists as keys, but once
|
||||
|
||||
@@ -20,7 +20,7 @@ Deserializing: string (yaml converts from utf-8) -> bytestring
|
||||
|
||||
|
||||
def _looks_like_an_old_cassette(data):
|
||||
return isinstance(data, list) and len(data) and 'request' in data[0]
|
||||
return isinstance(data, list) and len(data) and "request" in data[0]
|
||||
|
||||
|
||||
def _warn_about_old_cassette_format():
|
||||
@@ -41,23 +41,18 @@ def deserialize(cassette_string, serializer):
|
||||
if _looks_like_an_old_cassette(data):
|
||||
_warn_about_old_cassette_format()
|
||||
|
||||
requests = [Request._from_dict(r['request']) for r in data['interactions']]
|
||||
responses = [
|
||||
compat.convert_to_bytes(r['response']) for r in data['interactions']
|
||||
]
|
||||
requests = [Request._from_dict(r["request"]) for r in data["interactions"]]
|
||||
responses = [compat.convert_to_bytes(r["response"]) for r in data["interactions"]]
|
||||
return requests, responses
|
||||
|
||||
|
||||
def serialize(cassette_dict, serializer):
|
||||
interactions = ([{
|
||||
'request': compat.convert_to_unicode(request._to_dict()),
|
||||
'response': compat.convert_to_unicode(response),
|
||||
} for request, response in zip(
|
||||
cassette_dict['requests'],
|
||||
cassette_dict['responses'],
|
||||
)])
|
||||
data = {
|
||||
'version': CASSETTE_FORMAT_VERSION,
|
||||
'interactions': interactions,
|
||||
}
|
||||
interactions = [
|
||||
{
|
||||
"request": compat.convert_to_unicode(request._to_dict()),
|
||||
"response": compat.convert_to_unicode(response),
|
||||
}
|
||||
for request, response in zip(cassette_dict["requests"], cassette_dict["responses"])
|
||||
]
|
||||
data = {"version": CASSETTE_FORMAT_VERSION, "interactions": interactions}
|
||||
return serializer.serialize(data)
|
||||
|
||||
@@ -24,8 +24,8 @@ def convert_body_to_bytes(resp):
|
||||
http://pyyaml.org/wiki/PyYAMLDocumentation#Python3support
|
||||
"""
|
||||
try:
|
||||
if resp['body']['string'] is not None and not isinstance(resp['body']['string'], six.binary_type):
|
||||
resp['body']['string'] = resp['body']['string'].encode('utf-8')
|
||||
if resp["body"]["string"] is not None and not isinstance(resp["body"]["string"], six.binary_type):
|
||||
resp["body"]["string"] = resp["body"]["string"].encode("utf-8")
|
||||
except (KeyError, TypeError, UnicodeEncodeError):
|
||||
# The thing we were converting either wasn't a dictionary or didn't
|
||||
# have the keys we were expecting. Some of the tests just serialize
|
||||
@@ -45,7 +45,7 @@ def _convert_string_to_unicode(string):
|
||||
|
||||
try:
|
||||
if string is not None and not isinstance(string, six.text_type):
|
||||
result = string.decode('utf-8')
|
||||
result = string.decode("utf-8")
|
||||
except (TypeError, UnicodeDecodeError, AttributeError):
|
||||
# Sometimes the string actually is binary or StringIO object,
|
||||
# so if you can't decode it, just give up.
|
||||
@@ -63,17 +63,15 @@ def convert_body_to_unicode(resp):
|
||||
# Some of the tests just serialize and deserialize a string.
|
||||
return _convert_string_to_unicode(resp)
|
||||
else:
|
||||
body = resp.get('body')
|
||||
body = resp.get("body")
|
||||
|
||||
if body is not None:
|
||||
try:
|
||||
body['string'] = _convert_string_to_unicode(
|
||||
body['string']
|
||||
)
|
||||
body["string"] = _convert_string_to_unicode(body["string"])
|
||||
except (KeyError, TypeError, AttributeError):
|
||||
# The thing we were converting either wasn't a dictionary or
|
||||
# didn't have the keys we were expecting.
|
||||
# For example request object has no 'string' key.
|
||||
resp['body'] = _convert_string_to_unicode(body)
|
||||
resp["body"] = _convert_string_to_unicode(body)
|
||||
|
||||
return resp
|
||||
|
||||
@@ -23,7 +23,7 @@ def serialize(cassette_dict):
|
||||
b"Error serializing cassette to JSON",
|
||||
original.start,
|
||||
original.end,
|
||||
original.args[-1] + error_message
|
||||
original.args[-1] + error_message,
|
||||
)
|
||||
except TypeError as original: # py3
|
||||
except TypeError: # py3
|
||||
raise TypeError(error_message)
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
'''Stubs for patching HTTP and HTTPS requests'''
|
||||
"""Stubs for patching HTTP and HTTPS requests"""
|
||||
|
||||
import logging
|
||||
import six
|
||||
from six.moves.http_client import (
|
||||
HTTPConnection,
|
||||
HTTPSConnection,
|
||||
HTTPResponse,
|
||||
)
|
||||
from six.moves.http_client import HTTPConnection, HTTPSConnection, HTTPResponse
|
||||
from six import BytesIO
|
||||
from vcr.request import Request
|
||||
from vcr.errors import CannotOverwriteExistingCassetteException
|
||||
@@ -45,8 +41,7 @@ def parse_headers(header_list):
|
||||
header_string = b""
|
||||
for key, values in header_list.items():
|
||||
for v in values:
|
||||
header_string += \
|
||||
key.encode('utf-8') + b":" + v.encode('utf-8') + b"\r\n"
|
||||
header_string += key.encode("utf-8") + b":" + v.encode("utf-8") + b"\r\n"
|
||||
return compat.get_httpmessage(header_string)
|
||||
|
||||
|
||||
@@ -60,28 +55,30 @@ def serialize_headers(response):
|
||||
|
||||
class VCRHTTPResponse(HTTPResponse):
|
||||
"""
|
||||
Stub reponse class that gets returned instead of a HTTPResponse
|
||||
Stub response class that gets returned instead of a HTTPResponse
|
||||
"""
|
||||
|
||||
def __init__(self, recorded_response):
|
||||
self.fp = None
|
||||
self.recorded_response = recorded_response
|
||||
self.reason = recorded_response['status']['message']
|
||||
self.status = self.code = recorded_response['status']['code']
|
||||
self.reason = recorded_response["status"]["message"]
|
||||
self.status = self.code = recorded_response["status"]["code"]
|
||||
self.version = None
|
||||
self._content = BytesIO(self.recorded_response['body']['string'])
|
||||
self._content = BytesIO(self.recorded_response["body"]["string"])
|
||||
self._closed = False
|
||||
|
||||
headers = self.recorded_response['headers']
|
||||
headers = self.recorded_response["headers"]
|
||||
# Since we are loading a response that has already been serialized, our
|
||||
# response is no longer chunked. That means we don't want any
|
||||
# libraries trying to process a chunked response. By removing the
|
||||
# transfer-encoding: chunked header, this should cause the downstream
|
||||
# libraries to process this as a non-chunked response.
|
||||
te_key = [h for h in headers.keys() if h.upper() == 'TRANSFER-ENCODING']
|
||||
te_key = [h for h in headers.keys() if h.upper() == "TRANSFER-ENCODING"]
|
||||
if te_key:
|
||||
del headers[te_key[0]]
|
||||
self.headers = self.msg = parse_headers(headers)
|
||||
|
||||
self.length = compat.get_header(self.msg, 'content-length') or None
|
||||
self.length = compat.get_header(self.msg, "content-length") or None
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
@@ -93,9 +90,30 @@ class VCRHTTPResponse(HTTPResponse):
|
||||
def read(self, *args, **kwargs):
|
||||
return self._content.read(*args, **kwargs)
|
||||
|
||||
def readall(self):
|
||||
return self._content.readall()
|
||||
|
||||
def readinto(self, *args, **kwargs):
|
||||
return self._content.readinto(*args, **kwargs)
|
||||
|
||||
def readline(self, *args, **kwargs):
|
||||
return self._content.readline(*args, **kwargs)
|
||||
|
||||
def readlines(self, *args, **kwargs):
|
||||
return self._content.readlines(*args, **kwargs)
|
||||
|
||||
def seekable(self):
|
||||
return self._content.seekable()
|
||||
|
||||
def tell(self):
|
||||
return self._content.tell()
|
||||
|
||||
def isatty(self):
|
||||
return self._content.isatty()
|
||||
|
||||
def seek(self, *args, **kwargs):
|
||||
return self._content.seek(*args, **kwargs)
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
return True
|
||||
@@ -107,20 +125,23 @@ class VCRHTTPResponse(HTTPResponse):
|
||||
return self.closed
|
||||
|
||||
def info(self):
|
||||
return parse_headers(self.recorded_response['headers'])
|
||||
return parse_headers(self.recorded_response["headers"])
|
||||
|
||||
def getheaders(self):
|
||||
message = parse_headers(self.recorded_response['headers'])
|
||||
message = parse_headers(self.recorded_response["headers"])
|
||||
return list(compat.get_header_items(message))
|
||||
|
||||
def getheader(self, header, default=None):
|
||||
values = [v for (k, v) in self.getheaders() if k.lower() == header.lower()]
|
||||
|
||||
if values:
|
||||
return ', '.join(values)
|
||||
return ", ".join(values)
|
||||
else:
|
||||
return default
|
||||
|
||||
def readable(self):
|
||||
return self._content.readable()
|
||||
|
||||
|
||||
class VCRConnection(object):
|
||||
# A reference to the cassette that's currently being patched in
|
||||
@@ -131,40 +152,27 @@ class VCRConnection(object):
|
||||
Returns empty string for the default port and ':port' otherwise
|
||||
"""
|
||||
port = self.real_connection.port
|
||||
default_port = {'https': 443, 'http': 80}[self._protocol]
|
||||
return ':{}'.format(port) if port != default_port else ''
|
||||
default_port = {"https": 443, "http": 80}[self._protocol]
|
||||
return ":{}".format(port) if port != default_port else ""
|
||||
|
||||
def _uri(self, url):
|
||||
"""Returns request absolute URI"""
|
||||
if url and not url.startswith('/'):
|
||||
if url and not url.startswith("/"):
|
||||
# Then this must be a proxy request.
|
||||
return url
|
||||
uri = "{0}://{1}{2}{3}".format(
|
||||
self._protocol,
|
||||
self.real_connection.host,
|
||||
self._port_postfix(),
|
||||
url,
|
||||
)
|
||||
uri = "{}://{}{}{}".format(self._protocol, self.real_connection.host, self._port_postfix(), url)
|
||||
log.debug("Absolute URI: %s", uri)
|
||||
return uri
|
||||
|
||||
def _url(self, uri):
|
||||
"""Returns request selector url from absolute URI"""
|
||||
prefix = "{}://{}{}".format(
|
||||
self._protocol,
|
||||
self.real_connection.host,
|
||||
self._port_postfix(),
|
||||
)
|
||||
return uri.replace(prefix, '', 1)
|
||||
prefix = "{}://{}{}".format(self._protocol, self.real_connection.host, self._port_postfix())
|
||||
return uri.replace(prefix, "", 1)
|
||||
|
||||
def request(self, method, url, body=None, headers=None, *args, **kwargs):
|
||||
'''Persist the request metadata in self._vcr_request'''
|
||||
self._vcr_request = Request(
|
||||
method=method,
|
||||
uri=self._uri(url),
|
||||
body=body,
|
||||
headers=headers or {}
|
||||
)
|
||||
log.debug('Got {}'.format(self._vcr_request))
|
||||
"""Persist the request metadata in self._vcr_request"""
|
||||
self._vcr_request = Request(method=method, uri=self._uri(url), body=body, headers=headers or {})
|
||||
log.debug("Got {}".format(self._vcr_request))
|
||||
|
||||
# Note: The request may not actually be finished at this point, so
|
||||
# I'm not sending the actual request until getresponse(). This
|
||||
@@ -179,25 +187,19 @@ class VCRConnection(object):
|
||||
to start building up a request. Usually followed by a bunch
|
||||
of putheader() calls.
|
||||
"""
|
||||
self._vcr_request = Request(
|
||||
method=method,
|
||||
uri=self._uri(url),
|
||||
body="",
|
||||
headers={}
|
||||
)
|
||||
log.debug('Got {}'.format(self._vcr_request))
|
||||
self._vcr_request = Request(method=method, uri=self._uri(url), body="", headers={})
|
||||
log.debug("Got {}".format(self._vcr_request))
|
||||
|
||||
def putheader(self, header, *values):
|
||||
self._vcr_request.headers[header] = values
|
||||
|
||||
def send(self, data):
|
||||
'''
|
||||
"""
|
||||
This method is called after request(), to add additional data to the
|
||||
body of the request. So if that happens, let's just append the data
|
||||
onto the most recent request in the cassette.
|
||||
'''
|
||||
self._vcr_request.body = self._vcr_request.body + data \
|
||||
if self._vcr_request.body else data
|
||||
"""
|
||||
self._vcr_request.body = self._vcr_request.body + data if self._vcr_request.body else data
|
||||
|
||||
def close(self):
|
||||
# Note: the real connection will only close if it's open, so
|
||||
@@ -214,40 +216,27 @@ class VCRConnection(object):
|
||||
self._vcr_request.body = message_body
|
||||
|
||||
def getresponse(self, _=False, **kwargs):
|
||||
'''Retrieve the response'''
|
||||
"""Retrieve the response"""
|
||||
# Check to see if the cassette has a response for this request. If so,
|
||||
# then return it
|
||||
if self.cassette.can_play_response_for(self._vcr_request):
|
||||
log.info(
|
||||
"Playing response for {} from cassette".format(
|
||||
self._vcr_request
|
||||
)
|
||||
)
|
||||
log.info("Playing response for {} from cassette".format(self._vcr_request))
|
||||
response = self.cassette.play_response(self._vcr_request)
|
||||
return VCRHTTPResponse(response)
|
||||
else:
|
||||
if self.cassette.write_protected and self.cassette.filter_request(
|
||||
self._vcr_request
|
||||
):
|
||||
if self.cassette.write_protected and self.cassette.filter_request(self._vcr_request):
|
||||
raise CannotOverwriteExistingCassetteException(
|
||||
"No match for the request (%r) was found. "
|
||||
"Can't overwrite existing cassette (%r) in "
|
||||
"your current record mode (%r)."
|
||||
% (self._vcr_request, self.cassette._path,
|
||||
self.cassette.record_mode)
|
||||
cassette=self.cassette, failed_request=self._vcr_request
|
||||
)
|
||||
|
||||
# Otherwise, we should send the request, then get the response
|
||||
# and return it.
|
||||
|
||||
log.info(
|
||||
"{} not in cassette, sending to real server".format(
|
||||
self._vcr_request
|
||||
)
|
||||
)
|
||||
log.info("{} not in cassette, sending to real server".format(self._vcr_request))
|
||||
# This is imported here to avoid circular import.
|
||||
# TODO(@IvanMalison): Refactor to allow normal import.
|
||||
from vcr.patch import force_reset
|
||||
|
||||
with force_reset():
|
||||
self.real_connection.request(
|
||||
method=self._vcr_request.method,
|
||||
@@ -261,12 +250,9 @@ class VCRConnection(object):
|
||||
|
||||
# put the response into the cassette
|
||||
response = {
|
||||
'status': {
|
||||
'code': response.status,
|
||||
'message': response.reason
|
||||
},
|
||||
'headers': serialize_headers(response),
|
||||
'body': {'string': response.read()},
|
||||
"status": {"code": response.status, "message": response.reason},
|
||||
"headers": serialize_headers(response),
|
||||
"body": {"string": response.read()},
|
||||
}
|
||||
self.cassette.append(self._vcr_request, response)
|
||||
return VCRHTTPResponse(response)
|
||||
@@ -282,8 +268,7 @@ class VCRConnection(object):
|
||||
and are not write-protected.
|
||||
"""
|
||||
|
||||
if hasattr(self, '_vcr_request') and \
|
||||
self.cassette.can_play_response_for(self._vcr_request):
|
||||
if hasattr(self, "_vcr_request") and self.cassette.can_play_response_for(self._vcr_request):
|
||||
# We already have a response we are going to play, don't
|
||||
# actually connect
|
||||
return
|
||||
@@ -293,6 +278,7 @@ class VCRConnection(object):
|
||||
return
|
||||
|
||||
from vcr.patch import force_reset
|
||||
|
||||
with force_reset():
|
||||
return self.real_connection.connect(*args, **kwargs)
|
||||
|
||||
@@ -311,12 +297,13 @@ class VCRConnection(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if six.PY3:
|
||||
kwargs.pop('strict', None) # apparently this is gone in py3
|
||||
kwargs.pop("strict", None) # apparently this is gone in py3
|
||||
|
||||
# need to temporarily reset here because the real connection
|
||||
# inherits from the thing that we are mocking out. Take out
|
||||
# the reset if you want to see what I mean :)
|
||||
from vcr.patch import force_reset
|
||||
|
||||
with force_reset():
|
||||
self.real_connection = self._baseclass(*args, **kwargs)
|
||||
|
||||
@@ -348,7 +335,7 @@ class VCRConnection(object):
|
||||
Send requests for weird attributes up to the real connection
|
||||
(counterpart to __setattr above)
|
||||
"""
|
||||
if self.__dict__.get('real_connection'):
|
||||
if self.__dict__.get("real_connection"):
|
||||
# check in case real_connection has not been set yet, such as when
|
||||
# we're setting the real_connection itself for the first time
|
||||
return getattr(self.real_connection, name)
|
||||
@@ -362,13 +349,15 @@ for k, v in HTTPConnection.__dict__.items():
|
||||
|
||||
|
||||
class VCRHTTPConnection(VCRConnection):
|
||||
'''A Mocked class for HTTP requests'''
|
||||
"""A Mocked class for HTTP requests"""
|
||||
|
||||
_baseclass = HTTPConnection
|
||||
_protocol = 'http'
|
||||
_protocol = "http"
|
||||
|
||||
|
||||
class VCRHTTPSConnection(VCRConnection):
|
||||
'''A Mocked class for HTTPS requests'''
|
||||
"""A Mocked class for HTTPS requests"""
|
||||
|
||||
_baseclass = HTTPSConnection
|
||||
_protocol = 'https'
|
||||
_protocol = "https"
|
||||
is_verified = True
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
'''Stubs for aiohttp HTTP clients'''
|
||||
"""Stubs for aiohttp HTTP clients"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
import logging
|
||||
import json
|
||||
|
||||
from aiohttp import ClientResponse
|
||||
from aiohttp import ClientResponse, streams
|
||||
from multidict import CIMultiDict, CIMultiDictProxy
|
||||
from yarl import URL
|
||||
|
||||
from vcr.request import Request
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MockStream(asyncio.StreamReader, streams.AsyncStreamReaderMixin):
|
||||
pass
|
||||
|
||||
|
||||
class MockClientResponse(ClientResponse):
|
||||
def __init__(self, method, url):
|
||||
@@ -25,26 +33,86 @@ class MockClientResponse(ClientResponse):
|
||||
session=None,
|
||||
)
|
||||
|
||||
async def json(self, *, encoding='utf-8', loads=json.loads, **kwargs): # NOQA: E999
|
||||
return loads(self._body.decode(encoding))
|
||||
async def json(self, *, encoding="utf-8", loads=json.loads, **kwargs): # NOQA: E999
|
||||
stripped = self._body.strip()
|
||||
if not stripped:
|
||||
return None
|
||||
|
||||
async def text(self, encoding='utf-8'):
|
||||
return self._body.decode(encoding)
|
||||
return loads(stripped.decode(encoding))
|
||||
|
||||
async def text(self, encoding="utf-8", errors="strict"):
|
||||
return self._body.decode(encoding, errors=errors)
|
||||
|
||||
async def read(self):
|
||||
return self._body
|
||||
|
||||
async def release(self):
|
||||
def release(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
s = MockStream()
|
||||
s.feed_data(self._body)
|
||||
s.feed_eof()
|
||||
return s
|
||||
|
||||
|
||||
def build_response(vcr_request, vcr_response, history):
|
||||
response = MockClientResponse(vcr_request.method, URL(vcr_response.get("url")))
|
||||
response.status = vcr_response["status"]["code"]
|
||||
response._body = vcr_response["body"].get("string", b"")
|
||||
response.reason = vcr_response["status"]["message"]
|
||||
response._headers = CIMultiDictProxy(CIMultiDict(vcr_response["headers"]))
|
||||
response._history = tuple(history)
|
||||
|
||||
response.close()
|
||||
return response
|
||||
|
||||
|
||||
def play_responses(cassette, vcr_request):
|
||||
history = []
|
||||
vcr_response = cassette.play_response(vcr_request)
|
||||
response = build_response(vcr_request, vcr_response, history)
|
||||
|
||||
while cassette.can_play_response_for(vcr_request):
|
||||
history.append(response)
|
||||
vcr_response = cassette.play_response(vcr_request)
|
||||
response = build_response(vcr_request, vcr_response, history)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
async def record_response(cassette, vcr_request, response, past=False):
|
||||
body = {} if past else {"string": (await response.read())}
|
||||
headers = {str(key): value for key, value in response.headers.items()}
|
||||
|
||||
vcr_response = {
|
||||
"status": {"code": response.status, "message": response.reason},
|
||||
"headers": headers,
|
||||
"body": body, # NOQA: E999
|
||||
"url": str(response.url),
|
||||
}
|
||||
cassette.append(vcr_request, vcr_response)
|
||||
|
||||
|
||||
async def record_responses(cassette, vcr_request, response):
|
||||
for past_response in response.history:
|
||||
await record_response(cassette, vcr_request, past_response, past=True)
|
||||
|
||||
await record_response(cassette, vcr_request, response)
|
||||
|
||||
|
||||
def vcr_request(cassette, real_request):
|
||||
@functools.wraps(real_request)
|
||||
async def new_request(self, method, url, **kwargs):
|
||||
headers = kwargs.get('headers')
|
||||
headers = kwargs.get("headers")
|
||||
auth = kwargs.get("auth")
|
||||
headers = self._prepare_headers(headers)
|
||||
data = kwargs.get('data')
|
||||
params = kwargs.get('params')
|
||||
data = kwargs.get("data", kwargs.get("json"))
|
||||
params = kwargs.get("params")
|
||||
|
||||
if auth is not None:
|
||||
headers["AUTHORIZATION"] = auth.encode()
|
||||
|
||||
request_url = URL(url)
|
||||
if params:
|
||||
@@ -55,40 +123,24 @@ def vcr_request(cassette, real_request):
|
||||
vcr_request = Request(method, str(request_url), data, headers)
|
||||
|
||||
if cassette.can_play_response_for(vcr_request):
|
||||
vcr_response = cassette.play_response(vcr_request)
|
||||
|
||||
response = MockClientResponse(method, URL(vcr_response.get('url')))
|
||||
response.status = vcr_response['status']['code']
|
||||
response._body = vcr_response['body']['string']
|
||||
response.reason = vcr_response['status']['message']
|
||||
response._headers = vcr_response['headers']
|
||||
|
||||
response.close()
|
||||
return response
|
||||
return play_responses(cassette, vcr_request)
|
||||
|
||||
if cassette.write_protected and cassette.filter_request(vcr_request):
|
||||
response = MockClientResponse(method, URL(url))
|
||||
response.status = 599
|
||||
msg = ("No match for the request {!r} was found. Can't overwrite "
|
||||
"existing cassette {!r} in your current record mode {!r}.")
|
||||
msg = (
|
||||
"No match for the request {!r} was found. Can't overwrite "
|
||||
"existing cassette {!r} in your current record mode {!r}."
|
||||
)
|
||||
msg = msg.format(vcr_request, cassette._path, cassette.record_mode)
|
||||
response._body = msg.encode()
|
||||
response.close()
|
||||
return response
|
||||
|
||||
log.info("%s not in cassette, sending to real server", vcr_request)
|
||||
|
||||
response = await real_request(self, method, url, **kwargs) # NOQA: E999
|
||||
|
||||
vcr_response = {
|
||||
'status': {
|
||||
'code': response.status,
|
||||
'message': response.reason,
|
||||
},
|
||||
'headers': dict(response.headers),
|
||||
'body': {'string': (await response.read())}, # NOQA: E999
|
||||
'url': response.url,
|
||||
}
|
||||
cassette.append(vcr_request, vcr_response)
|
||||
|
||||
await record_responses(cassette, vcr_request, response)
|
||||
return response
|
||||
|
||||
return new_request
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
'''Stubs for boto3'''
|
||||
"""Stubs for boto3"""
|
||||
import six
|
||||
|
||||
try:
|
||||
# boto using awsrequest
|
||||
from botocore.awsrequest import AWSHTTPConnection as HTTPConnection
|
||||
from botocore.awsrequest import AWSHTTPSConnection as VerifiedHTTPSConnection
|
||||
|
||||
except ImportError: # pragma: nocover
|
||||
# boto using vendored requests
|
||||
# urllib3 defines its own HTTPConnection classes, which boto3 goes ahead and assumes
|
||||
# you're using. It includes some polyfills for newer features missing in older pythons.
|
||||
try:
|
||||
from urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
||||
except ImportError: # pragma: nocover
|
||||
from requests.packages.urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
||||
|
||||
from botocore.vendored.requests.packages.urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
||||
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||
|
||||
# urllib3 defines its own HTTPConnection classes, which boto3 goes ahead and assumes
|
||||
# you're using. It includes some polyfills for newer features missing in older pythons.
|
||||
|
||||
|
||||
class VCRRequestsHTTPConnection(VCRHTTPConnection, HTTPConnection):
|
||||
_baseclass = HTTPConnection
|
||||
@@ -13,3 +24,21 @@ class VCRRequestsHTTPConnection(VCRHTTPConnection, HTTPConnection):
|
||||
|
||||
class VCRRequestsHTTPSConnection(VCRHTTPSConnection, VerifiedHTTPSConnection):
|
||||
_baseclass = VerifiedHTTPSConnection
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if six.PY3:
|
||||
kwargs.pop("strict", None) # apparently this is gone in py3
|
||||
|
||||
# need to temporarily reset here because the real connection
|
||||
# inherits from the thing that we are mocking out. Take out
|
||||
# the reset if you want to see what I mean :)
|
||||
from vcr.patch import force_reset
|
||||
|
||||
with force_reset():
|
||||
self.real_connection = self._baseclass(*args, **kwargs)
|
||||
# Make sure to set those attributes as it seems `AWSHTTPConnection` does not
|
||||
# set them, making the connection to fail !
|
||||
self.real_connection.assert_hostname = kwargs.get("assert_hostname", False)
|
||||
self.real_connection.cert_reqs = kwargs.get("cert_reqs", "CERT_NONE")
|
||||
|
||||
self._sock = None
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'''Stubs for boto'''
|
||||
"""Stubs for boto"""
|
||||
|
||||
from boto.https_connection import CertValidatingHTTPSConnection
|
||||
from ..stubs import VCRHTTPSConnection
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import six
|
||||
from six import BytesIO
|
||||
from six.moves.http_client import HTTPMessage
|
||||
|
||||
try:
|
||||
import http.client
|
||||
except ImportError:
|
||||
|
||||
@@ -1,62 +1,60 @@
|
||||
'''Stubs for httplib2'''
|
||||
"""Stubs for httplib2"""
|
||||
|
||||
from httplib2 import HTTPConnectionWithTimeout, HTTPSConnectionWithTimeout
|
||||
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||
|
||||
|
||||
class VCRHTTPConnectionWithTimeout(VCRHTTPConnection,
|
||||
HTTPConnectionWithTimeout):
|
||||
class VCRHTTPConnectionWithTimeout(VCRHTTPConnection, HTTPConnectionWithTimeout):
|
||||
_baseclass = HTTPConnectionWithTimeout
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
'''I overrode the init because I need to clean kwargs before calling
|
||||
HTTPConnection.__init__.'''
|
||||
"""I overrode the init because I need to clean kwargs before calling
|
||||
HTTPConnection.__init__."""
|
||||
|
||||
# Delete the keyword arguments that HTTPConnection would not recognize
|
||||
safe_keys = {'host', 'port', 'strict', 'timeout', 'source_address'}
|
||||
safe_keys = {"host", "port", "strict", "timeout", "source_address"}
|
||||
unknown_keys = set(kwargs.keys()) - safe_keys
|
||||
safe_kwargs = kwargs.copy()
|
||||
for kw in unknown_keys:
|
||||
del safe_kwargs[kw]
|
||||
|
||||
self.proxy_info = kwargs.pop('proxy_info', None)
|
||||
self.proxy_info = kwargs.pop("proxy_info", None)
|
||||
VCRHTTPConnection.__init__(self, *args, **safe_kwargs)
|
||||
self.sock = self.real_connection.sock
|
||||
|
||||
|
||||
class VCRHTTPSConnectionWithTimeout(VCRHTTPSConnection,
|
||||
HTTPSConnectionWithTimeout):
|
||||
class VCRHTTPSConnectionWithTimeout(VCRHTTPSConnection, HTTPSConnectionWithTimeout):
|
||||
_baseclass = HTTPSConnectionWithTimeout
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
# Delete the keyword arguments that HTTPSConnection would not recognize
|
||||
safe_keys = {
|
||||
'host',
|
||||
'port',
|
||||
'key_file',
|
||||
'cert_file',
|
||||
'strict',
|
||||
'timeout',
|
||||
'source_address',
|
||||
'ca_certs',
|
||||
'disable_ssl_certificate_validation',
|
||||
"host",
|
||||
"port",
|
||||
"key_file",
|
||||
"cert_file",
|
||||
"strict",
|
||||
"timeout",
|
||||
"source_address",
|
||||
"ca_certs",
|
||||
"disable_ssl_certificate_validation",
|
||||
}
|
||||
unknown_keys = set(kwargs.keys()) - safe_keys
|
||||
safe_kwargs = kwargs.copy()
|
||||
for kw in unknown_keys:
|
||||
del safe_kwargs[kw]
|
||||
self.proxy_info = kwargs.pop('proxy_info', None)
|
||||
if 'ca_certs' not in kwargs or kwargs['ca_certs'] is None:
|
||||
self.proxy_info = kwargs.pop("proxy_info", None)
|
||||
if "ca_certs" not in kwargs or kwargs["ca_certs"] is None:
|
||||
try:
|
||||
import httplib2
|
||||
|
||||
self.ca_certs = httplib2.CA_CERTS
|
||||
except ImportError:
|
||||
self.ca_certs = None
|
||||
else:
|
||||
self.ca_certs = kwargs['ca_certs']
|
||||
self.ca_certs = kwargs["ca_certs"]
|
||||
|
||||
self.disable_ssl_certificate_validation = kwargs.pop(
|
||||
'disable_ssl_certificate_validation', None)
|
||||
self.disable_ssl_certificate_validation = kwargs.pop("disable_ssl_certificate_validation", None)
|
||||
VCRHTTPSConnection.__init__(self, *args, **safe_kwargs)
|
||||
self.sock = self.real_connection.sock
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'''Stubs for requests'''
|
||||
"""Stubs for requests"""
|
||||
|
||||
try:
|
||||
from urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'''Stubs for tornado HTTP clients'''
|
||||
"""Stubs for tornado HTTP clients"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
@@ -12,20 +12,19 @@ from vcr.request import Request
|
||||
|
||||
|
||||
def vcr_fetch_impl(cassette, real_fetch_impl):
|
||||
|
||||
@functools.wraps(real_fetch_impl)
|
||||
def new_fetch_impl(self, request, callback):
|
||||
headers = request.headers.copy()
|
||||
if request.user_agent:
|
||||
headers.setdefault('User-Agent', request.user_agent)
|
||||
headers.setdefault("User-Agent", request.user_agent)
|
||||
|
||||
# TODO body_producer, header_callback, and streaming_callback are not
|
||||
# yet supported.
|
||||
|
||||
unsupported_call = (
|
||||
getattr(request, 'body_producer', None) is not None or
|
||||
request.header_callback is not None or
|
||||
request.streaming_callback is not None
|
||||
getattr(request, "body_producer", None) is not None
|
||||
or request.header_callback is not None
|
||||
or request.streaming_callback is not None
|
||||
)
|
||||
if unsupported_call:
|
||||
response = HTTPResponse(
|
||||
@@ -40,18 +39,13 @@ def vcr_fetch_impl(cassette, real_fetch_impl):
|
||||
)
|
||||
return callback(response)
|
||||
|
||||
vcr_request = Request(
|
||||
request.method,
|
||||
request.url,
|
||||
request.body,
|
||||
headers,
|
||||
)
|
||||
vcr_request = Request(request.method, request.url, request.body, headers)
|
||||
|
||||
if cassette.can_play_response_for(vcr_request):
|
||||
vcr_response = cassette.play_response(vcr_request)
|
||||
headers = httputil.HTTPHeaders()
|
||||
|
||||
recorded_headers = vcr_response['headers']
|
||||
recorded_headers = vcr_response["headers"]
|
||||
if isinstance(recorded_headers, dict):
|
||||
recorded_headers = recorded_headers.items()
|
||||
for k, vs in recorded_headers:
|
||||
@@ -59,45 +53,34 @@ def vcr_fetch_impl(cassette, real_fetch_impl):
|
||||
headers.add(k, v)
|
||||
response = HTTPResponse(
|
||||
request,
|
||||
code=vcr_response['status']['code'],
|
||||
reason=vcr_response['status']['message'],
|
||||
code=vcr_response["status"]["code"],
|
||||
reason=vcr_response["status"]["message"],
|
||||
headers=headers,
|
||||
buffer=BytesIO(vcr_response['body']['string']),
|
||||
effective_url=vcr_response.get('url'),
|
||||
buffer=BytesIO(vcr_response["body"]["string"]),
|
||||
effective_url=vcr_response.get("url"),
|
||||
request_time=self.io_loop.time() - request.start_time,
|
||||
)
|
||||
return callback(response)
|
||||
else:
|
||||
if cassette.write_protected and cassette.filter_request(
|
||||
vcr_request
|
||||
):
|
||||
if cassette.write_protected and cassette.filter_request(vcr_request):
|
||||
response = HTTPResponse(
|
||||
request,
|
||||
599,
|
||||
error=CannotOverwriteExistingCassetteException(
|
||||
"No match for the request (%r) was found. "
|
||||
"Can't overwrite existing cassette (%r) in "
|
||||
"your current record mode (%r)."
|
||||
% (vcr_request, cassette._path, cassette.record_mode)
|
||||
cassette=cassette, failed_request=vcr_request
|
||||
),
|
||||
request_time=self.io_loop.time() - request.start_time,
|
||||
)
|
||||
return callback(response)
|
||||
|
||||
def new_callback(response):
|
||||
headers = [
|
||||
(k, response.headers.get_list(k))
|
||||
for k in response.headers.keys()
|
||||
]
|
||||
headers = [(k, response.headers.get_list(k)) for k in response.headers.keys()]
|
||||
|
||||
vcr_response = {
|
||||
'status': {
|
||||
'code': response.code,
|
||||
'message': response.reason,
|
||||
},
|
||||
'headers': headers,
|
||||
'body': {'string': response.body},
|
||||
'url': response.effective_url,
|
||||
"status": {"code": response.code, "message": response.reason},
|
||||
"headers": headers,
|
||||
"body": {"string": response.body},
|
||||
"url": response.effective_url,
|
||||
}
|
||||
cassette.append(vcr_request, vcr_response)
|
||||
return callback(response)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'''Stubs for urllib3'''
|
||||
"""Stubs for urllib3"""
|
||||
|
||||
from urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
||||
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||
|
||||
44
vcr/util.py
44
vcr/util.py
@@ -1,13 +1,17 @@
|
||||
import collections
|
||||
import types
|
||||
|
||||
try:
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
except ImportError:
|
||||
from collections import Mapping, MutableMapping
|
||||
|
||||
|
||||
# Shamelessly stolen from https://github.com/kennethreitz/requests/blob/master/requests/structures.py
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
class CaseInsensitiveDict(MutableMapping):
|
||||
"""
|
||||
A case-insensitive ``dict``-like object.
|
||||
Implements all methods and operations of
|
||||
``collections.MutableMapping`` as well as dict's ``copy``. Also
|
||||
``collections.abc.MutableMapping`` as well as dict's ``copy``. Also
|
||||
provides ``lower_items``.
|
||||
All keys are expected to be strings. The structure remembers the
|
||||
case of the last key to be set, and ``iter(instance)``,
|
||||
@@ -25,6 +29,7 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
operations are given keys that have equal ``.lower()``s, the
|
||||
behavior is undefined.
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, **kwargs):
|
||||
self._store = dict()
|
||||
if data is None:
|
||||
@@ -50,14 +55,10 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
|
||||
def lower_items(self):
|
||||
"""Like iteritems(), but with all lowercase keys."""
|
||||
return (
|
||||
(lowerkey, keyval[1])
|
||||
for (lowerkey, keyval)
|
||||
in self._store.items()
|
||||
)
|
||||
return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, collections.Mapping):
|
||||
if isinstance(other, Mapping):
|
||||
other = CaseInsensitiveDict(other)
|
||||
else:
|
||||
return NotImplemented
|
||||
@@ -88,37 +89,30 @@ def compose(*functions):
|
||||
if function:
|
||||
res = function(res)
|
||||
return res
|
||||
|
||||
return composed
|
||||
|
||||
|
||||
def read_body(request):
|
||||
if hasattr(request.body, 'read'):
|
||||
if hasattr(request.body, "read"):
|
||||
return request.body.read()
|
||||
return request.body
|
||||
|
||||
|
||||
def auto_decorate(
|
||||
decorator,
|
||||
predicate=lambda name, value: isinstance(value, types.FunctionType)
|
||||
):
|
||||
def auto_decorate(decorator, predicate=lambda name, value: isinstance(value, types.FunctionType)):
|
||||
def maybe_decorate(attribute, value):
|
||||
if predicate(attribute, value):
|
||||
value = decorator(value)
|
||||
return value
|
||||
|
||||
class DecorateAll(type):
|
||||
|
||||
def __setattr__(cls, attribute, value):
|
||||
return super(DecorateAll, cls).__setattr__(
|
||||
attribute, maybe_decorate(attribute, value)
|
||||
)
|
||||
return super(DecorateAll, cls).__setattr__(attribute, maybe_decorate(attribute, value))
|
||||
|
||||
def __new__(cls, name, bases, attributes_dict):
|
||||
new_attributes_dict = dict(
|
||||
(attribute, maybe_decorate(attribute, value))
|
||||
for attribute, value in attributes_dict.items()
|
||||
)
|
||||
return super(DecorateAll, cls).__new__(
|
||||
cls, name, bases, new_attributes_dict
|
||||
)
|
||||
new_attributes_dict = {
|
||||
attribute: maybe_decorate(attribute, value) for attribute, value in attributes_dict.items()
|
||||
}
|
||||
return super(DecorateAll, cls).__new__(cls, name, bases, new_attributes_dict)
|
||||
|
||||
return DecorateAll
|
||||
|
||||
Reference in New Issue
Block a user