diff options
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"] @@ -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 @@ -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' @@ -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') |