mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-09 09:13:23 +00:00
Compare commits
303 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 | ||
|
|
e42746fa88 | ||
|
|
03b1dd9faa | ||
|
|
f2a79d3fcc | ||
|
|
287ea4b06e | ||
|
|
0cf11d4525 | ||
|
|
75a334686f | ||
|
|
9a9cdb3a95 | ||
|
|
b38915a89a | ||
|
|
e93060c81b | ||
|
|
10736db427 | ||
|
|
cb4228cf90 | ||
|
|
f7c051cde6 | ||
|
|
075dde6707 | ||
|
|
af2742b6b9 | ||
|
|
e9d00a5e2a | ||
|
|
957db22d5c | ||
|
|
302ea35d9a | ||
|
|
895850b197 | ||
|
|
5ddcaa4870 | ||
|
|
76076e5ccb | ||
|
|
7417978e36 | ||
|
|
a9e75a545e | ||
|
|
e559be758a | ||
|
|
bb8d39dd20 | ||
|
|
ff7dd06f47 | ||
|
|
4b6b5effc7 | ||
|
|
06dc2190d6 | ||
|
|
eb4774a7d2 | ||
|
|
bed9e520a3 | ||
|
|
365a98bf66 | ||
|
|
fc95e34bd4 | ||
|
|
236dc1f4f2 | ||
|
|
43f4eb8156 | ||
|
|
aff71c5107 | ||
|
|
506700651d | ||
|
|
d1b11da610 | ||
|
|
306238d561 | ||
|
|
dbddaa0e44 | ||
|
|
0d4c9eccf5 | ||
|
|
1674741d9f | ||
|
|
75cb067e29 | ||
|
|
ab6e6b5b5d | ||
|
|
9e8bd382d3 | ||
|
|
ba79174a1f | ||
|
|
c341e48961 | ||
|
|
5be75692c4 | ||
|
|
b10b92bdbb | ||
|
|
3009cbbbe9 | ||
|
|
f811b41ad9 | ||
|
|
140bc2ee74 | ||
|
|
867fd9ab4b | ||
|
|
545c903ee2 | ||
|
|
cd864b5eca | ||
|
|
689d68a0a2 | ||
|
|
709017ea46 | ||
|
|
8621427f46 | ||
|
|
7e695ff7bc | ||
|
|
bd08e5119f | ||
|
|
6ab508d67d | ||
|
|
f1561ae0f8 | ||
|
|
f1f8ce2af4 | ||
|
|
26be756f47 | ||
|
|
f890709a20 | ||
|
|
d0ae5fa40b | ||
|
|
1562bc7659 | ||
|
|
16b69aa2e5 | ||
|
|
d9caff107d | ||
|
|
f317490eec | ||
|
|
cf13805973 | ||
|
|
389cb4d6e3 | ||
|
|
7a82d70391 | ||
|
|
f3b9966a2a | ||
|
|
5ba1c7fbb6 | ||
|
|
ad153bd733 | ||
|
|
42b3b16fe1 | ||
|
|
531dc02ca5 | ||
|
|
2156adb841 | ||
|
|
6caf7e962e | ||
|
|
97fbd7e0bd | ||
|
|
ead48b1907 | ||
|
|
1af4b2587e | ||
|
|
82fa50c092 | ||
|
|
58d8980cfa | ||
|
|
c111ebab0a | ||
|
|
943a15a967 | ||
|
|
d0aa6bcc8d | ||
|
|
04fd730a08 | ||
|
|
6156271c48 | ||
|
|
87666ba2e4 | ||
|
|
7915d07aff | ||
|
|
095e272191 | ||
|
|
42762ec806 | ||
|
|
bfb38af8e1 | ||
|
|
894695d13b | ||
|
|
a56a0726d4 | ||
|
|
c366852925 | ||
|
|
0cab15658f | ||
|
|
c3ecf8c5b2 | ||
|
|
81d453f7d3 | ||
|
|
262ad903cb | ||
|
|
ec60af0214 | ||
|
|
8cf8d3f69c | ||
|
|
034aeb4f17 | ||
|
|
d59efbc6e0 | ||
|
|
b753a491c9 | ||
|
|
9092b34dd1 | ||
|
|
0a3aaddca2 | ||
|
|
c55d976277 | ||
|
|
47ccddafee | ||
|
|
dcaf813657 | ||
|
|
ef727aaaaf | ||
|
|
ee17233aa0 | ||
|
|
f88294a9e6 | ||
|
|
572da2084d | ||
|
|
88bf8f0aac | ||
|
|
9b59e02374 | ||
|
|
ba290a32d2 | ||
|
|
420c2ceb6f | ||
|
|
ec786f2fd9 | ||
|
|
0c4020df7d | ||
|
|
204cb8f2ac | ||
|
|
0e421b5327 | ||
|
|
dc2dc306d5 | ||
|
|
1092bcd1a1 | ||
|
|
73dbc6f8cb | ||
|
|
3e9fb10c11 | ||
|
|
3588ed6341 | ||
|
|
26326c3ef0 | ||
|
|
7514d94262 | ||
|
|
1df577f0fc | ||
|
|
70f4707063 | ||
|
|
521146d64e | ||
|
|
091b402594 | ||
|
|
24b617a427 | ||
|
|
97473bb8d8 | ||
|
|
ed35643c3e | ||
|
|
2fb3b52c7e | ||
|
|
9e70993d57 | ||
|
|
6887e2cff9 | ||
|
|
ba38680402 | ||
|
|
06b00837fc | ||
|
|
a033bc729c | ||
|
|
6f8486e0a2 | ||
|
|
53c55b13e7 | ||
|
|
365e7cb112 | ||
|
|
e5d6327de9 | ||
|
|
d86ffe7130 | ||
|
|
d9fd563812 | ||
|
|
9e548718e5 | ||
|
|
83720793fb | ||
|
|
188326b10e | ||
|
|
ff90190660 | ||
|
|
1d9f8b5f7c | ||
|
|
2454aa2eb0 | ||
|
|
df5f6089af | ||
|
|
5738547288 | ||
|
|
8274b660c6 | ||
|
|
a8f1a65d62 | ||
|
|
9c275dd86a | ||
|
|
1fbd65a702 | ||
|
|
31b0e825b5 | ||
|
|
973d8339b3 | ||
|
|
c8db6cb731 | ||
|
|
ecbc192fc4 | ||
|
|
76d365314a | ||
|
|
830a3c2e04 | ||
|
|
9c432c7e50 | ||
|
|
6f7f45d0a8 | ||
|
|
8e352feb6a | ||
|
|
57a934d14b | ||
|
|
f9d7ccd33e | ||
|
|
265a158fe7 | ||
|
|
c65ff0e7b3 | ||
|
|
066752aa0b | ||
|
|
9a5214888b | ||
|
|
609d8e35be | ||
|
|
ce14de8251 | ||
|
|
574b22a62a | ||
|
|
1167b9ea4e | ||
|
|
77ae99bfda | ||
|
|
8851571ba7 | ||
|
|
f71d28d10e | ||
|
|
3355bd01eb | ||
|
|
17afa82bf4 | ||
|
|
f98684e8aa | ||
|
|
5a85e88a39 | ||
|
|
d2368eb2c4 | ||
|
|
3a46616ba6 | ||
|
|
37665581e0 | ||
|
|
57df0c6921 | ||
|
|
ddb29745a9 | ||
|
|
ac7c9244cc | ||
|
|
6da7cd0ea5 | ||
|
|
24df79b75f | ||
|
|
0800b99214 | ||
|
|
3dad89df3f | ||
|
|
5c9b0b4ccb | ||
|
|
5a848d277e | ||
|
|
c88c738df9 | ||
|
|
9a8067d8e7 | ||
|
|
787c6bdb77 | ||
|
|
c3298c25a3 | ||
|
|
2f4c803678 | ||
|
|
60145983bf | ||
|
|
b5c27f99d1 |
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
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,11 +1,17 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
.tox
|
.tox
|
||||||
|
.cache
|
||||||
|
.pytest_cache/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
*.egg/
|
*.egg/
|
||||||
.coverage
|
.coverage
|
||||||
|
coverage.xml
|
||||||
|
htmlcov/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
pytestdebug.log
|
pytestdebug.log
|
||||||
|
pip-wheel-metadata/
|
||||||
|
.python-version
|
||||||
|
|
||||||
fixtures/
|
fixtures/
|
||||||
/docs/_build
|
/docs/_build
|
||||||
|
|||||||
68
.travis.yml
68
.travis.yml
@@ -1,48 +1,44 @@
|
|||||||
|
dist: xenial
|
||||||
language: python
|
language: python
|
||||||
sudo: false
|
|
||||||
before_install: openssl version
|
before_install: openssl version
|
||||||
env:
|
env:
|
||||||
global:
|
# global:
|
||||||
- secure: AifoKzwhjV94cmcQZrdQmqRu/9rkZZvWpwBv1daeAQpLOKFPGsOm3D+x2cSw9+iCfkgDZDfqQVv1kCaFVxTll8v8jTq5SJdqEY0NmGWbj/UkNtShh609oRDsuzLxAEwtVKYjf/h8K2BRea+bl1tGkwZ2vtmYS6dxNlAijjWOfds=
|
# - secure: AifoKzwhjV94cmcQZrdQmqRu/9rkZZvWpwBv1daeAQpLOKFPGsOm3D+x2cSw9+iCfkgDZDfqQVv1kCaFVxTll8v8jTq5SJdqEY0NmGWbj/UkNtShh609oRDsuzLxAEwtVKYjf/h8K2BRea+bl1tGkwZ2vtmYS6dxNlAijjWOfds=
|
||||||
- secure: LBSEg/gMj4u4Hrpo3zs6Y/1mTpd2RtcN49mZIFgTdbJ9IhpiNPqcEt647Lz94F9Eses2x2WbNuKqZKZZReY7QLbEzU1m0nN5jlaKrjcG5NR5clNABfFFyhgc0jBikyS4abAG8jc2efeaTrFuQwdoF4sE8YiVrkiVj2X5Xoi6sBk=
|
# - secure: LBSEg/gMj4u4Hrpo3zs6Y/1mTpd2RtcN49mZIFgTdbJ9IhpiNPqcEt647Lz94F9Eses2x2WbNuKqZKZZReY7QLbEzU1m0nN5jlaKrjcG5NR5clNABfFFyhgc0jBikyS4abAG8jc2efeaTrFuQwdoF4sE8YiVrkiVj2X5Xoi6sBk=
|
||||||
matrix:
|
matrix:
|
||||||
- TOX_SUFFIX="flakes"
|
- TOX_SUFFIX="requests"
|
||||||
- TOX_SUFFIX="requests22"
|
|
||||||
- TOX_SUFFIX="requests23"
|
|
||||||
- TOX_SUFFIX="requests24"
|
|
||||||
- TOX_SUFFIX="requests25"
|
|
||||||
- TOX_SUFFIX="requests26"
|
|
||||||
- TOX_SUFFIX="requests27"
|
|
||||||
- TOX_SUFFIX="requests1"
|
|
||||||
- TOX_SUFFIX="httplib2"
|
- TOX_SUFFIX="httplib2"
|
||||||
- TOX_SUFFIX="boto"
|
- TOX_SUFFIX="boto3"
|
||||||
- TOX_SUFFIX="urllib317"
|
- TOX_SUFFIX="urllib3"
|
||||||
- TOX_SUFFIX="urllib319"
|
|
||||||
- TOX_SUFFIX="urllib3110"
|
|
||||||
- TOX_SUFFIX="tornado3"
|
|
||||||
- TOX_SUFFIX="tornado4"
|
- TOX_SUFFIX="tornado4"
|
||||||
|
- TOX_SUFFIX="aiohttp"
|
||||||
matrix:
|
matrix:
|
||||||
|
include:
|
||||||
|
# Only run lint on a single 3.x
|
||||||
|
- env: TOX_SUFFIX="lint"
|
||||||
|
python: "3.7"
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- env: TOX_SUFFIX="boto"
|
- env: TOX_SUFFIX="aiohttp"
|
||||||
|
python: "pypy3"
|
||||||
|
- python: "3.8-dev"
|
||||||
exclude:
|
exclude:
|
||||||
- env: TOX_SUFFIX="boto"
|
# Exclude aiohttp support
|
||||||
python: 3.3
|
- env: TOX_SUFFIX="aiohttp"
|
||||||
- env: TOX_SUFFIX="boto"
|
python: "2.7"
|
||||||
python: 3.4
|
- env: TOX_SUFFIX="aiohttp"
|
||||||
- env: TOX_SUFFIX="requests1"
|
python: "pypy"
|
||||||
python: 3.4
|
|
||||||
- env: TOX_SUFFIX="requests1"
|
|
||||||
python: 3.5
|
|
||||||
python:
|
python:
|
||||||
- 2.6
|
- "2.7"
|
||||||
- 2.7
|
- "3.5"
|
||||||
- 3.3
|
- "3.6"
|
||||||
- 3.4
|
- "3.7"
|
||||||
- 3.5
|
- "3.8-dev"
|
||||||
- pypy
|
- "pypy"
|
||||||
- pypy3
|
- "pypy3"
|
||||||
install:
|
install:
|
||||||
- pip install tox-travis
|
- pip install tox-travis codecov
|
||||||
- if [[ $TOX_SUFFIX != 'flakes' ]]; then python setup.py install ; fi
|
- if [[ $TOX_SUFFIX != 'lint' ]]; then python setup.py install ; fi
|
||||||
script:
|
script:
|
||||||
- tox -e "${TOX_SUFFIX}"
|
- tox -e "${TOX_SUFFIX}"
|
||||||
|
after_success:
|
||||||
|
- codecov
|
||||||
|
|||||||
18
README.rst
18
README.rst
@@ -1,4 +1,4 @@
|
|||||||
|PyPI| |Build Status| |Waffle Ready| |Gitter|
|
|PyPI| |Python versions| |Build Status| |CodeCov| |Gitter| |CodeStyleBlack|
|
||||||
|
|
||||||
VCR.py
|
VCR.py
|
||||||
======
|
======
|
||||||
@@ -13,7 +13,7 @@ Source code
|
|||||||
https://github.com/kevin1024/vcrpy
|
https://github.com/kevin1024/vcrpy
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
https://vcrpy.readthedocs.org/
|
https://vcrpy.readthedocs.io/
|
||||||
|
|
||||||
Rationale
|
Rationale
|
||||||
---------
|
---------
|
||||||
@@ -48,11 +48,17 @@ This library uses the MIT license. See `LICENSE.txt <LICENSE.txt>`__ for
|
|||||||
more details
|
more details
|
||||||
|
|
||||||
.. |PyPI| image:: https://img.shields.io/pypi/v/vcrpy.svg
|
.. |PyPI| image:: https://img.shields.io/pypi/v/vcrpy.svg
|
||||||
:target: https://pypi.python.org/pypi/vcrpy-unittest
|
:target: https://pypi.python.org/pypi/vcrpy
|
||||||
.. |Build Status| image:: https://secure.travis-ci.org/kevin1024/vcrpy.png?branch=master
|
.. |Python versions| image:: https://img.shields.io/pypi/pyversions/vcrpy.svg
|
||||||
|
: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
|
:target: http://travis-ci.org/kevin1024/vcrpy
|
||||||
.. |Waffle Ready| image:: https://badge.waffle.io/kevin1024/vcrpy.png?label=ready&title=waffle
|
|
||||||
:target: https://waffle.io/kevin1024/vcrpy
|
|
||||||
.. |Gitter| image:: https://badges.gitter.im/Join%20Chat.svg
|
.. |Gitter| image:: https://badges.gitter.im/Join%20Chat.svg
|
||||||
:alt: Join the chat at https://gitter.im/kevin1024/vcrpy
|
: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
|
: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):
|
def my_matcher(r1, r2):
|
||||||
|
|
||||||
Your method receives the two requests and must return ``True`` if they
|
Your method receives the two requests and can either:
|
||||||
match, ``False`` if they don't.
|
|
||||||
|
- 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.
|
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
|
import vcr
|
||||||
|
|
||||||
def jurassic_matcher(r1, r2):
|
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 = vcr.VCR()
|
||||||
my_vcr.register_matcher('jurassic', jurassic_matcher)
|
my_vcr.register_matcher('jurassic', jurassic_matcher)
|
||||||
@@ -122,6 +127,27 @@ Finally, register your method with VCR to use your new request matcher.
|
|||||||
with my_vcr.use_cassette('test.yml'):
|
with my_vcr.use_cassette('test.yml'):
|
||||||
# your http here
|
# your http here
|
||||||
|
|
||||||
|
Register your own cassette persister
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
Create your own persistence class, see the :ref:`persister_example`.
|
||||||
|
|
||||||
|
Your custom persister must implement both ``load_cassette`` and ``save_cassette``
|
||||||
|
methods. The ``load_cassette`` method must return a deserialized cassette or raise
|
||||||
|
``ValueError`` if no cassette is found.
|
||||||
|
|
||||||
|
Once the persister class is defined, register with VCR like so...
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import vcr
|
||||||
|
my_vcr = vcr.VCR()
|
||||||
|
|
||||||
|
class CustomerPersister(object):
|
||||||
|
# implement Persister methods...
|
||||||
|
|
||||||
|
my_vcr.register_persister(CustomPersister)
|
||||||
|
|
||||||
Filter sensitive data from the request
|
Filter sensitive data from the request
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
@@ -200,24 +226,25 @@ Custom Request filtering
|
|||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
If none of these covers your request filtering needs, you can register a
|
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
|
callback with the ``before_record_request`` configuration option to
|
||||||
cassette. Use the ``before_record`` configuration option to so this.
|
manipulate the HTTP request before adding it to the cassette, or return
|
||||||
Here is an example that will never record requests to the /login
|
``None`` to ignore it entirely. Here is an example that will never record
|
||||||
endpoint.
|
requests to the ``'/login'`` path:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
def before_record_cb(request):
|
def before_record_cb(request):
|
||||||
if request.path != '/login':
|
if request.path == '/login':
|
||||||
return request
|
return None
|
||||||
|
return request
|
||||||
|
|
||||||
my_vcr = vcr.VCR(
|
my_vcr = vcr.VCR(
|
||||||
before_record = before_record_cb,
|
before_record_request=before_record_cb,
|
||||||
)
|
)
|
||||||
with my_vcr.use_cassette('test.yml'):
|
with my_vcr.use_cassette('test.yml'):
|
||||||
# your http code here
|
# 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'``
|
could remove all query parameters from any requests to the ``'/login'``
|
||||||
path.
|
path.
|
||||||
|
|
||||||
@@ -225,11 +252,11 @@ path.
|
|||||||
|
|
||||||
def scrub_login_request(request):
|
def scrub_login_request(request):
|
||||||
if request.path == '/login':
|
if request.path == '/login':
|
||||||
request.uri, _ = urllib.splitquery(response.uri)
|
request.uri, _ = urllib.splitquery(request.uri)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
my_vcr = vcr.VCR(
|
my_vcr = vcr.VCR(
|
||||||
before_record=scrub_login_request,
|
before_record_request=scrub_login_request,
|
||||||
)
|
)
|
||||||
with my_vcr.use_cassette('test.yml'):
|
with my_vcr.use_cassette('test.yml'):
|
||||||
# your http code here
|
# your http code here
|
||||||
@@ -237,9 +264,12 @@ path.
|
|||||||
Custom Response Filtering
|
Custom Response Filtering
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
VCR.py also suports response filtering with the
|
You can also do response filtering with the
|
||||||
``before_record_response`` keyword argument. It's usage is similar to
|
``before_record_response`` configuration option. Its usage is
|
||||||
that of ``before_record``:
|
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
|
.. code:: python
|
||||||
|
|
||||||
@@ -281,8 +311,8 @@ in a few ways:
|
|||||||
or 0.0.0.0.
|
or 0.0.0.0.
|
||||||
- Set the ``ignore_hosts`` configuration option to a list of hosts to
|
- Set the ``ignore_hosts`` configuration option to a list of hosts to
|
||||||
ignore
|
ignore
|
||||||
- Add a ``before_record`` callback that returns None for requests you
|
- Add a ``before_record_request`` or ``before_record_response`` callback
|
||||||
want to ignore
|
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
|
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
|
played back from a cassette. VCR will completely ignore those requests
|
||||||
@@ -343,3 +373,16 @@ cassette names, use ``VCR.ensure_suffix`` as follows:
|
|||||||
|
|
||||||
@my_vcr.use_cassette
|
@my_vcr.use_cassette
|
||||||
def my_test_function():
|
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,53 @@
|
|||||||
Changelog
|
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)
|
||||||
|
Fix requests stub when using proxy (thanks @samuelfekete @daneoshiga)
|
||||||
|
(only for aiohttp stub) Drop support to python 3.4 asyncio.coroutine (aiohttp doesn't support python it anymore)
|
||||||
|
Fix aiohttp stub to work with aiohttp client (thanks @stj)
|
||||||
|
Fix aiohttp stub to accept content type passed
|
||||||
|
Improve docs (thanks @adamchainz)
|
||||||
|
- 1.13.0 - Fix support to latest aiohttp version (3.3.2). Fix content-type bug in aiohttp stub. Save URL with query params properly when using aiohttp.
|
||||||
|
- 1.12.0 - Fix support to latest aiohttp version (3.2.1), Adapted setup to PEP508, Support binary responses on aiohttp, Dropped support for EOL python versions (2.6 and 3.3)
|
||||||
|
- 1.11.1 Fix compatibility with newest requests and urllib3 releases
|
||||||
|
- 1.11.0 Allow injection of persistence methods + bugfixes (thanks @j-funk and @IvanMalison),
|
||||||
|
Support python 3.6 + CI tests (thanks @derekbekoe and @graingert),
|
||||||
|
Support pytest-asyncio coroutines (thanks @graingert)
|
||||||
|
- 1.10.5 Added a fix to httplib2 (thanks @carlosds730), Fix an issue with
|
||||||
|
aiohttp (thanks @madninja), Add missing requirement yarl (thanks @lamenezes),
|
||||||
|
Remove duplicate mock triple (thanks @FooBarQuaxx)
|
||||||
|
- 1.10.4 Fix an issue with asyncio aiohttp (thanks @madninja)
|
||||||
|
- 1.10.3 Fix some issues with asyncio and params (thanks @anovikov1984 and
|
||||||
|
@lamenezes), Fix some issues with cassette serialize / deserialize and empty
|
||||||
|
response bodies (thanks @gRoussac and @dz0ny)
|
||||||
|
- 1.10.2 Fix 1.10.1 release - add aiohttp support back in
|
||||||
|
- 1.10.1 [bad release] Fix build for Fedora package + python2 (thanks @puiterwijk and @lamenezes)
|
||||||
|
- 1.10.0 Add support for aiohttp (thanks @lamenezes)
|
||||||
|
- 1.9.0 Add support for boto3 (thanks @desdm, @foorbarna). Fix deepcopy issue
|
||||||
|
for response headers when `decode_compressed_response` is enabled (thanks
|
||||||
|
@nickdirienzo)
|
||||||
- 1.8.0 Fix for Serialization errors with JSON adapter (thanks
|
- 1.8.0 Fix for Serialization errors with JSON adapter (thanks
|
||||||
@aliaksandrb). Avoid concatenating bytes with strings (thanks
|
@aliaksandrb). Avoid concatenating bytes with strings (thanks
|
||||||
@jaysonsantos). Exclude __pycache__ dirs & compiled files in sdist
|
@jaysonsantos). Exclude __pycache__ dirs & compiled files in sdist
|
||||||
|
|||||||
167
docs/conf.py
167
docs/conf.py
@@ -17,50 +17,45 @@ import os
|
|||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# 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
|
# 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.
|
# 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 ------------------------------------------------
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# 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
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.coverage", "sphinx.ext.viewcode"]
|
||||||
'sphinx.ext.autodoc',
|
|
||||||
'sphinx.ext.intersphinx',
|
|
||||||
'sphinx.ext.coverage',
|
|
||||||
'sphinx.ext.viewcode',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
# The suffix(es) of source filenames.
|
# The suffix(es) of source filenames.
|
||||||
# You can specify multiple suffix as a list of string:
|
# You can specify multiple suffix as a list of string:
|
||||||
# source_suffix = ['.rst', '.md']
|
# source_suffix = ['.rst', '.md']
|
||||||
source_suffix = '.rst'
|
source_suffix = ".rst"
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
#source_encoding = 'utf-8-sig'
|
# source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = "index"
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'vcrpy'
|
project = u"vcrpy"
|
||||||
copyright = u'2015, Kevin McCarthy'
|
copyright = u"2015, Kevin McCarthy"
|
||||||
author = u'Kevin McCarthy'
|
author = u"Kevin McCarthy"
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '1.7.4'
|
version = "1.7.4"
|
||||||
# The full version, including alpha/beta/rc tags.
|
# 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
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# 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
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
# non-false value, then it is used:
|
# non-false value, then it is used:
|
||||||
#today = ''
|
# today = ''
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
# 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
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# 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
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
# documents.
|
# documents.
|
||||||
#default_role = None
|
# default_role = None
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# 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
|
# If true, the current module name will be prepended to all description
|
||||||
# unit titles (such as .. function::).
|
# unit titles (such as .. function::).
|
||||||
#add_module_names = True
|
# add_module_names = True
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
# output. They are ignored by default.
|
# output. They are ignored by default.
|
||||||
#show_authors = False
|
# show_authors = False
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# 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.
|
# 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.
|
# 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.
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
todo_include_todos = False
|
todo_include_todos = False
|
||||||
@@ -110,157 +105,149 @@ todo_include_todos = False
|
|||||||
# -- Options for HTML output ----------------------------------------------
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages.
|
# The theme to use for HTML and HTML Help pages.
|
||||||
# https://read-the-docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs
|
# 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
|
import sphinx_rtd_theme
|
||||||
html_theme = 'sphinx_rtd_theme'
|
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
||||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# 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
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#html_theme_options = {}
|
# html_theme_options = {}
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
# "<project> v<release> documentation".
|
# "<project> v<release> documentation".
|
||||||
#html_title = None
|
# html_title = None
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
# 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
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# 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
|
# 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
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
# pixels large.
|
# pixels large.
|
||||||
#html_favicon = None
|
# html_favicon = None
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# 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,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# 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
|
# Add any extra paths that contain custom files (such as robots.txt or
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
# .htaccess) here, relative to this directory. These files are copied
|
||||||
# directly to the root of the documentation.
|
# 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,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# 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
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
# typographically correct entities.
|
# typographically correct entities.
|
||||||
#html_use_smartypants = True
|
# html_use_smartypants = True
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
# 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
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
# template names.
|
# template names.
|
||||||
#html_additional_pages = {}
|
# html_additional_pages = {}
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#html_domain_indices = True
|
# html_domain_indices = True
|
||||||
|
|
||||||
# If false, no index is generated.
|
# 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.
|
# 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.
|
# 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.
|
# 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.
|
# 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
|
# 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
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
# base URL from which the finished HTML is served.
|
# 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").
|
# 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.
|
# Language to be used for generating the HTML full-text search index.
|
||||||
# Sphinx supports the following languages:
|
# Sphinx supports the following languages:
|
||||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
# '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.
|
# A dictionary with options for the search language support, empty by default.
|
||||||
# Now only 'ja' uses this config value
|
# 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
|
# The name of a javascript file (relative to the configuration directory) that
|
||||||
# implements a search results scorer. If empty, the default will be used.
|
# 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.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'vcrpydoc'
|
htmlhelp_basename = "vcrpydoc"
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
|
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
#'papersize': 'letterpaper',
|
#'papersize': 'letterpaper',
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
#'pointsize': '10pt',
|
||||||
#'pointsize': '10pt',
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#'preamble': '',
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Latex figure (float) alignment
|
||||||
#'preamble': '',
|
#'figure_align': 'htbp',
|
||||||
|
|
||||||
# Latex figure (float) alignment
|
|
||||||
#'figure_align': 'htbp',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title,
|
# (source start file, target name, title,
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [(master_doc, "vcrpy.tex", u"vcrpy Documentation", u"Kevin McCarthy", "manual")]
|
||||||
(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 name of an image file (relative to this directory) to place at the top of
|
||||||
# the title page.
|
# the title page.
|
||||||
#latex_logo = None
|
# latex_logo = None
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
# not chapters.
|
# not chapters.
|
||||||
#latex_use_parts = False
|
# latex_use_parts = False
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
# If true, show page references after internal links.
|
||||||
#latex_show_pagerefs = False
|
# latex_show_pagerefs = False
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# 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.
|
# Documents to append as an appendix to all manuals.
|
||||||
#latex_appendices = []
|
# latex_appendices = []
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#latex_domain_indices = True
|
# latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
# -- Options for manual page output ---------------------------------------
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [(master_doc, "vcrpy", u"vcrpy Documentation", [author], 1)]
|
||||||
(master_doc, 'vcrpy', u'vcrpy Documentation',
|
|
||||||
[author], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
#man_show_urls = False
|
# man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
# -- Options for Texinfo output -------------------------------------------
|
||||||
@@ -269,23 +256,29 @@ man_pages = [
|
|||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
(master_doc, 'vcrpy', u'vcrpy Documentation',
|
(
|
||||||
author, 'vcrpy', 'One line description of project.',
|
master_doc,
|
||||||
'Miscellaneous'),
|
"vcrpy",
|
||||||
|
u"vcrpy Documentation",
|
||||||
|
author,
|
||||||
|
"vcrpy",
|
||||||
|
"One line description of project.",
|
||||||
|
"Miscellaneous",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
#texinfo_appendices = []
|
# texinfo_appendices = []
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#texinfo_domain_indices = True
|
# texinfo_domain_indices = True
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
# 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.
|
# 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.
|
# 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,
|
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::
|
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
|
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
|
``test_gzip`` in the test suite, and only in the python 2.7 environment
|
||||||
@@ -19,7 +22,60 @@ that has ``requests`` installed.
|
|||||||
|
|
||||||
Also, in order for the boto tests to run, you will need an AWS key.
|
Also, in order for the boto tests to run, you will need an AWS key.
|
||||||
Refer to the `boto
|
Refer to the `boto
|
||||||
documentation <http://boto.readthedocs.org/en/latest/getting_started.html>`__
|
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
|
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
|
Travis so you don't have to worry about them failing if you submit a
|
||||||
pull request.
|
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
|
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
|
which matchers didn't match. This can help you with debugging custom
|
||||||
matchers.
|
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,18 +9,20 @@ with pip::
|
|||||||
Compatibility
|
Compatibility
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
VCR.py supports Python 2.6 and 2.7, 3.3, 3.4, and
|
VCR.py supports Python 2.7 and 3.5+, and
|
||||||
`pypy <http://pypy.org>`__.
|
`pypy <http://pypy.org>`__.
|
||||||
|
|
||||||
The following http libraries are supported:
|
The following HTTP libraries are supported:
|
||||||
|
|
||||||
- urllib2
|
- ``aiohttp``
|
||||||
- urllib3
|
- ``boto``
|
||||||
- http.client (python3)
|
- ``boto3``
|
||||||
- requests (both 1.x and 2.x versions)
|
- ``http.client``
|
||||||
- httplib2
|
- ``httplib2``
|
||||||
- boto
|
- ``requests`` (both 1.x and 2.x versions)
|
||||||
- Tornado's AsyncHTTPClient
|
- ``tornado.httpclient``
|
||||||
|
- ``urllib2``
|
||||||
|
- ``urllib3``
|
||||||
|
|
||||||
Speed
|
Speed
|
||||||
-----
|
-----
|
||||||
@@ -40,7 +42,7 @@ rebuilding pyyaml.
|
|||||||
|
|
||||||
brew install libyaml # Mac with Homebrew
|
brew install libyaml # Mac with Homebrew
|
||||||
apt-get install libyaml-dev # Ubuntu
|
apt-get install libyaml-dev # Ubuntu
|
||||||
dnf install libyaml-dev # Fedora
|
dnf install libyaml-devel # Fedora
|
||||||
|
|
||||||
3. Rebuild pyyaml with libyaml::
|
3. Rebuild pyyaml with libyaml::
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Usage
|
|||||||
assert 'Example domains' in response
|
assert 'Example domains' in response
|
||||||
|
|
||||||
Run this test once, and VCR.py will record the HTTP request to
|
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
|
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
|
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
|
(the test will continue to pass, even if you are offline, or iana.org
|
||||||
@@ -57,7 +57,7 @@ once
|
|||||||
file.
|
file.
|
||||||
|
|
||||||
It is similar to the new\_episodes record mode, but will prevent new,
|
It is similar to the new\_episodes record mode, but will prevent new,
|
||||||
unexpected requests from being made (i.e. because the request URI
|
unexpected requests from being made (e.g. because the request URI
|
||||||
changed).
|
changed).
|
||||||
|
|
||||||
once is the default record mode, used when you do not set one.
|
once is the default record mode, used when you do not set one.
|
||||||
@@ -95,3 +95,12 @@ Unittest Integration
|
|||||||
While it's possible to use the context manager or decorator forms with unittest,
|
While it's possible to use the context manager or decorator forms with unittest,
|
||||||
there's also a ``VCRTestCase`` provided separately by `vcrpy-unittest
|
there's also a ``VCRTestCase`` provided separately by `vcrpy-unittest
|
||||||
<https://github.com/agriffis/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
|
#!/bin/bash
|
||||||
|
|
||||||
REQUESTS_CA_BUNDLE=`python -m pytest_httpbin.certs` py.test $1
|
# 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 $*
|
||||||
|
|||||||
89
setup.py
89
setup.py
@@ -1,17 +1,14 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
|
||||||
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
from setuptools.command.test import test as TestCommand
|
from setuptools.command.test import test as TestCommand
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
long_description = open('README.rst', 'r').read()
|
long_description = open("README.rst", "r").read()
|
||||||
|
|
||||||
|
|
||||||
class PyTest(TestCommand):
|
class PyTest(TestCommand):
|
||||||
|
|
||||||
def finalize_options(self):
|
def finalize_options(self):
|
||||||
TestCommand.finalize_options(self)
|
TestCommand.finalize_options(self)
|
||||||
self.test_args = []
|
self.test_args = []
|
||||||
@@ -20,59 +17,53 @@ class PyTest(TestCommand):
|
|||||||
def run_tests(self):
|
def run_tests(self):
|
||||||
# import here, cause outside the eggs aren't loaded
|
# import here, cause outside the eggs aren't loaded
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
errno = pytest.main(self.test_args)
|
errno = pytest.main(self.test_args)
|
||||||
sys.exit(errno)
|
sys.exit(errno)
|
||||||
|
|
||||||
|
|
||||||
install_requires = ['PyYAML', 'wrapt', 'six>=1.5']
|
install_requires = [
|
||||||
|
"PyYAML",
|
||||||
|
"wrapt",
|
||||||
extras_require = {
|
"six>=1.5",
|
||||||
':python_version in "2.4, 2.5, 2.6"':
|
'contextlib2; python_version=="2.7"',
|
||||||
['contextlib2', 'backport_collections', 'mock'],
|
'mock; python_version=="2.7"',
|
||||||
':python_version in "2.7, 3.1, 3.2"': ['contextlib2', 'mock'],
|
'yarl; python_version>="3.6"',
|
||||||
}
|
'yarl<1.4; python_version=="3.5"',
|
||||||
|
]
|
||||||
|
|
||||||
try:
|
|
||||||
if 'bdist_wheel' not in sys.argv:
|
|
||||||
for key, value in extras_require.items():
|
|
||||||
if key.startswith(':') and pkg_resources.evaluate_marker(key[1:]):
|
|
||||||
install_requires.extend(value)
|
|
||||||
except Exception:
|
|
||||||
logging.getLogger(__name__).exception(
|
|
||||||
'Something went wrong calculating platform specific dependencies, so '
|
|
||||||
"you're getting them all!"
|
|
||||||
)
|
|
||||||
for key, value in extras_require.items():
|
|
||||||
if key.startswith(':'):
|
|
||||||
install_requires.extend(value)
|
|
||||||
|
|
||||||
|
excluded_packages = ["tests*"]
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
excluded_packages.append("vcr.stubs.aiohttp_stubs")
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='vcrpy',
|
name="vcrpy",
|
||||||
version='1.8.0',
|
version="2.1.1",
|
||||||
description=(
|
description=("Automatically mock your HTTP interactions to simplify and " "speed up testing"),
|
||||||
"Automatically mock your HTTP interactions to simplify and "
|
|
||||||
"speed up testing"
|
|
||||||
),
|
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
author='Kevin McCarthy',
|
author="Kevin McCarthy",
|
||||||
author_email='me@kevinmccarthy.org',
|
author_email="me@kevinmccarthy.org",
|
||||||
url='https://github.com/kevin1024/vcrpy',
|
url="https://github.com/kevin1024/vcrpy",
|
||||||
packages=find_packages(exclude=("tests*",)),
|
packages=find_packages(exclude=excluded_packages),
|
||||||
|
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
extras_require=extras_require,
|
license="MIT",
|
||||||
license='MIT',
|
tests_require=["pytest", "mock", "pytest-httpbin"],
|
||||||
tests_require=['pytest', 'mock', 'pytest-httpbin'],
|
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
"Development Status :: 5 - Production/Stable",
|
||||||
'Environment :: Console',
|
"Environment :: Console",
|
||||||
'Intended Audience :: Developers',
|
"Intended Audience :: Developers",
|
||||||
'Programming Language :: Python',
|
"Programming Language :: Python",
|
||||||
'Programming Language :: Python :: 3',
|
"Programming Language :: Python :: 2",
|
||||||
'Topic :: Software Development :: Testing',
|
"Programming Language :: Python :: 2.7",
|
||||||
'Topic :: Internet :: WWW/HTTP',
|
"Programming Language :: Python :: 3",
|
||||||
'License :: OSI Approved :: MIT License',
|
"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):
|
def assert_is_json(a_string):
|
||||||
try:
|
try:
|
||||||
json.loads(a_string.decode('utf-8'))
|
json.loads(a_string.decode("utf-8"))
|
||||||
except Exception:
|
except Exception:
|
||||||
assert False
|
assert False
|
||||||
assert True
|
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
|
method: GET
|
||||||
uri: http://httpbin.org/ip
|
uri: http://httpbin.org/ip
|
||||||
response:
|
response:
|
||||||
body: {string: !!python/unicode "{\n \"origin\": \"217.122.164.194\"\n}"}
|
body: {string: "{\n \"origin\": \"217.122.164.194\"\n}"}
|
||||||
headers:
|
headers:
|
||||||
access-control-allow-origin: ['*']
|
access-control-allow-origin: ['*']
|
||||||
content-type: [application/json]
|
content-type: [application/json]
|
||||||
|
|||||||
43
tests/integration/aiohttp_utils.py
Normal file
43
tests/integration/aiohttp_utils.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# flake8: noqa
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from aiohttp.test_utils import TestClient
|
||||||
|
|
||||||
|
|
||||||
|
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":
|
||||||
|
content = await response.text()
|
||||||
|
elif output == "json":
|
||||||
|
content_type = content_type or "application/json"
|
||||||
|
content = await response.json(encoding=encoding, content_type=content_type)
|
||||||
|
elif output == "raw":
|
||||||
|
content = await response.read()
|
||||||
|
elif output == "stream":
|
||||||
|
content = await response.content.read()
|
||||||
|
|
||||||
|
response_ctx._resp.close()
|
||||||
|
await session.close()
|
||||||
|
|
||||||
|
return response, content
|
||||||
|
|
||||||
|
|
||||||
|
def aiohttp_app():
|
||||||
|
async def hello(request):
|
||||||
|
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("/json", json)
|
||||||
|
app.router.add_get("/json/empty", json_empty_body)
|
||||||
|
return app
|
||||||
264
tests/integration/test_aiohttp.py
Normal file
264
tests/integration/test_aiohttp.py
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
import contextlib
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
asyncio = pytest.importorskip("asyncio")
|
||||||
|
aiohttp = pytest.importorskip("aiohttp")
|
||||||
|
|
||||||
|
import vcr # noqa: E402
|
||||||
|
from .aiohttp_utils import aiohttp_app, aiohttp_request # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def run_in_loop(fn):
|
||||||
|
with contextlib.closing(asyncio.new_event_loop()) as loop:
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
task = loop.create_task(fn(loop))
|
||||||
|
return loop.run_until_complete(task)
|
||||||
|
|
||||||
|
|
||||||
|
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 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."""
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
|
def test_status(tmpdir, scheme):
|
||||||
|
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:
|
||||||
|
cassette_response, _ = get(url)
|
||||||
|
assert cassette_response.status == response.status
|
||||||
|
assert cassette.play_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
@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:
|
||||||
|
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 "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"))):
|
||||||
|
_, response_text = get(url)
|
||||||
|
|
||||||
|
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"}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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")
|
||||||
|
|
||||||
|
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_stream(tmpdir, scheme):
|
||||||
|
url = scheme + "://httpbin.org/get"
|
||||||
|
|
||||||
|
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"}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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"}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
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"}
|
||||||
|
|
||||||
|
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)
|
||||||
|
request = cassette.requests[0]
|
||||||
|
assert request.url == url
|
||||||
|
assert cassette_response_json == response_json
|
||||||
|
assert cassette.play_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_aiohttp_test_client(aiohttp_client, tmpdir):
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
app = aiohttp_app()
|
||||||
|
url = "/"
|
||||||
|
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_text = loop.run_until_complete(response.text())
|
||||||
|
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:
|
||||||
|
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 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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''Basic tests for cassettes'''
|
"""Basic tests for cassettes"""
|
||||||
|
|
||||||
# External imports
|
# External imports
|
||||||
import os
|
import os
|
||||||
@@ -10,21 +10,21 @@ import vcr
|
|||||||
|
|
||||||
|
|
||||||
def test_nonexistent_directory(tmpdir, httpbin):
|
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
|
# 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
|
# 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()
|
urlopen(httpbin.url).read()
|
||||||
|
|
||||||
# This should have made the file and the directory
|
# 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):
|
def test_unpatch(tmpdir, httpbin):
|
||||||
'''Ensure that our cassette gets unpatched when we're done'''
|
"""Ensure that our cassette gets unpatched when we're done"""
|
||||||
with vcr.use_cassette(str(tmpdir.join('unpatch.yaml'))) as cass:
|
with vcr.use_cassette(str(tmpdir.join("unpatch.yaml"))) as cass:
|
||||||
urlopen(httpbin.url).read()
|
urlopen(httpbin.url).read()
|
||||||
|
|
||||||
# Make the same request, and assert that we haven't served any more
|
# 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):
|
def test_basic_json_use(tmpdir, httpbin):
|
||||||
'''
|
"""
|
||||||
Ensure you can load a json serialized cassette
|
Ensure you can load a json serialized cassette
|
||||||
'''
|
"""
|
||||||
test_fixture = str(tmpdir.join('synopsis.json'))
|
test_fixture = str(tmpdir.join("synopsis.json"))
|
||||||
with vcr.use_cassette(test_fixture, serializer='json'):
|
with vcr.use_cassette(test_fixture, serializer="json"):
|
||||||
response = urlopen(httpbin.url).read()
|
response = urlopen(httpbin.url).read()
|
||||||
assert b'difficult sometimes' in response
|
assert b"difficult sometimes" in response
|
||||||
|
|
||||||
|
|
||||||
def test_patched_content(tmpdir, httpbin):
|
def test_patched_content(tmpdir, httpbin):
|
||||||
'''
|
"""
|
||||||
Ensure that what you pull from a cassette is what came from the
|
Ensure that what you pull from a cassette is what came from the
|
||||||
request
|
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()
|
response = urlopen(httpbin.url).read()
|
||||||
assert cass.play_count == 0
|
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()
|
response2 = urlopen(httpbin.url).read()
|
||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
cass._save(force=True)
|
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()
|
response3 = urlopen(httpbin.url).read()
|
||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
|
|
||||||
@@ -66,12 +66,12 @@ def test_patched_content(tmpdir, httpbin):
|
|||||||
|
|
||||||
|
|
||||||
def test_patched_content_json(tmpdir, httpbin):
|
def test_patched_content_json(tmpdir, httpbin):
|
||||||
'''
|
"""
|
||||||
Ensure that what you pull from a json cassette is what came from the
|
Ensure that what you pull from a json cassette is what came from the
|
||||||
request
|
request
|
||||||
'''
|
"""
|
||||||
|
|
||||||
testfile = str(tmpdir.join('synopsis.json'))
|
testfile = str(tmpdir.join("synopsis.json"))
|
||||||
|
|
||||||
with vcr.use_cassette(testfile) as cass:
|
with vcr.use_cassette(testfile) as cass:
|
||||||
response = urlopen(httpbin.url).read()
|
response = urlopen(httpbin.url).read()
|
||||||
|
|||||||
@@ -1,77 +1,84 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
boto = pytest.importorskip("boto")
|
boto = pytest.importorskip("boto")
|
||||||
|
|
||||||
import boto # NOQA
|
import boto # NOQA
|
||||||
import boto.iam # NOQA
|
import boto.iam # NOQA
|
||||||
from boto.s3.connection import S3Connection # NOQA
|
from boto.s3.connection import S3Connection # NOQA
|
||||||
from boto.s3.key import Key # NOQA
|
from boto.s3.key import Key # NOQA
|
||||||
from ConfigParser import DuplicateSectionError # NOQA
|
|
||||||
import vcr # NOQA
|
import vcr # NOQA
|
||||||
|
|
||||||
|
try: # NOQA
|
||||||
|
from ConfigParser import DuplicateSectionError # NOQA
|
||||||
|
except ImportError: # NOQA
|
||||||
|
# python3
|
||||||
|
from configparser import DuplicateSectionError # NOQA
|
||||||
|
|
||||||
|
|
||||||
def test_boto_stubs(tmpdir):
|
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
|
# Perform the imports within the patched context so that
|
||||||
# CertValidatingHTTPSConnection refers to the patched version.
|
# CertValidatingHTTPSConnection refers to the patched version.
|
||||||
from boto.https_connection import CertValidatingHTTPSConnection
|
from boto.https_connection import CertValidatingHTTPSConnection
|
||||||
from vcr.stubs.boto_stubs import VCRCertValidatingHTTPSConnection
|
from vcr.stubs.boto_stubs import VCRCertValidatingHTTPSConnection
|
||||||
|
|
||||||
# Prove that the class was patched by the stub and that we can instantiate it.
|
# Prove that the class was patched by the stub and that we can instantiate it.
|
||||||
assert issubclass(CertValidatingHTTPSConnection, VCRCertValidatingHTTPSConnection)
|
assert issubclass(CertValidatingHTTPSConnection, VCRCertValidatingHTTPSConnection)
|
||||||
CertValidatingHTTPSConnection('hostname.does.not.matter')
|
CertValidatingHTTPSConnection("hostname.does.not.matter")
|
||||||
|
|
||||||
|
|
||||||
def test_boto_without_vcr():
|
def test_boto_without_vcr():
|
||||||
s3_conn = S3Connection()
|
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(s3_bucket)
|
||||||
k.key = 'test.txt'
|
k.key = "test.txt"
|
||||||
k.set_contents_from_string('hello world i am a string')
|
k.set_contents_from_string("hello world i am a string")
|
||||||
|
|
||||||
|
|
||||||
def test_boto_medium_difficulty(tmpdir):
|
def test_boto_medium_difficulty(tmpdir):
|
||||||
s3_conn = S3Connection()
|
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
|
||||||
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(s3_bucket)
|
||||||
k.key = 'test.txt'
|
k.key = "test.txt"
|
||||||
k.set_contents_from_string('hello world i am a string')
|
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(s3_bucket)
|
||||||
k.key = 'test.txt'
|
k.key = "test.txt"
|
||||||
k.set_contents_from_string('hello world i am a string')
|
k.set_contents_from_string("hello world i am a string")
|
||||||
|
|
||||||
|
|
||||||
def test_boto_hardcore_mode(tmpdir):
|
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_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(s3_bucket)
|
||||||
k.key = 'test.txt'
|
k.key = "test.txt"
|
||||||
k.set_contents_from_string('hello world i am a string')
|
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_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(s3_bucket)
|
||||||
k.key = 'test.txt'
|
k.key = "test.txt"
|
||||||
k.set_contents_from_string('hello world i am a string')
|
k.set_contents_from_string("hello world i am a string")
|
||||||
|
|
||||||
|
|
||||||
def test_boto_iam(tmpdir):
|
def test_boto_iam(tmpdir):
|
||||||
try:
|
try:
|
||||||
boto.config.add_section('Boto')
|
boto.config.add_section("Boto")
|
||||||
except DuplicateSectionError:
|
except DuplicateSectionError:
|
||||||
pass
|
pass
|
||||||
# Ensure that boto uses HTTPS
|
# Ensure that boto uses HTTPS
|
||||||
boto.config.set('Boto', 'is_secure', 'true')
|
boto.config.set("Boto", "is_secure", "true")
|
||||||
# Ensure that boto uses CertValidatingHTTPSConnection
|
# 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'))):
|
with vcr.use_cassette(str(tmpdir.join("boto-iam.yml"))):
|
||||||
iam_conn = boto.iam.connect_to_region('universal')
|
iam_conn = boto.iam.connect_to_region("universal")
|
||||||
iam_conn.get_all_users()
|
iam_conn.get_all_users()
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('boto-iam.yml'))):
|
with vcr.use_cassette(str(tmpdir.join("boto-iam.yml"))):
|
||||||
iam_conn = boto.iam.connect_to_region('universal')
|
iam_conn = boto.iam.connect_to_region("universal")
|
||||||
iam_conn.get_all_users()
|
iam_conn.get_all_users()
|
||||||
|
|||||||
122
tests/integration/test_boto3.py
Normal file
122
tests/integration/test_boto3.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
|
||||||
|
boto3 = pytest.importorskip("boto3")
|
||||||
|
|
||||||
|
import boto3 # NOQA
|
||||||
|
import botocore # NOQA
|
||||||
|
import vcr # NOQA
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
||||||
|
@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 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")
|
||||||
|
|
||||||
|
|
||||||
|
@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):
|
||||||
|
|
||||||
|
with vcr.use_cassette(str(tmpdir.join("boto3-medium.yml"))):
|
||||||
|
response = get_user()
|
||||||
|
assert response["User"]["UserName"] == IAM_USER_NAME
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@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:
|
||||||
|
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):
|
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'))):
|
with my_vcr.use_cassette(str(tmpdir.join("test.json"))):
|
||||||
assert my_vcr.serializer == 'json'
|
assert my_vcr.serializer == "json"
|
||||||
urlopen(httpbin.url + '/get')
|
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())
|
assert json.loads(f.read())
|
||||||
|
|
||||||
|
|
||||||
def test_default_set_cassette_library_dir(tmpdir, httpbin):
|
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'):
|
with my_vcr.use_cassette("test.json"):
|
||||||
urlopen(httpbin.url + '/get')
|
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):
|
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):
|
with my_vcr.use_cassette("test.json", cassette_library_dir=cld):
|
||||||
urlopen(httpbin.url + '/get')
|
urlopen(httpbin.url + "/get")
|
||||||
|
|
||||||
assert os.path.exists(str(tmpdir.join('subdir2').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')))
|
assert not os.path.exists(str(tmpdir.join("subdir").join("test.json")))
|
||||||
|
|
||||||
|
|
||||||
def test_override_match_on(tmpdir, httpbin):
|
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)
|
urlopen(httpbin.url)
|
||||||
|
|
||||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))) as cass:
|
with my_vcr.use_cassette(str(tmpdir.join("test.json"))) as cass:
|
||||||
urlopen(httpbin.url + '/get')
|
urlopen(httpbin.url + "/get")
|
||||||
|
|
||||||
assert len(cass) == 1
|
assert len(cass) == 1
|
||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
@@ -54,5 +54,5 @@ def test_missing_matcher():
|
|||||||
my_vcr = vcr.VCR()
|
my_vcr = vcr.VCR()
|
||||||
my_vcr.register_matcher("awesome", object)
|
my_vcr.register_matcher("awesome", object)
|
||||||
with pytest.raises(KeyError):
|
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
|
pass
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''Basic tests about save behavior'''
|
"""Basic tests about save behavior"""
|
||||||
|
|
||||||
# External imports
|
# External imports
|
||||||
import os
|
import os
|
||||||
@@ -11,11 +11,11 @@ import vcr
|
|||||||
|
|
||||||
|
|
||||||
def test_disk_saver_nowrite(tmpdir, httpbin):
|
def test_disk_saver_nowrite(tmpdir, httpbin):
|
||||||
'''
|
"""
|
||||||
Ensure that when you close a cassette without changing it it doesn't
|
Ensure that when you close a cassette without changing it it doesn't
|
||||||
rewrite the file
|
rewrite the file
|
||||||
'''
|
"""
|
||||||
fname = str(tmpdir.join('synopsis.yaml'))
|
fname = str(tmpdir.join("synopsis.yaml"))
|
||||||
with vcr.use_cassette(fname) as cass:
|
with vcr.use_cassette(fname) as cass:
|
||||||
urlopen(httpbin.url).read()
|
urlopen(httpbin.url).read()
|
||||||
assert cass.play_count == 0
|
assert cass.play_count == 0
|
||||||
@@ -31,11 +31,11 @@ def test_disk_saver_nowrite(tmpdir, httpbin):
|
|||||||
|
|
||||||
|
|
||||||
def test_disk_saver_write(tmpdir, httpbin):
|
def test_disk_saver_write(tmpdir, httpbin):
|
||||||
'''
|
"""
|
||||||
Ensure that when you close a cassette after changing it it does
|
Ensure that when you close a cassette after changing it it does
|
||||||
rewrite the file
|
rewrite the file
|
||||||
'''
|
"""
|
||||||
fname = str(tmpdir.join('synopsis.yaml'))
|
fname = str(tmpdir.join("synopsis.yaml"))
|
||||||
with vcr.use_cassette(fname) as cass:
|
with vcr.use_cassette(fname) as cass:
|
||||||
urlopen(httpbin.url).read()
|
urlopen(httpbin.url).read()
|
||||||
assert cass.play_count == 0
|
assert cass.play_count == 0
|
||||||
@@ -45,9 +45,9 @@ def test_disk_saver_write(tmpdir, httpbin):
|
|||||||
# the mtime doesn't change
|
# the mtime doesn't change
|
||||||
time.sleep(1)
|
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).read()
|
||||||
urlopen(httpbin.url + '/get').read()
|
urlopen(httpbin.url + "/get").read()
|
||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
assert cass.dirty
|
assert cass.dirty
|
||||||
last_mod2 = os.path.getmtime(fname)
|
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):
|
def _request_with_auth(url, username, password):
|
||||||
request = Request(url)
|
request = Request(url)
|
||||||
base64string = base64.b64encode(
|
base64string = base64.b64encode(username.encode("ascii") + b":" + password.encode("ascii"))
|
||||||
username.encode('ascii') + b':' + password.encode('ascii')
|
|
||||||
)
|
|
||||||
request.add_header(b"Authorization", b"Basic " + base64string)
|
request.add_header(b"Authorization", b"Basic " + base64string)
|
||||||
return urlopen(request)
|
return urlopen(request)
|
||||||
|
|
||||||
@@ -22,84 +20,84 @@ def _find_header(cassette, header):
|
|||||||
|
|
||||||
|
|
||||||
def test_filter_basic_auth(tmpdir, httpbin):
|
def test_filter_basic_auth(tmpdir, httpbin):
|
||||||
url = httpbin.url + '/basic-auth/user/passwd'
|
url = httpbin.url + "/basic-auth/user/passwd"
|
||||||
cass_file = str(tmpdir.join('basic_auth_filter.yaml'))
|
cass_file = str(tmpdir.join("basic_auth_filter.yaml"))
|
||||||
my_vcr = vcr.VCR(match_on=['uri', 'method', 'headers'])
|
my_vcr = vcr.VCR(match_on=["uri", "method", "headers"])
|
||||||
# 2 requests, one with auth failure and one with auth success
|
# 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):
|
with pytest.raises(HTTPError):
|
||||||
resp = _request_with_auth(url, 'user', 'wrongpasswd')
|
resp = _request_with_auth(url, "user", "wrongpasswd")
|
||||||
assert resp.getcode() == 401
|
assert resp.getcode() == 401
|
||||||
resp = _request_with_auth(url, 'user', 'passwd')
|
resp = _request_with_auth(url, "user", "passwd")
|
||||||
assert resp.getcode() == 200
|
assert resp.getcode() == 200
|
||||||
# make same 2 requests, this time both served from cassette.
|
# 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):
|
with pytest.raises(HTTPError):
|
||||||
resp = _request_with_auth(url, 'user', 'wrongpasswd')
|
resp = _request_with_auth(url, "user", "wrongpasswd")
|
||||||
assert resp.getcode() == 401
|
assert resp.getcode() == 401
|
||||||
resp = _request_with_auth(url, 'user', 'passwd')
|
resp = _request_with_auth(url, "user", "passwd")
|
||||||
assert resp.getcode() == 200
|
assert resp.getcode() == 200
|
||||||
# authorization header should not have been recorded
|
# authorization header should not have been recorded
|
||||||
assert not _find_header(cass, 'authorization')
|
assert not _find_header(cass, "authorization")
|
||||||
assert len(cass) == 2
|
assert len(cass) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_filter_querystring(tmpdir, httpbin):
|
def test_filter_querystring(tmpdir, httpbin):
|
||||||
url = httpbin.url + '/?foo=bar'
|
url = httpbin.url + "/?foo=bar"
|
||||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||||
with vcr.use_cassette(cass_file, filter_query_parameters=['foo']):
|
with vcr.use_cassette(cass_file, filter_query_parameters=["foo"]):
|
||||||
urlopen(url)
|
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)
|
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):
|
def test_filter_post_data(tmpdir, httpbin):
|
||||||
url = httpbin.url + '/post'
|
url = httpbin.url + "/post"
|
||||||
data = urlencode({'id': 'secret', 'foo': 'bar'}).encode('utf-8')
|
data = urlencode({"id": "secret", "foo": "bar"}).encode("utf-8")
|
||||||
cass_file = str(tmpdir.join('filter_pd.yaml'))
|
cass_file = str(tmpdir.join("filter_pd.yaml"))
|
||||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=['id']):
|
with vcr.use_cassette(cass_file, filter_post_data_parameters=["id"]):
|
||||||
urlopen(url, data)
|
urlopen(url, data)
|
||||||
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
|
assert b"id=secret" not in cass.requests[0].body
|
||||||
|
|
||||||
|
|
||||||
def test_filter_json_post_data(tmpdir, httpbin):
|
def test_filter_json_post_data(tmpdir, httpbin):
|
||||||
data = json.dumps({'id': 'secret', 'foo': 'bar'}).encode('utf-8')
|
data = json.dumps({"id": "secret", "foo": "bar"}).encode("utf-8")
|
||||||
request = Request(httpbin.url + '/post', data=data)
|
request = Request(httpbin.url + "/post", data=data)
|
||||||
request.add_header('Content-Type', 'application/json')
|
request.add_header("Content-Type", "application/json")
|
||||||
|
|
||||||
cass_file = str(tmpdir.join('filter_jpd.yaml'))
|
cass_file = str(tmpdir.join("filter_jpd.yaml"))
|
||||||
with vcr.use_cassette(cass_file, filter_post_data_parameters=['id']):
|
with vcr.use_cassette(cass_file, filter_post_data_parameters=["id"]):
|
||||||
urlopen(request)
|
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
|
assert b'"id": "secret"' not in cass.requests[0].body
|
||||||
|
|
||||||
|
|
||||||
def test_filter_callback(tmpdir, httpbin):
|
def test_filter_callback(tmpdir, httpbin):
|
||||||
url = httpbin.url + '/get'
|
url = httpbin.url + "/get"
|
||||||
cass_file = str(tmpdir.join('basic_auth_filter.yaml'))
|
cass_file = str(tmpdir.join("basic_auth_filter.yaml"))
|
||||||
|
|
||||||
def before_record_cb(request):
|
def before_record_cb(request):
|
||||||
if request.path != '/get':
|
if request.path != "/get":
|
||||||
return request
|
return request
|
||||||
|
|
||||||
# Test the legacy keyword.
|
# Test the legacy keyword.
|
||||||
my_vcr = vcr.VCR(before_record=before_record_cb)
|
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)
|
urlopen(url)
|
||||||
assert len(cass) == 0
|
assert len(cass) == 0
|
||||||
|
|
||||||
my_vcr = vcr.VCR(before_record_request=before_record_cb)
|
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)
|
urlopen(url)
|
||||||
assert len(cass) == 0
|
assert len(cass) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_decompress_gzip(tmpdir, httpbin):
|
def test_decompress_gzip(tmpdir, httpbin):
|
||||||
url = httpbin.url + '/gzip'
|
url = httpbin.url + "/gzip"
|
||||||
request = Request(url, headers={'Accept-Encoding': ['gzip, deflate']})
|
request = Request(url, headers={"Accept-Encoding": ["gzip, deflate"]})
|
||||||
cass_file = str(tmpdir.join('gzip_response.yaml'))
|
cass_file = str(tmpdir.join("gzip_response.yaml"))
|
||||||
with vcr.use_cassette(cass_file, decode_compressed_response=True):
|
with vcr.use_cassette(cass_file, decode_compressed_response=True):
|
||||||
urlopen(request)
|
urlopen(request)
|
||||||
with vcr.use_cassette(cass_file) as cass:
|
with vcr.use_cassette(cass_file) as cass:
|
||||||
@@ -109,9 +107,9 @@ def test_decompress_gzip(tmpdir, httpbin):
|
|||||||
|
|
||||||
|
|
||||||
def test_decompress_deflate(tmpdir, httpbin):
|
def test_decompress_deflate(tmpdir, httpbin):
|
||||||
url = httpbin.url + '/deflate'
|
url = httpbin.url + "/deflate"
|
||||||
request = Request(url, headers={'Accept-Encoding': ['gzip, deflate']})
|
request = Request(url, headers={"Accept-Encoding": ["gzip, deflate"]})
|
||||||
cass_file = str(tmpdir.join('deflate_response.yaml'))
|
cass_file = str(tmpdir.join("deflate_response.yaml"))
|
||||||
with vcr.use_cassette(cass_file, decode_compressed_response=True):
|
with vcr.use_cassette(cass_file, decode_compressed_response=True):
|
||||||
urlopen(request)
|
urlopen(request)
|
||||||
with vcr.use_cassette(cass_file) as cass:
|
with vcr.use_cassette(cass_file) as cass:
|
||||||
@@ -122,8 +120,8 @@ def test_decompress_deflate(tmpdir, httpbin):
|
|||||||
|
|
||||||
def test_decompress_regular(tmpdir, httpbin):
|
def test_decompress_regular(tmpdir, httpbin):
|
||||||
"""Test that it doesn't try to decompress content that isn't compressed"""
|
"""Test that it doesn't try to decompress content that isn't compressed"""
|
||||||
url = httpbin.url + '/get'
|
url = httpbin.url + "/get"
|
||||||
cass_file = str(tmpdir.join('noncompressed_response.yaml'))
|
cass_file = str(tmpdir.join("noncompressed_response.yaml"))
|
||||||
with vcr.use_cassette(cass_file, decode_compressed_response=True):
|
with vcr.use_cassette(cass_file, decode_compressed_response=True):
|
||||||
urlopen(url)
|
urlopen(url)
|
||||||
with vcr.use_cassette(cass_file) as cass:
|
with vcr.use_cassette(cass_file) as cass:
|
||||||
|
|||||||
22
tests/integration/test_http
Normal file
22
tests/integration/test_http
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
interactions:
|
||||||
|
- request:
|
||||||
|
body: null
|
||||||
|
headers: {}
|
||||||
|
method: GET
|
||||||
|
uri: https://httpbin.org/get?ham=spam
|
||||||
|
response:
|
||||||
|
body: {string: "{\n \"args\": {\n \"ham\": \"spam\"\n }, \n \"headers\"\
|
||||||
|
: {\n \"Accept\": \"*/*\", \n \"Accept-Encoding\": \"gzip, deflate\"\
|
||||||
|
, \n \"Connection\": \"close\", \n \"Host\": \"httpbin.org\", \n \
|
||||||
|
\ \"User-Agent\": \"Python/3.5 aiohttp/2.0.1\"\n }, \n \"origin\": \"213.86.221.35\"\
|
||||||
|
, \n \"url\": \"https://httpbin.org/get?ham=spam\"\n}\n"}
|
||||||
|
headers: {Access-Control-Allow-Credentials: 'true', Access-Control-Allow-Origin: '*',
|
||||||
|
Connection: keep-alive, Content-Length: '299', Content-Type: application/json,
|
||||||
|
Date: 'Wed, 22 Mar 2017 20:08:29 GMT', Server: gunicorn/19.7.1, Via: 1.1 vegur}
|
||||||
|
status: {code: 200, message: OK}
|
||||||
|
url: !!python/object/new:yarl.URL
|
||||||
|
state: !!python/tuple
|
||||||
|
- !!python/object/new:urllib.parse.SplitResult [https, httpbin.org, /get, ham=spam,
|
||||||
|
'']
|
||||||
|
- false
|
||||||
|
version: 1
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''Integration tests with httplib2'''
|
"""Integration tests with httplib2"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
# External imports
|
|
||||||
from six.moves.urllib_parse import urlencode
|
from six.moves.urllib_parse import urlencode
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_httpbin.certs
|
import pytest_httpbin.certs
|
||||||
|
|
||||||
# Internal imports
|
|
||||||
import vcr
|
import vcr
|
||||||
|
|
||||||
from assertions import assert_cassette_has_one_response
|
from assertions import assert_cassette_has_one_response
|
||||||
@@ -19,92 +19,90 @@ def http():
|
|||||||
Returns an httplib2 HTTP instance
|
Returns an httplib2 HTTP instance
|
||||||
with the certificate replaced by the httpbin one.
|
with the certificate replaced by the httpbin one.
|
||||||
"""
|
"""
|
||||||
return httplib2.Http(ca_certs=pytest_httpbin.certs.where())
|
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):
|
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
|
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)
|
resp, _ = http().request(url)
|
||||||
code = resp.status
|
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)
|
resp, _ = http().request(url)
|
||||||
assert code == resp.status
|
assert code == resp.status
|
||||||
|
|
||||||
|
|
||||||
def test_random_body(httpbin_both, tmpdir):
|
def test_random_body(httpbin_both, tmpdir):
|
||||||
'''Ensure we can read the content, and that it's served from cache'''
|
"""Ensure we can read the content, and that it's served from cache"""
|
||||||
url = httpbin_both.url + '/bytes/1024'
|
url = httpbin_both.url + "/bytes/1024"
|
||||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||||
_, content = http().request(url)
|
_, content = http().request(url)
|
||||||
body = content
|
body = content
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||||
_, content = http().request(url)
|
_, content = http().request(url)
|
||||||
assert body == content
|
assert body == content
|
||||||
|
|
||||||
|
|
||||||
def test_response_headers(tmpdir, httpbin_both):
|
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
|
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)
|
resp, _ = http().request(url)
|
||||||
headers = resp.items()
|
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)
|
resp, _ = http().request(url)
|
||||||
assert set(headers) == set(resp.items())
|
assert set(headers) == set(resp.items())
|
||||||
|
|
||||||
|
|
||||||
def test_effective_url(tmpdir, httpbin_both):
|
def test_effective_url(tmpdir, httpbin_both):
|
||||||
'''Ensure that the effective_url is captured'''
|
"""Ensure that the effective_url is captured"""
|
||||||
url = httpbin_both.url + '/redirect-to?url=/html'
|
url = httpbin_both.url + "/redirect-to?url=/html"
|
||||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||||
resp, _ = http().request(url)
|
resp, _ = http().request(url)
|
||||||
effective_url = resp['content-location']
|
effective_url = resp["content-location"]
|
||||||
assert effective_url == httpbin_both + '/html'
|
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)
|
resp, _ = http().request(url)
|
||||||
assert effective_url == resp['content-location']
|
assert effective_url == resp["content-location"]
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_requests(tmpdir, httpbin_both):
|
def test_multiple_requests(tmpdir, httpbin_both):
|
||||||
'''Ensure that we can cache multiple requests'''
|
"""Ensure that we can cache multiple requests"""
|
||||||
urls = [
|
urls = [httpbin_both.url, httpbin_both.url, httpbin_both.url + "/get", httpbin_both.url + "/bytes/1024"]
|
||||||
httpbin_both.url,
|
with vcr.use_cassette(str(tmpdir.join("multiple.yaml"))) as cass:
|
||||||
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]
|
[http().request(url) for url in urls]
|
||||||
assert len(cass) == len(urls)
|
assert len(cass) == len(urls)
|
||||||
|
|
||||||
|
|
||||||
def test_get_data(tmpdir, httpbin_both):
|
def test_get_data(tmpdir, httpbin_both):
|
||||||
'''Ensure that it works with query data'''
|
"""Ensure that it works with query data"""
|
||||||
data = urlencode({'some': 1, 'data': 'here'})
|
data = urlencode({"some": 1, "data": "here"})
|
||||||
url = httpbin_both.url + '/get?' + data
|
url = httpbin_both.url + "/get?" + data
|
||||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("get_data.yaml"))):
|
||||||
_, res1 = http().request(url)
|
_, 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)
|
_, res2 = http().request(url)
|
||||||
|
|
||||||
assert res1 == res2
|
assert res1 == res2
|
||||||
|
|
||||||
|
|
||||||
def test_post_data(tmpdir, httpbin_both):
|
def test_post_data(tmpdir, httpbin_both):
|
||||||
'''Ensure that it works when posting data'''
|
"""Ensure that it works when posting data"""
|
||||||
data = urlencode({'some': 1, 'data': 'here'})
|
data = urlencode({"some": 1, "data": "here"})
|
||||||
url = httpbin_both.url + '/post'
|
url = httpbin_both.url + "/post"
|
||||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||||
_, res1 = http().request(url, "POST", data)
|
_, 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)
|
_, res2 = http().request(url, "POST", data)
|
||||||
|
|
||||||
assert res1 == res2
|
assert res1 == res2
|
||||||
@@ -112,13 +110,13 @@ def test_post_data(tmpdir, httpbin_both):
|
|||||||
|
|
||||||
|
|
||||||
def test_post_unicode_data(tmpdir, httpbin_both):
|
def test_post_unicode_data(tmpdir, httpbin_both):
|
||||||
'''Ensure that it works when posting unicode data'''
|
"""Ensure that it works when posting unicode data"""
|
||||||
data = urlencode({'snowman': u'☃'.encode('utf-8')})
|
data = urlencode({"snowman": u"☃".encode("utf-8")})
|
||||||
url = httpbin_both.url + '/post'
|
url = httpbin_both.url + "/post"
|
||||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||||
_, res1 = http().request(url, "POST", data)
|
_, 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)
|
_, res2 = http().request(url, "POST", data)
|
||||||
|
|
||||||
assert res1 == res2
|
assert res1 == res2
|
||||||
@@ -126,11 +124,11 @@ def test_post_unicode_data(tmpdir, httpbin_both):
|
|||||||
|
|
||||||
|
|
||||||
def test_cross_scheme(tmpdir, httpbin, httpbin_secure):
|
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
|
# 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
|
# ensure that we haven't served anything out of cache, and we have two
|
||||||
# requests / response pairs in the cassette
|
# 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_secure.url)
|
||||||
http().request(httpbin.url)
|
http().request(httpbin.url)
|
||||||
assert len(cass) == 2
|
assert len(cass) == 2
|
||||||
@@ -138,17 +136,17 @@ def test_cross_scheme(tmpdir, httpbin, httpbin_secure):
|
|||||||
|
|
||||||
|
|
||||||
def test_decorator(tmpdir, httpbin_both):
|
def test_decorator(tmpdir, httpbin_both):
|
||||||
'''Test the decorator version of VCR.py'''
|
"""Test the decorator version of VCR.py"""
|
||||||
url = httpbin_both.url
|
url = httpbin_both.url
|
||||||
|
|
||||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
@vcr.use_cassette(str(tmpdir.join("atts.yaml")))
|
||||||
def inner1():
|
def inner1():
|
||||||
resp, _ = http().request(url)
|
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():
|
def inner2():
|
||||||
resp, _ = http().request(url)
|
resp, _ = http().request(url)
|
||||||
return resp['status']
|
return resp["status"]
|
||||||
|
|
||||||
assert inner1() == inner2()
|
assert inner1() == inner2()
|
||||||
|
|||||||
@@ -15,59 +15,53 @@ def overridden_dns(overrides):
|
|||||||
def fake_getaddrinfo(*args, **kwargs):
|
def fake_getaddrinfo(*args, **kwargs):
|
||||||
if args[0] in overrides:
|
if args[0] in overrides:
|
||||||
address = overrides[args[0]]
|
address = overrides[args[0]]
|
||||||
return [(2, 1, 6, '', (address, args[1]))]
|
return [(2, 1, 6, "", (address, args[1]))]
|
||||||
return real_getaddrinfo(*args, **kwargs)
|
return real_getaddrinfo(*args, **kwargs)
|
||||||
|
|
||||||
socket.getaddrinfo = fake_getaddrinfo
|
socket.getaddrinfo = fake_getaddrinfo
|
||||||
yield
|
yield
|
||||||
socket.getaddrinfo = real_getaddrinfo
|
socket.getaddrinfo = real_getaddrinfo
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_localhost(tmpdir, httpbin):
|
def test_ignore_localhost(tmpdir, httpbin):
|
||||||
with overridden_dns({'httpbin.org': '127.0.0.1'}):
|
with overridden_dns({"httpbin.org": "127.0.0.1"}):
|
||||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||||
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
|
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
|
||||||
urlopen('http://localhost:{0}/'.format(httpbin.port))
|
urlopen("http://localhost:{}/".format(httpbin.port))
|
||||||
assert len(cass) == 0
|
assert len(cass) == 0
|
||||||
urlopen('http://httpbin.org:{0}/'.format(httpbin.port))
|
urlopen("http://httpbin.org:{}/".format(httpbin.port))
|
||||||
assert len(cass) == 1
|
assert len(cass) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_httpbin(tmpdir, httpbin):
|
def test_ignore_httpbin(tmpdir, httpbin):
|
||||||
with overridden_dns({'httpbin.org': '127.0.0.1'}):
|
with overridden_dns({"httpbin.org": "127.0.0.1"}):
|
||||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||||
with vcr.use_cassette(
|
with vcr.use_cassette(cass_file, ignore_hosts=["httpbin.org"]) as cass:
|
||||||
cass_file,
|
urlopen("http://httpbin.org:{}/".format(httpbin.port))
|
||||||
ignore_hosts=['httpbin.org']
|
|
||||||
) as cass:
|
|
||||||
urlopen('http://httpbin.org:{0}/'.format(httpbin.port))
|
|
||||||
assert len(cass) == 0
|
assert len(cass) == 0
|
||||||
urlopen('http://localhost:{0}/'.format(httpbin.port))
|
urlopen("http://localhost:{}/".format(httpbin.port))
|
||||||
assert len(cass) == 1
|
assert len(cass) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_localhost_and_httpbin(tmpdir, httpbin):
|
def test_ignore_localhost_and_httpbin(tmpdir, httpbin):
|
||||||
with overridden_dns({'httpbin.org': '127.0.0.1'}):
|
with overridden_dns({"httpbin.org": "127.0.0.1"}):
|
||||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||||
with vcr.use_cassette(
|
with vcr.use_cassette(cass_file, ignore_hosts=["httpbin.org"], ignore_localhost=True) as cass:
|
||||||
cass_file,
|
urlopen("http://httpbin.org:{}".format(httpbin.port))
|
||||||
ignore_hosts=['httpbin.org'],
|
urlopen("http://localhost:{}".format(httpbin.port))
|
||||||
ignore_localhost=True
|
|
||||||
) as cass:
|
|
||||||
urlopen('http://httpbin.org:{0}'.format(httpbin.port))
|
|
||||||
urlopen('http://localhost:{0}'.format(httpbin.port))
|
|
||||||
assert len(cass) == 0
|
assert len(cass) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_localhost_twice(tmpdir, httpbin):
|
def test_ignore_localhost_twice(tmpdir, httpbin):
|
||||||
with overridden_dns({'httpbin.org': '127.0.0.1'}):
|
with overridden_dns({"httpbin.org": "127.0.0.1"}):
|
||||||
cass_file = str(tmpdir.join('filter_qs.yaml'))
|
cass_file = str(tmpdir.join("filter_qs.yaml"))
|
||||||
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
|
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
|
||||||
urlopen('http://localhost:{0}'.format(httpbin.port))
|
urlopen("http://localhost:{}".format(httpbin.port))
|
||||||
assert len(cass) == 0
|
assert len(cass) == 0
|
||||||
urlopen('http://httpbin.org:{0}'.format(httpbin.port))
|
urlopen("http://httpbin.org:{}".format(httpbin.port))
|
||||||
assert len(cass) == 1
|
assert len(cass) == 1
|
||||||
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
|
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
|
||||||
assert len(cass) == 1
|
assert len(cass) == 1
|
||||||
urlopen('http://localhost:{0}'.format(httpbin.port))
|
urlopen("http://localhost:{}".format(httpbin.port))
|
||||||
urlopen('http://httpbin.org:{0}'.format(httpbin.port))
|
urlopen("http://httpbin.org:{}".format(httpbin.port))
|
||||||
assert len(cass) == 1
|
assert len(cass) == 1
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import pytest
|
|||||||
from six.moves.urllib.request import urlopen
|
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):
|
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
|
@pytest.fixture
|
||||||
@@ -18,29 +18,22 @@ def cassette(tmpdir, httpbin, httpbin_secure):
|
|||||||
"""
|
"""
|
||||||
default_uri = _replace_httpbin(DEFAULT_URI, httpbin, httpbin_secure)
|
default_uri = _replace_httpbin(DEFAULT_URI, httpbin, httpbin_secure)
|
||||||
|
|
||||||
cassette_path = str(tmpdir.join('test.yml'))
|
cassette_path = str(tmpdir.join("test.yml"))
|
||||||
with vcr.use_cassette(cassette_path, record_mode='all'):
|
with vcr.use_cassette(cassette_path, record_mode="all"):
|
||||||
urlopen(default_uri)
|
urlopen(default_uri)
|
||||||
return cassette_path
|
return cassette_path
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("matcher, matching_uri, not_matching_uri", [
|
@pytest.mark.parametrize(
|
||||||
('uri',
|
"matcher, matching_uri, not_matching_uri",
|
||||||
'http://httpbin.org/get?p1=q1&p2=q2',
|
[
|
||||||
'http://httpbin.org/get?p2=q2&p1=q1'),
|
("uri", "http://httpbin.org/get?p1=q1&p2=q2", "http://httpbin.org/get?p2=q2&p1=q1"),
|
||||||
('scheme',
|
("scheme", "http://google.com/post?a=b", "https://httpbin.org/get?p1=q1&p2=q2"),
|
||||||
'http://google.com/post?a=b',
|
("host", "https://httpbin.org/post?a=b", "http://google.com/get?p1=q1&p2=q2"),
|
||||||
'https://httpbin.org/get?p1=q1&p2=q2'),
|
("path", "https://google.com/get?a=b", "http://httpbin.org/post?p1=q1&p2=q2"),
|
||||||
('host',
|
("query", "https://google.com/get?p2=q2&p1=q1", "http://httpbin.org/get?p1=q1&a=b"),
|
||||||
'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):
|
def test_matchers(httpbin, httpbin_secure, cassette, matcher, matching_uri, not_matching_uri):
|
||||||
|
|
||||||
matching_uri = _replace_httpbin(matching_uri, httpbin, httpbin_secure)
|
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)
|
default_uri = _replace_httpbin(DEFAULT_URI, httpbin, httpbin_secure)
|
||||||
|
|
||||||
# play cassette with matching on method
|
# play cassette with matching on method
|
||||||
with vcr.use_cassette(cassette, match_on=['method']) as cass:
|
with vcr.use_cassette(cassette, match_on=["method"]) as cass:
|
||||||
urlopen('https://google.com/get?a=b')
|
urlopen("https://google.com/get?a=b")
|
||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
|
|
||||||
# should fail if method does not match
|
# should fail if method does not match
|
||||||
with pytest.raises(vcr.errors.CannotOverwriteExistingCassetteException):
|
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
|
# is a POST request
|
||||||
urlopen(default_uri, data=b'')
|
urlopen(default_uri, data=b"")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("uri", [
|
@pytest.mark.parametrize(
|
||||||
DEFAULT_URI,
|
"uri", [DEFAULT_URI, "http://httpbin.org/get?p2=q2&p1=q1", "http://httpbin.org/get?p2=q2&p1=q1"]
|
||||||
'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):
|
def test_default_matcher_matches(cassette, uri, httpbin, httpbin_secure):
|
||||||
|
|
||||||
uri = _replace_httpbin(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
|
assert cass.play_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("uri", [
|
@pytest.mark.parametrize(
|
||||||
'https://httpbin.org/get?p1=q1&p2=q2',
|
"uri",
|
||||||
'http://google.com/get?p1=q1&p2=q2',
|
[
|
||||||
'http://httpbin.org/post?p1=q1&p2=q2',
|
"https://httpbin.org/get?p1=q1&p2=q2",
|
||||||
'http://httpbin.org/get?p1=q1&a=b'
|
"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):
|
def test_default_matcher_does_not_match(cassette, uri, httpbin, httpbin_secure):
|
||||||
uri = _replace_httpbin(uri, httpbin, httpbin_secure)
|
uri = _replace_httpbin(uri, httpbin, httpbin_secure)
|
||||||
with pytest.raises(vcr.errors.CannotOverwriteExistingCassetteException):
|
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 pytest.raises(vcr.errors.CannotOverwriteExistingCassetteException):
|
||||||
with vcr.use_cassette(cassette):
|
with vcr.use_cassette(cassette):
|
||||||
# is a POST request
|
# 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):
|
def test_making_extra_request_raises_exception(tmpdir, httpbin):
|
||||||
# make two requests in the first request that are considered
|
# make two requests in the first request that are considered
|
||||||
# identical (since the match is based on method)
|
# identical (since the match is based on method)
|
||||||
with vcr.use_cassette(str(tmpdir.join('test.json')), match_on=['method']):
|
with vcr.use_cassette(str(tmpdir.join("test.json")), match_on=["method"]):
|
||||||
urlopen(httpbin.url + '/status/200')
|
urlopen(httpbin.url + "/status/200")
|
||||||
urlopen(httpbin.url + '/status/201')
|
urlopen(httpbin.url + "/status/201")
|
||||||
|
|
||||||
# Now, try to make three requests. The first two should return the
|
# Now, try to make three requests. The first two should return the
|
||||||
# correct status codes in order, and the third should raise an
|
# correct status codes in order, and the third should raise an
|
||||||
# exception.
|
# exception.
|
||||||
with vcr.use_cassette(str(tmpdir.join('test.json')), match_on=['method']):
|
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/200").getcode() == 200
|
||||||
assert urlopen(httpbin.url + '/status/201').getcode() == 201
|
assert urlopen(httpbin.url + "/status/201").getcode() == 201
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
urlopen(httpbin.url + '/status/200')
|
urlopen(httpbin.url + "/status/200")
|
||||||
|
|||||||
59
tests/integration/test_proxy.py
Normal file
59
tests/integration/test_proxy.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Test using a proxy."""
|
||||||
|
|
||||||
|
# External imports
|
||||||
|
import multiprocessing
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from six.moves import socketserver, SimpleHTTPServer
|
||||||
|
from six.moves.urllib.request import urlopen
|
||||||
|
|
||||||
|
# Internal imports
|
||||||
|
import vcr
|
||||||
|
|
||||||
|
# Conditional imports
|
||||||
|
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:
|
||||||
|
status = upstream_response.status
|
||||||
|
headers = upstream_response.headers.items()
|
||||||
|
except AttributeError:
|
||||||
|
# In Python 2 the response is an addinfourl instance.
|
||||||
|
status = upstream_response.code
|
||||||
|
headers = upstream_response.info().items()
|
||||||
|
self.send_response(status, upstream_response.msg)
|
||||||
|
for header in headers:
|
||||||
|
self.send_header(*header)
|
||||||
|
self.end_headers()
|
||||||
|
self.copyfile(upstream_response, self.wfile)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.yield_fixture(scope="session")
|
||||||
|
def proxy_server():
|
||||||
|
httpd = socketserver.ThreadingTCPServer(("", 0), Proxy)
|
||||||
|
proxy_process = multiprocessing.Process(target=httpd.serve_forever)
|
||||||
|
proxy_process.start()
|
||||||
|
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})
|
||||||
|
|
||||||
|
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):
|
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"):
|
with vcr.use_cassette(testfile, record_mode="once"):
|
||||||
# cassette file doesn't exist, so create.
|
# cassette file doesn't exist, so create.
|
||||||
urlopen(httpbin.url).read()
|
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
|
# but, try to access something else from the same cassette, and an
|
||||||
# exception is raised.
|
# exception is raised.
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
urlopen(httpbin.url + '/get').read()
|
urlopen(httpbin.url + "/get").read()
|
||||||
|
|
||||||
|
|
||||||
def test_once_record_mode_two_times(tmpdir, httpbin):
|
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"):
|
with vcr.use_cassette(testfile, record_mode="once"):
|
||||||
# get two of the same file
|
# get two of the same file
|
||||||
urlopen(httpbin.url).read()
|
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):
|
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"):
|
with vcr.use_cassette(testfile, record_mode="once"):
|
||||||
# get three of the same file
|
# get three of the same file
|
||||||
urlopen(httpbin.url).read()
|
urlopen(httpbin.url).read()
|
||||||
@@ -43,7 +43,7 @@ def test_once_mode_three_times(tmpdir, httpbin):
|
|||||||
|
|
||||||
|
|
||||||
def test_new_episodes_record_mode(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"):
|
with vcr.use_cassette(testfile, record_mode="new_episodes"):
|
||||||
# cassette file doesn't exist, so create.
|
# 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
|
# in the "new_episodes" record mode, we can add more requests to
|
||||||
# a cassette without repurcussions.
|
# a cassette without repurcussions.
|
||||||
urlopen(httpbin.url + '/get').read()
|
urlopen(httpbin.url + "/get").read()
|
||||||
|
|
||||||
# one of the responses has been played
|
# one of the responses has been played
|
||||||
assert cass.play_count == 1
|
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):
|
def test_new_episodes_record_mode_two_times(tmpdir, httpbin):
|
||||||
testfile = str(tmpdir.join('recordmode.yml'))
|
testfile = str(tmpdir.join("recordmode.yml"))
|
||||||
url = httpbin.url + '/bytes/1024'
|
url = httpbin.url + "/bytes/1024"
|
||||||
with vcr.use_cassette(testfile, record_mode="new_episodes"):
|
with vcr.use_cassette(testfile, record_mode="new_episodes"):
|
||||||
# cassette file doesn't exist, so create.
|
# cassette file doesn't exist, so create.
|
||||||
original_first_response = urlopen(url).read()
|
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):
|
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"):
|
with vcr.use_cassette(testfile, record_mode="all"):
|
||||||
# cassette file doesn't exist, so create.
|
# 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
|
# in the "all" record mode, we can add more requests to
|
||||||
# a cassette without repurcussions.
|
# a cassette without repurcussions.
|
||||||
urlopen(httpbin.url + '/get').read()
|
urlopen(httpbin.url + "/get").read()
|
||||||
|
|
||||||
# The cassette was never actually played, even though it existed.
|
# The cassette was never actually played, even though it existed.
|
||||||
# that's because, in "all" mode, the requests all go directly to
|
# 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):
|
def test_none_record_mode(tmpdir, httpbin):
|
||||||
# Cassette file doesn't exist, yet we are trying to make a request.
|
# Cassette file doesn't exist, yet we are trying to make a request.
|
||||||
# raise hell.
|
# raise hell.
|
||||||
testfile = str(tmpdir.join('recordmode.yml'))
|
testfile = str(tmpdir.join("recordmode.yml"))
|
||||||
with vcr.use_cassette(testfile, record_mode="none"):
|
with vcr.use_cassette(testfile, record_mode="none"):
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
urlopen(httpbin.url).read()
|
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):
|
def test_none_record_mode_with_existing_cassette(tmpdir, httpbin):
|
||||||
# create a cassette file
|
# create a cassette file
|
||||||
testfile = str(tmpdir.join('recordmode.yml'))
|
testfile = str(tmpdir.join("recordmode.yml"))
|
||||||
|
|
||||||
with vcr.use_cassette(testfile, record_mode="all"):
|
with vcr.use_cassette(testfile, record_mode="all"):
|
||||||
urlopen(httpbin.url).read()
|
urlopen(httpbin.url).read()
|
||||||
@@ -139,4 +139,4 @@ def test_none_record_mode_with_existing_cassette(tmpdir, httpbin):
|
|||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
# but if I try to hit the net, raise an exception.
|
# but if I try to hit the net, raise an exception.
|
||||||
with pytest.raises(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):
|
def test_registered_true_matcher(tmpdir, httpbin):
|
||||||
my_vcr = vcr.VCR()
|
my_vcr = vcr.VCR()
|
||||||
my_vcr.register_matcher('true', true_matcher)
|
my_vcr.register_matcher("true", true_matcher)
|
||||||
testfile = str(tmpdir.join('test.yml'))
|
testfile = str(tmpdir.join("test.yml"))
|
||||||
with my_vcr.use_cassette(testfile, match_on=['true']):
|
with my_vcr.use_cassette(testfile, match_on=["true"]):
|
||||||
# These 2 different urls are stored as the same request
|
# These 2 different urls are stored as the same request
|
||||||
urlopen(httpbin.url)
|
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
|
# 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):
|
def test_registered_false_matcher(tmpdir, httpbin):
|
||||||
my_vcr = vcr.VCR()
|
my_vcr = vcr.VCR()
|
||||||
my_vcr.register_matcher('false', false_matcher)
|
my_vcr.register_matcher("false", false_matcher)
|
||||||
testfile = str(tmpdir.join('test.yml'))
|
testfile = str(tmpdir.join("test.yml"))
|
||||||
with my_vcr.use_cassette(testfile, match_on=['false']) as cass:
|
with my_vcr.use_cassette(testfile, match_on=["false"]) as cass:
|
||||||
# These 2 different urls are stored as different requests
|
# These 2 different urls are stored as different requests
|
||||||
urlopen(httpbin.url)
|
urlopen(httpbin.url)
|
||||||
urlopen(httpbin.url + '/get')
|
urlopen(httpbin.url + "/get")
|
||||||
assert len(cass) == 2
|
assert len(cass) == 2
|
||||||
|
|||||||
55
tests/integration/test_register_persister.py
Normal file
55
tests/integration/test_register_persister.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tests for cassettes with custom persistence"""
|
||||||
|
|
||||||
|
# External imports
|
||||||
|
import os
|
||||||
|
from six.moves.urllib.request import urlopen
|
||||||
|
|
||||||
|
# Internal imports
|
||||||
|
import vcr
|
||||||
|
from vcr.persisters.filesystem import FilesystemPersister
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFilesystemPersister(object):
|
||||||
|
"""Behaves just like default FilesystemPersister but adds .test extension
|
||||||
|
to the cassette file"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load_cassette(cassette_path, serializer):
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_cassette_with_custom_persister(tmpdir, httpbin):
|
||||||
|
"""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")))
|
||||||
|
|
||||||
|
# Run VCR to create dir and cassette file using new save_cassette callback
|
||||||
|
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")))
|
||||||
|
|
||||||
|
|
||||||
|
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"))
|
||||||
|
|
||||||
|
with my_vcr.use_cassette(test_fixture, serializer="json"):
|
||||||
|
response = urlopen(httpbin.url).read()
|
||||||
|
assert b"difficult sometimes" in response
|
||||||
@@ -10,7 +10,7 @@ class MockSerializer(object):
|
|||||||
def deserialize(self, cassette_string):
|
def deserialize(self, cassette_string):
|
||||||
self.serialize_count += 1
|
self.serialize_count += 1
|
||||||
self.cassette_string = cassette_string
|
self.cassette_string = cassette_string
|
||||||
return {'interactions': []}
|
return {"interactions": []}
|
||||||
|
|
||||||
def serialize(self, cassette_dict):
|
def serialize(self, cassette_dict):
|
||||||
self.deserialize_count += 1
|
self.deserialize_count += 1
|
||||||
@@ -20,13 +20,13 @@ class MockSerializer(object):
|
|||||||
def test_registered_serializer(tmpdir):
|
def test_registered_serializer(tmpdir):
|
||||||
ms = MockSerializer()
|
ms = MockSerializer()
|
||||||
my_vcr = vcr.VCR()
|
my_vcr = vcr.VCR()
|
||||||
my_vcr.register_serializer('mock', ms)
|
my_vcr.register_serializer("mock", ms)
|
||||||
tmpdir.join('test.mock').write('test_data')
|
tmpdir.join("test.mock").write("test_data")
|
||||||
with my_vcr.use_cassette(str(tmpdir.join('test.mock')), serializer='mock'):
|
with my_vcr.use_cassette(str(tmpdir.join("test.mock")), serializer="mock"):
|
||||||
# Serializer deserialized once
|
# Serializer deserialized once
|
||||||
assert ms.serialize_count == 1
|
assert ms.serialize_count == 1
|
||||||
# and serialized the test data string
|
# and serialized the test data string
|
||||||
assert ms.cassette_string == 'test_data'
|
assert ms.cassette_string == "test_data"
|
||||||
# and hasn't serialized yet
|
# and hasn't serialized yet
|
||||||
assert ms.deserialize_count == 0
|
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):
|
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
|
assert len(cass) == 0
|
||||||
urlopen(httpbin.url + '/redirect/3')
|
urlopen(httpbin.url + "/redirect/3")
|
||||||
assert cass.requests[0].uri == httpbin.url + '/redirect/3'
|
assert cass.requests[0].uri == httpbin.url + "/redirect/3"
|
||||||
assert cass.requests[3].uri == httpbin.url + '/get'
|
assert cass.requests[3].uri == httpbin.url + "/get"
|
||||||
assert len(cass) == 4
|
assert len(cass) == 4
|
||||||
|
|
||||||
|
|
||||||
def test_records_multiple_header_values(tmpdir, httpbin):
|
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
|
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 len(cass) == 1
|
||||||
assert cass.responses[0]['headers']['foo'] == ['bar', 'baz']
|
assert cass.responses[0]["headers"]["foo"] == ["bar", "baz"]
|
||||||
|
|||||||
@@ -1,72 +1,86 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''Test requests' interaction with vcr'''
|
"""Test requests' interaction with vcr"""
|
||||||
|
import platform
|
||||||
import pytest
|
import pytest
|
||||||
|
import sys
|
||||||
import vcr
|
import vcr
|
||||||
from assertions import assert_cassette_empty, assert_is_json
|
from assertions import assert_cassette_empty, assert_is_json
|
||||||
|
|
||||||
|
|
||||||
requests = pytest.importorskip("requests")
|
requests = pytest.importorskip("requests")
|
||||||
|
from requests.exceptions import ConnectionError # noqa E402
|
||||||
|
|
||||||
|
|
||||||
def test_status_code(httpbin_both, tmpdir):
|
def test_status_code(httpbin_both, tmpdir):
|
||||||
'''Ensure that we can read the status code'''
|
"""Ensure that we can read the status code"""
|
||||||
url = httpbin_both.url + '/'
|
url = httpbin_both.url + "/"
|
||||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||||
status_code = requests.get(url).status_code
|
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
|
assert status_code == requests.get(url).status_code
|
||||||
|
|
||||||
|
|
||||||
def test_headers(httpbin_both, tmpdir):
|
def test_headers(httpbin_both, tmpdir):
|
||||||
'''Ensure that we can read the headers back'''
|
"""Ensure that we can read the headers back"""
|
||||||
url = httpbin_both + '/'
|
url = httpbin_both + "/"
|
||||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||||
headers = requests.get(url).headers
|
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
|
assert headers == requests.get(url).headers
|
||||||
|
|
||||||
|
|
||||||
def test_body(tmpdir, httpbin_both):
|
def test_body(tmpdir, httpbin_both):
|
||||||
'''Ensure the responses are all identical enough'''
|
"""Ensure the responses are all identical enough"""
|
||||||
url = httpbin_both + '/bytes/1024'
|
url = httpbin_both + "/bytes/1024"
|
||||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||||
content = requests.get(url).content
|
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
|
assert content == requests.get(url).content
|
||||||
|
|
||||||
|
|
||||||
def test_effective_url(tmpdir, httpbin_both):
|
def test_get_empty_content_type_json(tmpdir, httpbin_both):
|
||||||
'''Ensure that the effective_url is captured'''
|
"""Ensure GET with application/json content-type and empty request body doesn't crash"""
|
||||||
url = httpbin_both.url + '/redirect-to?url=/html'
|
url = httpbin_both + "/status/200"
|
||||||
with vcr.use_cassette(str(tmpdir.join('url.yaml'))):
|
headers = {"Content-Type": "application/json"}
|
||||||
effective_url = requests.get(url).url
|
|
||||||
assert effective_url == httpbin_both.url + '/html'
|
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('url.yaml'))):
|
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",)):
|
||||||
|
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"))):
|
||||||
|
effective_url = requests.get(url).url
|
||||||
|
assert effective_url == httpbin_both.url + "/html"
|
||||||
|
|
||||||
|
with vcr.use_cassette(str(tmpdir.join("url.yaml"))):
|
||||||
assert effective_url == requests.get(url).url
|
assert effective_url == requests.get(url).url
|
||||||
|
|
||||||
|
|
||||||
def test_auth(tmpdir, httpbin_both):
|
def test_auth(tmpdir, httpbin_both):
|
||||||
'''Ensure that we can handle basic auth'''
|
"""Ensure that we can handle basic auth"""
|
||||||
auth = ('user', 'passwd')
|
auth = ("user", "passwd")
|
||||||
url = httpbin_both + '/basic-auth/user/passwd'
|
url = httpbin_both + "/basic-auth/user/passwd"
|
||||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))):
|
||||||
one = requests.get(url, auth=auth)
|
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)
|
two = requests.get(url, auth=auth)
|
||||||
assert one.content == two.content
|
assert one.content == two.content
|
||||||
assert one.status_code == two.status_code
|
assert one.status_code == two.status_code
|
||||||
|
|
||||||
|
|
||||||
def test_auth_failed(tmpdir, httpbin_both):
|
def test_auth_failed(tmpdir, httpbin_both):
|
||||||
'''Ensure that we can save failed auth statuses'''
|
"""Ensure that we can save failed auth statuses"""
|
||||||
auth = ('user', 'wrongwrongwrong')
|
auth = ("user", "wrongwrongwrong")
|
||||||
url = httpbin_both + '/basic-auth/user/passwd'
|
url = httpbin_both + "/basic-auth/user/passwd"
|
||||||
with vcr.use_cassette(str(tmpdir.join('auth-failed.yaml'))) as cass:
|
with vcr.use_cassette(str(tmpdir.join("auth-failed.yaml"))) as cass:
|
||||||
# Ensure that this is empty to begin with
|
# Ensure that this is empty to begin with
|
||||||
assert_cassette_empty(cass)
|
assert_cassette_empty(cass)
|
||||||
one = requests.get(url, auth=auth)
|
one = requests.get(url, auth=auth)
|
||||||
@@ -76,40 +90,59 @@ def test_auth_failed(tmpdir, httpbin_both):
|
|||||||
|
|
||||||
|
|
||||||
def test_post(tmpdir, httpbin_both):
|
def test_post(tmpdir, httpbin_both):
|
||||||
'''Ensure that we can post and cache the results'''
|
"""Ensure that we can post and cache the results"""
|
||||||
data = {'key1': 'value1', 'key2': 'value2'}
|
data = {"key1": "value1", "key2": "value2"}
|
||||||
url = httpbin_both + '/post'
|
url = httpbin_both + "/post"
|
||||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||||
req1 = requests.post(url, data).content
|
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
|
req2 = requests.post(url, data).content
|
||||||
|
|
||||||
assert req1 == req2
|
assert req1 == req2
|
||||||
|
|
||||||
|
|
||||||
def test_post_chunked_binary(tmpdir, httpbin_both):
|
def test_post_chunked_binary(tmpdir, httpbin):
|
||||||
'''Ensure that we can send chunked binary without breaking while trying to concatenate bytes with str.'''
|
"""Ensure that we can send chunked binary without breaking while trying to concatenate bytes with str."""
|
||||||
data1 = iter([b'data', b'to', b'send'])
|
data1 = iter([b"data", b"to", b"send"])
|
||||||
data2 = iter([b'data', b'to', b'send'])
|
data2 = iter([b"data", b"to", b"send"])
|
||||||
url = httpbin_both.url + '/post'
|
url = httpbin.url + "/post"
|
||||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||||
|
req1 = requests.post(url, data1).content
|
||||||
|
|
||||||
|
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||||
|
req2 = requests.post(url, data2).content
|
||||||
|
|
||||||
|
assert req1 == req2
|
||||||
|
|
||||||
|
|
||||||
|
@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"))):
|
||||||
req1 = requests.post(url, data1).content
|
req1 = requests.post(url, data1).content
|
||||||
print(req1)
|
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
|
req2 = requests.post(url, data2).content
|
||||||
|
|
||||||
assert req1 == req2
|
assert req1 == req2
|
||||||
|
|
||||||
|
|
||||||
def test_redirects(tmpdir, httpbin_both):
|
def test_redirects(tmpdir, httpbin_both):
|
||||||
'''Ensure that we can handle redirects'''
|
"""Ensure that we can handle redirects"""
|
||||||
url = httpbin_both + '/redirect-to?url=bytes/1024'
|
url = httpbin_both + "/redirect-to?url=bytes/1024"
|
||||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||||
content = requests.get(url).content
|
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
|
assert content == requests.get(url).content
|
||||||
# Ensure that we've now cached *two* responses. One for the redirect
|
# Ensure that we've now cached *two* responses. One for the redirect
|
||||||
# and one for the final fetch
|
# and one for the final fetch
|
||||||
@@ -118,137 +151,151 @@ def test_redirects(tmpdir, httpbin_both):
|
|||||||
|
|
||||||
|
|
||||||
def test_cross_scheme(tmpdir, httpbin_secure, httpbin):
|
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
|
# 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
|
# ensure that we haven't served anything out of cache, and we have two
|
||||||
# requests / response pairs in the cassette
|
# 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:
|
||||||
requests.get(httpbin_secure + '/')
|
requests.get(httpbin_secure + "/")
|
||||||
requests.get(httpbin + '/')
|
requests.get(httpbin + "/")
|
||||||
assert cass.play_count == 0
|
assert cass.play_count == 0
|
||||||
assert len(cass) == 2
|
assert len(cass) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_gzip(tmpdir, httpbin_both):
|
def test_gzip(tmpdir, httpbin_both):
|
||||||
'''
|
"""
|
||||||
Ensure that requests (actually urllib3) is able to automatically decompress
|
Ensure that requests (actually urllib3) is able to automatically decompress
|
||||||
the response body
|
the response body
|
||||||
'''
|
"""
|
||||||
url = httpbin_both + '/gzip'
|
url = httpbin_both + "/gzip"
|
||||||
response = requests.get(url)
|
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)
|
response = requests.get(url)
|
||||||
assert_is_json(response.content)
|
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)
|
assert_is_json(response.content)
|
||||||
|
|
||||||
|
|
||||||
def test_session_and_connection_close(tmpdir, httpbin):
|
def test_session_and_connection_close(tmpdir, httpbin):
|
||||||
'''
|
"""
|
||||||
This tests the issue in https://github.com/kevin1024/vcrpy/issues/48
|
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
|
If you use a requests.session and the connection is closed, then an
|
||||||
exception is raised in the urllib3 module vendored into requests:
|
exception is raised in the urllib3 module vendored into requests:
|
||||||
`AttributeError: 'NoneType' object has no attribute 'settimeout'`
|
`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 = 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):
|
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)
|
requests.get(httpbin_secure.url, verify=False)
|
||||||
|
|
||||||
|
|
||||||
def test_session_can_make_requests_after_requests_unpatched(tmpdir, httpbin):
|
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 = 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 = 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):
|
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
|
# 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()
|
session = requests.session()
|
||||||
body = session.get(url).content
|
body = session.get(url).content
|
||||||
|
|
||||||
# Create a session outside of any cassette context manager
|
# Create a session outside of any cassette context manager
|
||||||
session = requests.session()
|
session = requests.session()
|
||||||
# Make a request to make sure that a connectionpool is instantiated
|
# 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.
|
# These should only be the same if the patching succeeded.
|
||||||
assert session.get(url).content == body
|
assert session.get(url).content == body
|
||||||
|
|
||||||
|
|
||||||
def test_nested_cassettes_with_session_created_before_nesting(httpbin_both, tmpdir):
|
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
|
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
|
active is patched to the use the responses of a second cassette when it
|
||||||
is enabled.
|
is enabled.
|
||||||
'''
|
"""
|
||||||
url = httpbin_both + '/bytes/1024'
|
url = httpbin_both + "/bytes/1024"
|
||||||
with vcr.use_cassette(str(tmpdir.join('first_nested.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("first_nested.yaml"))):
|
||||||
session = requests.session()
|
session = requests.session()
|
||||||
first_body = session.get(url).content
|
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
|
second_body = session.get(url).content
|
||||||
third_body = requests.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()
|
session = requests.session()
|
||||||
assert session.get(url).content == second_body
|
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 == first_body
|
||||||
assert session.get(url).content == third_body
|
assert session.get(url).content == third_body
|
||||||
|
|
||||||
# Make sure that the session can now get content normally.
|
# 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):
|
def test_post_file(tmpdir, httpbin_both):
|
||||||
'''Ensure that we handle posting a file.'''
|
"""Ensure that we handle posting a file."""
|
||||||
url = httpbin_both + '/post'
|
url = httpbin_both + "/post"
|
||||||
with vcr.use_cassette(str(tmpdir.join('post_file.yaml'))) as cass:
|
with vcr.use_cassette(str(tmpdir.join("post_file.yaml"))) as cass, open("tox.ini", "rb") as f:
|
||||||
# Don't use 2.7+ only style ',' separated with here because we support python 2.6
|
original_response = requests.post(url, f).content
|
||||||
with open('tox.ini') 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.
|
# 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')),
|
with vcr.use_cassette(
|
||||||
match_on=('method', 'scheme', 'host', 'port', 'path', 'query', 'body')) as cass:
|
str(tmpdir.join("post_file.yaml")),
|
||||||
with open('tox.ini', 'rb') as f:
|
match_on=("method", "scheme", "host", "port", "path", "query", "body"),
|
||||||
|
) as cass:
|
||||||
|
with open("tox.ini", "rb") as f:
|
||||||
tox_content = f.read()
|
tox_content = f.read()
|
||||||
assert cass.requests[0].body.read() == tox_content
|
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
|
new_response = requests.post(url, f).content
|
||||||
assert original_response == new_response
|
assert original_response == new_response
|
||||||
|
|
||||||
|
|
||||||
def test_filter_post_params(tmpdir, httpbin_both):
|
def test_filter_post_params(tmpdir, httpbin_both):
|
||||||
'''
|
"""
|
||||||
This tests the issue in https://github.com/kevin1024/vcrpy/issues/158
|
This tests the issue in https://github.com/kevin1024/vcrpy/issues/158
|
||||||
|
|
||||||
Ensure that a post request made through requests can still be filtered.
|
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:
|
with vcr.use_cassette(cass_file, filter_post_data_parameters=['id']) as cass:
|
||||||
assert b'id=secret' not in cass.requests[0].body
|
assert b'id=secret' not in cass.requests[0].body
|
||||||
'''
|
"""
|
||||||
url = httpbin_both.url + '/post'
|
url = httpbin_both.url + "/post"
|
||||||
cass_loc = str(tmpdir.join('filter_post_params.yaml'))
|
cass_loc = str(tmpdir.join("filter_post_params.yaml"))
|
||||||
with vcr.use_cassette(cass_loc, filter_post_data_parameters=['key']) as cass:
|
with vcr.use_cassette(cass_loc, filter_post_data_parameters=["key"]) as cass:
|
||||||
requests.post(url, data={'key': 'value'})
|
requests.post(url, data={"key": "value"})
|
||||||
with vcr.use_cassette(cass_loc, filter_post_data_parameters=['key']) as cass:
|
with vcr.use_cassette(cass_loc, filter_post_data_parameters=["key"]) as cass:
|
||||||
assert b'key=value' not in cass.requests[0].body
|
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
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import vcr
|
import vcr
|
||||||
|
import zlib
|
||||||
|
import json
|
||||||
import six.moves.http_client as httplib
|
import six.moves.http_client as httplib
|
||||||
|
|
||||||
|
from assertions import assert_is_json
|
||||||
|
|
||||||
|
|
||||||
def _headers_are_case_insensitive(host, port):
|
def _headers_are_case_insensitive(host, port):
|
||||||
conn = httplib.HTTPConnection(host, port)
|
conn = httplib.HTTPConnection(host, port)
|
||||||
conn.request('GET', "/cookies/set?k1=v1")
|
conn.request("GET", "/cookies/set?k1=v1")
|
||||||
r1 = conn.getresponse()
|
r1 = conn.getresponse()
|
||||||
cookie_data1 = r1.getheader('set-cookie')
|
cookie_data1 = r1.getheader("set-cookie")
|
||||||
conn = httplib.HTTPConnection(host, port)
|
conn = httplib.HTTPConnection(host, port)
|
||||||
conn.request('GET', "/cookies/set?k1=v1")
|
conn.request("GET", "/cookies/set?k1=v1")
|
||||||
r2 = conn.getresponse()
|
r2 = conn.getresponse()
|
||||||
cookie_data2 = r2.getheader('Set-Cookie')
|
cookie_data2 = r2.getheader("Set-Cookie")
|
||||||
return cookie_data1 == cookie_data2
|
return cookie_data1 == cookie_data2
|
||||||
|
|
||||||
|
|
||||||
def test_case_insensitivity(tmpdir, httpbin):
|
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
|
# check if headers are case insensitive outside of vcrpy
|
||||||
host, port = httpbin.host, httpbin.port
|
host, port = httpbin.host, httpbin.port
|
||||||
outside = _headers_are_case_insensitive(host, port)
|
outside = _headers_are_case_insensitive(host, port)
|
||||||
@@ -31,16 +35,100 @@ def test_case_insensitivity(tmpdir, httpbin):
|
|||||||
|
|
||||||
def _multiple_header_value(httpbin):
|
def _multiple_header_value(httpbin):
|
||||||
conn = httplib.HTTPConnection(httpbin.host, httpbin.port)
|
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()
|
r = conn.getresponse()
|
||||||
return r.getheader('foo')
|
return r.getheader("foo")
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_headers(tmpdir, httpbin):
|
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)
|
outside = _multiple_header_value(httpbin)
|
||||||
|
|
||||||
with vcr.use_cassette(testfile):
|
with vcr.use_cassette(testfile):
|
||||||
inside = _multiple_header_value(httpbin)
|
inside = _multiple_header_value(httpbin)
|
||||||
|
|
||||||
assert outside == inside
|
assert outside == inside
|
||||||
|
|
||||||
|
|
||||||
|
def test_original_decoded_response_is_not_modified(tmpdir, httpbin):
|
||||||
|
testfile = str(tmpdir.join("decoded_response.yml"))
|
||||||
|
host, port = httpbin.host, httpbin.port
|
||||||
|
|
||||||
|
conn = httplib.HTTPConnection(host, port)
|
||||||
|
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")
|
||||||
|
inside = conn.getresponse()
|
||||||
|
|
||||||
|
# Assert that we do not modify the original response while appending
|
||||||
|
# to the casssette.
|
||||||
|
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")
|
||||||
|
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)
|
||||||
|
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")
|
||||||
|
inside = conn.getresponse()
|
||||||
|
|
||||||
|
assert "content-encoding" not in inside.headers
|
||||||
|
assert_is_json(inside.read())
|
||||||
|
|
||||||
|
|
||||||
|
def _make_before_record_response(fields, replacement="[REDACTED]"):
|
||||||
|
def before_record_response(response):
|
||||||
|
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()
|
||||||
|
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"))
|
||||||
|
host, port = httpbin.host, httpbin.port
|
||||||
|
field_to_scrub = "url"
|
||||||
|
replacement = "[YOU_CANT_HAVE_THE_MANGO]"
|
||||||
|
|
||||||
|
conn = httplib.HTTPConnection(host, port)
|
||||||
|
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")
|
||||||
|
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"))
|
||||||
|
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")
|
||||||
|
inside = conn.getresponse()
|
||||||
|
|
||||||
|
inside_body = json.loads(inside.read().decode("utf-8"))
|
||||||
|
assert inside_body[field_to_scrub] == replacement
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''Test requests' interaction with vcr'''
|
"""Test requests' interaction with vcr"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -17,105 +17,99 @@ http = pytest.importorskip("tornado.httpclient")
|
|||||||
supports_raise_error = tornado.version_info >= (4,)
|
supports_raise_error = tornado.version_info >= (4,)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=['simple', 'curl', 'default'])
|
@pytest.fixture(params=["simple", "curl", "default"])
|
||||||
def get_client(request):
|
def get_client(request):
|
||||||
if request.param == 'simple':
|
if request.param == "simple":
|
||||||
from tornado import simple_httpclient as 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")
|
curl = pytest.importorskip("tornado.curl_httpclient")
|
||||||
return (lambda: curl.CurlAsyncHTTPClient())
|
return lambda: curl.CurlAsyncHTTPClient()
|
||||||
else:
|
else:
|
||||||
return (lambda: http.AsyncHTTPClient())
|
return lambda: http.AsyncHTTPClient()
|
||||||
|
|
||||||
|
|
||||||
def get(client, url, **kwargs):
|
def get(client, url, **kwargs):
|
||||||
fetch_kwargs = {}
|
fetch_kwargs = {}
|
||||||
if supports_raise_error:
|
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(
|
return client.fetch(http.HTTPRequest(url, method="GET", **kwargs), **fetch_kwargs)
|
||||||
http.HTTPRequest(url, method='GET', **kwargs),
|
|
||||||
**fetch_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def post(client, url, data=None, **kwargs):
|
def post(client, url, data=None, **kwargs):
|
||||||
if data:
|
if data:
|
||||||
kwargs['body'] = json.dumps(data)
|
kwargs["body"] = json.dumps(data)
|
||||||
return client.fetch(http.HTTPRequest(url, method='POST', **kwargs))
|
return client.fetch(http.HTTPRequest(url, method="POST", **kwargs))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=["https", "http"])
|
@pytest.fixture(params=["https", "http"])
|
||||||
def scheme(request):
|
def scheme(request):
|
||||||
'''Fixture that returns both http and https.'''
|
"""Fixture that returns both http and https."""
|
||||||
return request.param
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_status_code(get_client, scheme, tmpdir):
|
def test_status_code(get_client, scheme, tmpdir):
|
||||||
'''Ensure that we can read the status code'''
|
"""Ensure that we can read the status code"""
|
||||||
url = scheme + '://httpbin.org/'
|
url = scheme + "://httpbin.org/"
|
||||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||||
status_code = (yield get(get_client(), url)).code
|
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 status_code == (yield get(get_client(), url)).code
|
||||||
assert 1 == cass.play_count
|
assert 1 == cass.play_count
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_headers(get_client, scheme, tmpdir):
|
def test_headers(get_client, scheme, tmpdir):
|
||||||
'''Ensure that we can read the headers back'''
|
"""Ensure that we can read the headers back"""
|
||||||
url = scheme + '://httpbin.org/'
|
url = scheme + "://httpbin.org/"
|
||||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||||
headers = (yield get(get_client(), url)).headers
|
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 headers == (yield get(get_client(), url)).headers
|
||||||
assert 1 == cass.play_count
|
assert 1 == cass.play_count
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_body(get_client, tmpdir, scheme):
|
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'
|
url = scheme + "://httpbin.org/bytes/1024"
|
||||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||||
content = (yield get(get_client(), url)).body
|
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 content == (yield get(get_client(), url)).body
|
||||||
assert 1 == cass.play_count
|
assert 1 == cass.play_count
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_effective_url(get_client, scheme, tmpdir):
|
def test_effective_url(get_client, scheme, tmpdir):
|
||||||
'''Ensure that the effective_url is captured'''
|
"""Ensure that the effective_url is captured"""
|
||||||
url = scheme + '://httpbin.org/redirect-to?url=/html'
|
url = scheme + "://httpbin.org/redirect-to?url=/html"
|
||||||
with vcr.use_cassette(str(tmpdir.join('url.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("url.yaml"))):
|
||||||
effective_url = (yield get(get_client(), url)).effective_url
|
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 effective_url == (yield get(get_client(), url)).effective_url
|
||||||
assert 1 == cass.play_count
|
assert 1 == cass.play_count
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_auth(get_client, tmpdir, scheme):
|
def test_auth(get_client, tmpdir, scheme):
|
||||||
'''Ensure that we can handle basic auth'''
|
"""Ensure that we can handle basic auth"""
|
||||||
auth = ('user', 'passwd')
|
auth = ("user", "passwd")
|
||||||
url = scheme + '://httpbin.org/basic-auth/user/passwd'
|
url = scheme + "://httpbin.org/basic-auth/user/passwd"
|
||||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))):
|
||||||
one = yield get(
|
one = yield get(get_client(), url, auth_username=auth[0], auth_password=auth[1])
|
||||||
get_client(), url, auth_username=auth[0], auth_password=auth[1]
|
|
||||||
)
|
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))) as cass:
|
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))) as cass:
|
||||||
two = yield get(
|
two = yield get(get_client(), url, auth_username=auth[0], auth_password=auth[1])
|
||||||
get_client(), url, auth_username=auth[0], auth_password=auth[1]
|
|
||||||
)
|
|
||||||
assert one.body == two.body
|
assert one.body == two.body
|
||||||
assert one.code == two.code
|
assert one.code == two.code
|
||||||
assert 1 == cass.play_count
|
assert 1 == cass.play_count
|
||||||
@@ -123,30 +117,20 @@ def test_auth(get_client, tmpdir, scheme):
|
|||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_auth_failed(get_client, tmpdir, scheme):
|
def test_auth_failed(get_client, tmpdir, scheme):
|
||||||
'''Ensure that we can save failed auth statuses'''
|
"""Ensure that we can save failed auth statuses"""
|
||||||
auth = ('user', 'wrongwrongwrong')
|
auth = ("user", "wrongwrongwrong")
|
||||||
url = scheme + '://httpbin.org/basic-auth/user/passwd'
|
url = scheme + "://httpbin.org/basic-auth/user/passwd"
|
||||||
with vcr.use_cassette(str(tmpdir.join('auth-failed.yaml'))) as cass:
|
with vcr.use_cassette(str(tmpdir.join("auth-failed.yaml"))) as cass:
|
||||||
# Ensure that this is empty to begin with
|
# Ensure that this is empty to begin with
|
||||||
assert_cassette_empty(cass)
|
assert_cassette_empty(cass)
|
||||||
with pytest.raises(http.HTTPError) as exc_info:
|
with pytest.raises(http.HTTPError) as exc_info:
|
||||||
yield get(
|
yield get(get_client(), url, auth_username=auth[0], auth_password=auth[1])
|
||||||
get_client(),
|
|
||||||
url,
|
|
||||||
auth_username=auth[0],
|
|
||||||
auth_password=auth[1],
|
|
||||||
)
|
|
||||||
one = exc_info.value.response
|
one = exc_info.value.response
|
||||||
assert exc_info.value.code == 401
|
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:
|
with pytest.raises(http.HTTPError) as exc_info:
|
||||||
two = yield get(
|
two = yield get(get_client(), url, auth_username=auth[0], auth_password=auth[1])
|
||||||
get_client(),
|
|
||||||
url,
|
|
||||||
auth_username=auth[0],
|
|
||||||
auth_password=auth[1],
|
|
||||||
)
|
|
||||||
two = exc_info.value.response
|
two = exc_info.value.response
|
||||||
assert exc_info.value.code == 401
|
assert exc_info.value.code == 401
|
||||||
assert one.body == two.body
|
assert one.body == two.body
|
||||||
@@ -156,13 +140,13 @@ def test_auth_failed(get_client, tmpdir, scheme):
|
|||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_post(get_client, tmpdir, scheme):
|
def test_post(get_client, tmpdir, scheme):
|
||||||
'''Ensure that we can post and cache the results'''
|
"""Ensure that we can post and cache the results"""
|
||||||
data = {'key1': 'value1', 'key2': 'value2'}
|
data = {"key1": "value1", "key2": "value2"}
|
||||||
url = scheme + '://httpbin.org/post'
|
url = scheme + "://httpbin.org/post"
|
||||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||||
req1 = (yield post(get_client(), url, data)).body
|
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
|
req2 = (yield post(get_client(), url, data)).body
|
||||||
|
|
||||||
assert req1 == req2
|
assert req1 == req2
|
||||||
@@ -171,55 +155,55 @@ def test_post(get_client, tmpdir, scheme):
|
|||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_redirects(get_client, tmpdir, scheme):
|
def test_redirects(get_client, tmpdir, scheme):
|
||||||
'''Ensure that we can handle redirects'''
|
"""Ensure that we can handle redirects"""
|
||||||
url = scheme + '://httpbin.org/redirect-to?url=bytes/1024'
|
url = scheme + "://httpbin.org/redirect-to?url=bytes/1024"
|
||||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("requests.yaml"))):
|
||||||
content = (yield get(get_client(), url)).body
|
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 content == (yield get(get_client(), url)).body
|
||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_cross_scheme(get_client, tmpdir, scheme):
|
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
|
# 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
|
# ensure that we haven't served anything out of cache, and we have two
|
||||||
# requests / response pairs in the cassette
|
# 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:
|
||||||
yield get(get_client(), 'https://httpbin.org/')
|
yield get(get_client(), "https://httpbin.org/")
|
||||||
yield get(get_client(), 'http://httpbin.org/')
|
yield get(get_client(), "http://httpbin.org/")
|
||||||
assert cass.play_count == 0
|
assert cass.play_count == 0
|
||||||
assert len(cass) == 2
|
assert len(cass) == 2
|
||||||
|
|
||||||
# Then repeat the same requests and ensure both were replayed.
|
# Then repeat the same requests and ensure both were replayed.
|
||||||
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
|
with vcr.use_cassette(str(tmpdir.join("cross_scheme.yaml"))) as cass:
|
||||||
yield get(get_client(), 'https://httpbin.org/')
|
yield get(get_client(), "https://httpbin.org/")
|
||||||
yield get(get_client(), 'http://httpbin.org/')
|
yield get(get_client(), "http://httpbin.org/")
|
||||||
assert cass.play_count == 2
|
assert cass.play_count == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_gzip(get_client, tmpdir, scheme):
|
def test_gzip(get_client, tmpdir, scheme):
|
||||||
'''
|
"""
|
||||||
Ensure that httpclient is able to automatically decompress the response
|
Ensure that httpclient is able to automatically decompress the response
|
||||||
body
|
body
|
||||||
'''
|
"""
|
||||||
url = scheme + '://httpbin.org/gzip'
|
url = scheme + "://httpbin.org/gzip"
|
||||||
|
|
||||||
# use_gzip was renamed to decompress_response in 4.0
|
# use_gzip was renamed to decompress_response in 4.0
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if tornado.version_info < (4,):
|
if tornado.version_info < (4,):
|
||||||
kwargs['use_gzip'] = True
|
kwargs["use_gzip"] = True
|
||||||
else:
|
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)
|
response = yield get(get_client(), url, **kwargs)
|
||||||
assert_is_json(response.body)
|
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)
|
response = yield get(get_client(), url, **kwargs)
|
||||||
assert_is_json(response.body)
|
assert_is_json(response.body)
|
||||||
assert 1 == cass.play_count
|
assert 1 == cass.play_count
|
||||||
@@ -227,28 +211,26 @@ def test_gzip(get_client, tmpdir, scheme):
|
|||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_https_with_cert_validation_disabled(get_client, tmpdir):
|
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):
|
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:
|
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
|
assert 1 == cass.play_count
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_unsupported_features_raises_in_future(get_client, tmpdir):
|
def test_unsupported_features_raises_in_future(get_client, tmpdir):
|
||||||
'''Ensure that the exception for an AsyncHTTPClient feature not being
|
"""Ensure that the exception for an AsyncHTTPClient feature not being
|
||||||
supported is raised inside the future.'''
|
supported is raised inside the future."""
|
||||||
|
|
||||||
def callback(chunk):
|
def callback(chunk):
|
||||||
assert False, "Did not expect to be called."
|
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"))):
|
||||||
future = get(
|
future = get(get_client(), "http://httpbin.org", streaming_callback=callback)
|
||||||
get_client(), 'http://httpbin.org', streaming_callback=callback
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(Exception) as excinfo:
|
with pytest.raises(Exception) as excinfo:
|
||||||
yield future
|
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)
|
assert "not yet supported by VCR" in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(not supports_raise_error, reason="raise_error unavailable in tornado <= 3")
|
||||||
not supports_raise_error,
|
|
||||||
reason='raise_error unavailable in tornado <= 3',
|
|
||||||
)
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_unsupported_features_raise_error_disabled(get_client, tmpdir):
|
def test_unsupported_features_raise_error_disabled(get_client, tmpdir):
|
||||||
'''Ensure that the exception for an AsyncHTTPClient feature not being
|
"""Ensure that the exception for an AsyncHTTPClient feature not being
|
||||||
supported is not raised if raise_error=False.'''
|
supported is not raised if raise_error=False."""
|
||||||
|
|
||||||
def callback(chunk):
|
def callback(chunk):
|
||||||
assert False, "Did not expect to be called."
|
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(
|
response = yield get(
|
||||||
get_client(),
|
get_client(), "http://httpbin.org", streaming_callback=callback, raise_error=False
|
||||||
'http://httpbin.org',
|
|
||||||
streaming_callback=callback,
|
|
||||||
raise_error=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "not yet supported by VCR" in str(response.error)
|
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
|
@pytest.mark.gen_test
|
||||||
def test_cannot_overwrite_cassette_raises_in_future(get_client, tmpdir):
|
def test_cannot_overwrite_cassette_raises_in_future(get_client, tmpdir):
|
||||||
'''Ensure that CannotOverwriteExistingCassetteException is raised inside
|
"""Ensure that CannotOverwriteExistingCassetteException is raised inside
|
||||||
the future.'''
|
the future."""
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('overwrite.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("overwrite.yaml"))):
|
||||||
yield get(get_client(), 'http://httpbin.org/get')
|
yield get(get_client(), "http://httpbin.org/get")
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('overwrite.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("overwrite.yaml"))):
|
||||||
future = get(get_client(), 'http://httpbin.org/headers')
|
future = get(get_client(), "http://httpbin.org/headers")
|
||||||
|
|
||||||
with pytest.raises(CannotOverwriteExistingCassetteException):
|
with pytest.raises(CannotOverwriteExistingCassetteException):
|
||||||
yield future
|
yield future
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(not supports_raise_error, reason="raise_error unavailable in tornado <= 3")
|
||||||
not supports_raise_error,
|
|
||||||
reason='raise_error unavailable in tornado <= 3',
|
|
||||||
)
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_cannot_overwrite_cassette_raise_error_disabled(get_client, tmpdir):
|
def test_cannot_overwrite_cassette_raise_error_disabled(get_client, tmpdir):
|
||||||
'''Ensure that CannotOverwriteExistingCassetteException is not raised if
|
"""Ensure that CannotOverwriteExistingCassetteException is not raised if
|
||||||
raise_error=False in the fetch() call.'''
|
raise_error=False in the fetch() call."""
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('overwrite.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("overwrite.yaml"))):
|
||||||
yield get(
|
yield get(get_client(), "http://httpbin.org/get", raise_error=False)
|
||||||
get_client(), 'http://httpbin.org/get', raise_error=False
|
|
||||||
)
|
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('overwrite.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("overwrite.yaml"))):
|
||||||
response = yield get(
|
response = yield get(get_client(), "http://httpbin.org/headers", raise_error=False)
|
||||||
get_client(), 'http://httpbin.org/headers', raise_error=False
|
|
||||||
)
|
|
||||||
|
|
||||||
assert isinstance(response.error, CannotOverwriteExistingCassetteException)
|
assert isinstance(response.error, CannotOverwriteExistingCassetteException)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@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):
|
def test_tornado_with_decorator_use_cassette(get_client):
|
||||||
response = yield get_client().fetch(
|
response = yield get_client().fetch(http.HTTPRequest("http://www.google.com/", method="GET"))
|
||||||
http.HTTPRequest('http://www.google.com/', method='GET')
|
assert response.body.decode("utf-8") == "not actually google"
|
||||||
)
|
|
||||||
assert response.body.decode('utf-8') == "not actually google"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@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):
|
def test_tornado_exception_can_be_caught(get_client):
|
||||||
try:
|
try:
|
||||||
yield get(get_client(), 'http://httpbin.org/status/500')
|
yield get(get_client(), "http://httpbin.org/status/500")
|
||||||
except http.HTTPError as e:
|
except http.HTTPError as e:
|
||||||
assert e.code == 500
|
assert e.code == 500
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield get(get_client(), 'http://httpbin.org/status/404')
|
yield get(get_client(), "http://httpbin.org/status/404")
|
||||||
except http.HTTPError as e:
|
except http.HTTPError as e:
|
||||||
assert e.code == 404
|
assert e.code == 404
|
||||||
|
|
||||||
@@ -343,41 +310,41 @@ def test_tornado_exception_can_be_caught(get_client):
|
|||||||
def test_existing_references_get_patched(tmpdir):
|
def test_existing_references_get_patched(tmpdir):
|
||||||
from tornado.httpclient import AsyncHTTPClient
|
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()
|
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:
|
with vcr.use_cassette(str(tmpdir.join("data.yaml"))) as cass:
|
||||||
yield get(client, 'http://httpbin.org/get')
|
yield get(client, "http://httpbin.org/get")
|
||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_existing_instances_get_patched(get_client, tmpdir):
|
def test_existing_instances_get_patched(get_client, tmpdir):
|
||||||
'''Ensure that existing instances of AsyncHTTPClient get patched upon
|
"""Ensure that existing instances of AsyncHTTPClient get patched upon
|
||||||
entering VCR context.'''
|
entering VCR context."""
|
||||||
|
|
||||||
client = get_client()
|
client = get_client()
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('data.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("data.yaml"))):
|
||||||
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:
|
with vcr.use_cassette(str(tmpdir.join("data.yaml"))) as cass:
|
||||||
yield get(client, 'http://httpbin.org/get')
|
yield get(client, "http://httpbin.org/get")
|
||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_request_time_is_set(get_client, tmpdir):
|
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()
|
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 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()
|
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 response.request_time is not None
|
||||||
assert cass.play_count == 1
|
assert cass.play_count == 1
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''Integration tests with urllib2'''
|
"""Integration tests with urllib2"""
|
||||||
|
|
||||||
|
import ssl
|
||||||
from six.moves.urllib.request import urlopen
|
from six.moves.urllib.request import urlopen
|
||||||
from six.moves.urllib_parse import urlencode
|
from six.moves.urllib_parse import urlencode
|
||||||
import pytest_httpbin.certs
|
import pytest_httpbin.certs
|
||||||
@@ -12,91 +13,88 @@ from assertions import assert_cassette_has_one_response
|
|||||||
|
|
||||||
|
|
||||||
def urlopen_with_cafile(*args, **kwargs):
|
def urlopen_with_cafile(*args, **kwargs):
|
||||||
kwargs['cafile'] = pytest_httpbin.certs.where()
|
context = ssl.create_default_context(cafile=pytest_httpbin.certs.where())
|
||||||
|
context.check_hostname = False
|
||||||
|
kwargs["context"] = context
|
||||||
try:
|
try:
|
||||||
return urlopen(*args, **kwargs)
|
return urlopen(*args, **kwargs)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# python2/pypi don't let us override this
|
# python2/pypi don't let us override this
|
||||||
del kwargs['cafile']
|
del kwargs["cafile"]
|
||||||
return urlopen(*args, **kwargs)
|
return urlopen(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def test_response_code(httpbin_both, tmpdir):
|
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
|
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()
|
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()
|
assert code == urlopen_with_cafile(url).getcode()
|
||||||
|
|
||||||
|
|
||||||
def test_random_body(httpbin_both, tmpdir):
|
def test_random_body(httpbin_both, tmpdir):
|
||||||
'''Ensure we can read the content, and that it's served from cache'''
|
"""Ensure we can read the content, and that it's served from cache"""
|
||||||
url = httpbin_both.url + '/bytes/1024'
|
url = httpbin_both.url + "/bytes/1024"
|
||||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||||
body = urlopen_with_cafile(url).read()
|
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()
|
assert body == urlopen_with_cafile(url).read()
|
||||||
|
|
||||||
|
|
||||||
def test_response_headers(httpbin_both, tmpdir):
|
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
|
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()
|
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()
|
open2 = urlopen_with_cafile(url).info().items()
|
||||||
|
|
||||||
assert sorted(open1) == sorted(open2)
|
assert sorted(open1) == sorted(open2)
|
||||||
|
|
||||||
|
|
||||||
def test_effective_url(httpbin_both, tmpdir):
|
def test_effective_url(httpbin_both, tmpdir):
|
||||||
'''Ensure that the effective_url is captured'''
|
"""Ensure that the effective_url is captured"""
|
||||||
url = httpbin_both.url + '/redirect-to?url=/html'
|
url = httpbin_both.url + "/redirect-to?url=/html"
|
||||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||||
effective_url = urlopen_with_cafile(url).geturl()
|
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()
|
assert effective_url == urlopen_with_cafile(url).geturl()
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_requests(httpbin_both, tmpdir):
|
def test_multiple_requests(httpbin_both, tmpdir):
|
||||||
'''Ensure that we can cache multiple requests'''
|
"""Ensure that we can cache multiple requests"""
|
||||||
urls = [
|
urls = [httpbin_both.url, httpbin_both.url, httpbin_both.url + "/get", httpbin_both.url + "/bytes/1024"]
|
||||||
httpbin_both.url,
|
with vcr.use_cassette(str(tmpdir.join("multiple.yaml"))) as cass:
|
||||||
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]
|
[urlopen_with_cafile(url) for url in urls]
|
||||||
assert len(cass) == len(urls)
|
assert len(cass) == len(urls)
|
||||||
|
|
||||||
|
|
||||||
def test_get_data(httpbin_both, tmpdir):
|
def test_get_data(httpbin_both, tmpdir):
|
||||||
'''Ensure that it works with query data'''
|
"""Ensure that it works with query data"""
|
||||||
data = urlencode({'some': 1, 'data': 'here'})
|
data = urlencode({"some": 1, "data": "here"})
|
||||||
url = httpbin_both.url + '/get?' + data
|
url = httpbin_both.url + "/get?" + data
|
||||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("get_data.yaml"))):
|
||||||
res1 = urlopen_with_cafile(url).read()
|
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()
|
res2 = urlopen_with_cafile(url).read()
|
||||||
assert res1 == res2
|
assert res1 == res2
|
||||||
|
|
||||||
|
|
||||||
def test_post_data(httpbin_both, tmpdir):
|
def test_post_data(httpbin_both, tmpdir):
|
||||||
'''Ensure that it works when posting data'''
|
"""Ensure that it works when posting data"""
|
||||||
data = urlencode({'some': 1, 'data': 'here'}).encode('utf-8')
|
data = urlencode({"some": 1, "data": "here"}).encode("utf-8")
|
||||||
url = httpbin_both.url + '/post'
|
url = httpbin_both.url + "/post"
|
||||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||||
res1 = urlopen_with_cafile(url, data).read()
|
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()
|
res2 = urlopen_with_cafile(url, data).read()
|
||||||
assert len(cass) == 1
|
assert len(cass) == 1
|
||||||
|
|
||||||
@@ -105,13 +103,13 @@ def test_post_data(httpbin_both, tmpdir):
|
|||||||
|
|
||||||
|
|
||||||
def test_post_unicode_data(httpbin_both, tmpdir):
|
def test_post_unicode_data(httpbin_both, tmpdir):
|
||||||
'''Ensure that it works when posting unicode data'''
|
"""Ensure that it works when posting unicode data"""
|
||||||
data = urlencode({'snowman': u'☃'.encode('utf-8')}).encode('utf-8')
|
data = urlencode({"snowman": u"☃".encode("utf-8")}).encode("utf-8")
|
||||||
url = httpbin_both.url + '/post'
|
url = httpbin_both.url + "/post"
|
||||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||||
res1 = urlopen_with_cafile(url, data).read()
|
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()
|
res2 = urlopen_with_cafile(url, data).read()
|
||||||
assert len(cass) == 1
|
assert len(cass) == 1
|
||||||
|
|
||||||
@@ -120,11 +118,11 @@ def test_post_unicode_data(httpbin_both, tmpdir):
|
|||||||
|
|
||||||
|
|
||||||
def test_cross_scheme(tmpdir, httpbin_secure, httpbin):
|
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
|
# 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
|
# ensure that we haven't served anything out of cache, and we have two
|
||||||
# requests / response pairs in the cassette
|
# 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_secure.url)
|
||||||
urlopen_with_cafile(httpbin.url)
|
urlopen_with_cafile(httpbin.url)
|
||||||
assert len(cass) == 2
|
assert len(cass) == 2
|
||||||
@@ -132,14 +130,14 @@ def test_cross_scheme(tmpdir, httpbin_secure, httpbin):
|
|||||||
|
|
||||||
|
|
||||||
def test_decorator(httpbin_both, tmpdir):
|
def test_decorator(httpbin_both, tmpdir):
|
||||||
'''Test the decorator version of VCR.py'''
|
"""Test the decorator version of VCR.py"""
|
||||||
url = httpbin_both.url
|
url = httpbin_both.url
|
||||||
|
|
||||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
@vcr.use_cassette(str(tmpdir.join("atts.yaml")))
|
||||||
def inner1():
|
def inner1():
|
||||||
return urlopen_with_cafile(url).getcode()
|
return urlopen_with_cafile(url).getcode()
|
||||||
|
|
||||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
@vcr.use_cassette(str(tmpdir.join("atts.yaml")))
|
||||||
def inner2():
|
def inner2():
|
||||||
return urlopen_with_cafile(url).getcode()
|
return urlopen_with_cafile(url).getcode()
|
||||||
|
|
||||||
|
|||||||
@@ -1,106 +1,107 @@
|
|||||||
'''Integration tests with urllib3'''
|
"""Integration tests with urllib3"""
|
||||||
|
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_httpbin
|
import pytest_httpbin
|
||||||
import vcr
|
import vcr
|
||||||
|
from vcr.patch import force_reset
|
||||||
from assertions import assert_cassette_empty, assert_is_json
|
from assertions import assert_cassette_empty, assert_is_json
|
||||||
|
|
||||||
urllib3 = pytest.importorskip("urllib3")
|
urllib3 = pytest.importorskip("urllib3")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='module')
|
@pytest.fixture(scope="module")
|
||||||
def verify_pool_mgr():
|
def verify_pool_mgr():
|
||||||
return urllib3.PoolManager(
|
return urllib3.PoolManager(
|
||||||
cert_reqs='CERT_REQUIRED', # Force certificate check.
|
cert_reqs="CERT_REQUIRED", ca_certs=pytest_httpbin.certs.where() # Force certificate check.
|
||||||
ca_certs=pytest_httpbin.certs.where()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='module')
|
@pytest.fixture(scope="module")
|
||||||
def pool_mgr():
|
def pool_mgr():
|
||||||
return urllib3.PoolManager()
|
return urllib3.PoolManager(cert_reqs="CERT_NONE")
|
||||||
|
|
||||||
|
|
||||||
def test_status_code(httpbin_both, tmpdir, verify_pool_mgr):
|
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
|
url = httpbin_both.url
|
||||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||||
status_code = verify_pool_mgr.request('GET', url).status
|
status_code = verify_pool_mgr.request("GET", url).status
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||||
assert status_code == verify_pool_mgr.request('GET', url).status
|
assert status_code == verify_pool_mgr.request("GET", url).status
|
||||||
|
|
||||||
|
|
||||||
def test_headers(tmpdir, httpbin_both, verify_pool_mgr):
|
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
|
url = httpbin_both.url
|
||||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||||
headers = verify_pool_mgr.request('GET', url).headers
|
headers = verify_pool_mgr.request("GET", url).headers
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||||
assert headers == verify_pool_mgr.request('GET', url).headers
|
assert headers == verify_pool_mgr.request("GET", url).headers
|
||||||
|
|
||||||
|
|
||||||
def test_body(tmpdir, httpbin_both, verify_pool_mgr):
|
def test_body(tmpdir, httpbin_both, verify_pool_mgr):
|
||||||
'''Ensure the responses are all identical enough'''
|
"""Ensure the responses are all identical enough"""
|
||||||
url = httpbin_both.url + '/bytes/1024'
|
url = httpbin_both.url + "/bytes/1024"
|
||||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||||
content = verify_pool_mgr.request('GET', url).data
|
content = verify_pool_mgr.request("GET", url).data
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||||
assert content == verify_pool_mgr.request('GET', url).data
|
assert content == verify_pool_mgr.request("GET", url).data
|
||||||
|
|
||||||
|
|
||||||
def test_auth(tmpdir, httpbin_both, verify_pool_mgr):
|
def test_auth(tmpdir, httpbin_both, verify_pool_mgr):
|
||||||
'''Ensure that we can handle basic auth'''
|
"""Ensure that we can handle basic auth"""
|
||||||
auth = ('user', 'passwd')
|
auth = ("user", "passwd")
|
||||||
headers = urllib3.util.make_headers(basic_auth='{0}:{1}'.format(*auth))
|
headers = urllib3.util.make_headers(basic_auth="{}:{}".format(*auth))
|
||||||
url = httpbin_both.url + '/basic-auth/user/passwd'
|
url = httpbin_both.url + "/basic-auth/user/passwd"
|
||||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))):
|
||||||
one = verify_pool_mgr.request('GET', url, headers=headers)
|
one = verify_pool_mgr.request("GET", url, headers=headers)
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("auth.yaml"))):
|
||||||
two = verify_pool_mgr.request('GET', url, headers=headers)
|
two = verify_pool_mgr.request("GET", url, headers=headers)
|
||||||
assert one.data == two.data
|
assert one.data == two.data
|
||||||
assert one.status == two.status
|
assert one.status == two.status
|
||||||
|
|
||||||
|
|
||||||
def test_auth_failed(tmpdir, httpbin_both, verify_pool_mgr):
|
def test_auth_failed(tmpdir, httpbin_both, verify_pool_mgr):
|
||||||
'''Ensure that we can save failed auth statuses'''
|
"""Ensure that we can save failed auth statuses"""
|
||||||
auth = ('user', 'wrongwrongwrong')
|
auth = ("user", "wrongwrongwrong")
|
||||||
headers = urllib3.util.make_headers(basic_auth='{0}:{1}'.format(*auth))
|
headers = urllib3.util.make_headers(basic_auth="{}:{}".format(*auth))
|
||||||
url = httpbin_both.url + '/basic-auth/user/passwd'
|
url = httpbin_both.url + "/basic-auth/user/passwd"
|
||||||
with vcr.use_cassette(str(tmpdir.join('auth-failed.yaml'))) as cass:
|
with vcr.use_cassette(str(tmpdir.join("auth-failed.yaml"))) as cass:
|
||||||
# Ensure that this is empty to begin with
|
# Ensure that this is empty to begin with
|
||||||
assert_cassette_empty(cass)
|
assert_cassette_empty(cass)
|
||||||
one = 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)
|
two = verify_pool_mgr.request("GET", url, headers=headers)
|
||||||
assert one.data == two.data
|
assert one.data == two.data
|
||||||
assert one.status == two.status == 401
|
assert one.status == two.status == 401
|
||||||
|
|
||||||
|
|
||||||
def test_post(tmpdir, httpbin_both, verify_pool_mgr):
|
def test_post(tmpdir, httpbin_both, verify_pool_mgr):
|
||||||
'''Ensure that we can post and cache the results'''
|
"""Ensure that we can post and cache the results"""
|
||||||
data = {'key1': 'value1', 'key2': 'value2'}
|
data = {"key1": "value1", "key2": "value2"}
|
||||||
url = httpbin_both.url + '/post'
|
url = httpbin_both.url + "/post"
|
||||||
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))):
|
||||||
req1 = verify_pool_mgr.request('POST', url, data).data
|
req1 = verify_pool_mgr.request("POST", url, data).data
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))):
|
||||||
req2 = verify_pool_mgr.request('POST', url, data).data
|
req2 = verify_pool_mgr.request("POST", url, data).data
|
||||||
|
|
||||||
assert req1 == req2
|
assert req1 == req2
|
||||||
|
|
||||||
|
|
||||||
def test_redirects(tmpdir, httpbin_both, verify_pool_mgr):
|
def test_redirects(tmpdir, httpbin_both, verify_pool_mgr):
|
||||||
'''Ensure that we can handle redirects'''
|
"""Ensure that we can handle redirects"""
|
||||||
url = httpbin_both.url + '/redirect-to?url=bytes/1024'
|
url = httpbin_both.url + "/redirect-to?url=bytes/1024"
|
||||||
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))):
|
||||||
content = verify_pool_mgr.request('GET', url).data
|
content = verify_pool_mgr.request("GET", url).data
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))) as cass:
|
with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))) as cass:
|
||||||
assert content == verify_pool_mgr.request('GET', url).data
|
assert content == verify_pool_mgr.request("GET", url).data
|
||||||
# Ensure that we've now cached *two* responses. One for the redirect
|
# Ensure that we've now cached *two* responses. One for the redirect
|
||||||
# and one for the final fetch
|
# and one for the final fetch
|
||||||
assert len(cass) == 2
|
assert len(cass) == 2
|
||||||
@@ -108,33 +109,51 @@ def test_redirects(tmpdir, httpbin_both, verify_pool_mgr):
|
|||||||
|
|
||||||
|
|
||||||
def test_cross_scheme(tmpdir, httpbin, httpbin_secure, 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
|
# 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
|
# ensure that we haven't served anything out of cache, and we have two
|
||||||
# requests / response pairs in the cassette
|
# 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:
|
||||||
verify_pool_mgr.request('GET', httpbin_secure.url)
|
verify_pool_mgr.request("GET", httpbin_secure.url)
|
||||||
verify_pool_mgr.request('GET', httpbin.url)
|
verify_pool_mgr.request("GET", httpbin.url)
|
||||||
assert cass.play_count == 0
|
assert cass.play_count == 0
|
||||||
assert len(cass) == 2
|
assert len(cass) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_gzip(tmpdir, httpbin_both, verify_pool_mgr):
|
def test_gzip(tmpdir, httpbin_both, verify_pool_mgr):
|
||||||
'''
|
"""
|
||||||
Ensure that requests (actually urllib3) is able to automatically decompress
|
Ensure that requests (actually urllib3) is able to automatically decompress
|
||||||
the response body
|
the response body
|
||||||
'''
|
"""
|
||||||
url = httpbin_both.url + '/gzip'
|
url = httpbin_both.url + "/gzip"
|
||||||
response = verify_pool_mgr.request('GET', url)
|
response = verify_pool_mgr.request("GET", url)
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("gzip.yaml"))):
|
||||||
response = verify_pool_mgr.request('GET', url)
|
response = verify_pool_mgr.request("GET", url)
|
||||||
assert_is_json(response.data)
|
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)
|
assert_is_json(response.data)
|
||||||
|
|
||||||
|
|
||||||
def test_https_with_cert_validation_disabled(tmpdir, httpbin_secure, pool_mgr):
|
def test_https_with_cert_validation_disabled(tmpdir, httpbin_secure, pool_mgr):
|
||||||
with vcr.use_cassette(str(tmpdir.join('cert_validation_disabled.yaml'))):
|
with vcr.use_cassette(str(tmpdir.join("cert_validation_disabled.yaml"))):
|
||||||
pool_mgr.request('GET', httpbin_secure.url)
|
pool_mgr.request("GET", httpbin_secure.url)
|
||||||
|
|
||||||
|
|
||||||
|
def test_urllib3_force_reset():
|
||||||
|
cpool = urllib3.connectionpool
|
||||||
|
http_original = cpool.HTTPConnection
|
||||||
|
https_original = cpool.HTTPSConnection
|
||||||
|
verified_https_original = cpool.VerifiedHTTPSConnection
|
||||||
|
with vcr.use_cassette(path="test"):
|
||||||
|
first_cassette_HTTPConnection = cpool.HTTPConnection
|
||||||
|
first_cassette_HTTPSConnection = cpool.HTTPSConnection
|
||||||
|
first_cassette_VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
|
||||||
|
with force_reset():
|
||||||
|
assert cpool.HTTPConnection is http_original
|
||||||
|
assert cpool.HTTPSConnection is https_original
|
||||||
|
assert cpool.VerifiedHTTPSConnection is verified_https_original
|
||||||
|
assert cpool.HTTPConnection is first_cassette_HTTPConnection
|
||||||
|
assert cpool.HTTPSConnection is first_cassette_HTTPSConnection
|
||||||
|
assert cpool.VerifiedHTTPSConnection is first_cassette_VerifiedHTTPSConnection
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import multiprocessing
|
||||||
import pytest
|
import pytest
|
||||||
from six.moves import xmlrpc_client
|
from six.moves import xmlrpc_client, xmlrpc_server
|
||||||
|
|
||||||
requests = pytest.importorskip("requests")
|
requests = pytest.importorskip("requests")
|
||||||
|
|
||||||
@@ -12,13 +13,13 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
def test_domain_redirect():
|
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
|
# In this example, seomoz.org redirects to moz.com, and if those
|
||||||
# requests are considered identical, then we'll be stuck in a redirect
|
# requests are considered identical, then we'll be stuck in a redirect
|
||||||
# loop.
|
# loop.
|
||||||
url = 'http://seomoz.org/'
|
url = "http://seomoz.org/"
|
||||||
with vcr.use_cassette('tests/fixtures/wild/domain_redirect.yaml') as cass:
|
with vcr.use_cassette("tests/fixtures/wild/domain_redirect.yaml") as cass:
|
||||||
requests.get(url, headers={'User-Agent': 'vcrpy-test'})
|
requests.get(url, headers={"User-Agent": "vcrpy-test"})
|
||||||
# Ensure that we've now served two responses. One for the original
|
# Ensure that we've now served two responses. One for the original
|
||||||
# redirect, and a second for the actual fetch
|
# redirect, and a second for the actual fetch
|
||||||
assert len(cass) == 2
|
assert len(cass) == 2
|
||||||
@@ -29,13 +30,11 @@ def test_flickr_multipart_upload(httpbin, tmpdir):
|
|||||||
The python-flickr-api project does a multipart
|
The python-flickr-api project does a multipart
|
||||||
upload that confuses vcrpy
|
upload that confuses vcrpy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _pretend_to_be_flickr_library():
|
def _pretend_to_be_flickr_library():
|
||||||
content_type, body = "text/plain", "HELLO WORLD"
|
content_type, body = "text/plain", "HELLO WORLD"
|
||||||
h = httplib.HTTPConnection(httpbin.host, httpbin.port)
|
h = httplib.HTTPConnection(httpbin.host, httpbin.port)
|
||||||
headers = {
|
headers = {"Content-Type": content_type, "content-length": str(len(body))}
|
||||||
"Content-Type": content_type,
|
|
||||||
"content-length": str(len(body))
|
|
||||||
}
|
|
||||||
h.request("POST", "/post/", headers=headers)
|
h.request("POST", "/post/", headers=headers)
|
||||||
h.send(body)
|
h.send(body)
|
||||||
r = h.getresponse()
|
r = h.getresponse()
|
||||||
@@ -44,7 +43,7 @@ def test_flickr_multipart_upload(httpbin, tmpdir):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
testfile = str(tmpdir.join('flickr.yml'))
|
testfile = str(tmpdir.join("flickr.yml"))
|
||||||
with vcr.use_cassette(testfile) as cass:
|
with vcr.use_cassette(testfile) as cass:
|
||||||
_pretend_to_be_flickr_library()
|
_pretend_to_be_flickr_library()
|
||||||
assert len(cass) == 1
|
assert len(cass) == 1
|
||||||
@@ -56,37 +55,55 @@ def test_flickr_multipart_upload(httpbin, tmpdir):
|
|||||||
|
|
||||||
|
|
||||||
def test_flickr_should_respond_with_200(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):
|
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
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_cookies(tmpdir, httpbin):
|
def test_cookies(tmpdir, httpbin):
|
||||||
testfile = str(tmpdir.join('cookies.yml'))
|
testfile = str(tmpdir.join("cookies.yml"))
|
||||||
with vcr.use_cassette(testfile):
|
with vcr.use_cassette(testfile):
|
||||||
s = requests.Session()
|
s = requests.Session()
|
||||||
s.get(httpbin.url + "/cookies/set?k1=v1&k2=v2")
|
s.get(httpbin.url + "/cookies/set?k1=v1&k2=v2")
|
||||||
|
|
||||||
r2 = s.get(httpbin.url + "/cookies")
|
r2 = s.get(httpbin.url + "/cookies")
|
||||||
assert len(r2.json()['cookies']) == 2
|
assert len(r2.json()["cookies"]) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_amazon_doctype(tmpdir):
|
def test_amazon_doctype(tmpdir):
|
||||||
# amazon gzips its homepage. For some reason, in requests 2.7, it's not
|
# amazon gzips its homepage. For some reason, in requests 2.7, it's not
|
||||||
# getting gunzipped.
|
# getting gunzipped.
|
||||||
with vcr.use_cassette(str(tmpdir.join('amz.yml'))):
|
with vcr.use_cassette(str(tmpdir.join("amz.yml"))):
|
||||||
r = requests.get('http://www.amazon.com')
|
r = requests.get("http://www.amazon.com", verify=False)
|
||||||
assert 'html' in r.text
|
assert "html" in r.text
|
||||||
|
|
||||||
|
|
||||||
def test_xmlrpclib(tmpdir):
|
def start_rpc_server(q):
|
||||||
with vcr.use_cassette(str(tmpdir.join('xmlrpcvideo.yaml'))):
|
httpd = xmlrpc_server.SimpleXMLRPCServer(("127.0.0.1", 0))
|
||||||
roundup_server = xmlrpc_client.ServerProxy('http://bugs.python.org/xmlrpc', allow_none=True)
|
httpd.register_function(pow)
|
||||||
original_schema = roundup_server.schema()
|
q.put("http://{}:{}".format(*httpd.server_address))
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||
with vcr.use_cassette(str(tmpdir.join('xmlrpcvideo.yaml'))):
|
|
||||||
roundup_server = xmlrpc_client.ServerProxy('http://bugs.python.org/xmlrpc', allow_none=True)
|
@pytest.yield_fixture(scope="session")
|
||||||
second_schema = roundup_server.schema()
|
def rpc_server():
|
||||||
|
q = multiprocessing.Queue()
|
||||||
|
proxy_process = multiprocessing.Process(target=start_rpc_server, args=(q,))
|
||||||
|
try:
|
||||||
|
proxy_process.start()
|
||||||
|
yield q.get()
|
||||||
|
finally:
|
||||||
|
proxy_process.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
def test_xmlrpclib(tmpdir, rpc_server):
|
||||||
|
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"))):
|
||||||
|
roundup_server = xmlrpc_client.ServerProxy(rpc_server, allow_none=True)
|
||||||
|
second_schema = roundup_server.pow(2, 4)
|
||||||
|
|
||||||
assert original_schema == second_schema
|
assert original_schema == second_schema
|
||||||
|
|||||||
@@ -14,31 +14,36 @@ from vcr.stubs import VCRHTTPSConnection
|
|||||||
|
|
||||||
|
|
||||||
def test_cassette_load(tmpdir):
|
def test_cassette_load(tmpdir):
|
||||||
a_file = tmpdir.join('test_cassette.yml')
|
a_file = tmpdir.join("test_cassette.yml")
|
||||||
a_file.write(yaml.dump({'interactions': [
|
a_file.write(
|
||||||
{'request': {'body': '', 'uri': 'foo', 'method': 'GET', 'headers': {}},
|
yaml.dump(
|
||||||
'response': 'bar'}
|
{
|
||||||
]}))
|
"interactions": [
|
||||||
|
{"request": {"body": "", "uri": "foo", "method": "GET", "headers": {}}, "response": "bar"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
a_cassette = Cassette.load(path=str(a_file))
|
a_cassette = Cassette.load(path=str(a_file))
|
||||||
assert len(a_cassette) == 1
|
assert len(a_cassette) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_cassette_not_played():
|
def test_cassette_not_played():
|
||||||
a = Cassette('test')
|
a = Cassette("test")
|
||||||
assert not a.play_count
|
assert not a.play_count
|
||||||
|
|
||||||
|
|
||||||
def test_cassette_append():
|
def test_cassette_append():
|
||||||
a = Cassette('test')
|
a = Cassette("test")
|
||||||
a.append('foo', 'bar')
|
a.append("foo", "bar")
|
||||||
assert a.requests == ['foo']
|
assert a.requests == ["foo"]
|
||||||
assert a.responses == ['bar']
|
assert a.responses == ["bar"]
|
||||||
|
|
||||||
|
|
||||||
def test_cassette_len():
|
def test_cassette_len():
|
||||||
a = Cassette('test')
|
a = Cassette("test")
|
||||||
a.append('foo', 'bar')
|
a.append("foo", "bar")
|
||||||
a.append('foo2', 'bar2')
|
a.append("foo2", "bar2")
|
||||||
assert len(a) == 2
|
assert len(a) == 2
|
||||||
|
|
||||||
|
|
||||||
@@ -46,34 +51,34 @@ def _mock_requests_match(request1, request2, matchers):
|
|||||||
return request1 == request2
|
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():
|
def test_cassette_contains():
|
||||||
a = Cassette('test')
|
a = Cassette("test")
|
||||||
a.append('foo', 'bar')
|
a.append("foo", "bar")
|
||||||
assert 'foo' in a
|
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():
|
def test_cassette_responses_of():
|
||||||
a = Cassette('test')
|
a = Cassette("test")
|
||||||
a.append('foo', 'bar')
|
a.append("foo", "bar")
|
||||||
assert a.responses_of('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():
|
def test_cassette_get_missing_response():
|
||||||
a = Cassette('test')
|
a = Cassette("test")
|
||||||
with pytest.raises(UnhandledHTTPRequestError):
|
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():
|
def test_cassette_cant_read_same_request_twice():
|
||||||
a = Cassette('test')
|
a = Cassette("test")
|
||||||
a.append('foo', 'bar')
|
a.append("foo", "bar")
|
||||||
a.play_response('foo')
|
a.play_response("foo")
|
||||||
with pytest.raises(UnhandledHTTPRequestError):
|
with pytest.raises(UnhandledHTTPRequestError):
|
||||||
a.play_response('foo')
|
a.play_response("foo")
|
||||||
|
|
||||||
|
|
||||||
def make_get_request():
|
def make_get_request():
|
||||||
@@ -82,104 +87,119 @@ def make_get_request():
|
|||||||
return conn.getresponse()
|
return conn.getresponse()
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('vcr.cassette.requests_match', return_value=True)
|
@mock.patch("vcr.cassette.requests_match", return_value=True)
|
||||||
@mock.patch('vcr.cassette.load_cassette', lambda *args, **kwargs: (('foo',), (mock.MagicMock(),)))
|
@mock.patch(
|
||||||
@mock.patch('vcr.cassette.Cassette.can_play_response_for', return_value=True)
|
"vcr.cassette.FilesystemPersister.load_cassette",
|
||||||
@mock.patch('vcr.stubs.VCRHTTPResponse')
|
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):
|
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):
|
for i in range(4):
|
||||||
decorated_function()
|
decorated_function()
|
||||||
|
|
||||||
|
|
||||||
def test_arg_getter_functionality():
|
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)
|
context_decorator = Cassette.use_arg_getter(arg_getter)
|
||||||
|
|
||||||
with context_decorator as cassette:
|
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:
|
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
|
@context_decorator
|
||||||
def function():
|
def function():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
with mock.patch.object(
|
with mock.patch.object(Cassette, "load", return_value=mock.MagicMock(inject=False)) as cassette_load:
|
||||||
Cassette, 'load',
|
|
||||||
return_value=mock.MagicMock(inject=False)
|
|
||||||
) as cassette_load:
|
|
||||||
function()
|
function()
|
||||||
cassette_load.assert_called_once_with(**arg_getter.return_value)
|
cassette_load.assert_called_once_with(**arg_getter.return_value)
|
||||||
|
|
||||||
|
|
||||||
def test_cassette_not_all_played():
|
def test_cassette_not_all_played():
|
||||||
a = Cassette('test')
|
a = Cassette("test")
|
||||||
a.append('foo', 'bar')
|
a.append("foo", "bar")
|
||||||
assert not a.all_played
|
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():
|
def test_cassette_all_played():
|
||||||
a = Cassette('test')
|
a = Cassette("test")
|
||||||
a.append('foo', 'bar')
|
a.append("foo", "bar")
|
||||||
a.play_response('foo')
|
a.play_response("foo")
|
||||||
assert a.all_played
|
assert a.all_played
|
||||||
|
|
||||||
|
|
||||||
def test_before_record_response():
|
@mock.patch("vcr.cassette.requests_match", _mock_requests_match)
|
||||||
before_record_response = mock.Mock(return_value='mutated')
|
def test_cassette_rewound():
|
||||||
cassette = Cassette('test', before_record_response=before_record_response)
|
a = Cassette("test")
|
||||||
cassette.append('req', 'res')
|
a.append("foo", "bar")
|
||||||
|
a.play_response("foo")
|
||||||
|
assert a.all_played
|
||||||
|
|
||||||
before_record_response.assert_called_once_with('res')
|
a.rewind()
|
||||||
assert cassette.responses[0] == 'mutated'
|
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):
|
def assert_get_response_body_is(value):
|
||||||
conn = httplib.HTTPConnection("www.python.org")
|
conn = httplib.HTTPConnection("www.python.org")
|
||||||
conn.request("GET", "/index.html")
|
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.requests_match", _mock_requests_match)
|
||||||
@mock.patch('vcr.cassette.Cassette.can_play_response_for', return_value=True)
|
@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.Cassette._save", return_value=True)
|
||||||
def test_nesting_cassette_context_managers(*args):
|
def test_nesting_cassette_context_managers(*args):
|
||||||
first_response = {'body': {'string': b'first_response'}, 'headers': {},
|
first_response = {
|
||||||
'status': {'message': 'm', 'code': 200}}
|
"body": {"string": b"first_response"},
|
||||||
|
"headers": {},
|
||||||
|
"status": {"message": "m", "code": 200},
|
||||||
|
}
|
||||||
|
|
||||||
second_response = copy.deepcopy(first_response)
|
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:
|
with contextlib.ExitStack() as exit_stack:
|
||||||
first_cassette = exit_stack.enter_context(Cassette.use(path='test'))
|
first_cassette = exit_stack.enter_context(Cassette.use(path="test"))
|
||||||
exit_stack.enter_context(mock.patch.object(first_cassette, 'play_response',
|
exit_stack.enter_context(
|
||||||
return_value=first_response))
|
mock.patch.object(first_cassette, "play_response", return_value=first_response)
|
||||||
assert_get_response_body_is('first_response')
|
)
|
||||||
|
assert_get_response_body_is("first_response")
|
||||||
|
|
||||||
# Make sure a second cassette can supercede the first
|
# Make sure a second cassette can supercede the first
|
||||||
with Cassette.use(path='test') as second_cassette:
|
with Cassette.use(path="test") as second_cassette:
|
||||||
with mock.patch.object(second_cassette, 'play_response', return_value=second_response):
|
with mock.patch.object(second_cassette, "play_response", return_value=second_response):
|
||||||
assert_get_response_body_is('second_response')
|
assert_get_response_body_is("second_response")
|
||||||
|
|
||||||
# Now the first cassette should be back in effect
|
# 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():
|
def test_nesting_context_managers_by_checking_references_of_http_connection():
|
||||||
original = httplib.HTTPConnection
|
original = httplib.HTTPConnection
|
||||||
with Cassette.use(path='test'):
|
with Cassette.use(path="test"):
|
||||||
first_cassette_HTTPConnection = httplib.HTTPConnection
|
first_cassette_HTTPConnection = httplib.HTTPConnection
|
||||||
with Cassette.use(path='test'):
|
with Cassette.use(path="test"):
|
||||||
second_cassette_HTTPConnection = httplib.HTTPConnection
|
second_cassette_HTTPConnection = httplib.HTTPConnection
|
||||||
assert second_cassette_HTTPConnection is not first_cassette_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
|
assert httplib.HTTPConnection is not second_cassette_HTTPConnection
|
||||||
with force_reset():
|
with force_reset():
|
||||||
assert httplib.HTTPConnection is original
|
assert httplib.HTTPConnection is original
|
||||||
@@ -190,14 +210,13 @@ def test_nesting_context_managers_by_checking_references_of_http_connection():
|
|||||||
def test_custom_patchers():
|
def test_custom_patchers():
|
||||||
class Test(object):
|
class Test(object):
|
||||||
attribute = None
|
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 issubclass(Test.attribute, VCRHTTPSConnection)
|
||||||
assert VCRHTTPSConnection is not Test.attribute
|
assert VCRHTTPSConnection is not Test.attribute
|
||||||
old_attribute = Test.attribute
|
old_attribute = Test.attribute
|
||||||
|
|
||||||
with Cassette.use(path='custom_patches',
|
with Cassette.use(path="custom_patches", custom_patches=((Test, "attribute", VCRHTTPSConnection),)):
|
||||||
custom_patches=((Test, 'attribute', VCRHTTPSConnection),)):
|
|
||||||
assert issubclass(Test.attribute, VCRHTTPSConnection)
|
assert issubclass(Test.attribute, VCRHTTPSConnection)
|
||||||
assert VCRHTTPSConnection is not Test.attribute
|
assert VCRHTTPSConnection is not Test.attribute
|
||||||
assert Test.attribute is not old_attribute
|
assert Test.attribute is not old_attribute
|
||||||
@@ -211,15 +230,15 @@ def test_decorated_functions_are_reentrant():
|
|||||||
info = {"second": False}
|
info = {"second": False}
|
||||||
original_conn = httplib.HTTPConnection
|
original_conn = httplib.HTTPConnection
|
||||||
|
|
||||||
@Cassette.use(path='whatever', inject=True)
|
@Cassette.use(path="whatever", inject=True)
|
||||||
def test_function(cassette):
|
def test_function(cassette):
|
||||||
if info['second']:
|
if info["second"]:
|
||||||
assert httplib.HTTPConnection is not info['first_conn']
|
assert httplib.HTTPConnection is not info["first_conn"]
|
||||||
else:
|
else:
|
||||||
info['first_conn'] = httplib.HTTPConnection
|
info["first_conn"] = httplib.HTTPConnection
|
||||||
info['second'] = True
|
info["second"] = True
|
||||||
test_function()
|
test_function()
|
||||||
assert httplib.HTTPConnection is info['first_conn']
|
assert httplib.HTTPConnection is info["first_conn"]
|
||||||
|
|
||||||
test_function()
|
test_function()
|
||||||
assert httplib.HTTPConnection is original_conn
|
assert httplib.HTTPConnection is original_conn
|
||||||
@@ -228,43 +247,39 @@ def test_decorated_functions_are_reentrant():
|
|||||||
def test_cassette_use_called_without_path_uses_function_to_generate_path():
|
def test_cassette_use_called_without_path_uses_function_to_generate_path():
|
||||||
@Cassette.use(inject=True)
|
@Cassette.use(inject=True)
|
||||||
def function_name(cassette):
|
def function_name(cassette):
|
||||||
assert cassette._path == 'function_name'
|
assert cassette._path == "function_name"
|
||||||
|
|
||||||
function_name()
|
function_name()
|
||||||
|
|
||||||
|
|
||||||
def test_path_transformer_with_function_path():
|
def test_path_transformer_with_function_path():
|
||||||
def path_transformer(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)
|
@Cassette.use(inject=True, path_transformer=path_transformer)
|
||||||
def function_name(cassette):
|
def function_name(cassette):
|
||||||
assert cassette._path == os.path.join('a', 'function_name')
|
assert cassette._path == os.path.join("a", "function_name")
|
||||||
|
|
||||||
function_name()
|
function_name()
|
||||||
|
|
||||||
|
|
||||||
def test_path_transformer_with_context_manager():
|
def test_path_transformer_with_context_manager():
|
||||||
with Cassette.use(
|
with Cassette.use(path="b", path_transformer=lambda *args: "a") as cassette:
|
||||||
path='b', path_transformer=lambda *args: 'a'
|
assert cassette._path == "a"
|
||||||
) as cassette:
|
|
||||||
assert cassette._path == 'a'
|
|
||||||
|
|
||||||
|
|
||||||
def test_path_transformer_None():
|
def test_path_transformer_None():
|
||||||
with Cassette.use(
|
with Cassette.use(path="a", path_transformer=None) as cassette:
|
||||||
path='a', path_transformer=None,
|
assert cassette._path == "a"
|
||||||
) as cassette:
|
|
||||||
assert cassette._path == 'a'
|
|
||||||
|
|
||||||
|
|
||||||
def test_func_path_generator():
|
def test_func_path_generator():
|
||||||
def generator(function):
|
def generator(function):
|
||||||
return os.path.join(os.path.dirname(inspect.getfile(function)),
|
return os.path.join(os.path.dirname(inspect.getfile(function)), function.__name__)
|
||||||
function.__name__)
|
|
||||||
|
|
||||||
@Cassette.use(inject=True, func_path_generator=generator)
|
@Cassette.use(inject=True, func_path_generator=generator)
|
||||||
def function_name(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()
|
function_name()
|
||||||
|
|
||||||
@@ -305,3 +320,51 @@ def test_use_as_decorator_on_generator():
|
|||||||
yield 2
|
yield 2
|
||||||
|
|
||||||
assert list(test_function()) == [1, 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 six import BytesIO
|
||||||
from vcr.filters import (
|
from vcr.filters import (
|
||||||
remove_headers, replace_headers,
|
remove_headers,
|
||||||
remove_query_parameters, replace_query_parameters,
|
replace_headers,
|
||||||
remove_post_data_parameters, replace_post_data_parameters,
|
remove_query_parameters,
|
||||||
decode_response
|
replace_query_parameters,
|
||||||
|
remove_post_data_parameters,
|
||||||
|
replace_post_data_parameters,
|
||||||
|
decode_response,
|
||||||
)
|
)
|
||||||
from vcr.compat import mock
|
from vcr.compat import mock
|
||||||
from vcr.request import Request
|
from vcr.request import Request
|
||||||
@@ -20,31 +23,24 @@ def test_replace_headers():
|
|||||||
# 4. replacing a header using a callable
|
# 4. replacing a header using a callable
|
||||||
# 5. removing a header using a callable
|
# 5. removing a header using a callable
|
||||||
# 6. replacing a header that doesn't exist
|
# 6. replacing a header that doesn't exist
|
||||||
headers = {
|
headers = {"one": ["keep"], "two": ["lose"], "three": ["change"], "four": ["shout"], "five": ["whisper"]}
|
||||||
'one': ['keep'],
|
request = Request("GET", "http://google.com", "", headers)
|
||||||
'two': ['lose'],
|
replace_headers(
|
||||||
'three': ['change'],
|
request,
|
||||||
'four': ['shout'],
|
[
|
||||||
'five': ['whisper'],
|
("two", None),
|
||||||
}
|
("three", "tada"),
|
||||||
request = Request('GET', 'http://google.com', '', headers)
|
("four", lambda key, value, request: value.upper()),
|
||||||
replace_headers(request, [
|
("five", lambda key, value, request: None),
|
||||||
('two', None),
|
("six", "doesntexist"),
|
||||||
('three', 'tada'),
|
],
|
||||||
('four', lambda key, value, request: value.upper()),
|
)
|
||||||
('five', lambda key, value, request: None),
|
assert request.headers == {"one": "keep", "three": "tada", "four": "SHOUT"}
|
||||||
('six', 'doesntexist'),
|
|
||||||
])
|
|
||||||
assert request.headers == {
|
|
||||||
'one': 'keep',
|
|
||||||
'three': 'tada',
|
|
||||||
'four': 'SHOUT',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_replace_headers_empty():
|
def test_replace_headers_empty():
|
||||||
headers = {'hello': 'goodbye', 'secret': 'header'}
|
headers = {"hello": "goodbye", "secret": "header"}
|
||||||
request = Request('GET', 'http://google.com', '', headers)
|
request = Request("GET", "http://google.com", "", headers)
|
||||||
replace_headers(request, [])
|
replace_headers(request, [])
|
||||||
assert request.headers == headers
|
assert request.headers == headers
|
||||||
|
|
||||||
@@ -52,22 +48,20 @@ def test_replace_headers_empty():
|
|||||||
def test_replace_headers_callable():
|
def test_replace_headers_callable():
|
||||||
# This goes beyond test_replace_headers() to ensure that the callable
|
# This goes beyond test_replace_headers() to ensure that the callable
|
||||||
# receives the expected arguments.
|
# receives the expected arguments.
|
||||||
headers = {'hey': 'there'}
|
headers = {"hey": "there"}
|
||||||
request = Request('GET', 'http://google.com', '', headers)
|
request = Request("GET", "http://google.com", "", headers)
|
||||||
callme = mock.Mock(return_value='ho')
|
callme = mock.Mock(return_value="ho")
|
||||||
replace_headers(request, [('hey', callme)])
|
replace_headers(request, [("hey", callme)])
|
||||||
assert request.headers == {'hey': 'ho'}
|
assert request.headers == {"hey": "ho"}
|
||||||
assert callme.call_args == ((), {'request': request,
|
assert callme.call_args == ((), {"request": request, "key": "hey", "value": "there"})
|
||||||
'key': 'hey',
|
|
||||||
'value': 'there'})
|
|
||||||
|
|
||||||
|
|
||||||
def test_remove_headers():
|
def test_remove_headers():
|
||||||
# Test the backward-compatible API wrapper.
|
# Test the backward-compatible API wrapper.
|
||||||
headers = {'hello': ['goodbye'], 'secret': ['header']}
|
headers = {"hello": ["goodbye"], "secret": ["header"]}
|
||||||
request = Request('GET', 'http://google.com', '', headers)
|
request = Request("GET", "http://google.com", "", headers)
|
||||||
remove_headers(request, ['secret'])
|
remove_headers(request, ["secret"])
|
||||||
assert request.headers == {'hello': 'goodbye'}
|
assert request.headers == {"hello": "goodbye"}
|
||||||
|
|
||||||
|
|
||||||
def test_replace_query_parameters():
|
def test_replace_query_parameters():
|
||||||
@@ -78,48 +72,45 @@ def test_replace_query_parameters():
|
|||||||
# 4. replacing a parameter using a callable
|
# 4. replacing a parameter using a callable
|
||||||
# 5. removing a parameter using a callable
|
# 5. removing a parameter using a callable
|
||||||
# 6. replacing a parameter that doesn't exist
|
# 6. replacing a parameter that doesn't exist
|
||||||
uri = 'http://g.com/?one=keep&two=lose&three=change&four=shout&five=whisper'
|
uri = "http://g.com/?one=keep&two=lose&three=change&four=shout&five=whisper"
|
||||||
request = Request('GET', uri, '', {})
|
request = Request("GET", uri, "", {})
|
||||||
replace_query_parameters(request, [
|
replace_query_parameters(
|
||||||
('two', None),
|
request,
|
||||||
('three', 'tada'),
|
[
|
||||||
('four', lambda key, value, request: value.upper()),
|
("two", None),
|
||||||
('five', lambda key, value, request: None),
|
("three", "tada"),
|
||||||
('six', 'doesntexist'),
|
("four", lambda key, value, request: value.upper()),
|
||||||
])
|
("five", lambda key, value, request: None),
|
||||||
assert request.query == [
|
("six", "doesntexist"),
|
||||||
('four', 'SHOUT'),
|
],
|
||||||
('one', 'keep'),
|
)
|
||||||
('three', 'tada'),
|
assert request.query == [("four", "SHOUT"), ("one", "keep"), ("three", "tada")]
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_remove_all_query_parameters():
|
def test_remove_all_query_parameters():
|
||||||
uri = 'http://g.com/?q=cowboys&w=1'
|
uri = "http://g.com/?q=cowboys&w=1"
|
||||||
request = Request('GET', uri, '', {})
|
request = Request("GET", uri, "", {})
|
||||||
replace_query_parameters(request, [('w', None), ('q', None)])
|
replace_query_parameters(request, [("w", None), ("q", None)])
|
||||||
assert request.uri == 'http://g.com/'
|
assert request.uri == "http://g.com/"
|
||||||
|
|
||||||
|
|
||||||
def test_replace_query_parameters_callable():
|
def test_replace_query_parameters_callable():
|
||||||
# This goes beyond test_replace_query_parameters() to ensure that the
|
# This goes beyond test_replace_query_parameters() to ensure that the
|
||||||
# callable receives the expected arguments.
|
# callable receives the expected arguments.
|
||||||
uri = 'http://g.com/?hey=there'
|
uri = "http://g.com/?hey=there"
|
||||||
request = Request('GET', uri, '', {})
|
request = Request("GET", uri, "", {})
|
||||||
callme = mock.Mock(return_value='ho')
|
callme = mock.Mock(return_value="ho")
|
||||||
replace_query_parameters(request, [('hey', callme)])
|
replace_query_parameters(request, [("hey", callme)])
|
||||||
assert request.uri == 'http://g.com/?hey=ho'
|
assert request.uri == "http://g.com/?hey=ho"
|
||||||
assert callme.call_args == ((), {'request': request,
|
assert callme.call_args == ((), {"request": request, "key": "hey", "value": "there"})
|
||||||
'key': 'hey',
|
|
||||||
'value': 'there'})
|
|
||||||
|
|
||||||
|
|
||||||
def test_remove_query_parameters():
|
def test_remove_query_parameters():
|
||||||
# Test the backward-compatible API wrapper.
|
# Test the backward-compatible API wrapper.
|
||||||
uri = 'http://g.com/?q=cowboys&w=1'
|
uri = "http://g.com/?q=cowboys&w=1"
|
||||||
request = Request('GET', uri, '', {})
|
request = Request("GET", uri, "", {})
|
||||||
remove_query_parameters(request, ['w'])
|
remove_query_parameters(request, ["w"])
|
||||||
assert request.uri == 'http://g.com/?q=cowboys'
|
assert request.uri == "http://g.com/?q=cowboys"
|
||||||
|
|
||||||
|
|
||||||
def test_replace_post_data_parameters():
|
def test_replace_post_data_parameters():
|
||||||
@@ -130,38 +121,58 @@ def test_replace_post_data_parameters():
|
|||||||
# 4. replacing a parameter using a callable
|
# 4. replacing a parameter using a callable
|
||||||
# 5. removing a parameter using a callable
|
# 5. removing a parameter using a callable
|
||||||
# 6. replacing a parameter that doesn't exist
|
# 6. replacing a parameter that doesn't exist
|
||||||
body = b'one=keep&two=lose&three=change&four=shout&five=whisper'
|
body = b"one=keep&two=lose&three=change&four=shout&five=whisper"
|
||||||
request = Request('POST', 'http://google.com', body, {})
|
request = Request("POST", "http://google.com", body, {})
|
||||||
replace_post_data_parameters(request, [
|
replace_post_data_parameters(
|
||||||
('two', None),
|
request,
|
||||||
('three', 'tada'),
|
[
|
||||||
('four', lambda key, value, request: value.upper()),
|
("two", None),
|
||||||
('five', lambda key, value, request: None),
|
("three", "tada"),
|
||||||
('six', 'doesntexist'),
|
("four", lambda key, value, request: value.upper()),
|
||||||
])
|
("five", lambda key, value, request: None),
|
||||||
assert request.body == b'one=keep&three=tada&four=SHOUT'
|
("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():
|
def test_remove_post_data_parameters():
|
||||||
# Test the backward-compatible API wrapper.
|
# Test the backward-compatible API wrapper.
|
||||||
body = b'id=secret&foo=bar'
|
body = b"id=secret&foo=bar"
|
||||||
request = Request('POST', 'http://google.com', body, {})
|
request = Request("POST", "http://google.com", body, {})
|
||||||
remove_post_data_parameters(request, ['id'])
|
remove_post_data_parameters(request, ["id"])
|
||||||
assert request.body == b'foo=bar'
|
assert request.body == b"foo=bar"
|
||||||
|
|
||||||
|
|
||||||
def test_preserve_multiple_post_data_parameters():
|
def test_preserve_multiple_post_data_parameters():
|
||||||
body = b'id=secret&foo=bar&foo=baz'
|
body = b"id=secret&foo=bar&foo=baz"
|
||||||
request = Request('POST', 'http://google.com', body, {})
|
request = Request("POST", "http://google.com", body, {})
|
||||||
replace_post_data_parameters(request, [('id', None)])
|
replace_post_data_parameters(request, [("id", None)])
|
||||||
assert request.body == b'foo=bar&foo=baz'
|
assert request.body == b"foo=bar&foo=baz"
|
||||||
|
|
||||||
|
|
||||||
def test_remove_all_post_data_parameters():
|
def test_remove_all_post_data_parameters():
|
||||||
body = b'id=secret&foo=bar'
|
body = b"id=secret&foo=bar"
|
||||||
request = Request('POST', 'http://google.com', body, {})
|
request = Request("POST", "http://google.com", body, {})
|
||||||
replace_post_data_parameters(request, [('id', None), ('foo', None)])
|
replace_post_data_parameters(request, [("id", None), ("foo", None)])
|
||||||
assert request.body == b''
|
assert request.body == b""
|
||||||
|
|
||||||
|
|
||||||
def test_replace_json_post_data_parameters():
|
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
|
# 5. removing a parameter using a callable
|
||||||
# 6. replacing a parameter that doesn't exist
|
# 6. replacing a parameter that doesn't exist
|
||||||
body = b'{"one": "keep", "two": "lose", "three": "change", "four": "shout", "five": "whisper"}'
|
body = b'{"one": "keep", "two": "lose", "three": "change", "four": "shout", "five": "whisper"}'
|
||||||
request = Request('POST', 'http://google.com', body, {})
|
request = Request("POST", "http://google.com", body, {})
|
||||||
request.headers['Content-Type'] = 'application/json'
|
request.headers["Content-Type"] = "application/json"
|
||||||
replace_post_data_parameters(request, [
|
replace_post_data_parameters(
|
||||||
('two', None),
|
request,
|
||||||
('three', 'tada'),
|
[
|
||||||
('four', lambda key, value, request: value.upper()),
|
("two", None),
|
||||||
('five', lambda key, value, request: None),
|
("three", "tada"),
|
||||||
('six', 'doesntexist'),
|
("four", lambda key, value, request: value.upper()),
|
||||||
])
|
("five", lambda key, value, request: None),
|
||||||
request_data = json.loads(request.body.decode('utf-8'))
|
("six", "doesntexist"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
request_data = json.loads(request.body.decode("utf-8"))
|
||||||
expected_data = json.loads('{"one": "keep", "three": "tada", "four": "SHOUT"}')
|
expected_data = json.loads('{"one": "keep", "three": "tada", "four": "SHOUT"}')
|
||||||
assert request_data == expected_data
|
assert request_data == expected_data
|
||||||
|
|
||||||
@@ -190,85 +204,80 @@ def test_replace_json_post_data_parameters():
|
|||||||
def test_remove_json_post_data_parameters():
|
def test_remove_json_post_data_parameters():
|
||||||
# Test the backward-compatible API wrapper.
|
# Test the backward-compatible API wrapper.
|
||||||
body = b'{"id": "secret", "foo": "bar", "baz": "qux"}'
|
body = b'{"id": "secret", "foo": "bar", "baz": "qux"}'
|
||||||
request = Request('POST', 'http://google.com', body, {})
|
request = Request("POST", "http://google.com", body, {})
|
||||||
request.headers['Content-Type'] = 'application/json'
|
request.headers["Content-Type"] = "application/json"
|
||||||
remove_post_data_parameters(request, ['id'])
|
remove_post_data_parameters(request, ["id"])
|
||||||
request_body_json = json.loads(request.body.decode('utf-8'))
|
request_body_json = json.loads(request.body.decode("utf-8"))
|
||||||
expected_json = json.loads(b'{"foo": "bar", "baz": "qux"}'.decode('utf-8'))
|
expected_json = json.loads(b'{"foo": "bar", "baz": "qux"}'.decode("utf-8"))
|
||||||
assert request_body_json == expected_json
|
assert request_body_json == expected_json
|
||||||
|
|
||||||
|
|
||||||
def test_remove_all_json_post_data_parameters():
|
def test_remove_all_json_post_data_parameters():
|
||||||
body = b'{"id": "secret", "foo": "bar"}'
|
body = b'{"id": "secret", "foo": "bar"}'
|
||||||
request = Request('POST', 'http://google.com', body, {})
|
request = Request("POST", "http://google.com", body, {})
|
||||||
request.headers['Content-Type'] = 'application/json'
|
request.headers["Content-Type"] = "application/json"
|
||||||
replace_post_data_parameters(request, [('id', None), ('foo', None)])
|
replace_post_data_parameters(request, [("id", None), ("foo", None)])
|
||||||
assert request.body == b'{}'
|
assert request.body == b"{}"
|
||||||
|
|
||||||
|
|
||||||
def test_decode_response_uncompressed():
|
def test_decode_response_uncompressed():
|
||||||
recorded_response = {
|
recorded_response = {
|
||||||
"status": {
|
"status": {"message": "OK", "code": 200},
|
||||||
"message": "OK",
|
|
||||||
"code": 200
|
|
||||||
},
|
|
||||||
"headers": {
|
"headers": {
|
||||||
"content-length": ["10806"],
|
"content-length": ["10806"],
|
||||||
"date": ["Fri, 24 Oct 2014 18:35:37 GMT"],
|
"date": ["Fri, 24 Oct 2014 18:35:37 GMT"],
|
||||||
"content-type": ["text/html; charset=utf-8"],
|
"content-type": ["text/html; charset=utf-8"],
|
||||||
},
|
},
|
||||||
"body": {
|
"body": {"string": b""},
|
||||||
"string": b""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assert decode_response(recorded_response) == recorded_response
|
assert decode_response(recorded_response) == recorded_response
|
||||||
|
|
||||||
|
|
||||||
def test_decode_response_deflate():
|
def test_decode_response_deflate():
|
||||||
body = b'deflate message'
|
body = b"deflate message"
|
||||||
deflate_response = {
|
deflate_response = {
|
||||||
'body': {'string': zlib.compress(body)},
|
"body": {"string": zlib.compress(body)},
|
||||||
'headers': {
|
"headers": {
|
||||||
'access-control-allow-credentials': ['true'],
|
"access-control-allow-credentials": ["true"],
|
||||||
'access-control-allow-origin': ['*'],
|
"access-control-allow-origin": ["*"],
|
||||||
'connection': ['keep-alive'],
|
"connection": ["keep-alive"],
|
||||||
'content-encoding': ['deflate'],
|
"content-encoding": ["deflate"],
|
||||||
'content-length': ['177'],
|
"content-length": ["177"],
|
||||||
'content-type': ['application/json'],
|
"content-type": ["application/json"],
|
||||||
'date': ['Wed, 02 Dec 2015 19:44:32 GMT'],
|
"date": ["Wed, 02 Dec 2015 19:44:32 GMT"],
|
||||||
'server': ['nginx']
|
"server": ["nginx"],
|
||||||
},
|
},
|
||||||
'status': {'code': 200, 'message': 'OK'}
|
"status": {"code": 200, "message": "OK"},
|
||||||
}
|
}
|
||||||
decoded_response = decode_response(deflate_response)
|
decoded_response = decode_response(deflate_response)
|
||||||
assert decoded_response['body']['string'] == body
|
assert decoded_response["body"]["string"] == body
|
||||||
assert decoded_response['headers']['content-length'] == [str(len(body))]
|
assert decoded_response["headers"]["content-length"] == [str(len(body))]
|
||||||
|
|
||||||
|
|
||||||
def test_decode_response_gzip():
|
def test_decode_response_gzip():
|
||||||
body = b'gzip message'
|
body = b"gzip message"
|
||||||
|
|
||||||
buf = BytesIO()
|
buf = BytesIO()
|
||||||
f = gzip.GzipFile('a', fileobj=buf, mode='wb')
|
f = gzip.GzipFile("a", fileobj=buf, mode="wb")
|
||||||
f.write(body)
|
f.write(body)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
compressed_body = buf.getvalue()
|
compressed_body = buf.getvalue()
|
||||||
buf.close()
|
buf.close()
|
||||||
gzip_response = {
|
gzip_response = {
|
||||||
'body': {'string': compressed_body},
|
"body": {"string": compressed_body},
|
||||||
'headers': {
|
"headers": {
|
||||||
'access-control-allow-credentials': ['true'],
|
"access-control-allow-credentials": ["true"],
|
||||||
'access-control-allow-origin': ['*'],
|
"access-control-allow-origin": ["*"],
|
||||||
'connection': ['keep-alive'],
|
"connection": ["keep-alive"],
|
||||||
'content-encoding': ['gzip'],
|
"content-encoding": ["gzip"],
|
||||||
'content-length': ['177'],
|
"content-length": ["177"],
|
||||||
'content-type': ['application/json'],
|
"content-type": ["application/json"],
|
||||||
'date': ['Wed, 02 Dec 2015 19:44:32 GMT'],
|
"date": ["Wed, 02 Dec 2015 19:44:32 GMT"],
|
||||||
'server': ['nginx']
|
"server": ["nginx"],
|
||||||
},
|
},
|
||||||
'status': {'code': 200, 'message': 'OK'}
|
"status": {"code": 200, "message": "OK"},
|
||||||
}
|
}
|
||||||
decoded_response = decode_response(gzip_response)
|
decoded_response = decode_response(gzip_response)
|
||||||
assert decoded_response['body']['string'] == body
|
assert decoded_response["body"]["string"] == body
|
||||||
assert decoded_response['headers']['content-length'] == [str(len(body))]
|
assert decoded_response["headers"]["content-length"] == [str(len(body))]
|
||||||
|
|||||||
@@ -4,16 +4,14 @@ from vcr.request import Request
|
|||||||
|
|
||||||
|
|
||||||
def test_serialize_binary():
|
def test_serialize_binary():
|
||||||
request = Request(
|
request = Request(method="GET", uri="http://localhost/", body="", headers={})
|
||||||
method='GET',
|
cassette = {"requests": [request], "responses": [{"body": b"\x8c"}]}
|
||||||
uri='http://localhost/',
|
|
||||||
body='',
|
|
||||||
headers={},
|
|
||||||
)
|
|
||||||
cassette = {'requests': [request], 'responses': [{'body': b'\x8c'}]}
|
|
||||||
|
|
||||||
with pytest.raises(Exception) as e:
|
with pytest.raises(Exception) as e:
|
||||||
serialize(cassette)
|
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 \
|
HTTP interaction contain binary data? If so, use a different \
|
||||||
serializer (like the yaml serializer) for this request"
|
serializer (like the yaml serializer) for this request"
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import itertools
|
import itertools
|
||||||
|
from vcr.compat import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -8,152 +9,266 @@ from vcr import request
|
|||||||
# the dict contains requests with corresponding to its key difference
|
# the dict contains requests with corresponding to its key difference
|
||||||
# with 'base' request.
|
# with 'base' request.
|
||||||
REQUESTS = {
|
REQUESTS = {
|
||||||
'base': request.Request('GET', 'http://host.com/p?a=b', '', {}),
|
"base": request.Request("GET", "http://host.com/p?a=b", "", {}),
|
||||||
'method': request.Request('POST', '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', '', {}),
|
"scheme": request.Request("GET", "https://host.com:80/p?a=b", "", {}),
|
||||||
'host': request.Request('GET', 'http://another-host.com/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', '', {}),
|
"port": request.Request("GET", "http://host.com:90/p?a=b", "", {}),
|
||||||
'path': request.Request('GET', 'http://host.com/x?a=b', '', {}),
|
"path": request.Request("GET", "http://host.com/x?a=b", "", {}),
|
||||||
'query': request.Request('GET', 'http://host.com/p?c=d', '', {}),
|
"query": request.Request("GET", "http://host.com/p?c=d", "", {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def assert_matcher(matcher_name):
|
def assert_matcher(matcher_name):
|
||||||
matcher = getattr(matchers, matcher_name)
|
matcher = getattr(matchers, matcher_name)
|
||||||
for k1, k2 in itertools.permutations(REQUESTS, 2):
|
for k1, k2 in itertools.permutations(REQUESTS, 2):
|
||||||
matched = matcher(REQUESTS[k1], REQUESTS[k2])
|
expecting_assertion_error = matcher_name in {k1, k2}
|
||||||
if matcher_name in set((k1, k2)):
|
if expecting_assertion_error:
|
||||||
assert not matched
|
with pytest.raises(AssertionError):
|
||||||
|
matcher(REQUESTS[k1], REQUESTS[k2])
|
||||||
else:
|
else:
|
||||||
assert matched
|
assert matcher(REQUESTS[k1], REQUESTS[k2]) is None
|
||||||
|
|
||||||
|
|
||||||
def test_uri_matcher():
|
def test_uri_matcher():
|
||||||
for k1, k2 in itertools.permutations(REQUESTS, 2):
|
for k1, k2 in itertools.permutations(REQUESTS, 2):
|
||||||
matched = matchers.uri(REQUESTS[k1], REQUESTS[k2])
|
expecting_assertion_error = {k1, k2} != {"base", "method"}
|
||||||
if set((k1, k2)) != set(('base', 'method')):
|
if expecting_assertion_error:
|
||||||
assert not matched
|
with pytest.raises(AssertionError):
|
||||||
|
matchers.uri(REQUESTS[k1], REQUESTS[k2])
|
||||||
else:
|
else:
|
||||||
assert matched
|
assert matchers.uri(REQUESTS[k1], REQUESTS[k2]) is None
|
||||||
|
|
||||||
|
|
||||||
req1_body = (b"<?xml version='1.0'?><methodCall><methodName>test</methodName>"
|
req1_body = (
|
||||||
b"<params><param><value><array><data><value><struct>"
|
b"<?xml version='1.0'?><methodCall><methodName>test</methodName>"
|
||||||
b"<member><name>a</name><value><string>1</string></value></member>"
|
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>")
|
b"<member><name>b</name><value><string>2</string></value></member>"
|
||||||
req2_body = (b"<?xml version='1.0'?><methodCall><methodName>test</methodName>"
|
b"</struct></value></data></array></value></param></params></methodCall>"
|
||||||
b"<params><param><value><array><data><value><struct>"
|
)
|
||||||
b"<member><name>b</name><value><string>2</string></value></member>"
|
req2_body = (
|
||||||
b"<member><name>a</name><value><string>1</string></value></member>"
|
b"<?xml version='1.0'?><methodCall><methodName>test</methodName>"
|
||||||
b"</struct></value></data></array></value></param></params></methodCall>")
|
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", [
|
@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/", "123", {}),
|
||||||
),
|
request.Request("POST", "http://another-host.com/", "123", {"Some-Header": "value"}),
|
||||||
(
|
|
||||||
request.Request('POST', 'http://host.com/', 'a=1&b=2',
|
|
||||||
{'Content-Type': 'application/x-www-form-urlencoded'}),
|
|
||||||
request.Request('POST', 'http://host.com/', 'b=2&a=1',
|
|
||||||
{'Content-Type': 'application/x-www-form-urlencoded'})
|
|
||||||
),
|
|
||||||
(
|
|
||||||
request.Request('POST', 'http://host.com/', '123', {}),
|
|
||||||
request.Request('POST', 'http://another-host.com/', '123', {'Some-Header': 'value'})
|
|
||||||
),
|
|
||||||
(
|
|
||||||
request.Request(
|
|
||||||
'POST', 'http://host.com/', 'a=1&b=2',
|
|
||||||
{'Content-Type': 'application/x-www-form-urlencoded'}
|
|
||||||
),
|
),
|
||||||
request.Request(
|
(
|
||||||
'POST', 'http://host.com/', 'b=2&a=1',
|
request.Request(
|
||||||
{'Content-Type': 'application/x-www-form-urlencoded'}
|
"POST", "http://host.com/", "a=1&b=2", {"Content-Type": "application/x-www-form-urlencoded"}
|
||||||
)
|
),
|
||||||
),
|
request.Request(
|
||||||
(
|
"POST", "http://host.com/", "b=2&a=1", {"Content-Type": "application/x-www-form-urlencoded"}
|
||||||
request.Request(
|
),
|
||||||
'POST', 'http://host.com/', '{"a": 1, "b": 2}',
|
|
||||||
{'Content-Type': 'application/json'}
|
|
||||||
),
|
),
|
||||||
request.Request(
|
(
|
||||||
'POST', 'http://host.com/', '{"b": 2, "a": 1}',
|
request.Request("POST", "http://host.com/", "123", {}),
|
||||||
{'content-type': 'application/json'}
|
request.Request("POST", "http://another-host.com/", "123", {"Some-Header": "value"}),
|
||||||
)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
request.Request(
|
|
||||||
'POST', 'http://host.com/', req1_body,
|
|
||||||
{'User-Agent': 'xmlrpclib', 'Content-Type': 'text/xml'}
|
|
||||||
),
|
),
|
||||||
request.Request(
|
(
|
||||||
'POST', 'http://host.com/', req2_body,
|
request.Request(
|
||||||
{'user-agent': 'somexmlrpc', 'content-type': 'text/xml'}
|
"POST", "http://host.com/", "a=1&b=2", {"Content-Type": "application/x-www-form-urlencoded"}
|
||||||
)
|
),
|
||||||
),
|
request.Request(
|
||||||
(
|
"POST", "http://host.com/", "b=2&a=1", {"Content-Type": "application/x-www-form-urlencoded"}
|
||||||
request.Request(
|
),
|
||||||
'POST', 'http://host.com/',
|
|
||||||
'{"a": 1, "b": 2}', {'Content-Type': 'application/json'}
|
|
||||||
),
|
),
|
||||||
request.Request(
|
(
|
||||||
'POST', 'http://host.com/',
|
request.Request(
|
||||||
'{"b": 2, "a": 1}', {'content-type': 'application/json'}
|
"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):
|
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", [
|
@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": 2}', {}),
|
||||||
(
|
request.Request("POST", "http://host.com/", '{"b": 2, "a": 1}', {}),
|
||||||
request.Request(
|
|
||||||
'POST', 'http://host.com/',
|
|
||||||
'{"a": 1, "b": 3}', {'Content-Type': 'application/json'}
|
|
||||||
),
|
),
|
||||||
request.Request(
|
(
|
||||||
'POST', 'http://host.com/',
|
request.Request(
|
||||||
'{"b": 2, "a": 1}', {'content-type': 'application/json'}
|
"POST", "http://host.com/", '{"a": 1, "b": 3}', {"Content-Type": "application/json"}
|
||||||
)
|
),
|
||||||
),
|
request.Request(
|
||||||
(
|
"POST", "http://host.com/", '{"b": 2, "a": 1}', {"content-type": "application/json"}
|
||||||
request.Request(
|
),
|
||||||
'POST', 'http://host.com/', req1_body, {'Content-Type': 'text/xml'}
|
|
||||||
),
|
),
|
||||||
request.Request(
|
(
|
||||||
'POST', 'http://host.com/', req2_body, {'content-type': 'text/xml'}
|
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):
|
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():
|
def test_query_matcher():
|
||||||
req1 = request.Request('GET', 'http://host.com/?a=b&c=d', '', {})
|
req1 = request.Request("GET", "http://host.com/?a=b&c=d", "", {})
|
||||||
req2 = request.Request('GET', 'http://host.com/?c=d&a=b', '', {})
|
req2 = request.Request("GET", "http://host.com/?c=d&a=b", "", {})
|
||||||
assert matchers.query(req1, req2)
|
assert matchers.query(req1, req2) is None
|
||||||
|
|
||||||
req1 = request.Request('GET', 'http://host.com/?a=b&a=b&c=d', '', {})
|
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', '', {})
|
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', '', {})
|
req3 = request.Request("GET", "http://host.com/?c=d&a=b&a=b", "", {})
|
||||||
assert matchers.query(req1, req2)
|
assert matchers.query(req1, req2) is None
|
||||||
assert matchers.query(req1, req3)
|
assert matchers.query(req1, req3) is None
|
||||||
|
|
||||||
|
|
||||||
def test_metchers():
|
def test_matchers():
|
||||||
assert_matcher('method')
|
assert_matcher("method")
|
||||||
assert_matcher('scheme')
|
assert_matcher("scheme")
|
||||||
assert_matcher('host')
|
assert_matcher("host")
|
||||||
assert_matcher('port')
|
assert_matcher("port")
|
||||||
assert_matcher('path')
|
assert_matcher("path")
|
||||||
assert_matcher('query')
|
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
|
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):
|
def test_try_migrate_with_json(tmpdir):
|
||||||
cassette = tmpdir.join('cassette.json').strpath
|
cassette = tmpdir.join("cassette.json").strpath
|
||||||
shutil.copy('tests/fixtures/migration/old_cassette.json', cassette)
|
shutil.copy("tests/fixtures/migration/old_cassette.json", cassette)
|
||||||
assert vcr.migration.try_migrate(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)
|
expected_json = json.load(f)
|
||||||
with open(cassette, 'r') as f:
|
with open(cassette, "r") as f:
|
||||||
actual_json = json.load(f)
|
actual_json = json.load(f)
|
||||||
assert actual_json == expected_json
|
assert actual_json == expected_json
|
||||||
|
|
||||||
|
|
||||||
def test_try_migrate_with_yaml(tmpdir):
|
def test_try_migrate_with_yaml(tmpdir):
|
||||||
cassette = tmpdir.join('cassette.yaml').strpath
|
cassette = tmpdir.join("cassette.yaml").strpath
|
||||||
shutil.copy('tests/fixtures/migration/old_cassette.yaml', cassette)
|
shutil.copy("tests/fixtures/migration/old_cassette.yaml", cassette)
|
||||||
assert vcr.migration.try_migrate(cassette)
|
assert vcr.migration.try_migrate(cassette)
|
||||||
with open('tests/fixtures/migration/new_cassette.yaml', 'r') as f:
|
with open("tests/fixtures/migration/new_cassette.yaml", "r") as f:
|
||||||
expected_yaml = yaml.load(f)
|
expected_yaml = yaml.load(f, Loader=Loader)
|
||||||
with open(cassette, 'r') as f:
|
with open(cassette, "r") as f:
|
||||||
actual_yaml = yaml.load(f)
|
actual_yaml = yaml.load(f, Loader=Loader)
|
||||||
assert actual_yaml == expected_yaml
|
assert actual_yaml == expected_yaml
|
||||||
|
|
||||||
|
|
||||||
def test_try_migrate_with_invalid_or_new_cassettes(tmpdir):
|
def test_try_migrate_with_invalid_or_new_cassettes(tmpdir):
|
||||||
cassette = tmpdir.join('cassette').strpath
|
cassette = tmpdir.join("cassette").strpath
|
||||||
files = [
|
files = [
|
||||||
'tests/fixtures/migration/not_cassette.txt',
|
"tests/fixtures/migration/not_cassette.txt",
|
||||||
'tests/fixtures/migration/new_cassette.yaml',
|
"tests/fixtures/migration/new_cassette.yaml",
|
||||||
'tests/fixtures/migration/new_cassette.json',
|
"tests/fixtures/migration/new_cassette.json",
|
||||||
]
|
]
|
||||||
for file_path in files:
|
for file_path in files:
|
||||||
shutil.copy(file_path, cassette)
|
shutil.copy(file_path, cassette)
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import vcr.persist
|
from vcr.persisters.filesystem import FilesystemPersister
|
||||||
from vcr.serializers import jsonserializer, yamlserializer
|
from vcr.serializers import jsonserializer, yamlserializer
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("cassette_path, serializer", [
|
@pytest.mark.parametrize(
|
||||||
('tests/fixtures/migration/old_cassette.json', jsonserializer),
|
"cassette_path, serializer",
|
||||||
('tests/fixtures/migration/old_cassette.yaml', yamlserializer),
|
[
|
||||||
])
|
("tests/fixtures/migration/old_cassette.json", jsonserializer),
|
||||||
|
("tests/fixtures/migration/old_cassette.yaml", yamlserializer),
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_load_cassette_with_old_cassettes(cassette_path, serializer):
|
def test_load_cassette_with_old_cassettes(cassette_path, serializer):
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
vcr.persist.load_cassette(cassette_path, serializer)
|
FilesystemPersister.load_cassette(cassette_path, serializer)
|
||||||
assert "run the migration script" in excinfo.exconly()
|
assert "run the migration script" in excinfo.exconly()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("cassette_path, serializer", [
|
@pytest.mark.parametrize(
|
||||||
('tests/fixtures/migration/not_cassette.txt', jsonserializer),
|
"cassette_path, serializer",
|
||||||
('tests/fixtures/migration/not_cassette.txt', yamlserializer),
|
[
|
||||||
])
|
("tests/fixtures/migration/not_cassette.txt", jsonserializer),
|
||||||
|
("tests/fixtures/migration/not_cassette.txt", yamlserializer),
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_load_cassette_with_invalid_cassettes(cassette_path, serializer):
|
def test_load_cassette_with_invalid_cassettes(cassette_path, serializer):
|
||||||
with pytest.raises(Exception) as excinfo:
|
with pytest.raises(Exception) as excinfo:
|
||||||
vcr.persist.load_cassette(cassette_path, serializer)
|
FilesystemPersister.load_cassette(cassette_path, serializer)
|
||||||
assert "run the migration script" not in excinfo.exconly()
|
assert "run the migration script" not in excinfo.exconly()
|
||||||
|
|||||||
@@ -3,44 +3,60 @@ import pytest
|
|||||||
from vcr.request import Request, HeadersDict
|
from vcr.request import Request, HeadersDict
|
||||||
|
|
||||||
|
|
||||||
def test_str():
|
@pytest.mark.parametrize(
|
||||||
req = Request('GET', 'http://www.google.com/', '', {})
|
"method, uri, expected_str",
|
||||||
str(req) == '<Request (GET) http://www.google.com/>'
|
[
|
||||||
|
("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():
|
def test_headers():
|
||||||
headers = {'X-Header1': ['h1'], 'X-Header2': 'h2'}
|
headers = {"X-Header1": ["h1"], "X-Header2": "h2"}
|
||||||
req = Request('GET', 'http://go.com/', '', headers)
|
req = Request("GET", "http://go.com/", "", headers)
|
||||||
assert req.headers == {'X-Header1': 'h1', 'X-Header2': 'h2'}
|
assert req.headers == {"X-Header1": "h1", "X-Header2": "h2"}
|
||||||
req.headers['X-Header1'] = 'h11'
|
req.headers["X-Header1"] = "h11"
|
||||||
assert req.headers == {'X-Header1': 'h11', 'X-Header2': 'h2'}
|
assert req.headers == {"X-Header1": "h11", "X-Header2": "h2"}
|
||||||
|
|
||||||
|
|
||||||
def test_add_header_deprecated():
|
def test_add_header_deprecated():
|
||||||
req = Request('GET', 'http://go.com/', '', {})
|
req = Request("GET", "http://go.com/", "", {})
|
||||||
pytest.deprecated_call(req.add_header, 'foo', 'bar')
|
pytest.deprecated_call(req.add_header, "foo", "bar")
|
||||||
assert req.headers == {'foo': 'bar'}
|
assert req.headers == {"foo": "bar"}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("uri, expected_port", [
|
@pytest.mark.parametrize(
|
||||||
('http://go.com/', 80),
|
"uri, expected_port",
|
||||||
('http://go.com:80/', 80),
|
[
|
||||||
('http://go.com:3000/', 3000),
|
("http://go.com/", 80),
|
||||||
('https://go.com/', 443),
|
("http://go.com:80/", 80),
|
||||||
('https://go.com:443/', 443),
|
("http://go.com:3000/", 3000),
|
||||||
('https://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):
|
def test_port(uri, expected_port):
|
||||||
req = Request('GET', uri, '', {})
|
req = Request("GET", uri, "", {})
|
||||||
assert req.port == expected_port
|
assert req.port == expected_port
|
||||||
|
|
||||||
|
|
||||||
def test_uri():
|
@pytest.mark.parametrize(
|
||||||
req = Request('GET', 'http://go.com/', '', {})
|
"method, uri",
|
||||||
assert req.uri == 'http://go.com/'
|
[
|
||||||
|
("GET", "http://go.com/"),
|
||||||
req = Request('GET', 'http://go.com:80/', '', {})
|
("GET", "http://go.com:80/"),
|
||||||
assert req.uri == 'http://go.com:80/'
|
("CONNECT", "localhost:1234"),
|
||||||
|
("OPTIONS", "*"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_uri(method, uri):
|
||||||
|
assert Request(method, uri, "", {}).uri == uri
|
||||||
|
|
||||||
|
|
||||||
def test_HeadersDict():
|
def test_HeadersDict():
|
||||||
@@ -48,23 +64,23 @@ def test_HeadersDict():
|
|||||||
# Simple test of CaseInsensitiveDict
|
# Simple test of CaseInsensitiveDict
|
||||||
h = HeadersDict()
|
h = HeadersDict()
|
||||||
assert h == {}
|
assert h == {}
|
||||||
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'
|
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
|
# Test feature of HeadersDict: devolve list to first element
|
||||||
h = HeadersDict()
|
h = HeadersDict()
|
||||||
assert h == {}
|
assert h == {}
|
||||||
h['x'] = ['foo', 'bar']
|
h["x"] = ["foo", "bar"]
|
||||||
assert h == {'x': 'foo'}
|
assert h == {"x": "foo"}
|
||||||
|
|
||||||
# Test feature of HeadersDict: preserve original key case
|
# Test feature of HeadersDict: preserve original key case
|
||||||
h = HeadersDict()
|
h = HeadersDict()
|
||||||
assert h == {}
|
assert h == {}
|
||||||
h['Content-Type'] = 'application/json'
|
h["Content-Type"] = "application/json"
|
||||||
assert h == {'Content-Type': 'application/json'}
|
assert h == {"Content-Type": "application/json"}
|
||||||
h['content-type'] = 'text/plain'
|
h["content-type"] = "text/plain"
|
||||||
assert h == {'Content-Type': 'text/plain'}
|
assert h == {"Content-Type": "text/plain"}
|
||||||
h['CONtent-tyPE'] = 'whoa'
|
h["CONtent-tyPE"] = "whoa"
|
||||||
assert h == {'Content-Type': 'whoa'}
|
assert h == {"Content-Type": "whoa"}
|
||||||
|
|||||||
@@ -1,13 +1,71 @@
|
|||||||
# coding: UTF-8
|
# coding: UTF-8
|
||||||
|
import io
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
from vcr.stubs import VCRHTTPResponse
|
from vcr.stubs import VCRHTTPResponse
|
||||||
|
|
||||||
|
|
||||||
def test_response_should_have_headers_field():
|
def test_response_should_have_headers_field():
|
||||||
recorded_response = {
|
recorded_response = {
|
||||||
"status": {
|
"status": {"message": "OK", "code": 200},
|
||||||
"message": "OK",
|
"headers": {
|
||||||
"code": 200
|
"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": {
|
"headers": {
|
||||||
"content-length": ["0"],
|
"content-length": ["0"],
|
||||||
"server": ["gunicorn/18.0"],
|
"server": ["gunicorn/18.0"],
|
||||||
@@ -18,51 +76,28 @@ def test_response_should_have_headers_field():
|
|||||||
"content-type": ["text/html; charset=utf-8"],
|
"content-type": ["text/html; charset=utf-8"],
|
||||||
},
|
},
|
||||||
"body": {
|
"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)
|
vcr_response = VCRHTTPResponse(recorded_response)
|
||||||
|
handle = io.TextIOWrapper(io.BufferedReader(vcr_response), encoding="utf-8")
|
||||||
assert response.headers is not None
|
handle = iter(handle)
|
||||||
|
articles = [line for line in handle]
|
||||||
|
assert len(articles) > 1
|
||||||
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"
|
|
||||||
|
|||||||
@@ -4,32 +4,32 @@ import pytest
|
|||||||
from vcr.compat import mock
|
from vcr.compat import mock
|
||||||
from vcr.request import Request
|
from vcr.request import Request
|
||||||
from vcr.serialize import deserialize, serialize
|
from vcr.serialize import deserialize, serialize
|
||||||
from vcr.serializers import yamlserializer, jsonserializer
|
from vcr.serializers import yamlserializer, jsonserializer, compat
|
||||||
|
|
||||||
|
|
||||||
def test_deserialize_old_yaml_cassette():
|
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):
|
with pytest.raises(ValueError):
|
||||||
deserialize(f.read(), yamlserializer)
|
deserialize(f.read(), yamlserializer)
|
||||||
|
|
||||||
|
|
||||||
def test_deserialize_old_json_cassette():
|
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):
|
with pytest.raises(ValueError):
|
||||||
deserialize(f.read(), jsonserializer)
|
deserialize(f.read(), jsonserializer)
|
||||||
|
|
||||||
|
|
||||||
def test_deserialize_new_yaml_cassette():
|
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)
|
deserialize(f.read(), yamlserializer)
|
||||||
|
|
||||||
|
|
||||||
def test_deserialize_new_json_cassette():
|
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)
|
deserialize(f.read(), jsonserializer)
|
||||||
|
|
||||||
|
|
||||||
REQBODY_TEMPLATE = u'''\
|
REQBODY_TEMPLATE = u"""\
|
||||||
interactions:
|
interactions:
|
||||||
- request:
|
- request:
|
||||||
body: {req_body}
|
body: {req_body}
|
||||||
@@ -44,90 +44,76 @@ interactions:
|
|||||||
content-length: ['0']
|
content-length: ['0']
|
||||||
content-type: [application/json]
|
content-type: [application/json]
|
||||||
status: {{code: 200, message: OK}}
|
status: {{code: 200, message: OK}}
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
# A cassette generated under Python 2 stores the request body as a string,
|
# 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".
|
# 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
|
# Make sure we accept both forms, regardless of whether we're running under
|
||||||
# Python 2 or 3.
|
# Python 2 or 3.
|
||||||
@pytest.mark.parametrize("req_body, expect", [
|
@pytest.mark.parametrize(
|
||||||
# Cassette written under Python 2 (pure ASCII body)
|
"req_body, expect",
|
||||||
('x=5&y=2', b'x=5&y=2'),
|
[
|
||||||
# Cassette written under Python 3 (pure ASCII body)
|
# Cassette written under Python 2 (pure ASCII body)
|
||||||
('!!binary |\n eD01Jnk9Mg==', b'x=5&y=2'),
|
("x=5&y=2", b"x=5&y=2"),
|
||||||
|
# Cassette written under Python 3 (pure ASCII body)
|
||||||
# Request body has non-ASCII chars (x=föo&y=2), encoded in UTF-8.
|
("!!binary |\n eD01Jnk9Mg==", b"x=5&y=2"),
|
||||||
('!!python/str "x=f\\xF6o&y=2"', b'x=f\xc3\xb6o&y=2'),
|
# Request body has non-ASCII chars (x=föo&y=2), encoded in UTF-8.
|
||||||
('!!binary |\n eD1mw7ZvJnk9Mg==', b'x=f\xc3\xb6o&y=2'),
|
('!!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
|
# 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
|
# write the same YAML file under both Python 2 and 3, so there's only
|
||||||
# one test case here.
|
# one test case here.
|
||||||
('!!binary |\n //54AD0AZgD2AG8AJgB5AD0AMgA=',
|
(
|
||||||
b'\xff\xfex\x00=\x00f\x00\xf6\x00o\x00&\x00y\x00=\x002\x00'),
|
"!!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'),
|
# 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):
|
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))
|
cfile.write(REQBODY_TEMPLATE.format(req_body=req_body))
|
||||||
with open(str(cfile)) as f:
|
with open(str(cfile)) as f:
|
||||||
(requests, responses) = deserialize(f.read(), yamlserializer)
|
(requests, responses) = deserialize(f.read(), yamlserializer)
|
||||||
assert requests[0].body == expect
|
assert requests[0].body == expect
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(jsonserializer.json, 'dumps',
|
@mock.patch.object(
|
||||||
side_effect=UnicodeDecodeError('utf-8', b'unicode error in serialization',
|
jsonserializer.json,
|
||||||
0, 10, 'blew up'))
|
"dumps",
|
||||||
|
side_effect=UnicodeDecodeError("utf-8", b"unicode error in serialization", 0, 10, "blew up"),
|
||||||
|
)
|
||||||
def test_serialize_constructs_UnicodeDecodeError(mock_dumps):
|
def test_serialize_constructs_UnicodeDecodeError(mock_dumps):
|
||||||
with pytest.raises(UnicodeDecodeError):
|
with pytest.raises(UnicodeDecodeError):
|
||||||
jsonserializer.serialize({})
|
jsonserializer.serialize({})
|
||||||
|
|
||||||
|
|
||||||
def test_serialize_empty_request():
|
def test_serialize_empty_request():
|
||||||
request = Request(
|
request = Request(method="POST", uri="http://localhost/", body="", headers={})
|
||||||
method='POST',
|
|
||||||
uri='http://localhost/',
|
|
||||||
body='',
|
|
||||||
headers={},
|
|
||||||
)
|
|
||||||
|
|
||||||
serialize(
|
serialize({"requests": [request], "responses": [{}]}, jsonserializer)
|
||||||
{'requests': [request], 'responses': [{}]},
|
|
||||||
jsonserializer
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_serialize_json_request():
|
def test_serialize_json_request():
|
||||||
request = Request(
|
request = Request(method="POST", uri="http://localhost/", body="{'hello': 'world'}", headers={})
|
||||||
method='POST',
|
|
||||||
uri='http://localhost/',
|
|
||||||
body="{'hello': 'world'}",
|
|
||||||
headers={},
|
|
||||||
)
|
|
||||||
|
|
||||||
serialize(
|
serialize({"requests": [request], "responses": [{}]}, jsonserializer)
|
||||||
{'requests': [request], 'responses': [{}]},
|
|
||||||
jsonserializer
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_serialize_binary_request():
|
def test_serialize_binary_request():
|
||||||
msg = "Does this HTTP interaction contain binary data?"
|
msg = "Does this HTTP interaction contain binary data?"
|
||||||
|
|
||||||
request = Request(
|
request = Request(method="POST", uri="http://localhost/", body=b"\x8c", headers={})
|
||||||
method='POST',
|
|
||||||
uri='http://localhost/',
|
|
||||||
body=b'\x8c',
|
|
||||||
headers={},
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
serialize(
|
serialize({"requests": [request], "responses": [{}]}, jsonserializer)
|
||||||
{'requests': [request], 'responses': [{}]},
|
|
||||||
jsonserializer
|
|
||||||
)
|
|
||||||
except (UnicodeDecodeError, TypeError) as exc:
|
except (UnicodeDecodeError, TypeError) as exc:
|
||||||
assert msg in str(exc)
|
assert msg in str(exc)
|
||||||
|
|
||||||
|
|
||||||
|
def test_deserialize_no_body_string():
|
||||||
|
data = {"body": {"string": None}}
|
||||||
|
output = compat.convert_to_bytes(data)
|
||||||
|
assert data == output
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
from vcr.stubs import VCRHTTPSConnection
|
from vcr.stubs import VCRHTTPSConnection
|
||||||
|
from vcr.compat import mock
|
||||||
|
from vcr.cassette import Cassette
|
||||||
|
|
||||||
|
|
||||||
class TestVCRConnection(object):
|
class TestVCRConnection(object):
|
||||||
|
|
||||||
def test_setting_of_attributes_get_propogated_to_real_connection(self):
|
def test_setting_of_attributes_get_propogated_to_real_connection(self):
|
||||||
vcr_connection = VCRHTTPSConnection('www.examplehost.com')
|
vcr_connection = VCRHTTPSConnection("www.examplehost.com")
|
||||||
vcr_connection.ssl_version = 'example_ssl_version'
|
vcr_connection.ssl_version = "example_ssl_version"
|
||||||
assert vcr_connection.real_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)
|
||||||
|
def testing_connect(*args):
|
||||||
|
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()
|
record_mode = mock.Mock()
|
||||||
test_vcr = VCR(record_mode=record_mode)
|
test_vcr = VCR(record_mode=record_mode)
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
'vcr.cassette.Cassette.load',
|
"vcr.cassette.Cassette.load", return_value=mock.MagicMock(inject=False)
|
||||||
return_value=mock.MagicMock(inject=False)
|
|
||||||
) as mock_cassette_load:
|
) as mock_cassette_load:
|
||||||
|
|
||||||
@test_vcr.use_cassette('test')
|
@test_vcr.use_cassette("test")
|
||||||
def function():
|
def function():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert mock_cassette_load.call_count == 0
|
assert mock_cassette_load.call_count == 0
|
||||||
function()
|
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
|
# Make sure that calls to function now use cassettes with the
|
||||||
# new filter_header_settings
|
# new filter_header_settings
|
||||||
test_vcr.record_mode = mock.Mock()
|
test_vcr.record_mode = mock.Mock()
|
||||||
function()
|
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
|
# Ensure that explicitly provided arguments still supercede
|
||||||
# those on the vcr.
|
# those on the vcr.
|
||||||
new_record_mode = mock.Mock()
|
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
|
assert cassette.record_mode == new_record_mode
|
||||||
|
|
||||||
|
|
||||||
def test_vcr_before_record_request_params():
|
def test_vcr_before_record_request_params():
|
||||||
base_path = 'http://httpbin.org/'
|
base_path = "http://httpbin.org/"
|
||||||
|
|
||||||
def before_record_cb(request):
|
def before_record_cb(request):
|
||||||
if request.path != '/get':
|
if request.path != "/get":
|
||||||
return request
|
return request
|
||||||
|
|
||||||
test_vcr = VCR(filter_headers=('cookie', ('bert', 'ernie')),
|
test_vcr = VCR(
|
||||||
before_record_request=before_record_cb,
|
filter_headers=("cookie", ("bert", "ernie")),
|
||||||
ignore_hosts=('www.test.com',), ignore_localhost=True,
|
before_record_request=before_record_cb,
|
||||||
filter_query_parameters=('foo', ('tom', 'jerry')),
|
ignore_hosts=("www.test.com",),
|
||||||
filter_post_data_parameters=('posted', ('no', 'trespassing')))
|
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
|
# 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
|
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
|
assert cassette.filter_request(request) is not None
|
||||||
|
|
||||||
# Test filter_query_parameters
|
# Test filter_query_parameters
|
||||||
request = Request('GET', base_path + '?foo=bar', '', {})
|
request = Request("GET", base_path + "?foo=bar", "", {})
|
||||||
assert cassette.filter_request(request).query == []
|
assert cassette.filter_request(request).query == []
|
||||||
request = Request('GET', base_path + '?tom=nobody', '', {})
|
request = Request("GET", base_path + "?tom=nobody", "", {})
|
||||||
assert cassette.filter_request(request).query == [('tom', 'jerry')]
|
assert cassette.filter_request(request).query == [("tom", "jerry")]
|
||||||
|
|
||||||
# Test filter_headers
|
# Test filter_headers
|
||||||
request = Request('GET', base_path + '?foo=bar', '',
|
request = Request(
|
||||||
{'cookie': 'test', 'other': 'fun', 'bert': 'nobody'})
|
"GET", base_path + "?foo=bar", "", {"cookie": "test", "other": "fun", "bert": "nobody"}
|
||||||
assert (cassette.filter_request(request).headers ==
|
)
|
||||||
{'other': 'fun', 'bert': 'ernie'})
|
assert cassette.filter_request(request).headers == {"other": "fun", "bert": "ernie"}
|
||||||
|
|
||||||
# Test ignore_hosts
|
# Test ignore_hosts
|
||||||
request = Request('GET', 'http://www.test.com' + '?foo=bar', '',
|
request = Request("GET", "http://www.test.com" + "?foo=bar", "", {"cookie": "test", "other": "fun"})
|
||||||
{'cookie': 'test', 'other': 'fun'})
|
|
||||||
assert cassette.filter_request(request) is None
|
assert cassette.filter_request(request) is None
|
||||||
|
|
||||||
# Test ignore_localhost
|
# Test ignore_localhost
|
||||||
request = Request('GET', 'http://localhost:8000' + '?foo=bar', '',
|
request = Request("GET", "http://localhost:8000" + "?foo=bar", "", {"cookie": "test", "other": "fun"})
|
||||||
{'cookie': 'test', 'other': 'fun'})
|
|
||||||
assert cassette.filter_request(request) is None
|
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.
|
# Test that before_record can be overwritten in context manager.
|
||||||
assert cassette.filter_request(request_get) is not None
|
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():
|
def test_vcr_before_record_response_iterable():
|
||||||
# Regression test for #191
|
# Regression test for #191
|
||||||
|
|
||||||
request = Request('GET', '/', '', {})
|
request = Request("GET", "/", "", {})
|
||||||
response = object() # just can't be None
|
response = object() # just can't be None
|
||||||
|
|
||||||
# Prevent actually saving the cassette
|
# Prevent actually saving the cassette
|
||||||
with mock.patch('vcr.cassette.save_cassette'):
|
with mock.patch("vcr.cassette.FilesystemPersister.save_cassette"):
|
||||||
|
|
||||||
# Baseline: non-iterable before_record_response should work
|
# Baseline: non-iterable before_record_response should work
|
||||||
mock_filter = mock.Mock()
|
mock_filter = mock.Mock()
|
||||||
vcr = VCR(before_record_response=mock_filter)
|
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
|
assert mock_filter.call_count == 0
|
||||||
cassette.append(request, response)
|
cassette.append(request, response)
|
||||||
assert mock_filter.call_count == 1
|
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
|
# Regression test: iterable before_record_response should work too
|
||||||
mock_filter = mock.Mock()
|
mock_filter = mock.Mock()
|
||||||
vcr = VCR(before_record_response=(mock_filter,))
|
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
|
assert mock_filter.call_count == 0
|
||||||
cassette.append(request, response)
|
cassette.append(request, response)
|
||||||
assert mock_filter.call_count == 1
|
assert mock_filter.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_before_record_response_as_filter():
|
def test_before_record_response_as_filter():
|
||||||
request = Request('GET', '/', '', {})
|
request = Request("GET", "/", "", {})
|
||||||
response = object() # just can't be None
|
response = object() # just can't be None
|
||||||
|
|
||||||
# Prevent actually saving the cassette
|
# Prevent actually saving the cassette
|
||||||
with mock.patch('vcr.cassette.save_cassette'):
|
with mock.patch("vcr.cassette.FilesystemPersister.save_cassette"):
|
||||||
|
|
||||||
filter_all = mock.Mock(return_value=None)
|
filter_all = mock.Mock(return_value=None)
|
||||||
vcr = VCR(before_record_response=filter_all)
|
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)
|
cassette.append(request, response)
|
||||||
assert cassette.data == []
|
assert cassette.data == []
|
||||||
assert not cassette.dirty
|
assert not cassette.dirty
|
||||||
@@ -132,22 +132,22 @@ def test_vcr_path_transformer():
|
|||||||
# Regression test for #199
|
# Regression test for #199
|
||||||
|
|
||||||
# Prevent actually saving the cassette
|
# Prevent actually saving the cassette
|
||||||
with mock.patch('vcr.cassette.save_cassette'):
|
with mock.patch("vcr.cassette.FilesystemPersister.save_cassette"):
|
||||||
|
|
||||||
# Baseline: path should be unchanged
|
# Baseline: path should be unchanged
|
||||||
vcr = VCR()
|
vcr = VCR()
|
||||||
with vcr.use_cassette('test') as cassette:
|
with vcr.use_cassette("test") as cassette:
|
||||||
assert cassette._path == 'test'
|
assert cassette._path == "test"
|
||||||
|
|
||||||
# Regression test: path_transformer=None should do the same.
|
# Regression test: path_transformer=None should do the same.
|
||||||
vcr = VCR(path_transformer=None)
|
vcr = VCR(path_transformer=None)
|
||||||
with vcr.use_cassette('test') as cassette:
|
with vcr.use_cassette("test") as cassette:
|
||||||
assert cassette._path == 'test'
|
assert cassette._path == "test"
|
||||||
|
|
||||||
# and it should still work with cassette_library_dir
|
# and it should still work with cassette_library_dir
|
||||||
vcr = VCR(cassette_library_dir='/foo')
|
vcr = VCR(cassette_library_dir="/foo")
|
||||||
with vcr.use_cassette('test') as cassette:
|
with vcr.use_cassette("test") as cassette:
|
||||||
assert cassette._path == '/foo/test'
|
assert os.path.abspath(cassette._path) == os.path.abspath("/foo/test")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -155,7 +155,7 @@ def random_fixture():
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@use_cassette('test')
|
@use_cassette("test")
|
||||||
def test_fixtures_with_use_cassette(random_fixture):
|
def test_fixtures_with_use_cassette(random_fixture):
|
||||||
# Applying a decorator to a test function that requests features can cause
|
# Applying a decorator to a test function that requests features can cause
|
||||||
# problems if the decorator does not preserve the signature of the original
|
# problems if the decorator does not preserve the signature of the original
|
||||||
@@ -173,15 +173,13 @@ def test_custom_patchers():
|
|||||||
class Test(object):
|
class Test(object):
|
||||||
attribute = None
|
attribute = None
|
||||||
attribute2 = 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 issubclass(Test.attribute, VCRHTTPSConnection)
|
||||||
assert VCRHTTPSConnection is not Test.attribute
|
assert VCRHTTPSConnection is not Test.attribute
|
||||||
|
|
||||||
with test_vcr.use_cassette(
|
with test_vcr.use_cassette("custom_patches", custom_patches=((Test, "attribute2", VCRHTTPSConnection),)):
|
||||||
'custom_patches',
|
|
||||||
custom_patches=((Test, 'attribute2', VCRHTTPSConnection),)
|
|
||||||
):
|
|
||||||
assert issubclass(Test.attribute, VCRHTTPSConnection)
|
assert issubclass(Test.attribute, VCRHTTPSConnection)
|
||||||
assert VCRHTTPSConnection is not Test.attribute
|
assert VCRHTTPSConnection is not Test.attribute
|
||||||
assert Test.attribute is Test.attribute2
|
assert Test.attribute is Test.attribute2
|
||||||
@@ -190,11 +188,11 @@ def test_custom_patchers():
|
|||||||
def test_inject_cassette():
|
def test_inject_cassette():
|
||||||
vcr = VCR(inject_cassette=True)
|
vcr = VCR(inject_cassette=True)
|
||||||
|
|
||||||
@vcr.use_cassette('test', record_mode='once')
|
@vcr.use_cassette("test", record_mode="once")
|
||||||
def with_cassette_injected(cassette):
|
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():
|
def without_cassette_injected():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -203,92 +201,92 @@ def test_inject_cassette():
|
|||||||
|
|
||||||
|
|
||||||
def test_with_current_defaults():
|
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):
|
def changing_defaults(cassette, checks):
|
||||||
checks(cassette)
|
checks(cassette)
|
||||||
|
|
||||||
@vcr.use_cassette('test', with_current_defaults=True)
|
@vcr.use_cassette("test", with_current_defaults=True)
|
||||||
def current_defaults(cassette, checks):
|
def current_defaults(cassette, checks):
|
||||||
checks(cassette)
|
checks(cassette)
|
||||||
|
|
||||||
def assert_record_mode_once(cassette):
|
def assert_record_mode_once(cassette):
|
||||||
assert cassette.record_mode == 'once'
|
assert cassette.record_mode == "once"
|
||||||
|
|
||||||
def assert_record_mode_all(cassette):
|
def assert_record_mode_all(cassette):
|
||||||
assert cassette.record_mode == 'all'
|
assert cassette.record_mode == "all"
|
||||||
|
|
||||||
changing_defaults(assert_record_mode_once)
|
changing_defaults(assert_record_mode_once)
|
||||||
current_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)
|
changing_defaults(assert_record_mode_all)
|
||||||
current_defaults(assert_record_mode_once)
|
current_defaults(assert_record_mode_once)
|
||||||
|
|
||||||
|
|
||||||
def test_cassette_library_dir_with_decoration_and_no_explicit_path():
|
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 = VCR(inject_cassette=True, cassette_library_dir=library_dir)
|
||||||
|
|
||||||
@vcr.use_cassette()
|
@vcr.use_cassette()
|
||||||
def function_name(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()
|
function_name()
|
||||||
|
|
||||||
|
|
||||||
def test_cassette_library_dir_with_decoration_and_explicit_path():
|
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 = 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):
|
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()
|
function_name()
|
||||||
|
|
||||||
|
|
||||||
def test_cassette_library_dir_with_decoration_and_super_explicit_path():
|
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 = 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):
|
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()
|
function_name()
|
||||||
|
|
||||||
|
|
||||||
def test_cassette_library_dir_with_path_transformer():
|
def test_cassette_library_dir_with_path_transformer():
|
||||||
library_dir = '/libary_dir'
|
library_dir = "/libary_dir"
|
||||||
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir,
|
vcr = VCR(
|
||||||
path_transformer=lambda path: path + '.json')
|
inject_cassette=True, cassette_library_dir=library_dir, path_transformer=lambda path: path + ".json"
|
||||||
|
)
|
||||||
|
|
||||||
@vcr.use_cassette()
|
@vcr.use_cassette()
|
||||||
def function_name(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()
|
function_name()
|
||||||
|
|
||||||
|
|
||||||
def test_use_cassette_with_no_extra_invocation():
|
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
|
@vcr.use_cassette
|
||||||
def function_name(cassette):
|
def function_name(cassette):
|
||||||
assert cassette._path == os.path.join('/', 'function_name')
|
assert cassette._path == os.path.join("/", "function_name")
|
||||||
|
|
||||||
function_name()
|
function_name()
|
||||||
|
|
||||||
|
|
||||||
def test_path_transformer():
|
def test_path_transformer():
|
||||||
vcr = VCR(inject_cassette=True, cassette_library_dir='/',
|
vcr = VCR(inject_cassette=True, cassette_library_dir="/", path_transformer=lambda x: x + "_test")
|
||||||
path_transformer=lambda x: x + '_test')
|
|
||||||
|
|
||||||
@vcr.use_cassette
|
@vcr.use_cassette
|
||||||
def function_name(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()
|
function_name()
|
||||||
|
|
||||||
@@ -298,32 +296,31 @@ def test_cassette_name_generator_defaults_to_using_module_function_defined_in():
|
|||||||
|
|
||||||
@vcr.use_cassette
|
@vcr.use_cassette
|
||||||
def function_name(cassette):
|
def function_name(cassette):
|
||||||
assert cassette._path == os.path.join(os.path.dirname(__file__),
|
assert cassette._path == os.path.join(os.path.dirname(__file__), "function_name")
|
||||||
'function_name')
|
|
||||||
function_name()
|
function_name()
|
||||||
|
|
||||||
|
|
||||||
def test_ensure_suffix():
|
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
|
@vcr.use_cassette
|
||||||
def function_name(cassette):
|
def function_name(cassette):
|
||||||
assert cassette._path == os.path.join(os.path.dirname(__file__),
|
assert cassette._path == os.path.join(os.path.dirname(__file__), "function_name.yaml")
|
||||||
'function_name.yaml')
|
|
||||||
|
|
||||||
function_name()
|
function_name()
|
||||||
|
|
||||||
|
|
||||||
def test_additional_matchers():
|
def test_additional_matchers():
|
||||||
vcr = VCR(match_on=('uri',), inject_cassette=True)
|
vcr = VCR(match_on=("uri",), inject_cassette=True)
|
||||||
|
|
||||||
@vcr.use_cassette
|
@vcr.use_cassette
|
||||||
def function_defaults(cassette):
|
def function_defaults(cassette):
|
||||||
assert set(cassette._match_on) == set([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):
|
def function_additional(cassette):
|
||||||
assert set(cassette._match_on) == set([vcr.matchers['uri'], vcr.matchers['body']])
|
assert set(cassette._match_on) == {vcr.matchers["uri"], vcr.matchers["body"]}
|
||||||
|
|
||||||
function_defaults()
|
function_defaults()
|
||||||
function_additional()
|
function_additional()
|
||||||
@@ -331,7 +328,7 @@ def test_additional_matchers():
|
|||||||
|
|
||||||
def test_decoration_should_respect_function_return_value():
|
def test_decoration_should_respect_function_return_value():
|
||||||
vcr = VCR()
|
vcr = VCR()
|
||||||
ret = 'a-return-value'
|
ret = "a-return-value"
|
||||||
|
|
||||||
@vcr.use_cassette
|
@vcr.use_cassette
|
||||||
def function_with_return():
|
def function_with_return():
|
||||||
@@ -341,7 +338,6 @@ def test_decoration_should_respect_function_return_value():
|
|||||||
|
|
||||||
|
|
||||||
class TestVCRClass(VCR().test_case()):
|
class TestVCRClass(VCR().test_case()):
|
||||||
|
|
||||||
def no_decoration(self):
|
def no_decoration(self):
|
||||||
assert httplib.HTTPConnection == _HTTPConnection
|
assert httplib.HTTPConnection == _HTTPConnection
|
||||||
self.test_dynamically_added()
|
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
|
||||||
75
tox.ini
75
tox.ini
@@ -1,40 +1,71 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = {py26,py27,py33,py34,pypy,pypy3}-{flakes,requests27,requests26,requests25,requests24,requests23,requests22,requests1,httplib2,urllib317,urllib319,urllib3110,tornado3,tornado4,boto}
|
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
|
skipsdist = True
|
||||||
commands =
|
commands =
|
||||||
|
black --version
|
||||||
|
black --check --diff .
|
||||||
flake8 --version
|
flake8 --version
|
||||||
flake8 --exclude="./docs/conf.py"
|
flake8 --exclude=./docs/conf.py,./.tox/
|
||||||
pyflakes ./docs/conf.py
|
pyflakes ./docs/conf.py
|
||||||
deps = flake8
|
deps =
|
||||||
|
flake8
|
||||||
|
black
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
# Need to use develop install so that paths
|
||||||
|
# for aggregate code coverage combine
|
||||||
|
usedevelop=true
|
||||||
commands =
|
commands =
|
||||||
./runtests.sh {posargs}
|
./runtests.sh --cov=./vcr --cov-branch --cov-report=xml --cov-append {posargs}
|
||||||
deps =
|
deps =
|
||||||
|
Flask
|
||||||
mock
|
mock
|
||||||
pytest
|
pytest
|
||||||
pytest-httpbin
|
pytest-httpbin
|
||||||
|
pytest-cov
|
||||||
PyYAML
|
PyYAML
|
||||||
requests1: requests==1.2.3
|
ipaddress
|
||||||
requests27: requests==2.7.0
|
requests: requests>=2.22.0
|
||||||
requests26: requests==2.6.0
|
|
||||||
requests25: requests==2.5.0
|
|
||||||
requests24: requests==2.4.0
|
|
||||||
requests23: requests==2.3.0
|
|
||||||
requests22: requests==2.2.1
|
|
||||||
httplib2: httplib2
|
httplib2: httplib2
|
||||||
urllib317: urllib3==1.7.1
|
urllib3: urllib3
|
||||||
urllib319: urllib3==1.9.1
|
{py27,py35,py36,pypy}-tornado4: tornado>=4,<5
|
||||||
urllib3110: urllib3==1.10.2
|
{py27,py35,py36,pypy}-tornado4: pytest-tornado
|
||||||
{py26,py27,py33,py34,pypy}-tornado3: tornado>=3,<4
|
{py27,py35,py36}-tornado4: pycurl
|
||||||
{py26,py27,py33,py34,pypy}-tornado4: tornado>=4,<5
|
boto3: boto3
|
||||||
{py26,py27,py33,py34,pypy}-tornado3: pytest-tornado
|
boto3: urllib3
|
||||||
{py26,py27,py33,py34,pypy}-tornado4: pytest-tornado
|
aiohttp: aiohttp
|
||||||
{py26,py27,py33,py34}-tornado3: pycurl
|
aiohttp: pytest-asyncio
|
||||||
{py26,py27,py33,py34}-tornado4: pycurl
|
aiohttp: pytest-aiohttp
|
||||||
boto: boto
|
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]
|
[flake8]
|
||||||
max_line_length = 110
|
max_line_length = 110
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import warnings
|
||||||
|
import sys
|
||||||
from .config import VCR
|
from .config import VCR
|
||||||
|
|
||||||
# Set default logging handler to avoid "No handler found" warnings.
|
# Set default logging handler to avoid "No handler found" warnings.
|
||||||
try: # Python 2.7+
|
try: # Python 2.7+
|
||||||
from logging import NullHandler
|
from logging import NullHandler
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
||||||
class NullHandler(logging.Handler):
|
class NullHandler(logging.Handler):
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
pass
|
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())
|
logging.getLogger(__name__).addHandler(NullHandler())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3
vcr/_handle_coroutine.py
Normal file
3
vcr/_handle_coroutine.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
async def handle_coroutine(vcr, fn): # noqa: E999
|
||||||
|
with vcr as cassette:
|
||||||
|
return await fn(cassette) # noqa: E999
|
||||||
190
vcr/cassette.py
190
vcr/cassette.py
@@ -1,17 +1,34 @@
|
|||||||
|
import collections
|
||||||
|
import copy
|
||||||
import sys
|
import sys
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import wrapt
|
import wrapt
|
||||||
|
|
||||||
from .compat import contextlib, collections
|
from .compat import contextlib
|
||||||
from .errors import UnhandledHTTPRequestError
|
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 .patch import CassettePatcherBuilder
|
||||||
from .persist import load_cassette, save_cassette
|
|
||||||
from .serializers import yamlserializer
|
from .serializers import yamlserializer
|
||||||
|
from .persisters.filesystem import FilesystemPersister
|
||||||
from .util import partition_dict
|
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")
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -34,7 +51,7 @@ class CassetteContextDecorator(object):
|
|||||||
this class as a context manager in ``__exit__``.
|
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
|
@classmethod
|
||||||
def from_args(cls, cassette_class, **kwargs):
|
def from_args(cls, cassette_class, **kwargs):
|
||||||
@@ -49,14 +66,10 @@ class CassetteContextDecorator(object):
|
|||||||
with contextlib.ExitStack() as exit_stack:
|
with contextlib.ExitStack() as exit_stack:
|
||||||
for patcher in CassettePatcherBuilder(cassette).build():
|
for patcher in CassettePatcherBuilder(cassette).build():
|
||||||
exit_stack.enter_context(patcher)
|
exit_stack.enter_context(patcher)
|
||||||
log_format = '{action} context for cassette at {path}.'
|
log_format = "{action} context for cassette at {path}."
|
||||||
log.debug(log_format.format(
|
log.debug(log_format.format(action="Entering", path=cassette._path))
|
||||||
action="Entering", path=cassette._path
|
|
||||||
))
|
|
||||||
yield cassette
|
yield cassette
|
||||||
log.debug(log_format.format(
|
log.debug(log_format.format(action="Exiting", path=cassette._path))
|
||||||
action="Exiting", path=cassette._path
|
|
||||||
))
|
|
||||||
# TODO(@IvanMalison): Hmmm. it kind of feels like this should be
|
# TODO(@IvanMalison): Hmmm. it kind of feels like this should be
|
||||||
# somewhere else.
|
# somewhere else.
|
||||||
cassette._save()
|
cassette._save()
|
||||||
@@ -72,12 +85,11 @@ class CassetteContextDecorator(object):
|
|||||||
# pass
|
# pass
|
||||||
assert self.__finish is None, "Cassette already open."
|
assert self.__finish is None, "Cassette already open."
|
||||||
other_kwargs, cassette_kwargs = partition_dict(
|
other_kwargs, cassette_kwargs = partition_dict(
|
||||||
lambda key, _: key in self._non_cassette_arguments,
|
lambda key, _: key in self._non_cassette_arguments, self._args_getter()
|
||||||
self._args_getter()
|
|
||||||
)
|
)
|
||||||
if other_kwargs.get('path_transformer'):
|
if other_kwargs.get("path_transformer"):
|
||||||
transformer = other_kwargs['path_transformer']
|
transformer = other_kwargs["path_transformer"]
|
||||||
cassette_kwargs['path'] = transformer(cassette_kwargs['path'])
|
cassette_kwargs["path"] = transformer(cassette_kwargs["path"])
|
||||||
self.__finish = self._patch_generator(self.cls.load(**cassette_kwargs))
|
self.__finish = self._patch_generator(self.cls.load(**cassette_kwargs))
|
||||||
return next(self.__finish)
|
return next(self.__finish)
|
||||||
|
|
||||||
@@ -91,23 +103,28 @@ class CassetteContextDecorator(object):
|
|||||||
# functions are reentrant. This is required for thread
|
# functions are reentrant. This is required for thread
|
||||||
# safety and the correct operation of recursive functions.
|
# safety and the correct operation of recursive functions.
|
||||||
args_getter = self._build_args_getter_for_decorator(function)
|
args_getter = self._build_args_getter_for_decorator(function)
|
||||||
return type(self)(self.cls, args_getter)._execute_function(
|
return type(self)(self.cls, args_getter)._execute_function(function, args, kwargs)
|
||||||
function, args, kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def _execute_function(self, function, args, kwargs):
|
def _execute_function(self, function, args, kwargs):
|
||||||
if inspect.isgeneratorfunction(function):
|
def handle_function(cassette):
|
||||||
handler = self._handle_coroutine
|
if cassette.inject:
|
||||||
else:
|
return function(cassette, *args, **kwargs)
|
||||||
handler = self._handle_function
|
else:
|
||||||
return handler(function, args, kwargs)
|
return function(*args, **kwargs)
|
||||||
|
|
||||||
def _handle_coroutine(self, function, args, kwargs):
|
if iscoroutinefunction(function):
|
||||||
"""Wraps a coroutine so that we're inside the cassette context for the
|
return handle_coroutine(vcr=self, fn=handle_function)
|
||||||
duration of the coroutine.
|
if inspect.isgeneratorfunction(function):
|
||||||
|
return self._handle_generator(fn=handle_function)
|
||||||
|
|
||||||
|
return self._handle_function(fn=handle_function)
|
||||||
|
|
||||||
|
def _handle_generator(self, fn):
|
||||||
|
"""Wraps a generator so that we're inside the cassette context for the
|
||||||
|
duration of the generator.
|
||||||
"""
|
"""
|
||||||
with self as cassette:
|
with self as cassette:
|
||||||
coroutine = self.__handle_function(cassette, function, args, kwargs)
|
coroutine = fn(cassette)
|
||||||
# We don't need to catch StopIteration. The caller (Tornado's
|
# We don't need to catch StopIteration. The caller (Tornado's
|
||||||
# gen.coroutine, for example) will handle that.
|
# gen.coroutine, for example) will handle that.
|
||||||
to_yield = next(coroutine)
|
to_yield = next(coroutine)
|
||||||
@@ -117,17 +134,14 @@ class CassetteContextDecorator(object):
|
|||||||
except Exception:
|
except Exception:
|
||||||
to_yield = coroutine.throw(*sys.exc_info())
|
to_yield = coroutine.throw(*sys.exc_info())
|
||||||
else:
|
else:
|
||||||
to_yield = coroutine.send(to_send)
|
try:
|
||||||
|
to_yield = coroutine.send(to_send)
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
|
||||||
def __handle_function(self, cassette, function, args, kwargs):
|
def _handle_function(self, fn):
|
||||||
if cassette.inject:
|
|
||||||
return function(cassette, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
return function(*args, **kwargs)
|
|
||||||
|
|
||||||
def _handle_function(self, function, args, kwargs):
|
|
||||||
with self as cassette:
|
with self as cassette:
|
||||||
return self.__handle_function(cassette, function, args, kwargs)
|
return fn(cassette)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_function_name(function):
|
def get_function_name(function):
|
||||||
@@ -136,12 +150,12 @@ class CassetteContextDecorator(object):
|
|||||||
def _build_args_getter_for_decorator(self, function):
|
def _build_args_getter_for_decorator(self, function):
|
||||||
def new_args_getter():
|
def new_args_getter():
|
||||||
kwargs = self._args_getter()
|
kwargs = self._args_getter()
|
||||||
if 'path' not in kwargs:
|
if "path" not in kwargs:
|
||||||
name_generator = (kwargs.get('func_path_generator') or
|
name_generator = kwargs.get("func_path_generator") or self.get_function_name
|
||||||
self.get_function_name)
|
|
||||||
path = name_generator(function)
|
path = name_generator(function)
|
||||||
kwargs['path'] = path
|
kwargs["path"] = path
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
return new_args_getter
|
return new_args_getter
|
||||||
|
|
||||||
|
|
||||||
@@ -163,15 +177,24 @@ class Cassette(object):
|
|||||||
def use(cls, **kwargs):
|
def use(cls, **kwargs):
|
||||||
return CassetteContextDecorator.from_args(cls, **kwargs)
|
return CassetteContextDecorator.from_args(cls, **kwargs)
|
||||||
|
|
||||||
def __init__(self, path, serializer=yamlserializer, record_mode='once',
|
def __init__(
|
||||||
match_on=(uri, method), before_record_request=None,
|
self,
|
||||||
before_record_response=None, custom_patches=(),
|
path,
|
||||||
inject=False):
|
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._path = path
|
||||||
self._serializer = serializer
|
self._serializer = serializer or yamlserializer
|
||||||
self._match_on = match_on
|
self._match_on = match_on
|
||||||
self._before_record_request = before_record_request or (lambda x: x)
|
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._before_record_response = before_record_response or (lambda x: x)
|
||||||
self.inject = inject
|
self.inject = inject
|
||||||
self.record_mode = record_mode
|
self.record_mode = record_mode
|
||||||
@@ -202,14 +225,17 @@ class Cassette(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def write_protected(self):
|
def write_protected(self):
|
||||||
return self.rewound and self.record_mode == 'once' or \
|
return self.rewound and self.record_mode == "once" or self.record_mode == "none"
|
||||||
self.record_mode == 'none'
|
|
||||||
|
|
||||||
def append(self, request, response):
|
def append(self, request, response):
|
||||||
"""Add a request, response pair to this cassette"""
|
"""Add a request, response pair to this cassette"""
|
||||||
|
log.info("Appending request %s and response %s", request, response)
|
||||||
request = self._before_record_request(request)
|
request = self._before_record_request(request)
|
||||||
if not request:
|
if not request:
|
||||||
return
|
return
|
||||||
|
# Deepcopy is here because mutation of `response` will corrupt the
|
||||||
|
# real response.
|
||||||
|
response = copy.deepcopy(response)
|
||||||
response = self._before_record_response(response)
|
response = self._before_record_response(response)
|
||||||
if response is None:
|
if response is None:
|
||||||
return
|
return
|
||||||
@@ -231,9 +257,7 @@ class Cassette(object):
|
|||||||
|
|
||||||
def can_play_response_for(self, request):
|
def can_play_response_for(self, request):
|
||||||
request = self._before_record_request(request)
|
request = self._before_record_request(request)
|
||||||
return request and request in self and \
|
return request and request in self and self.record_mode != "all" and self.rewound
|
||||||
self.record_mode != 'all' and \
|
|
||||||
self.rewound
|
|
||||||
|
|
||||||
def play_response(self, request):
|
def play_response(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -246,8 +270,7 @@ class Cassette(object):
|
|||||||
return response
|
return response
|
||||||
# The cassette doesn't contain the request asked for.
|
# The cassette doesn't contain the request asked for.
|
||||||
raise UnhandledHTTPRequestError(
|
raise UnhandledHTTPRequestError(
|
||||||
"The cassette (%r) doesn't contain the request (%r) asked for"
|
"The cassette (%r) doesn't contain the request (%r) asked for" % (self._path, request)
|
||||||
% (self._path, request)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def responses_of(self, request):
|
def responses_of(self, request):
|
||||||
@@ -262,39 +285,68 @@ class Cassette(object):
|
|||||||
return responses
|
return responses
|
||||||
# The cassette doesn't contain the request asked for.
|
# The cassette doesn't contain the request asked for.
|
||||||
raise UnhandledHTTPRequestError(
|
raise UnhandledHTTPRequestError(
|
||||||
"The cassette (%r) doesn't contain the request (%r) asked for"
|
"The cassette (%r) doesn't contain the request (%r) asked for" % (self._path, request)
|
||||||
% (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):
|
def _as_dict(self):
|
||||||
return {"requests": self.requests, "responses": self.responses}
|
return {"requests": self.requests, "responses": self.responses}
|
||||||
|
|
||||||
def _save(self, force=False):
|
def _save(self, force=False):
|
||||||
if force or self.dirty:
|
if force or self.dirty:
|
||||||
save_cassette(
|
self._persister.save_cassette(self._path, self._as_dict(), serializer=self._serializer)
|
||||||
self._path,
|
|
||||||
self._as_dict(),
|
|
||||||
serializer=self._serializer
|
|
||||||
)
|
|
||||||
self.dirty = False
|
self.dirty = False
|
||||||
|
|
||||||
def _load(self):
|
def _load(self):
|
||||||
try:
|
try:
|
||||||
requests, responses = load_cassette(
|
requests, responses = self._persister.load_cassette(self._path, serializer=self._serializer)
|
||||||
self._path,
|
|
||||||
serializer=self._serializer
|
|
||||||
)
|
|
||||||
for request, response in zip(requests, responses):
|
for request, response in zip(requests, responses):
|
||||||
self.append(request, response)
|
self.append(request, response)
|
||||||
self.dirty = False
|
self.dirty = False
|
||||||
self.rewound = True
|
self.rewound = True
|
||||||
except IOError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<Cassette containing {0} recorded response(s)>".format(
|
return "<Cassette containing {} recorded response(s)>".format(len(self))
|
||||||
len(self)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""Return the number of request,response pairs stored in here"""
|
"""Return the number of request,response pairs stored in here"""
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import contextlib2 as contextlib
|
import contextlib2 as contextlib
|
||||||
else:
|
else:
|
||||||
if not hasattr(contextlib, 'ExitStack'):
|
if not hasattr(contextlib, "ExitStack"):
|
||||||
import contextlib2 as contextlib
|
import contextlib2 as contextlib
|
||||||
|
|
||||||
import collections
|
__all__ = ["mock", "contextlib"]
|
||||||
if not hasattr(collections, 'Counter'):
|
|
||||||
import backport_collections as collections
|
|
||||||
|
|
||||||
__all__ = ['mock', 'contextlib', 'collections']
|
|
||||||
|
|||||||
207
vcr/config.py
207
vcr/config.py
@@ -1,4 +1,9 @@
|
|||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
try:
|
||||||
|
from collections import abc as collections_abc # only works on python 3.3+
|
||||||
|
except ImportError:
|
||||||
|
import collections as collections_abc
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
@@ -6,20 +11,18 @@ import types
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from .compat import collections
|
|
||||||
from .cassette import Cassette
|
from .cassette import Cassette
|
||||||
from .serializers import yamlserializer, jsonserializer
|
from .serializers import yamlserializer, jsonserializer
|
||||||
|
from .persisters.filesystem import FilesystemPersister
|
||||||
from .util import compose, auto_decorate
|
from .util import compose, auto_decorate
|
||||||
from . import matchers
|
from . import matchers
|
||||||
from . import filters
|
from . import filters
|
||||||
|
|
||||||
|
|
||||||
class VCR(object):
|
class VCR(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_test_method(method_name, function):
|
def is_test_method(method_name, function):
|
||||||
return method_name.startswith('test') and \
|
return method_name.startswith("test") and isinstance(function, types.FunctionType)
|
||||||
isinstance(function, types.FunctionType)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ensure_suffix(suffix):
|
def ensure_suffix(suffix):
|
||||||
@@ -27,36 +30,47 @@ class VCR(object):
|
|||||||
if not path.endswith(suffix):
|
if not path.endswith(suffix):
|
||||||
return path + suffix
|
return path + suffix
|
||||||
return path
|
return path
|
||||||
|
|
||||||
return ensure
|
return ensure
|
||||||
|
|
||||||
def __init__(self, path_transformer=None, before_record_request=None,
|
def __init__(
|
||||||
custom_patches=(), filter_query_parameters=(), ignore_hosts=(),
|
self,
|
||||||
record_mode="once", ignore_localhost=False, filter_headers=(),
|
path_transformer=None,
|
||||||
before_record_response=None, filter_post_data_parameters=(),
|
before_record_request=None,
|
||||||
match_on=('method', 'scheme', 'host', 'port', 'path', 'query'),
|
custom_patches=(),
|
||||||
before_record=None, inject_cassette=False, serializer='yaml',
|
filter_query_parameters=(),
|
||||||
cassette_library_dir=None, func_path_generator=None,
|
ignore_hosts=(),
|
||||||
decode_compressed_response=False):
|
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.serializer = serializer
|
||||||
self.match_on = match_on
|
self.match_on = match_on
|
||||||
self.cassette_library_dir = cassette_library_dir
|
self.cassette_library_dir = cassette_library_dir
|
||||||
self.serializers = {
|
self.serializers = {"yaml": yamlserializer, "json": jsonserializer}
|
||||||
'yaml': yamlserializer,
|
|
||||||
'json': jsonserializer,
|
|
||||||
}
|
|
||||||
self.matchers = {
|
self.matchers = {
|
||||||
'method': matchers.method,
|
"method": matchers.method,
|
||||||
'uri': matchers.uri,
|
"uri": matchers.uri,
|
||||||
'url': matchers.uri, # matcher for backwards compatibility
|
"url": matchers.uri, # matcher for backwards compatibility
|
||||||
'scheme': matchers.scheme,
|
"scheme": matchers.scheme,
|
||||||
'host': matchers.host,
|
"host": matchers.host,
|
||||||
'port': matchers.port,
|
"port": matchers.port,
|
||||||
'path': matchers.path,
|
"path": matchers.path,
|
||||||
'query': matchers.query,
|
"query": matchers.query,
|
||||||
'headers': matchers.headers,
|
"headers": matchers.headers,
|
||||||
'raw_body': matchers.raw_body,
|
"raw_body": matchers.raw_body,
|
||||||
'body': matchers.body,
|
"body": matchers.body,
|
||||||
}
|
}
|
||||||
|
self.persister = FilesystemPersister
|
||||||
self.record_mode = record_mode
|
self.record_mode = record_mode
|
||||||
self.filter_headers = filter_headers
|
self.filter_headers = filter_headers
|
||||||
self.filter_query_parameters = filter_query_parameters
|
self.filter_query_parameters = filter_query_parameters
|
||||||
@@ -75,11 +89,7 @@ class VCR(object):
|
|||||||
try:
|
try:
|
||||||
serializer = self.serializers[serializer_name]
|
serializer = self.serializers[serializer_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError(
|
raise KeyError("Serializer {} doesn't exist or isn't registered".format(serializer_name))
|
||||||
"Serializer {0} doesn't exist or isn't registered".format(
|
|
||||||
serializer_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return serializer
|
return serializer
|
||||||
|
|
||||||
def _get_matchers(self, matcher_names):
|
def _get_matchers(self, matcher_names):
|
||||||
@@ -88,9 +98,7 @@ class VCR(object):
|
|||||||
for m in matcher_names:
|
for m in matcher_names:
|
||||||
matchers.append(self.matchers[m])
|
matchers.append(self.matchers[m])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError(
|
raise KeyError("Matcher {} doesn't exist or isn't registered".format(m))
|
||||||
"Matcher {0} doesn't exist or isn't registered".format(m)
|
|
||||||
)
|
|
||||||
return matchers
|
return matchers
|
||||||
|
|
||||||
def use_cassette(self, path=None, **kwargs):
|
def use_cassette(self, path=None, **kwargs):
|
||||||
@@ -112,67 +120,53 @@ class VCR(object):
|
|||||||
return Cassette.use_arg_getter(args_getter)
|
return Cassette.use_arg_getter(args_getter)
|
||||||
|
|
||||||
def get_merged_config(self, **kwargs):
|
def get_merged_config(self, **kwargs):
|
||||||
serializer_name = kwargs.get('serializer', self.serializer)
|
serializer_name = kwargs.get("serializer", self.serializer)
|
||||||
matcher_names = kwargs.get('match_on', self.match_on)
|
matcher_names = kwargs.get("match_on", self.match_on)
|
||||||
path_transformer = kwargs.get(
|
path_transformer = kwargs.get("path_transformer", self.path_transformer)
|
||||||
'path_transformer',
|
func_path_generator = kwargs.get("func_path_generator", self.func_path_generator)
|
||||||
self.path_transformer
|
cassette_library_dir = kwargs.get("cassette_library_dir", self.cassette_library_dir)
|
||||||
)
|
additional_matchers = kwargs.get("additional_matchers", ())
|
||||||
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:
|
if cassette_library_dir:
|
||||||
|
|
||||||
def add_cassette_library_dir(path):
|
def add_cassette_library_dir(path):
|
||||||
if not path.startswith(cassette_library_dir):
|
if not path.startswith(cassette_library_dir):
|
||||||
return os.path.join(cassette_library_dir, path)
|
return os.path.join(cassette_library_dir, path)
|
||||||
return 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:
|
elif not func_path_generator:
|
||||||
# If we don't have a library dir, use the functions
|
# If we don't have a library dir, use the functions
|
||||||
# location to build a full path for cassettes.
|
# location to build a full path for cassettes.
|
||||||
func_path_generator = self._build_path_from_func_using_module
|
func_path_generator = self._build_path_from_func_using_module
|
||||||
|
|
||||||
merged_config = {
|
merged_config = {
|
||||||
'serializer': self._get_serializer(serializer_name),
|
"serializer": self._get_serializer(serializer_name),
|
||||||
'match_on': self._get_matchers(
|
"persister": self.persister,
|
||||||
tuple(matcher_names) + tuple(additional_matchers)
|
"match_on": self._get_matchers(tuple(matcher_names) + tuple(additional_matchers)),
|
||||||
),
|
"record_mode": kwargs.get("record_mode", self.record_mode),
|
||||||
'record_mode': kwargs.get('record_mode', self.record_mode),
|
"before_record_request": self._build_before_record_request(kwargs),
|
||||||
'before_record_request': self._build_before_record_request(kwargs),
|
"before_record_response": self._build_before_record_response(kwargs),
|
||||||
'before_record_response': self._build_before_record_response(kwargs),
|
"custom_patches": self._custom_patches + kwargs.get("custom_patches", ()),
|
||||||
'custom_patches': self._custom_patches + kwargs.get(
|
"inject": kwargs.get("inject_cassette", self.inject_cassette),
|
||||||
'custom_patches', ()
|
"path_transformer": path_transformer,
|
||||||
),
|
"func_path_generator": func_path_generator,
|
||||||
'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:
|
if path:
|
||||||
merged_config['path'] = path
|
merged_config["path"] = path
|
||||||
return merged_config
|
return merged_config
|
||||||
|
|
||||||
def _build_before_record_response(self, options):
|
def _build_before_record_response(self, options):
|
||||||
before_record_response = options.get(
|
before_record_response = options.get("before_record_response", self.before_record_response)
|
||||||
'before_record_response', self.before_record_response
|
|
||||||
)
|
|
||||||
decode_compressed_response = options.get(
|
decode_compressed_response = options.get(
|
||||||
'decode_compressed_response', self.decode_compressed_response
|
"decode_compressed_response", self.decode_compressed_response
|
||||||
)
|
)
|
||||||
filter_functions = []
|
filter_functions = []
|
||||||
if decode_compressed_response:
|
if decode_compressed_response:
|
||||||
filter_functions.append(filters.decode_response)
|
filter_functions.append(filters.decode_response)
|
||||||
if before_record_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,)
|
before_record_response = (before_record_response,)
|
||||||
filter_functions.extend(before_record_response)
|
filter_functions.extend(before_record_response)
|
||||||
|
|
||||||
@@ -182,63 +176,43 @@ class VCR(object):
|
|||||||
break
|
break
|
||||||
response = function(response)
|
response = function(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
return before_record_response
|
return before_record_response
|
||||||
|
|
||||||
def _build_before_record_request(self, options):
|
def _build_before_record_request(self, options):
|
||||||
filter_functions = []
|
filter_functions = []
|
||||||
filter_headers = options.get(
|
filter_headers = options.get("filter_headers", self.filter_headers)
|
||||||
'filter_headers', self.filter_headers
|
filter_query_parameters = options.get("filter_query_parameters", self.filter_query_parameters)
|
||||||
)
|
|
||||||
filter_query_parameters = options.get(
|
|
||||||
'filter_query_parameters', self.filter_query_parameters
|
|
||||||
)
|
|
||||||
filter_post_data_parameters = options.get(
|
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_request",
|
"before_record_request", options.get("before_record", self.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
|
|
||||||
)
|
)
|
||||||
|
ignore_hosts = options.get("ignore_hosts", self.ignore_hosts)
|
||||||
|
ignore_localhost = options.get("ignore_localhost", self.ignore_localhost)
|
||||||
if filter_headers:
|
if filter_headers:
|
||||||
replacements = [h if isinstance(h, tuple) else (h, None)
|
replacements = [h if isinstance(h, tuple) else (h, None) for h in filter_headers]
|
||||||
for h in filter_headers]
|
filter_functions.append(functools.partial(filters.replace_headers, replacements=replacements))
|
||||||
filter_functions.append(
|
|
||||||
functools.partial(
|
|
||||||
filters.replace_headers,
|
|
||||||
replacements=replacements,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if filter_query_parameters:
|
if filter_query_parameters:
|
||||||
replacements = [p if isinstance(p, tuple) else (p, None)
|
replacements = [p if isinstance(p, tuple) else (p, None) for p in filter_query_parameters]
|
||||||
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]
|
|
||||||
filter_functions.append(
|
filter_functions.append(
|
||||||
functools.partial(
|
functools.partial(filters.replace_query_parameters, replacements=replacements)
|
||||||
filters.replace_post_data_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)
|
hosts_to_ignore = set(ignore_hosts)
|
||||||
if ignore_localhost:
|
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:
|
if hosts_to_ignore:
|
||||||
filter_functions.append(self._build_ignore_hosts(hosts_to_ignore))
|
filter_functions.append(self._build_ignore_hosts(hosts_to_ignore))
|
||||||
|
|
||||||
if before_record_request:
|
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,)
|
before_record_request = (before_record_request,)
|
||||||
filter_functions.extend(before_record_request)
|
filter_functions.extend(before_record_request)
|
||||||
|
|
||||||
@@ -249,20 +223,21 @@ class VCR(object):
|
|||||||
break
|
break
|
||||||
request = function(request)
|
request = function(request)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
return before_record_request
|
return before_record_request
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_ignore_hosts(hosts_to_ignore):
|
def _build_ignore_hosts(hosts_to_ignore):
|
||||||
def filter_ignored_hosts(request):
|
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
|
||||||
return request
|
return request
|
||||||
|
|
||||||
return filter_ignored_hosts
|
return filter_ignored_hosts
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_path_from_func_using_module(function):
|
def _build_path_from_func_using_module(function):
|
||||||
return os.path.join(os.path.dirname(inspect.getfile(function)),
|
return os.path.join(os.path.dirname(inspect.getfile(function)), function.__name__)
|
||||||
function.__name__)
|
|
||||||
|
|
||||||
def register_serializer(self, name, serializer):
|
def register_serializer(self, name, serializer):
|
||||||
self.serializers[name] = serializer
|
self.serializers[name] = serializer
|
||||||
@@ -270,6 +245,10 @@ class VCR(object):
|
|||||||
def register_matcher(self, name, matcher):
|
def register_matcher(self, name, matcher):
|
||||||
self.matchers[name] = matcher
|
self.matchers[name] = matcher
|
||||||
|
|
||||||
|
def register_persister(self, persister):
|
||||||
|
# Singleton, no name required
|
||||||
|
self.persister = persister
|
||||||
|
|
||||||
def test_case(self, predicate=None):
|
def test_case(self, predicate=None):
|
||||||
predicate = predicate or self.is_test_method
|
predicate = predicate or self.is_test_method
|
||||||
return six.with_metaclass(auto_decorate(self.use_cassette, predicate))
|
return six.with_metaclass(auto_decorate(self.use_cassette, predicate))
|
||||||
|
|||||||
@@ -1,7 +1,42 @@
|
|||||||
class CannotOverwriteExistingCassetteException(Exception):
|
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):
|
class UnhandledHTTPRequestError(KeyError):
|
||||||
"""Raised when a cassette does not contain the request we want."""
|
"""Raised when a cassette does not contain the request we want."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -82,10 +82,14 @@ def replace_post_data_parameters(request, replacements):
|
|||||||
3. A callable which accepts (key, value, request) and returns a string
|
3. A callable which accepts (key, value, request) and returns a string
|
||||||
value or None.
|
value or None.
|
||||||
"""
|
"""
|
||||||
|
if not request.body:
|
||||||
|
# Nothing to replace
|
||||||
|
return request
|
||||||
|
|
||||||
replacements = dict(replacements)
|
replacements = dict(replacements)
|
||||||
if request.method == 'POST' and not isinstance(request.body, BytesIO):
|
if request.method == "POST" and not isinstance(request.body, BytesIO):
|
||||||
if request.headers.get('Content-Type') == 'application/json':
|
if request.headers.get("Content-Type") == "application/json":
|
||||||
json_data = json.loads(request.body.decode('utf-8'))
|
json_data = json.loads(request.body.decode("utf-8"))
|
||||||
for k, rv in replacements.items():
|
for k, rv in replacements.items():
|
||||||
if k in json_data:
|
if k in json_data:
|
||||||
ov = json_data.pop(k)
|
ov = json_data.pop(k)
|
||||||
@@ -93,28 +97,26 @@ def replace_post_data_parameters(request, replacements):
|
|||||||
rv = rv(key=k, value=ov, request=request)
|
rv = rv(key=k, value=ov, request=request)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
json_data[k] = rv
|
json_data[k] = rv
|
||||||
request.body = json.dumps(json_data).encode('utf-8')
|
request.body = json.dumps(json_data).encode("utf-8")
|
||||||
else:
|
else:
|
||||||
if isinstance(request.body, text_type):
|
if isinstance(request.body, text_type):
|
||||||
request.body = request.body.encode('utf-8')
|
request.body = request.body.encode("utf-8")
|
||||||
splits = [p.partition(b'=') for p in request.body.split(b'&')]
|
splits = [p.partition(b"=") for p in request.body.split(b"&")]
|
||||||
new_splits = []
|
new_splits = []
|
||||||
for k, sep, ov in splits:
|
for k, sep, ov in splits:
|
||||||
if sep is None:
|
if sep is None:
|
||||||
new_splits.append((k, sep, ov))
|
new_splits.append((k, sep, ov))
|
||||||
else:
|
else:
|
||||||
rk = k.decode('utf-8')
|
rk = k.decode("utf-8")
|
||||||
if rk not in replacements:
|
if rk not in replacements:
|
||||||
new_splits.append((k, sep, ov))
|
new_splits.append((k, sep, ov))
|
||||||
else:
|
else:
|
||||||
rv = replacements[rk]
|
rv = replacements[rk]
|
||||||
if callable(rv):
|
if callable(rv):
|
||||||
rv = rv(key=rk, value=ov.decode('utf-8'),
|
rv = rv(key=rk, value=ov.decode("utf-8"), request=request)
|
||||||
request=request)
|
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
new_splits.append((k, sep, rv.encode('utf-8')))
|
new_splits.append((k, sep, rv.encode("utf-8")))
|
||||||
request.body = b'&'.join(k if sep is None else b''.join([k, sep, v])
|
request.body = b"&".join(k if sep is None else b"".join([k, sep, v]) for k, sep, v in new_splits)
|
||||||
for k, sep, v in new_splits)
|
|
||||||
return request
|
return request
|
||||||
|
|
||||||
|
|
||||||
@@ -133,29 +135,32 @@ def decode_response(response):
|
|||||||
2. delete the content-encoding header
|
2. delete the content-encoding header
|
||||||
3. update content-length header to decompressed length
|
3. update content-length header to decompressed length
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def is_compressed(headers):
|
def is_compressed(headers):
|
||||||
encoding = headers.get('content-encoding', [])
|
encoding = headers.get("content-encoding", [])
|
||||||
return encoding and encoding[0] in ('gzip', 'deflate')
|
return encoding and encoding[0] in ("gzip", "deflate")
|
||||||
|
|
||||||
def decompress_body(body, encoding):
|
def decompress_body(body, encoding):
|
||||||
"""Returns decompressed body according to encoding using zlib.
|
"""Returns decompressed body according to encoding using zlib.
|
||||||
to (de-)compress gzip format, use wbits = zlib.MAX_WBITS | 16
|
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)
|
return zlib.decompress(body, zlib.MAX_WBITS | 16)
|
||||||
else: # encoding == 'deflate'
|
else: # encoding == 'deflate'
|
||||||
return zlib.decompress(body)
|
return zlib.decompress(body)
|
||||||
|
|
||||||
headers = CaseInsensitiveDict(response['headers'])
|
# 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"])
|
||||||
if is_compressed(headers):
|
if is_compressed(headers):
|
||||||
response = copy.deepcopy(response)
|
encoding = headers["content-encoding"][0]
|
||||||
encoding = headers['content-encoding'][0]
|
headers["content-encoding"].remove(encoding)
|
||||||
headers['content-encoding'].remove(encoding)
|
if not headers["content-encoding"]:
|
||||||
if not headers['content-encoding']:
|
del headers["content-encoding"]
|
||||||
del headers['content-encoding']
|
|
||||||
|
|
||||||
new_body = decompress_body(response['body']['string'], encoding)
|
new_body = decompress_body(response["body"]["string"], encoding)
|
||||||
response['body']['string'] = new_body
|
response["body"]["string"] = new_body
|
||||||
headers['content-length'] = [str(len(new_body))]
|
headers["content-length"] = [str(len(new_body))]
|
||||||
response['headers'] = dict(headers)
|
response["headers"] = dict(headers)
|
||||||
return response
|
return response
|
||||||
|
|||||||
120
vcr/matchers.py
120
vcr/matchers.py
@@ -8,40 +8,56 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def method(r1, r2):
|
def method(r1, r2):
|
||||||
return r1.method == r2.method
|
assert r1.method == r2.method, "{} != {}".format(r1.method, r2.method)
|
||||||
|
|
||||||
|
|
||||||
def uri(r1, r2):
|
def uri(r1, r2):
|
||||||
return r1.uri == r2.uri
|
assert r1.uri == r2.uri, "{} != {}".format(r1.uri, r2.uri)
|
||||||
|
|
||||||
|
|
||||||
def host(r1, r2):
|
def host(r1, r2):
|
||||||
return r1.host == r2.host
|
assert r1.host == r2.host, "{} != {}".format(r1.host, r2.host)
|
||||||
|
|
||||||
|
|
||||||
def scheme(r1, r2):
|
def scheme(r1, r2):
|
||||||
return r1.scheme == r2.scheme
|
assert r1.scheme == r2.scheme, "{} != {}".format(r1.scheme, r2.scheme)
|
||||||
|
|
||||||
|
|
||||||
def port(r1, r2):
|
def port(r1, r2):
|
||||||
return r1.port == r2.port
|
assert r1.port == r2.port, "{} != {}".format(r1.port, r2.port)
|
||||||
|
|
||||||
|
|
||||||
def path(r1, r2):
|
def path(r1, r2):
|
||||||
return r1.path == r2.path
|
assert r1.path == r2.path, "{} != {}".format(r1.path, r2.path)
|
||||||
|
|
||||||
|
|
||||||
def query(r1, r2):
|
def query(r1, r2):
|
||||||
return r1.query == r2.query
|
assert r1.query == r2.query, "{} != {}".format(r1.query, r2.query)
|
||||||
|
|
||||||
|
|
||||||
def raw_body(r1, r2):
|
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):
|
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
|
return checker
|
||||||
|
|
||||||
|
|
||||||
@@ -49,14 +65,18 @@ def _transform_json(body):
|
|||||||
# Request body is always a byte string, but json.loads() wants a text
|
# Request body is always a byte string, but json.loads() wants a text
|
||||||
# string. RFC 7159 says the default encoding is UTF-8 (although UTF-16
|
# string. RFC 7159 says the default encoding is UTF-8 (although UTF-16
|
||||||
# and UTF-32 are also allowed: hmmmmm).
|
# and UTF-32 are also allowed: hmmmmm).
|
||||||
return json.loads(body.decode('utf-8'))
|
if body:
|
||||||
|
return json.loads(body.decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
_xml_header_checker = _header_checker('text/xml')
|
_xml_header_checker = _header_checker("text/xml")
|
||||||
_xmlrpc_header_checker = _header_checker('xmlrpc', header='User-Agent')
|
_xmlrpc_header_checker = _header_checker("xmlrpc", header="User-Agent")
|
||||||
_checker_transformer_pairs = (
|
_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),
|
(lambda request: _xml_header_checker(request) and _xmlrpc_header_checker(request), xmlrpc_client.loads),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -73,28 +93,50 @@ def _get_transformer(request):
|
|||||||
return _identity
|
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 {0} and {1} differ according to "
|
|
||||||
"the following matchers: {2}".format(r1, r2, differences)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def requests_match(r1, r2, matchers):
|
def requests_match(r1, r2, matchers):
|
||||||
matches = [(m(r1, r2), m) for m in matchers]
|
successes, failures = get_matchers_results(r1, r2, matchers)
|
||||||
_log_matches(r1, r2, matches)
|
if failures:
|
||||||
return all([m[0] for m in matches])
|
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.
|
# versions. So this just strips the tags before deserializing.
|
||||||
|
|
||||||
STRINGS_TO_NUKE = [
|
STRINGS_TO_NUKE = [
|
||||||
'!!python/object:vcr.request.Request',
|
"!!python/object:vcr.request.Request",
|
||||||
'!!python/object/apply:__builtin__.frozenset',
|
"!!python/object/apply:__builtin__.frozenset",
|
||||||
'!!python/object/apply:builtins.frozenset',
|
"!!python/object/apply:builtins.frozenset",
|
||||||
]
|
]
|
||||||
for s in STRINGS_TO_NUKE:
|
for s in STRINGS_TO_NUKE:
|
||||||
cassette = cassette.replace(s, '')
|
cassette = cassette.replace(s, "")
|
||||||
return cassette
|
return cassette
|
||||||
|
|
||||||
|
|
||||||
PARTS = [
|
PARTS = ["protocol", "host", "port", "path"]
|
||||||
'protocol',
|
|
||||||
'host',
|
|
||||||
'port',
|
|
||||||
'path',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def build_uri(**parts):
|
def build_uri(**parts):
|
||||||
port = parts['port']
|
port = parts["port"]
|
||||||
scheme = parts['protocol']
|
scheme = parts["protocol"]
|
||||||
default_port = {'https': 443, 'http': 80}[scheme]
|
default_port = {"https": 443, "http": 80}[scheme]
|
||||||
parts['port'] = ':{0}'.format(port) if port != default_port else ''
|
parts["port"] = ":{}".format(port) if port != default_port else ""
|
||||||
return "{protocol}://{host}{port}{path}".format(**parts)
|
return "{protocol}://{host}{port}{path}".format(**parts)
|
||||||
|
|
||||||
|
|
||||||
def _migrate(data):
|
def _migrate(data):
|
||||||
interactions = []
|
interactions = []
|
||||||
for item in data:
|
for item in data:
|
||||||
req = item['request']
|
req = item["request"]
|
||||||
res = item['response']
|
res = item["response"]
|
||||||
uri = dict((k, req.pop(k)) for k in PARTS)
|
uri = {k: req.pop(k) for k in PARTS}
|
||||||
req['uri'] = build_uri(**uri)
|
req["uri"] = build_uri(**uri)
|
||||||
# convert headers to dict of lists
|
# convert headers to dict of lists
|
||||||
headers = req['headers']
|
headers = req["headers"]
|
||||||
for k in headers:
|
for k in headers:
|
||||||
headers[k] = [headers[k]]
|
headers[k] = [headers[k]]
|
||||||
response_headers = {}
|
response_headers = {}
|
||||||
for k, v in get_httpmessage(
|
for k, v in get_httpmessage(b"".join(h.encode("utf-8") for h in res["headers"])).items():
|
||||||
b"".join(h.encode('utf-8') for h in res['headers'])
|
|
||||||
).items():
|
|
||||||
response_headers.setdefault(k, [])
|
response_headers.setdefault(k, [])
|
||||||
response_headers[k].append(v)
|
response_headers[k].append(v)
|
||||||
res['headers'] = response_headers
|
res["headers"] = response_headers
|
||||||
interactions.append({'request': req, 'response': res})
|
interactions.append({"request": req, "response": res})
|
||||||
return {
|
return {
|
||||||
'requests': [
|
"requests": [request.Request._from_dict(i["request"]) for i in interactions],
|
||||||
request.Request._from_dict(i['request']) for i in interactions
|
"responses": [i["response"] 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):
|
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):
|
def _already_migrated(data):
|
||||||
try:
|
try:
|
||||||
if data.get('version') == 1:
|
if data.get("version") == 1:
|
||||||
return True
|
return True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
@@ -116,9 +107,7 @@ def migrate_yml(in_fp, out_fp):
|
|||||||
if _already_migrated(data):
|
if _already_migrated(data):
|
||||||
return False
|
return False
|
||||||
for i in range(len(data)):
|
for i in range(len(data)):
|
||||||
data[i]['request']['headers'] = _list_of_tuples_to_dict(
|
data[i]["request"]["headers"] = _list_of_tuples_to_dict(data[i]["request"]["headers"])
|
||||||
data[i]['request']['headers']
|
|
||||||
)
|
|
||||||
interactions = _migrate(data)
|
interactions = _migrate(data)
|
||||||
out_fp.write(serialize(interactions, yamlserializer))
|
out_fp.write(serialize(interactions, yamlserializer))
|
||||||
return True
|
return True
|
||||||
@@ -127,42 +116,42 @@ def migrate_yml(in_fp, out_fp):
|
|||||||
def migrate(file_path, migration_fn):
|
def migrate(file_path, migration_fn):
|
||||||
# because we assume that original files can be reverted
|
# because we assume that original files can be reverted
|
||||||
# we will try to copy the content. (os.rename not needed)
|
# we will try to copy the content. (os.rename not needed)
|
||||||
with tempfile.TemporaryFile(mode='w+') as out_fp:
|
with tempfile.TemporaryFile(mode="w+") as out_fp:
|
||||||
with open(file_path, 'r') as in_fp:
|
with open(file_path, "r") as in_fp:
|
||||||
if not migration_fn(in_fp, out_fp):
|
if not migration_fn(in_fp, out_fp):
|
||||||
return False
|
return False
|
||||||
with open(file_path, 'w') as in_fp:
|
with open(file_path, "w") as in_fp:
|
||||||
out_fp.seek(0)
|
out_fp.seek(0)
|
||||||
shutil.copyfileobj(out_fp, in_fp)
|
shutil.copyfileobj(out_fp, in_fp)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def try_migrate(path):
|
def try_migrate(path):
|
||||||
if path.endswith('.json'):
|
if path.endswith(".json"):
|
||||||
return migrate(path, migrate_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 migrate(path, migrate_yml)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
raise SystemExit("Please provide path to cassettes directory or file. "
|
raise SystemExit(
|
||||||
"Usage: python -m vcr.migration PATH")
|
"Please provide path to cassettes directory or file. " "Usage: python -m vcr.migration PATH"
|
||||||
|
)
|
||||||
|
|
||||||
path = sys.argv[1]
|
path = sys.argv[1]
|
||||||
if not os.path.isabs(path):
|
if not os.path.isabs(path):
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
files = [path]
|
files = [path]
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
files = (os.path.join(root, name)
|
files = (os.path.join(root, name) for (root, dirs, files) in os.walk(path) for name in files)
|
||||||
for (root, dirs, files) in os.walk(path)
|
|
||||||
for name in files)
|
|
||||||
for file_path in files:
|
for file_path in files:
|
||||||
migrated = try_migrate(file_path)
|
migrated = try_migrate(file_path)
|
||||||
status = 'OK' if migrated else 'FAIL'
|
status = "OK" if migrated else "FAIL"
|
||||||
sys.stderr.write("[{0}] {1}\n".format(status, file_path))
|
sys.stderr.write("[{}] {}\n".format(status, file_path))
|
||||||
sys.stderr.write("Done.\n")
|
sys.stderr.write("Done.\n")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
319
vcr/patch.py
319
vcr/patch.py
@@ -1,4 +1,4 @@
|
|||||||
'''Utilities for patching in cassettes'''
|
"""Utilities for patching in cassettes"""
|
||||||
import functools
|
import functools
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
@@ -6,15 +6,34 @@ from .compat import contextlib, mock
|
|||||||
from .stubs import VCRHTTPConnection, VCRHTTPSConnection
|
from .stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||||
from six.moves import http_client as httplib
|
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
|
# Save some of the original types for the purposes of unpatching
|
||||||
_HTTPConnection = httplib.HTTPConnection
|
_HTTPConnection = httplib.HTTPConnection
|
||||||
_HTTPSConnection = httplib.HTTPSConnection
|
_HTTPSConnection = httplib.HTTPSConnection
|
||||||
|
|
||||||
|
# Try to save the original types for boto3
|
||||||
# Try to save the original types for requests
|
|
||||||
try:
|
try:
|
||||||
import requests.packages.urllib3.connectionpool as cpool
|
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 = AWSHTTPSConnection
|
||||||
|
_cpoolBoto3HTTPConnection = AWSHTTPConnection
|
||||||
|
_cpoolBoto3HTTPSConnection = AWSHTTPSConnection
|
||||||
|
|
||||||
|
cpool = None
|
||||||
|
# Try to save the original types for urllib3
|
||||||
|
try:
|
||||||
|
import urllib3.connectionpool as cpool
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -22,15 +41,16 @@ else:
|
|||||||
_cpoolHTTPConnection = cpool.HTTPConnection
|
_cpoolHTTPConnection = cpool.HTTPConnection
|
||||||
_cpoolHTTPSConnection = cpool.HTTPSConnection
|
_cpoolHTTPSConnection = cpool.HTTPSConnection
|
||||||
|
|
||||||
|
# Try to save the original types for requests
|
||||||
# Try to save the original types for urllib3
|
|
||||||
try:
|
try:
|
||||||
import urllib3
|
if not cpool:
|
||||||
|
import requests.packages.urllib3.connectionpool as cpool
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
_VerifiedHTTPSConnection = urllib3.connectionpool.VerifiedHTTPSConnection
|
_VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
|
||||||
|
_cpoolHTTPConnection = cpool.HTTPConnection
|
||||||
|
_cpoolHTTPSConnection = cpool.HTTPSConnection
|
||||||
|
|
||||||
# Try to save the original types for httplib2
|
# Try to save the original types for httplib2
|
||||||
try:
|
try:
|
||||||
@@ -42,7 +62,6 @@ else:
|
|||||||
_HTTPSConnectionWithTimeout = httplib2.HTTPSConnectionWithTimeout
|
_HTTPSConnectionWithTimeout = httplib2.HTTPSConnectionWithTimeout
|
||||||
_SCHEME_TO_CONNECTION = httplib2.SCHEME_TO_CONNECTION
|
_SCHEME_TO_CONNECTION = httplib2.SCHEME_TO_CONNECTION
|
||||||
|
|
||||||
|
|
||||||
# Try to save the original types for boto
|
# Try to save the original types for boto
|
||||||
try:
|
try:
|
||||||
import boto.https_connection
|
import boto.https_connection
|
||||||
@@ -51,34 +70,35 @@ except ImportError: # pragma: no cover
|
|||||||
else:
|
else:
|
||||||
_CertValidatingHTTPSConnection = boto.https_connection.CertValidatingHTTPSConnection
|
_CertValidatingHTTPSConnection = boto.https_connection.CertValidatingHTTPSConnection
|
||||||
|
|
||||||
|
|
||||||
# Try to save the original types for Tornado
|
# Try to save the original types for Tornado
|
||||||
try:
|
try:
|
||||||
import tornado.simple_httpclient
|
import tornado.simple_httpclient
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
_SimpleAsyncHTTPClient_fetch_impl = \
|
_SimpleAsyncHTTPClient_fetch_impl = tornado.simple_httpclient.SimpleAsyncHTTPClient.fetch_impl
|
||||||
tornado.simple_httpclient.SimpleAsyncHTTPClient.fetch_impl
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import tornado.curl_httpclient
|
import tornado.curl_httpclient
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
_CurlAsyncHTTPClient_fetch_impl = \
|
_CurlAsyncHTTPClient_fetch_impl = tornado.curl_httpclient.CurlAsyncHTTPClient.fetch_impl
|
||||||
tornado.curl_httpclient.CurlAsyncHTTPClient.fetch_impl
|
|
||||||
|
try:
|
||||||
|
import aiohttp.client
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
_AiohttpClientSessionRequest = aiohttp.client.ClientSession._request
|
||||||
|
|
||||||
|
|
||||||
class CassettePatcherBuilder(object):
|
class CassettePatcherBuilder(object):
|
||||||
|
|
||||||
def _build_patchers_from_mock_triples_decorator(function):
|
def _build_patchers_from_mock_triples_decorator(function):
|
||||||
@functools.wraps(function)
|
@functools.wraps(function)
|
||||||
def wrapped(self, *args, **kwargs):
|
def wrapped(self, *args, **kwargs):
|
||||||
return self._build_patchers_from_mock_triples(
|
return self._build_patchers_from_mock_triples(function(self, *args, **kwargs))
|
||||||
function(self, *args, **kwargs)
|
|
||||||
)
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
def __init__(self, cassette):
|
def __init__(self, cassette):
|
||||||
@@ -87,11 +107,15 @@ class CassettePatcherBuilder(object):
|
|||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
return itertools.chain(
|
return itertools.chain(
|
||||||
self._httplib(), self._requests(), self._urllib3(),
|
self._httplib(),
|
||||||
self._httplib2(), self._boto(), self._tornado(),
|
self._requests(),
|
||||||
self._build_patchers_from_mock_triples(
|
self._boto3(),
|
||||||
self._cassette.custom_patches
|
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):
|
def _build_patchers_from_mock_triples(self, mock_triples):
|
||||||
@@ -104,9 +128,9 @@ class CassettePatcherBuilder(object):
|
|||||||
if not hasattr(obj, patched_attribute):
|
if not hasattr(obj, patched_attribute):
|
||||||
return
|
return
|
||||||
|
|
||||||
return mock.patch.object(obj, patched_attribute,
|
return mock.patch.object(
|
||||||
self._recursively_apply_get_cassette_subclass(
|
obj, patched_attribute, self._recursively_apply_get_cassette_subclass(replacement_class)
|
||||||
replacement_class))
|
)
|
||||||
|
|
||||||
def _recursively_apply_get_cassette_subclass(self, replacement_dict_or_obj):
|
def _recursively_apply_get_cassette_subclass(self, replacement_dict_or_obj):
|
||||||
"""One of the subtleties of this class is that it does not directly
|
"""One of the subtleties of this class is that it does not directly
|
||||||
@@ -128,13 +152,11 @@ class CassettePatcherBuilder(object):
|
|||||||
"""
|
"""
|
||||||
if isinstance(replacement_dict_or_obj, dict):
|
if isinstance(replacement_dict_or_obj, dict):
|
||||||
for key, replacement_obj in replacement_dict_or_obj.items():
|
for key, replacement_obj in replacement_dict_or_obj.items():
|
||||||
replacement_obj = self._recursively_apply_get_cassette_subclass(
|
replacement_obj = self._recursively_apply_get_cassette_subclass(replacement_obj)
|
||||||
replacement_obj)
|
|
||||||
replacement_dict_or_obj[key] = replacement_obj
|
replacement_dict_or_obj[key] = replacement_obj
|
||||||
return replacement_dict_or_obj
|
return replacement_dict_or_obj
|
||||||
if hasattr(replacement_dict_or_obj, 'cassette'):
|
if hasattr(replacement_dict_or_obj, "cassette"):
|
||||||
replacement_dict_or_obj = self._get_cassette_subclass(
|
replacement_dict_or_obj = self._get_cassette_subclass(replacement_dict_or_obj)
|
||||||
replacement_dict_or_obj)
|
|
||||||
return replacement_dict_or_obj
|
return replacement_dict_or_obj
|
||||||
|
|
||||||
def _get_cassette_subclass(self, klass):
|
def _get_cassette_subclass(self, klass):
|
||||||
@@ -149,22 +171,45 @@ class CassettePatcherBuilder(object):
|
|||||||
bases = (base_class,)
|
bases = (base_class,)
|
||||||
if not issubclass(base_class, object): # Check for old style class
|
if not issubclass(base_class, object): # Check for old style class
|
||||||
bases += (object,)
|
bases += (object,)
|
||||||
return type('{0}{1}'.format(base_class.__name__, self._cassette._path),
|
return type(
|
||||||
bases, dict(cassette=self._cassette))
|
"{}{}".format(base_class.__name__, self._cassette._path), bases, dict(cassette=self._cassette)
|
||||||
|
)
|
||||||
|
|
||||||
@_build_patchers_from_mock_triples_decorator
|
@_build_patchers_from_mock_triples_decorator
|
||||||
def _httplib(self):
|
def _httplib(self):
|
||||||
yield httplib, 'HTTPConnection', VCRHTTPConnection
|
yield httplib, "HTTPConnection", VCRHTTPConnection
|
||||||
yield httplib, 'HTTPSConnection', VCRHTTPSConnection
|
yield httplib, "HTTPSConnection", VCRHTTPSConnection
|
||||||
|
|
||||||
def _requests(self):
|
def _requests(self):
|
||||||
try:
|
try:
|
||||||
import requests.packages.urllib3.connectionpool as cpool
|
from .stubs import requests_stubs
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
return ()
|
return ()
|
||||||
from .stubs import requests_stubs
|
|
||||||
return self._urllib3_patchers(cpool, requests_stubs)
|
return self._urllib3_patchers(cpool, requests_stubs)
|
||||||
|
|
||||||
|
@_build_patchers_from_mock_triples_decorator
|
||||||
|
def _boto3(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
# botocore using awsrequest
|
||||||
|
import botocore.awsrequest as cpool
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
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):
|
def _patched_get_conn(self, connection_pool_class, connection_class_getter):
|
||||||
get_conn = connection_pool_class._get_conn
|
get_conn = connection_pool_class._get_conn
|
||||||
|
|
||||||
@@ -172,8 +217,8 @@ class CassettePatcherBuilder(object):
|
|||||||
def patched_get_conn(pool, timeout=None):
|
def patched_get_conn(pool, timeout=None):
|
||||||
connection = get_conn(pool, timeout)
|
connection = get_conn(pool, timeout)
|
||||||
connection_class = (
|
connection_class = (
|
||||||
pool.ConnectionCls if hasattr(pool, 'ConnectionCls')
|
pool.ConnectionCls if hasattr(pool, "ConnectionCls") else connection_class_getter()
|
||||||
else connection_class_getter())
|
)
|
||||||
# We need to make sure that we are actually providing a
|
# We need to make sure that we are actually providing a
|
||||||
# patched version of the connection class. This might not
|
# patched version of the connection class. This might not
|
||||||
# always be the case because the pool keeps previously
|
# always be the case because the pool keeps previously
|
||||||
@@ -203,6 +248,7 @@ class CassettePatcherBuilder(object):
|
|||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
return ()
|
return ()
|
||||||
from .stubs import urllib3_stubs
|
from .stubs import urllib3_stubs
|
||||||
|
|
||||||
return self._urllib3_patchers(cpool, urllib3_stubs)
|
return self._urllib3_patchers(cpool, urllib3_stubs)
|
||||||
|
|
||||||
@_build_patchers_from_mock_triples_decorator
|
@_build_patchers_from_mock_triples_decorator
|
||||||
@@ -215,10 +261,12 @@ class CassettePatcherBuilder(object):
|
|||||||
from .stubs.httplib2_stubs import VCRHTTPConnectionWithTimeout
|
from .stubs.httplib2_stubs import VCRHTTPConnectionWithTimeout
|
||||||
from .stubs.httplib2_stubs import VCRHTTPSConnectionWithTimeout
|
from .stubs.httplib2_stubs import VCRHTTPSConnectionWithTimeout
|
||||||
|
|
||||||
yield cpool, 'HTTPConnectionWithTimeout', VCRHTTPConnectionWithTimeout
|
yield cpool, "HTTPConnectionWithTimeout", VCRHTTPConnectionWithTimeout
|
||||||
yield cpool, 'HTTPSConnectionWithTimeout', VCRHTTPSConnectionWithTimeout
|
yield cpool, "HTTPSConnectionWithTimeout", VCRHTTPSConnectionWithTimeout
|
||||||
yield cpool, 'SCHEME_TO_CONNECTION', {'http': VCRHTTPConnectionWithTimeout,
|
yield cpool, "SCHEME_TO_CONNECTION", {
|
||||||
'https': VCRHTTPSConnectionWithTimeout}
|
"http": VCRHTTPConnectionWithTimeout,
|
||||||
|
"https": VCRHTTPSConnectionWithTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
@_build_patchers_from_mock_triples_decorator
|
@_build_patchers_from_mock_triples_decorator
|
||||||
def _boto(self):
|
def _boto(self):
|
||||||
@@ -228,7 +276,8 @@ class CassettePatcherBuilder(object):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
from .stubs.boto_stubs import VCRCertValidatingHTTPSConnection
|
from .stubs.boto_stubs import VCRCertValidatingHTTPSConnection
|
||||||
yield cpool, 'CertValidatingHTTPSConnection', VCRCertValidatingHTTPSConnection
|
|
||||||
|
yield cpool, "CertValidatingHTTPSConnection", VCRCertValidatingHTTPSConnection
|
||||||
|
|
||||||
@_build_patchers_from_mock_triples_decorator
|
@_build_patchers_from_mock_triples_decorator
|
||||||
def _tornado(self):
|
def _tornado(self):
|
||||||
@@ -239,10 +288,8 @@ class CassettePatcherBuilder(object):
|
|||||||
else:
|
else:
|
||||||
from .stubs.tornado_stubs import vcr_fetch_impl
|
from .stubs.tornado_stubs import vcr_fetch_impl
|
||||||
|
|
||||||
new_fetch_impl = vcr_fetch_impl(
|
new_fetch_impl = vcr_fetch_impl(self._cassette, _SimpleAsyncHTTPClient_fetch_impl)
|
||||||
self._cassette, _SimpleAsyncHTTPClient_fetch_impl
|
yield simple.SimpleAsyncHTTPClient, "fetch_impl", new_fetch_impl
|
||||||
)
|
|
||||||
yield simple.SimpleAsyncHTTPClient, 'fetch_impl', new_fetch_impl
|
|
||||||
try:
|
try:
|
||||||
import tornado.curl_httpclient as curl
|
import tornado.curl_httpclient as curl
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
@@ -250,10 +297,20 @@ class CassettePatcherBuilder(object):
|
|||||||
else:
|
else:
|
||||||
from .stubs.tornado_stubs import vcr_fetch_impl
|
from .stubs.tornado_stubs import vcr_fetch_impl
|
||||||
|
|
||||||
new_fetch_impl = vcr_fetch_impl(
|
new_fetch_impl = vcr_fetch_impl(self._cassette, _CurlAsyncHTTPClient_fetch_impl)
|
||||||
self._cassette, _CurlAsyncHTTPClient_fetch_impl
|
yield curl.CurlAsyncHTTPClient, "fetch_impl", new_fetch_impl
|
||||||
)
|
|
||||||
yield curl.CurlAsyncHTTPClient, 'fetch_impl', new_fetch_impl
|
@_build_patchers_from_mock_triples_decorator
|
||||||
|
def _aiohttp(self):
|
||||||
|
try:
|
||||||
|
import aiohttp.client as client
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
from .stubs.aiohttp_stubs import vcr_request
|
||||||
|
|
||||||
|
new_request = vcr_request(self._cassette, _AiohttpClientSessionRequest)
|
||||||
|
yield client.ClientSession, "_request", new_request
|
||||||
|
|
||||||
def _urllib3_patchers(self, cpool, stubs):
|
def _urllib3_patchers(self, cpool, stubs):
|
||||||
http_connection_remover = ConnectionRemover(
|
http_connection_remover = ConnectionRemover(
|
||||||
@@ -263,35 +320,45 @@ class CassettePatcherBuilder(object):
|
|||||||
self._get_cassette_subclass(stubs.VCRRequestsHTTPSConnection)
|
self._get_cassette_subclass(stubs.VCRRequestsHTTPSConnection)
|
||||||
)
|
)
|
||||||
mock_triples = (
|
mock_triples = (
|
||||||
(cpool, 'VerifiedHTTPSConnection', stubs.VCRRequestsHTTPSConnection),
|
(cpool, "VerifiedHTTPSConnection", stubs.VCRRequestsHTTPSConnection),
|
||||||
(cpool, 'VerifiedHTTPSConnection', stubs.VCRRequestsHTTPSConnection),
|
(cpool, "HTTPConnection", stubs.VCRRequestsHTTPConnection),
|
||||||
(cpool, 'HTTPConnection', stubs.VCRRequestsHTTPConnection),
|
(cpool, "HTTPSConnection", stubs.VCRRequestsHTTPSConnection),
|
||||||
(cpool, 'HTTPSConnection', stubs.VCRRequestsHTTPSConnection),
|
(cpool, "is_connection_dropped", mock.Mock(return_value=False)), # Needed on Windows only
|
||||||
(cpool, 'is_connection_dropped', mock.Mock(return_value=False)), # Needed on Windows only
|
(cpool.HTTPConnectionPool, "ConnectionCls", stubs.VCRRequestsHTTPConnection),
|
||||||
(cpool.HTTPConnectionPool, 'ConnectionCls', stubs.VCRRequestsHTTPConnection),
|
(cpool.HTTPSConnectionPool, "ConnectionCls", stubs.VCRRequestsHTTPSConnection),
|
||||||
(cpool.HTTPSConnectionPool, 'ConnectionCls', stubs.VCRRequestsHTTPSConnection),
|
|
||||||
)
|
)
|
||||||
# These handle making sure that sessions only use the
|
# These handle making sure that sessions only use the
|
||||||
# connections of the appropriate type.
|
# connections of the appropriate type.
|
||||||
mock_triples += ((cpool.HTTPConnectionPool, '_get_conn',
|
mock_triples += (
|
||||||
self._patched_get_conn(cpool.HTTPConnectionPool,
|
(
|
||||||
lambda: cpool.HTTPConnection)),
|
cpool.HTTPConnectionPool,
|
||||||
(cpool.HTTPSConnectionPool, '_get_conn',
|
"_get_conn",
|
||||||
self._patched_get_conn(cpool.HTTPSConnectionPool,
|
self._patched_get_conn(cpool.HTTPConnectionPool, lambda: cpool.HTTPConnection),
|
||||||
lambda: cpool.HTTPSConnection)),
|
),
|
||||||
(cpool.HTTPConnectionPool, '_new_conn',
|
(
|
||||||
self._patched_new_conn(cpool.HTTPConnectionPool,
|
cpool.HTTPSConnectionPool,
|
||||||
http_connection_remover)),
|
"_get_conn",
|
||||||
(cpool.HTTPSConnectionPool, '_new_conn',
|
self._patched_get_conn(cpool.HTTPSConnectionPool, lambda: cpool.HTTPSConnection),
|
||||||
self._patched_new_conn(cpool.HTTPSConnectionPool,
|
),
|
||||||
https_connection_remover)))
|
(
|
||||||
|
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),
|
return itertools.chain(
|
||||||
(http_connection_remover, https_connection_remover))
|
self._build_patchers_from_mock_triples(mock_triples),
|
||||||
|
(http_connection_remover, https_connection_remover),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConnectionRemover(object):
|
class ConnectionRemover(object):
|
||||||
|
|
||||||
def __init__(self, connection_class):
|
def __init__(self, connection_class):
|
||||||
self._connection_class = connection_class
|
self._connection_class = connection_class
|
||||||
self._connection_pool_to_connections = {}
|
self._connection_pool_to_connections = {}
|
||||||
@@ -321,75 +388,109 @@ class ConnectionRemover(object):
|
|||||||
|
|
||||||
|
|
||||||
def reset_patchers():
|
def reset_patchers():
|
||||||
yield mock.patch.object(httplib, 'HTTPConnection', _HTTPConnection)
|
yield mock.patch.object(httplib, "HTTPConnection", _HTTPConnection)
|
||||||
yield mock.patch.object(httplib, 'HTTPSConnection', _HTTPSConnection)
|
yield mock.patch.object(httplib, "HTTPSConnection", _HTTPSConnection)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import requests.packages.urllib3.connectionpool as cpool
|
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)
|
||||||
|
# Second, this is unmocking twice the same classes with different namespaces
|
||||||
|
# and is creating weird issues and bugs:
|
||||||
|
# > AssertionError: assert <class 'urllib3.connection.HTTPConnection'>
|
||||||
|
# > is <class 'requests.packages.urllib3.connection.HTTPConnection'>
|
||||||
|
# This assert should work!!!
|
||||||
|
# Note that this also means that now, requests.packages is never imported
|
||||||
|
# if requests 2.16.3 or greater is used with VCRPy.
|
||||||
|
import requests.packages.urllib3.connectionpool as cpool
|
||||||
|
else:
|
||||||
|
raise ImportError("Skip requests not vendored anymore")
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# unpatch requests v1.x
|
# unpatch requests v1.x
|
||||||
yield mock.patch.object(cpool, 'VerifiedHTTPSConnection', _VerifiedHTTPSConnection)
|
yield mock.patch.object(cpool, "VerifiedHTTPSConnection", _VerifiedHTTPSConnection)
|
||||||
yield mock.patch.object(cpool, 'HTTPConnection', _cpoolHTTPConnection)
|
yield mock.patch.object(cpool, "HTTPConnection", _cpoolHTTPConnection)
|
||||||
# unpatch requests v2.x
|
# unpatch requests v2.x
|
||||||
if hasattr(cpool.HTTPConnectionPool, 'ConnectionCls'):
|
if hasattr(cpool.HTTPConnectionPool, "ConnectionCls"):
|
||||||
yield mock.patch.object(cpool.HTTPConnectionPool, 'ConnectionCls',
|
yield mock.patch.object(cpool.HTTPConnectionPool, "ConnectionCls", _cpoolHTTPConnection)
|
||||||
_cpoolHTTPConnection)
|
yield mock.patch.object(cpool.HTTPSConnectionPool, "ConnectionCls", _cpoolHTTPSConnection)
|
||||||
yield mock.patch.object(cpool.HTTPSConnectionPool, 'ConnectionCls',
|
|
||||||
_cpoolHTTPSConnection)
|
|
||||||
|
|
||||||
if hasattr(cpool, 'HTTPSConnection'):
|
if hasattr(cpool, "HTTPSConnection"):
|
||||||
yield mock.patch.object(cpool, 'HTTPSConnection', _cpoolHTTPSConnection)
|
yield mock.patch.object(cpool, "HTTPSConnection", _cpoolHTTPSConnection)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import urllib3.connectionpool as cpool
|
import urllib3.connectionpool as cpool
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield mock.patch.object(cpool, 'VerifiedHTTPSConnection', _VerifiedHTTPSConnection)
|
yield mock.patch.object(cpool, "VerifiedHTTPSConnection", _VerifiedHTTPSConnection)
|
||||||
yield mock.patch.object(cpool, 'HTTPConnection', _HTTPConnection)
|
yield mock.patch.object(cpool, "HTTPConnection", _cpoolHTTPConnection)
|
||||||
yield mock.patch.object(cpool, 'HTTPSConnection', _HTTPSConnection)
|
yield mock.patch.object(cpool, "HTTPSConnection", _cpoolHTTPSConnection)
|
||||||
if hasattr(cpool.HTTPConnectionPool, 'ConnectionCls'):
|
if hasattr(cpool.HTTPConnectionPool, "ConnectionCls"):
|
||||||
yield mock.patch.object(cpool.HTTPConnectionPool, 'ConnectionCls', _HTTPConnection)
|
yield mock.patch.object(cpool.HTTPConnectionPool, "ConnectionCls", _cpoolHTTPConnection)
|
||||||
yield mock.patch.object(cpool.HTTPSConnectionPool, 'ConnectionCls', _HTTPSConnection)
|
yield mock.patch.object(cpool.HTTPSConnectionPool, "ConnectionCls", _cpoolHTTPSConnection)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# unpatch botocore with awsrequest
|
||||||
|
import botocore.awsrequest as cpool
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
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)
|
||||||
|
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:
|
try:
|
||||||
import httplib2 as cpool
|
import httplib2 as cpool
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield mock.patch.object(cpool, 'HTTPConnectionWithTimeout', _HTTPConnectionWithTimeout)
|
yield mock.patch.object(cpool, "HTTPConnectionWithTimeout", _HTTPConnectionWithTimeout)
|
||||||
yield mock.patch.object(cpool, 'HTTPSConnectionWithTimeout', _HTTPSConnectionWithTimeout)
|
yield mock.patch.object(cpool, "HTTPSConnectionWithTimeout", _HTTPSConnectionWithTimeout)
|
||||||
yield mock.patch.object(cpool, 'SCHEME_TO_CONNECTION', _SCHEME_TO_CONNECTION)
|
yield mock.patch.object(cpool, "SCHEME_TO_CONNECTION", _SCHEME_TO_CONNECTION)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import boto.https_connection as cpool
|
import boto.https_connection as cpool
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield mock.patch.object(cpool, 'CertValidatingHTTPSConnection',
|
yield mock.patch.object(cpool, "CertValidatingHTTPSConnection", _CertValidatingHTTPSConnection)
|
||||||
_CertValidatingHTTPSConnection)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import tornado.simple_httpclient as simple
|
import tornado.simple_httpclient as simple
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield mock.patch.object(
|
yield mock.patch.object(simple.SimpleAsyncHTTPClient, "fetch_impl", _SimpleAsyncHTTPClient_fetch_impl)
|
||||||
simple.SimpleAsyncHTTPClient,
|
|
||||||
'fetch_impl',
|
|
||||||
_SimpleAsyncHTTPClient_fetch_impl,
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
import tornado.curl_httpclient as curl
|
import tornado.curl_httpclient as curl
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield mock.patch.object(
|
yield mock.patch.object(curl.CurlAsyncHTTPClient, "fetch_impl", _CurlAsyncHTTPClient_fetch_impl)
|
||||||
curl.CurlAsyncHTTPClient,
|
|
||||||
'fetch_impl',
|
|
||||||
_CurlAsyncHTTPClient_fetch_impl,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
from .persisters.filesystem import FilesystemPersister
|
|
||||||
from .serialize import serialize, deserialize
|
|
||||||
|
|
||||||
|
|
||||||
def load_cassette(cassette_path, serializer):
|
|
||||||
with open(cassette_path) as f:
|
|
||||||
cassette_content = f.read()
|
|
||||||
cassette = deserialize(cassette_content, serializer)
|
|
||||||
return cassette
|
|
||||||
|
|
||||||
|
|
||||||
def save_cassette(cassette_path, cassette_dict, serializer):
|
|
||||||
data = serialize(cassette_dict, serializer)
|
|
||||||
FilesystemPersister.write(cassette_path, data)
|
|
||||||
@@ -1,11 +1,25 @@
|
|||||||
|
# .. _persister_example:
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from ..serialize import serialize, deserialize
|
||||||
|
|
||||||
|
|
||||||
class FilesystemPersister(object):
|
class FilesystemPersister(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def write(cls, cassette_path, data):
|
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.")
|
||||||
|
cassette = deserialize(cassette_content, serializer)
|
||||||
|
return cassette
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save_cassette(cassette_path, cassette_dict, serializer):
|
||||||
|
data = serialize(cassette_dict, serializer)
|
||||||
dirname, filename = os.path.split(cassette_path)
|
dirname, filename = os.path.split(cassette_path)
|
||||||
if dirname and not os.path.exists(dirname):
|
if dirname and not os.path.exists(dirname):
|
||||||
os.makedirs(dirname)
|
os.makedirs(dirname)
|
||||||
with open(cassette_path, 'w') as f:
|
with open(cassette_path, "w") as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import warnings
|
|||||||
from six import BytesIO, text_type
|
from six import BytesIO, text_type
|
||||||
from six.moves.urllib.parse import urlparse, parse_qsl
|
from six.moves.urllib.parse import urlparse, parse_qsl
|
||||||
from .util import CaseInsensitiveDict
|
from .util import CaseInsensitiveDict
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Request(object):
|
class Request(object):
|
||||||
@@ -12,12 +15,13 @@ class Request(object):
|
|||||||
def __init__(self, method, uri, body, headers):
|
def __init__(self, method, uri, body, headers):
|
||||||
self.method = method
|
self.method = method
|
||||||
self.uri = uri
|
self.uri = uri
|
||||||
self._was_file = hasattr(body, 'read')
|
self._was_file = hasattr(body, "read")
|
||||||
if self._was_file:
|
if self._was_file:
|
||||||
self.body = body.read()
|
self.body = body.read()
|
||||||
else:
|
else:
|
||||||
self.body = body
|
self.body = body
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
|
log.debug("Invoking Request %s", self.uri)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def headers(self):
|
def headers(self):
|
||||||
@@ -36,13 +40,14 @@ class Request(object):
|
|||||||
@body.setter
|
@body.setter
|
||||||
def body(self, value):
|
def body(self, value):
|
||||||
if isinstance(value, text_type):
|
if isinstance(value, text_type):
|
||||||
value = value.encode('utf-8')
|
value = value.encode("utf-8")
|
||||||
self._body = value
|
self._body = value
|
||||||
|
|
||||||
def add_header(self, key, value):
|
def add_header(self, key, value):
|
||||||
warnings.warn("Request.add_header is deprecated. "
|
warnings.warn(
|
||||||
"Please assign to request.headers instead.",
|
"Request.add_header is deprecated. " "Please assign to request.headers instead.",
|
||||||
DeprecationWarning)
|
DeprecationWarning,
|
||||||
|
)
|
||||||
self.headers[key] = value
|
self.headers[key] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -58,7 +63,10 @@ class Request(object):
|
|||||||
parse_uri = urlparse(self.uri)
|
parse_uri = urlparse(self.uri)
|
||||||
port = parse_uri.port
|
port = parse_uri.port
|
||||||
if port is None:
|
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
|
return port
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -81,17 +89,17 @@ class Request(object):
|
|||||||
return self.scheme
|
return self.scheme
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<Request ({0}) {1}>".format(self.method, self.uri)
|
return "<Request ({}) {}>".format(self.method, self.uri)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
def _to_dict(self):
|
def _to_dict(self):
|
||||||
return {
|
return {
|
||||||
'method': self.method,
|
"method": self.method,
|
||||||
'uri': self.uri,
|
"uri": self.uri,
|
||||||
'body': self.body,
|
"body": self.body,
|
||||||
'headers': dict(((k, [v]) for k, v in self.headers.items())),
|
"headers": {k: [v] for k, v in self.headers.items()},
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -112,7 +120,7 @@ class HeadersDict(CaseInsensitiveDict):
|
|||||||
In addition, some servers sometimes send the same header more than once,
|
In addition, some servers sometimes send the same header more than once,
|
||||||
and httplib *can* deal with this situation.
|
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.
|
similar as possible.
|
||||||
|
|
||||||
For this reason, in cassettes I keep a dict with lists as keys, but once
|
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):
|
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():
|
def _warn_about_old_cassette_format():
|
||||||
@@ -41,23 +41,18 @@ def deserialize(cassette_string, serializer):
|
|||||||
if _looks_like_an_old_cassette(data):
|
if _looks_like_an_old_cassette(data):
|
||||||
_warn_about_old_cassette_format()
|
_warn_about_old_cassette_format()
|
||||||
|
|
||||||
requests = [Request._from_dict(r['request']) for r in data['interactions']]
|
requests = [Request._from_dict(r["request"]) for r in data["interactions"]]
|
||||||
responses = [
|
responses = [compat.convert_to_bytes(r["response"]) for r in data["interactions"]]
|
||||||
compat.convert_to_bytes(r['response']) for r in data['interactions']
|
|
||||||
]
|
|
||||||
return requests, responses
|
return requests, responses
|
||||||
|
|
||||||
|
|
||||||
def serialize(cassette_dict, serializer):
|
def serialize(cassette_dict, serializer):
|
||||||
interactions = ([{
|
interactions = [
|
||||||
'request': compat.convert_to_unicode(request._to_dict()),
|
{
|
||||||
'response': compat.convert_to_unicode(response),
|
"request": compat.convert_to_unicode(request._to_dict()),
|
||||||
} for request, response in zip(
|
"response": compat.convert_to_unicode(response),
|
||||||
cassette_dict['requests'],
|
}
|
||||||
cassette_dict['responses'],
|
for request, response in zip(cassette_dict["requests"], cassette_dict["responses"])
|
||||||
)])
|
]
|
||||||
data = {
|
data = {"version": CASSETTE_FORMAT_VERSION, "interactions": interactions}
|
||||||
'version': CASSETTE_FORMAT_VERSION,
|
|
||||||
'interactions': interactions,
|
|
||||||
}
|
|
||||||
return serializer.serialize(data)
|
return serializer.serialize(data)
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ def convert_body_to_bytes(resp):
|
|||||||
http://pyyaml.org/wiki/PyYAMLDocumentation#Python3support
|
http://pyyaml.org/wiki/PyYAMLDocumentation#Python3support
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if not isinstance(resp['body']['string'], six.binary_type):
|
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')
|
resp["body"]["string"] = resp["body"]["string"].encode("utf-8")
|
||||||
except (KeyError, TypeError, UnicodeEncodeError):
|
except (KeyError, TypeError, UnicodeEncodeError):
|
||||||
# The thing we were converting either wasn't a dictionary or didn't
|
# 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
|
# have the keys we were expecting. Some of the tests just serialize
|
||||||
@@ -45,7 +45,7 @@ def _convert_string_to_unicode(string):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if string is not None and not isinstance(string, six.text_type):
|
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):
|
except (TypeError, UnicodeDecodeError, AttributeError):
|
||||||
# Sometimes the string actually is binary or StringIO object,
|
# Sometimes the string actually is binary or StringIO object,
|
||||||
# so if you can't decode it, just give up.
|
# 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.
|
# Some of the tests just serialize and deserialize a string.
|
||||||
return _convert_string_to_unicode(resp)
|
return _convert_string_to_unicode(resp)
|
||||||
else:
|
else:
|
||||||
body = resp.get('body')
|
body = resp.get("body")
|
||||||
|
|
||||||
if body is not None:
|
if body is not None:
|
||||||
try:
|
try:
|
||||||
body['string'] = _convert_string_to_unicode(
|
body["string"] = _convert_string_to_unicode(body["string"])
|
||||||
body['string']
|
|
||||||
)
|
|
||||||
except (KeyError, TypeError, AttributeError):
|
except (KeyError, TypeError, AttributeError):
|
||||||
# The thing we were converting either wasn't a dictionary or
|
# The thing we were converting either wasn't a dictionary or
|
||||||
# didn't have the keys we were expecting.
|
# didn't have the keys we were expecting.
|
||||||
# For example request object has no 'string' key.
|
# For example request object has no 'string' key.
|
||||||
resp['body'] = _convert_string_to_unicode(body)
|
resp["body"] = _convert_string_to_unicode(body)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def serialize(cassette_dict):
|
|||||||
b"Error serializing cassette to JSON",
|
b"Error serializing cassette to JSON",
|
||||||
original.start,
|
original.start,
|
||||||
original.end,
|
original.end,
|
||||||
original.args[-1] + error_message
|
original.args[-1] + error_message,
|
||||||
)
|
)
|
||||||
except TypeError as original: # py3
|
except TypeError: # py3
|
||||||
raise TypeError(error_message)
|
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 logging
|
||||||
import six
|
import six
|
||||||
from six.moves.http_client import (
|
from six.moves.http_client import HTTPConnection, HTTPSConnection, HTTPResponse
|
||||||
HTTPConnection,
|
|
||||||
HTTPSConnection,
|
|
||||||
HTTPResponse,
|
|
||||||
)
|
|
||||||
from six import BytesIO
|
from six import BytesIO
|
||||||
from vcr.request import Request
|
from vcr.request import Request
|
||||||
from vcr.errors import CannotOverwriteExistingCassetteException
|
from vcr.errors import CannotOverwriteExistingCassetteException
|
||||||
@@ -18,7 +14,7 @@ log = logging.getLogger(__name__)
|
|||||||
class VCRFakeSocket(object):
|
class VCRFakeSocket(object):
|
||||||
"""
|
"""
|
||||||
A socket that doesn't do anything!
|
A socket that doesn't do anything!
|
||||||
Used when playing back casssettes, when there
|
Used when playing back cassettes, when there
|
||||||
is no actual open socket.
|
is no actual open socket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -45,8 +41,7 @@ def parse_headers(header_list):
|
|||||||
header_string = b""
|
header_string = b""
|
||||||
for key, values in header_list.items():
|
for key, values in header_list.items():
|
||||||
for v in values:
|
for v in values:
|
||||||
header_string += \
|
header_string += key.encode("utf-8") + b":" + v.encode("utf-8") + b"\r\n"
|
||||||
key.encode('utf-8') + b":" + v.encode('utf-8') + b"\r\n"
|
|
||||||
return compat.get_httpmessage(header_string)
|
return compat.get_httpmessage(header_string)
|
||||||
|
|
||||||
|
|
||||||
@@ -60,28 +55,30 @@ def serialize_headers(response):
|
|||||||
|
|
||||||
class VCRHTTPResponse(HTTPResponse):
|
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):
|
def __init__(self, recorded_response):
|
||||||
|
self.fp = None
|
||||||
self.recorded_response = recorded_response
|
self.recorded_response = recorded_response
|
||||||
self.reason = recorded_response['status']['message']
|
self.reason = recorded_response["status"]["message"]
|
||||||
self.status = self.code = recorded_response['status']['code']
|
self.status = self.code = recorded_response["status"]["code"]
|
||||||
self.version = None
|
self.version = None
|
||||||
self._content = BytesIO(self.recorded_response['body']['string'])
|
self._content = BytesIO(self.recorded_response["body"]["string"])
|
||||||
self._closed = False
|
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
|
# Since we are loading a response that has already been serialized, our
|
||||||
# response is no longer chunked. That means we don't want any
|
# response is no longer chunked. That means we don't want any
|
||||||
# libraries trying to process a chunked response. By removing the
|
# libraries trying to process a chunked response. By removing the
|
||||||
# transfer-encoding: chunked header, this should cause the downstream
|
# transfer-encoding: chunked header, this should cause the downstream
|
||||||
# libraries to process this as a non-chunked response.
|
# 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:
|
if te_key:
|
||||||
del headers[te_key[0]]
|
del headers[te_key[0]]
|
||||||
self.headers = self.msg = parse_headers(headers)
|
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
|
@property
|
||||||
def closed(self):
|
def closed(self):
|
||||||
@@ -93,9 +90,30 @@ class VCRHTTPResponse(HTTPResponse):
|
|||||||
def read(self, *args, **kwargs):
|
def read(self, *args, **kwargs):
|
||||||
return self._content.read(*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):
|
def readline(self, *args, **kwargs):
|
||||||
return self._content.readline(*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):
|
def close(self):
|
||||||
self._closed = True
|
self._closed = True
|
||||||
return True
|
return True
|
||||||
@@ -107,20 +125,23 @@ class VCRHTTPResponse(HTTPResponse):
|
|||||||
return self.closed
|
return self.closed
|
||||||
|
|
||||||
def info(self):
|
def info(self):
|
||||||
return parse_headers(self.recorded_response['headers'])
|
return parse_headers(self.recorded_response["headers"])
|
||||||
|
|
||||||
def getheaders(self):
|
def getheaders(self):
|
||||||
message = parse_headers(self.recorded_response['headers'])
|
message = parse_headers(self.recorded_response["headers"])
|
||||||
return list(compat.get_header_items(message))
|
return list(compat.get_header_items(message))
|
||||||
|
|
||||||
def getheader(self, header, default=None):
|
def getheader(self, header, default=None):
|
||||||
values = [v for (k, v) in self.getheaders() if k.lower() == header.lower()]
|
values = [v for (k, v) in self.getheaders() if k.lower() == header.lower()]
|
||||||
|
|
||||||
if values:
|
if values:
|
||||||
return ', '.join(values)
|
return ", ".join(values)
|
||||||
else:
|
else:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
return self._content.readable()
|
||||||
|
|
||||||
|
|
||||||
class VCRConnection(object):
|
class VCRConnection(object):
|
||||||
# A reference to the cassette that's currently being patched in
|
# A reference to the cassette that's currently being patched in
|
||||||
@@ -131,68 +152,54 @@ class VCRConnection(object):
|
|||||||
Returns empty string for the default port and ':port' otherwise
|
Returns empty string for the default port and ':port' otherwise
|
||||||
"""
|
"""
|
||||||
port = self.real_connection.port
|
port = self.real_connection.port
|
||||||
default_port = {'https': 443, 'http': 80}[self._protocol]
|
default_port = {"https": 443, "http": 80}[self._protocol]
|
||||||
return ':{0}'.format(port) if port != default_port else ''
|
return ":{}".format(port) if port != default_port else ""
|
||||||
|
|
||||||
def _uri(self, url):
|
def _uri(self, url):
|
||||||
"""Returns request absolute URI"""
|
"""Returns request absolute URI"""
|
||||||
uri = "{0}://{1}{2}{3}".format(
|
if url and not url.startswith("/"):
|
||||||
self._protocol,
|
# Then this must be a proxy request.
|
||||||
self.real_connection.host,
|
return url
|
||||||
self._port_postfix(),
|
uri = "{}://{}{}{}".format(self._protocol, self.real_connection.host, self._port_postfix(), url)
|
||||||
url,
|
log.debug("Absolute URI: %s", uri)
|
||||||
)
|
|
||||||
return uri
|
return uri
|
||||||
|
|
||||||
def _url(self, uri):
|
def _url(self, uri):
|
||||||
"""Returns request selector url from absolute URI"""
|
"""Returns request selector url from absolute URI"""
|
||||||
prefix = "{0}://{1}{2}".format(
|
prefix = "{}://{}{}".format(self._protocol, self.real_connection.host, self._port_postfix())
|
||||||
self._protocol,
|
return uri.replace(prefix, "", 1)
|
||||||
self.real_connection.host,
|
|
||||||
self._port_postfix(),
|
|
||||||
)
|
|
||||||
return uri.replace(prefix, '', 1)
|
|
||||||
|
|
||||||
def request(self, method, url, body=None, headers=None):
|
def request(self, method, url, body=None, headers=None, *args, **kwargs):
|
||||||
'''Persist the request metadata in self._vcr_request'''
|
"""Persist the request metadata in self._vcr_request"""
|
||||||
self._vcr_request = Request(
|
self._vcr_request = Request(method=method, uri=self._uri(url), body=body, headers=headers or {})
|
||||||
method=method,
|
log.debug("Got {}".format(self._vcr_request))
|
||||||
uri=self._uri(url),
|
|
||||||
body=body,
|
|
||||||
headers=headers or {}
|
|
||||||
)
|
|
||||||
log.debug('Got {0}'.format(self._vcr_request))
|
|
||||||
|
|
||||||
# Note: The request may not actually be finished at this point, so
|
# Note: The request may not actually be finished at this point, so
|
||||||
# I'm not sending the actual request until getresponse(). This
|
# I'm not sending the actual request until getresponse(). This
|
||||||
# allows me to compare the entire length of the response to see if it
|
# allows me to compare the entire length of the response to see if it
|
||||||
# exists in the cassette.
|
# exists in the cassette.
|
||||||
|
|
||||||
|
self._sock = VCRFakeSocket()
|
||||||
|
|
||||||
def putrequest(self, method, url, *args, **kwargs):
|
def putrequest(self, method, url, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
httplib gives you more than one way to do it. This is a way
|
httplib gives you more than one way to do it. This is a way
|
||||||
to start building up a request. Usually followed by a bunch
|
to start building up a request. Usually followed by a bunch
|
||||||
of putheader() calls.
|
of putheader() calls.
|
||||||
"""
|
"""
|
||||||
self._vcr_request = Request(
|
self._vcr_request = Request(method=method, uri=self._uri(url), body="", headers={})
|
||||||
method=method,
|
log.debug("Got {}".format(self._vcr_request))
|
||||||
uri=self._uri(url),
|
|
||||||
body="",
|
|
||||||
headers={}
|
|
||||||
)
|
|
||||||
log.debug('Got {0}'.format(self._vcr_request))
|
|
||||||
|
|
||||||
def putheader(self, header, *values):
|
def putheader(self, header, *values):
|
||||||
self._vcr_request.headers[header] = values
|
self._vcr_request.headers[header] = values
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
'''
|
"""
|
||||||
This method is called after request(), to add additional data to the
|
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
|
body of the request. So if that happens, let's just append the data
|
||||||
onto the most recent request in the cassette.
|
onto the most recent request in the cassette.
|
||||||
'''
|
"""
|
||||||
self._vcr_request.body = self._vcr_request.body + data \
|
self._vcr_request.body = self._vcr_request.body + data if self._vcr_request.body else data
|
||||||
if self._vcr_request.body else data
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
# Note: the real connection will only close if it's open, so
|
# Note: the real connection will only close if it's open, so
|
||||||
@@ -209,40 +216,27 @@ class VCRConnection(object):
|
|||||||
self._vcr_request.body = message_body
|
self._vcr_request.body = message_body
|
||||||
|
|
||||||
def getresponse(self, _=False, **kwargs):
|
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,
|
# Check to see if the cassette has a response for this request. If so,
|
||||||
# then return it
|
# then return it
|
||||||
if self.cassette.can_play_response_for(self._vcr_request):
|
if self.cassette.can_play_response_for(self._vcr_request):
|
||||||
log.info(
|
log.info("Playing response for {} from cassette".format(self._vcr_request))
|
||||||
"Playing response for {0} from cassette".format(
|
|
||||||
self._vcr_request
|
|
||||||
)
|
|
||||||
)
|
|
||||||
response = self.cassette.play_response(self._vcr_request)
|
response = self.cassette.play_response(self._vcr_request)
|
||||||
return VCRHTTPResponse(response)
|
return VCRHTTPResponse(response)
|
||||||
else:
|
else:
|
||||||
if self.cassette.write_protected and self.cassette.filter_request(
|
if self.cassette.write_protected and self.cassette.filter_request(self._vcr_request):
|
||||||
self._vcr_request
|
|
||||||
):
|
|
||||||
raise CannotOverwriteExistingCassetteException(
|
raise CannotOverwriteExistingCassetteException(
|
||||||
"No match for the request (%r) was found. "
|
cassette=self.cassette, failed_request=self._vcr_request
|
||||||
"Can't overwrite existing cassette (%r) in "
|
|
||||||
"your current record mode (%r)."
|
|
||||||
% (self._vcr_request, self.cassette._path,
|
|
||||||
self.cassette.record_mode)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Otherwise, we should send the request, then get the response
|
# Otherwise, we should send the request, then get the response
|
||||||
# and return it.
|
# and return it.
|
||||||
|
|
||||||
log.info(
|
log.info("{} not in cassette, sending to real server".format(self._vcr_request))
|
||||||
"{0} not in cassette, sending to real server".format(
|
|
||||||
self._vcr_request
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# This is imported here to avoid circular import.
|
# This is imported here to avoid circular import.
|
||||||
# TODO(@IvanMalison): Refactor to allow normal import.
|
# TODO(@IvanMalison): Refactor to allow normal import.
|
||||||
from vcr.patch import force_reset
|
from vcr.patch import force_reset
|
||||||
|
|
||||||
with force_reset():
|
with force_reset():
|
||||||
self.real_connection.request(
|
self.real_connection.request(
|
||||||
method=self._vcr_request.method,
|
method=self._vcr_request.method,
|
||||||
@@ -256,12 +250,9 @@ class VCRConnection(object):
|
|||||||
|
|
||||||
# put the response into the cassette
|
# put the response into the cassette
|
||||||
response = {
|
response = {
|
||||||
'status': {
|
"status": {"code": response.status, "message": response.reason},
|
||||||
'code': response.status,
|
"headers": serialize_headers(response),
|
||||||
'message': response.reason
|
"body": {"string": response.read()},
|
||||||
},
|
|
||||||
'headers': serialize_headers(response),
|
|
||||||
'body': {'string': response.read()},
|
|
||||||
}
|
}
|
||||||
self.cassette.append(self._vcr_request, response)
|
self.cassette.append(self._vcr_request, response)
|
||||||
return VCRHTTPResponse(response)
|
return VCRHTTPResponse(response)
|
||||||
@@ -277,8 +268,7 @@ class VCRConnection(object):
|
|||||||
and are not write-protected.
|
and are not write-protected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if hasattr(self, '_vcr_request') and \
|
if hasattr(self, "_vcr_request") and self.cassette.can_play_response_for(self._vcr_request):
|
||||||
self.cassette.can_play_response_for(self._vcr_request):
|
|
||||||
# We already have a response we are going to play, don't
|
# We already have a response we are going to play, don't
|
||||||
# actually connect
|
# actually connect
|
||||||
return
|
return
|
||||||
@@ -287,13 +277,18 @@ class VCRConnection(object):
|
|||||||
# Cassette is write-protected, don't actually connect
|
# Cassette is write-protected, don't actually connect
|
||||||
return
|
return
|
||||||
|
|
||||||
return self.real_connection.connect(*args, **kwargs)
|
from vcr.patch import force_reset
|
||||||
|
|
||||||
|
with force_reset():
|
||||||
|
return self.real_connection.connect(*args, **kwargs)
|
||||||
|
|
||||||
|
self._sock = VCRFakeSocket()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sock(self):
|
def sock(self):
|
||||||
if self.real_connection.sock:
|
if self.real_connection.sock:
|
||||||
return self.real_connection.sock
|
return self.real_connection.sock
|
||||||
return VCRFakeSocket()
|
return self._sock
|
||||||
|
|
||||||
@sock.setter
|
@sock.setter
|
||||||
def sock(self, value):
|
def sock(self, value):
|
||||||
@@ -302,15 +297,18 @@ class VCRConnection(object):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if six.PY3:
|
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
|
# need to temporarily reset here because the real connection
|
||||||
# inherits from the thing that we are mocking out. Take out
|
# inherits from the thing that we are mocking out. Take out
|
||||||
# the reset if you want to see what I mean :)
|
# the reset if you want to see what I mean :)
|
||||||
from vcr.patch import force_reset
|
from vcr.patch import force_reset
|
||||||
|
|
||||||
with force_reset():
|
with force_reset():
|
||||||
self.real_connection = self._baseclass(*args, **kwargs)
|
self.real_connection = self._baseclass(*args, **kwargs)
|
||||||
|
|
||||||
|
self._sock = None
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
"""
|
"""
|
||||||
We need to define this because any attributes that are set on the
|
We need to define this because any attributes that are set on the
|
||||||
@@ -332,15 +330,34 @@ class VCRConnection(object):
|
|||||||
|
|
||||||
super(VCRConnection, self).__setattr__(name, value)
|
super(VCRConnection, self).__setattr__(name, value)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
"""
|
||||||
|
Send requests for weird attributes up to the real connection
|
||||||
|
(counterpart to __setattr above)
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
return super(VCRConnection, self).__getattr__(name)
|
||||||
|
|
||||||
|
|
||||||
|
for k, v in HTTPConnection.__dict__.items():
|
||||||
|
if isinstance(v, staticmethod):
|
||||||
|
setattr(VCRConnection, k, v)
|
||||||
|
|
||||||
|
|
||||||
class VCRHTTPConnection(VCRConnection):
|
class VCRHTTPConnection(VCRConnection):
|
||||||
'''A Mocked class for HTTP requests'''
|
"""A Mocked class for HTTP requests"""
|
||||||
|
|
||||||
_baseclass = HTTPConnection
|
_baseclass = HTTPConnection
|
||||||
_protocol = 'http'
|
_protocol = "http"
|
||||||
|
|
||||||
|
|
||||||
class VCRHTTPSConnection(VCRConnection):
|
class VCRHTTPSConnection(VCRConnection):
|
||||||
'''A Mocked class for HTTPS requests'''
|
"""A Mocked class for HTTPS requests"""
|
||||||
|
|
||||||
_baseclass = HTTPSConnection
|
_baseclass = HTTPSConnection
|
||||||
_protocol = 'https'
|
_protocol = "https"
|
||||||
is_verified = True
|
is_verified = True
|
||||||
|
|||||||
146
vcr/stubs/aiohttp_stubs/__init__.py
Normal file
146
vcr/stubs/aiohttp_stubs/__init__.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
"""Stubs for aiohttp HTTP clients"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
|
||||||
|
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):
|
||||||
|
super().__init__(
|
||||||
|
method=method,
|
||||||
|
url=url,
|
||||||
|
writer=None,
|
||||||
|
continue100=None,
|
||||||
|
timer=None,
|
||||||
|
request_info=None,
|
||||||
|
traces=None,
|
||||||
|
loop=asyncio.get_event_loop(),
|
||||||
|
session=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def json(self, *, encoding="utf-8", loads=json.loads, **kwargs): # NOQA: E999
|
||||||
|
stripped = self._body.strip()
|
||||||
|
if not stripped:
|
||||||
|
return None
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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")
|
||||||
|
auth = kwargs.get("auth")
|
||||||
|
headers = self._prepare_headers(headers)
|
||||||
|
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:
|
||||||
|
for k, v in params.items():
|
||||||
|
params[k] = str(v)
|
||||||
|
request_url = URL(url).with_query(params)
|
||||||
|
|
||||||
|
vcr_request = Request(method, str(request_url), data, headers)
|
||||||
|
|
||||||
|
if cassette.can_play_response_for(vcr_request):
|
||||||
|
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 = 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
|
||||||
|
await record_responses(cassette, vcr_request, response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
return new_request
|
||||||
44
vcr/stubs/boto3_stubs.py
Normal file
44
vcr/stubs/boto3_stubs.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"""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 ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||||
|
|
||||||
|
|
||||||
|
class VCRRequestsHTTPConnection(VCRHTTPConnection, HTTPConnection):
|
||||||
|
_baseclass = 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 boto.https_connection import CertValidatingHTTPSConnection
|
||||||
from ..stubs import VCRHTTPSConnection
|
from ..stubs import VCRHTTPSConnection
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import six
|
import six
|
||||||
from six import BytesIO
|
from six import BytesIO
|
||||||
from six.moves.http_client import HTTPMessage
|
from six.moves.http_client import HTTPMessage
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import http.client
|
import http.client
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@@ -1,63 +1,60 @@
|
|||||||
'''Stubs for httplib2'''
|
"""Stubs for httplib2"""
|
||||||
|
|
||||||
from httplib2 import HTTPConnectionWithTimeout, HTTPSConnectionWithTimeout
|
from httplib2 import HTTPConnectionWithTimeout, HTTPSConnectionWithTimeout
|
||||||
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||||
|
|
||||||
|
|
||||||
class VCRHTTPConnectionWithTimeout(VCRHTTPConnection,
|
class VCRHTTPConnectionWithTimeout(VCRHTTPConnection, HTTPConnectionWithTimeout):
|
||||||
HTTPConnectionWithTimeout):
|
|
||||||
_baseclass = HTTPConnectionWithTimeout
|
_baseclass = HTTPConnectionWithTimeout
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
'''I overrode the init because I need to clean kwargs before calling
|
"""I overrode the init because I need to clean kwargs before calling
|
||||||
HTTPConnection.__init__.'''
|
HTTPConnection.__init__."""
|
||||||
|
|
||||||
# Delete the keyword arguments that HTTPConnection would not recognize
|
# Delete the keyword arguments that HTTPConnection would not recognize
|
||||||
safe_keys = set(
|
safe_keys = {"host", "port", "strict", "timeout", "source_address"}
|
||||||
('host', 'port', 'strict', 'timeout', 'source_address')
|
|
||||||
)
|
|
||||||
unknown_keys = set(kwargs.keys()) - safe_keys
|
unknown_keys = set(kwargs.keys()) - safe_keys
|
||||||
safe_kwargs = kwargs.copy()
|
safe_kwargs = kwargs.copy()
|
||||||
for kw in unknown_keys:
|
for kw in unknown_keys:
|
||||||
del safe_kwargs[kw]
|
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)
|
VCRHTTPConnection.__init__(self, *args, **safe_kwargs)
|
||||||
self.sock = self.real_connection.sock
|
self.sock = self.real_connection.sock
|
||||||
|
|
||||||
|
|
||||||
class VCRHTTPSConnectionWithTimeout(VCRHTTPSConnection,
|
class VCRHTTPSConnectionWithTimeout(VCRHTTPSConnection, HTTPSConnectionWithTimeout):
|
||||||
HTTPSConnectionWithTimeout):
|
|
||||||
_baseclass = HTTPSConnectionWithTimeout
|
_baseclass = HTTPSConnectionWithTimeout
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
# Delete the keyword arguments that HTTPSConnection would not recognize
|
# Delete the keyword arguments that HTTPSConnection would not recognize
|
||||||
safe_keys = set((
|
safe_keys = {
|
||||||
'host',
|
"host",
|
||||||
'port',
|
"port",
|
||||||
'key_file',
|
"key_file",
|
||||||
'cert_file',
|
"cert_file",
|
||||||
'strict',
|
"strict",
|
||||||
'timeout',
|
"timeout",
|
||||||
'source_address',
|
"source_address",
|
||||||
'ca_certs',
|
"ca_certs",
|
||||||
))
|
"disable_ssl_certificate_validation",
|
||||||
|
}
|
||||||
unknown_keys = set(kwargs.keys()) - safe_keys
|
unknown_keys = set(kwargs.keys()) - safe_keys
|
||||||
safe_kwargs = kwargs.copy()
|
safe_kwargs = kwargs.copy()
|
||||||
for kw in unknown_keys:
|
for kw in unknown_keys:
|
||||||
del safe_kwargs[kw]
|
del safe_kwargs[kw]
|
||||||
self.proxy_info = kwargs.pop('proxy_info', None)
|
self.proxy_info = kwargs.pop("proxy_info", None)
|
||||||
if 'ca_certs' not in kwargs or kwargs['ca_certs'] is None:
|
if "ca_certs" not in kwargs or kwargs["ca_certs"] is None:
|
||||||
try:
|
try:
|
||||||
import httplib2
|
import httplib2
|
||||||
|
|
||||||
self.ca_certs = httplib2.CA_CERTS
|
self.ca_certs = httplib2.CA_CERTS
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.ca_certs = None
|
self.ca_certs = None
|
||||||
else:
|
else:
|
||||||
self.ca_certs = kwargs['ca_certs']
|
self.ca_certs = kwargs["ca_certs"]
|
||||||
|
|
||||||
self.disable_ssl_certificate_validation = kwargs.pop(
|
self.disable_ssl_certificate_validation = kwargs.pop("disable_ssl_certificate_validation", None)
|
||||||
'disable_ssl_certificate_validation', None)
|
|
||||||
VCRHTTPSConnection.__init__(self, *args, **safe_kwargs)
|
VCRHTTPSConnection.__init__(self, *args, **safe_kwargs)
|
||||||
self.sock = self.real_connection.sock
|
self.sock = self.real_connection.sock
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'''Stubs for requests'''
|
"""Stubs for requests"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from requests.packages.urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
|
||||||
except ImportError:
|
|
||||||
from urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
from urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
||||||
|
except ImportError:
|
||||||
|
from requests.packages.urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
||||||
|
|
||||||
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
'''Stubs for tornado HTTP clients'''
|
"""Stubs for tornado HTTP clients"""
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
@@ -12,20 +12,19 @@ from vcr.request import Request
|
|||||||
|
|
||||||
|
|
||||||
def vcr_fetch_impl(cassette, real_fetch_impl):
|
def vcr_fetch_impl(cassette, real_fetch_impl):
|
||||||
|
|
||||||
@functools.wraps(real_fetch_impl)
|
@functools.wraps(real_fetch_impl)
|
||||||
def new_fetch_impl(self, request, callback):
|
def new_fetch_impl(self, request, callback):
|
||||||
headers = request.headers.copy()
|
headers = request.headers.copy()
|
||||||
if request.user_agent:
|
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
|
# TODO body_producer, header_callback, and streaming_callback are not
|
||||||
# yet supported.
|
# yet supported.
|
||||||
|
|
||||||
unsupported_call = (
|
unsupported_call = (
|
||||||
getattr(request, 'body_producer', None) is not None or
|
getattr(request, "body_producer", None) is not None
|
||||||
request.header_callback is not None or
|
or request.header_callback is not None
|
||||||
request.streaming_callback is not None
|
or request.streaming_callback is not None
|
||||||
)
|
)
|
||||||
if unsupported_call:
|
if unsupported_call:
|
||||||
response = HTTPResponse(
|
response = HTTPResponse(
|
||||||
@@ -40,18 +39,13 @@ def vcr_fetch_impl(cassette, real_fetch_impl):
|
|||||||
)
|
)
|
||||||
return callback(response)
|
return callback(response)
|
||||||
|
|
||||||
vcr_request = Request(
|
vcr_request = Request(request.method, request.url, request.body, headers)
|
||||||
request.method,
|
|
||||||
request.url,
|
|
||||||
request.body,
|
|
||||||
headers,
|
|
||||||
)
|
|
||||||
|
|
||||||
if cassette.can_play_response_for(vcr_request):
|
if cassette.can_play_response_for(vcr_request):
|
||||||
vcr_response = cassette.play_response(vcr_request)
|
vcr_response = cassette.play_response(vcr_request)
|
||||||
headers = httputil.HTTPHeaders()
|
headers = httputil.HTTPHeaders()
|
||||||
|
|
||||||
recorded_headers = vcr_response['headers']
|
recorded_headers = vcr_response["headers"]
|
||||||
if isinstance(recorded_headers, dict):
|
if isinstance(recorded_headers, dict):
|
||||||
recorded_headers = recorded_headers.items()
|
recorded_headers = recorded_headers.items()
|
||||||
for k, vs in recorded_headers:
|
for k, vs in recorded_headers:
|
||||||
@@ -59,45 +53,34 @@ def vcr_fetch_impl(cassette, real_fetch_impl):
|
|||||||
headers.add(k, v)
|
headers.add(k, v)
|
||||||
response = HTTPResponse(
|
response = HTTPResponse(
|
||||||
request,
|
request,
|
||||||
code=vcr_response['status']['code'],
|
code=vcr_response["status"]["code"],
|
||||||
reason=vcr_response['status']['message'],
|
reason=vcr_response["status"]["message"],
|
||||||
headers=headers,
|
headers=headers,
|
||||||
buffer=BytesIO(vcr_response['body']['string']),
|
buffer=BytesIO(vcr_response["body"]["string"]),
|
||||||
effective_url=vcr_response.get('url'),
|
effective_url=vcr_response.get("url"),
|
||||||
request_time=self.io_loop.time() - request.start_time,
|
request_time=self.io_loop.time() - request.start_time,
|
||||||
)
|
)
|
||||||
return callback(response)
|
return callback(response)
|
||||||
else:
|
else:
|
||||||
if cassette.write_protected and cassette.filter_request(
|
if cassette.write_protected and cassette.filter_request(vcr_request):
|
||||||
vcr_request
|
|
||||||
):
|
|
||||||
response = HTTPResponse(
|
response = HTTPResponse(
|
||||||
request,
|
request,
|
||||||
599,
|
599,
|
||||||
error=CannotOverwriteExistingCassetteException(
|
error=CannotOverwriteExistingCassetteException(
|
||||||
"No match for the request (%r) was found. "
|
cassette=cassette, failed_request=vcr_request
|
||||||
"Can't overwrite existing cassette (%r) in "
|
|
||||||
"your current record mode (%r)."
|
|
||||||
% (vcr_request, cassette._path, cassette.record_mode)
|
|
||||||
),
|
),
|
||||||
request_time=self.io_loop.time() - request.start_time,
|
request_time=self.io_loop.time() - request.start_time,
|
||||||
)
|
)
|
||||||
return callback(response)
|
return callback(response)
|
||||||
|
|
||||||
def new_callback(response):
|
def new_callback(response):
|
||||||
headers = [
|
headers = [(k, response.headers.get_list(k)) for k in response.headers.keys()]
|
||||||
(k, response.headers.get_list(k))
|
|
||||||
for k in response.headers.keys()
|
|
||||||
]
|
|
||||||
|
|
||||||
vcr_response = {
|
vcr_response = {
|
||||||
'status': {
|
"status": {"code": response.code, "message": response.reason},
|
||||||
'code': response.code,
|
"headers": headers,
|
||||||
'message': response.reason,
|
"body": {"string": response.body},
|
||||||
},
|
"url": response.effective_url,
|
||||||
'headers': headers,
|
|
||||||
'body': {'string': response.body},
|
|
||||||
'url': response.effective_url,
|
|
||||||
}
|
}
|
||||||
cassette.append(vcr_request, vcr_response)
|
cassette.append(vcr_request, vcr_response)
|
||||||
return callback(response)
|
return callback(response)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
'''Stubs for urllib3'''
|
"""Stubs for urllib3"""
|
||||||
|
|
||||||
from urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
from urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
||||||
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||||
|
|||||||
44
vcr/util.py
44
vcr/util.py
@@ -1,13 +1,17 @@
|
|||||||
import collections
|
|
||||||
import types
|
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
|
# 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.
|
A case-insensitive ``dict``-like object.
|
||||||
Implements all methods and operations of
|
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``.
|
provides ``lower_items``.
|
||||||
All keys are expected to be strings. The structure remembers the
|
All keys are expected to be strings. The structure remembers the
|
||||||
case of the last key to be set, and ``iter(instance)``,
|
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
|
operations are given keys that have equal ``.lower()``s, the
|
||||||
behavior is undefined.
|
behavior is undefined.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data=None, **kwargs):
|
def __init__(self, data=None, **kwargs):
|
||||||
self._store = dict()
|
self._store = dict()
|
||||||
if data is None:
|
if data is None:
|
||||||
@@ -50,14 +55,10 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
|||||||
|
|
||||||
def lower_items(self):
|
def lower_items(self):
|
||||||
"""Like iteritems(), but with all lowercase keys."""
|
"""Like iteritems(), but with all lowercase keys."""
|
||||||
return (
|
return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
|
||||||
(lowerkey, keyval[1])
|
|
||||||
for (lowerkey, keyval)
|
|
||||||
in self._store.items()
|
|
||||||
)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, collections.Mapping):
|
if isinstance(other, Mapping):
|
||||||
other = CaseInsensitiveDict(other)
|
other = CaseInsensitiveDict(other)
|
||||||
else:
|
else:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
@@ -88,37 +89,30 @@ def compose(*functions):
|
|||||||
if function:
|
if function:
|
||||||
res = function(res)
|
res = function(res)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
return composed
|
return composed
|
||||||
|
|
||||||
|
|
||||||
def read_body(request):
|
def read_body(request):
|
||||||
if hasattr(request.body, 'read'):
|
if hasattr(request.body, "read"):
|
||||||
return request.body.read()
|
return request.body.read()
|
||||||
return request.body
|
return request.body
|
||||||
|
|
||||||
|
|
||||||
def auto_decorate(
|
def auto_decorate(decorator, predicate=lambda name, value: isinstance(value, types.FunctionType)):
|
||||||
decorator,
|
|
||||||
predicate=lambda name, value: isinstance(value, types.FunctionType)
|
|
||||||
):
|
|
||||||
def maybe_decorate(attribute, value):
|
def maybe_decorate(attribute, value):
|
||||||
if predicate(attribute, value):
|
if predicate(attribute, value):
|
||||||
value = decorator(value)
|
value = decorator(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
class DecorateAll(type):
|
class DecorateAll(type):
|
||||||
|
|
||||||
def __setattr__(cls, attribute, value):
|
def __setattr__(cls, attribute, value):
|
||||||
return super(DecorateAll, cls).__setattr__(
|
return super(DecorateAll, cls).__setattr__(attribute, maybe_decorate(attribute, value))
|
||||||
attribute, maybe_decorate(attribute, value)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __new__(cls, name, bases, attributes_dict):
|
def __new__(cls, name, bases, attributes_dict):
|
||||||
new_attributes_dict = dict(
|
new_attributes_dict = {
|
||||||
(attribute, maybe_decorate(attribute, value))
|
attribute: maybe_decorate(attribute, value) for attribute, value in attributes_dict.items()
|
||||||
for attribute, value in attributes_dict.items()
|
}
|
||||||
)
|
return super(DecorateAll, cls).__new__(cls, name, bases, new_attributes_dict)
|
||||||
return super(DecorateAll, cls).__new__(
|
|
||||||
cls, name, bases, new_attributes_dict
|
|
||||||
)
|
|
||||||
return DecorateAll
|
return DecorateAll
|
||||||
|
|||||||
Reference in New Issue
Block a user