summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/bento.yml13
-rw-r--r--.github/workflows/pyenv.yml4
-rw-r--r--FUNDING.yml1
-rw-r--r--Makefile17
-rw-r--r--README.rst22
-rw-r--r--development.txt7
-rw-r--r--docs/Makefile7
-rw-r--r--docs/source/acks.rst4
-rw-r--r--docs/source/api.rst18
-rw-r--r--docs/source/changelog.rst92
-rw-r--r--docs/source/conf.py2
-rw-r--r--docs/source/index.rst13
-rw-r--r--docs/source/introduction.rst43
-rw-r--r--httpretty/core.py299
-rw-r--r--httpretty/errors.py21
-rw-r--r--httpretty/version.py2
-rw-r--r--setup.py4
-rw-r--r--tests/functional/bugfixes/test_242_ssl_bad_handshake.py9
-rw-r--r--tests/functional/bugfixes/test_387_regex_port.py26
-rw-r--r--tests/functional/bugfixes/test_388_unmocked_error_with_url.py56
-rw-r--r--tests/functional/bugfixes/test_413_regex.py39
-rw-r--r--tests/functional/bugfixes/test_414_httpx.py12
-rw-r--r--tests/functional/bugfixes/test_416_boto3.py32
-rw-r--r--tests/functional/bugfixes/test_417_openssl.py31
-rw-r--r--tests/functional/test_bypass.py6
-rw-r--r--tests/functional/test_debug.py10
-rw-r--r--tests/functional/test_httplib2.py20
-rw-r--r--tests/functional/test_passthrough.py9
-rw-r--r--tests/functional/test_requests.py50
-rw-r--r--tests/functional/test_urllib2.py20
-rw-r--r--tests/functional/testserver.py54
-rw-r--r--tests/unit/test_core.py6
32 files changed, 665 insertions, 284 deletions
diff --git a/.github/workflows/bento.yml b/.github/workflows/bento.yml
deleted file mode 100644
index f8a0944..0000000
--- a/.github/workflows/bento.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: Bento
-on: [pull_request]
-jobs:
- bento:
- runs-on: ubuntu-latest
- name: Check
- steps:
- - uses: actions/checkout@v2
- - name: Bento
- id: bento
- uses: returntocorp/bento-action@v1
- with:
- acceptTermsWithEmail: gabriel@nacaolivre.org
diff --git a/.github/workflows/pyenv.yml b/.github/workflows/pyenv.yml
index c7c9ef3..e5e6bb1 100644
--- a/.github/workflows/pyenv.yml
+++ b/.github/workflows/pyenv.yml
@@ -15,11 +15,13 @@ jobs:
python:
- 3.6.5
- 3.7.3
+ - 3.8.6
+ - 3.9.0
steps:
- uses: actions/checkout@v2
- name: Install python version
- uses: gabrielfalcao/pyenv-action@v4
+ uses: gabrielfalcao/pyenv-action@v7
with:
default: "${{ matrix.python }}"
diff --git a/FUNDING.yml b/FUNDING.yml
new file mode 100644
index 0000000..8fae09c
--- /dev/null
+++ b/FUNDING.yml
@@ -0,0 +1 @@
+custom: ["https://xscode.com/gabrielfalcao/HTTPretty"]
diff --git a/Makefile b/Makefile
index 610022b..3712aef 100644
--- a/Makefile
+++ b/Makefile
@@ -1,13 +1,11 @@
-.PHONY: tests all unit functional clean dependencies tdd docs html purge dist
+.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)
-BENTO_BIN := $(shell which bento)
DOCS_INDEX := $(HTML_ROOT)/index.html
-BENTO_EMAIL := gabriel@nacaolivre.org
export VENV
export PYTHONASYNCIODEBUG :=1
@@ -19,7 +17,7 @@ $(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/python $(VENV)/bin/pip: # installs latest pip
+setup $(VENV)/bin/sphinx-build $(VENV)/bin/twine $(VENV)/bin/nosetests $(VENV)/bin/python $(VENV)/bin/pip: # installs latest pip
test -e $(VENV)/bin/pip || make $(VENV)
$(VENV)/bin/pip install -r development.txt
$(VENV)/bin/pip install -e .
@@ -51,22 +49,19 @@ functional: $(VENV)/bin/nosetests # runs functional tests
-$(DOCS_INDEX): | $(VENV)/bin/sphinx-build
+$(DOCS_INDEX): $(VENV)/bin/sphinx-build
cd docs && make html
-html: $(DOCS_INDEX)
+html: $(DOCS_INDEX) $(VENV)/bin/sphinx-build
-docs: $(DOCS_INDEX)
+docs: $(DOCS_INDEX) $(VENV)/bin/sphinx-build
open $(DOCS_INDEX)
-release: | clean bento unit functional tests html
+release: | clean unit functional tests html
@rm -rf dist/*
@./.release
@make pypi
-bento: | $(BENTO_BIN)
- $(BENTO_BIN) --agree --email=$(BENTO_EMAIL) check --all
-
dist: | clean
$(VENV)/bin/python setup.py build sdist
diff --git a/README.rst b/README.rst
index e806b3c..8faad2f 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,10 @@
-HTTPretty 1.0.2
+HTTPretty 1.1.1
===============
-HTTP Client mocking tool for Python. Provides a full fake TCP socket module. Inspired by `FakeWeb <https://github.com/chrisk/fakeweb>`_
+.. 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/>`_
@@ -12,8 +15,8 @@ HTTP Client mocking tool for Python. Provides a full fake TCP socket module. Ins
- **3.6**
- **3.7**
-
-.. image:: https://github.com/gabrielfalcao/HTTPretty/raw/master/docs/source/_static/logo.svg?sanitize=true
+- **3.8**
+- **3.9**
.. image:: https://img.shields.io/pypi/dm/HTTPretty
:target: https://pypi.org/project/HTTPretty
@@ -21,10 +24,7 @@ HTTP Client mocking tool for Python. Provides a full fake TCP socket module. Ins
.. 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/python-3.6?label=python%203.6
- :target: https://github.com/gabrielfalcao/HTTPretty/actions
-
-.. image:: https://img.shields.io/github/workflow/status/gabrielfalcao/HTTPretty/python-3.7?label=python%203.7
+.. 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
@@ -87,7 +87,7 @@ Simple Example
import requests
- @httpretty.activate
+ @httpretty.activate(verbose=True, allow_net_connect=False)
def test_httpbin():
httpretty.register_uri(
httpretty.GET,
@@ -108,7 +108,7 @@ checking multiple responses
.. code:: python
- @httpretty.activate
+ @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)
@@ -125,7 +125,7 @@ License
::
<HTTPretty - HTTP client mock for Python>
- Copyright (C) <2011-2020> Gabriel Falcão <gabriel@nacaolivre.org>
+ 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
diff --git a/development.txt b/development.txt
index 72cdf21..8977c86 100644
--- a/development.txt
+++ b/development.txt
@@ -1,9 +1,11 @@
+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"
@@ -16,10 +18,11 @@ redis==3.4.1
rednose>=1.3.0
requests-toolbelt>=0.9.1
singledispatch>=3.4.0.3
-sphinx-rtd-theme>=0.4.3
-sphinx>=2.4.4
+sphinx-rtd-theme>=0.5.2
+sphinx>=4.0.1
sure>=1.4.11
tornado>=6.0.4
tox>=3.14.5
twine>=1.15.0
urllib3>=1.25.8
+boto3>=1.17.72
diff --git a/docs/Makefile b/docs/Makefile
index 898ba26..cf144d4 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -1,9 +1,12 @@
# Minimal makefile for Sphinx documentation
#
+GIT_ROOT := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))/..)
+VENV_ROOT := $(GIT_ROOT)/.venv
+VENV ?= $(VENV_ROOT)
# You can set these variables from the command line.
SPHINXOPTS =
-SPHINXBUILD = sphinx-build
+SPHINXBUILD = $(VENV)/bin/sphinx-build
SPHINXPROJ = HTTPretty
SOURCEDIR = source
BUILDDIR = build
@@ -17,4 +20,4 @@ help:
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
- @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/source/acks.rst b/docs/source/acks.rst
index ceffe06..036b795 100644
--- a/docs/source/acks.rst
+++ b/docs/source/acks.rst
@@ -1,7 +1,7 @@
Acknowledgements
################
-caveats
+Caveats
=======
``forcing_headers`` + ``Content-Length``
@@ -11,7 +11,7 @@ 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
+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:
diff --git a/docs/source/api.rst b/docs/source/api.rst
index a94f65b..71fe992 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -9,31 +9,37 @@ 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
@@ -45,6 +51,7 @@ activate
.. autoclass:: httpretty.activate
:members:
+ :noindex:
.. _httprettified:
@@ -53,6 +60,7 @@ httprettified
-------------
.. autofunction:: httpretty.core.httprettified
+ :noindex:
.. _enabled:
@@ -62,6 +70,7 @@ enabled
.. autoclass:: httpretty.enabled
:members:
+ :noindex:
.. _httprettized:
@@ -71,6 +80,8 @@ httprettized
.. autoclass:: httpretty.core.httprettized
:members:
+ :noindex:
+
.. _HTTPrettyRequest:
@@ -80,6 +91,7 @@ HTTPrettyRequest
.. autoclass:: httpretty.core.HTTPrettyRequest
:members:
+ :noindex:
.. _HTTPrettyRequestEmpty:
@@ -89,6 +101,7 @@ HTTPrettyRequestEmpty
.. autoclass:: httpretty.core.HTTPrettyRequestEmpty
:members:
+ :noindex:
.. _FakeSockFile:
@@ -97,6 +110,7 @@ FakeSockFile
.. autoclass:: httpretty.core.FakeSockFile
:members:
+ :noindex:
.. _FakeSSLSocket:
@@ -106,6 +120,7 @@ FakeSSLSocket
.. autoclass:: httpretty.core.FakeSSLSocket
:members:
+ :noindex:
.. _URIInfo:
@@ -115,6 +130,7 @@ URIInfo
.. autoclass:: httpretty.URIInfo
:members:
+ :noindex:
.. _URIMatcher:
@@ -124,6 +140,7 @@ URIMatcher
.. autoclass:: httpretty.URIMatcher
:members:
+ :noindex:
.. _Entry:
@@ -133,6 +150,7 @@ Entry
.. autoclass:: httpretty.Entry
:members:
+ :noindex:
.. _api modules:
diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst
index 8f70ae4..c36174a 100644
--- a/docs/source/changelog.rst
+++ b/docs/source/changelog.rst
@@ -1,9 +1,53 @@
Release Notes
=============
+Release 1.1.1
+-------------
-1.0.0
------
+- 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>`_.
@@ -23,8 +67,8 @@ Release Notes
- Clarify relation between ``enabled`` and ``httprettized`` in API docs.
- Align signature with builtin socket.
-0.9.4
------
+Release 0.9.4
+-------------
Improvements:
@@ -35,8 +79,8 @@ Improvements:
- Migrate from `pip <https://pypi.org/project/pip/>`_ to `pipenv <https://docs.pipenv.org/>`_
-0.8.4
------
+Release 0.8.4
+-------------
Improvements:
@@ -48,8 +92,8 @@ Bug fixes:
- POST requests being called twice
`#100 <https://github.com/gabrielfalcao/HTTPretty/pull/100>`__
-0.6.5
------
+Release 0.6.5
+-------------
Applied pull requests:
@@ -78,8 +122,8 @@ Applied pull requests:
`#87 <https://github.com/gabrielfalcao/HTTPretty/pull/87>`__ by
`mardiros <http://github.com/mardiros>`__
-0.6.2
------
+Release 0.6.2
+-------------
- Fixing bug of lack of trailing slashes
`#73 <https://github.com/gabrielfalcao/HTTPretty/issues/73>`__
@@ -90,8 +134,8 @@ Applied pull requests:
- Keyword arg coercion fix by @dupuy
- @papaeye fixed content-length calculation.
-0.6.1
------
+Release 0.6.1
+-------------
- New API, no more camel case and everything is available through a
simple import:
@@ -108,8 +152,8 @@ Applied pull requests:
- Re-organized module into submodules
-0.5.14
-------
+Release 0.5.14
+--------------
- Delegate calls to other methods on socket
@@ -122,30 +166,30 @@ Applied pull requests:
- Normalize urls matching for url quoting
-0.5.12
-------
+Release 0.5.12
+--------------
- HTTPretty doesn't hang when using other application protocols under a
@httprettified decorated test.
-0.5.11
-------
+Release 0.5.11
+--------------
- Ability to know whether HTTPretty is or not enabled through
``httpretty.is_enabled()``
-0.5.10
-------
+Release 0.5.10
+--------------
- Support to multiple methods per registered URL. Thanks @hughsaunders
-0.5.9
------
+Release 0.5.9
+-------------
- Fixed python 3 support. Thanks @spulec
-0.5.8
------
+Release 0.5.8
+-------------
- Support to `register regular expressions to match
urls <#matching-regular-expressions>`__
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 0218a7e..a50a23f 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -15,7 +15,7 @@ from httpretty.version import version # noqa
project = 'HTTPretty'
-copyright = '2020, Gabriel Falcao'
+copyright = '2011-2021, Gabriel Falcao'
author = 'Gabriel Falcao'
# The short X.Y version
diff --git a/docs/source/index.rst b/docs/source/index.rst
index b8ee1e5..9591c22 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -6,7 +6,9 @@
HTTPretty's - HTTP Client Mocking for Python
============================================
-HTTP Client mocking tool for Python. Provides a full fake TCP socket module. Inspired by `FakeWeb <https://github.com/chrisk/fakeweb>`_
+.. 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>`_ ?
@@ -14,8 +16,8 @@ Looking for the `Github Repository <https://github.com/gabrielfalcao/HTTPretty>`
- **3.6**
- **3.7**
-
-.. image:: https://github.com/gabrielfalcao/HTTPretty/raw/master/docs/source/_static/logo.svg?sanitize=true
+- **3.8**
+- **3.9**
.. image:: https://img.shields.io/pypi/dm/HTTPretty
:target: https://pypi.org/project/HTTPretty
@@ -23,10 +25,7 @@ Looking for the `Github Repository <https://github.com/gabrielfalcao/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/python-3.6?label=python%203.6
- :target: https://github.com/gabrielfalcao/HTTPretty/actions
-
-.. image:: https://img.shields.io/github/workflow/status/gabrielfalcao/HTTPretty/python-3.7?label=python%203.7
+.. 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
diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst
index 836a5d2..a5339d2 100644
--- a/docs/source/introduction.rst
+++ b/docs/source/introduction.rst
@@ -17,13 +17,15 @@ 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
+ @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"}]',
@@ -37,8 +39,13 @@ Don't worry, HTTPretty is here for you:
A more technical description
============================
-HTTPretty is a HTTP client mock library for Python 100% inspired on ruby's [FakeWeb](http://fakeweb.rubyforge.org/).
-If you come from ruby this would probably sound familiar :smiley:
+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
==========
@@ -65,7 +72,7 @@ expecting a simple response body
import httpretty
def test_one():
- httpretty.enable() # enable HTTPretty so that it will monkey patch the socket module
+ 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")
@@ -95,7 +102,7 @@ making assertions in a callback that generates the response body
return [200, response_headers, json.dumps({"hello": "world"})]
httpretty.register_uri(
- HTTPretty.POST, "https://httpretty.example.com/api",
+ 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"}')
@@ -160,20 +167,16 @@ problem:
*"I'm gonna need to mock all those requests"*
-It brings a lot of hassle, you will need to use a generic mocking
-tool, mess with scope and so on.
-
-The idea behind HTTPretty (how it works)
-========================================
-
-
-HTTPretty `monkey patches <http://en.wikipedia.org/wiki/Monkey_patch>`_
-Python's `socket <http://docs.python.org/library/socket.html>`_ core
-module, reimplementing the HTTP protocol, by mocking requests and
-responses.
+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.
-As for how it works this way, you don't need to worry what http
-library you're gonna use.
+HTTPretty `monkey patches
+<http://en.wikipedia.org/wiki/Monkey_patch>`_ Python's
+:py:mod:`socket` core module with a fake version of the module.
-HTTPretty will mock the response for you :) *(and also give you the
-latest requests so that you can check them)*
+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.
diff --git a/httpretty/core.py b/httpretty/core.py
index 8313354..28c001c 100644
--- a/httpretty/core.py
+++ b/httpretty/core.py
@@ -23,6 +23,7 @@
# OTHER DEALINGS IN THE SOFTWARE.
import io
+import time
import codecs
import contextlib
import functools
@@ -73,6 +74,7 @@ from datetime import timedelta
from errno import EAGAIN
old_socket = socket.socket
+old_socketpair = getattr(socket, 'socketpair', None)
old_SocketType = socket.SocketType
old_create_connection = socket.create_connection
old_gethostbyname = socket.gethostbyname
@@ -115,11 +117,23 @@ try:
except ImportError:
_ssl = None
# used to handle error caused by ndg-httpsclient
+pyopenssl_overrides_inject = []
+pyopenssl_overrides_extract = []
try:
from requests.packages.urllib3.contrib.pyopenssl import inject_into_urllib3, extract_from_urllib3
- pyopenssl_override = True
+ pyopenssl_overrides_extract.append(extract_from_urllib)
+ pyopenssl_overrides_inject.append(inject_from_urllib)
except Exception:
- pyopenssl_override = False
+ pass
+
+
+
+try:
+ from urllib3.contrib.pyopenssl import extract_from_urllib3, inject_into_urllib3
+ pyopenssl_overrides_extract.append(extract_from_urllib)
+ pyopenssl_overrides_inject.append(inject_from_urllib)
+except Exception:
+ pass
try:
@@ -146,8 +160,7 @@ def FALLBACK_FUNCTION(x):
class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass):
- r"""
- Represents a HTTP request. It takes a valid multi-line,
+ r"""Represents a HTTP request. It takes a valid multi-line,
``\r\n`` separated string with HTTP headers and parse them out using
the internal `parse_request` method.
@@ -160,15 +173,24 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass):
``headers`` -> a mimetype object that can be cast into a dictionary,
contains all the request headers
- ``method`` -> the HTTP method used in this request
+ ``protocol`` -> the protocol of this host, inferred from the port
+ of the underlying fake TCP socket.
+
+ ``host`` -> the hostname of this request.
+
+ ``url`` -> the full url of this request.
+
+ ``path`` -> the path of the request.
+
+ ``method`` -> the HTTP method used in this request.
``querystring`` -> a dictionary containing lists with the
attributes. Please notice that if you need a single value from a
query string you will need to get it manually like:
- ``body`` -> the request body as a string
+ ``body`` -> the request body as a string.
- ``parsed_body`` -> the request body parsed by ``parse_request_body``
+ ``parsed_body`` -> the request body parsed by ``parse_request_body``.
.. testcode::
@@ -176,16 +198,15 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass):
{'name': ['Gabriel Falcao']}
>>> print request.querystring['name'][0]
-
-
"""
- def __init__(self, headers, body=''):
+ def __init__(self, headers, body='', sock=None, path_encoding = 'iso-8859-1'):
# first of all, lets make sure that if headers or body are
# unicode strings, it must be converted into a utf-8 encoded
# byte string
+ self.created_at = time.time()
self.raw_headers = utf8(headers.strip())
self._body = utf8(body)
-
+ self.connection = sock
# Now let's concatenate the headers with the body, and create
# `rfile` based on it
self.rfile = io.BytesIO(b'\r\n\r\n'.join([self.raw_headers, self.body]))
@@ -205,14 +226,13 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass):
if not self.parse_request():
return
- # making the HTTP method string available as the command
- self.method = self.command
-
# Now 2 convenient attributes for the HTTPretty API:
- # `querystring` holds a dictionary with the parsed query string
+ # - `path`
+ # - `querystring` holds a dictionary with the parsed query string
+ # - `parsed_body` a string
try:
- self.path = self.path.encode('iso-8859-1')
+ self.path = self.path.encode(path_encoding)
except UnicodeDecodeError:
pass
@@ -232,6 +252,25 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass):
self.parsed_body = self.parse_request_body(self._body)
@property
+ def method(self):
+ """the HTTP method used in this request"""
+ return self.command
+
+ @property
+ def protocol(self):
+ """the protocol used in this request"""
+ proto = ''
+ if not self.connection:
+ return ''
+ elif self.connection.is_http:
+ proto = 'http'
+
+ if self.connection.is_secure:
+ proto = 'https'
+
+ return proto
+
+ @property
def body(self):
return self._body
@@ -246,11 +285,21 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass):
def __nonzero__(self):
return bool(self.body) or bool(self.raw_headers)
+ @property
+ def url(self):
+ """the full url of this recorded request"""
+ return "{}://{}{}".format(self.protocol, self.host, self.path)
+
+ @property
+ def host(self):
+ return self.headers.get('Host') or '<unknown>'
+
def __str__(self):
- tmpl = '<HTTPrettyRequest("{}", total_headers={}, body_length={})>'
+ tmpl = '<HTTPrettyRequest("{}", "{}", headers={}, body={})>'
return tmpl.format(
- self.headers.get('content-type', ''),
- len(self.headers),
+ self.method,
+ self.url,
+ dict(self.headers),
len(self.body),
)
@@ -327,7 +376,6 @@ class FakeSockFile(object):
def close(self):
self.socket.close()
- self.file.close()
def fileno(self):
return self._fileno
@@ -360,6 +408,10 @@ class FakeAddressTuple(object):
raise AssertionError('socket {} is not connected'.format(self.fakesocket.truesock))
+def fake_socketpair(*args, **kw):
+ with restored_libs():
+ return old_socketpair(*args, **kw)
+
class fakesock(object):
"""
fake :py:mod:`socket`
@@ -368,9 +420,11 @@ class fakesock(object):
"""drop-in replacement for :py:class:`socket.socket`
"""
_entry = None
+ _read_buf = None
+
debuglevel = 0
_sent_data = []
-
+ is_secure = False
def __init__(
self,
family=socket.AF_INET,
@@ -395,7 +449,14 @@ class fakesock(object):
self.is_http = False
self._bufsize = 32 * 1024
- def create_socket(self):
+ def __repr__(self):
+ return '{self.__class__.__module__}.{self.__class__.__name__}("{self.host}")'.format(**locals())
+
+ @property
+ def host(self):
+ return ":".join(map(str, self._address))
+
+ def create_socket(self, address=None):
return old_socket(self.socket_family, self.socket_type, self.socket_proto)
def getpeercert(self, *a, **kw):
@@ -447,14 +508,15 @@ class fakesock(object):
ports_to_check = (
POTENTIAL_HTTP_PORTS.union(POTENTIAL_HTTPS_PORTS))
self.is_http = self._port in ports_to_check
+ self.is_secure = self._port in POTENTIAL_HTTPS_PORTS
if not self.is_http:
- self.connect_truesock()
- elif self.truesock and not self.__truesock_is_connected__:
+ self.connect_truesock(address=address)
+ elif self.truesock and not self.real_socket_is_connected():
# TODO: remove nested if
matcher = httpretty.match_http_address(self._host, self._port)
if matcher is None:
- self.connect_truesock()
+ self.connect_truesock(address=address)
def bind(self, address):
self._address = (self._host, self._port) = address
@@ -465,19 +527,30 @@ class fakesock(object):
if httpretty.allow_net_connect and not self.truesock:
self.truesock = self.create_socket()
elif not self.truesock:
- raise UnmockedError()
+ raise UnmockedError('Failed to socket.bind() because because a real socket was never created.', address=address)
return self.truesock.bind(address)
- def connect_truesock(self):
- if httpretty.allow_net_connect and not self.truesock:
- self.truesock = self.create_socket()
- elif not self.truesock:
- raise UnmockedError()
+ def connect_truesock(self, request=None, address=None):
+ address = address or self._address
if self.__truesock_is_connected__:
return self.truesock
- with restored_libs():
+
+ if request:
+ logger.warning('real call to socket.connect() for {request}'.format(**locals()))
+ elif address:
+ logger.warning('real call to socket.connect() for {address}'.format(**locals()))
+ else:
+ logger.warning('real call to socket.connect()')
+
+ if httpretty.allow_net_connect and not self.truesock:
+ self.truesock = self.create_socket(address)
+ elif not self.truesock:
+ raise UnmockedError('Failed to socket.connect() because because a real socket was never created.', request=request, address=address)
+
+ undo_patch_socket()
+ try:
hostname = self._address[0]
port = 80
if len(self._address) == 2:
@@ -490,6 +563,9 @@ class fakesock(object):
sock.connect(self._address)
self.__truesock_is_connected__ = True
self.truesock = sock
+ finally:
+ apply_patch_socket()
+
return self.truesock
def real_socket_is_connected(self):
@@ -540,17 +616,22 @@ class fakesock(object):
buffer so that HTTPretty can return it accordingly when
necessary.
"""
+ request = kw.pop('request', None)
+ if request:
+ bytecount = len(data)
+ logger.warning('{self}.real_sendall({bytecount} bytes) to {request.url} via {request.method} at {request.created_at}'.format(**locals()))
if httpretty.allow_net_connect and not self.truesock:
- self.connect_truesock()
+
+ self.connect_truesock(request=request)
elif not self.truesock:
- raise UnmockedError()
+ raise UnmockedError(request=request)
if not self.is_http:
self.truesock.setblocking(1)
return self.truesock.sendall(data, *args, **kw)
- sock = self.connect_truesock()
+ sock = self.connect_truesock(request=request)
sock.setblocking(1)
sock.sendall(data, *args, **kw)
@@ -612,7 +693,7 @@ class fakesock(object):
else:
self._entry.request.body += body
- httpretty.historify_request(headers, body, False)
+ httpretty.historify_request(headers, body, sock=self)
return
if path[:2] == '//':
@@ -627,7 +708,7 @@ class fakesock(object):
headers = ''
body = data
- request = httpretty.historify_request(headers, body)
+ request = httpretty.historify_request(headers, body, sock=self)
info = URIInfo(
hostname=self._host,
@@ -640,27 +721,17 @@ class fakesock(object):
matcher, entries = httpretty.match_uriinfo(info)
if not entries:
+ logger.debug('no entries matching {}'.format(request))
self._entry = None
- self.real_sendall(data)
+ self._read_buf = None
+ self.real_sendall(data, request=request)
return
self._entry = matcher.get_next_entry(method, info, request)
def forward_and_trace(self, function_name, *a, **kw):
- if self.truesock and not self.__truesock_is_connected__:
- self.truesock = self.create_socket()
- ### self.connect_truesock()
-
- if self.__truesock_is_connected__:
- function = getattr(self.truesock, function_name)
-
- if self.is_http:
- if self.truesock and not self.__truesock_is_connected__:
- self.truesock = self.create_socket()
- ### self.connect_truesock()
-
if not self.truesock:
- raise UnmockedError()
+ raise UnmockedError('Failed to socket.{}() because because a real socket was never created.'.format(function_name))
callback = getattr(self.truesock, function_name)
return callback(*a, **kw)
@@ -671,8 +742,9 @@ class fakesock(object):
if self.truesock:
self.truesock.settimeout(new_timeout)
- def send(self, *args, **kwargs):
- return self.forward_and_trace('send', *args, **kwargs)
+ def send(self, data, *args, **kwargs):
+ self.sendall(data, *args, **kwargs)
+ return len(data)
def sendto(self, *args, **kwargs):
return self.forward_and_trace('sendto', *args, **kwargs)
@@ -681,44 +753,68 @@ class fakesock(object):
return self.forward_and_trace('recvfrom_into', *args, **kwargs)
def recv_into(self, *args, **kwargs):
- if self.truesock and not self.__truesock_is_connected__:
- self.connect_truesock()
return self.forward_and_trace('recv_into', *args, **kwargs)
def recvfrom(self, *args, **kwargs):
- if self.truesock and not self.__truesock_is_connected__:
- self.connect_truesock()
return self.forward_and_trace('recvfrom', *args, **kwargs)
- def recv(self, buffersize=None, *args, **kwargs):
- if self.truesock and not self.__truesock_is_connected__:
- self.connect_truesock()
- buffersize = buffersize or self._bufsize
- return self.forward_and_trace('recv', buffersize, *args, **kwargs)
+ def recv(self, buffersize=0, *args, **kwargs):
+ if not self._read_buf:
+ self._read_buf = io.BytesIO()
+
+ if self._entry:
+ self._entry.fill_filekind(self._read_buf)
+
+ if not self._read_buf:
+ raise UnmockedError('socket cannot recv(): {!r}'.format(self))
+
+ return self._read_buf.read(buffersize)
def __getattr__(self, name):
- if httpretty.allow_net_connect and not self.truesock:
+ if name in ('getsockopt', 'selected_alpn_protocol') and not self.truesock:
+ self.truesock = self.create_socket()
+ elif httpretty.allow_net_connect and not self.truesock:
# can't call self.connect_truesock() here because we
# don't know if user wants to execute server of client
# calls (or can they?)
self.truesock = self.create_socket()
elif not self.truesock:
- raise UnmockedError()
+ # Special case for
+ # `hasattr(sock, "version")` call added in urllib3>=1.26.
+ if name == 'version':
+ raise AttributeError(
+ "HTTPretty synthesized this error to fix urllib3 compatibility "
+ "(see issue https://github.com/gabrielfalcao/HTTPretty/issues/409). "
+ "Please open an issue if this error causes further unexpected issues."
+ )
+
+ raise UnmockedError('Failed to socket.{} because because a real socket does not exist'.format(name))
+
return getattr(self.truesock, name)
+def with_socket_is_secure(sock, kw):
+ sock.is_secure = True
+ sock.kwargs = kw
+ for k, v in kw.items():
+ setattr(sock, k, v)
+ return sock
def fake_wrap_socket(orig_wrap_socket_fn, *args, **kw):
"""drop-in replacement for py:func:`ssl.wrap_socket`
"""
+ if 'sock' in kw:
+ sock = kw['sock']
+ else:
+ sock = args[0]
+
server_hostname = kw.get('server_hostname')
if server_hostname is not None:
matcher = httpretty.match_https_hostname(server_hostname)
if matcher is None:
- return orig_wrap_socket_fn(*args, **kw)
- if 'sock' in kw:
- return kw['sock']
- else:
- return args[0]
+ logger.debug('no requests registered for hostname: "{}"'.format(server_hostname))
+ return with_socket_is_secure(sock, kw)
+
+ return with_socket_is_secure(sock, kw)
def create_fake_connection(
@@ -1286,7 +1382,7 @@ class httpretty(HttpBaseClass):
@classmethod
@contextlib.contextmanager
- def record(cls, filename, indentation=4, encoding='utf-8'):
+ def record(cls, filename, indentation=4, encoding='utf-8', verbose=False, allow_net_connect=True, pool_manager_params=None):
"""
.. testcode::
@@ -1316,9 +1412,9 @@ class httpretty(HttpBaseClass):
)
raise RuntimeError(msg)
- http = urllib3.PoolManager()
+ http = urllib3.PoolManager(**pool_manager_params or {})
- cls.enable()
+ cls.enable(allow_net_connect, verbose=verbose)
calls = []
def record_request(request, uri, headers):
@@ -1347,7 +1443,7 @@ class httpretty(HttpBaseClass):
'headers': dict(response.headers.items())
}
})
- cls.enable()
+ cls.enable(allow_net_connect, verbose=verbose)
return response.status, response.headers, response.data
for method in cls.METHODS:
@@ -1360,7 +1456,7 @@ class httpretty(HttpBaseClass):
@classmethod
@contextlib.contextmanager
- def playback(cls, filename):
+ def playback(cls, filename, allow_net_connect=True, verbose=False):
"""
.. testcode::
@@ -1378,7 +1474,7 @@ class httpretty(HttpBaseClass):
:param filename: a string
:returns: a `context-manager <https://docs.python.org/3/reference/datamodel.html#context-managers>`_
"""
- cls.enable()
+ cls.enable(allow_net_connect, verbose=verbose)
data = json.loads(open(filename).read())
for item in data:
@@ -1402,7 +1498,7 @@ class httpretty(HttpBaseClass):
cls.last_request = HTTPrettyRequestEmpty()
@classmethod
- def historify_request(cls, headers, body='', append=True):
+ def historify_request(cls, headers, body='', sock=None):
"""appends request to a list for later retrieval
.. testcode::
@@ -1415,12 +1511,16 @@ class httpretty(HttpBaseClass):
assert httpretty.latest_requests[-1].url == 'https://httpbin.org/ip'
"""
- request = HTTPrettyRequest(headers, body)
+ request = HTTPrettyRequest(headers, body, sock=sock)
cls.last_request = request
- if append or not cls.latest_requests:
+
+ if request not in cls.latest_requests:
cls.latest_requests.append(request)
else:
- cls.latest_requests[-1] = request
+ pos = cls.latest_requests.index(request)
+ cls.latest_requests[pos] = request
+
+ logger.info("captured: {}".format(request))
return request
@classmethod
@@ -1469,7 +1569,7 @@ class httpretty(HttpBaseClass):
"""
uri_is_string = isinstance(uri, str)
- if uri_is_string and re.search(r'^\w+://[^/]+[.]\w{2,}$', uri):
+ if uri_is_string and re.search(r'^\w+://[^/]+[.]\w{2,}(:[0-9]+)?$', uri):
uri += '/'
if isinstance(responses, list) and len(responses) > 0:
@@ -1578,16 +1678,18 @@ class httpretty(HttpBaseClass):
return cls._is_enabled
@classmethod
- def enable(cls, allow_net_connect=True):
+ def enable(cls, allow_net_connect=True, verbose=False):
"""Enables HTTPretty.
- When ``allow_net_connect`` is ``False`` any connection to an unregistered uri will throw :py:class:`httpretty.errors.UnmockedError`.
+
+ :param allow_net_connect: boolean to determine if unmatched requests are forwarded to a real network connection OR throw :py:class:`httpretty.errors.UnmockedError`.
+ :param verbose: boolean to set HTTPretty's logging level to DEBUG
.. testcode::
import re, json
import httpretty
- httpretty.enable()
+ httpretty.enable(allow_net_connect=True, verbose=True)
httpretty.register_uri(
httpretty.GET,
@@ -1604,9 +1706,13 @@ class httpretty(HttpBaseClass):
.. warning:: after calling this method the original :py:mod:`socket` is replaced with :py:class:`httpretty.core.fakesock`. Make sure to call :py:meth:`~httpretty.disable` after done with your tests or use the :py:class:`httpretty.enabled` as decorator or `context-manager <https://docs.python.org/3/reference/datamodel.html#context-managers>`_
"""
- cls.allow_net_connect = allow_net_connect
+ httpretty.allow_net_connect = allow_net_connect
apply_patch_socket()
cls._is_enabled = True
+ if verbose:
+ logger.setLevel(logging.DEBUG)
+ else:
+ logger.setLevel(logging.getLogger().level)
def apply_patch_socket():
@@ -1616,6 +1722,7 @@ def apply_patch_socket():
new_wrap = None
socket.socket = fakesock.socket
+ socket.socketpair = fake_socketpair
socket._socketobject = fakesock.socket
if not bad_socket_shadow:
socket.SocketType = fakesock.socket
@@ -1626,6 +1733,7 @@ def apply_patch_socket():
socket.getaddrinfo = fake_getaddrinfo
socket.__dict__['socket'] = fakesock.socket
+ socket.__dict__['socketpair'] = fake_socketpair
socket.__dict__['_socketobject'] = fakesock.socket
if not bad_socket_shadow:
socket.__dict__['SocketType'] = fakesock.socket
@@ -1636,9 +1744,9 @@ def apply_patch_socket():
socket.__dict__['getaddrinfo'] = fake_getaddrinfo
- if pyopenssl_override:
- # Take out the pyopenssl version - use the default implementation
- extract_from_urllib3()
+ # Take out the pyopenssl version - use the default implementation
+ for extract_from_urllib3 in pyopenssl_overrides_extract:
+ extract_into_urllib3()
if requests_urllib3_connection is not None:
urllib3_wrap = partial(fake_wrap_socket, old_requests_ssl_wrap_socket)
@@ -1672,6 +1780,7 @@ def apply_patch_socket():
def undo_patch_socket():
socket.socket = old_socket
+ socket.socketpair = old_socketpair
socket.SocketType = old_SocketType
socket._socketobject = old_socket
@@ -1681,6 +1790,7 @@ def undo_patch_socket():
socket.getaddrinfo = old_getaddrinfo
socket.__dict__['socket'] = old_socket
+ socket.__dict__['socketpair'] = old_socketpair
socket.__dict__['_socketobject'] = old_socket
socket.__dict__['SocketType'] = old_SocketType
@@ -1709,8 +1819,9 @@ def undo_patch_socket():
requests_urllib3_connection.__dict__['ssl_wrap_socket'] = \
old_requests_ssl_wrap_socket
- if pyopenssl_override:
- # Put the pyopenssl version back in place
+
+ # Put the pyopenssl version back in place
+ for inject_from_urllib3 in pyopenssl_overrides_inject:
inject_into_urllib3()
@@ -1738,19 +1849,20 @@ class httprettized(object):
assert httpretty.latest_requests[-1].url == 'https://httpbin.org/ip'
assert response.json() == {'origin': '42.42.42.42'}
"""
- def __init__(self, allow_net_connect=True):
+ def __init__(self, allow_net_connect=True, verbose=False):
self.allow_net_connect = allow_net_connect
+ self.verbose = verbose
def __enter__(self):
httpretty.reset()
- httpretty.enable(allow_net_connect=self.allow_net_connect)
+ httpretty.enable(allow_net_connect=self.allow_net_connect, verbose=self.verbose)
def __exit__(self, exc_type, exc_value, db):
httpretty.disable()
httpretty.reset()
-def httprettified(test=None, allow_net_connect=True):
+def httprettified(test=None, allow_net_connect=True, verbose=False):
"""decorator for test functions
.. tip:: Also available under the alias :py:func:`httpretty.activate`
@@ -1808,7 +1920,7 @@ def httprettified(test=None, allow_net_connect=True):
def new_setUp(self):
httpretty.reset()
- httpretty.enable(allow_net_connect)
+ httpretty.enable(allow_net_connect, verbose=verbose)
if use_addCleanup:
self.addCleanup(httpretty.disable)
if original_setUp:
@@ -1848,7 +1960,6 @@ def httprettified(test=None, allow_net_connect=True):
except ImportError:
return False
- "A decorator for tests that use HTTPretty"
def decorate_class(klass):
if is_unittest_TestCase(klass):
return decorate_unittest_TestCase_setUp(klass)
diff --git a/httpretty/errors.py b/httpretty/errors.py
index d0a55a9..10faca3 100644
--- a/httpretty/errors.py
+++ b/httpretty/errors.py
@@ -25,15 +25,24 @@
# 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):
- super(UnmockedError, self).__init__(
- 'No mocking was registered, and real connections are '
- 'not allowed (httpretty.allow_net_connect = False).'
- )
+ 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()))
diff --git a/httpretty/version.py b/httpretty/version.py
index 4eda78c..813ff3b 100644
--- a/httpretty/version.py
+++ b/httpretty/version.py
@@ -1 +1 @@
-version = '1.0.2'
+version = '1.1.1'
diff --git a/setup.py b/setup.py
index 758d6e4..b0f5a56 100644
--- a/setup.py
+++ b/setup.py
@@ -52,7 +52,7 @@ setup(
long_description=local_file('README.rst'),
author='Gabriel Falcao',
author_email='gabriel@nacaolivre.org',
- url='https://httpretty.readthedocs.io',
+ url='https://httpretty.readthedocs.io/en/latest/',
zip_safe=False,
packages=find_packages(exclude=['*tests*']),
tests_require=local_file('development.txt').splitlines(),
@@ -67,6 +67,8 @@ setup(
'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'
diff --git a/tests/functional/bugfixes/test_242_ssl_bad_handshake.py b/tests/functional/bugfixes/test_242_ssl_bad_handshake.py
index 7e4bbc0..0653b41 100644
--- a/tests/functional/bugfixes/test_242_ssl_bad_handshake.py
+++ b/tests/functional/bugfixes/test_242_ssl_bad_handshake.py
@@ -14,3 +14,12 @@ def test_test_ssl_bad_handshake():
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)
diff --git a/tests/functional/bugfixes/test_387_regex_port.py b/tests/functional/bugfixes/test_387_regex_port.py
new file mode 100644
index 0000000..c3f90cd
--- /dev/null
+++ b/tests/functional/bugfixes/test_387_regex_port.py
@@ -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"})
diff --git a/tests/functional/bugfixes/test_388_unmocked_error_with_url.py b/tests/functional/bugfixes/test_388_unmocked_error_with_url.py
new file mode 100644
index 0000000..751d0ad
--- /dev/null
+++ b/tests/functional/bugfixes/test_388_unmocked_error_with_url.py
@@ -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():
+ "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')
diff --git a/tests/functional/bugfixes/test_413_regex.py b/tests/functional/bugfixes/test_413_regex.py
new file mode 100644
index 0000000..2131f7f
--- /dev/null
+++ b/tests/functional/bugfixes/test_413_regex.py
@@ -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")
diff --git a/tests/functional/bugfixes/test_414_httpx.py b/tests/functional/bugfixes/test_414_httpx.py
new file mode 100644
index 0000000..6360ddc
--- /dev/null
+++ b/tests/functional/bugfixes/test_414_httpx.py
@@ -0,0 +1,12 @@
+import httpretty
+import httpx
+from sure import expect
+
+@httpretty.activate(verbose=True, allow_net_connect=False)
+def test_httpx():
+ httpretty.register_uri(httpretty.GET, "https://blog.falcao.it/",
+ body="Posts")
+
+ response = httpx.get('https://blog.falcao.it')
+
+ expect(response.text).to.equal("Posts")
diff --git a/tests/functional/bugfixes/test_416_boto3.py b/tests/functional/bugfixes/test_416_boto3.py
new file mode 100644
index 0000000..9d11eaa
--- /dev/null
+++ b/tests/functional/bugfixes/test_416_boto3.py
@@ -0,0 +1,32 @@
+import httpretty
+import boto3
+from botocore.exceptions import ClientError
+
+from sure import expect
+
+
+@httpretty.activate(allow_net_connect=False, verbose=True)
+def test_boto3():
+ 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')
diff --git a/tests/functional/bugfixes/test_417_openssl.py b/tests/functional/bugfixes/test_417_openssl.py
new file mode 100644
index 0000000..29d82da
--- /dev/null
+++ b/tests/functional/bugfixes/test_417_openssl.py
@@ -0,0 +1,31 @@
+# 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():
+ 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():
+ expect(urllib3.util.IS_PYOPENSSL).to.be.false
+ httpretty.enable()
+ httpretty.disable()
+ extract_from_urllib3()
+ expect(urllib3.util.IS_PYOPENSSL).to.be.false
diff --git a/tests/functional/test_bypass.py b/tests/functional/test_bypass.py
index 97dec3d..904bc12 100644
--- a/tests/functional/test_bypass.py
+++ b/tests/functional/test_bypass.py
@@ -120,7 +120,7 @@ def test_httpretty_bypasses_when_disabled(context):
core.POTENTIAL_HTTP_PORTS.remove(context.http_port)
-@httpretty.activate
+@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"
@@ -143,7 +143,7 @@ def test_httpretty_bypasses_a_unregistered_request(context):
core.POTENTIAL_HTTP_PORTS.remove(context.http_port)
-@httpretty.activate
+@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"
@@ -163,7 +163,7 @@ def test_using_httpretty_with_other_tcp_protocols(context):
@httpretty.activate(allow_net_connect=False)
@that_with_context(start_http_server, stop_http_server)
-def test_disallow_net_connect_1(context):
+def test_disallow_net_connect_1(context, verbose=True):
"""
When allow_net_connect = False, a request that otherwise
would have worked results in UnmockedError.
diff --git a/tests/functional/test_debug.py b/tests/functional/test_debug.py
index 86de965..ff00840 100644
--- a/tests/functional/test_debug.py
+++ b/tests/functional/test_debug.py
@@ -58,16 +58,6 @@ def test_httpretty_debugs_socket_sendto(context):
)
-@httprettified
-@scenario(create_socket)
-def test_httpretty_debugs_socket_recv(context):
- "HTTPretty should forward_and_trace socket.recv"
-
- expect(context.sock.recv).when.called.to.throw(
- "not connected"
- )
-
-
@skip('not currently supported')
@httprettified
@scenario(create_socket)
diff --git a/tests/functional/test_httplib2.py b/tests/functional/test_httplib2.py
index 5901c85..9241f51 100644
--- a/tests/functional/test_httplib2.py
+++ b/tests/functional/test_httplib2.py
@@ -29,13 +29,13 @@ from __future__ import unicode_literals
import re
import httplib2
from freezegun import freeze_time
-from sure import expect, within, microseconds
+from sure import expect, within, miliseconds
from httpretty import HTTPretty, httprettified
from httpretty.core import decode_utf8
@httprettified
-@within(two=microseconds)
+@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"
@@ -50,7 +50,7 @@ def test_httpretty_should_mock_a_simple_get_with_httplib2_read(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_provides_easy_access_to_querystrings(now):
"HTTPretty should provide an easy access to the querystring"
@@ -112,7 +112,7 @@ def test_httpretty_should_allow_adding_and_overwritting_httplib2():
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_should_allow_forcing_headers_httplib2(now):
"HTTPretty should allow forcing headers with httplib2"
@@ -165,7 +165,7 @@ def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2():
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_rotating_responses_with_httplib2(now):
"HTTPretty should support rotating responses with httplib2"
@@ -196,7 +196,7 @@ def test_rotating_responses_with_httplib2(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_can_inspect_last_request(now):
"HTTPretty.last_request is a mimetools.Message request from last match"
@@ -222,7 +222,7 @@ def test_can_inspect_last_request(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_can_inspect_last_request_with_ssl(now):
"HTTPretty.last_request is recorded even when mocking 'https' (SSL)"
@@ -248,7 +248,7 @@ def test_can_inspect_last_request_with_ssl(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_ignores_querystrings_from_registered_uri(now):
"Registering URIs with query string cause them to be ignored"
@@ -263,9 +263,9 @@ def test_httpretty_ignores_querystrings_from_registered_uri(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_callback_response(now):
- ("HTTPretty should all a callback function to be set as the body with"
+ ("HTTPretty should call a callback function to be set as the body with"
" httplib2")
def request_callback(request, uri, headers):
diff --git a/tests/functional/test_passthrough.py b/tests/functional/test_passthrough.py
index 5bb3485..14ad2ae 100644
--- a/tests/functional/test_passthrough.py
+++ b/tests/functional/test_passthrough.py
@@ -24,7 +24,6 @@
import requests
import httpretty
-from unittest import skip
from sure import expect
@@ -42,15 +41,15 @@ def test_http_passthrough():
response1 = http().get(url)
- httpretty.enable()
+ 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="")
+ 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(response1.content)
+ response3.content.should.equal(b"mocked")
httpretty.disable()
@@ -63,7 +62,7 @@ def test_https_passthrough():
response1 = http().get(url)
- httpretty.enable()
+ httpretty.enable(allow_net_connect=True, verbose=True)
httpretty.register_uri(httpretty.GET, 'https://google.com/', body="Not Google")
httpretty.register_uri(httpretty.GET, url, body="mocked")
diff --git a/tests/functional/test_requests.py b/tests/functional/test_requests.py
index 0ee6ab9..cd77255 100644
--- a/tests/functional/test_requests.py
+++ b/tests/functional/test_requests.py
@@ -30,9 +30,8 @@ import signal
import httpretty
from freezegun import freeze_time
-from unittest import skip
from contextlib import contextmanager
-from sure import within, microseconds, expect
+from sure import within, miliseconds, expect
from tornado import version as tornado_version
from httpretty import HTTPretty, httprettified
from httpretty.core import decode_utf8
@@ -55,7 +54,7 @@ server_url = lambda path, port: "http://localhost:{}/{}".format(port, path.lstri
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_should_mock_a_simple_get_with_requests_read(now):
"HTTPretty should mock a simple GET with requests.get"
@@ -69,7 +68,7 @@ def test_httpretty_should_mock_a_simple_get_with_requests_read(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_hostname_case_insensitive(now):
"HTTPretty should match the hostname case insensitive"
@@ -83,7 +82,7 @@ def test_hostname_case_insensitive(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_provides_easy_access_to_querystrings(now):
"HTTPretty should provide an easy access to the querystring"
@@ -145,7 +144,7 @@ def test_httpretty_should_allow_adding_and_overwritting_requests():
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_should_allow_forcing_headers_requests(now):
"HTTPretty should allow forcing headers with requests"
@@ -189,7 +188,7 @@ def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2():
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_rotating_responses_with_requests(now):
"HTTPretty should support rotating responses with requests"
@@ -220,7 +219,7 @@ def test_rotating_responses_with_requests(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_can_inspect_last_request(now):
"HTTPretty.last_request is a mimetools.Message request from last match"
@@ -246,7 +245,7 @@ def test_can_inspect_last_request(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_can_inspect_last_request_with_ssl(now):
"HTTPretty.last_request is recorded even when mocking 'https' (SSL)"
@@ -272,7 +271,7 @@ def test_can_inspect_last_request_with_ssl(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_ignores_querystrings_from_registered_uri(now):
"HTTPretty should ignore querystrings from the registered uri (requests library)"
@@ -286,7 +285,7 @@ def test_httpretty_ignores_querystrings_from_registered_uri(now):
@httprettified
-@within(five=microseconds)
+@within(five=miliseconds)
def test_streaming_responses(now):
"""
Mock a streaming HTTP response, like those returned by the Twitter streaming
@@ -388,7 +387,7 @@ def test_streaming_responses(now):
@httprettified
def test_multiline():
- url = 'http://httpbin.org/post'
+ 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',
@@ -402,16 +401,18 @@ def test_multiline():
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(1)
+ expect(len(HTTPretty.latest_requests)).to.equal(2)
@httprettified
def test_octet_stream():
- url = 'http://httpbin.org/post'
+ url = 'https://httpbin.org/post'
data = b"\xf5\x00\x00\x00" # utf-8 with invalid start byte
headers = {
'Content-Type': 'application/octet-stream',
@@ -424,16 +425,18 @@ def test_octet_stream():
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(1)
+ expect(len(HTTPretty.latest_requests)).to.equal(2)
@httprettified
def test_multipart():
- url = 'http://httpbin.org/post'
+ 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(
@@ -443,15 +446,17 @@ def test_multipart():
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(1)
+ expect(len(HTTPretty.latest_requests)).to.equal(2)
@httprettified
-@within(two=microseconds)
+@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")
@@ -480,7 +485,7 @@ def test_callback_response(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_callback_body_remains_callable_for_any_subsequent_requests(now):
("HTTPretty should call a callback function more than one"
" requests")
@@ -500,7 +505,7 @@ def test_callback_body_remains_callable_for_any_subsequent_requests(now):
@httprettified
-@within(two=microseconds)
+@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")
@@ -551,7 +556,7 @@ def test_httpretty_should_respect_matcher_priority():
@httprettified
-@within(two=microseconds)
+@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")
@@ -621,8 +626,7 @@ def test_httpretty_provides_easy_access_to_querystrings_with_regexes():
})
-@skip('TODO: refactor this flaky test')
-@httprettified
+@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"
diff --git a/tests/functional/test_urllib2.py b/tests/functional/test_urllib2.py
index 06229aa..fb36eb5 100644
--- a/tests/functional/test_urllib2.py
+++ b/tests/functional/test_urllib2.py
@@ -35,13 +35,13 @@ except ImportError:
urlopen = urllib2.urlopen
from freezegun import freeze_time
-from sure import within, microseconds
+from sure import within, miliseconds
from httpretty import HTTPretty, httprettified
from httpretty.core import decode_utf8
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_should_mock_a_simple_get_with_urllib2_read():
"HTTPretty should mock a simple GET with urllib2.read()"
@@ -56,7 +56,7 @@ def test_httpretty_should_mock_a_simple_get_with_urllib2_read():
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_provides_easy_access_to_querystrings(now):
"HTTPretty should provide an easy access to the querystring"
@@ -127,7 +127,7 @@ def test_httpretty_should_allow_adding_and_overwritting_urllib2():
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_should_allow_forcing_headers_urllib2():
"HTTPretty should allow forcing headers with urllib2"
@@ -177,7 +177,7 @@ def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2():
@httprettified
-@within(two=microseconds)
+@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")
@@ -210,7 +210,7 @@ def test_httpretty_should_support_a_list_of_successive_responses_urllib2(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_can_inspect_last_request(now):
"HTTPretty.last_request is a mimetools.Message request from last match"
@@ -239,7 +239,7 @@ def test_can_inspect_last_request(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_can_inspect_last_request_with_ssl(now):
"HTTPretty.last_request is recorded even when mocking 'https' (SSL)"
@@ -268,7 +268,7 @@ def test_can_inspect_last_request_with_ssl(now):
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_httpretty_ignores_querystrings_from_registered_uri():
"HTTPretty should mock a simple GET with urllib2.read()"
@@ -285,9 +285,9 @@ def test_httpretty_ignores_querystrings_from_registered_uri():
@httprettified
-@within(two=microseconds)
+@within(two=miliseconds)
def test_callback_response(now):
- ("HTTPretty should all a callback function to be set as the body with"
+ ("HTTPretty should call a callback function to be set as the body with"
" urllib2")
def request_callback(request, uri, headers):
diff --git a/tests/functional/testserver.py b/tests/functional/testserver.py
index 1a43ef1..d18df0a 100644
--- a/tests/functional/testserver.py
+++ b/tests/functional/testserver.py
@@ -56,6 +56,17 @@ class ComeHandler(RequestHandler):
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
@@ -71,22 +82,13 @@ class TornadoServer(object):
])
def start(self):
- def go(app, port, data={}):
- from httpretty import HTTPretty
- HTTPretty.disable()
-
- http = HTTPServer(app)
- HTTPretty.disable()
-
- http.listen(int(port))
- IOLoop.instance().start()
app = self.get_handlers()
data = {}
args = (app, self.port, data)
HTTPretty.disable()
- self.process = Process(target=go, args=args)
+ self.process = Process(target=subprocess_server_tornado, args=args)
self.process.start()
time.sleep(1)
@@ -99,6 +101,22 @@ class TornadoServer(object):
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)
@@ -106,23 +124,9 @@ class TCPServer(object):
def start(self):
HTTPretty.disable()
- def go(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()
args = [self.port]
- self.process = Process(target=go, args=args)
+ self.process = Process(target=subprocess_server_tcp, args=args)
self.process.start()
time.sleep(1)
diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py
index c73836c..80c4a86 100644
--- a/tests/unit/test_core.py
+++ b/tests/unit/test_core.py
@@ -159,15 +159,16 @@ def test_request_string_representation():
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)
+ 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("JPEG-baby", total_headers=1, body_length=14)>')
+ 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():
@@ -297,6 +298,7 @@ def test_fakesock_socket_real_sendall(old_socket):
# 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')