Import Upstream version 1.1.4
This commit is contained in:
commit
4ea5d66812
|
@ -0,0 +1,22 @@
|
||||||
|
Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,9 @@
|
||||||
|
include COPYING
|
||||||
|
include Makefile
|
||||||
|
include README.rst
|
||||||
|
include requirements.txt
|
||||||
|
include tests/functional/fixtures/playback-*.json
|
||||||
|
include tox.ini
|
||||||
|
recursive-include docs *.*
|
||||||
|
recursive-include tests *.py
|
||||||
|
include *.cfg *.rst *.txt
|
|
@ -0,0 +1,83 @@
|
||||||
|
.PHONY: tests all unit functional clean dependencies tdd docs html purge dist setup
|
||||||
|
|
||||||
|
GIT_ROOT := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||||
|
DOCS_ROOT := $(GIT_ROOT)/docs
|
||||||
|
HTML_ROOT := $(DOCS_ROOT)/build/html
|
||||||
|
VENV_ROOT := $(GIT_ROOT)/.venv
|
||||||
|
VENV ?= $(VENV_ROOT)
|
||||||
|
DOCS_INDEX := $(HTML_ROOT)/index.html
|
||||||
|
|
||||||
|
export VENV
|
||||||
|
export PYTHONASYNCIODEBUG :=1
|
||||||
|
|
||||||
|
|
||||||
|
all: dependencies tests
|
||||||
|
|
||||||
|
$(VENV): # creates $(VENV) folder if does not exist
|
||||||
|
python3 -mvenv $(VENV)
|
||||||
|
$(VENV)/bin/pip install -U pip setuptools
|
||||||
|
|
||||||
|
$(VENV)/bin/sphinx-build $(VENV)/bin/twine $(VENV)/bin/nosetests $(VENV)/bin/pytest $(VENV)/bin/python $(VENV)/bin/pip: # installs latest pip
|
||||||
|
test -e $(VENV)/bin/pip || make $(VENV)
|
||||||
|
$(MAKE) setup
|
||||||
|
|
||||||
|
setup: | $(VENV)/bin/pip
|
||||||
|
$(VENV)/bin/pip install -r development.txt
|
||||||
|
$(VENV)/bin/pip install -e .
|
||||||
|
|
||||||
|
# Runs the unit and functional tests
|
||||||
|
tests: unit bugfixes functional pyopenssl
|
||||||
|
|
||||||
|
|
||||||
|
tdd: $(VENV)/bin/nosetests # runs all tests
|
||||||
|
$(VENV)/bin/nosetests tests --with-watch --cover-erase
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
dependencies: | setup $(VENV)/bin/nosetests
|
||||||
|
|
||||||
|
# runs unit tests
|
||||||
|
unit: $(VENV)/bin/nosetests # runs only unit tests
|
||||||
|
$(VENV)/bin/nosetests --cover-erase tests/unit
|
||||||
|
|
||||||
|
|
||||||
|
pyopenssl: $(VENV)/bin/nosetests
|
||||||
|
$(VENV)/bin/nosetests --cover-erase tests/pyopenssl
|
||||||
|
|
||||||
|
bugfixes: $(VENV)/bin/nosetests $(VENV)/bin/pytest # runs tests for specific bugfixes
|
||||||
|
$(VENV)/bin/nosetests tests/bugfixes/nosetests
|
||||||
|
$(VENV)/bin/pytest --maxfail=1 --mypy tests/bugfixes/pytest
|
||||||
|
|
||||||
|
# runs functional tests
|
||||||
|
functional: $(VENV)/bin/nosetests # runs functional tests
|
||||||
|
$(VENV)/bin/nosetests tests/functional
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$(DOCS_INDEX): $(VENV)/bin/sphinx-build
|
||||||
|
cd docs && make html
|
||||||
|
|
||||||
|
html: $(DOCS_INDEX) $(VENV)/bin/sphinx-build
|
||||||
|
|
||||||
|
docs: $(DOCS_INDEX) $(VENV)/bin/sphinx-build
|
||||||
|
open $(DOCS_INDEX)
|
||||||
|
|
||||||
|
release: | clean tests html
|
||||||
|
@rm -rf dist/*
|
||||||
|
@./.release
|
||||||
|
@make pypi
|
||||||
|
|
||||||
|
dist: | clean
|
||||||
|
$(VENV)/bin/python setup.py build sdist
|
||||||
|
|
||||||
|
pypi: dist | $(VENV)/bin/twine
|
||||||
|
$(VENV)/bin/twine upload dist/*.tar.gz
|
||||||
|
|
||||||
|
# cleanup temp files
|
||||||
|
clean:
|
||||||
|
rm -rf $(HTML_ROOT) build dist
|
||||||
|
|
||||||
|
|
||||||
|
# purge all virtualenv and temp files, causes everything to be rebuilt
|
||||||
|
# from scratch by other tasks
|
||||||
|
purge: clean
|
||||||
|
rm -rf $(VENV)
|
|
@ -0,0 +1,190 @@
|
||||||
|
Metadata-Version: 1.2
|
||||||
|
Name: httpretty
|
||||||
|
Version: 1.1.4
|
||||||
|
Summary: HTTP client mock for Python
|
||||||
|
Home-page: https://httpretty.readthedocs.io/en/latest/
|
||||||
|
Author: Gabriel Falcao
|
||||||
|
Author-email: gabriel@nacaolivre.org
|
||||||
|
License: MIT
|
||||||
|
Project-URL: Documentation, https://httpretty.readthedocs.io/en/latest/
|
||||||
|
Project-URL: Source Code, https://github.com/gabrielfalcao/httpretty
|
||||||
|
Project-URL: Issue Tracker, https://github.com/gabrielfalcao/httpretty/issues
|
||||||
|
Project-URL: Continuous Integration, https://github.com/gabrielfalcao/HTTPretty/actions/workflows/pyenv.yml?query=branch%3Amaster+event%3Apush
|
||||||
|
Project-URL: Test Coverage, https://codecov.io/gh/gabrielfalcao/httpretty
|
||||||
|
Description: HTTPretty 1.1.4
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. image:: https://github.com/gabrielfalcao/HTTPretty/raw/master/docs/source/_static/logo.svg?sanitize=true
|
||||||
|
|
||||||
|
HTTP Client mocking tool for Python created by `Gabriel Falcão <https://github.com/gabrielfalcao>`_ . It provides a full fake TCP socket module. Inspired by `FakeWeb <https://github.com/chrisk/fakeweb>`_
|
||||||
|
|
||||||
|
|
||||||
|
- `Github Repository <https://github.com/gabrielfalcao/HTTPretty>`_
|
||||||
|
- `Documentation <https://httpretty.readthedocs.io/en/latest/>`_
|
||||||
|
- `PyPI Package <https://pypi.org/project/httpretty/>`_
|
||||||
|
|
||||||
|
|
||||||
|
**Python Support:**
|
||||||
|
|
||||||
|
- **3.6**
|
||||||
|
- **3.7**
|
||||||
|
- **3.8**
|
||||||
|
- **3.9**
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/dm/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/codecov/c/github/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://codecov.io/gh/gabrielfalcao/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/workflow/status/gabrielfalcao/HTTPretty/HTTPretty%20Tests?label=Python%203.6%20-%203.9
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/actions
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/readthedocs/httpretty
|
||||||
|
:target: https://httpretty.readthedocs.io/
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/license/gabrielfalcao/HTTPretty?label=Github%20License
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/blob/master/COPYING
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/l/HTTPretty?label=PyPi%20License
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/format/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/status/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/pyversions/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/implementation/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/snyk/vulnerabilities/github/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/network/alerts
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/v/tag/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/releases
|
||||||
|
|
||||||
|
.. |Join the chat at https://gitter.im/gabrielfalcao/HTTPretty| image:: https://badges.gitter.im/gabrielfalcao/HTTPretty.svg
|
||||||
|
:target: https://gitter.im/gabrielfalcao/HTTPretty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||||
|
|
||||||
|
Install
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
pip install httpretty
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Common Use Cases
|
||||||
|
================
|
||||||
|
|
||||||
|
- Test-driven development of API integrations
|
||||||
|
- Fake responses of external APIs
|
||||||
|
- Record and playback HTTP requests
|
||||||
|
|
||||||
|
|
||||||
|
Simple Example
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import sure
|
||||||
|
import httpretty
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_httpbin():
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
"https://httpbin.org/ip",
|
||||||
|
body='{"origin": "127.0.0.1"}'
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('https://httpbin.org/ip')
|
||||||
|
response.json().should.equal({'origin': '127.0.0.1'})
|
||||||
|
|
||||||
|
httpretty.latest_requests().should.have.length_of(1)
|
||||||
|
httpretty.last_request().should.equal(httpretty.latest_requests()[0])
|
||||||
|
httpretty.last_request().body.should.equal('{"origin": "127.0.0.1"}')
|
||||||
|
|
||||||
|
|
||||||
|
checking multiple responses
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_post_bodies():
|
||||||
|
url = 'http://httpbin.org/post'
|
||||||
|
httpretty.register_uri(httpretty.POST, url, status=200)
|
||||||
|
httpretty.register_uri(httpretty.POST, url, status=400)
|
||||||
|
requests.post(url, data={'foo': 'bar'})
|
||||||
|
requests.post(url, data={'zoo': 'zoo'})
|
||||||
|
assert 'foo=bar' in httpretty.latest_requests()[0].body
|
||||||
|
assert 'zoo=bar' in httpretty.latest_requests()[1].body
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<HTTPretty - HTTP client mock for Python>
|
||||||
|
Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
Main contributors
|
||||||
|
=================
|
||||||
|
|
||||||
|
HTTPretty has received `many contributions <https://github.com/gabrielfalcao/HTTPretty/graphs/contributors>`_
|
||||||
|
but some folks made remarkable contributions and deserve extra credit:
|
||||||
|
|
||||||
|
- Andrew Gross ~> `@andrewgross <https://github.com/andrewgross>`_
|
||||||
|
- Hugh Saunders ~> `@hughsaunders <https://github.com/hughsaunders>`_
|
||||||
|
- James Rowe ~> `@JNRowe <https://github.com/JNRowe>`_
|
||||||
|
- Matt Luongo ~> `@mhluongo <https://github.com/mhluongo>`_
|
||||||
|
- Steve Pulec ~> `@spulec <https://github.com/spulec>`_
|
||||||
|
- Miro Hrončok ~> `@hroncok <https://github.com/hroncok>`_
|
||||||
|
Mario Jonke ~> `@mariojonke <https://github.com/mariojonke>`_
|
||||||
|
|
||||||
|
Platform: UNKNOWN
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP
|
||||||
|
Classifier: Topic :: Software Development :: Testing
|
||||||
|
Requires-Python: >=3
|
|
@ -0,0 +1,163 @@
|
||||||
|
HTTPretty 1.1.4
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. image:: https://github.com/gabrielfalcao/HTTPretty/raw/master/docs/source/_static/logo.svg?sanitize=true
|
||||||
|
|
||||||
|
HTTP Client mocking tool for Python created by `Gabriel Falcão <https://github.com/gabrielfalcao>`_ . It provides a full fake TCP socket module. Inspired by `FakeWeb <https://github.com/chrisk/fakeweb>`_
|
||||||
|
|
||||||
|
|
||||||
|
- `Github Repository <https://github.com/gabrielfalcao/HTTPretty>`_
|
||||||
|
- `Documentation <https://httpretty.readthedocs.io/en/latest/>`_
|
||||||
|
- `PyPI Package <https://pypi.org/project/httpretty/>`_
|
||||||
|
|
||||||
|
|
||||||
|
**Python Support:**
|
||||||
|
|
||||||
|
- **3.6**
|
||||||
|
- **3.7**
|
||||||
|
- **3.8**
|
||||||
|
- **3.9**
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/dm/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/codecov/c/github/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://codecov.io/gh/gabrielfalcao/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/workflow/status/gabrielfalcao/HTTPretty/HTTPretty%20Tests?label=Python%203.6%20-%203.9
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/actions
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/readthedocs/httpretty
|
||||||
|
:target: https://httpretty.readthedocs.io/
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/license/gabrielfalcao/HTTPretty?label=Github%20License
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/blob/master/COPYING
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/l/HTTPretty?label=PyPi%20License
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/format/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/status/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/pyversions/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/implementation/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/snyk/vulnerabilities/github/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/network/alerts
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/v/tag/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/releases
|
||||||
|
|
||||||
|
.. |Join the chat at https://gitter.im/gabrielfalcao/HTTPretty| image:: https://badges.gitter.im/gabrielfalcao/HTTPretty.svg
|
||||||
|
:target: https://gitter.im/gabrielfalcao/HTTPretty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||||
|
|
||||||
|
Install
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
pip install httpretty
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Common Use Cases
|
||||||
|
================
|
||||||
|
|
||||||
|
- Test-driven development of API integrations
|
||||||
|
- Fake responses of external APIs
|
||||||
|
- Record and playback HTTP requests
|
||||||
|
|
||||||
|
|
||||||
|
Simple Example
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import sure
|
||||||
|
import httpretty
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_httpbin():
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
"https://httpbin.org/ip",
|
||||||
|
body='{"origin": "127.0.0.1"}'
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('https://httpbin.org/ip')
|
||||||
|
response.json().should.equal({'origin': '127.0.0.1'})
|
||||||
|
|
||||||
|
httpretty.latest_requests().should.have.length_of(1)
|
||||||
|
httpretty.last_request().should.equal(httpretty.latest_requests()[0])
|
||||||
|
httpretty.last_request().body.should.equal('{"origin": "127.0.0.1"}')
|
||||||
|
|
||||||
|
|
||||||
|
checking multiple responses
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_post_bodies():
|
||||||
|
url = 'http://httpbin.org/post'
|
||||||
|
httpretty.register_uri(httpretty.POST, url, status=200)
|
||||||
|
httpretty.register_uri(httpretty.POST, url, status=400)
|
||||||
|
requests.post(url, data={'foo': 'bar'})
|
||||||
|
requests.post(url, data={'zoo': 'zoo'})
|
||||||
|
assert 'foo=bar' in httpretty.latest_requests()[0].body
|
||||||
|
assert 'zoo=bar' in httpretty.latest_requests()[1].body
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<HTTPretty - HTTP client mock for Python>
|
||||||
|
Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
Main contributors
|
||||||
|
=================
|
||||||
|
|
||||||
|
HTTPretty has received `many contributions <https://github.com/gabrielfalcao/HTTPretty/graphs/contributors>`_
|
||||||
|
but some folks made remarkable contributions and deserve extra credit:
|
||||||
|
|
||||||
|
- Andrew Gross ~> `@andrewgross <https://github.com/andrewgross>`_
|
||||||
|
- Hugh Saunders ~> `@hughsaunders <https://github.com/hughsaunders>`_
|
||||||
|
- James Rowe ~> `@JNRowe <https://github.com/JNRowe>`_
|
||||||
|
- Matt Luongo ~> `@mhluongo <https://github.com/mhluongo>`_
|
||||||
|
- Steve Pulec ~> `@spulec <https://github.com/spulec>`_
|
||||||
|
- Miro Hrončok ~> `@hroncok <https://github.com/hroncok>`_
|
||||||
|
Mario Jonke ~> `@mariojonke <https://github.com/mariojonke>`_
|
|
@ -0,0 +1,31 @@
|
||||||
|
check-manifest==0.41
|
||||||
|
coverage>=5.0.3
|
||||||
|
cryptography>=2.8
|
||||||
|
eventlet==0.25.1 # issue #254
|
||||||
|
flake8>=3.7.9
|
||||||
|
freezegun>=0.3.15
|
||||||
|
httplib2>=0.17.0
|
||||||
|
httpx>=0.18.1
|
||||||
|
ipdb>=0.13.2
|
||||||
|
mccabe>=0.6.1
|
||||||
|
mock>=3.0.5;python_version<"3.3"
|
||||||
|
ndg-httpsclient>=0.5.1
|
||||||
|
nose-randomly>=1.2.6
|
||||||
|
nose>=1.3.7
|
||||||
|
pathlib2>=2.3.5
|
||||||
|
pyOpenSSL>=19.1.0
|
||||||
|
redis==3.4.1
|
||||||
|
rednose>=1.3.0
|
||||||
|
requests-toolbelt>=0.9.1
|
||||||
|
singledispatch>=3.4.0.3
|
||||||
|
sphinx-rtd-theme>=0.5.2
|
||||||
|
sphinx>=4.0.2
|
||||||
|
sure>=1.4.11
|
||||||
|
tornado>=6.0.4
|
||||||
|
tox>=3.14.5
|
||||||
|
twine>=1.15.0
|
||||||
|
urllib3>=1.25.8
|
||||||
|
boto3>=1.17.72
|
||||||
|
ndg-httpsclient>=0.5.1
|
||||||
|
pytest-mypy==0.8.1
|
||||||
|
sphinxcontrib.asciinema==0.3.2
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,36 @@
|
||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
|
if "%SPHINXBUILD%" == "" (
|
||||||
|
set SPHINXBUILD=sphinx-build
|
||||||
|
)
|
||||||
|
set SOURCEDIR=source
|
||||||
|
set BUILDDIR=build
|
||||||
|
set SPHINXPROJ=HTTPretty
|
||||||
|
|
||||||
|
if "%1" == "" goto help
|
||||||
|
|
||||||
|
%SPHINXBUILD% >NUL 2>NUL
|
||||||
|
if errorlevel 9009 (
|
||||||
|
echo.
|
||||||
|
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||||
|
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||||
|
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||||
|
echo.may add the Sphinx directory to PATH.
|
||||||
|
echo.
|
||||||
|
echo.If you don't have Sphinx installed, grab it from
|
||||||
|
echo.http://sphinx-doc.org/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:help
|
||||||
|
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
popd
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,20 @@
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from httpretty import httprettified, HTTPretty
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified(verbose=True, allow_net_connect=False)
|
||||||
|
def test_basic_body():
|
||||||
|
|
||||||
|
def my_callback(request, url, headers):
|
||||||
|
body = {}
|
||||||
|
import ipdb;ipdb.set_trace()
|
||||||
|
return (200, headers, json.dumps(body))
|
||||||
|
|
||||||
|
# Match any url via the regular expression
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, re.compile(r'.*'), body=my_callback)
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, re.compile(r'.*'), body=my_callback)
|
||||||
|
|
||||||
|
# will trigger ipdb
|
||||||
|
response = requests.post('https://test.com', data=json.dumps({'hello': 'world'}))
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="244px" height="258.19px" viewBox="0 0 244 258.19" enable-background="new 0 0 244 258.19" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" fill="#454546" d="M177.616,173.224c-2.802,0-6.968,0-12.002,0l11.372-11.372
|
||||||
|
c3.188-3.193,5.573-6.884,7.159-10.822c20.81-3.156,36.777-21.074,36.777-42.762c0-23.918-19.388-43.306-43.306-43.306
|
||||||
|
c-8.386,0-16.147,2.48-22.777,6.609c-0.75-3.024-1.94-5.847-3.468-8.47c-9.411-24.225-32.811-41.445-60.367-41.445
|
||||||
|
c-35.877,0-64.956,29.079-64.956,64.956c0,24.966,14.095,46.611,34.746,57.486c1.057,6.498,4.008,12.742,9.02,17.754l10.669,10.669
|
||||||
|
C37.621,167.324,4.392,130.88,4.392,86.612C4.392,38.78,43.167,0,91.004,0c32.505,0,60.79,17.934,75.605,44.416
|
||||||
|
c3.595-0.619,7.232-1.11,11.008-1.11c35.877,0,64.956,29.084,64.956,64.962C242.572,144.14,213.494,173.224,177.616,173.224z
|
||||||
|
M112.575,143.368"/>
|
||||||
|
<g>
|
||||||
|
<path fill="#FED243" d="M177.934,124.586c0.957,3.237,1.915,6.474,2.872,9.71c-0.245,1.498,0.341,2.901,0.525,4.348
|
||||||
|
c0.576,4.552,0.864,9.087-0.617,13.527c-1.248,3.74-3.467,6.645-7.415,7.822c-8.14,2.428-16.288,4.827-24.443,7.203
|
||||||
|
c-7.293,2.125-13.027-0.222-16.803-6.842c-0.837-1.467-1.459-3.029-1.94-4.654c-2.588-8.744-5.103-17.51-7.833-26.21
|
||||||
|
c-1.556-4.958-4.818-8.406-10.057-9.629c-3.105-0.725-6.204-0.561-9.256,0.317c-3.721,1.07-7.446,2.136-11.134,3.312
|
||||||
|
c-1.291,0.412-1.901,0.254-2.242-1.163c-0.541-2.244-1.216-4.461-1.953-6.65c-1.531-4.544-0.492-8.619,2.165-12.398
|
||||||
|
c1.673-2.379,3.856-4.239,6.3-5.794c1.503-0.956,3.021-1.89,4.532-2.834c5.994-1.773,11.988-3.546,17.982-5.319
|
||||||
|
c2.324,0.473,4.674-0.031,7.02,0.243c7.911,0.923,12.896,5.041,15.148,12.671c3.357,11.378,6.749,22.745,10.048,34.14
|
||||||
|
c0.442,1.528,1.028,1.761,2.492,1.333c1.448-0.424,1.665-0.993,1.243-2.377c-2.278-7.464-4.422-14.969-6.711-22.43
|
||||||
|
c-0.46-1.499-0.363-2.198,1.331-2.621c3.146-0.786,6.279-1.687,9.327-2.79c4.088-1.48,7.456-0.364,10.497,2.405
|
||||||
|
c2.531,2.304,4.255,5.182,5.858,8.165C176.009,120.192,176.528,122.597,177.934,124.586z M160.522,114.11
|
||||||
|
c-2.597,0.759-4.05,3.495-3.307,6.227c0.613,2.257,3.798,4.218,5.977,3.474c2.63-0.899,4.174-3.796,3.356-6.378
|
||||||
|
C165.729,114.845,163.097,113.357,160.522,114.11z"/>
|
||||||
|
<path fill="#3973A6" d="M83.829,29.567c4.751,0,9.501,0,14.252,0c0.225,0.557,0.737,0.343,1.132,0.404
|
||||||
|
c4.542,0.698,9.062,1.384,13.024,4.041c3.347,2.245,6.355,4.879,6.451,9.12c0.211,9.301,0.181,18.614-0.034,27.915
|
||||||
|
c-0.115,4.97-2.608,8.767-7.133,11.075c-2.786,1.421-5.762,1.943-8.871,1.935c-8.744-0.023-17.489-0.052-26.233,0.001
|
||||||
|
c-8.966,0.054-14.428,4.835-15.561,13.69c-0.604,4.719-0.162,9.482-0.206,14.225c-0.011,1.211-0.317,1.635-1.568,1.59
|
||||||
|
c-2.495-0.091-4.996-0.032-7.494-0.027c-3.691,0.008-6.807-1.413-9.419-3.93c-3.46-3.334-4.881-7.69-5.985-12.207
|
||||||
|
c-0.421-1.724-0.28-3.565-1.11-5.198c0-4.251,0-8.501,0-12.752c0.545-0.225,0.361-0.736,0.424-1.134
|
||||||
|
c0.635-4.027,1.544-7.971,3.704-11.497c3.252-5.308,7.896-8.095,14.244-8.058c7.527,0.044,19.705,0.025,36.534,0.007
|
||||||
|
c0.537-0.001,1.408,0.251,1.53-0.314c0.217-1.009,0.243-2.135,0.015-3.137c-0.156-0.687-1.086-0.393-1.674-0.395
|
||||||
|
c-7.931-0.019-15.863-0.047-23.794,0.012c-1.38,0.01-1.828-0.335-1.783-1.767c0.106-3.431,0.157-6.875-0.011-10.302
|
||||||
|
c-0.19-3.87,1.575-6.568,4.61-8.623c2.84-1.923,6.074-2.822,9.376-3.582C80.099,30.233,82.07,30.438,83.829,29.567z
|
||||||
|
M76.001,48.022c2.756,0.02,4.878-2.046,4.92-4.788c0.042-2.791-2.171-5.042-4.927-5.01c-2.638,0.03-4.875,2.291-4.876,4.928
|
||||||
|
C71.116,45.828,73.296,48.003,76.001,48.022z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#3973A6" d="M126.485,217.504c-2.819,3.824-5.638,7.648-8.457,11.471c-0.581-0.149-0.714,0.39-0.997,0.672
|
||||||
|
c-3.257,3.242-6.491,6.473-10.981,8.085c-3.793,1.362-7.698,2.221-11.169-0.219c-7.611-5.349-15.09-10.899-22.449-16.592
|
||||||
|
c-3.933-3.042-5.509-7.302-4.681-12.313c0.51-3.086,1.855-5.791,3.707-8.288c5.207-7.025,10.42-14.046,15.566-21.116
|
||||||
|
c5.277-7.249,4.669-14.483-1.785-20.649c-3.44-3.287-7.536-5.757-11.328-8.607c-0.968-0.727-1.128-1.226-0.349-2.205
|
||||||
|
c1.554-1.955,2.99-4.002,4.469-6.016c2.184-2.976,5.176-4.641,8.753-5.25c4.737-0.807,9.087,0.634,13.377,2.426
|
||||||
|
c1.638,0.684,3.036,1.89,4.842,2.191c3.421,2.522,6.843,5.044,10.264,7.567c-0.142,0.572,0.378,0.727,0.661,1.014
|
||||||
|
c2.864,2.901,5.5,5.972,7.056,9.804c2.343,5.767,1.831,11.159-1.966,16.247c-4.501,6.032-11.713,15.846-21.684,29.403
|
||||||
|
c-0.318,0.432-1.038,0.984-0.655,1.417c0.683,0.773,1.574,1.463,2.516,1.874c0.646,0.282,0.961-0.641,1.311-1.114
|
||||||
|
c4.722-6.373,9.451-12.74,14.109-19.159c0.811-1.117,1.354-1.272,2.481-0.387c2.699,2.122,5.44,4.206,8.298,6.104
|
||||||
|
c3.228,2.144,4.352,5.165,4.205,8.828c-0.137,3.427-1.333,6.563-2.68,9.672C128.162,214.107,126.827,215.571,126.485,217.504z
|
||||||
|
M116.274,200.252c-1.651,2.207-1.248,5.141,0.935,6.801c2.222,1.69,5.346,1.244,6.957-0.993c1.541-2.141,1.049-5.283-1.073-6.849
|
||||||
|
C120.939,197.622,117.895,198.087,116.274,200.252z"/>
|
||||||
|
<path fill="#FED243" d="M4.875,182.411c1.487-3.03,2.974-6.06,4.462-9.09c1.195-0.937,1.713-2.366,2.558-3.555
|
||||||
|
c2.659-3.74,5.517-7.272,9.614-9.536c3.451-1.907,7.051-2.542,10.753-0.735c7.633,3.725,15.254,7.478,22.863,11.251
|
||||||
|
c6.806,3.375,9.436,8.984,7.731,16.413c-0.378,1.646-0.978,3.217-1.724,4.739c-4.016,8.186-8.102,16.34-11.984,24.59
|
||||||
|
c-2.213,4.702-2.146,9.448,0.88,13.896c1.794,2.636,4.185,4.613,7.025,6.034c3.463,1.732,6.925,3.47,10.436,5.102
|
||||||
|
c1.229,0.571,1.571,1.1,0.862,2.374c-1.121,2.017-2.126,4.106-3.066,6.216c-1.951,4.381-5.474,6.675-9.988,7.657
|
||||||
|
c-2.842,0.618-5.708,0.509-8.559-0.003c-1.754-0.314-3.503-0.655-5.254-0.983c-5.611-2.754-11.223-5.508-16.834-8.262
|
||||||
|
c-1.39-1.922-3.461-3.143-5.001-4.932c-5.196-6.036-6.076-12.442-2.566-19.581c5.234-10.646,10.435-21.307,15.724-31.926
|
||||||
|
c0.709-1.423,0.436-1.992-0.931-2.668c-1.353-0.668-1.897-0.397-2.524,0.908c-3.378,7.035-6.882,14.01-10.25,21.05
|
||||||
|
c-0.677,1.414-1.221,1.864-2.754,1.028c-2.847-1.552-5.763-3.011-8.753-4.263c-4.01-1.679-5.733-4.78-6.096-8.878
|
||||||
|
c-0.302-3.409,0.378-6.694,1.218-9.975C3.316,186.948,4.563,184.827,4.875,182.411z M10.594,201.91
|
||||||
|
c2.425,1.2,5.347,0.17,6.65-2.343c1.077-2.076,0.062-5.676-2.046-6.603c-2.544-1.119-5.642-0.033-6.789,2.42
|
||||||
|
C7.26,197.844,8.189,200.72,10.594,201.91z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.4 KiB |
|
@ -0,0 +1,39 @@
|
||||||
|
import requests, time
|
||||||
|
from threading import Event
|
||||||
|
|
||||||
|
from httpretty import httprettified
|
||||||
|
from httpretty import HTTPretty
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified(allow_net_connect=False)
|
||||||
|
def test_read_timeout():
|
||||||
|
event = Event()
|
||||||
|
wait_seconds = 10
|
||||||
|
connect_timeout = 0.1
|
||||||
|
read_timeout = 0.1
|
||||||
|
|
||||||
|
def my_callback(request, url, headers):
|
||||||
|
event.wait(wait_seconds)
|
||||||
|
return 200, headers, "Received"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "http://example.com",
|
||||||
|
body=my_callback
|
||||||
|
)
|
||||||
|
|
||||||
|
requested_at = time.time()
|
||||||
|
try:
|
||||||
|
requests.get(
|
||||||
|
"http://example.com",
|
||||||
|
timeout=(connect_timeout, read_timeout))
|
||||||
|
except requests.exceptions.ReadTimeout:
|
||||||
|
pass
|
||||||
|
|
||||||
|
event_set_at = time.time()
|
||||||
|
event.set()
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
assert now - event_set_at < 0.2
|
||||||
|
total_duration = now - requested_at
|
||||||
|
assert total_duration < 0.2
|
|
@ -0,0 +1,14 @@
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=False, verbose=True)
|
||||||
|
def test_regex():
|
||||||
|
httpretty.register_uri(httpretty.GET, re.compile(r'.*'), status=418)
|
||||||
|
|
||||||
|
response1 = requests.get('http://foo.com')
|
||||||
|
assert response1.status_code == 418
|
||||||
|
|
||||||
|
response2 = requests.get('http://test.com')
|
||||||
|
assert response2.status_code == 418
|
|
@ -0,0 +1,235 @@
|
||||||
|
{"version": 2, "width": 115, "height": 30, "timestamp": 1621882710, "env": {"SHELL": "/usr/local/bin/bash", "TERM": "xterm-256color"}}
|
||||||
|
[1.115441, "o", "HTTPretty $ "]
|
||||||
|
[1.648012, "o", "."]
|
||||||
|
[1.67985, "o", "v"]
|
||||||
|
[1.767955, "o", "e"]
|
||||||
|
[2.045646, "o", "nv/"]
|
||||||
|
[2.888016, "o", "b"]
|
||||||
|
[2.960179, "o", "i"]
|
||||||
|
[3.046058, "o", "n/"]
|
||||||
|
[4.703758, "o", "o"]
|
||||||
|
[5.007876, "o", "\b\u001b[K"]
|
||||||
|
[5.183768, "o", "n"]
|
||||||
|
[5.249014, "o", "o"]
|
||||||
|
[5.286233, "o", "s"]
|
||||||
|
[5.423938, "o", "e"]
|
||||||
|
[5.553841, "o", "\u0007tests"]
|
||||||
|
[6.562148, "o", " "]
|
||||||
|
[6.680246, "o", "d"]
|
||||||
|
[6.791965, "o", "o"]
|
||||||
|
[6.880409, "o", "cs/"]
|
||||||
|
[7.533022, "o", "s"]
|
||||||
|
[7.596079, "o", "ource/"]
|
||||||
|
[7.636732, "o", "o"]
|
||||||
|
[7.728134, "o", "u"]
|
||||||
|
[8.176255, "o", "\b\u001b[K"]
|
||||||
|
[8.321962, "o", "\b\u001b[K"]
|
||||||
|
[8.463491, "o", "_"]
|
||||||
|
[8.584109, "o", "s"]
|
||||||
|
[8.727909, "o", "t"]
|
||||||
|
[8.780703, "o", "atic/"]
|
||||||
|
[9.403611, "o", "\u0007"]
|
||||||
|
[10.393067, "o", "\r\n"]
|
||||||
|
[10.393402, "o", "__pycache__/ guide-callback-regex-ipdb.py logo.svg\r\nHTTPretty $ .venv/bin/nosetests docs/source/_static/"]
|
||||||
|
[10.517051, "o", "\r\n"]
|
||||||
|
[10.517371, "o", "__pycache__/ guide-callback-regex-ipdb.py logo.svg\r\nHTTPretty $ .venv/bin/nosetests docs/source/_static/"]
|
||||||
|
[11.423948, "o", "g"]
|
||||||
|
[11.532343, "o", "uide-callback-regex-ipdb.py "]
|
||||||
|
[14.504165, "o", "\r\n"]
|
||||||
|
[16.477912, "o", "#190 guide-callback-regex-ipdb.test_basic_body ... "]
|
||||||
|
[18.01153, "o", "> \u001b[0;32m/Users/gabrielfalcao/projects/personal/HTTPretty/docs/source/_static/guide-callback-regex-ipdb.py\u001b[0m(13)\u001b[0;36mmy_callback\u001b[0;34m()\u001b[0m\r\n\u001b[0;32m 12 \u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0mipdb\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0mipdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[0m\u001b[0;32m---> 13 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m200\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mjson\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbody\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[0m\u001b[0;32m 14 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[0m\r\n"]
|
||||||
|
[18.014165, "o", "\u001b[?1l"]
|
||||||
|
[18.014537, "o", "\u001b[6n"]
|
||||||
|
[18.030527, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[6D\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[18.071481, "o", "\u001b[?25l\u001b[?7l\u001b[6D\u001b[0;38;5;28mipdb> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \b\u001b[22A\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[21.666012, "o", "\u001b[?25l\u001b[?7l\u001b[0ml\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[21.879456, "o", "\u001b[?25l\u001b[?7l\u001b[7D\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[0ml\u001b[7D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
|
||||||
|
[21.882208, "o", "\u001b[1;32m 8 \u001b[0m\u001b[0;32mdef\u001b[0m \u001b[0mtest_basic_body\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[1;32m 9 \u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[1;32m 10 \u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmy_callback\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[1;32m 11 \u001b[0m \u001b[0mbody\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[1;32m 12 \u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mipdb\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0mipdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[0;32m---> 13 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m200\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mjson\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbody\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[0m\u001b[1;32m 14 \u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[1;32m 15 \u001b[0m \u001b[0;31m# Match any url via the regular expression\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[1;32m 16 \u001b[0m \u001b[0mHTTPrett"]
|
||||||
|
[21.882467, "o", "y\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mregister_uri\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mHTTPretty\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGET\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcompile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mr'.*'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbody\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmy_callback\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[1;32m 17 \u001b[0m \u001b[0mHTTPretty\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mregister_uri\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mHTTPretty\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPOST\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcompile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mr'.*'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbody\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmy_callback\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\u001b[1;32m 18 \u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\r\n\r\n"]
|
||||||
|
[21.883476, "o", "\u001b[?1l"]
|
||||||
|
[21.883604, "o", "\u001b[6n"]
|
||||||
|
[21.887032, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[6D\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[21.892331, "o", "\u001b[?25l\u001b[?7l\u001b[6D\u001b[0;38;5;28mipdb> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \b\u001b[9A\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[22.81778, "o", "\u001b[?25l\u001b[?7l\u001b[0mp\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[22.969469, "o", "\u001b[?25l\u001b[?7l\u001b[0mp\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[23.181313, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[23.322972, "o", "\u001b[?25l\u001b[?7l\u001b[0mr\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[23.378349, "o", "\u001b[?25l\u001b[?7l\u001b[0me\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[23.544925, "o", "\u001b[?25l\u001b[?7l\u001b[0mq\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[23.665954, "o", "\u001b[?25l\u001b[?7l\u001b[0mu\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[23.741902, "o", "\u001b[?25l\u001b[?7l\u001b[0me\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[23.879494, "o", "\u001b[?25l\u001b[?7l\u001b[0ms\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[24.000226, "o", "\u001b[?25l\u001b[?7l\u001b[0mt\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[24.365902, "o", "\u001b[?25l\u001b[?7l\u001b[16D\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[0mpp request\u001b[16D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
|
||||||
|
[24.367405, "o", "<HTTPrettyRequest(\"POST\", \"https://test.com/\", headers={'Host': 'test.com', 'User-Agent': 'python-requests/2.25.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '18'}, body=18)>\r\n"]
|
||||||
|
[24.368531, "o", "\u001b[?1l"]
|
||||||
|
[24.368635, "o", "\u001b[6n"]
|
||||||
|
[24.371795, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[6D\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[24.376988, "o", "\u001b[?25l\u001b[?7l\u001b[6D\u001b[0;38;5;28mipdb> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \b\u001b[5A\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[27.359342, "o", "\u001b[?25l\u001b[?7l\u001b[0mpp request\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[27.82273, "o", "\u001b[?25l\u001b[?7l\u001b[0m.\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[28.255766, "o", "\u001b[?25l\u001b[?7l\u001b[0mh\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[28.313307, "o", "\u001b[?25l\u001b[?7l\u001b[0me\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[28.405472, "o", "\u001b[?25l\u001b[?7l\u001b[0ma\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[28.850422, "o", "\u001b[?25l\u001b[?7l\u001b[0md\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[28.912138, "o", "\u001b[?25l\u001b[?7l\u001b[0me\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[29.015229, "o", "\u001b[?25l\u001b[?7l\u001b[0mr\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[29.112081, "o", "\u001b[?25l\u001b[?7l\u001b[0ms\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[29.429921, "o", "\u001b[?25l\u001b[?7l\u001b[24D\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[0mpp request.headers\u001b[24D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
|
||||||
|
[29.431801, "o", "<http.client.HTTPMessage object at 0x112326a30>\r\n"]
|
||||||
|
[29.433016, "o", "\u001b[?1l"]
|
||||||
|
[29.433238, "o", "\u001b[6n"]
|
||||||
|
[29.436901, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[6D\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[29.444544, "o", "\u001b[?25l\u001b[?7l\u001b[6D\u001b[0;38;5;28mipdb> \u001b[0m\r\r\n\r\r\n\r\r\n\u001b[0m \b\u001b[3A\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[30.156471, "o", "\u001b[?25l\u001b[?7l\u001b[0mpp request.headers\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[30.608228, "o", "\u001b[?25l\u001b[?7l\u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[30.765725, "o", "\u001b[?25l\u001b[?7l\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[30.902549, "o", "\u001b[?25l\u001b[?7l\u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[31.381908, "o", "\u001b[?25l\u001b[?7l\u001b[0mdrequest.headers\u001b[15D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[31.510943, "o", "\u001b[?25l\u001b[?7l\u001b[0mirequest.headers\u001b[15D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[31.57281, "o", "\u001b[?25l\u001b[?7l\u001b[0mcrequest.headers\u001b[15D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[31.725778, "o", "\u001b[?25l\u001b[?7l\u001b[0mtrequest.headers\u001b[15D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[31.890771, "o", "\u001b[?25l\u001b[?7l\u001b[0m(request.headers\u001b[15D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[32.215385, "o", "\u001b[?25l\u001b[?7l\u001b[15C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[32.495352, "o", "\u001b[?25l\u001b[?7l\u001b[0m)\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[32.983501, "o", "\u001b[?25l\u001b[?7l\u001b[30D\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[0mpp dict(request.headers)\u001b[30D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
|
||||||
|
[32.98525, "o", "{'Accept': '*/*',\r\n 'Accept-Encoding': 'gzip, deflate',\r\n 'Connection': 'keep-alive',\r\n 'Content-Length': '18',\r\n 'Host': 'test.com',\r\n 'User-Agent': 'python-requests/2.25.1'}\r\n"]
|
||||||
|
[32.986518, "o", "\u001b[?1l"]
|
||||||
|
[32.986746, "o", "\u001b[6n"]
|
||||||
|
[32.990263, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[6D\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[32.996419, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[35.671881, "o", "\u001b[?25l\u001b[?7l\u001b[0mp\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[35.856772, "o", "\u001b[?25l\u001b[?7l\u001b[0mp\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[37.215658, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[37.693648, "o", "\u001b[?25l\u001b[?7l\u001b[0mr\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[37.758114, "o", "\u001b[?25l\u001b[?7l\u001b[0me\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[38.110067, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m\u001b[K\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[38.247765, "o", "\u001b[?25l\u001b[?7l\u001b[2D\u001b[0m\u001b[K\u001b[C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[38.397251, "o", "\u001b[?25l\u001b[?7l\u001b[0mh\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[38.444177, "o", "\u001b[?25l\u001b[?7l\u001b[0me\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[38.561504, "o", "\u001b[?25l\u001b[?7l\u001b[0ma\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[38.790802, "o", "\u001b[?25l\u001b[?7l\u001b[0md\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[38.926065, "o", "\u001b[?25l\u001b[?7l\u001b[0me\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[39.04009, "o", "\u001b[?25l\u001b[?7l\u001b[0mr\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[39.140742, "o", "\u001b[?25l\u001b[?7l\u001b[0ms\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[39.437672, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[39.886449, "o", "\u001b[?25l\u001b[?7l\u001b[17D\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[0mpp headers\u001b[16D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
|
||||||
|
[39.888255, "o", "{'connection': 'close',\r\n 'date': 'Mon, 24 May 2021 18:58:47 GMT',\r\n 'server': 'Python/HTTPretty',\r\n 'status': 200}\r\n"]
|
||||||
|
[39.890098, "o", "\u001b[?1l"]
|
||||||
|
[39.890285, "o", "\u001b[6n"]
|
||||||
|
[39.894169, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[6D\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[39.898533, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[45.717659, "o", "\u001b[?25l\u001b[?7l\u001b[0mh\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[45.733999, "o", "\u001b[?25l\u001b[?7l\u001b[0me\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[45.856076, "o", "\u001b[?25l\u001b[?7l\u001b[0ma\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[45.94174, "o", "\u001b[?25l\u001b[?7l\u001b[0md\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[46.030913, "o", "\u001b[?25l\u001b[?7l\u001b[0me\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[46.118401, "o", "\u001b[?25l\u001b[?7l\u001b[0mr\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[46.205205, "o", "\u001b[?25l\u001b[?7l\u001b[0ms\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[46.470445, "o", "\u001b[?25l\u001b[?7l\u001b[0m[\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[46.589064, "o", "\u001b[?25l\u001b[?7l\u001b[0m]\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[46.916657, "o", "\u001b[?25l\u001b[?7l\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[47.326705, "o", "\u001b[?25l\u001b[?7l\u001b[0m'']\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[47.354431, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[47.384967, "o", "\u001b[?25l\u001b[?7l\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[47.47368, "o", "\u001b[?25l\u001b[?7l\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[48.062051, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[48.345923, "o", "\u001b[?25l\u001b[?7l\u001b[0mC']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[48.59897, "o", "\u001b[?25l\u001b[?7l\u001b[0mo']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[48.663284, "o", "\u001b[?25l\u001b[?7l\u001b[0mn']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[48.750459, "o", "\u001b[?25l\u001b[?7l\u001b[0mt']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[48.809164, "o", "\u001b[?25l\u001b[?7l\u001b[0me']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[48.888522, "o", "\u001b[?25l\u001b[?7l\u001b[0mn']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[48.949883, "o", "\u001b[?25l\u001b[?7l\u001b[0mt']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[49.088369, "o", "\u001b[?25l\u001b[?7l\u001b[0m-']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[49.265468, "o", "\u001b[?25l\u001b[?7l\u001b[0mT']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[49.450521, "o", "\u001b[?25l\u001b[?7l\u001b[0my']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[49.516735, "o", "\u001b[?25l\u001b[?7l\u001b[0mp']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[49.662079, "o", "\u001b[?25l\u001b[?7l\u001b[0me']\u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[50.01415, "o", "\u001b[?25l\u001b[?7l\u001b[2C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[50.444668, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[50.506213, "o", "\u001b[?25l\u001b[?7l\u001b[0m=\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[50.566841, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[51.624083, "o", "\u001b[?25l\u001b[?7l\u001b[0m'\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[51.970211, "o", "\u001b[?25l\u001b[?7l\u001b[0m'\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[52.078158, "o", "\u001b[?25l\u001b[?7l\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[52.254808, "o", "\u001b[?25l\u001b[?7l\u001b[0ma'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[52.349951, "o", "\u001b[?25l\u001b[?7l\u001b[0mp'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[52.515528, "o", "\u001b[?25l\u001b[?7l\u001b[0mp'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[52.629864, "o", "\u001b[?25l\u001b[?7l\u001b[0ml'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[52.8266, "o", "\u001b[?25l\u001b[?7l\u001b[0mi'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[52.912236, "o", "\u001b[?25l\u001b[?7l\u001b[0mc'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[52.96185, "o", "\u001b[?25l\u001b[?7l\u001b[0ma'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[53.099131, "o", "\u001b[?25l\u001b[?7l\u001b[0mt'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[53.185005, "o", "\u001b[?25l\u001b[?7l\u001b[0mio'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[53.254551, "o", "\u001b[?25l\u001b[?7l\u001b[0mn'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[53.439432, "o", "\u001b[?25l\u001b[?7l\u001b[0m/'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[53.67937, "o", "\u001b[?25l\u001b[?7l\u001b[0mj'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[53.792533, "o", "\u001b[?25l\u001b[?7l\u001b[0ms'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[53.843024, "o", "\u001b[?25l\u001b[?7l\u001b[0mo'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[53.905637, "o", "\u001b[?25l\u001b[?7l\u001b[0mn'\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[54.32111, "o", "\u001b[?25l\u001b[?7l\u001b[49D\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[0mheaders['Content-Type'] = 'application/json'\u001b[50D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
|
||||||
|
[54.323453, "o", "\u001b[?1l"]
|
||||||
|
[54.323703, "o", "\u001b[6n"]
|
||||||
|
[54.32769, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[6D\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[54.33208, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[55.431101, "o", "\u001b[?25l\u001b[?7l\u001b[0mb\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[55.502203, "o", "\u001b[?25l\u001b[?7l\u001b[0mo\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[55.577597, "o", "\u001b[?25l\u001b[?7l\u001b[0md\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[55.702116, "o", "\u001b[?25l\u001b[?7l\u001b[0my\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[55.887196, "o", "\u001b[?25l\u001b[?7l\u001b[10D\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[0mbody\u001b[10D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[55.887473, "o", "\u001b[?2004l"]
|
||||||
|
[55.888628, "o", "{}\r\n"]
|
||||||
|
[55.889744, "o", "\u001b[?1l"]
|
||||||
|
[55.889857, "o", "\u001b[6n"]
|
||||||
|
[55.893146, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[6D\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[55.89828, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[56.247068, "o", "\u001b[?25l\u001b[?7l\u001b[0mb\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[56.319912, "o", "\u001b[?25l\u001b[?7l\u001b[0mo\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[56.448876, "o", "\u001b[?25l\u001b[?7l\u001b[0md\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[56.521946, "o", "\u001b[?25l\u001b[?7l\u001b[0my\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[56.870316, "o", "\u001b[?25l\u001b[?7l\u001b[0m=\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[57.286911, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m\u001b[K\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[57.598176, "o", "\u001b[?25l\u001b[?7l\u001b[0m[\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[58.044934, "o", "\u001b[?25l\u001b[?7l\u001b[0m\"\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[58.398262, "o", "\u001b[?25l\u001b[?7l\u001b[0mo\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[58.568438, "o", "\u001b[?25l\u001b[?7l\u001b[0mo\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[58.814278, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m\u001b[K\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[58.98533, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m\u001b[K\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[59.183923, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m\u001b[K\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[60.874043, "o", "\u001b[?25l\u001b[?7l\u001b[0m\"\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[61.07425, "o", "\u001b[?25l\u001b[?7l\u001b[0mf\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[61.154785, "o", "\u001b[?25l\u001b[?7l\u001b[0mo\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[61.28069, "o", "\u001b[?25l\u001b[?7l\u001b[0mo\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[61.831178, "o", "\u001b[?25l\u001b[?7l\u001b[0m\"\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[61.927243, "o", "\u001b[?25l\u001b[?7l\u001b[0m]\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[62.205321, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[62.288705, "o", "\u001b[?25l\u001b[?7l\u001b[0m=\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[62.381831, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[62.827726, "o", "\u001b[?25l\u001b[?7l\u001b[0m\"\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[62.999464, "o", "\u001b[?25l\u001b[?7l\u001b[0mb\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[63.070687, "o", "\u001b[?25l\u001b[?7l\u001b[0ma\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[63.157669, "o", "\u001b[?25l\u001b[?7l\u001b[0mr\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[63.421688, "o", "\u001b[?25l\u001b[?7l\u001b[0m\"\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[63.710915, "o", "\u001b[?25l\u001b[?7l\u001b[25D\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[0mbody[\"foo\"] = \"bar\"\u001b[25D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[63.711171, "o", "\u001b[?2004l"]
|
||||||
|
[63.713816, "o", "\u001b[?1l"]
|
||||||
|
[63.71398, "o", "\u001b[6n"]
|
||||||
|
[63.717705, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[6D\u001b[6C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[63.721978, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[64.750191, "o", "\u001b[?25l\u001b[?7l\u001b[0mc\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[65.540761, "o", "\u001b[?25l\u001b[?7l\u001b[7D\u001b[0m\u001b[J\u001b[0;38;5;28mipdb> \u001b[0mc\u001b[7D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
|
||||||
|
[65.541109, "o", "\u001b[?2004l"]
|
||||||
|
[65.544155, "o", "\u001b[32mpassed\u001b[0m\r\n"]
|
||||||
|
[65.54465, "o", "\r\n"]
|
||||||
|
[65.849412, "o", "Name Stmts Miss Branch BrPart Cover\r\n---------------------------------------------------------\r\n"]
|
||||||
|
[65.849701, "o", "httpretty/__init__.py 37 2 0 0 95%\r\nhttpretty/compat.py 17 2 0 0 88%\r\nhttpretty/core.py 1007 200 304 59 76%\r\nhttpretty/errors.py 16 2 6 3 77%\r\nhttpretty/http.py 28 0 4 1 97%\r\nhttpretty/utils.py 8 0 4 0 100%\r\nhttpretty/version.py 1 0 0 0 100%\r\n---------------------------------------------------------\r\nTOTAL 1114 206 318 63 77%\r\n"]
|
||||||
|
[66.339561, "o", "\u001b[30m-----------------------------------------------------------------------------\u001b[0m\r\n"]
|
||||||
|
[66.33975, "o", "1 test run in 49.067 seconds\u001b[32m (1 test passed)\u001b[0m\r\n"]
|
||||||
|
[66.340776, "o", "Error in atexit._run_exitfuncs:\r\nTraceback (most recent call last):\r\n"]
|
||||||
|
[66.340975, "o", " File \"/Users/gabrielfalcao/projects/personal/HTTPretty/.venv/lib/python3.8/site-packages/IPython/core/history.py\", line 780, in writeout_cache\r\n"]
|
||||||
|
[66.342214, "o", " self._writeout_input_cache(conn)\r\n"]
|
||||||
|
[66.342401, "o", " File \"/Users/gabrielfalcao/projects/personal/HTTPretty/.venv/lib/python3.8/site-packages/IPython/core/history.py\", line 763, in _writeout_input_cache\r\n"]
|
||||||
|
[66.343137, "o", " conn.execute(\"INSERT INTO history VALUES (?, ?, ?, ?)\",\r\nsqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread. The object was created in thread id 123145518436352 and this is thread id 4682546624.\r\n"]
|
||||||
|
[66.624442, "o", "HTTPretty $ "]
|
||||||
|
[67.88294, "o", "exit\r\n"]
|
|
@ -0,0 +1,21 @@
|
||||||
|
Acknowledgements
|
||||||
|
################
|
||||||
|
|
||||||
|
Caveats
|
||||||
|
=======
|
||||||
|
|
||||||
|
``forcing_headers`` + ``Content-Length``
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
When using the ``forcing_headers`` option make sure to add the header
|
||||||
|
``Content-Length`` otherwise calls using :py:mod:`requests` will try
|
||||||
|
to load the response endlessly.
|
||||||
|
|
||||||
|
Supported Libraries
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Because HTTPretty works in the socket level it should work with any HTTP client libraries, although it is `battle tested <https://github.com/gabrielfalcao/HTTPretty/tree/master/tests/functional>`_ against:
|
||||||
|
|
||||||
|
* `requests <http://docs.python-requests.org/en/latest/>`_
|
||||||
|
* `httplib2 <http://code.google.com/p/httplib2/>`_
|
||||||
|
* `urllib2 <http://docs.python.org/2/library/urllib2.html>`_
|
|
@ -0,0 +1,191 @@
|
||||||
|
.. _api:
|
||||||
|
|
||||||
|
API Reference
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. _httpretty:
|
||||||
|
|
||||||
|
register_uri
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. automethod:: httpretty.core.httpretty.register_uri
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
enable
|
||||||
|
------
|
||||||
|
|
||||||
|
.. automethod:: httpretty.core.httpretty.enable
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
disable
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. automethod:: httpretty.core.httpretty.disable
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
is_enabled
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. automethod:: httpretty.core.httpretty.is_enabled
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
last_request
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. autofunction:: httpretty.last_request
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
latest_requests
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. autofunction:: httpretty.latest_requests
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. automodule:: httpretty
|
||||||
|
|
||||||
|
.. _activate:
|
||||||
|
|
||||||
|
activate
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.activate
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. _httprettified:
|
||||||
|
|
||||||
|
httprettified
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. autofunction:: httpretty.core.httprettified
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. _enabled:
|
||||||
|
|
||||||
|
enabled
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.enabled
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. _httprettized:
|
||||||
|
|
||||||
|
httprettized
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.core.httprettized
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _HTTPrettyRequest:
|
||||||
|
|
||||||
|
HTTPrettyRequest
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.core.HTTPrettyRequest
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. _HTTPrettyRequestEmpty:
|
||||||
|
|
||||||
|
HTTPrettyRequestEmpty
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.core.HTTPrettyRequestEmpty
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
.. _FakeSockFile:
|
||||||
|
|
||||||
|
FakeSockFile
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.core.FakeSockFile
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. _FakeSSLSocket:
|
||||||
|
|
||||||
|
FakeSSLSocket
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.core.FakeSSLSocket
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. _URIInfo:
|
||||||
|
|
||||||
|
URIInfo
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.URIInfo
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. _URIMatcher:
|
||||||
|
|
||||||
|
URIMatcher
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.URIMatcher
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. _Entry:
|
||||||
|
|
||||||
|
Entry
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. autoclass:: httpretty.Entry
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
.. _api modules:
|
||||||
|
|
||||||
|
Modules
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. _api module core:
|
||||||
|
|
||||||
|
Core
|
||||||
|
----
|
||||||
|
|
||||||
|
.. automodule:: httpretty.core
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. _api module http:
|
||||||
|
|
||||||
|
Http
|
||||||
|
----
|
||||||
|
|
||||||
|
.. automodule:: httpretty.http
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. _api module utils:
|
||||||
|
|
||||||
|
Utils
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. automodule:: httpretty.utils
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. _api module exceptions:
|
||||||
|
|
||||||
|
Exceptions
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. automodule:: httpretty.errors
|
||||||
|
:members:
|
|
@ -0,0 +1,212 @@
|
||||||
|
Release Notes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Release 1.1.4
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Bugfix: `#435 <https://github.com/gabrielfalcao/HTTPretty/issues/435>`_ Fallback to WARNING when logging.getLogger().level is None.
|
||||||
|
|
||||||
|
Release 1.1.3
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Bugfix: `#430 <https://github.com/gabrielfalcao/HTTPretty/issues/430>`_ Respect socket timeout.
|
||||||
|
|
||||||
|
Release 1.1.2
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Bugfix: `#426 <https://github.com/gabrielfalcao/HTTPretty/issues/426>`_ Segmentation fault when running against a large amount of tests with ``pytest --mypy``.
|
||||||
|
|
||||||
|
Release 1.1.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Bugfix: `httpretty.disable()` injects pyopenssl into :py:mod:`urllib3` even if it originally wasn't `#417 <https://github.com/gabrielfalcao/HTTPretty/issues/417>`_
|
||||||
|
- Bugfix: "Incompatibility with boto3 S3 put_object" `#416 <https://github.com/gabrielfalcao/HTTPretty/issues/416>`_
|
||||||
|
- Bugfix: "Regular expression for URL -> TypeError: wrap_socket() missing 1 required" `#413 <https://github.com/gabrielfalcao/HTTPretty/issues/413>`_
|
||||||
|
- Bugfix: "Making requests to non-stadard port throws TimeoutError "`#387 <https://github.com/gabrielfalcao/HTTPretty/issues/387>`_
|
||||||
|
|
||||||
|
|
||||||
|
Release 1.1.0
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Feature: Display mismatched URL within ``UnmockedError`` whenever possible. `#388 <https://github.com/gabrielfalcao/HTTPretty/issues/388>`_
|
||||||
|
- Feature: Display mismatched URL via logging. `#419 <https://github.com/gabrielfalcao/HTTPretty/pull/419>`_
|
||||||
|
- Add new properties to :py:class:`httpretty.core.HTTPrettyRequest` (``protocol, host, url, path, method``).
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
.. testcode::
|
||||||
|
|
||||||
|
import httpretty
|
||||||
|
import requests
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_mismatches():
|
||||||
|
requests.get('http://sql-server.local')
|
||||||
|
requests.get('https://redis.local')
|
||||||
|
|
||||||
|
|
||||||
|
Release 1.0.5
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Bugfix: Support `socket.socketpair() <https://docs.python.org/3/library/socket.html#socket.socketpair>`_ . `#402 <https://github.com/gabrielfalcao/HTTPretty/issues/402>`_
|
||||||
|
- Bugfix: Prevent exceptions from re-applying monkey patches. `#406 <https://github.com/gabrielfalcao/HTTPretty/issues/406>`_
|
||||||
|
|
||||||
|
Release 1.0.4
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Python 3.8 and 3.9 support. `#407 <https://github.com/gabrielfalcao/HTTPretty/issues/407>`_
|
||||||
|
|
||||||
|
Release 1.0.3
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Fix compatibility with urllib3>=1.26. `#410 <https://github.com/gabrielfalcao/HTTPretty/pull/410>`_
|
||||||
|
|
||||||
|
Release 1.0.0
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Drop Python 2 support.
|
||||||
|
- Fix usage with redis and improve overall real-socket passthrough. `#271 <https://github.com/gabrielfalcao/HTTPretty/issues/271>`_.
|
||||||
|
- Fix TypeError: wrap_socket() missing 1 required positional argument: 'sock' (`#393 <https://github.com/gabrielfalcao/HTTPretty/pull/393>`_)
|
||||||
|
- Merge pull request `#364 <https://github.com/gabrielfalcao/HTTPretty/pull/364>`_
|
||||||
|
- Merge pull request `#371 <https://github.com/gabrielfalcao/HTTPretty/pull/371>`_
|
||||||
|
- Merge pull request `#379 <https://github.com/gabrielfalcao/HTTPretty/pull/379>`_
|
||||||
|
- Merge pull request `#386 <https://github.com/gabrielfalcao/HTTPretty/pull/386>`_
|
||||||
|
- Merge pull request `#302 <https://github.com/gabrielfalcao/HTTPretty/pull/302>`_
|
||||||
|
- Merge pull request `#373 <https://github.com/gabrielfalcao/HTTPretty/pull/373>`_
|
||||||
|
- Merge pull request `#383 <https://github.com/gabrielfalcao/HTTPretty/pull/383>`_
|
||||||
|
- Merge pull request `#385 <https://github.com/gabrielfalcao/HTTPretty/pull/385>`_
|
||||||
|
- Merge pull request `#389 <https://github.com/gabrielfalcao/HTTPretty/pull/389>`_
|
||||||
|
- Merge pull request `#391 <https://github.com/gabrielfalcao/HTTPretty/pull/391>`_
|
||||||
|
- Fix simple typo: neighter -> neither.
|
||||||
|
- Updated documentation for register_uri concerning using ports.
|
||||||
|
- Clarify relation between ``enabled`` and ``httprettized`` in API docs.
|
||||||
|
- Align signature with builtin socket.
|
||||||
|
|
||||||
|
Release 0.9.4
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
|
||||||
|
- Official Python 3.6 support
|
||||||
|
- Normalized coding style to comform with PEP8 (partially)
|
||||||
|
- Add more API reference coverage in docstrings of members such as :py:class:`httpretty.core.Entry`
|
||||||
|
- Continuous Integration building python 2.7 and 3.6
|
||||||
|
- Migrate from `pip <https://pypi.org/project/pip/>`_ to `pipenv <https://docs.pipenv.org/>`_
|
||||||
|
|
||||||
|
|
||||||
|
Release 0.8.4
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
|
||||||
|
- Refactored ``core.py`` and increased its unit test coverage to 80%.
|
||||||
|
HTTPretty is slightly more robust now.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
- POST requests being called twice
|
||||||
|
`#100 <https://github.com/gabrielfalcao/HTTPretty/pull/100>`__
|
||||||
|
|
||||||
|
Release 0.6.5
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Applied pull requests:
|
||||||
|
|
||||||
|
- continue on EAGAIN socket errors:
|
||||||
|
`#102 <https://github.com/gabrielfalcao/HTTPretty/pull/102>`__ by
|
||||||
|
`kouk <http://github.com/kouk>`__.
|
||||||
|
- Fix ``fake_gethostbyname`` for requests 2.0:
|
||||||
|
`#101 <https://github.com/gabrielfalcao/HTTPretty/pull/101>`__ by
|
||||||
|
`mgood <http://github.com/mgood>`__
|
||||||
|
- Add a way to match the querystrings:
|
||||||
|
`#98 <https://github.com/gabrielfalcao/HTTPretty/pull/98>`__ by
|
||||||
|
`ametaireau <http://github.com/ametaireau>`__
|
||||||
|
- Use common string case for URIInfo hostname comparison:
|
||||||
|
`#95 <https://github.com/gabrielfalcao/HTTPretty/pull/95>`__ by
|
||||||
|
`mikewaters <http://github.com/mikewaters>`__
|
||||||
|
- Expose httpretty.reset() to public API:
|
||||||
|
`#91 <https://github.com/gabrielfalcao/HTTPretty/pull/91>`__ by
|
||||||
|
`imankulov <http://github.com/imankulov>`__
|
||||||
|
- Don't duplicate http ports number:
|
||||||
|
`#89 <https://github.com/gabrielfalcao/HTTPretty/pull/89>`__ by
|
||||||
|
`mardiros <http://github.com/mardiros>`__
|
||||||
|
- Adding parsed\_body parameter to simplify checks:
|
||||||
|
`#88 <https://github.com/gabrielfalcao/HTTPretty/pull/88>`__ by
|
||||||
|
`toumorokoshi <http://github.com/toumorokoshi>`__
|
||||||
|
- Use the real socket if it's not HTTP:
|
||||||
|
`#87 <https://github.com/gabrielfalcao/HTTPretty/pull/87>`__ by
|
||||||
|
`mardiros <http://github.com/mardiros>`__
|
||||||
|
|
||||||
|
Release 0.6.2
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Fixing bug of lack of trailing slashes
|
||||||
|
`#73 <https://github.com/gabrielfalcao/HTTPretty/issues/73>`__
|
||||||
|
- Applied pull requests
|
||||||
|
`#71 <https://github.com/gabrielfalcao/HTTPretty/pull/71>`__ and
|
||||||
|
`#72 <https://github.com/gabrielfalcao/HTTPretty/pull/72>`__ by
|
||||||
|
@andresriancho
|
||||||
|
- Keyword arg coercion fix by @dupuy
|
||||||
|
- @papaeye fixed content-length calculation.
|
||||||
|
|
||||||
|
Release 0.6.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- New API, no more camel case and everything is available through a
|
||||||
|
simple import:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
@httpretty.activate
|
||||||
|
def test_function():
|
||||||
|
# httpretty.register_uri(...)
|
||||||
|
# make request...
|
||||||
|
pass
|
||||||
|
|
||||||
|
- Re-organized module into submodules
|
||||||
|
|
||||||
|
Release 0.5.14
|
||||||
|
--------------
|
||||||
|
|
||||||
|
- Delegate calls to other methods on socket
|
||||||
|
|
||||||
|
- `Normalized
|
||||||
|
header <https://github.com/gabrielfalcao/HTTPretty/pull/49>`__
|
||||||
|
strings
|
||||||
|
|
||||||
|
- Callbacks are `more intelligent
|
||||||
|
now <https://github.com/gabrielfalcao/HTTPretty/pull/47>`__
|
||||||
|
|
||||||
|
- Normalize urls matching for url quoting
|
||||||
|
|
||||||
|
Release 0.5.12
|
||||||
|
--------------
|
||||||
|
|
||||||
|
- HTTPretty doesn't hang when using other application protocols under a
|
||||||
|
@httprettified decorated test.
|
||||||
|
|
||||||
|
Release 0.5.11
|
||||||
|
--------------
|
||||||
|
|
||||||
|
- Ability to know whether HTTPretty is or not enabled through
|
||||||
|
``httpretty.is_enabled()``
|
||||||
|
|
||||||
|
Release 0.5.10
|
||||||
|
--------------
|
||||||
|
|
||||||
|
- Support to multiple methods per registered URL. Thanks @hughsaunders
|
||||||
|
|
||||||
|
Release 0.5.9
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Fixed python 3 support. Thanks @spulec
|
||||||
|
|
||||||
|
Release 0.5.8
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Support to `register regular expressions to match
|
||||||
|
urls <#matching-regular-expressions>`__
|
||||||
|
- `Body callback <#dynamic-responses-through-callbacks>`__ suppport
|
||||||
|
- Python 3 support
|
|
@ -0,0 +1,87 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import sphinx_rtd_theme
|
||||||
|
try:
|
||||||
|
from pathlib2 import Path
|
||||||
|
except ImportError:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
project_path = Path(__file__).absolute().parent.joinpath('../..')
|
||||||
|
|
||||||
|
sys.path.insert(0, project_path.as_posix())
|
||||||
|
|
||||||
|
from httpretty.version import version # noqa
|
||||||
|
|
||||||
|
|
||||||
|
project = 'HTTPretty'
|
||||||
|
copyright = '2011-2021, Gabriel Falcao'
|
||||||
|
author = 'Gabriel Falcao'
|
||||||
|
|
||||||
|
# The short X.Y version
|
||||||
|
version = version
|
||||||
|
# The full version, including alpha/beta/rc tags
|
||||||
|
release = version
|
||||||
|
|
||||||
|
|
||||||
|
extensions = [
|
||||||
|
'sphinx.ext.napoleon',
|
||||||
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.doctest',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
'sphinx.ext.coverage',
|
||||||
|
'sphinx.ext.ifconfig',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
'sphinx.ext.githubpages',
|
||||||
|
'sphinx.ext.autosummary',
|
||||||
|
'sphinx.ext.autosummary',
|
||||||
|
'sphinxcontrib.asciinema',
|
||||||
|
]
|
||||||
|
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
source_suffix = '.rst'
|
||||||
|
master_doc = 'index'
|
||||||
|
language = None
|
||||||
|
exclude_patterns = []
|
||||||
|
pygments_style = 'friendly'
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||||
|
# html_theme_options = {}
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
htmlhelp_basename = 'HTTPrettydoc'
|
||||||
|
|
||||||
|
latex_elements = {}
|
||||||
|
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, 'HTTPretty.tex', 'HTTPretty Documentation',
|
||||||
|
'Gabriel Falcao', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
man_pages = [
|
||||||
|
(master_doc, 'httpretty', 'HTTPretty Documentation',
|
||||||
|
[author], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
texinfo_documents = [
|
||||||
|
(master_doc, 'HTTPretty', 'HTTPretty Documentation',
|
||||||
|
author, 'HTTPretty', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
epub_title = project
|
||||||
|
epub_author = author
|
||||||
|
epub_publisher = author
|
||||||
|
epub_copyright = copyright
|
||||||
|
|
||||||
|
epub_exclude_files = ['search.html']
|
||||||
|
intersphinx_mapping = {
|
||||||
|
'python': ('https://docs.python.org/3', None),
|
||||||
|
|
||||||
|
'httplib2': ('https://httplib2.readthedocs.io/en/latest/', None),
|
||||||
|
'requests': ('https://requests.readthedocs.io/en/master/', None),
|
||||||
|
'urllib3': ('https://urllib3.readthedocs.io/en/latest/', None),
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
Hacking on HTTPretty
|
||||||
|
====================
|
||||||
|
|
||||||
|
install development dependencies
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
.. note:: HTTPretty uses `GNU Make
|
||||||
|
<https://www.gnu.org/software/make/>`_ as default build
|
||||||
|
tool.
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
make dependencies
|
||||||
|
|
||||||
|
|
||||||
|
next steps
|
||||||
|
----------
|
||||||
|
|
||||||
|
1. run the tests with make:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
make tests
|
||||||
|
|
||||||
|
2. hack at will
|
||||||
|
3. commit, push etc
|
||||||
|
4. send a pull request
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<HTTPretty - HTTP client mock for Python>
|
||||||
|
Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
Main contributors
|
||||||
|
=================
|
||||||
|
|
||||||
|
HTTPretty has received `many contributions <https://github.com/gabrielfalcao/HTTPretty/graphs/contributors>`_
|
||||||
|
but some folks made remarkable contributions and deserve extra credit:
|
||||||
|
|
||||||
|
- Andrew Gross ~> `@andrewgross <https://github.com/andrewgross>`_
|
||||||
|
- Hugh Saunders ~> `@hughsaunders <https://github.com/hughsaunders>`_
|
||||||
|
- James Rowe ~> `@JNRowe <https://github.com/JNRowe>`_
|
||||||
|
- Matt Luongo ~> `@mhluongo <https://github.com/mhluongo>`_
|
||||||
|
- Steve Pulec ~> `@spulec <https://github.com/spulec>`_
|
|
@ -0,0 +1,119 @@
|
||||||
|
.. _guides:
|
||||||
|
|
||||||
|
|
||||||
|
Guides
|
||||||
|
######
|
||||||
|
|
||||||
|
A series of guides to using HTTPretty for various interesting
|
||||||
|
purposes.
|
||||||
|
|
||||||
|
|
||||||
|
.. _matching_urls_via_regular_expressions:
|
||||||
|
|
||||||
|
Matching URLs via regular expressions
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
You can pass a compiled regular expression via :py:func:`re.compile`,
|
||||||
|
for example for intercepting all requests to a specific host.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
|
||||||
|
.. literalinclude:: _static/regex-example.py
|
||||||
|
:emphasize-lines: 8,10,13
|
||||||
|
|
||||||
|
|
||||||
|
Response Callbacks
|
||||||
|
==================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
You can use the `body` parameter of
|
||||||
|
:py:meth:`~httpretty.core.httpretty.register_uri` in useful, practical
|
||||||
|
ways because it accepts a :py:func:`callable` as value.
|
||||||
|
|
||||||
|
As matter of example, this is analogous to `defining routes in Flask
|
||||||
|
<https://flask.palletsprojects.com/en/2.0.x/quickstart/#routing>`_ when combined with :ref:`matching urls via regular expressions <matching_urls_via_regular_expressions>`
|
||||||
|
|
||||||
|
This analogy breaks down, though, because HTTPretty does not provide
|
||||||
|
tools to make it easy to handle cookies, parse querystrings etc.
|
||||||
|
|
||||||
|
So far this has been a deliberate decision to keep HTTPretty operating
|
||||||
|
mostly at the TCP socket level.
|
||||||
|
|
||||||
|
Nothing prevents you from being creative with callbacks though, and as
|
||||||
|
you will see in the examples below, the request parameter is an
|
||||||
|
instance of :py:class:`~httpretty.core.HTTPrettyRequest` which has
|
||||||
|
everything you need to create elaborate fake APIs.
|
||||||
|
|
||||||
|
|
||||||
|
Defining callbacks
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The body parameter callback must:
|
||||||
|
|
||||||
|
- Accept 3 arguments:
|
||||||
|
|
||||||
|
- `request` - :py:class:`~httpretty.core.HTTPrettyRequest`
|
||||||
|
- `uri` - :py:class:`str`
|
||||||
|
- `headers` - :py:class:`dict` with default response headers (including the ones from the parameters ``adding_headers`` and ``forcing_headers`` of :py:meth:`~httpretty.core.httpretty.register_uri`
|
||||||
|
|
||||||
|
- Return 3 a tuple (or list) with 3 values
|
||||||
|
|
||||||
|
- :py:class:`int` - HTTP Status Code
|
||||||
|
- :py:class:`dict` - Response Headers
|
||||||
|
- :py:class:`st` - Response Body
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
The **Content-Length** should match the byte length of the body.
|
||||||
|
|
||||||
|
Changing **Content-Length** it in your handler can cause your HTTP
|
||||||
|
client to misbehave, be very intentional when modifying it in our
|
||||||
|
callback.
|
||||||
|
|
||||||
|
The suggested way to manipulate headers is by modifying the
|
||||||
|
response headers passed as argument and returning them in the tuple
|
||||||
|
at the end.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from typing import Tuple
|
||||||
|
from httpretty.core import HTTPrettyRequest
|
||||||
|
|
||||||
|
def my_callback(
|
||||||
|
request: HTTPrettyRequest,
|
||||||
|
url: str,
|
||||||
|
headers: dict
|
||||||
|
|
||||||
|
) -> Tuple[int, dict, str]:
|
||||||
|
|
||||||
|
headers['Content-Type'] = 'text/plain'
|
||||||
|
return (200, headers, "the body")
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "https://test.com", body=my_callback)
|
||||||
|
|
||||||
|
|
||||||
|
Debug requests interactively with ipdb
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
The library `ipdb <https://pypi.org/project/ipdb/>`_ comes in handy to
|
||||||
|
introspect the request interactively with auto-complete via IPython.
|
||||||
|
|
||||||
|
.. literalinclude:: _static/guide-callback-regex-ipdb.py
|
||||||
|
:emphasize-lines: 12,16,17
|
||||||
|
|
||||||
|
.. asciinema:: 415981
|
||||||
|
:preload: 1
|
||||||
|
|
||||||
|
|
||||||
|
Emulating timeouts
|
||||||
|
------------------
|
||||||
|
|
||||||
|
In the bug report `#430
|
||||||
|
<https://github.com/gabrielfalcao/HTTPretty/issues/430>`_ the contributor `@mariojonke
|
||||||
|
<https://github.com/mariojonke>`_ provided a neat example of how to
|
||||||
|
emulate read timeout errors by "waiting" inside of a body callback.
|
||||||
|
|
||||||
|
|
||||||
|
.. literalinclude:: _static/read-timeout.py
|
||||||
|
:emphasize-lines: 11-13,21,28
|
|
@ -0,0 +1,80 @@
|
||||||
|
.. HTTPretty documentation master file, created by
|
||||||
|
sphinx-quickstart on Sun Dec 13 07:25:00 2015.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
HTTPretty's - HTTP Client Mocking for Python
|
||||||
|
============================================
|
||||||
|
|
||||||
|
.. image:: https://github.com/gabrielfalcao/HTTPretty/raw/master/docs/source/_static/logo.svg?sanitize=true
|
||||||
|
|
||||||
|
HTTP Client mocking tool for Python created by `Gabriel Falcão <https://github.com/gabrielfalcao>`_ . It provides a full fake TCP socket module. Inspired by `FakeWeb <https://github.com/chrisk/fakeweb>`_
|
||||||
|
|
||||||
|
Looking for the `Github Repository <https://github.com/gabrielfalcao/HTTPretty>`_ ?
|
||||||
|
|
||||||
|
**Python Support:**
|
||||||
|
|
||||||
|
- **3.6**
|
||||||
|
- **3.7**
|
||||||
|
- **3.8**
|
||||||
|
- **3.9**
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/dm/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/codecov/c/github/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://codecov.io/gh/gabrielfalcao/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/workflow/status/gabrielfalcao/HTTPretty/HTTPretty%20Tests?label=Python%203.6%20-%203.9
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/actions
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/readthedocs/httpretty
|
||||||
|
:target: https://httpretty.readthedocs.io/
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/license/gabrielfalcao/HTTPretty?label=Github%20License
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/blob/master/COPYING
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/l/HTTPretty?label=PyPi%20License
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/format/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/status/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/pyversions/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/implementation/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/snyk/vulnerabilities/github/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/network/alerts
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/v/tag/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/releases
|
||||||
|
|
||||||
|
.. |Join the chat at https://gitter.im/gabrielfalcao/HTTPretty| image:: https://badges.gitter.im/gabrielfalcao/HTTPretty.svg
|
||||||
|
:target: https://gitter.im/gabrielfalcao/HTTPretty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
introduction
|
||||||
|
guides
|
||||||
|
acks
|
||||||
|
api
|
||||||
|
contributing
|
||||||
|
changelog
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
|
@ -0,0 +1,182 @@
|
||||||
|
.. _introduction:
|
||||||
|
|
||||||
|
`Github <https://github.com/gabrielfalcao/HTTPretty>`_
|
||||||
|
|
||||||
|
|
||||||
|
What is HTTPretty ?
|
||||||
|
###################
|
||||||
|
|
||||||
|
.. highlight:: python
|
||||||
|
|
||||||
|
Once upon a time a python developer wanted to use a RESTful api,
|
||||||
|
everything was fine but until the day he needed to test the code that
|
||||||
|
hits the RESTful API: what if the API server is down? What if its
|
||||||
|
content has changed ?
|
||||||
|
|
||||||
|
Don't worry, HTTPretty is here for you:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
from sure import expect
|
||||||
|
|
||||||
|
logging.getLogger('httpretty.core').setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=False)
|
||||||
|
def test_yipit_api_returning_deals():
|
||||||
|
httpretty.register_uri(httpretty.GET, "http://api.yipit.com/v1/deals/",
|
||||||
|
body='[{"title": "Test Deal"}]',
|
||||||
|
content_type="application/json")
|
||||||
|
|
||||||
|
response = requests.get('http://api.yipit.com/v1/deals/')
|
||||||
|
|
||||||
|
expect(response.json()).to.equal([{"title": "Test Deal"}])
|
||||||
|
|
||||||
|
|
||||||
|
A more technical description
|
||||||
|
============================
|
||||||
|
|
||||||
|
HTTPretty is a python library that swaps the modules :py:mod:`socket`
|
||||||
|
and :py:mod:`ssl` with fake implementations that intercept HTTP
|
||||||
|
requests at the level of a TCP connection.
|
||||||
|
|
||||||
|
It is inspired on Ruby's `FakeWeb <http://fakeweb.rubyforge.org/>`_.
|
||||||
|
|
||||||
|
If you come from the Ruby programming language this would probably sound familiar :smiley:
|
||||||
|
|
||||||
|
Installing
|
||||||
|
==========
|
||||||
|
|
||||||
|
Installing httpretty is as easy as:
|
||||||
|
|
||||||
|
.. highlight:: bash
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
pip install httpretty
|
||||||
|
|
||||||
|
|
||||||
|
Demo
|
||||||
|
####
|
||||||
|
|
||||||
|
expecting a simple response body
|
||||||
|
================================
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
def test_one():
|
||||||
|
httpretty.enable(verbose=True, allow_net_connect=False) # enable HTTPretty so that it will monkey patch the socket module
|
||||||
|
httpretty.register_uri(httpretty.GET, "http://yipit.com/",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
response = requests.get('http://yipit.com')
|
||||||
|
|
||||||
|
assert response.text == "Find the best daily deals"
|
||||||
|
|
||||||
|
httpretty.disable() # disable afterwards, so that you will have no problems in code that uses that socket module
|
||||||
|
httpretty.reset() # reset HTTPretty state (clean up registered urls and request history)
|
||||||
|
|
||||||
|
|
||||||
|
making assertions in a callback that generates the response body
|
||||||
|
================================================================
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
@httpretty.activate
|
||||||
|
def test_with_callback_response():
|
||||||
|
def request_callback(request, uri, response_headers):
|
||||||
|
content_type = request.headers.get('Content-Type')
|
||||||
|
assert request.body == '{"nothing": "here"}', 'unexpected body: {}'.format(request.body)
|
||||||
|
assert content_type == 'application/json', 'expected application/json but received Content-Type: {}'.format(content_type)
|
||||||
|
return [200, response_headers, json.dumps({"hello": "world"})]
|
||||||
|
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.POST, "https://httpretty.example.com/api",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
response = requests.post('https://httpretty.example.com/api', headers={'Content-Type': 'application/json'}, data='{"nothing": "here"}')
|
||||||
|
|
||||||
|
expect(response.json()).to.equal({"hello": "world"})
|
||||||
|
|
||||||
|
|
||||||
|
Link headers
|
||||||
|
============
|
||||||
|
|
||||||
|
|
||||||
|
Tests link headers by using the `adding_headers` parameter.
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from sure import expect
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate
|
||||||
|
def test_link_response():
|
||||||
|
first_url = "http://foo-api.com/data"
|
||||||
|
second_url = "http://foo-api.com/data?page=2"
|
||||||
|
link_str = "<%s>; rel='next'" % second_url
|
||||||
|
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
first_url,
|
||||||
|
body='{"success": true}',
|
||||||
|
status=200,
|
||||||
|
content_type="text/json",
|
||||||
|
adding_headers={"Link": link_str},
|
||||||
|
)
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
second_url,
|
||||||
|
body='{"success": false}',
|
||||||
|
status=500,
|
||||||
|
content_type="text/json",
|
||||||
|
)
|
||||||
|
# Performs a request to `first_url` followed by some testing
|
||||||
|
response = requests.get(first_url)
|
||||||
|
expect(response.json()).to.equal({"success": True})
|
||||||
|
expect(response.status_code).to.equal(200)
|
||||||
|
next_url = response.links["next"]["url"]
|
||||||
|
expect(next_url).to.equal(second_url)
|
||||||
|
|
||||||
|
# Follow the next URL and perform some testing.
|
||||||
|
response2 = requests.get(next_url)
|
||||||
|
expect(response2.json()).to.equal({"success": False})
|
||||||
|
expect(response2.status_code).to.equal(500)
|
||||||
|
|
||||||
|
|
||||||
|
Motivation
|
||||||
|
##########
|
||||||
|
|
||||||
|
When building systems that access external resources such as RESTful
|
||||||
|
webservices, XMLRPC or even simple HTTP requests, we stumble in the
|
||||||
|
problem:
|
||||||
|
|
||||||
|
*"I'm gonna need to mock all those requests"*
|
||||||
|
|
||||||
|
It can be a bit of a hassle to use something like
|
||||||
|
:py:class:`mock.Mock` to stub the requests, this can work well for
|
||||||
|
low-level unit tests but when writing functional or integration tests
|
||||||
|
we should be able to allow the http calls to go through the TCP socket
|
||||||
|
module.
|
||||||
|
|
||||||
|
HTTPretty `monkey patches
|
||||||
|
<http://en.wikipedia.org/wiki/Monkey_patch>`_ Python's
|
||||||
|
:py:mod:`socket` core module with a fake version of the module.
|
||||||
|
|
||||||
|
Because HTTPretty implements a fake the modules :py:mod:`socket` and
|
||||||
|
:py:mod:`ssl` you can use write tests to code against any HTTP library
|
||||||
|
that use those modules.
|
|
@ -0,0 +1,190 @@
|
||||||
|
Metadata-Version: 1.2
|
||||||
|
Name: httpretty
|
||||||
|
Version: 1.1.4
|
||||||
|
Summary: HTTP client mock for Python
|
||||||
|
Home-page: https://httpretty.readthedocs.io/en/latest/
|
||||||
|
Author: Gabriel Falcao
|
||||||
|
Author-email: gabriel@nacaolivre.org
|
||||||
|
License: MIT
|
||||||
|
Project-URL: Documentation, https://httpretty.readthedocs.io/en/latest/
|
||||||
|
Project-URL: Source Code, https://github.com/gabrielfalcao/httpretty
|
||||||
|
Project-URL: Issue Tracker, https://github.com/gabrielfalcao/httpretty/issues
|
||||||
|
Project-URL: Continuous Integration, https://github.com/gabrielfalcao/HTTPretty/actions/workflows/pyenv.yml?query=branch%3Amaster+event%3Apush
|
||||||
|
Project-URL: Test Coverage, https://codecov.io/gh/gabrielfalcao/httpretty
|
||||||
|
Description: HTTPretty 1.1.4
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. image:: https://github.com/gabrielfalcao/HTTPretty/raw/master/docs/source/_static/logo.svg?sanitize=true
|
||||||
|
|
||||||
|
HTTP Client mocking tool for Python created by `Gabriel Falcão <https://github.com/gabrielfalcao>`_ . It provides a full fake TCP socket module. Inspired by `FakeWeb <https://github.com/chrisk/fakeweb>`_
|
||||||
|
|
||||||
|
|
||||||
|
- `Github Repository <https://github.com/gabrielfalcao/HTTPretty>`_
|
||||||
|
- `Documentation <https://httpretty.readthedocs.io/en/latest/>`_
|
||||||
|
- `PyPI Package <https://pypi.org/project/httpretty/>`_
|
||||||
|
|
||||||
|
|
||||||
|
**Python Support:**
|
||||||
|
|
||||||
|
- **3.6**
|
||||||
|
- **3.7**
|
||||||
|
- **3.8**
|
||||||
|
- **3.9**
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/dm/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/codecov/c/github/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://codecov.io/gh/gabrielfalcao/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/workflow/status/gabrielfalcao/HTTPretty/HTTPretty%20Tests?label=Python%203.6%20-%203.9
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/actions
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/readthedocs/httpretty
|
||||||
|
:target: https://httpretty.readthedocs.io/
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/license/gabrielfalcao/HTTPretty?label=Github%20License
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/blob/master/COPYING
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/l/HTTPretty?label=PyPi%20License
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/format/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/status/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/pyversions/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/implementation/HTTPretty
|
||||||
|
:target: https://pypi.org/project/HTTPretty
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/snyk/vulnerabilities/github/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/network/alerts
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/v/tag/gabrielfalcao/HTTPretty
|
||||||
|
:target: https://github.com/gabrielfalcao/HTTPretty/releases
|
||||||
|
|
||||||
|
.. |Join the chat at https://gitter.im/gabrielfalcao/HTTPretty| image:: https://badges.gitter.im/gabrielfalcao/HTTPretty.svg
|
||||||
|
:target: https://gitter.im/gabrielfalcao/HTTPretty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||||
|
|
||||||
|
Install
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
pip install httpretty
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Common Use Cases
|
||||||
|
================
|
||||||
|
|
||||||
|
- Test-driven development of API integrations
|
||||||
|
- Fake responses of external APIs
|
||||||
|
- Record and playback HTTP requests
|
||||||
|
|
||||||
|
|
||||||
|
Simple Example
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import sure
|
||||||
|
import httpretty
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_httpbin():
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
"https://httpbin.org/ip",
|
||||||
|
body='{"origin": "127.0.0.1"}'
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('https://httpbin.org/ip')
|
||||||
|
response.json().should.equal({'origin': '127.0.0.1'})
|
||||||
|
|
||||||
|
httpretty.latest_requests().should.have.length_of(1)
|
||||||
|
httpretty.last_request().should.equal(httpretty.latest_requests()[0])
|
||||||
|
httpretty.last_request().body.should.equal('{"origin": "127.0.0.1"}')
|
||||||
|
|
||||||
|
|
||||||
|
checking multiple responses
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_post_bodies():
|
||||||
|
url = 'http://httpbin.org/post'
|
||||||
|
httpretty.register_uri(httpretty.POST, url, status=200)
|
||||||
|
httpretty.register_uri(httpretty.POST, url, status=400)
|
||||||
|
requests.post(url, data={'foo': 'bar'})
|
||||||
|
requests.post(url, data={'zoo': 'zoo'})
|
||||||
|
assert 'foo=bar' in httpretty.latest_requests()[0].body
|
||||||
|
assert 'zoo=bar' in httpretty.latest_requests()[1].body
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<HTTPretty - HTTP client mock for Python>
|
||||||
|
Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
Main contributors
|
||||||
|
=================
|
||||||
|
|
||||||
|
HTTPretty has received `many contributions <https://github.com/gabrielfalcao/HTTPretty/graphs/contributors>`_
|
||||||
|
but some folks made remarkable contributions and deserve extra credit:
|
||||||
|
|
||||||
|
- Andrew Gross ~> `@andrewgross <https://github.com/andrewgross>`_
|
||||||
|
- Hugh Saunders ~> `@hughsaunders <https://github.com/hughsaunders>`_
|
||||||
|
- James Rowe ~> `@JNRowe <https://github.com/JNRowe>`_
|
||||||
|
- Matt Luongo ~> `@mhluongo <https://github.com/mhluongo>`_
|
||||||
|
- Steve Pulec ~> `@spulec <https://github.com/spulec>`_
|
||||||
|
- Miro Hrončok ~> `@hroncok <https://github.com/hroncok>`_
|
||||||
|
Mario Jonke ~> `@mariojonke <https://github.com/mariojonke>`_
|
||||||
|
|
||||||
|
Platform: UNKNOWN
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP
|
||||||
|
Classifier: Topic :: Software Development :: Testing
|
||||||
|
Requires-Python: >=3
|
|
@ -0,0 +1,82 @@
|
||||||
|
COPYING
|
||||||
|
MANIFEST.in
|
||||||
|
Makefile
|
||||||
|
README.rst
|
||||||
|
development.txt
|
||||||
|
requirements.txt
|
||||||
|
setup.cfg
|
||||||
|
setup.py
|
||||||
|
tox.ini
|
||||||
|
docs/make.bat
|
||||||
|
docs/build/doctrees/acks.doctree
|
||||||
|
docs/build/doctrees/api.doctree
|
||||||
|
docs/build/doctrees/changelog.doctree
|
||||||
|
docs/build/doctrees/contributing.doctree
|
||||||
|
docs/build/doctrees/environment.pickle
|
||||||
|
docs/build/doctrees/guides.doctree
|
||||||
|
docs/build/doctrees/index.doctree
|
||||||
|
docs/build/doctrees/introduction.doctree
|
||||||
|
docs/source/acks.rst
|
||||||
|
docs/source/api.rst
|
||||||
|
docs/source/changelog.rst
|
||||||
|
docs/source/conf.py
|
||||||
|
docs/source/contributing.rst
|
||||||
|
docs/source/guides.rst
|
||||||
|
docs/source/index.rst
|
||||||
|
docs/source/introduction.rst
|
||||||
|
docs/source/_static/guide-callback-regex-ipdb.py
|
||||||
|
docs/source/_static/logo.svg
|
||||||
|
docs/source/_static/read-timeout.py
|
||||||
|
docs/source/_static/regex-example.py
|
||||||
|
docs/source/_static/tmplun_dcms-ascii.cast
|
||||||
|
docs/source/_static/__pycache__/guide-callback-regex-ipdb.cpython-38.pyc
|
||||||
|
docs/source/_static/__pycache__/read-timeout.cpython-38-pytest-6.2.4.pyc
|
||||||
|
docs/source/_static/__pycache__/read-timeout.cpython-38.pyc
|
||||||
|
docs/source/_static/__pycache__/regex-example.cpython-38.pyc
|
||||||
|
httpretty/__init__.py
|
||||||
|
httpretty/compat.py
|
||||||
|
httpretty/core.py
|
||||||
|
httpretty/errors.py
|
||||||
|
httpretty/http.py
|
||||||
|
httpretty/utils.py
|
||||||
|
httpretty/version.py
|
||||||
|
httpretty.egg-info/PKG-INFO
|
||||||
|
httpretty.egg-info/SOURCES.txt
|
||||||
|
httpretty.egg-info/dependency_links.txt
|
||||||
|
httpretty.egg-info/not-zip-safe
|
||||||
|
httpretty.egg-info/top_level.txt
|
||||||
|
tests/__init__.py
|
||||||
|
tests/compat.py
|
||||||
|
tests/bugfixes/nosetests/__init__.py
|
||||||
|
tests/bugfixes/nosetests/test_242_ssl_bad_handshake.py
|
||||||
|
tests/bugfixes/nosetests/test_387_regex_port.py
|
||||||
|
tests/bugfixes/nosetests/test_388_unmocked_error_with_url.py
|
||||||
|
tests/bugfixes/nosetests/test_413_regex.py
|
||||||
|
tests/bugfixes/nosetests/test_414_httpx.py
|
||||||
|
tests/bugfixes/nosetests/test_416_boto3.py
|
||||||
|
tests/bugfixes/nosetests/test_417_openssl.py
|
||||||
|
tests/bugfixes/nosetests/test_425_latest_requests.py
|
||||||
|
tests/bugfixes/nosetests/test_430_respect_timeout.py
|
||||||
|
tests/bugfixes/nosetests/test_eventlet.py
|
||||||
|
tests/bugfixes/nosetests/test_redis.py
|
||||||
|
tests/bugfixes/nosetests/test_tornado_bind_unused_port.py
|
||||||
|
tests/bugfixes/pytest/test_426_mypy_segfault.py
|
||||||
|
tests/functional/__init__.py
|
||||||
|
tests/functional/base.py
|
||||||
|
tests/functional/test_bypass.py
|
||||||
|
tests/functional/test_debug.py
|
||||||
|
tests/functional/test_decorator.py
|
||||||
|
tests/functional/test_fakesocket.py
|
||||||
|
tests/functional/test_httplib2.py
|
||||||
|
tests/functional/test_passthrough.py
|
||||||
|
tests/functional/test_requests.py
|
||||||
|
tests/functional/test_urllib2.py
|
||||||
|
tests/functional/testserver.py
|
||||||
|
tests/functional/fixtures/playback-1.json
|
||||||
|
tests/pyopenssl/__init__.py
|
||||||
|
tests/pyopenssl/test_mock.py
|
||||||
|
tests/unit/__init__.py
|
||||||
|
tests/unit/test_core.py
|
||||||
|
tests/unit/test_http.py
|
||||||
|
tests/unit/test_httpretty.py
|
||||||
|
tests/unit/test_main.py
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
httpretty
|
|
@ -0,0 +1,94 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
# flake8: noqa
|
||||||
|
|
||||||
|
|
||||||
|
from . import core
|
||||||
|
|
||||||
|
from .core import httpretty, httprettified, EmptyRequestHeaders
|
||||||
|
from .core import set_default_thread_timeout, get_default_thread_timeout
|
||||||
|
from .errors import HTTPrettyError, UnmockedError
|
||||||
|
from .version import version
|
||||||
|
|
||||||
|
__version__ = version
|
||||||
|
|
||||||
|
# aliases
|
||||||
|
EmptyRequestHeaders = core.EmptyRequestHeaders
|
||||||
|
Entry = core.Entry
|
||||||
|
HTTPrettyRequestEmpty = core.HTTPrettyRequestEmpty
|
||||||
|
URIInfo = core.URIInfo
|
||||||
|
URIMatcher = core.URIMatcher
|
||||||
|
httprettified = core.httprettified
|
||||||
|
httprettized = core.httprettized
|
||||||
|
httpretty = core.httpretty
|
||||||
|
|
||||||
|
HTTPretty = httpretty
|
||||||
|
activate = httprettified
|
||||||
|
|
||||||
|
enabled = httprettized
|
||||||
|
|
||||||
|
enable = httpretty.enable
|
||||||
|
register_uri = httpretty.register_uri
|
||||||
|
disable = httpretty.disable
|
||||||
|
is_enabled = httpretty.is_enabled
|
||||||
|
reset = httpretty.reset
|
||||||
|
Response = httpretty.Response
|
||||||
|
|
||||||
|
GET = httpretty.GET
|
||||||
|
"""Match requests of GET method"""
|
||||||
|
PUT = httpretty.PUT
|
||||||
|
"""Match requests of PUT method"""
|
||||||
|
POST = httpretty.POST
|
||||||
|
"""Match requests of POST method"""
|
||||||
|
DELETE = httpretty.DELETE
|
||||||
|
"""Match requests of DELETE method"""
|
||||||
|
HEAD = httpretty.HEAD
|
||||||
|
"""Match requests of HEAD method"""
|
||||||
|
PATCH = httpretty.PATCH
|
||||||
|
"""Match requests of OPTIONS method"""
|
||||||
|
OPTIONS = httpretty.OPTIONS
|
||||||
|
"""Match requests of OPTIONS method"""
|
||||||
|
CONNECT = httpretty.CONNECT
|
||||||
|
"""Match requests of CONNECT method"""
|
||||||
|
|
||||||
|
|
||||||
|
def last_request():
|
||||||
|
"""
|
||||||
|
:returns: the last :py:class:`~httpretty.core.HTTPrettyRequest`
|
||||||
|
"""
|
||||||
|
return httpretty.last_request
|
||||||
|
|
||||||
|
|
||||||
|
def latest_requests():
|
||||||
|
"""returns the history of made requests"""
|
||||||
|
return httpretty.latest_requests
|
||||||
|
|
||||||
|
|
||||||
|
def has_request():
|
||||||
|
"""
|
||||||
|
:returns: bool - whether any request has been made
|
||||||
|
"""
|
||||||
|
return not isinstance(httpretty.last_request.headers, EmptyRequestHeaders)
|
|
@ -0,0 +1,60 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import io
|
||||||
|
import types
|
||||||
|
from urllib.parse import urlsplit
|
||||||
|
from urllib.parse import urlunsplit
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
from urllib.parse import quote
|
||||||
|
from urllib.parse import quote_plus
|
||||||
|
from urllib.parse import unquote
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
from http.server import BaseHTTPRequestHandler
|
||||||
|
|
||||||
|
unquote_utf8 = unquote
|
||||||
|
|
||||||
|
|
||||||
|
def encode_obj(in_obj):
|
||||||
|
return in_obj
|
||||||
|
|
||||||
|
|
||||||
|
class BaseClass(object):
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'BaseClass',
|
||||||
|
'BaseHTTPRequestHandler',
|
||||||
|
'quote',
|
||||||
|
'quote_plus',
|
||||||
|
'urlencode',
|
||||||
|
'urlunsplit',
|
||||||
|
'urlsplit',
|
||||||
|
'parse_qs',
|
||||||
|
]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,48 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import json
|
||||||
|
|
||||||
|
class HTTPrettyError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnmockedError(HTTPrettyError):
|
||||||
|
def __init__(self, message='Failed to handle network request', request=None, address=None):
|
||||||
|
hint = 'Tip: You could try setting (allow_net_connect=True) to allow unregistered requests through a real TCP connection in addition to (verbose=True) to debug the issue.'
|
||||||
|
if request:
|
||||||
|
headers = json.dumps(dict(request.headers), indent=2)
|
||||||
|
message = '{message}.\n\nIntercepted unknown {request.method} request {request.url}\n\nWith headers {headers}'.format(**locals())
|
||||||
|
|
||||||
|
if isinstance(address, (tuple, list)):
|
||||||
|
address = ":".join(map(str, address))
|
||||||
|
|
||||||
|
if address:
|
||||||
|
hint = 'address: {address} | {hint}'.format(**locals())
|
||||||
|
|
||||||
|
self.request = request
|
||||||
|
super(UnmockedError, self).__init__('{message}\n\n{hint}'.format(**locals()))
|
|
@ -0,0 +1,153 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
from .compat import BaseClass
|
||||||
|
from .utils import decode_utf8
|
||||||
|
|
||||||
|
|
||||||
|
STATUSES = {
|
||||||
|
100: "Continue",
|
||||||
|
101: "Switching Protocols",
|
||||||
|
102: "Processing",
|
||||||
|
200: "OK",
|
||||||
|
201: "Created",
|
||||||
|
202: "Accepted",
|
||||||
|
203: "Non-Authoritative Information",
|
||||||
|
204: "No Content",
|
||||||
|
205: "Reset Content",
|
||||||
|
206: "Partial Content",
|
||||||
|
207: "Multi-Status",
|
||||||
|
208: "Already Reported",
|
||||||
|
226: "IM Used",
|
||||||
|
300: "Multiple Choices",
|
||||||
|
301: "Moved Permanently",
|
||||||
|
302: "Found",
|
||||||
|
303: "See Other",
|
||||||
|
304: "Not Modified",
|
||||||
|
305: "Use Proxy",
|
||||||
|
306: "Switch Proxy",
|
||||||
|
307: "Temporary Redirect",
|
||||||
|
308: "Permanent Redirect",
|
||||||
|
400: "Bad Request",
|
||||||
|
401: "Unauthorized",
|
||||||
|
402: "Payment Required",
|
||||||
|
403: "Forbidden",
|
||||||
|
404: "Not Found",
|
||||||
|
405: "Method Not Allowed",
|
||||||
|
406: "Not Acceptable",
|
||||||
|
407: "Proxy Authentication Required",
|
||||||
|
408: "Request a Timeout",
|
||||||
|
409: "Conflict",
|
||||||
|
410: "Gone",
|
||||||
|
411: "Length Required",
|
||||||
|
412: "Precondition Failed",
|
||||||
|
413: "Request Entity Too Large",
|
||||||
|
414: "Request-URI Too Long",
|
||||||
|
415: "Unsupported Media Type",
|
||||||
|
416: "Requested Range Not Satisfiable",
|
||||||
|
417: "Expectation Failed",
|
||||||
|
418: "I'm a teapot",
|
||||||
|
420: "Enhance Your Calm",
|
||||||
|
422: "Unprocessable Entity",
|
||||||
|
423: "Locked",
|
||||||
|
424: "Failed Dependency",
|
||||||
|
425: "Unordered Collection",
|
||||||
|
426: "Upgrade Required",
|
||||||
|
428: "Precondition Required",
|
||||||
|
429: "Too Many Requests",
|
||||||
|
431: "Request Header Fields Too Large",
|
||||||
|
444: "No Response",
|
||||||
|
449: "Retry With",
|
||||||
|
450: "Blocked by Windows Parental Controls",
|
||||||
|
451: "Unavailable For Legal Reasons",
|
||||||
|
494: "Request Header Too Large",
|
||||||
|
495: "Cert Error",
|
||||||
|
496: "No Cert",
|
||||||
|
497: "HTTP to HTTPS",
|
||||||
|
499: "Client Closed Request",
|
||||||
|
500: "Internal Server Error",
|
||||||
|
501: "Not Implemented",
|
||||||
|
502: "Bad Gateway",
|
||||||
|
503: "Service Unavailable",
|
||||||
|
504: "Gateway Timeout",
|
||||||
|
505: "HTTP Version Not Supported",
|
||||||
|
506: "Variant Also Negotiates",
|
||||||
|
507: "Insufficient Storage",
|
||||||
|
508: "Loop Detected",
|
||||||
|
509: "Bandwidth Limit Exceeded",
|
||||||
|
510: "Not Extended",
|
||||||
|
511: "Network Authentication Required",
|
||||||
|
598: "Network read timeout error",
|
||||||
|
599: "Network connect timeout error",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HttpBaseClass(BaseClass):
|
||||||
|
GET = 'GET'
|
||||||
|
PUT = 'PUT'
|
||||||
|
POST = 'POST'
|
||||||
|
DELETE = 'DELETE'
|
||||||
|
HEAD = 'HEAD'
|
||||||
|
PATCH = 'PATCH'
|
||||||
|
OPTIONS = 'OPTIONS'
|
||||||
|
CONNECT = 'CONNECT'
|
||||||
|
METHODS = (GET, PUT, POST, DELETE, HEAD, PATCH, OPTIONS, CONNECT)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_requestline(s):
|
||||||
|
"""
|
||||||
|
http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5
|
||||||
|
|
||||||
|
>>> parse_requestline('GET / HTTP/1.0')
|
||||||
|
('GET', '/', '1.0')
|
||||||
|
>>> parse_requestline('post /testurl htTP/1.1')
|
||||||
|
('POST', '/testurl', '1.1')
|
||||||
|
>>> parse_requestline('Im not a RequestLine')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: Not a Request-Line
|
||||||
|
"""
|
||||||
|
methods = '|'.join(HttpBaseClass.METHODS)
|
||||||
|
m = re.match(r'(' + methods + r')\s+(.*)\s+HTTP/(1.[0|1])', s, re.I)
|
||||||
|
if m:
|
||||||
|
return m.group(1).upper(), m.group(2), m.group(3)
|
||||||
|
else:
|
||||||
|
raise ValueError('Not a Request-Line')
|
||||||
|
|
||||||
|
|
||||||
|
def last_requestline(sent_data):
|
||||||
|
"""
|
||||||
|
Find the last line in sent_data that can be parsed with parse_requestline
|
||||||
|
"""
|
||||||
|
for line in reversed(sent_data):
|
||||||
|
try:
|
||||||
|
parse_requestline(decode_utf8(line))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return line
|
|
@ -0,0 +1,37 @@
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
def utf8(s):
|
||||||
|
if isinstance(s, str):
|
||||||
|
s = s.encode('utf-8')
|
||||||
|
|
||||||
|
return bytes(s)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_utf8(s):
|
||||||
|
if isinstance(s, bytes):
|
||||||
|
s = s.decode("utf-8")
|
||||||
|
|
||||||
|
return str(s)
|
|
@ -0,0 +1 @@
|
||||||
|
version = '1.1.4'
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
[nosetests]
|
||||||
|
verbosity = 2
|
||||||
|
rednose = 1
|
||||||
|
with-coverage = 1
|
||||||
|
cover-inclusive = 1
|
||||||
|
cover-package = httpretty
|
||||||
|
cover-branches = 1
|
||||||
|
nocapture = 1
|
||||||
|
nologcapture = 1
|
||||||
|
stop = 1
|
||||||
|
with-id = 1
|
||||||
|
cover-xml = 1
|
||||||
|
cover-xml-file = coverage.xml
|
||||||
|
|
||||||
|
[egg_info]
|
||||||
|
tag_build =
|
||||||
|
tag_date = 0
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
|
def read_version():
|
||||||
|
ctx = {}
|
||||||
|
exec(local_file('httpretty', 'version.py'), ctx)
|
||||||
|
return ctx['version']
|
||||||
|
|
||||||
|
|
||||||
|
local_file = lambda *f: \
|
||||||
|
io.open(
|
||||||
|
os.path.join(os.path.dirname(__file__), *f), encoding='utf-8').read()
|
||||||
|
|
||||||
|
|
||||||
|
install_requires = []
|
||||||
|
tests_requires = ['nose', 'sure', 'coverage', 'mock;python_version<"3.3"',
|
||||||
|
'rednose']
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='httpretty',
|
||||||
|
version=read_version(),
|
||||||
|
description='HTTP client mock for Python',
|
||||||
|
long_description=local_file('README.rst'),
|
||||||
|
author='Gabriel Falcao',
|
||||||
|
author_email='gabriel@nacaolivre.org',
|
||||||
|
url='https://httpretty.readthedocs.io/en/latest/',
|
||||||
|
zip_safe=False,
|
||||||
|
packages=find_packages(exclude=['*tests*']),
|
||||||
|
tests_require=local_file('development.txt').splitlines(),
|
||||||
|
install_requires=install_requires,
|
||||||
|
license='MIT',
|
||||||
|
test_suite='nose.collector',
|
||||||
|
project_urls={
|
||||||
|
"Documentation": "https://httpretty.readthedocs.io/en/latest/",
|
||||||
|
"Source Code": "https://github.com/gabrielfalcao/httpretty",
|
||||||
|
"Issue Tracker": "https://github.com/gabrielfalcao/httpretty/issues",
|
||||||
|
"Continuous Integration": "https://github.com/gabrielfalcao/HTTPretty/actions/workflows/pyenv.yml?query=branch%3Amaster+event%3Apush",
|
||||||
|
"Test Coverage": "https://codecov.io/gh/gabrielfalcao/httpretty",
|
||||||
|
},
|
||||||
|
python_requires='>=3',
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Programming Language :: Python :: 3.8',
|
||||||
|
'Programming Language :: Python :: 3.9',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Topic :: Internet :: WWW/HTTP',
|
||||||
|
'Topic :: Software Development :: Testing'
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import sure
|
|
@ -0,0 +1,25 @@
|
||||||
|
import httpretty
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate
|
||||||
|
def test_test_ssl_bad_handshake():
|
||||||
|
# Reproduces https://github.com/gabrielfalcao/HTTPretty/issues/242
|
||||||
|
|
||||||
|
url_http = 'http://httpbin.org/status/200'
|
||||||
|
url_https = 'https://github.com/gabrielfalcao/HTTPretty'
|
||||||
|
|
||||||
|
httpretty.register_uri(httpretty.GET, url_http, body='insecure')
|
||||||
|
httpretty.register_uri(httpretty.GET, url_https, body='encrypted')
|
||||||
|
|
||||||
|
requests.get(url_http).text.should.equal('insecure')
|
||||||
|
requests.get(url_https).text.should.equal('encrypted')
|
||||||
|
|
||||||
|
httpretty.latest_requests().should.have.length_of(2)
|
||||||
|
insecure_request, secure_request = httpretty.latest_requests()[:2]
|
||||||
|
|
||||||
|
insecure_request.protocol.should.be.equal('http')
|
||||||
|
secure_request.protocol.should.be.equal('https')
|
||||||
|
|
||||||
|
insecure_request.url.should.be.equal(url_http)
|
||||||
|
secure_request.url.should.be.equal(url_https)
|
|
@ -0,0 +1,26 @@
|
||||||
|
# based on the snippet from https://github.com/gabrielfalcao/HTTPretty/issues/387
|
||||||
|
|
||||||
|
import httpretty
|
||||||
|
import requests
|
||||||
|
from sure import expect
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=False, verbose=True)
|
||||||
|
def test_match_with_port_no_slashes():
|
||||||
|
"Reproduce #387 registering host:port without trailing slash"
|
||||||
|
httpretty.register_uri(httpretty.GET, 'http://fakeuri.com:8080', body='{"hello":"world"}')
|
||||||
|
req = requests.get('http://fakeuri.com:8080', timeout=1)
|
||||||
|
expect(req.status_code).to.equal(200)
|
||||||
|
expect(req.json()).to.equal({"hello": "world"})
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=False, verbose=True)
|
||||||
|
def test_match_with_port_trailing_slash():
|
||||||
|
"Reproduce #387 registering host:port with trailing slash"
|
||||||
|
httpretty.register_uri(httpretty.GET, 'https://fakeuri.com:443/', body='{"hello":"world"}')
|
||||||
|
req = requests.get('https://fakeuri.com:443', timeout=1)
|
||||||
|
expect(req.status_code).to.equal(200)
|
||||||
|
expect(req.json()).to.equal({"hello": "world"})
|
||||||
|
|
||||||
|
req = requests.get('https://fakeuri.com:443/', timeout=1)
|
||||||
|
expect(req.status_code).to.equal(200)
|
||||||
|
expect(req.json()).to.equal({"hello": "world"})
|
|
@ -0,0 +1,56 @@
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
import requests
|
||||||
|
import httpretty
|
||||||
|
from httpretty.errors import UnmockedError
|
||||||
|
|
||||||
|
from unittest import skip
|
||||||
|
from sure import expect
|
||||||
|
|
||||||
|
|
||||||
|
def http():
|
||||||
|
sess = requests.Session()
|
||||||
|
adapter = requests.adapters.HTTPAdapter(pool_connections=1, pool_maxsize=1)
|
||||||
|
sess.mount('http://', adapter)
|
||||||
|
sess.mount('https://', adapter)
|
||||||
|
return sess
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=False)
|
||||||
|
def test_https_forwarding():
|
||||||
|
"#388 UnmockedError is raised with details about the mismatched request"
|
||||||
|
httpretty.register_uri(httpretty.GET, 'http://google.com/', body="Not Google")
|
||||||
|
httpretty.register_uri(httpretty.GET, 'https://google.com/', body="Not Google")
|
||||||
|
response1 = http().get('http://google.com/')
|
||||||
|
response2 = http().get('https://google.com/')
|
||||||
|
|
||||||
|
http().get.when.called_with("https://github.com/gabrielfalcao/HTTPretty").should.have.raised(UnmockedError, 'https://github.com/gabrielfalcao/HTTPretty')
|
||||||
|
|
||||||
|
response1.text.should.equal(response2.text)
|
||||||
|
try:
|
||||||
|
http().get("https://github.com/gabrielfalcao/HTTPretty")
|
||||||
|
except UnmockedError as exc:
|
||||||
|
expect(exc).to.have.property('request')
|
||||||
|
expect(exc.request).to.have.property('host').being.equal('github.com')
|
||||||
|
expect(exc.request).to.have.property('protocol').being.equal('https')
|
||||||
|
expect(exc.request).to.have.property('url').being.equal('https://github.com/gabrielfalcao/HTTPretty')
|
|
@ -0,0 +1,39 @@
|
||||||
|
# File based on the snippet provided in https://github.com/gabrielfalcao/HTTPretty/issues/413#issue-787264551
|
||||||
|
import requests
|
||||||
|
import httpretty
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def mock_body(request, url, response_headers):
|
||||||
|
return [200, response_headers, "Mocked " + url]
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_works_with_regex_path():
|
||||||
|
"Issue #413 regex with path"
|
||||||
|
patmatchpat = re.compile("/file-one")
|
||||||
|
|
||||||
|
httpretty.register_uri(httpretty.GET, patmatchpat, body=mock_body)
|
||||||
|
|
||||||
|
response = requests.get("https://example.com/file-one.html")
|
||||||
|
response.status_code.should.equal(200)
|
||||||
|
response.text.should.equal("Mocked https://example.com/file-one.html")
|
||||||
|
|
||||||
|
response = requests.get("https://github.com/file-one.json")
|
||||||
|
response.status_code.should.equal(200)
|
||||||
|
response.text.should.equal("Mocked https://github.com/file-one.json")
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_works_with_regex_dotall():
|
||||||
|
"Issue #413 regex with .*"
|
||||||
|
patmatchpat = re.compile(".*/file-two.*")
|
||||||
|
|
||||||
|
httpretty.register_uri(httpretty.GET, patmatchpat, body=mock_body)
|
||||||
|
|
||||||
|
response = requests.get("https://example.com/file-two.html")
|
||||||
|
response.status_code.should.equal(200)
|
||||||
|
response.text.should.equal("Mocked https://example.com/file-two.html")
|
||||||
|
|
||||||
|
response = requests.get("https://github.com/file-two.json")
|
||||||
|
response.status_code.should.equal(200)
|
||||||
|
response.text.should.equal("Mocked https://github.com/file-two.json")
|
|
@ -0,0 +1,13 @@
|
||||||
|
import httpretty
|
||||||
|
import httpx
|
||||||
|
from sure import expect
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||||
|
def test_httpx():
|
||||||
|
"#414 httpx support"
|
||||||
|
httpretty.register_uri(httpretty.GET, "https://blog.falcao.it/",
|
||||||
|
body="Posts")
|
||||||
|
|
||||||
|
response = httpx.get('https://blog.falcao.it')
|
||||||
|
|
||||||
|
expect(response.text).to.equal("Posts")
|
|
@ -0,0 +1,33 @@
|
||||||
|
import httpretty
|
||||||
|
import boto3
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
|
||||||
|
from sure import expect
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=False, verbose=True)
|
||||||
|
def test_boto3():
|
||||||
|
"#416 boto3 issue"
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.PUT,
|
||||||
|
"https://foo-bucket.s3.amazonaws.com/foo-object",
|
||||||
|
body="""<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Error>
|
||||||
|
<Code>AccessDenied</Code>
|
||||||
|
<Message>Access Denied</Message>
|
||||||
|
<RequestId>foo</RequestId>
|
||||||
|
<HostId>foo</HostId>
|
||||||
|
</Error>""",
|
||||||
|
status=403
|
||||||
|
)
|
||||||
|
|
||||||
|
session = boto3.Session(aws_access_key_id="foo", aws_secret_access_key="foo")
|
||||||
|
s3_client = session.client('s3')
|
||||||
|
|
||||||
|
put_object = expect(s3_client.put_object).when.called_with(
|
||||||
|
Bucket="foo-bucket",
|
||||||
|
Key="foo-object",
|
||||||
|
Body=b"foo"
|
||||||
|
)
|
||||||
|
|
||||||
|
put_object.should.have.raised(ClientError, 'Access Denied')
|
|
@ -0,0 +1,33 @@
|
||||||
|
# This test is based on @ento's example snippet:
|
||||||
|
# https://gist.github.com/ento/e1e33d7d67e406bf03fe61f018404c21
|
||||||
|
|
||||||
|
# Original Issue:
|
||||||
|
# https://github.com/gabrielfalcao/HTTPretty/issues/417
|
||||||
|
import httpretty
|
||||||
|
import requests
|
||||||
|
import urllib3
|
||||||
|
from sure import expect
|
||||||
|
from unittest import skipIf
|
||||||
|
try:
|
||||||
|
from urllib3.contrib.pyopenssl import extract_from_urllib3
|
||||||
|
except Exception:
|
||||||
|
extract_from_urllib3 = None
|
||||||
|
|
||||||
|
|
||||||
|
@skipIf(extract_from_urllib3 is None,
|
||||||
|
"urllib3.contrib.pyopenssl.extract_from_urllib3 does not exist")
|
||||||
|
def test_enable_disable_httpretty_extract():
|
||||||
|
"#417 urllib3.contrib.pyopenssl enable -> disable extract"
|
||||||
|
expect(urllib3.util.IS_PYOPENSSL).to.be.false
|
||||||
|
httpretty.enable()
|
||||||
|
httpretty.disable()
|
||||||
|
extract_from_urllib3()
|
||||||
|
expect(urllib3.util.IS_PYOPENSSL).to.be.false
|
||||||
|
|
||||||
|
def test_enable_disable_httpretty():
|
||||||
|
"#417 urllib3.contrib.pyopenssl enable -> disable extract"
|
||||||
|
expect(urllib3.util.IS_PYOPENSSL).to.be.false
|
||||||
|
httpretty.enable()
|
||||||
|
httpretty.disable()
|
||||||
|
extract_from_urllib3()
|
||||||
|
expect(urllib3.util.IS_PYOPENSSL).to.be.false
|
|
@ -0,0 +1,29 @@
|
||||||
|
import requests
|
||||||
|
import httpretty
|
||||||
|
from httpretty.errors import UnmockedError
|
||||||
|
|
||||||
|
from unittest import skip
|
||||||
|
from sure import expect
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=True)
|
||||||
|
def test_latest_requests():
|
||||||
|
"#425 - httpretty.latest_requests() can be called multiple times"
|
||||||
|
httpretty.register_uri(httpretty.GET, 'http://google.com/', body="Not Google")
|
||||||
|
httpretty.register_uri(httpretty.GET, 'https://google.com/', body="Not Google")
|
||||||
|
|
||||||
|
requests.get('http://google.com/')
|
||||||
|
httpretty.latest_requests()[-1].url.should.equal('http://google.com/')
|
||||||
|
requests.get('https://google.com/')
|
||||||
|
httpretty.latest_requests()[-1].url.should.equal('https://google.com/')
|
||||||
|
|
||||||
|
httpretty.latest_requests().should.have.length_of(2)
|
||||||
|
httpretty.latest_requests()[-1].url.should.equal('https://google.com/')
|
||||||
|
|
||||||
|
requests.get('https://google.com/')
|
||||||
|
httpretty.latest_requests().should.have.length_of(3)
|
||||||
|
httpretty.latest_requests()[-1].url.should.equal('https://google.com/')
|
||||||
|
|
||||||
|
requests.get('http://google.com/')
|
||||||
|
httpretty.latest_requests().should.have.length_of(4)
|
||||||
|
httpretty.latest_requests()[-1].url.should.equal('http://google.com/')
|
|
@ -0,0 +1,54 @@
|
||||||
|
# This test is based on @mariojonke snippet:
|
||||||
|
# https://github.com/gabrielfalcao/HTTPretty/issues/430
|
||||||
|
import time
|
||||||
|
from requests import Session
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from requests.exceptions import ReadTimeout
|
||||||
|
|
||||||
|
from threading import Event
|
||||||
|
|
||||||
|
from httpretty import httprettified
|
||||||
|
from httpretty import HTTPretty
|
||||||
|
|
||||||
|
|
||||||
|
def http(max_connections=1):
|
||||||
|
session = Session()
|
||||||
|
adapter = HTTPAdapter(
|
||||||
|
pool_connections=max_connections,
|
||||||
|
pool_maxsize=max_connections
|
||||||
|
)
|
||||||
|
session.mount('http://', adapter)
|
||||||
|
session.mount('https://', adapter)
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified(verbose=True, allow_net_connect=False)
|
||||||
|
def test_read_timeout():
|
||||||
|
"#430 httpretty should respect read timeout"
|
||||||
|
event = Event()
|
||||||
|
uri = "http://example.com"
|
||||||
|
|
||||||
|
# Given that I register a uri with a callback body that delays 10 seconds
|
||||||
|
wait_seconds = 10
|
||||||
|
|
||||||
|
def my_callback(request, url, headers):
|
||||||
|
event.wait(wait_seconds)
|
||||||
|
return 200, headers, "Received"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, uri, body=my_callback)
|
||||||
|
|
||||||
|
# And I use a thread pool with 1 TCP connection max
|
||||||
|
max_connections = 1
|
||||||
|
request = http(max_connections)
|
||||||
|
started_at = time.time()
|
||||||
|
# When I make an HTTP request with a read timeout of 0.1 and an indefinite connect timeout
|
||||||
|
when_called = request.get.when.called_with(uri, timeout=(None, 0.1))
|
||||||
|
|
||||||
|
# Then the request should have raised a connection timeout
|
||||||
|
when_called.should.have.raised(ReadTimeout)
|
||||||
|
|
||||||
|
# And the total execution time should be less than 0.2 seconds
|
||||||
|
event.set()
|
||||||
|
total_time = time.time() - started_at
|
||||||
|
total_time.should.be.lower_than(0.2)
|
|
@ -0,0 +1,11 @@
|
||||||
|
import httpretty
|
||||||
|
import requests
|
||||||
|
import eventlet
|
||||||
|
eventlet.monkey_patch(all=False, socket=True)
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate
|
||||||
|
def test_something():
|
||||||
|
|
||||||
|
httpretty.register_uri(httpretty.GET, 'https://example.com', body='foo')
|
||||||
|
requests.get('https://example.com').text.should.equal('foo')
|
|
@ -0,0 +1,52 @@
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
try:
|
||||||
|
from redis import Redis
|
||||||
|
except ImportError:
|
||||||
|
Redis = None
|
||||||
|
|
||||||
|
from unittest import skipUnless
|
||||||
|
|
||||||
|
|
||||||
|
def redis_available():
|
||||||
|
if Redis is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
params = dict(
|
||||||
|
host=os.getenv('REDIS_HOST') or '127.0.0.1',
|
||||||
|
port=int(os.getenv('REDIS_PORT') or 6379)
|
||||||
|
)
|
||||||
|
conn = Redis(**params)
|
||||||
|
try:
|
||||||
|
conn.keys('*')
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@skipUnless(redis_available(), reason='no redis server available for test')
|
||||||
|
@httpretty.activate()
|
||||||
|
def test_work_in_parallel_to_redis():
|
||||||
|
"HTTPretty should passthrough redis connections"
|
||||||
|
|
||||||
|
redis = Redis()
|
||||||
|
|
||||||
|
keys = redis.keys('*')
|
||||||
|
for key in keys:
|
||||||
|
redis.delete(key)
|
||||||
|
|
||||||
|
redis.append('item1', 'value1')
|
||||||
|
redis.append('item2', 'value2')
|
||||||
|
|
||||||
|
sorted(redis.keys('*')).should.equal([b'item1', b'item2'])
|
||||||
|
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
"http://redis.io",
|
||||||
|
body="salvatore")
|
||||||
|
|
||||||
|
response = requests.get('http://redis.io')
|
||||||
|
response.text.should.equal('salvatore')
|
|
@ -0,0 +1,18 @@
|
||||||
|
import httpretty
|
||||||
|
from unittest import skip
|
||||||
|
from tornado.testing import bind_unused_port
|
||||||
|
|
||||||
|
|
||||||
|
@skip('')
|
||||||
|
@httpretty.activate(allow_net_connect=True)
|
||||||
|
def test_passthrough_binding_socket():
|
||||||
|
# issue #247
|
||||||
|
|
||||||
|
result = bind_unused_port()
|
||||||
|
result.should.be.a(tuple)
|
||||||
|
result.should.have.length_of(2)
|
||||||
|
|
||||||
|
socket, port = result
|
||||||
|
|
||||||
|
port.should.be.an(int)
|
||||||
|
socket.close()
|
|
@ -0,0 +1,81 @@
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
import re
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateTests(type):
|
||||||
|
def __init__(cls, name, bases, attrs):
|
||||||
|
if name in ('GenerateTestMeta',): return
|
||||||
|
|
||||||
|
count = getattr(cls, '__generate_count__', attrs.get('__generate_count__'))
|
||||||
|
if not isinstance(count, int):
|
||||||
|
raise SyntaxError(f'Metaclass requires def `__generate_count__ = NUMBER_OF_TESTS` to be set to an integer')
|
||||||
|
|
||||||
|
generate_method = getattr(cls, '__generate_method__', attrs.get('__generate_method__'))
|
||||||
|
if not callable(generate_method):
|
||||||
|
raise SyntaxError(f'Metaclass requires def `__generate_method__(test_name):` to be implemented')
|
||||||
|
|
||||||
|
|
||||||
|
for x in range(count):
|
||||||
|
test_name = "test_{}".format(x)
|
||||||
|
def test_func(self, *args, **kwargs):
|
||||||
|
run_test = generate_method(test_name)
|
||||||
|
run_test(self, *args, **kwargs)
|
||||||
|
|
||||||
|
test_func.__name__ = test_name
|
||||||
|
attrs[test_name] = test_func
|
||||||
|
setattr(cls, test_name, test_func)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBug426MypySegfaultWithCallbackAndPayload(unittest.TestCase, metaclass=GenerateTests):
|
||||||
|
__generate_count__ = 1000
|
||||||
|
|
||||||
|
def __generate_method__(test_name):
|
||||||
|
@httpretty.httprettified(allow_net_connect=False)
|
||||||
|
def test_func(self):
|
||||||
|
httpretty.register_uri(httpretty.GET, 'http://github.com', body=self.json_response_callback({"kind": "insecure"}))
|
||||||
|
httpretty.register_uri(httpretty.GET, 'https://github.com', body=self.json_response_callback({"kind": "secure"}))
|
||||||
|
httpretty.register_uri(httpretty.POST, re.compile('github.com/.*'), body=self.json_response_callback({"kind": "regex"}) )
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
'https://github.com/foo',
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
data=json.dumps({test_name: time.time()}))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get('https://gitlab.com')
|
||||||
|
assert response.status_code == 200
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return test_func
|
||||||
|
|
||||||
|
def json_response_callback(self, data):
|
||||||
|
|
||||||
|
payload = dict(data)
|
||||||
|
payload.update({
|
||||||
|
"time": time.time()
|
||||||
|
})
|
||||||
|
|
||||||
|
def request_callback(request, path, headers):
|
||||||
|
return [200, headers, json.dumps(payload)]
|
||||||
|
|
||||||
|
return request_callback
|
||||||
|
|
||||||
|
|
||||||
|
class TestBug426MypySegfaultWithEmptyMethod(unittest.TestCase, metaclass=GenerateTests):
|
||||||
|
__generate_count__ = 10000
|
||||||
|
|
||||||
|
def __generate_method__(test_name):
|
||||||
|
@httpretty.httprettified(allow_net_connect=False)
|
||||||
|
def test_func(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return test_func
|
|
@ -0,0 +1,4 @@
|
||||||
|
try:
|
||||||
|
from unittest.mock import Mock, patch, call, MagicMock
|
||||||
|
except ImportError:
|
||||||
|
from mock import Mock, patch, call, MagicMock
|
|
@ -0,0 +1,28 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
warnings.simplefilter('ignore')
|
|
@ -0,0 +1,118 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import tornado.ioloop
|
||||||
|
import tornado.web
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from os.path import abspath, dirname, join
|
||||||
|
from httpretty.core import POTENTIAL_HTTP_PORTS, old_socket
|
||||||
|
|
||||||
|
|
||||||
|
def get_free_tcp_port():
|
||||||
|
"""returns a TCP port that can be used for listen in the host.
|
||||||
|
"""
|
||||||
|
tcp = old_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
tcp.bind(('', 0))
|
||||||
|
host, port = tcp.getsockname()
|
||||||
|
tcp.close()
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
LOCAL_FILE = lambda *path: join(abspath(dirname(__file__)), *path)
|
||||||
|
FIXTURE_FILE = lambda name: LOCAL_FILE('fixtures', name)
|
||||||
|
|
||||||
|
|
||||||
|
class JSONEchoHandler(tornado.web.RequestHandler):
|
||||||
|
def get(self, matched):
|
||||||
|
payload = dict([(x, self.get_argument(x)) for x in self.request.arguments])
|
||||||
|
self.write(json.dumps({matched or 'index': payload}, indent=4))
|
||||||
|
|
||||||
|
def post(self, matched):
|
||||||
|
payload = dict(self.request.arguments)
|
||||||
|
self.write(json.dumps({
|
||||||
|
matched or 'index': payload,
|
||||||
|
'req_body': self.request.body.decode('utf-8'),
|
||||||
|
'req_headers': dict(self.request.headers.items()),
|
||||||
|
}, indent=4))
|
||||||
|
|
||||||
|
|
||||||
|
class JSONEchoServer(threading.Thread):
|
||||||
|
def __init__(self, lock, port, *args, **kw):
|
||||||
|
self.lock = lock
|
||||||
|
self.port = int(port)
|
||||||
|
self._stop = threading.Event()
|
||||||
|
super(JSONEchoServer, self).__init__(*args, **kw)
|
||||||
|
self.daemon = True
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._stop.set()
|
||||||
|
|
||||||
|
def stopped(self):
|
||||||
|
return self._stop.isSet()
|
||||||
|
|
||||||
|
def setup_application(self):
|
||||||
|
return tornado.web.Application([
|
||||||
|
(r"/(.*)", JSONEchoHandler),
|
||||||
|
])
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
loop = tornado.ioloop.IOLoop()
|
||||||
|
|
||||||
|
application = self.setup_application()
|
||||||
|
application.listen(self.port)
|
||||||
|
self.lock.release()
|
||||||
|
loop.start()
|
||||||
|
|
||||||
|
|
||||||
|
def use_tornado_server(callback):
|
||||||
|
lock = threading.Lock()
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
@wraps(callback)
|
||||||
|
def func(*args, **kw):
|
||||||
|
port = os.getenv('TEST_PORT', get_free_tcp_port())
|
||||||
|
POTENTIAL_HTTP_PORTS.add(port)
|
||||||
|
kw['port'] = port
|
||||||
|
server = JSONEchoServer(lock, port)
|
||||||
|
server.start()
|
||||||
|
try:
|
||||||
|
lock.acquire()
|
||||||
|
callback(*args, **kw)
|
||||||
|
finally:
|
||||||
|
lock.release()
|
||||||
|
server.stop()
|
||||||
|
if port in POTENTIAL_HTTP_PORTS:
|
||||||
|
POTENTIAL_HTTP_PORTS.remove(port)
|
||||||
|
return func
|
|
@ -0,0 +1,58 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"body": "",
|
||||||
|
"headers": {
|
||||||
|
"host": "localhost:8888",
|
||||||
|
"accept-encoding": "gzip, deflate, compress",
|
||||||
|
"content-length": "0",
|
||||||
|
"accept": "*/*",
|
||||||
|
"user-agent": "python-requests/1.1.0 CPython/2.7.5 Darwin/12.5.0"
|
||||||
|
},
|
||||||
|
"querystring": {
|
||||||
|
"age": [
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"name": [
|
||||||
|
"Gabriel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uri": "http://localhost:8888/foobar?name=Gabriel&age=25",
|
||||||
|
"method": "GET"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"body": "{\n \"foobar\": {\n \"age\": \"25\", \n \"name\": \"Gabriel\"\n }\n}",
|
||||||
|
"headers": {
|
||||||
|
"content-length": "73",
|
||||||
|
"etag": "\"6fdccaba6542114e7d1098d22a01623dc2aa5761\"",
|
||||||
|
"content-type": "text/html; charset=UTF-8",
|
||||||
|
"server": "TornadoServer/2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"body": "{\"test\": \"123\"}",
|
||||||
|
"headers": {
|
||||||
|
"host": "localhost:8888",
|
||||||
|
"accept-encoding": "gzip, deflate, compress",
|
||||||
|
"content-length": "15",
|
||||||
|
"accept": "*/*",
|
||||||
|
"user-agent": "python-requests/1.1.0 CPython/2.7.5 Darwin/12.5.0"
|
||||||
|
},
|
||||||
|
"querystring": {},
|
||||||
|
"uri": "http://localhost:8888/foobar",
|
||||||
|
"method": "POST"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"body": "{\n \"foobar\": {}\n}",
|
||||||
|
"headers": {
|
||||||
|
"content-length": "20",
|
||||||
|
"content-type": "text/html; charset=UTF-8",
|
||||||
|
"server": "TornadoServer/2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,212 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# <httpretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
try:
|
||||||
|
import urllib.request as urllib2
|
||||||
|
except ImportError:
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
from .testserver import TornadoServer, TCPServer, TCPClient
|
||||||
|
from .base import get_free_tcp_port
|
||||||
|
from sure import expect, that_with_context
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
import httpretty
|
||||||
|
from httpretty import core, HTTPretty
|
||||||
|
|
||||||
|
|
||||||
|
def start_http_server(context):
|
||||||
|
if httpretty.httpretty._is_enabled:
|
||||||
|
allow_net_connect = httpretty.httpretty.allow_net_connect
|
||||||
|
else:
|
||||||
|
allow_net_connect = True
|
||||||
|
httpretty.disable()
|
||||||
|
context.http_port = get_free_tcp_port()
|
||||||
|
context.server = TornadoServer(context.http_port)
|
||||||
|
context.server.start()
|
||||||
|
ready = False
|
||||||
|
timeout = 2
|
||||||
|
started_at = time.time()
|
||||||
|
while not ready:
|
||||||
|
httpretty.disable()
|
||||||
|
time.sleep(.1)
|
||||||
|
try:
|
||||||
|
requests.get('http://localhost:{}/'.format(context.http_port))
|
||||||
|
ready = True
|
||||||
|
except Exception:
|
||||||
|
if time.time() - started_at >= timeout:
|
||||||
|
break
|
||||||
|
|
||||||
|
httpretty.enable(allow_net_connect=allow_net_connect)
|
||||||
|
|
||||||
|
|
||||||
|
def stop_http_server(context):
|
||||||
|
context.server.stop()
|
||||||
|
httpretty.enable()
|
||||||
|
|
||||||
|
|
||||||
|
def start_tcp_server(context):
|
||||||
|
context.tcp_port = get_free_tcp_port()
|
||||||
|
context.server = TCPServer(context.tcp_port)
|
||||||
|
context.server.start()
|
||||||
|
context.client = TCPClient(context.tcp_port)
|
||||||
|
httpretty.enable()
|
||||||
|
|
||||||
|
|
||||||
|
def stop_tcp_server(context):
|
||||||
|
context.server.stop()
|
||||||
|
context.client.close()
|
||||||
|
httpretty.enable()
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate
|
||||||
|
@that_with_context(start_http_server, stop_http_server)
|
||||||
|
def test_httpretty_bypasses_when_disabled(context):
|
||||||
|
"httpretty should bypass all requests by disabling it"
|
||||||
|
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET, "http://localhost:{}/go-for-bubbles/".format(context.http_port),
|
||||||
|
body="glub glub")
|
||||||
|
|
||||||
|
httpretty.disable()
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost:{}/go-for-bubbles/'.format(context.http_port))
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got1).to.equal(
|
||||||
|
b'. o O 0 O o . o O 0 O o . o O 0 O o . o O 0 O o . o O 0 O o .')
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost:{}/come-again/'.format(context.http_port))
|
||||||
|
got2 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got2).to.equal(b'<- HELLO WORLD ->')
|
||||||
|
|
||||||
|
httpretty.enable()
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost:{}/go-for-bubbles/'.format(context.http_port))
|
||||||
|
got3 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got3).to.equal(b'glub glub')
|
||||||
|
core.POTENTIAL_HTTP_PORTS.remove(context.http_port)
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True)
|
||||||
|
@that_with_context(start_http_server, stop_http_server)
|
||||||
|
def test_httpretty_bypasses_a_unregistered_request(context):
|
||||||
|
"httpretty should bypass a unregistered request by disabling it"
|
||||||
|
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET, "http://localhost:{}/go-for-bubbles/".format(context.http_port),
|
||||||
|
body="glub glub")
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost:{}/go-for-bubbles/'.format(context.http_port))
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got1).to.equal(b'glub glub')
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost:{}/come-again/'.format(context.http_port))
|
||||||
|
got2 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got2).to.equal(b'<- HELLO WORLD ->')
|
||||||
|
core.POTENTIAL_HTTP_PORTS.remove(context.http_port)
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(verbose=True)
|
||||||
|
@that_with_context(start_tcp_server, stop_tcp_server)
|
||||||
|
def test_using_httpretty_with_other_tcp_protocols(context):
|
||||||
|
"httpretty should work even when testing code that also use other TCP-based protocols"
|
||||||
|
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET, "http://falcao.it/foo/",
|
||||||
|
body="BAR")
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://falcao.it/foo/')
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got1).to.equal(b'BAR')
|
||||||
|
|
||||||
|
expect(context.client.send("foobar")).to.equal(b"RECEIVED: foobar")
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=False)
|
||||||
|
@that_with_context(start_http_server, stop_http_server)
|
||||||
|
def test_disallow_net_connect_1(context, verbose=True):
|
||||||
|
"""
|
||||||
|
When allow_net_connect = False, a request that otherwise
|
||||||
|
would have worked results in UnmockedError.
|
||||||
|
"""
|
||||||
|
httpretty.register_uri(httpretty.GET, "http://falcao.it/foo/",
|
||||||
|
body="BAR")
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
fd = None
|
||||||
|
try:
|
||||||
|
fd = urllib2.urlopen('http://localhost:{}/go-for-bubbles/'.format(context.http_port))
|
||||||
|
finally:
|
||||||
|
if fd:
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
foo.should.throw(httpretty.UnmockedError)
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=False)
|
||||||
|
def test_disallow_net_connect_2():
|
||||||
|
"""
|
||||||
|
When allow_net_connect = False, a request that would have
|
||||||
|
failed results in UnmockedError.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
fd = None
|
||||||
|
try:
|
||||||
|
fd = urllib2.urlopen('http://example.com/nonsense')
|
||||||
|
finally:
|
||||||
|
if fd:
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
foo.should.throw(httpretty.UnmockedError)
|
||||||
|
|
||||||
|
|
||||||
|
@httpretty.activate(allow_net_connect=False)
|
||||||
|
def test_disallow_net_connect_3():
|
||||||
|
"When allow_net_connect = False, mocked requests still work correctly."
|
||||||
|
|
||||||
|
httpretty.register_uri(httpretty.GET, "http://falcao.it/foo/",
|
||||||
|
body="BAR")
|
||||||
|
fd = urllib2.urlopen('http://falcao.it/foo/')
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
expect(got1).to.equal(b'BAR')
|
|
@ -0,0 +1,91 @@
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
import socket
|
||||||
|
from unittest import skip
|
||||||
|
from sure import scenario, expect
|
||||||
|
from httpretty import httprettified
|
||||||
|
|
||||||
|
|
||||||
|
def create_socket(context):
|
||||||
|
context.sock = socket.socket(
|
||||||
|
socket.AF_INET,
|
||||||
|
socket.SOCK_STREAM,
|
||||||
|
socket.IPPROTO_TCP,
|
||||||
|
)
|
||||||
|
context.sock.is_http = True
|
||||||
|
|
||||||
|
|
||||||
|
@skip('not currently supported')
|
||||||
|
@httprettified
|
||||||
|
@scenario(create_socket)
|
||||||
|
def test_httpretty_debugs_socket_send(context):
|
||||||
|
"HTTPretty should forward_and_trace socket.send"
|
||||||
|
|
||||||
|
expect(context.sock.send).when.called_with(b'data').to.throw(
|
||||||
|
"not connected"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@skip('not currently supported')
|
||||||
|
@httprettified
|
||||||
|
@scenario(create_socket)
|
||||||
|
def test_httpretty_debugs_socket_sendto(context):
|
||||||
|
"HTTPretty should forward_and_trace socket.sendto"
|
||||||
|
|
||||||
|
expect(context.sock.sendto).when.called.to.throw(
|
||||||
|
"not connected"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@skip('not currently supported')
|
||||||
|
@httprettified
|
||||||
|
@scenario(create_socket)
|
||||||
|
def test_httpretty_debugs_socket_recvfrom(context):
|
||||||
|
"HTTPretty should forward_and_trace socket.recvfrom"
|
||||||
|
|
||||||
|
expect(context.sock.recvfrom).when.called.to.throw(
|
||||||
|
"not connected"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@skip('not currently supported')
|
||||||
|
@httprettified
|
||||||
|
@scenario(create_socket)
|
||||||
|
def test_httpretty_debugs_socket_recv_into(context):
|
||||||
|
"HTTPretty should forward_and_trace socket.recv_into"
|
||||||
|
buf = bytearray()
|
||||||
|
expect(context.sock.recv_into).when.called_with(buf).to.throw(
|
||||||
|
"not connected"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@skip('not currently supported')
|
||||||
|
@httprettified
|
||||||
|
@scenario(create_socket)
|
||||||
|
def test_httpretty_debugs_socket_recvfrom_into(context):
|
||||||
|
"HTTPretty should forward_and_trace socket.recvfrom_into"
|
||||||
|
|
||||||
|
expect(context.sock.recvfrom_into).when.called.to.throw(
|
||||||
|
"not connected"
|
||||||
|
)
|
|
@ -0,0 +1,116 @@
|
||||||
|
# coding: utf-8
|
||||||
|
from unittest import TestCase
|
||||||
|
from sure import expect
|
||||||
|
from httpretty import httprettified, HTTPretty
|
||||||
|
|
||||||
|
try:
|
||||||
|
import urllib.request as urllib2
|
||||||
|
except ImportError:
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_decor():
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "http://localhost/",
|
||||||
|
body="glub glub")
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost/')
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got1).to.equal(b'glub glub')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
class DecoratedNonUnitTest(object):
|
||||||
|
|
||||||
|
def test_fail(self):
|
||||||
|
raise AssertionError('Tests in this class should not '
|
||||||
|
'be executed by the test runner.')
|
||||||
|
|
||||||
|
def test_decorated(self):
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "http://localhost/",
|
||||||
|
body="glub glub")
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost/')
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got1).to.equal(b'glub glub')
|
||||||
|
|
||||||
|
|
||||||
|
class NonUnitTestTest(TestCase):
|
||||||
|
"""
|
||||||
|
Checks that the test methods in DecoratedNonUnitTest were decorated.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_decorated(self):
|
||||||
|
DecoratedNonUnitTest().test_decorated()
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
class ClassDecorator(TestCase):
|
||||||
|
|
||||||
|
def test_decorated(self):
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "http://localhost/",
|
||||||
|
body="glub glub")
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost/')
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got1).to.equal(b'glub glub')
|
||||||
|
|
||||||
|
def test_decorated2(self):
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "http://localhost/",
|
||||||
|
body="buble buble")
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost/')
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got1).to.equal(b'buble buble')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
class ClassDecoratorWithSetUp(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "http://localhost/",
|
||||||
|
responses=[
|
||||||
|
HTTPretty.Response("glub glub"),
|
||||||
|
HTTPretty.Response("buble buble"),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_decorated(self):
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost/')
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got1).to.equal(b'glub glub')
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost/')
|
||||||
|
got2 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got2).to.equal(b'buble buble')
|
||||||
|
|
||||||
|
def test_decorated2(self):
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost/')
|
||||||
|
got1 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got1).to.equal(b'glub glub')
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://localhost/')
|
||||||
|
got2 = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
expect(got2).to.equal(b'buble buble')
|
|
@ -0,0 +1,75 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import socket
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSocket(socket.socket):
|
||||||
|
"""
|
||||||
|
Just an editable socket factory
|
||||||
|
It allows mock to patch readonly functions
|
||||||
|
"""
|
||||||
|
connect = sendall = lambda *args, **kw: None
|
||||||
|
|
||||||
|
|
||||||
|
fake_socket_interupter_flag = {}
|
||||||
|
|
||||||
|
|
||||||
|
def recv(flag, size):
|
||||||
|
"""
|
||||||
|
Two pass recv implementation
|
||||||
|
|
||||||
|
This implementation will for the first time send something that is smaller than
|
||||||
|
the asked size passed in argument.
|
||||||
|
Any further call will just raise RuntimeError
|
||||||
|
"""
|
||||||
|
if 'was_here' in flag:
|
||||||
|
raise RuntimeError('Already sent everything')
|
||||||
|
else:
|
||||||
|
flag['was_here'] = None
|
||||||
|
return 'a' * (size - 1)
|
||||||
|
|
||||||
|
|
||||||
|
recv = functools.partial(recv, fake_socket_interupter_flag)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('httpretty.old_socket', new=FakeSocket)
|
||||||
|
def _test_shorten_response():
|
||||||
|
u"HTTPretty shouldn't try to read from server when communication is over"
|
||||||
|
from sure import expect
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
fakesocket = httpretty.fakesock.socket(socket.AF_INET,
|
||||||
|
socket.SOCK_STREAM)
|
||||||
|
with mock.patch.object(fakesocket.truesock, 'recv', recv):
|
||||||
|
fakesocket.connect(('localhost', 80))
|
||||||
|
fakesocket._true_sendall('WHATEVER')
|
||||||
|
expect(fakesocket.fd.read()).to.equal(
|
||||||
|
'a' * (httpretty.socket_buffer_size - 1))
|
|
@ -0,0 +1,306 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import httplib2
|
||||||
|
from freezegun import freeze_time
|
||||||
|
from sure import expect, within, miliseconds
|
||||||
|
from httpretty import HTTPretty, httprettified
|
||||||
|
from httpretty.core import decode_utf8
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_should_mock_a_simple_get_with_httplib2_read(now):
|
||||||
|
"HTTPretty should mock a simple GET with httplib2.context.http"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
_, got = httplib2.Http().request('http://yipit.com', 'GET')
|
||||||
|
expect(got).to.equal(b'Find the best daily deals')
|
||||||
|
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_provides_easy_access_to_querystrings(now):
|
||||||
|
"HTTPretty should provide an easy access to the querystring"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
httplib2.Http().request('http://yipit.com?foo=bar&foo=baz&chuck=norris', 'GET')
|
||||||
|
expect(HTTPretty.last_request.querystring).to.equal({
|
||||||
|
'foo': ['bar', 'baz'],
|
||||||
|
'chuck': ['norris'],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_httpretty_should_mock_headers_httplib2():
|
||||||
|
"HTTPretty should mock basic headers with httplib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
status=201)
|
||||||
|
|
||||||
|
headers, _ = httplib2.Http().request('http://github.com', 'GET')
|
||||||
|
expect(headers['status']).to.equal('201')
|
||||||
|
expect(dict(headers)).to.equal({
|
||||||
|
'content-type': 'text/plain; charset=utf-8',
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': '35',
|
||||||
|
'status': '201',
|
||||||
|
'server': 'Python/HTTPretty',
|
||||||
|
'date': 'Fri, 04 Oct 2013 04:20:00 GMT',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_httpretty_should_allow_adding_and_overwritting_httplib2():
|
||||||
|
"HTTPretty should allow adding and overwritting headers with httplib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
adding_headers={
|
||||||
|
'Server': 'Apache',
|
||||||
|
'Content-Length': '27',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
})
|
||||||
|
|
||||||
|
headers, _ = httplib2.Http().request('http://github.com/foo', 'GET')
|
||||||
|
|
||||||
|
expect(dict(headers)).to.equal({
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'content-location': 'http://github.com/foo',
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': '27',
|
||||||
|
'status': '200',
|
||||||
|
'server': 'Apache',
|
||||||
|
'date': 'Fri, 04 Oct 2013 04:20:00 GMT',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_should_allow_forcing_headers_httplib2(now):
|
||||||
|
"HTTPretty should allow forcing headers with httplib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
forcing_headers={
|
||||||
|
'Content-Type': 'application/xml',
|
||||||
|
})
|
||||||
|
|
||||||
|
headers, _ = httplib2.Http().request('http://github.com/foo', 'GET')
|
||||||
|
|
||||||
|
expect(dict(headers)).to.equal({
|
||||||
|
'content-location': 'http://github.com/foo', # httplib2 FORCES
|
||||||
|
# content-location
|
||||||
|
# even if the
|
||||||
|
# server does not
|
||||||
|
# provide it
|
||||||
|
'content-type': 'application/xml',
|
||||||
|
'status': '200', # httplib2 also ALWAYS put status on headers
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2():
|
||||||
|
"HTTPretty should allow adding and overwritting headers by keyword args " \
|
||||||
|
"with httplib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
server='Apache',
|
||||||
|
content_length='27',
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
headers, _ = httplib2.Http().request('http://github.com/foo', 'GET')
|
||||||
|
|
||||||
|
expect(dict(headers)).to.equal({
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'content-location': 'http://github.com/foo', # httplib2 FORCES
|
||||||
|
# content-location
|
||||||
|
# even if the
|
||||||
|
# server does not
|
||||||
|
# provide it
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': '27',
|
||||||
|
'status': '200',
|
||||||
|
'server': 'Apache',
|
||||||
|
'date': 'Fri, 04 Oct 2013 04:20:00 GMT',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_rotating_responses_with_httplib2(now):
|
||||||
|
"HTTPretty should support rotating responses with httplib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "https://api.yahoo.com/test",
|
||||||
|
responses=[
|
||||||
|
HTTPretty.Response(body="first response", status=201),
|
||||||
|
HTTPretty.Response(body='second and last response', status=202),
|
||||||
|
])
|
||||||
|
|
||||||
|
headers1, body1 = httplib2.Http().request(
|
||||||
|
'https://api.yahoo.com/test', 'GET')
|
||||||
|
|
||||||
|
expect(headers1['status']).to.equal('201')
|
||||||
|
expect(body1).to.equal(b'first response')
|
||||||
|
|
||||||
|
headers2, body2 = httplib2.Http().request(
|
||||||
|
'https://api.yahoo.com/test', 'GET')
|
||||||
|
|
||||||
|
expect(headers2['status']).to.equal('202')
|
||||||
|
expect(body2).to.equal(b'second and last response')
|
||||||
|
|
||||||
|
headers3, body3 = httplib2.Http().request(
|
||||||
|
'https://api.yahoo.com/test', 'GET')
|
||||||
|
|
||||||
|
expect(headers3['status']).to.equal('202')
|
||||||
|
expect(body3).to.equal(b'second and last response')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_can_inspect_last_request(now):
|
||||||
|
"HTTPretty.last_request is a mimetools.Message request from last match"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, "http://api.github.com/",
|
||||||
|
body='{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
headers, body = httplib2.Http().request(
|
||||||
|
'http://api.github.com', 'POST',
|
||||||
|
body='{"username": "gabrielfalcao"}',
|
||||||
|
headers={
|
||||||
|
'content-type': 'text/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('POST')
|
||||||
|
expect(HTTPretty.last_request.body).to.equal(
|
||||||
|
b'{"username": "gabrielfalcao"}',
|
||||||
|
)
|
||||||
|
expect(HTTPretty.last_request.headers['content-type']).to.equal(
|
||||||
|
'text/json',
|
||||||
|
)
|
||||||
|
expect(body).to.equal(b'{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_can_inspect_last_request_with_ssl(now):
|
||||||
|
"HTTPretty.last_request is recorded even when mocking 'https' (SSL)"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, "https://secure.github.com/",
|
||||||
|
body='{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
headers, body = httplib2.Http().request(
|
||||||
|
'https://secure.github.com', 'POST',
|
||||||
|
body='{"username": "gabrielfalcao"}',
|
||||||
|
headers={
|
||||||
|
'content-type': 'text/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('POST')
|
||||||
|
expect(HTTPretty.last_request.body).to.equal(
|
||||||
|
b'{"username": "gabrielfalcao"}',
|
||||||
|
)
|
||||||
|
expect(HTTPretty.last_request.headers['content-type']).to.equal(
|
||||||
|
'text/json',
|
||||||
|
)
|
||||||
|
expect(body).to.equal(b'{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_ignores_querystrings_from_registered_uri(now):
|
||||||
|
"Registering URIs with query string cause them to be ignored"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/?id=123",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
_, got = httplib2.Http().request('http://yipit.com/?id=123', 'GET')
|
||||||
|
|
||||||
|
expect(got).to.equal(b'Find the best daily deals')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/?id=123')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_callback_response(now):
|
||||||
|
("HTTPretty should call a callback function to be set as the body with"
|
||||||
|
" httplib2")
|
||||||
|
|
||||||
|
def request_callback(request, uri, headers):
|
||||||
|
return [200, headers, "The {} response from {}".format(decode_utf8(request.method), uri)]
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "https://api.yahoo.com/test",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
headers1, body1 = httplib2.Http().request(
|
||||||
|
'https://api.yahoo.com/test', 'GET')
|
||||||
|
|
||||||
|
expect(body1).to.equal(b"The GET response from https://api.yahoo.com/test")
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST, "https://api.yahoo.com/test_post",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
headers2, body2 = httplib2.Http().request(
|
||||||
|
'https://api.yahoo.com/test_post', 'POST')
|
||||||
|
|
||||||
|
expect(body2).to.equal(b"The POST response from https://api.yahoo.com/test_post")
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_allow_registering_regexes():
|
||||||
|
"HTTPretty should allow registering regexes with httplib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
'http://api.yipit.com/v1/deal;brand=gap',
|
||||||
|
body="Found brand",
|
||||||
|
)
|
||||||
|
|
||||||
|
response, body = httplib2.Http().request('http://api.yipit.com/v1/deal;brand=gap', 'GET')
|
||||||
|
expect(body).to.equal(b'Found brand')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap')
|
|
@ -0,0 +1,78 @@
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
import requests
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
from sure import expect
|
||||||
|
|
||||||
|
|
||||||
|
def http():
|
||||||
|
sess = requests.Session()
|
||||||
|
adapter = requests.adapters.HTTPAdapter(pool_connections=1, pool_maxsize=1)
|
||||||
|
sess.mount('http://', adapter)
|
||||||
|
sess.mount('https://', adapter)
|
||||||
|
return sess
|
||||||
|
|
||||||
|
|
||||||
|
def test_http_passthrough():
|
||||||
|
url = 'http://httpbin.org/status/200'
|
||||||
|
response1 = http().get(url)
|
||||||
|
|
||||||
|
response1 = http().get(url)
|
||||||
|
|
||||||
|
httpretty.enable(allow_net_connect=False, verbose=True)
|
||||||
|
httpretty.register_uri(httpretty.GET, 'http://google.com/', body="Not Google")
|
||||||
|
httpretty.register_uri(httpretty.GET, url, body="mocked")
|
||||||
|
|
||||||
|
response2 = http().get('http://google.com/')
|
||||||
|
expect(response2.content).to.equal(b'Not Google')
|
||||||
|
|
||||||
|
response3 = http().get(url)
|
||||||
|
response3.content.should.equal(b"mocked")
|
||||||
|
|
||||||
|
httpretty.disable()
|
||||||
|
|
||||||
|
response4 = http().get(url)
|
||||||
|
(response4.content).should.equal(response1.content)
|
||||||
|
|
||||||
|
|
||||||
|
def test_https_passthrough():
|
||||||
|
url = 'https://httpbin.org/status/200'
|
||||||
|
|
||||||
|
response1 = http().get(url)
|
||||||
|
|
||||||
|
httpretty.enable(allow_net_connect=False, verbose=True)
|
||||||
|
httpretty.register_uri(httpretty.GET, 'https://google.com/', body="Not Google")
|
||||||
|
httpretty.register_uri(httpretty.GET, url, body="mocked")
|
||||||
|
|
||||||
|
response2 = http().get('https://google.com/')
|
||||||
|
expect(response2.content).to.equal(b'Not Google')
|
||||||
|
|
||||||
|
response3 = http().get(url)
|
||||||
|
(response3.text).should.equal('mocked')
|
||||||
|
|
||||||
|
httpretty.disable()
|
||||||
|
|
||||||
|
response4 = http().get(url)
|
||||||
|
(response4.content).should.equal(response1.content)
|
|
@ -0,0 +1,949 @@
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import signal
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
from freezegun import freeze_time
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from sure import within, miliseconds, expect
|
||||||
|
from tornado import version as tornado_version
|
||||||
|
from httpretty import HTTPretty, httprettified
|
||||||
|
from httpretty.core import decode_utf8
|
||||||
|
|
||||||
|
from tests.functional.base import FIXTURE_FILE, use_tornado_server
|
||||||
|
|
||||||
|
from tests.compat import Mock
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
advance_iterator = next
|
||||||
|
except NameError:
|
||||||
|
def advance_iterator(it):
|
||||||
|
return it.next()
|
||||||
|
|
||||||
|
|
||||||
|
next = advance_iterator
|
||||||
|
|
||||||
|
server_url = lambda path, port: "http://localhost:{}/{}".format(port, path.lstrip('/'))
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_should_mock_a_simple_get_with_requests_read(now):
|
||||||
|
"HTTPretty should mock a simple GET with requests.get"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
response = requests.get('http://yipit.com')
|
||||||
|
expect(response.text).to.equal('Find the best daily deals')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_hostname_case_insensitive(now):
|
||||||
|
"HTTPretty should match the hostname case insensitive"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit/",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
response = requests.get('http://YIPIT')
|
||||||
|
expect(response.text).to.equal('Find the best daily deals')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_provides_easy_access_to_querystrings(now):
|
||||||
|
"HTTPretty should provide an easy access to the querystring"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
requests.get('http://yipit.com/?foo=bar&foo=baz&chuck=norris')
|
||||||
|
expect(HTTPretty.last_request.querystring).to.equal({
|
||||||
|
'foo': ['bar', 'baz'],
|
||||||
|
'chuck': ['norris'],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_httpretty_should_mock_headers_requests():
|
||||||
|
"HTTPretty should mock basic headers with requests"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
status=201)
|
||||||
|
|
||||||
|
response = requests.get('http://github.com')
|
||||||
|
expect(response.status_code).to.equal(201)
|
||||||
|
|
||||||
|
expect(dict(response.headers)).to.equal({
|
||||||
|
'content-type': 'text/plain; charset=utf-8',
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': '35',
|
||||||
|
'status': '201',
|
||||||
|
'server': 'Python/HTTPretty',
|
||||||
|
'date': 'Fri, 04 Oct 2013 04:20:00 GMT',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_httpretty_should_allow_adding_and_overwritting_requests():
|
||||||
|
"HTTPretty should allow adding and overwritting headers with requests"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
adding_headers={
|
||||||
|
'Server': 'Apache',
|
||||||
|
'Content-Length': '27',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
})
|
||||||
|
|
||||||
|
response = requests.get('http://github.com/foo')
|
||||||
|
|
||||||
|
expect(dict(response.headers)).to.equal({
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': '27',
|
||||||
|
'status': '200',
|
||||||
|
'server': 'Apache',
|
||||||
|
'date': 'Fri, 04 Oct 2013 04:20:00 GMT',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_should_allow_forcing_headers_requests(now):
|
||||||
|
"HTTPretty should allow forcing headers with requests"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo",
|
||||||
|
body="<root><baz /</root>",
|
||||||
|
forcing_headers={
|
||||||
|
'Content-Type': 'application/xml',
|
||||||
|
'Content-Length': '19',
|
||||||
|
})
|
||||||
|
|
||||||
|
response = requests.get('http://github.com/foo')
|
||||||
|
|
||||||
|
expect(dict(response.headers)).to.equal({
|
||||||
|
'content-type': 'application/xml',
|
||||||
|
'content-length': '19',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2():
|
||||||
|
"HTTPretty should allow adding and overwritting headers by keyword args " \
|
||||||
|
"with requests"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
server='Apache',
|
||||||
|
content_length='27',
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
response = requests.get('http://github.com/foo')
|
||||||
|
|
||||||
|
expect(dict(response.headers)).to.equal({
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': '27',
|
||||||
|
'status': '200',
|
||||||
|
'server': 'Apache',
|
||||||
|
'date': 'Fri, 04 Oct 2013 04:20:00 GMT',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_rotating_responses_with_requests(now):
|
||||||
|
"HTTPretty should support rotating responses with requests"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "https://api.yahoo.com/test",
|
||||||
|
responses=[
|
||||||
|
HTTPretty.Response(body=b"first response", status=201),
|
||||||
|
HTTPretty.Response(body=b'second and last response', status=202),
|
||||||
|
])
|
||||||
|
|
||||||
|
response1 = requests.get(
|
||||||
|
'https://api.yahoo.com/test')
|
||||||
|
|
||||||
|
expect(response1.status_code).to.equal(201)
|
||||||
|
expect(response1.text).to.equal('first response')
|
||||||
|
|
||||||
|
response2 = requests.get(
|
||||||
|
'https://api.yahoo.com/test')
|
||||||
|
|
||||||
|
expect(response2.status_code).to.equal(202)
|
||||||
|
expect(response2.text).to.equal('second and last response')
|
||||||
|
|
||||||
|
response3 = requests.get(
|
||||||
|
'https://api.yahoo.com/test')
|
||||||
|
|
||||||
|
expect(response3.status_code).to.equal(202)
|
||||||
|
expect(response3.text).to.equal('second and last response')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_can_inspect_last_request(now):
|
||||||
|
"HTTPretty.last_request is a mimetools.Message request from last match"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, "http://api.github.com/",
|
||||||
|
body='{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
'http://api.github.com',
|
||||||
|
'{"username": "gabrielfalcao"}',
|
||||||
|
headers={
|
||||||
|
'content-type': 'text/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('POST')
|
||||||
|
expect(HTTPretty.last_request.body).to.equal(
|
||||||
|
b'{"username": "gabrielfalcao"}',
|
||||||
|
)
|
||||||
|
expect(HTTPretty.last_request.headers['content-type']).to.equal(
|
||||||
|
'text/json',
|
||||||
|
)
|
||||||
|
expect(response.json()).to.equal({"repositories": ["HTTPretty", "lettuce"]})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_can_inspect_last_request_with_ssl(now):
|
||||||
|
"HTTPretty.last_request is recorded even when mocking 'https' (SSL)"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, "https://secure.github.com/",
|
||||||
|
body='{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
'https://secure.github.com',
|
||||||
|
'{"username": "gabrielfalcao"}',
|
||||||
|
headers={
|
||||||
|
'content-type': 'text/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('POST')
|
||||||
|
expect(HTTPretty.last_request.body).to.equal(
|
||||||
|
b'{"username": "gabrielfalcao"}',
|
||||||
|
)
|
||||||
|
expect(HTTPretty.last_request.headers['content-type']).to.equal(
|
||||||
|
'text/json',
|
||||||
|
)
|
||||||
|
expect(response.json()).to.equal({"repositories": ["HTTPretty", "lettuce"]})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_ignores_querystrings_from_registered_uri(now):
|
||||||
|
"HTTPretty should ignore querystrings from the registered uri (requests library)"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/?id=123",
|
||||||
|
body=b"Find the best daily deals")
|
||||||
|
|
||||||
|
response = requests.get('http://yipit.com/', params={'id': 123})
|
||||||
|
expect(response.text).to.equal('Find the best daily deals')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/?id=123')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(five=miliseconds)
|
||||||
|
def test_streaming_responses(now):
|
||||||
|
"""
|
||||||
|
Mock a streaming HTTP response, like those returned by the Twitter streaming
|
||||||
|
API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def in_time(time, message):
|
||||||
|
"""
|
||||||
|
A context manager that uses signals to force a time limit in tests
|
||||||
|
(unlike the `@within` decorator, which only complains afterward), or
|
||||||
|
raise an AssertionError.
|
||||||
|
"""
|
||||||
|
def handler(signum, frame):
|
||||||
|
raise AssertionError(message)
|
||||||
|
signal.signal(signal.SIGALRM, handler)
|
||||||
|
signal.setitimer(signal.ITIMER_REAL, time)
|
||||||
|
yield
|
||||||
|
signal.setitimer(signal.ITIMER_REAL, 0)
|
||||||
|
|
||||||
|
# XXX this obviously isn't a fully functional twitter streaming client!
|
||||||
|
twitter_response_lines = [
|
||||||
|
b'{"text":"If \\"for the boobs\\" requests to follow me one more time I\'m calling the police. http://t.co/a0mDEAD8"}\r\n',
|
||||||
|
b'\r\n',
|
||||||
|
b'{"text":"RT @onedirection: Thanks for all your # FollowMe1D requests Directioners! We\u2019ll be following 10 people throughout the day starting NOW. G ..."}\r\n'
|
||||||
|
]
|
||||||
|
|
||||||
|
TWITTER_STREAMING_URL = "https://stream.twitter.com/1/statuses/filter.json"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, TWITTER_STREAMING_URL,
|
||||||
|
body=(l for l in twitter_response_lines),
|
||||||
|
streaming=True)
|
||||||
|
|
||||||
|
# taken from the requests docs
|
||||||
|
|
||||||
|
# test iterating by line
|
||||||
|
# Http://docs.python-requests.org/en/latest/user/advanced/# streaming-requests
|
||||||
|
response = requests.post(TWITTER_STREAMING_URL, data={'track': 'requests'},
|
||||||
|
auth=('username', 'password'), stream=True)
|
||||||
|
|
||||||
|
line_iter = response.iter_lines()
|
||||||
|
with in_time(0.01, 'Iterating by line is taking forever!'):
|
||||||
|
for i in range(len(twitter_response_lines)):
|
||||||
|
expect(next(line_iter).strip()).to.equal(
|
||||||
|
twitter_response_lines[i].strip())
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, TWITTER_STREAMING_URL,
|
||||||
|
body=(l for l in twitter_response_lines),
|
||||||
|
streaming=True)
|
||||||
|
# test iterating by line after a second request
|
||||||
|
response = requests.post(
|
||||||
|
TWITTER_STREAMING_URL,
|
||||||
|
data={
|
||||||
|
'track': 'requests'
|
||||||
|
},
|
||||||
|
auth=('username', 'password'),
|
||||||
|
stream=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
line_iter = response.iter_lines()
|
||||||
|
with in_time(0.01, 'Iterating by line is taking forever the second time '
|
||||||
|
'around!'):
|
||||||
|
for i in range(len(twitter_response_lines)):
|
||||||
|
expect(next(line_iter).strip()).to.equal(
|
||||||
|
twitter_response_lines[i].strip())
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, TWITTER_STREAMING_URL,
|
||||||
|
body=(l for l in twitter_response_lines),
|
||||||
|
streaming=True)
|
||||||
|
# test iterating by char
|
||||||
|
response = requests.post(
|
||||||
|
TWITTER_STREAMING_URL,
|
||||||
|
data={
|
||||||
|
'track': 'requests'
|
||||||
|
},
|
||||||
|
auth=('username', 'password'),
|
||||||
|
stream=True
|
||||||
|
)
|
||||||
|
|
||||||
|
twitter_expected_response_body = b''.join(twitter_response_lines)
|
||||||
|
with in_time(0.02, 'Iterating by char is taking forever!'):
|
||||||
|
twitter_body = b''.join(c for c in response.iter_content(chunk_size=1))
|
||||||
|
|
||||||
|
expect(twitter_body).to.equal(twitter_expected_response_body)
|
||||||
|
|
||||||
|
# test iterating by chunks larger than the stream
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, TWITTER_STREAMING_URL,
|
||||||
|
body=(l for l in twitter_response_lines),
|
||||||
|
streaming=True)
|
||||||
|
response = requests.post(TWITTER_STREAMING_URL, data={'track': 'requests'},
|
||||||
|
auth=('username', 'password'), stream=True)
|
||||||
|
|
||||||
|
with in_time(0.02, 'Iterating by large chunks is taking forever!'):
|
||||||
|
twitter_body = b''.join(c for c in
|
||||||
|
response.iter_content(chunk_size=1024))
|
||||||
|
|
||||||
|
expect(twitter_body).to.equal(twitter_expected_response_body)
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_multiline():
|
||||||
|
url = 'https://httpbin.org/post'
|
||||||
|
data = b'content=Im\r\na multiline\r\n\r\nsentence\r\n'
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||||
|
'Accept': 'text/plain',
|
||||||
|
}
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST,
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
response = requests.post(url, data=data, headers=headers)
|
||||||
|
|
||||||
|
expect(response.status_code).to.equal(200)
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('POST')
|
||||||
|
expect(HTTPretty.last_request.url).to.equal('https://httpbin.org/post')
|
||||||
|
expect(HTTPretty.last_request.protocol).to.equal('https')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/post')
|
||||||
|
expect(HTTPretty.last_request.body).to.equal(data)
|
||||||
|
expect(HTTPretty.last_request.headers['content-length']).to.equal('37')
|
||||||
|
expect(HTTPretty.last_request.headers['content-type']).to.equal('application/x-www-form-urlencoded; charset=utf-8')
|
||||||
|
expect(len(HTTPretty.latest_requests)).to.equal(2)
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_octet_stream():
|
||||||
|
url = 'https://httpbin.org/post'
|
||||||
|
data = b"\xf5\x00\x00\x00" # utf-8 with invalid start byte
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
}
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST,
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
response = requests.post(url, data=data, headers=headers)
|
||||||
|
|
||||||
|
expect(response.status_code).to.equal(200)
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('POST')
|
||||||
|
expect(HTTPretty.last_request.url).to.equal('https://httpbin.org/post')
|
||||||
|
expect(HTTPretty.last_request.protocol).to.equal('https')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/post')
|
||||||
|
expect(HTTPretty.last_request.body).to.equal(data)
|
||||||
|
expect(HTTPretty.last_request.headers['content-length']).to.equal('4')
|
||||||
|
expect(HTTPretty.last_request.headers['content-type']).to.equal('application/octet-stream')
|
||||||
|
expect(len(HTTPretty.latest_requests)).to.equal(2)
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_multipart():
|
||||||
|
url = 'https://httpbin.org/post'
|
||||||
|
data = b'--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="content"\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 68\r\n\r\nAction: comment\nText: Comment with attach\nAttachment: x1.txt, x2.txt\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_2"; filename="x.txt"\r\nContent-Type: text/plain\r\nContent-Length: 4\r\n\r\nbye\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_1"; filename="x.txt"\r\nContent-Type: text/plain\r\nContent-Length: 4\r\n\r\nbye\n\r\n--xXXxXXyYYzzz--\r\n'
|
||||||
|
headers = {'Content-Length': '495', 'Content-Type': 'multipart/form-data; boundary=xXXxXXyYYzzz', 'Accept': 'text/plain'}
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST,
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
response = requests.post(url, data=data, headers=headers)
|
||||||
|
expect(response.status_code).to.equal(200)
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('POST')
|
||||||
|
expect(HTTPretty.last_request.url).to.equal('https://httpbin.org/post')
|
||||||
|
expect(HTTPretty.last_request.protocol).to.equal('https')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/post')
|
||||||
|
expect(HTTPretty.last_request.body).to.equal(data)
|
||||||
|
expect(HTTPretty.last_request.headers['content-length']).to.equal('495')
|
||||||
|
expect(HTTPretty.last_request.headers['content-type']).to.equal('multipart/form-data; boundary=xXXxXXyYYzzz')
|
||||||
|
expect(len(HTTPretty.latest_requests)).to.equal(2)
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_callback_response(now):
|
||||||
|
("HTTPretty should call a callback function and set its return value as the body of the response"
|
||||||
|
" requests")
|
||||||
|
|
||||||
|
def request_callback(request, uri, headers):
|
||||||
|
return [200, headers, "The {} response from {}".format(decode_utf8(request.method), uri)]
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "https://api.yahoo.com/test",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
response = requests.get('https://api.yahoo.com/test')
|
||||||
|
|
||||||
|
expect(response.text).to.equal("The GET response from https://api.yahoo.com/test")
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST, "https://api.yahoo.com/test_post",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
"https://api.yahoo.com/test_post",
|
||||||
|
{"username": "gabrielfalcao"}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.text).to.equal("The POST response from https://api.yahoo.com/test_post")
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_callback_body_remains_callable_for_any_subsequent_requests(now):
|
||||||
|
("HTTPretty should call a callback function more than one"
|
||||||
|
" requests")
|
||||||
|
|
||||||
|
def request_callback(request, uri, headers):
|
||||||
|
return [200, headers, "The {} response from {}".format(decode_utf8(request.method), uri)]
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "https://api.yahoo.com/test",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
response = requests.get('https://api.yahoo.com/test')
|
||||||
|
expect(response.text).to.equal("The GET response from https://api.yahoo.com/test")
|
||||||
|
|
||||||
|
response = requests.get('https://api.yahoo.com/test')
|
||||||
|
expect(response.text).to.equal("The GET response from https://api.yahoo.com/test")
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_callback_setting_headers_and_status_response(now):
|
||||||
|
("HTTPretty should call a callback function and uses it retur tuple as status code, headers and body"
|
||||||
|
" requests")
|
||||||
|
|
||||||
|
def request_callback(request, uri, headers):
|
||||||
|
headers.update({'a': 'b'})
|
||||||
|
return [418, headers, "The {} response from {}".format(decode_utf8(request.method), uri)]
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "https://api.yahoo.com/test",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
response = requests.get('https://api.yahoo.com/test')
|
||||||
|
expect(response.text).to.equal("The GET response from https://api.yahoo.com/test")
|
||||||
|
expect(response.headers).to.have.key('a').being.equal("b")
|
||||||
|
expect(response.status_code).to.equal(418)
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST, "https://api.yahoo.com/test_post",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
"https://api.yahoo.com/test_post",
|
||||||
|
{"username": "gabrielfalcao"}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.text).to.equal("The POST response from https://api.yahoo.com/test_post")
|
||||||
|
expect(response.headers).to.have.key('a').being.equal("b")
|
||||||
|
expect(response.status_code).to.equal(418)
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_respect_matcher_priority():
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r".*"),
|
||||||
|
body='high priority',
|
||||||
|
priority=5,
|
||||||
|
)
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r".+"),
|
||||||
|
body='low priority',
|
||||||
|
priority=0,
|
||||||
|
)
|
||||||
|
response = requests.get('http://api.yipit.com/v1/')
|
||||||
|
expect(response.text).to.equal('high priority')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_callback_setting_content_length_on_head(now):
|
||||||
|
("HTTPretty should call a callback function, use it's return tuple as status code, headers and body"
|
||||||
|
" requests and respect the content-length header when responding to HEAD")
|
||||||
|
|
||||||
|
def request_callback(request, uri, headers):
|
||||||
|
headers.update({'content-length': 12345})
|
||||||
|
return [200, headers, ""]
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.HEAD, "https://api.yahoo.com/test",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
response = requests.head('https://api.yahoo.com/test')
|
||||||
|
expect(response.headers).to.have.key('content-length').being.equal("12345")
|
||||||
|
expect(response.status_code).to.equal(200)
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_allow_registering_regexes_and_give_a_proper_match_to_the_callback():
|
||||||
|
"HTTPretty should allow registering regexes with requests and giva a proper match to the callback"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r"https://api.yipit.com/v1/deal;brand=(?P<brand_name>\w+)"),
|
||||||
|
body=lambda method, uri, headers: [200, headers, uri]
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('https://api.yipit.com/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
|
||||||
|
expect(response.text).to.equal('https://api.yipit.com/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_allow_registering_regexes():
|
||||||
|
"HTTPretty should allow registering regexes with requests"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r"https://api.yipit.com/v1/deal;brand=(?P<brand_name>\w+)"),
|
||||||
|
body="Found brand",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('https://api.yipit.com/v1/deal;brand=gap?first_name=chuck&last_name=norris'
|
||||||
|
)
|
||||||
|
expect(response.text).to.equal('Found brand')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_provides_easy_access_to_querystrings_with_regexes():
|
||||||
|
"HTTPretty should match regexes even if they have a different querystring"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r"https://api.yipit.com/v1/(?P<endpoint>\w+)/$"),
|
||||||
|
body="Find the best daily deals"
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('https://api.yipit.com/v1/deals/?foo=bar&foo=baz&chuck=norris')
|
||||||
|
expect(response.text).to.equal("Find the best daily deals")
|
||||||
|
expect(HTTPretty.last_request.querystring).to.equal({
|
||||||
|
'foo': ['bar', 'baz'],
|
||||||
|
'chuck': ['norris'],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified(verbose=True)
|
||||||
|
def test_httpretty_allows_to_chose_if_querystring_should_be_matched():
|
||||||
|
"HTTPretty should provide a way to not match regexes that have a different querystring"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
"http://localhost:9090",
|
||||||
|
)
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r"http://localhost:9090/what/?$"),
|
||||||
|
body="Nudge, nudge, wink, wink. Know what I mean?",
|
||||||
|
match_querystring=True
|
||||||
|
)
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r"http://localhost:9090/what.*[?]?.*"),
|
||||||
|
body="Different",
|
||||||
|
match_querystring=False
|
||||||
|
)
|
||||||
|
response = requests.get('http://localhost:9090/what/')
|
||||||
|
expect(response.text).to.equal('Nudge, nudge, wink, wink. Know what I mean?')
|
||||||
|
|
||||||
|
response = requests.get('http://localhost:9090/what/', params={'flying': 'coconuts'})
|
||||||
|
expect(response.text).to.not_be.equal('Nudge, nudge, wink, wink. Know what I mean?')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_allow_multiple_methods_for_the_same_uri():
|
||||||
|
"HTTPretty should allow registering multiple methods for the same uri"
|
||||||
|
|
||||||
|
url = 'http://test.com/test'
|
||||||
|
methods = ['GET', 'POST', 'PUT', 'OPTIONS']
|
||||||
|
for method in methods:
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
getattr(HTTPretty, method),
|
||||||
|
url,
|
||||||
|
method
|
||||||
|
)
|
||||||
|
|
||||||
|
for method in methods:
|
||||||
|
request_action = getattr(requests, method.lower())
|
||||||
|
expect(request_action(url).text).to.equal(method)
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_allow_registering_regexes_with_streaming_responses():
|
||||||
|
"HTTPretty should allow registering regexes with streaming responses"
|
||||||
|
|
||||||
|
os.environ['DEBUG'] = 'true'
|
||||||
|
|
||||||
|
def my_callback(request, url, headers):
|
||||||
|
request.body.should.equal(b'hithere')
|
||||||
|
return 200, headers, "Received"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST,
|
||||||
|
re.compile(r"https://api.yipit.com/v1/deal;brand=(?P<brand_name>\w+)"),
|
||||||
|
body=my_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
def gen():
|
||||||
|
yield b'hi'
|
||||||
|
yield b'there'
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
'https://api.yipit.com/v1/deal;brand=gap?first_name=chuck&last_name=norris',
|
||||||
|
data=gen(),
|
||||||
|
)
|
||||||
|
expect(response.content).to.equal(b"Received")
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('POST')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_allow_multiple_responses_with_multiple_methods():
|
||||||
|
"HTTPretty should allow multiple responses when binding multiple methods to the same uri"
|
||||||
|
|
||||||
|
url = 'http://test.com/list'
|
||||||
|
|
||||||
|
# add get responses
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, url,
|
||||||
|
responses=[
|
||||||
|
HTTPretty.Response(body='a'),
|
||||||
|
HTTPretty.Response(body='b'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# add post responses
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST, url,
|
||||||
|
responses=[
|
||||||
|
HTTPretty.Response(body='c'),
|
||||||
|
HTTPretty.Response(body='d'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(requests.get(url).text).to.equal('a')
|
||||||
|
expect(requests.post(url).text).to.equal('c')
|
||||||
|
|
||||||
|
expect(requests.get(url).text).to.equal('b')
|
||||||
|
expect(requests.get(url).text).to.equal('b')
|
||||||
|
expect(requests.get(url).text).to.equal('b')
|
||||||
|
|
||||||
|
expect(requests.post(url).text).to.equal('d')
|
||||||
|
expect(requests.post(url).text).to.equal('d')
|
||||||
|
expect(requests.post(url).text).to.equal('d')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_normalize_url_patching():
|
||||||
|
"HTTPretty should normalize all url patching"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
"http://yipit.com/foo(bar)",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
response = requests.get('http://yipit.com/foo%28bar%29')
|
||||||
|
expect(response.text).to.equal('Find the best daily deals')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_lack_of_trailing_slash():
|
||||||
|
("HTTPretty should automatically append a slash to given urls")
|
||||||
|
url = 'http://www.youtube.com'
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, url, body='')
|
||||||
|
response = requests.get(url)
|
||||||
|
response.status_code.should.equal(200)
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_unicode_querystrings():
|
||||||
|
("Querystrings should accept unicode characters")
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/login",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
requests.get('http://yipit.com/login?user=Gabriel+Falcão')
|
||||||
|
expect(HTTPretty.last_request.querystring['user'][0]).should.be.equal('Gabriel Falcão')
|
||||||
|
|
||||||
|
|
||||||
|
@use_tornado_server
|
||||||
|
def test_recording_calls(port):
|
||||||
|
("HTTPretty should be able to record calls")
|
||||||
|
# Given a destination path:
|
||||||
|
destination = FIXTURE_FILE("recording-1.json")
|
||||||
|
|
||||||
|
# When I record some calls
|
||||||
|
with HTTPretty.record(destination):
|
||||||
|
requests.get(server_url("/foobar?name=Gabriel&age=25", port))
|
||||||
|
requests.post(server_url("/foobar", port),
|
||||||
|
data=json.dumps({'test': '123'}),
|
||||||
|
headers={"Test": "foobar"})
|
||||||
|
|
||||||
|
# Then the destination path should exist
|
||||||
|
os.path.exists(destination).should.be.true
|
||||||
|
|
||||||
|
# And the contents should be json
|
||||||
|
raw = open(destination).read()
|
||||||
|
json.loads.when.called_with(raw).should_not.throw(ValueError)
|
||||||
|
|
||||||
|
# And the contents should be expected
|
||||||
|
data = json.loads(raw)
|
||||||
|
data.should.be.a(list)
|
||||||
|
data.should.have.length_of(2)
|
||||||
|
|
||||||
|
# And the responses should have the expected keys
|
||||||
|
response = data[0]
|
||||||
|
response.should.have.key("request").being.length_of(5)
|
||||||
|
response.should.have.key("response").being.length_of(3)
|
||||||
|
|
||||||
|
response['request'].should.have.key("method").being.equal("GET")
|
||||||
|
response['request'].should.have.key("headers").being.a(dict)
|
||||||
|
response['request'].should.have.key("querystring").being.equal({
|
||||||
|
"age": [
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"name": [
|
||||||
|
"Gabriel"
|
||||||
|
]
|
||||||
|
})
|
||||||
|
response['response'].should.have.key("status").being.equal(200)
|
||||||
|
response['response'].should.have.key("body").being.an(str)
|
||||||
|
response['response'].should.have.key("headers").being.a(dict)
|
||||||
|
# older urllib3 had a bug where header keys were lower-cased:
|
||||||
|
# https://github.com/shazow/urllib3/issues/236
|
||||||
|
# cope with that
|
||||||
|
if 'server' in response['response']["headers"]:
|
||||||
|
response['response']["headers"]["Server"] = response['response']["headers"].pop("server")
|
||||||
|
response['response']["headers"].should.have.key("Server").being.equal("TornadoServer/" + tornado_version)
|
||||||
|
|
||||||
|
# And When I playback the previously recorded calls
|
||||||
|
with HTTPretty.playback(destination):
|
||||||
|
# And make the expected requests
|
||||||
|
response1 = requests.get(server_url("/foobar?name=Gabriel&age=25", port))
|
||||||
|
response2 = requests.post(
|
||||||
|
server_url("/foobar", port),
|
||||||
|
data=json.dumps({'test': '123'}),
|
||||||
|
headers={"Test": "foobar"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Then the responses should be the expected
|
||||||
|
response1.json().should.equal({"foobar": {"age": "25", "name": "Gabriel"}})
|
||||||
|
response2.json()["foobar"].should.equal({})
|
||||||
|
response2.json()["req_body"].should.equal(json.dumps({"test": "123"}))
|
||||||
|
response2.json()["req_headers"].should.have.key("Test")
|
||||||
|
response2.json()["req_headers"]["Test"].should.equal("foobar")
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_py26_callback_response():
|
||||||
|
("HTTPretty should call a callback function *once* and set its return value"
|
||||||
|
" as the body of the response requests")
|
||||||
|
|
||||||
|
def _request_callback(request, uri, headers):
|
||||||
|
return [200, headers, "The {} response from {}".format(decode_utf8(request.method), uri)]
|
||||||
|
|
||||||
|
request_callback = Mock()
|
||||||
|
request_callback.side_effect = _request_callback
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST, "https://api.yahoo.com/test_post",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
requests.post(
|
||||||
|
"https://api.yahoo.com/test_post",
|
||||||
|
{"username": "gabrielfalcao"}
|
||||||
|
)
|
||||||
|
os.environ['STOP'] = 'true'
|
||||||
|
expect(request_callback.call_count).equal(1)
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_work_with_non_standard_ports():
|
||||||
|
"HTTPretty should work with a non-standard port number"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r"https://api.yipit.com:1234/v1/deal;brand=(?P<brand_name>\w+)"),
|
||||||
|
body=lambda method, uri, headers: [200, headers, uri]
|
||||||
|
)
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST,
|
||||||
|
"https://asdf.com:666/meow",
|
||||||
|
body=lambda method, uri, headers: [200, headers, uri]
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('https://api.yipit.com:1234/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
|
||||||
|
expect(response.text).to.equal('https://api.yipit.com:1234/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
|
||||||
|
response = requests.post('https://asdf.com:666/meow')
|
||||||
|
|
||||||
|
expect(response.text).to.equal('https://asdf.com:666/meow')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('POST')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/meow')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_reset_by_switching_protocols_for_same_port():
|
||||||
|
"HTTPretty should reset protocol/port associations"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
"http://api.yipit.com:1234/v1/deal",
|
||||||
|
body=lambda method, uri, headers: [200, headers, uri]
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('http://api.yipit.com:1234/v1/deal')
|
||||||
|
|
||||||
|
expect(response.text).to.equal('http://api.yipit.com:1234/v1/deal')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/v1/deal')
|
||||||
|
|
||||||
|
HTTPretty.reset()
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
"https://api.yipit.com:1234/v1/deal",
|
||||||
|
body=lambda method, uri, headers: [200, headers, uri]
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('https://api.yipit.com:1234/v1/deal')
|
||||||
|
|
||||||
|
expect(response.text).to.equal('https://api.yipit.com:1234/v1/deal')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/v1/deal')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_allow_registering_regexes_with_port_and_give_a_proper_match_to_the_callback():
|
||||||
|
"HTTPretty should allow registering regexes with requests and giva a proper match to the callback"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r"https://api.yipit.com:1234/v1/deal;brand=(?P<brand_name>\w+)"),
|
||||||
|
body=lambda method, uri, headers: [200, headers, uri]
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('https://api.yipit.com:1234/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
|
||||||
|
expect(response.text).to.equal('https://api.yipit.com:1234/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap?first_name=chuck&last_name=norris')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_handle_paths_starting_with_two_slashes():
|
||||||
|
"HTTPretty should handle URLs with paths starting with //"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "http://example.com//foo",
|
||||||
|
body="Find the best foo"
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.get('http://example.com//foo')
|
||||||
|
expect(response.text).to.equal('Find the best foo')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('//foo')
|
|
@ -0,0 +1,341 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
try:
|
||||||
|
from urllib.request import urlopen
|
||||||
|
import urllib.request as urllib2
|
||||||
|
except ImportError:
|
||||||
|
import urllib2
|
||||||
|
urlopen = urllib2.urlopen
|
||||||
|
|
||||||
|
from freezegun import freeze_time
|
||||||
|
from sure import within, miliseconds
|
||||||
|
from httpretty import HTTPretty, httprettified
|
||||||
|
from httpretty.core import decode_utf8
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_should_mock_a_simple_get_with_urllib2_read():
|
||||||
|
"HTTPretty should mock a simple GET with urllib2.read()"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
fd = urlopen('http://yipit.com')
|
||||||
|
got = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
got.should.equal(b'Find the best daily deals')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_provides_easy_access_to_querystrings(now):
|
||||||
|
"HTTPretty should provide an easy access to the querystring"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('http://yipit.com/?foo=bar&foo=baz&chuck=norris')
|
||||||
|
fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
HTTPretty.last_request.querystring.should.equal({
|
||||||
|
'foo': ['bar', 'baz'],
|
||||||
|
'chuck': ['norris'],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_httpretty_should_mock_headers_urllib2():
|
||||||
|
"HTTPretty should mock basic headers with urllib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
status=201)
|
||||||
|
|
||||||
|
request = urlopen('http://github.com')
|
||||||
|
|
||||||
|
headers = dict(request.headers)
|
||||||
|
request.close()
|
||||||
|
|
||||||
|
request.code.should.equal(201)
|
||||||
|
headers.should.equal({
|
||||||
|
'content-type': 'text/plain; charset=utf-8',
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': '35',
|
||||||
|
'status': '201',
|
||||||
|
'server': 'Python/HTTPretty',
|
||||||
|
'date': 'Fri, 04 Oct 2013 04:20:00 GMT',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_httpretty_should_allow_adding_and_overwritting_urllib2():
|
||||||
|
"HTTPretty should allow adding and overwritting headers with urllib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
adding_headers={
|
||||||
|
'Server': 'Apache',
|
||||||
|
'Content-Length': '27',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
})
|
||||||
|
|
||||||
|
request = urlopen('http://github.com')
|
||||||
|
headers = dict(request.headers)
|
||||||
|
request.close()
|
||||||
|
|
||||||
|
request.code.should.equal(200)
|
||||||
|
headers.should.equal({
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': '27',
|
||||||
|
'status': '200',
|
||||||
|
'server': 'Apache',
|
||||||
|
'date': 'Fri, 04 Oct 2013 04:20:00 GMT',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_should_allow_forcing_headers_urllib2():
|
||||||
|
"HTTPretty should allow forcing headers with urllib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/",
|
||||||
|
body="this is supposed to be the response",
|
||||||
|
forcing_headers={
|
||||||
|
'Content-Type': 'application/xml',
|
||||||
|
'Content-Length': '35a',
|
||||||
|
})
|
||||||
|
|
||||||
|
request = urlopen('http://github.com')
|
||||||
|
headers = dict(request.headers)
|
||||||
|
request.close()
|
||||||
|
|
||||||
|
headers.should.equal({
|
||||||
|
'content-type': 'application/xml',
|
||||||
|
'content-length': '35a',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2():
|
||||||
|
("HTTPretty should allow adding and overwritting headers by "
|
||||||
|
"keyword args with urllib2")
|
||||||
|
|
||||||
|
body = "this is supposed to be the response, indeed"
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://github.com/",
|
||||||
|
body=body,
|
||||||
|
server='Apache',
|
||||||
|
content_length=len(body),
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
request = urlopen('http://github.com')
|
||||||
|
headers = dict(request.headers)
|
||||||
|
request.close()
|
||||||
|
|
||||||
|
request.code.should.equal(200)
|
||||||
|
headers.should.equal({
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': str(len(body)),
|
||||||
|
'status': '200',
|
||||||
|
'server': 'Apache',
|
||||||
|
'date': 'Fri, 04 Oct 2013 04:20:00 GMT',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_should_support_a_list_of_successive_responses_urllib2(now):
|
||||||
|
("HTTPretty should support adding a list of successive "
|
||||||
|
"responses with urllib2")
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "https://api.yahoo.com/test",
|
||||||
|
responses=[
|
||||||
|
HTTPretty.Response(body="first response", status=201),
|
||||||
|
HTTPretty.Response(body='second and last response', status=202),
|
||||||
|
])
|
||||||
|
|
||||||
|
request1 = urlopen('https://api.yahoo.com/test')
|
||||||
|
body1 = request1.read()
|
||||||
|
request1.close()
|
||||||
|
|
||||||
|
request1.code.should.equal(201)
|
||||||
|
body1.should.equal(b'first response')
|
||||||
|
|
||||||
|
request2 = urlopen('https://api.yahoo.com/test')
|
||||||
|
body2 = request2.read()
|
||||||
|
request2.close()
|
||||||
|
request2.code.should.equal(202)
|
||||||
|
body2.should.equal(b'second and last response')
|
||||||
|
|
||||||
|
request3 = urlopen('https://api.yahoo.com/test')
|
||||||
|
body3 = request3.read()
|
||||||
|
request3.close()
|
||||||
|
request3.code.should.equal(202)
|
||||||
|
body3.should.equal(b'second and last response')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_can_inspect_last_request(now):
|
||||||
|
"HTTPretty.last_request is a mimetools.Message request from last match"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, "http://api.github.com/",
|
||||||
|
body='{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
request = urllib2.Request(
|
||||||
|
'http://api.github.com',
|
||||||
|
b'{"username": "gabrielfalcao"}',
|
||||||
|
{
|
||||||
|
'content-type': 'text/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
fd = urlopen(request)
|
||||||
|
got = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
HTTPretty.last_request.method.should.equal('POST')
|
||||||
|
HTTPretty.last_request.body.should.equal(
|
||||||
|
b'{"username": "gabrielfalcao"}',
|
||||||
|
)
|
||||||
|
HTTPretty.last_request.headers['content-type'].should.equal(
|
||||||
|
'text/json',
|
||||||
|
)
|
||||||
|
got.should.equal(b'{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_can_inspect_last_request_with_ssl(now):
|
||||||
|
"HTTPretty.last_request is recorded even when mocking 'https' (SSL)"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.POST, "https://secure.github.com/",
|
||||||
|
body='{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
request = urllib2.Request(
|
||||||
|
'https://secure.github.com',
|
||||||
|
b'{"username": "gabrielfalcao"}',
|
||||||
|
{
|
||||||
|
'content-type': 'text/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
fd = urlopen(request)
|
||||||
|
got = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
HTTPretty.last_request.method.should.equal('POST')
|
||||||
|
HTTPretty.last_request.body.should.equal(
|
||||||
|
b'{"username": "gabrielfalcao"}',
|
||||||
|
)
|
||||||
|
HTTPretty.last_request.headers['content-type'].should.equal(
|
||||||
|
'text/json',
|
||||||
|
)
|
||||||
|
got.should.equal(b'{"repositories": ["HTTPretty", "lettuce"]}')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_httpretty_ignores_querystrings_from_registered_uri():
|
||||||
|
"HTTPretty should mock a simple GET with urllib2.read()"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/?id=123",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
fd = urlopen('http://yipit.com/?id=123')
|
||||||
|
got = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
got.should.equal(b'Find the best daily deals')
|
||||||
|
HTTPretty.last_request.method.should.equal('GET')
|
||||||
|
HTTPretty.last_request.path.should.equal('/?id=123')
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
@within(two=miliseconds)
|
||||||
|
def test_callback_response(now):
|
||||||
|
("HTTPretty should call a callback function to be set as the body with"
|
||||||
|
" urllib2")
|
||||||
|
|
||||||
|
def request_callback(request, uri, headers):
|
||||||
|
return [200, headers, "The {} response from {}".format(decode_utf8(request.method), uri)]
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET, "https://api.yahoo.com/test",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
fd = urllib2.urlopen('https://api.yahoo.com/test')
|
||||||
|
got = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
got.should.equal(b"The GET response from https://api.yahoo.com/test")
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.POST, "https://api.yahoo.com/test_post",
|
||||||
|
body=request_callback)
|
||||||
|
|
||||||
|
request = urllib2.Request(
|
||||||
|
"https://api.yahoo.com/test_post",
|
||||||
|
b'{"username": "gabrielfalcao"}',
|
||||||
|
{
|
||||||
|
'content-type': 'text/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
fd = urllib2.urlopen(request)
|
||||||
|
got = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
got.should.equal(b"The POST response from https://api.yahoo.com/test_post")
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_should_allow_registering_regexes():
|
||||||
|
"HTTPretty should allow registering regexes with urllib2"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r"https://api.yipit.com/v1/deal;brand=(?P<brand_name>\w+)"),
|
||||||
|
body="Found brand",
|
||||||
|
)
|
||||||
|
|
||||||
|
request = urllib2.Request(
|
||||||
|
"https://api.yipit.com/v1/deal;brand=GAP",
|
||||||
|
)
|
||||||
|
fd = urllib2.urlopen(request)
|
||||||
|
got = fd.read()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
got.should.equal(b"Found brand")
|
|
@ -0,0 +1,162 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from tornado.web import Application
|
||||||
|
from tornado.web import RequestHandler
|
||||||
|
from tornado.httpserver import HTTPServer
|
||||||
|
from tornado.ioloop import IOLoop
|
||||||
|
from httpretty import HTTPretty
|
||||||
|
from httpretty.core import old_socket as true_socket
|
||||||
|
from multiprocessing import Process
|
||||||
|
|
||||||
|
|
||||||
|
def utf8(s):
|
||||||
|
if isinstance(s, str):
|
||||||
|
s = s.encode('utf-8')
|
||||||
|
|
||||||
|
return bytes(s)
|
||||||
|
|
||||||
|
|
||||||
|
class BubblesHandler(RequestHandler):
|
||||||
|
def get(self):
|
||||||
|
self.write(". o O 0 O o . o O 0 O o . o O 0 O o . o O 0 O o . o O 0 O o .")
|
||||||
|
|
||||||
|
|
||||||
|
class ComeHandler(RequestHandler):
|
||||||
|
def get(self):
|
||||||
|
self.write("<- HELLO WORLD ->")
|
||||||
|
|
||||||
|
|
||||||
|
def subprocess_server_tornado(app, port, data={}):
|
||||||
|
from httpretty import HTTPretty
|
||||||
|
HTTPretty.disable()
|
||||||
|
|
||||||
|
http = HTTPServer(app)
|
||||||
|
HTTPretty.disable()
|
||||||
|
|
||||||
|
http.listen(int(port))
|
||||||
|
IOLoop.instance().start()
|
||||||
|
|
||||||
|
|
||||||
|
class TornadoServer(object):
|
||||||
|
is_running = False
|
||||||
|
|
||||||
|
def __init__(self, port):
|
||||||
|
self.port = int(port)
|
||||||
|
self.process = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_handlers(cls):
|
||||||
|
return Application([
|
||||||
|
(r"/go-for-bubbles/?", BubblesHandler),
|
||||||
|
(r"/come-again/?", ComeHandler),
|
||||||
|
])
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
|
||||||
|
app = self.get_handlers()
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
args = (app, self.port, data)
|
||||||
|
HTTPretty.disable()
|
||||||
|
self.process = Process(target=subprocess_server_tornado, args=args)
|
||||||
|
self.process.start()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
try:
|
||||||
|
os.kill(self.process.pid, 9)
|
||||||
|
except OSError:
|
||||||
|
self.process.terminate()
|
||||||
|
finally:
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
|
|
||||||
|
def subprocess_server_tcp(port):
|
||||||
|
from httpretty import HTTPretty
|
||||||
|
HTTPretty.disable()
|
||||||
|
import socket
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.bind(('localhost', port))
|
||||||
|
s.listen(True)
|
||||||
|
conn, addr = s.accept()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = conn.recv(1024)
|
||||||
|
conn.send(b"RECEIVED: " + bytes(data))
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
class TCPServer(object):
|
||||||
|
def __init__(self, port):
|
||||||
|
self.port = int(port)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
HTTPretty.disable()
|
||||||
|
|
||||||
|
|
||||||
|
args = [self.port]
|
||||||
|
self.process = Process(target=subprocess_server_tcp, args=args)
|
||||||
|
self.process.start()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
try:
|
||||||
|
os.kill(self.process.pid, 9)
|
||||||
|
except OSError:
|
||||||
|
self.process.terminate()
|
||||||
|
finally:
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
|
|
||||||
|
class TCPClient(object):
|
||||||
|
def __init__(self, port):
|
||||||
|
self.port = int(port)
|
||||||
|
self.sock = true_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.sock.connect(('localhost', self.port))
|
||||||
|
|
||||||
|
def send(self, data):
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.encode('utf-8')
|
||||||
|
|
||||||
|
self.sock.sendall(data)
|
||||||
|
return self.sock.recv(len(data) + 11)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
try:
|
||||||
|
self.sock.close()
|
||||||
|
except socket.error:
|
||||||
|
pass # already closed
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.close()
|
|
@ -0,0 +1,28 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
warnings.simplefilter('ignore')
|
|
@ -0,0 +1,45 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from httpretty import HTTPretty, httprettified
|
||||||
|
from sure import expect
|
||||||
|
|
||||||
|
|
||||||
|
@httprettified
|
||||||
|
def test_httpretty_overrides_when_pyopenssl_installed():
|
||||||
|
('HTTPretty should remove PyOpenSSLs urllib3 mock if it is installed')
|
||||||
|
# And HTTPretty works successfully
|
||||||
|
HTTPretty.register_uri(HTTPretty.GET, "https://yipit.com/",
|
||||||
|
body="Find the best daily deals")
|
||||||
|
|
||||||
|
response = requests.get('https://yipit.com')
|
||||||
|
expect(response.text).to.equal('Find the best daily deals')
|
||||||
|
expect(HTTPretty.last_request.method).to.equal('GET')
|
||||||
|
expect(HTTPretty.last_request.path).to.equal('/')
|
|
@ -0,0 +1,25 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,668 @@
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import errno
|
||||||
|
|
||||||
|
from freezegun import freeze_time
|
||||||
|
from sure import expect
|
||||||
|
|
||||||
|
from httpretty.core import HTTPrettyRequest, FakeSSLSocket, fakesock, httpretty
|
||||||
|
from httpretty.core import URIMatcher, URIInfo
|
||||||
|
|
||||||
|
from tests.compat import Mock, patch, call
|
||||||
|
|
||||||
|
|
||||||
|
class SocketErrorStub(Exception):
|
||||||
|
def __init__(self, errno):
|
||||||
|
self.errno = errno
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_stubs_internals():
|
||||||
|
("HTTPrettyRequest is a BaseHTTPRequestHandler that replaces "
|
||||||
|
"real socket file descriptors with in-memory ones")
|
||||||
|
|
||||||
|
# Given a valid HTTP request header string
|
||||||
|
headers = "\r\n".join([
|
||||||
|
'POST /somewhere/?name=foo&age=bar HTTP/1.1',
|
||||||
|
'accept-encoding: identity',
|
||||||
|
'host: github.com',
|
||||||
|
'content-type: application/json',
|
||||||
|
'connection: close',
|
||||||
|
'user-agent: Python-urllib/2.7',
|
||||||
|
])
|
||||||
|
|
||||||
|
# When I create a HTTPrettyRequest with an empty body
|
||||||
|
request = HTTPrettyRequest(headers, body='')
|
||||||
|
|
||||||
|
# Then it should have parsed the headers
|
||||||
|
dict(request.headers).should.equal({
|
||||||
|
'accept-encoding': 'identity',
|
||||||
|
'connection': 'close',
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'host': 'github.com',
|
||||||
|
'user-agent': 'Python-urllib/2.7'
|
||||||
|
})
|
||||||
|
|
||||||
|
# And the `rfile` should be a io.BytesIO
|
||||||
|
type_as_str = io.BytesIO.__module__ + '.' + io.BytesIO.__name__
|
||||||
|
|
||||||
|
request.should.have.property('rfile').being.a(type_as_str)
|
||||||
|
|
||||||
|
# And the `wfile` should be a io.BytesIO
|
||||||
|
request.should.have.property('wfile').being.a(type_as_str)
|
||||||
|
|
||||||
|
# And the `method` should be available
|
||||||
|
request.should.have.property('method').being.equal('POST')
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_parse_querystring():
|
||||||
|
("HTTPrettyRequest#parse_querystring should parse unicode data")
|
||||||
|
|
||||||
|
# Given a request string containing a unicode encoded querystring
|
||||||
|
|
||||||
|
headers = "\r\n".join([
|
||||||
|
'POST /create?name=Gabriel+Falcão HTTP/1.1',
|
||||||
|
'Content-Type: multipart/form-data',
|
||||||
|
])
|
||||||
|
|
||||||
|
# When I create a HTTPrettyRequest with an empty body
|
||||||
|
request = HTTPrettyRequest(headers, body='')
|
||||||
|
|
||||||
|
# Then it should have a parsed querystring
|
||||||
|
request.querystring.should.equal({'name': ['Gabriel Falcão']})
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_parse_body_when_it_is_application_json():
|
||||||
|
("HTTPrettyRequest#parse_request_body recognizes the "
|
||||||
|
"content-type `application/json` and parses it")
|
||||||
|
|
||||||
|
# Given a request string containing a unicode encoded querystring
|
||||||
|
headers = "\r\n".join([
|
||||||
|
'POST /create HTTP/1.1',
|
||||||
|
'Content-Type: application/json',
|
||||||
|
])
|
||||||
|
# And a valid json body
|
||||||
|
body = json.dumps({'name': 'Gabriel Falcão'})
|
||||||
|
|
||||||
|
# When I create a HTTPrettyRequest with that data
|
||||||
|
request = HTTPrettyRequest(headers, body)
|
||||||
|
|
||||||
|
# Then it should have a parsed body
|
||||||
|
request.parsed_body.should.equal({'name': 'Gabriel Falcão'})
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_parse_body_when_it_is_text_json():
|
||||||
|
("HTTPrettyRequest#parse_request_body recognizes the "
|
||||||
|
"content-type `text/json` and parses it")
|
||||||
|
|
||||||
|
# Given a request string containing a unicode encoded querystring
|
||||||
|
headers = "\r\n".join([
|
||||||
|
'POST /create HTTP/1.1',
|
||||||
|
'Content-Type: text/json',
|
||||||
|
])
|
||||||
|
# And a valid json body
|
||||||
|
body = json.dumps({'name': 'Gabriel Falcão'})
|
||||||
|
|
||||||
|
# When I create a HTTPrettyRequest with that data
|
||||||
|
request = HTTPrettyRequest(headers, body)
|
||||||
|
|
||||||
|
# Then it should have a parsed body
|
||||||
|
request.parsed_body.should.equal({'name': 'Gabriel Falcão'})
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_parse_body_when_it_is_urlencoded():
|
||||||
|
("HTTPrettyRequest#parse_request_body recognizes the "
|
||||||
|
"content-type `application/x-www-form-urlencoded` and parses it")
|
||||||
|
|
||||||
|
# Given a request string containing a unicode encoded querystring
|
||||||
|
headers = "\r\n".join([
|
||||||
|
'POST /create HTTP/1.1',
|
||||||
|
'Content-Type: application/x-www-form-urlencoded',
|
||||||
|
])
|
||||||
|
# And a valid urlencoded body
|
||||||
|
body = "name=Gabriel+Falcão&age=25&projects=httpretty&projects=sure&projects=lettuce"
|
||||||
|
|
||||||
|
# When I create a HTTPrettyRequest with that data
|
||||||
|
request = HTTPrettyRequest(headers, body)
|
||||||
|
|
||||||
|
# Then it should have a parsed body
|
||||||
|
request.parsed_body.should.equal({
|
||||||
|
'name': ['Gabriel Falcão'],
|
||||||
|
'age': ["25"],
|
||||||
|
'projects': ["httpretty", "sure", "lettuce"]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_parse_body_when_unrecognized():
|
||||||
|
("HTTPrettyRequest#parse_request_body returns the value as "
|
||||||
|
"is if the Content-Type is not recognized")
|
||||||
|
|
||||||
|
# Given a request string containing a unicode encoded querystring
|
||||||
|
headers = "\r\n".join([
|
||||||
|
'POST /create HTTP/1.1',
|
||||||
|
'Content-Type: whatever',
|
||||||
|
])
|
||||||
|
# And a valid urlencoded body
|
||||||
|
body = "foobar:\nlalala"
|
||||||
|
|
||||||
|
# When I create a HTTPrettyRequest with that data
|
||||||
|
request = HTTPrettyRequest(headers, body)
|
||||||
|
|
||||||
|
# Then it should have a parsed body
|
||||||
|
request.parsed_body.should.equal("foobar:\nlalala")
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_string_representation():
|
||||||
|
("HTTPrettyRequest should have a forward_and_trace-friendly "
|
||||||
|
"string representation")
|
||||||
|
|
||||||
|
# Given a request string containing a unicode encoded querystring
|
||||||
|
headers = "\r\n".join([
|
||||||
|
'POST /create HTTP/1.1',
|
||||||
|
'Content-Type: JPEG-baby',
|
||||||
|
'Host: blog.falcao.it'
|
||||||
|
])
|
||||||
|
# And a valid urlencoded body
|
||||||
|
body = "foobar:\nlalala"
|
||||||
|
|
||||||
|
# When I create a HTTPrettyRequest with that data
|
||||||
|
request = HTTPrettyRequest(headers, body, sock=Mock(is_https=True))
|
||||||
|
|
||||||
|
# Then its string representation should show the headers and the body
|
||||||
|
str(request).should.equal('<HTTPrettyRequest("POST", "https://blog.falcao.it/create", headers={\'Content-Type\': \'JPEG-baby\', \'Host\': \'blog.falcao.it\'}, body=14)>')
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_ssl_socket_proxies_its_ow_socket():
|
||||||
|
("FakeSSLSocket is a simpel wrapper around its own socket, "
|
||||||
|
"which was designed to be a HTTPretty fake socket")
|
||||||
|
|
||||||
|
# Given a sentinel mock object
|
||||||
|
socket = Mock()
|
||||||
|
|
||||||
|
# And a FakeSSLSocket wrapping it
|
||||||
|
ssl = FakeSSLSocket(socket)
|
||||||
|
|
||||||
|
# When I make a method call
|
||||||
|
ssl.send("FOO")
|
||||||
|
|
||||||
|
# Then it should bypass any method calls to its own socket
|
||||||
|
socket.send.assert_called_once_with("FOO")
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2013-10-04 04:20:00")
|
||||||
|
def test_fakesock_socket_getpeercert():
|
||||||
|
("fakesock.socket#getpeercert should return a hardcoded fake certificate")
|
||||||
|
# Given a fake socket instance
|
||||||
|
socket = fakesock.socket()
|
||||||
|
|
||||||
|
# And that it's bound to some host
|
||||||
|
socket._host = 'somewhere.com'
|
||||||
|
|
||||||
|
# When I retrieve the peer certificate
|
||||||
|
certificate = socket.getpeercert()
|
||||||
|
|
||||||
|
# Then it should return a hardcoded value
|
||||||
|
certificate.should.equal({
|
||||||
|
u'notAfter': 'Sep 29 04:20:00 GMT',
|
||||||
|
u'subject': (
|
||||||
|
((u'organizationName', u'*.somewhere.com'),),
|
||||||
|
((u'organizationalUnitName', u'Domain Control Validated'),),
|
||||||
|
((u'commonName', u'*.somewhere.com'),)),
|
||||||
|
u'subjectAltName': (
|
||||||
|
(u'DNS', u'*.somewhere.com'),
|
||||||
|
(u'DNS', u'somewhere.com'),
|
||||||
|
(u'DNS', u'*')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def test_fakesock_socket_ssl():
|
||||||
|
("fakesock.socket#ssl should take a socket instance and return itself")
|
||||||
|
# Given a fake socket instance
|
||||||
|
socket = fakesock.socket()
|
||||||
|
|
||||||
|
# And a stubbed socket sentinel
|
||||||
|
sentinel = Mock()
|
||||||
|
|
||||||
|
# When I call `ssl` on that mock
|
||||||
|
result = socket.ssl(sentinel)
|
||||||
|
|
||||||
|
# Then it should have returned its first argument
|
||||||
|
result.should.equal(sentinel)
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
@patch('httpretty.core.POTENTIAL_HTTP_PORTS')
|
||||||
|
def test_fakesock_socket_connect_fallback(POTENTIAL_HTTP_PORTS, old_socket):
|
||||||
|
("fakesock.socket#connect should open a real connection if the "
|
||||||
|
"given port is not a potential http port")
|
||||||
|
# Background: the potential http ports are 80 and 443
|
||||||
|
POTENTIAL_HTTP_PORTS.__contains__.side_effect = lambda other: int(other) in (80, 443)
|
||||||
|
|
||||||
|
# Given a fake socket instance
|
||||||
|
socket = fakesock.socket()
|
||||||
|
|
||||||
|
# When it is connected to a remote server in a port that isn't 80 nor 443
|
||||||
|
socket.connect(('somewhere.com', 42))
|
||||||
|
|
||||||
|
# Then it should have open a real connection in the background
|
||||||
|
old_socket.return_value.connect.assert_called_once_with(('somewhere.com', 42))
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
def test_fakesock_socket_close(old_socket):
|
||||||
|
("fakesock.socket#close should close the actual socket in case "
|
||||||
|
"it's not http and __truesock_is_connected__ is True")
|
||||||
|
# Given a fake socket instance that is synthetically open
|
||||||
|
socket = fakesock.socket()
|
||||||
|
socket.__truesock_is_connected__ = True
|
||||||
|
|
||||||
|
# When I close it
|
||||||
|
socket.close()
|
||||||
|
|
||||||
|
# Then its real socket should have been closed
|
||||||
|
old_socket.return_value.close.assert_called_once_with()
|
||||||
|
socket.__truesock_is_connected__.should.be.false
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
def test_fakesock_socket_makefile(old_socket):
|
||||||
|
("fakesock.socket#makefile should set the mode, "
|
||||||
|
"bufsize and return its mocked file descriptor")
|
||||||
|
|
||||||
|
# Given a fake socket that has a mocked Entry associated with it
|
||||||
|
socket = fakesock.socket()
|
||||||
|
socket._entry = Mock()
|
||||||
|
|
||||||
|
# When I call makefile()
|
||||||
|
fd = socket.makefile(mode='rw', bufsize=512)
|
||||||
|
|
||||||
|
# Then it should have returned the socket's own filedescriptor
|
||||||
|
expect(fd).to.equal(socket.fd)
|
||||||
|
# And the mode should have been set in the socket instance
|
||||||
|
socket._mode.should.equal('rw')
|
||||||
|
# And the bufsize should have been set in the socket instance
|
||||||
|
socket._bufsize.should.equal(512)
|
||||||
|
|
||||||
|
# And the entry should have been filled with that filedescriptor
|
||||||
|
socket._entry.fill_filekind.assert_called_once_with(fd)
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
def test_fakesock_socket_real_sendall(old_socket):
|
||||||
|
("fakesock.socket#real_sendall calls truesock#connect and bails "
|
||||||
|
"out when not http")
|
||||||
|
# Background: the real socket will stop returning bytes after the
|
||||||
|
# first call
|
||||||
|
real_socket = old_socket.return_value
|
||||||
|
real_socket.recv.side_effect = [b'response from server', b""]
|
||||||
|
|
||||||
|
# Given a fake socket
|
||||||
|
socket = fakesock.socket()
|
||||||
|
socket._address = ('1.2.3.4', 42)
|
||||||
|
|
||||||
|
# When I call real_sendall with data, some args and kwargs
|
||||||
|
socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar')
|
||||||
|
|
||||||
|
# Then it should have called sendall in the real socket
|
||||||
|
real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar')
|
||||||
|
|
||||||
|
# # And setblocking was never called
|
||||||
|
# real_socket.setblocking.called.should.be.false
|
||||||
|
|
||||||
|
# And recv was never called
|
||||||
|
real_socket.recv.called.should.be.false
|
||||||
|
|
||||||
|
# And the buffer is empty
|
||||||
|
socket.fd.read().should.equal(b'')
|
||||||
|
|
||||||
|
# And connect was never called
|
||||||
|
real_socket.connect.called.should.be.false
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
def test_fakesock_socket_real_sendall_when_http(old_socket):
|
||||||
|
("fakesock.socket#real_sendall sends data and buffers "
|
||||||
|
"the response in the file descriptor")
|
||||||
|
# Background: the real socket will stop returning bytes after the
|
||||||
|
# first call
|
||||||
|
real_socket = old_socket.return_value
|
||||||
|
real_socket.recv.side_effect = [b'response from server', b""]
|
||||||
|
|
||||||
|
# Given a fake socket
|
||||||
|
socket = fakesock.socket()
|
||||||
|
socket._address = ('1.2.3.4', 42)
|
||||||
|
socket.is_http = True
|
||||||
|
|
||||||
|
# When I call real_sendall with data, some args and kwargs
|
||||||
|
socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar')
|
||||||
|
|
||||||
|
# Then it should have called sendall in the real socket
|
||||||
|
real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar')
|
||||||
|
|
||||||
|
# And the socket was set to blocking
|
||||||
|
real_socket.setblocking.assert_called_once_with(1)
|
||||||
|
|
||||||
|
# And recv was called with the bufsize
|
||||||
|
real_socket.recv.assert_has_calls([
|
||||||
|
call(socket._bufsize)
|
||||||
|
])
|
||||||
|
|
||||||
|
# And the buffer should contain the data from the server
|
||||||
|
socket.fd.read().should.equal(b"response from server")
|
||||||
|
|
||||||
|
# And connect was called
|
||||||
|
real_socket.connect.called.should.be.true
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
@patch('httpretty.core.socket')
|
||||||
|
def test_fakesock_socket_real_sendall_continue_eagain_when_http(socket, old_socket):
|
||||||
|
("fakesock.socket#real_sendall should continue if the socket error was EAGAIN")
|
||||||
|
socket.error = SocketErrorStub
|
||||||
|
# Background: the real socket will stop returning bytes after the
|
||||||
|
# first call
|
||||||
|
real_socket = old_socket.return_value
|
||||||
|
real_socket.recv.side_effect = [SocketErrorStub(errno.EAGAIN), b'after error', b""]
|
||||||
|
|
||||||
|
# Given a fake socket
|
||||||
|
socket = fakesock.socket()
|
||||||
|
socket._address = ('1.2.3.4', 42)
|
||||||
|
socket.is_http = True
|
||||||
|
|
||||||
|
# When I call real_sendall with data, some args and kwargs
|
||||||
|
socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar')
|
||||||
|
|
||||||
|
# Then it should have called sendall in the real socket
|
||||||
|
real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar')
|
||||||
|
|
||||||
|
# And the socket was set to blocking
|
||||||
|
real_socket.setblocking.assert_called_once_with(1)
|
||||||
|
|
||||||
|
# And recv was called with the bufsize
|
||||||
|
real_socket.recv.assert_has_calls([
|
||||||
|
call(socket._bufsize)
|
||||||
|
])
|
||||||
|
|
||||||
|
# And the buffer should contain the data from the server
|
||||||
|
socket.fd.read().should.equal(b"after error")
|
||||||
|
|
||||||
|
# And connect was called
|
||||||
|
real_socket.connect.called.should.be.true
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
@patch('httpretty.core.socket')
|
||||||
|
def test_fakesock_socket_real_sendall_socket_error_when_http(socket, old_socket):
|
||||||
|
("fakesock.socket#real_sendall should continue if the socket error was EAGAIN")
|
||||||
|
socket.error = SocketErrorStub
|
||||||
|
# Background: the real socket will stop returning bytes after the
|
||||||
|
# first call
|
||||||
|
real_socket = old_socket.return_value
|
||||||
|
real_socket.recv.side_effect = [SocketErrorStub(42), b'after error', ""]
|
||||||
|
|
||||||
|
# Given a fake socket
|
||||||
|
socket = fakesock.socket()
|
||||||
|
socket._address = ('1.2.3.4', 42)
|
||||||
|
socket.is_http = True
|
||||||
|
|
||||||
|
# When I call real_sendall with data, some args and kwargs
|
||||||
|
socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar')
|
||||||
|
|
||||||
|
# Then it should have called sendall in the real socket
|
||||||
|
real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar')
|
||||||
|
|
||||||
|
# And the socket was set to blocking
|
||||||
|
real_socket.setblocking.assert_called_once_with(1)
|
||||||
|
|
||||||
|
# And recv was called with the bufsize
|
||||||
|
real_socket.recv.assert_called_once_with(socket._bufsize)
|
||||||
|
|
||||||
|
# And the buffer should contain the data from the server
|
||||||
|
socket.fd.read().should.equal(b"")
|
||||||
|
|
||||||
|
# And connect was called
|
||||||
|
real_socket.connect.called.should.be.true
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
@patch('httpretty.core.POTENTIAL_HTTP_PORTS')
|
||||||
|
def test_fakesock_socket_real_sendall_when_sending_data(POTENTIAL_HTTP_PORTS, old_socket):
|
||||||
|
("fakesock.socket#real_sendall should connect before sending data")
|
||||||
|
# Background: the real socket will stop returning bytes after the
|
||||||
|
# first call
|
||||||
|
real_socket = old_socket.return_value
|
||||||
|
real_socket.recv.side_effect = [b'response from foobar :)', b""]
|
||||||
|
|
||||||
|
# And the potential http port is 4000
|
||||||
|
POTENTIAL_HTTP_PORTS.__contains__.side_effect = lambda other: int(other) == 4000
|
||||||
|
POTENTIAL_HTTP_PORTS.union.side_effect = lambda other: POTENTIAL_HTTP_PORTS
|
||||||
|
|
||||||
|
# Given a fake socket
|
||||||
|
socket = fakesock.socket()
|
||||||
|
|
||||||
|
# When I call connect to a server in a port that is considered HTTP
|
||||||
|
socket.connect(('foobar.com', 4000))
|
||||||
|
|
||||||
|
# And send some data
|
||||||
|
socket.real_sendall(b"SOMEDATA")
|
||||||
|
|
||||||
|
# Then connect should have been called
|
||||||
|
real_socket.connect.assert_called_once_with(('foobar.com', 4000))
|
||||||
|
|
||||||
|
# And the socket was set to blocking
|
||||||
|
real_socket.setblocking.assert_called_once_with(1)
|
||||||
|
|
||||||
|
# And recv was called with the bufsize
|
||||||
|
real_socket.recv.assert_has_calls([
|
||||||
|
call(socket._bufsize)
|
||||||
|
])
|
||||||
|
|
||||||
|
# And the buffer should contain the data from the server
|
||||||
|
socket.fd.read().should.equal(b"response from foobar :)")
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
@patch('httpretty.core.httpretty')
|
||||||
|
@patch('httpretty.core.POTENTIAL_HTTP_PORTS')
|
||||||
|
def test_fakesock_socket_sendall_with_valid_requestline(POTENTIAL_HTTP_PORTS, httpretty, old_socket):
|
||||||
|
("fakesock.socket#sendall should create an entry if it's given a valid request line")
|
||||||
|
matcher = Mock(name='matcher')
|
||||||
|
info = Mock(name='info')
|
||||||
|
httpretty.match_uriinfo.return_value = (matcher, info)
|
||||||
|
httpretty.register_uri(httpretty.GET, 'http://foo.com/foobar')
|
||||||
|
|
||||||
|
# Background:
|
||||||
|
# using a subclass of socket that mocks out real_sendall
|
||||||
|
class MySocket(fakesock.socket):
|
||||||
|
def real_sendall(self, data, *args, **kw):
|
||||||
|
raise AssertionError('should never call this...')
|
||||||
|
|
||||||
|
# Given an instance of that socket
|
||||||
|
socket = MySocket()
|
||||||
|
|
||||||
|
# And that is is considered http
|
||||||
|
socket.connect(('foo.com', 80))
|
||||||
|
|
||||||
|
# When I try to send data
|
||||||
|
socket.sendall(b"GET /foobar HTTP/1.1\r\nContent-Type: application/json\r\n\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
@patch('httpretty.core.httpretty')
|
||||||
|
@patch('httpretty.core.POTENTIAL_HTTP_PORTS')
|
||||||
|
def test_fakesock_socket_sendall_with_valid_requestline_2(POTENTIAL_HTTP_PORTS, httpretty, old_socket):
|
||||||
|
("fakesock.socket#sendall should create an entry if it's given a valid request line")
|
||||||
|
matcher = Mock(name='matcher')
|
||||||
|
info = Mock(name='info')
|
||||||
|
httpretty.match_uriinfo.return_value = (matcher, info)
|
||||||
|
httpretty.register_uri(httpretty.GET, 'http://foo.com/foobar')
|
||||||
|
|
||||||
|
# Background:
|
||||||
|
# using a subclass of socket that mocks out real_sendall
|
||||||
|
class MySocket(fakesock.socket):
|
||||||
|
def real_sendall(self, data, *args, **kw):
|
||||||
|
raise AssertionError('should never call this...')
|
||||||
|
|
||||||
|
# Given an instance of that socket
|
||||||
|
socket = MySocket()
|
||||||
|
|
||||||
|
# And that is is considered http
|
||||||
|
socket.connect(('foo.com', 80))
|
||||||
|
|
||||||
|
# When I try to send data
|
||||||
|
socket.sendall(b"GET /foobar HTTP/1.1\r\nContent-Type: application/json\r\n\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
def test_fakesock_socket_sendall_with_body_data_no_entry(old_socket):
|
||||||
|
("fakesock.socket#sendall should call real_sendall when not parsing headers and there is no entry")
|
||||||
|
# Background:
|
||||||
|
# Using a subclass of socket that mocks out real_sendall
|
||||||
|
|
||||||
|
class MySocket(fakesock.socket):
|
||||||
|
def real_sendall(self, data):
|
||||||
|
data.should.equal(b'BLABLABLABLA')
|
||||||
|
return 'cool'
|
||||||
|
|
||||||
|
# Given an instance of that socket
|
||||||
|
socket = MySocket()
|
||||||
|
socket._entry = None
|
||||||
|
|
||||||
|
# And that is is considered http
|
||||||
|
socket.connect(('foo.com', 80))
|
||||||
|
|
||||||
|
# When I try to send data
|
||||||
|
result = socket.sendall(b"BLABLABLABLA")
|
||||||
|
|
||||||
|
# Then the result should be the return value from real_sendall
|
||||||
|
result.should.equal('cool')
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
@patch('httpretty.core.POTENTIAL_HTTP_PORTS')
|
||||||
|
def test_fakesock_socket_sendall_with_body_data_with_entry(POTENTIAL_HTTP_PORTS, old_socket):
|
||||||
|
("fakesock.socket#sendall should call real_sendall when there is no entry")
|
||||||
|
# Background:
|
||||||
|
# Using a subclass of socket that mocks out real_sendall
|
||||||
|
data_sent = []
|
||||||
|
|
||||||
|
class MySocket(fakesock.socket):
|
||||||
|
def real_sendall(self, data):
|
||||||
|
data_sent.append(data)
|
||||||
|
|
||||||
|
# Given an instance of that socket
|
||||||
|
socket = MySocket()
|
||||||
|
|
||||||
|
# And that is is considered http
|
||||||
|
socket.connect(('foo.com', 80))
|
||||||
|
|
||||||
|
# When I try to send data
|
||||||
|
socket.sendall(b"BLABLABLABLA")
|
||||||
|
|
||||||
|
# Then it should have called real_sendall
|
||||||
|
data_sent.should.equal([b'BLABLABLABLA'])
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.core.httpretty.match_uriinfo')
|
||||||
|
@patch('httpretty.core.old_socket')
|
||||||
|
@patch('httpretty.core.POTENTIAL_HTTP_PORTS')
|
||||||
|
def test_fakesock_socket_sendall_with_body_data_with_chunked_entry(POTENTIAL_HTTP_PORTS, old_socket, match_uriinfo):
|
||||||
|
("fakesock.socket#sendall should call real_sendall when not ")
|
||||||
|
# Background:
|
||||||
|
# Using a subclass of socket that mocks out real_sendall
|
||||||
|
|
||||||
|
class MySocket(fakesock.socket):
|
||||||
|
def real_sendall(self, data):
|
||||||
|
raise AssertionError('should have never been called')
|
||||||
|
|
||||||
|
matcher = Mock(name='matcher')
|
||||||
|
info = Mock(name='info')
|
||||||
|
httpretty.match_uriinfo.return_value = (matcher, info)
|
||||||
|
|
||||||
|
# Using a mocked entry
|
||||||
|
entry = Mock()
|
||||||
|
entry.method = 'GET'
|
||||||
|
entry.info.path = '/foo'
|
||||||
|
|
||||||
|
entry.request.headers = {
|
||||||
|
'transfer-encoding': 'chunked',
|
||||||
|
}
|
||||||
|
entry.request.body = b''
|
||||||
|
|
||||||
|
# Given an instance of that socket
|
||||||
|
socket = MySocket()
|
||||||
|
socket._entry = entry
|
||||||
|
|
||||||
|
# And that is is considered http
|
||||||
|
socket.connect(('foo.com', 80))
|
||||||
|
|
||||||
|
# When I try to send data
|
||||||
|
socket.sendall(b"BLABLABLABLA")
|
||||||
|
|
||||||
|
# Then the entry should have that body
|
||||||
|
httpretty.last_request.body.should.equal(b'BLABLABLABLA')
|
||||||
|
|
||||||
|
|
||||||
|
def test_fakesock_socket_sendall_with_path_starting_with_two_slashes():
|
||||||
|
("fakesock.socket#sendall handles paths starting with // well")
|
||||||
|
|
||||||
|
httpretty.register_uri(httpretty.GET, 'http://example.com//foo')
|
||||||
|
|
||||||
|
class MySocket(fakesock.socket):
|
||||||
|
def real_sendall(self, data, *args, **kw):
|
||||||
|
raise AssertionError('should never call this...')
|
||||||
|
|
||||||
|
# Given an instance of that socket
|
||||||
|
socket = MySocket()
|
||||||
|
|
||||||
|
# And that is is considered http
|
||||||
|
socket.connect(('example.com', 80))
|
||||||
|
|
||||||
|
# When I try to send data
|
||||||
|
socket.sendall(b"GET //foo HTTP/1.1\r\nContent-Type: application/json\r\n\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_URIMatcher_respects_querystring():
|
||||||
|
("URIMatcher response querystring")
|
||||||
|
matcher = URIMatcher('http://www.foo.com/?query=true', None)
|
||||||
|
info = URIInfo.from_uri('http://www.foo.com/', None)
|
||||||
|
assert matcher.matches(info)
|
||||||
|
|
||||||
|
matcher = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True)
|
||||||
|
info = URIInfo.from_uri('http://www.foo.com/', None)
|
||||||
|
assert not matcher.matches(info)
|
||||||
|
|
||||||
|
matcher = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True)
|
||||||
|
info = URIInfo.from_uri('http://www.foo.com/?query=true', None)
|
||||||
|
assert matcher.matches(info)
|
||||||
|
|
||||||
|
matcher = URIMatcher('http://www.foo.com/?query=true&unquery=false', None, match_querystring=True)
|
||||||
|
info = URIInfo.from_uri('http://www.foo.com/?unquery=false&query=true', None)
|
||||||
|
assert matcher.matches(info)
|
||||||
|
|
||||||
|
matcher = URIMatcher('http://www.foo.com/?unquery=false&query=true', None, match_querystring=True)
|
||||||
|
info = URIInfo.from_uri('http://www.foo.com/?query=true&unquery=false', None)
|
||||||
|
assert matcher.matches(info)
|
||||||
|
|
||||||
|
|
||||||
|
def test_URIMatcher_equality_respects_querystring():
|
||||||
|
("URIMatcher equality check should check querystring")
|
||||||
|
matcher_a = URIMatcher('http://www.foo.com/?query=true', None)
|
||||||
|
matcher_b = URIMatcher('http://www.foo.com/?query=false', None)
|
||||||
|
assert matcher_a == matcher_b
|
||||||
|
|
||||||
|
matcher_a = URIMatcher('http://www.foo.com/?query=true', None)
|
||||||
|
matcher_b = URIMatcher('http://www.foo.com/', None)
|
||||||
|
assert matcher_a == matcher_b
|
||||||
|
|
||||||
|
matcher_a = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True)
|
||||||
|
matcher_b = URIMatcher('http://www.foo.com/?query=false', None, match_querystring=True)
|
||||||
|
assert not matcher_a == matcher_b
|
||||||
|
|
||||||
|
matcher_a = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True)
|
||||||
|
matcher_b = URIMatcher('http://www.foo.com/', None, match_querystring=True)
|
||||||
|
assert not matcher_a == matcher_b
|
||||||
|
|
||||||
|
matcher_a = URIMatcher('http://www.foo.com/?query=true&unquery=false', None, match_querystring=True)
|
||||||
|
matcher_b = URIMatcher('http://www.foo.com/?unquery=false&query=true', None, match_querystring=True)
|
||||||
|
assert matcher_a == matcher_b
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from httpretty.http import parse_requestline
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_request_line_connect():
|
||||||
|
("parse_requestline should parse the CONNECT method appropriately")
|
||||||
|
|
||||||
|
# Given a valid request line string that has the CONNECT method
|
||||||
|
line = "CONNECT / HTTP/1.1"
|
||||||
|
|
||||||
|
# When I parse it
|
||||||
|
result = parse_requestline(line)
|
||||||
|
|
||||||
|
# Then it should return a tuple
|
||||||
|
result.should.equal(("CONNECT", "/", "1.1"))
|
|
@ -0,0 +1,429 @@
|
||||||
|
# #!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# <HTTPretty - HTTP client mock for Python>
|
||||||
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
from sure import expect
|
||||||
|
import httpretty
|
||||||
|
from httpretty import HTTPretty
|
||||||
|
from httpretty import HTTPrettyError
|
||||||
|
from httpretty import core
|
||||||
|
from httpretty.core import URIInfo, BaseClass, Entry, FakeSockFile, HTTPrettyRequest
|
||||||
|
from httpretty.http import STATUSES
|
||||||
|
|
||||||
|
from tests.compat import MagicMock, patch
|
||||||
|
|
||||||
|
|
||||||
|
TEST_HEADER = """
|
||||||
|
GET /test/test.html HTTP/1.1
|
||||||
|
Host: www.host1.com:80
|
||||||
|
Content-Type: %(content_type)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_httpretty_should_raise_proper_exception_on_inconsistent_length():
|
||||||
|
("HTTPretty should raise proper exception on inconsistent Content-Length / "
|
||||||
|
"registered response body")
|
||||||
|
|
||||||
|
HTTPretty.register_uri.when.called_with(
|
||||||
|
HTTPretty.GET,
|
||||||
|
"http://github.com/gabrielfalcao",
|
||||||
|
body="that's me!",
|
||||||
|
adding_headers={
|
||||||
|
'Content-Length': '999'
|
||||||
|
}
|
||||||
|
).should.have.raised(
|
||||||
|
HTTPrettyError,
|
||||||
|
'HTTPretty got inconsistent parameters. The header Content-Length you registered expects size "999" '
|
||||||
|
'but the body you registered for that has actually length "10".'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_does_not_have_last_request_by_default():
|
||||||
|
'HTTPretty.last_request is a dummy object by default'
|
||||||
|
HTTPretty.reset()
|
||||||
|
|
||||||
|
expect(HTTPretty.last_request.headers).to.be.empty
|
||||||
|
expect(HTTPretty.last_request.body).to.be.empty
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_codes():
|
||||||
|
"HTTPretty supports N status codes"
|
||||||
|
|
||||||
|
expect(STATUSES).to.equal({
|
||||||
|
100: "Continue",
|
||||||
|
101: "Switching Protocols",
|
||||||
|
102: "Processing",
|
||||||
|
200: "OK",
|
||||||
|
201: "Created",
|
||||||
|
202: "Accepted",
|
||||||
|
203: "Non-Authoritative Information",
|
||||||
|
204: "No Content",
|
||||||
|
205: "Reset Content",
|
||||||
|
206: "Partial Content",
|
||||||
|
207: "Multi-Status",
|
||||||
|
208: "Already Reported",
|
||||||
|
226: "IM Used",
|
||||||
|
300: "Multiple Choices",
|
||||||
|
301: "Moved Permanently",
|
||||||
|
302: "Found",
|
||||||
|
303: "See Other",
|
||||||
|
304: "Not Modified",
|
||||||
|
305: "Use Proxy",
|
||||||
|
306: "Switch Proxy",
|
||||||
|
307: "Temporary Redirect",
|
||||||
|
308: "Permanent Redirect",
|
||||||
|
400: "Bad Request",
|
||||||
|
401: "Unauthorized",
|
||||||
|
402: "Payment Required",
|
||||||
|
403: "Forbidden",
|
||||||
|
404: "Not Found",
|
||||||
|
405: "Method Not Allowed",
|
||||||
|
406: "Not Acceptable",
|
||||||
|
407: "Proxy Authentication Required",
|
||||||
|
408: "Request a Timeout",
|
||||||
|
409: "Conflict",
|
||||||
|
410: "Gone",
|
||||||
|
411: "Length Required",
|
||||||
|
412: "Precondition Failed",
|
||||||
|
413: "Request Entity Too Large",
|
||||||
|
414: "Request-URI Too Long",
|
||||||
|
415: "Unsupported Media Type",
|
||||||
|
416: "Requested Range Not Satisfiable",
|
||||||
|
417: "Expectation Failed",
|
||||||
|
418: "I'm a teapot",
|
||||||
|
420: "Enhance Your Calm",
|
||||||
|
422: "Unprocessable Entity",
|
||||||
|
423: "Locked",
|
||||||
|
424: "Failed Dependency",
|
||||||
|
425: "Unordered Collection",
|
||||||
|
426: "Upgrade Required",
|
||||||
|
428: "Precondition Required",
|
||||||
|
429: "Too Many Requests",
|
||||||
|
431: "Request Header Fields Too Large",
|
||||||
|
444: "No Response",
|
||||||
|
449: "Retry With",
|
||||||
|
450: "Blocked by Windows Parental Controls",
|
||||||
|
451: "Unavailable For Legal Reasons",
|
||||||
|
494: "Request Header Too Large",
|
||||||
|
495: "Cert Error",
|
||||||
|
496: "No Cert",
|
||||||
|
497: "HTTP to HTTPS",
|
||||||
|
499: "Client Closed Request",
|
||||||
|
500: "Internal Server Error",
|
||||||
|
501: "Not Implemented",
|
||||||
|
502: "Bad Gateway",
|
||||||
|
503: "Service Unavailable",
|
||||||
|
504: "Gateway Timeout",
|
||||||
|
505: "HTTP Version Not Supported",
|
||||||
|
506: "Variant Also Negotiates",
|
||||||
|
507: "Insufficient Storage",
|
||||||
|
508: "Loop Detected",
|
||||||
|
509: "Bandwidth Limit Exceeded",
|
||||||
|
510: "Not Extended",
|
||||||
|
511: "Network Authentication Required",
|
||||||
|
598: "Network read timeout error",
|
||||||
|
599: "Network connect timeout error",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def test_uri_info_full_url():
|
||||||
|
uri_info = URIInfo(
|
||||||
|
username='johhny',
|
||||||
|
password='password',
|
||||||
|
hostname=b'google.com',
|
||||||
|
port=80,
|
||||||
|
path=b'/',
|
||||||
|
query=b'foo=bar&baz=test',
|
||||||
|
fragment='',
|
||||||
|
scheme='',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(uri_info.full_url()).to.equal(
|
||||||
|
"http://johhny:password@google.com/?baz=test&foo=bar"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(uri_info.full_url(use_querystring=False)).to.equal(
|
||||||
|
"http://johhny:password@google.com/"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_uri_info_eq_ignores_case():
|
||||||
|
"""Test that URIInfo.__eq__ method ignores case for
|
||||||
|
hostname matching.
|
||||||
|
"""
|
||||||
|
uri_info_uppercase = URIInfo(
|
||||||
|
username='johhny',
|
||||||
|
password='password',
|
||||||
|
hostname=b'GOOGLE.COM',
|
||||||
|
port=80,
|
||||||
|
path=b'/',
|
||||||
|
query=b'foo=bar&baz=test',
|
||||||
|
fragment='',
|
||||||
|
scheme='',
|
||||||
|
)
|
||||||
|
uri_info_lowercase = URIInfo(
|
||||||
|
username='johhny',
|
||||||
|
password='password',
|
||||||
|
hostname=b'google.com',
|
||||||
|
port=80,
|
||||||
|
path=b'/',
|
||||||
|
query=b'foo=bar&baz=test',
|
||||||
|
fragment='',
|
||||||
|
scheme='',
|
||||||
|
)
|
||||||
|
expect(uri_info_uppercase).to.equal(uri_info_lowercase)
|
||||||
|
|
||||||
|
|
||||||
|
def test_global_boolean_enabled():
|
||||||
|
HTTPretty.disable()
|
||||||
|
expect(HTTPretty.is_enabled()).to.be.falsy
|
||||||
|
HTTPretty.enable()
|
||||||
|
expect(HTTPretty.is_enabled()).to.be.truthy
|
||||||
|
HTTPretty.disable()
|
||||||
|
expect(HTTPretty.is_enabled()).to.be.falsy
|
||||||
|
|
||||||
|
|
||||||
|
def test_py3kobject_implements_valid__repr__based_on__str__():
|
||||||
|
class MyObject(BaseClass):
|
||||||
|
def __str__(self):
|
||||||
|
return 'hi'
|
||||||
|
|
||||||
|
myobj = MyObject()
|
||||||
|
expect(repr(myobj)).to.be.equal('hi')
|
||||||
|
|
||||||
|
|
||||||
|
def test_Entry_class_normalizes_headers():
|
||||||
|
entry = Entry(HTTPretty.GET, 'http://example.com', 'example',
|
||||||
|
host='example.com', cache_control='no-cache', x_forward_for='proxy')
|
||||||
|
|
||||||
|
entry.adding_headers.should.equal({
|
||||||
|
'Host': 'example.com',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'X-Forward-For': 'proxy'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def test_Entry_class_counts_multibyte_characters_in_bytes():
|
||||||
|
entry = Entry(HTTPretty.GET, 'http://example.com', 'こんにちは')
|
||||||
|
buf = FakeSockFile()
|
||||||
|
entry.fill_filekind(buf)
|
||||||
|
response = buf.read()
|
||||||
|
expect(b'content-length: 15\n').to.be.within(response)
|
||||||
|
|
||||||
|
|
||||||
|
def test_Entry_class_counts_dynamic():
|
||||||
|
result = (200, {}, 'こんにちは'.encode('utf-8'))
|
||||||
|
entry = Entry(HTTPretty.GET, 'http://example.com', lambda *args: result)
|
||||||
|
entry.info = URIInfo.from_uri('http://example.com', entry)
|
||||||
|
buf = FakeSockFile()
|
||||||
|
entry.fill_filekind(buf)
|
||||||
|
response = buf.getvalue()
|
||||||
|
expect(b'content-length: 15\n').to.be.within(response)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_setblocking():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.setblocking).called_with(0).should_not.throw(AttributeError)
|
||||||
|
s.truesock.setblocking.assert_called_with(0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_fileno():
|
||||||
|
import socket
|
||||||
|
with httpretty.enabled():
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.fileno).called_with().should_not.throw(AttributeError)
|
||||||
|
s.truesock.fileno.assert_called_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_getsockopt():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.getsockopt).called_with(socket.SOL_SOCKET, 1).should_not.throw(AttributeError)
|
||||||
|
s.truesock.getsockopt.assert_called_with(socket.SOL_SOCKET, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_bind():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.bind).called_with(('127.0.0.1', 1000)).should_not.throw(AttributeError)
|
||||||
|
s.truesock.bind.assert_called_with(('127.0.0.1', 1000))
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_connect_ex():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.connect_ex).called_with().should_not.throw(AttributeError)
|
||||||
|
s.truesock.connect_ex.assert_called_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_listen():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.listen).called_with().should_not.throw(AttributeError)
|
||||||
|
s.truesock.listen.assert_called_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_getpeername():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.getpeername).called_with().should_not.throw(AttributeError)
|
||||||
|
s.truesock.getpeername.assert_called_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_getsockname():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.getsockname).called_with().should_not.throw(AttributeError)
|
||||||
|
s.truesock.getsockname.assert_called_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_gettimeout():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.gettimeout).called_with().should_not.throw(AttributeError)
|
||||||
|
s.truesock.gettimeout.assert_called_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake_socket_passes_through_shutdown():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.truesock = MagicMock()
|
||||||
|
expect(s.shutdown).called_with(socket.SHUT_RD).should_not.throw(AttributeError)
|
||||||
|
s.truesock.shutdown.assert_called_with(socket.SHUT_RD)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unix_socket():
|
||||||
|
import socket
|
||||||
|
HTTPretty.enable()
|
||||||
|
|
||||||
|
# Create a UDS socket
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
server_address = './not-exist-socket'
|
||||||
|
try:
|
||||||
|
sock.connect(server_address)
|
||||||
|
except socket.error:
|
||||||
|
# We expect this, since the server_address does not exist
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_HTTPrettyRequest_json_body():
|
||||||
|
""" A content-type of application/json should parse a valid json body """
|
||||||
|
header = TEST_HEADER % {'content_type': 'application/json'}
|
||||||
|
test_dict = {'hello': 'world'}
|
||||||
|
request = HTTPrettyRequest(header, json.dumps(test_dict))
|
||||||
|
expect(request.parsed_body).to.equal(test_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_HTTPrettyRequest_invalid_json_body():
|
||||||
|
""" A content-type of application/json with an invalid json body should return the content unaltered """
|
||||||
|
header = TEST_HEADER % {'content_type': 'application/json'}
|
||||||
|
invalid_json = u"{'hello', 'world','thisstringdoesntstops}"
|
||||||
|
request = HTTPrettyRequest(header, invalid_json)
|
||||||
|
expect(request.parsed_body).to.equal(invalid_json)
|
||||||
|
|
||||||
|
|
||||||
|
def test_HTTPrettyRequest_queryparam():
|
||||||
|
""" A content-type of x-www-form-urlencoded with a valid queryparam body should return parsed content """
|
||||||
|
header = TEST_HEADER % {'content_type': 'application/x-www-form-urlencoded'}
|
||||||
|
valid_queryparam = u"hello=world&this=isavalidquerystring"
|
||||||
|
valid_results = {'hello': ['world'], 'this': ['isavalidquerystring']}
|
||||||
|
request = HTTPrettyRequest(header, valid_queryparam)
|
||||||
|
expect(request.parsed_body).to.equal(valid_results)
|
||||||
|
|
||||||
|
|
||||||
|
def test_HTTPrettyRequest_arbitrarypost():
|
||||||
|
""" A non-handled content type request's post body should return the content unaltered """
|
||||||
|
header = TEST_HEADER % {'content_type': 'thisis/notarealcontenttype'}
|
||||||
|
gibberish_body = "1234567890!@#$%^&*()"
|
||||||
|
request = HTTPrettyRequest(header, gibberish_body)
|
||||||
|
expect(request.parsed_body).to.equal(gibberish_body)
|
||||||
|
|
||||||
|
|
||||||
|
def test_socktype_bad_python_version_regression():
|
||||||
|
""" Some versions of python accidentally internally shadowed the SockType
|
||||||
|
variable, so it was no longer the socket object but and int Enum representing
|
||||||
|
the socket type e.g. AF_INET. Make sure we don't patch SockType in these cases
|
||||||
|
https://bugs.python.org/issue20386
|
||||||
|
"""
|
||||||
|
import socket
|
||||||
|
someObject = object()
|
||||||
|
with patch('socket.SocketType', someObject):
|
||||||
|
HTTPretty.enable()
|
||||||
|
expect(socket.SocketType).to.equal(someObject)
|
||||||
|
HTTPretty.disable()
|
||||||
|
|
||||||
|
|
||||||
|
def test_socktype_good_python_version():
|
||||||
|
import socket
|
||||||
|
with patch('socket.SocketType', socket.socket):
|
||||||
|
HTTPretty.enable()
|
||||||
|
expect(socket.SocketType).to.equal(socket.socket)
|
||||||
|
HTTPretty.disable()
|
||||||
|
|
||||||
|
|
||||||
|
def test_httpretty_should_allow_registering_regex_hostnames():
|
||||||
|
"HTTPretty should allow registering regexes with requests"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r'^http://\w+\.foo\.com/baz$'),
|
||||||
|
body="yay",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert HTTPretty.match_http_address('www.foo.com', 80)
|
||||||
|
|
||||||
|
|
||||||
|
def test_httpretty_should_allow_registering_regex_hostnames_ssl():
|
||||||
|
"HTTPretty should allow registering regexes with requests (ssl version)"
|
||||||
|
|
||||||
|
HTTPretty.register_uri(
|
||||||
|
HTTPretty.GET,
|
||||||
|
re.compile(r'^https://\w+\.foo\.com/baz$'),
|
||||||
|
body="yay",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert HTTPretty.match_https_hostname('www.foo.com')
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import httpretty
|
||||||
|
from httpretty.core import HTTPrettyRequest
|
||||||
|
|
||||||
|
try:
|
||||||
|
from unittest.mock import patch
|
||||||
|
except ImportError:
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.httpretty')
|
||||||
|
def test_last_request(original):
|
||||||
|
("httpretty.last_request() should return httpretty.core.last_request")
|
||||||
|
|
||||||
|
httpretty.last_request().should.equal(original.last_request)
|
||||||
|
|
||||||
|
|
||||||
|
@patch('httpretty.httpretty')
|
||||||
|
def test_latest_requests(original):
|
||||||
|
("httpretty.latest_requests() should return httpretty.core.latest_requests")
|
||||||
|
|
||||||
|
httpretty.latest_requests().should.equal(original.latest_requests)
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_request():
|
||||||
|
("httpretty.has_request() correctly detects "
|
||||||
|
"whether or not a request has been made")
|
||||||
|
httpretty.reset()
|
||||||
|
httpretty.has_request().should.be.false
|
||||||
|
with patch('httpretty.httpretty.last_request', return_value=HTTPrettyRequest('')):
|
||||||
|
httpretty.has_request().should.be.true
|
Loading…
Reference in New Issue