Import Upstream version 1.1.4

This commit is contained in:
su-fang 2023-02-06 15:37:15 +08:00
commit 4ea5d66812
83 changed files with 8986 additions and 0 deletions

22
COPYING Normal file
View File

@ -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.

9
MANIFEST.in Normal file
View File

@ -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

83
Makefile Normal file
View File

@ -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)

190
PKG-INFO Normal file
View File

@ -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

163
README.rst Normal file
View File

@ -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>`_

31
development.txt Normal file
View File

@ -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

BIN
docs/build/doctrees/acks.doctree vendored Normal file

Binary file not shown.

BIN
docs/build/doctrees/api.doctree vendored Normal file

Binary file not shown.

BIN
docs/build/doctrees/changelog.doctree vendored Normal file

Binary file not shown.

BIN
docs/build/doctrees/contributing.doctree vendored Normal file

Binary file not shown.

BIN
docs/build/doctrees/environment.pickle vendored Normal file

Binary file not shown.

BIN
docs/build/doctrees/guides.doctree vendored Normal file

Binary file not shown.

BIN
docs/build/doctrees/index.doctree vendored Normal file

Binary file not shown.

BIN
docs/build/doctrees/introduction.doctree vendored Normal file

Binary file not shown.

36
docs/make.bat Normal file
View File

@ -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

View File

@ -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'}))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"]

21
docs/source/acks.rst Normal file
View File

@ -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>`_

191
docs/source/api.rst Normal file
View File

@ -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:

212
docs/source/changelog.rst Normal file
View File

@ -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

87
docs/source/conf.py Normal file
View File

@ -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),
}

View File

@ -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>`_

119
docs/source/guides.rst Normal file
View File

@ -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

80
docs/source/index.rst Normal file
View File

@ -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`

View File

@ -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.

190
httpretty.egg-info/PKG-INFO Normal file
View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
httpretty

94
httpretty/__init__.py Normal file
View File

@ -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)

60
httpretty/compat.py Normal file
View File

@ -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',
]

2082
httpretty/core.py Normal file

File diff suppressed because it is too large Load Diff

48
httpretty/errors.py Normal file
View File

@ -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()))

153
httpretty/http.py Normal file
View File

@ -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

37
httpretty/utils.py Normal file
View File

@ -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)

1
httpretty/version.py Normal file
View File

@ -0,0 +1 @@
version = '1.1.4'

1
requirements.txt Normal file
View File

@ -0,0 +1 @@

18
setup.cfg Normal file
View File

@ -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

83
setup.py Normal file
View File

@ -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'
],
)

4
tests/__init__.py Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sure

View File

View File

@ -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)

View File

@ -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"})

View File

@ -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')

View File

@ -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")

View File

@ -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")

View File

@ -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')

View File

@ -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

View File

@ -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/')

View File

@ -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)

View File

@ -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')

View File

@ -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')

View File

@ -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()

View File

@ -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

4
tests/compat.py Normal file
View File

@ -0,0 +1,4 @@
try:
from unittest.mock import Mock, patch, call, MagicMock
except ImportError:
from mock import Mock, patch, call, MagicMock

View File

@ -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')

118
tests/functional/base.py Normal file
View File

@ -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

View File

@ -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"
}
}
}
]

View File

@ -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')

View File

@ -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"
)

View File

@ -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')

View File

@ -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))

View File

@ -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')

View File

@ -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)

View File

@ -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')

View File

@ -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")

View File

@ -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()

View File

@ -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')

View File

@ -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('/')

25
tests/unit/__init__.py Normal file
View File

@ -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.

668
tests/unit/test_core.py Normal file
View File

@ -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

17
tests/unit/test_http.py Normal file
View File

@ -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"))

View File

@ -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')

33
tests/unit/test_main.py Normal file
View File

@ -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

7
tox.ini Normal file
View File

@ -0,0 +1,7 @@
[tox]
envlist = py27, py36, py37
[testenv]
deps = pipenv
commands = make test