diff options
author | maxim-yurchuk <maxim-yurchuk@yandex-team.com> | 2024-10-09 12:29:46 +0300 |
---|---|---|
committer | maxim-yurchuk <maxim-yurchuk@yandex-team.com> | 2024-10-09 13:14:22 +0300 |
commit | 9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80 (patch) | |
tree | a8fb3181d5947c0d78cf402aa56e686130179049 /contrib/python/requests-mock | |
parent | a44b779cd359f06c3ebbef4ec98c6b38609d9d85 (diff) | |
download | ydb-9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80.tar.gz |
publishFullContrib: true for ydb
<HIDDEN_URL>
commit_hash:c82a80ac4594723cebf2c7387dec9c60217f603e
Diffstat (limited to 'contrib/python/requests-mock')
51 files changed, 6546 insertions, 0 deletions
diff --git a/contrib/python/requests-mock/py2/.dist-info/METADATA b/contrib/python/requests-mock/py2/.dist-info/METADATA new file mode 100644 index 0000000000..d8eadeaec2 --- /dev/null +++ b/contrib/python/requests-mock/py2/.dist-info/METADATA @@ -0,0 +1,144 @@ +Metadata-Version: 2.1 +Name: requests-mock +Version: 1.11.0 +Summary: Mock out responses from the requests package +Home-page: https://requests-mock.readthedocs.io/ +Author: Jamie Lennox +Author-email: jamielennox@gmail.com +License: Apache-2 +Project-URL: Source, https://github.com/jamielennox/requests-mock +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Testing +License-File: LICENSE +Requires-Dist: requests (<3,>=2.3) +Requires-Dist: six +Provides-Extra: fixture +Requires-Dist: fixtures ; extra == 'fixture' +Provides-Extra: test +Requires-Dist: fixtures ; extra == 'test' +Requires-Dist: purl ; extra == 'test' +Requires-Dist: pytest ; extra == 'test' +Requires-Dist: sphinx ; extra == 'test' +Requires-Dist: testtools ; extra == 'test' +Requires-Dist: requests-futures ; extra == 'test' +Requires-Dist: mock ; (( python_version < '3.3')) and extra == 'test' + +=============================== +requests-mock +=============================== + +.. image:: https://badge.fury.io/py/requests-mock.png + :target: https://pypi.org/project/requests-mock/ + +Intro +===== + +`requests-mock` provides a building block to stub out the HTTP `requests`_ portions of your testing code. +You should checkout the `docs`_ for more information. + +The Basics +========== + +Everything in `requests`_ eventually goes through an adapter to do the transport work. +`requests-mock` creates a custom `adapter` that allows you to predefine responses when certain URIs are called. + +There are then a number of methods provided to get the adapter used. + +A simple example: + +.. code:: python + + >>> import requests + >>> import requests_mock + + >>> session = requests.Session() + >>> adapter = requests_mock.Adapter() + >>> session.mount('mock://', adapter) + + >>> adapter.register_uri('GET', 'mock://test.com', text='data') + >>> resp = session.get('mock://test.com') + >>> resp.status_code, resp.text + (200, 'data') + +Obviously having all URLs be `mock://` prefixed isn't going to be useful, +so you can use `requests_mock.Mocker` to get the adapter into place. + +As a context manager: + +.. code:: python + + >>> with requests_mock.Mocker() as m: + ... m.get('http://test.com', text='data') + ... requests.get('http://test.com').text + ... + 'data' + +Or as a decorator: + +.. code:: python + + >>> @requests_mock.Mocker() + ... def test_func(m): + ... m.get('http://test.com', text='data') + ... return requests.get('http://test.com').text + ... + >>> test_func() + 'data' + +Or as a pytest fixture: + +.. code:: python + + >>> def test_simple(requests_mock): + ... requests_mock.get('http://test.com', text='data') + ... assert 'data' == requests.get('http://test.com').text + +For more information checkout the `docs`_. + +Reporting Bugs +============== + +Development and bug tracking is performed on `GitHub`_. + +Questions +========= + +There is a tag dedicated to `requests-mock` on `StackOverflow`_ where you can ask usage questions. + +License +======= + +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. You may obtain +a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. + +.. _requests: https://requests.readthedocs.io +.. _docs: https://requests-mock.readthedocs.io/ +.. _GitHub: https://github.com/jamielennox/requests-mock +.. _StackOverflow: https://stackoverflow.com/questions/tagged/requests-mock + diff --git a/contrib/python/requests-mock/py2/.dist-info/entry_points.txt b/contrib/python/requests-mock/py2/.dist-info/entry_points.txt new file mode 100644 index 0000000000..b157e5a5ec --- /dev/null +++ b/contrib/python/requests-mock/py2/.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +requests_mock = requests_mock.contrib._pytest_plugin diff --git a/contrib/python/requests-mock/py2/.dist-info/top_level.txt b/contrib/python/requests-mock/py2/.dist-info/top_level.txt new file mode 100644 index 0000000000..65a92dd61d --- /dev/null +++ b/contrib/python/requests-mock/py2/.dist-info/top_level.txt @@ -0,0 +1 @@ +requests_mock diff --git a/contrib/python/requests-mock/py2/patches/01-fix-tests.patch b/contrib/python/requests-mock/py2/patches/01-fix-tests.patch new file mode 100644 index 0000000000..c738d3d08e --- /dev/null +++ b/contrib/python/requests-mock/py2/patches/01-fix-tests.patch @@ -0,0 +1,13 @@ +--- contrib/python/requests-mock/py2/tests/base.py (index) ++++ contrib/python/requests-mock/py2/tests/base.py (working tree) +@@ -10,8 +10,8 @@ + # License for the specific language governing permissions and limitations + # under the License. + +-import testtools ++import unittest + + +-class TestCase(testtools.TestCase): ++class TestCase(unittest.TestCase): + pass diff --git a/contrib/python/requests-mock/py2/requests_mock/__init__.py b/contrib/python/requests-mock/py2/requests_mock/__init__.py new file mode 100644 index 0000000000..799b752ee7 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/__init__.py @@ -0,0 +1,37 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from requests_mock.adapter import Adapter, ANY +from requests_mock.exceptions import MockException, NoMockAddress +from requests_mock.mocker import mock, Mocker, MockerCore +from requests_mock.mocker import DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT +from requests_mock.response import create_response, CookieJar + + +__all__ = ['Adapter', + 'ANY', + 'create_response', + 'CookieJar', + 'mock', + 'Mocker', + 'MockerCore', + 'MockException', + 'NoMockAddress', + + 'DELETE', + 'GET', + 'HEAD', + 'OPTIONS', + 'PATCH', + 'POST', + 'PUT', + ] diff --git a/contrib/python/requests-mock/py2/requests_mock/__init__.pyi b/contrib/python/requests-mock/py2/requests_mock/__init__.pyi new file mode 100644 index 0000000000..9d8dd002e9 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/__init__.pyi @@ -0,0 +1,33 @@ +# Stubs for requests_mock + +from requests_mock.adapter import ( + ANY as ANY, + Adapter as Adapter, + Callback as Callback, + AdditionalMatcher as AdditionalMatcher, +) +from requests_mock.exceptions import ( + MockException as MockException, + NoMockAddress as NoMockAddress, +) +from requests_mock.mocker import ( + DELETE as DELETE, + GET as GET, + HEAD as HEAD, + Mocker as Mocker, + MockerCore as MockerCore, + OPTIONS as OPTIONS, + PATCH as PATCH, + POST as POST, + PUT as PUT, + mock as mock, +) +from requests_mock.request import ( + Request as Request, + _RequestObjectProxy as _RequestObjectProxy, # For backward compatibility +) +from requests_mock.response import ( + CookieJar as CookieJar, + create_response as create_response, + Context as Context, +) diff --git a/contrib/python/requests-mock/py2/requests_mock/adapter.py b/contrib/python/requests-mock/py2/requests_mock/adapter.py new file mode 100644 index 0000000000..e0560b2226 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/adapter.py @@ -0,0 +1,323 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import weakref + +from requests.adapters import BaseAdapter +from requests.utils import requote_uri +import six +from six.moves.urllib import parse as urlparse + +from requests_mock import exceptions +from requests_mock.request import _RequestObjectProxy +from requests_mock.response import _MatcherResponse + +import logging + +logger = logging.getLogger(__name__) + +try: + import purl + purl_types = (purl.URL,) +except ImportError: + purl = None + purl_types = () + +ANY = object() + + +class _RequestHistoryTracker(object): + + def __init__(self): + self.request_history = [] + + def _add_to_history(self, request): + self.request_history.append(request) + + @property + def last_request(self): + """Retrieve the latest request sent""" + try: + return self.request_history[-1] + except IndexError: + return None + + @property + def called(self): + return self.call_count > 0 + + @property + def called_once(self): + return self.call_count == 1 + + @property + def call_count(self): + return len(self.request_history) + + def reset(self): + self.request_history = [] + + +class _RunRealHTTP(Exception): + """A fake exception to jump out of mocking and allow a real request. + + This exception is caught at the mocker level and allows it to execute this + request through the real requests mechanism rather than the mocker. + + It should never be exposed to a user. + """ + + +class _Matcher(_RequestHistoryTracker): + """Contains all the information about a provided URL to match.""" + + def __init__(self, method, url, responses, complete_qs, request_headers, + additional_matcher, real_http, case_sensitive): + """ + :param bool complete_qs: Match the entire query string. By default URLs + match if all the provided matcher query arguments are matched and + extra query arguments are ignored. Set complete_qs to true to + require that the entire query string needs to match. + """ + super(_Matcher, self).__init__() + + self._method = method + self._url = url + self._responses = responses + self._complete_qs = complete_qs + self._request_headers = request_headers + self._real_http = real_http + self._additional_matcher = additional_matcher + + # url can be a regex object or ANY so don't always run urlparse + if isinstance(url, six.string_types): + url_parts = urlparse.urlparse(url) + self._scheme = url_parts.scheme.lower() + self._netloc = url_parts.netloc.lower() + self._path = requote_uri(url_parts.path or '/') + self._query = url_parts.query + + if not case_sensitive: + self._path = self._path.lower() + self._query = self._query.lower() + + elif isinstance(url, purl_types): + self._scheme = url.scheme() + self._netloc = url.netloc() + self._path = url.path() + self._query = url.query() + + if not case_sensitive: + self._path = self._path.lower() + self._query = self._query.lower() + + else: + self._scheme = None + self._netloc = None + self._path = None + self._query = None + + def _match_method(self, request): + if self._method is ANY: + return True + + if request.method.lower() == self._method.lower(): + return True + + return False + + def _match_url(self, request): + if self._url is ANY: + return True + + # regular expression matching + if hasattr(self._url, 'search'): + return self._url.search(request.url) is not None + + # scheme is always matched case insensitive + if self._scheme and request.scheme.lower() != self._scheme: + return False + + # netloc is always matched case insensitive + if self._netloc and request.netloc.lower() != self._netloc: + return False + + if (request.path or '/') != self._path: + return False + + # construct our own qs structure as we remove items from it below + request_qs = urlparse.parse_qs(request.query, keep_blank_values=True) + matcher_qs = urlparse.parse_qs(self._query, keep_blank_values=True) + + for k, vals in six.iteritems(matcher_qs): + for v in vals: + try: + request_qs.get(k, []).remove(v) + except ValueError: + return False + + if self._complete_qs: + for v in six.itervalues(request_qs): + if v: + return False + + return True + + def _match_headers(self, request): + for k, vals in six.iteritems(self._request_headers): + + try: + header = request.headers[k] + except KeyError: + # NOTE(jamielennox): This seems to be a requests 1.2/2 + # difference, in 2 they are just whatever the user inputted in + # 1 they are bytes. Let's optionally handle both and look at + # removing this when we depend on requests 2. + if not isinstance(k, six.text_type): + return False + + try: + header = request.headers[k.encode('utf-8')] + except KeyError: + return False + + if header != vals: + return False + + return True + + def _match_additional(self, request): + if callable(self._additional_matcher): + return self._additional_matcher(request) + + if self._additional_matcher is not None: + raise TypeError("Unexpected format of additional matcher.") + + return True + + def _match(self, request): + return (self._match_method(request) and + self._match_url(request) and + self._match_headers(request) and + self._match_additional(request)) + + def __call__(self, request): + if not self._match(request): + return None + + # doing this before _add_to_history means real requests are not stored + # in the request history. I'm not sure what is better here. + if self._real_http: + raise _RunRealHTTP() + + if len(self._responses) > 1: + response_matcher = self._responses.pop(0) + else: + response_matcher = self._responses[0] + + self._add_to_history(request) + return response_matcher.get_response(request) + + +class Adapter(BaseAdapter, _RequestHistoryTracker): + """A fake adapter than can return predefined responses. + + """ + def __init__(self, case_sensitive=False): + super(Adapter, self).__init__() + self._case_sensitive = case_sensitive + self._matchers = [] + + def send(self, request, **kwargs): + request = _RequestObjectProxy(request, + case_sensitive=self._case_sensitive, + **kwargs) + self._add_to_history(request) + + for matcher in reversed(self._matchers): + try: + resp = matcher(request) + except Exception: + request._matcher = weakref.ref(matcher) + raise + + if resp is not None: + request._matcher = weakref.ref(matcher) + resp.connection = self + logger.debug('{} {} {}'.format(request._request.method, + request._request.url, + resp.status_code)) + return resp + + raise exceptions.NoMockAddress(request) + + def close(self): + pass + + def register_uri(self, method, url, response_list=None, **kwargs): + """Register a new URI match and fake response. + + :param str method: The HTTP method to match. + :param str url: The URL to match. + """ + complete_qs = kwargs.pop('complete_qs', False) + additional_matcher = kwargs.pop('additional_matcher', None) + request_headers = kwargs.pop('request_headers', {}) + real_http = kwargs.pop('_real_http', False) + json_encoder = kwargs.pop('json_encoder', None) + + if response_list and kwargs: + raise RuntimeError('You should specify either a list of ' + 'responses OR response kwargs. Not both.') + elif real_http and (response_list or kwargs): + raise RuntimeError('You should specify either response data ' + 'OR real_http. Not both.') + elif not response_list: + if json_encoder is not None: + kwargs['json_encoder'] = json_encoder + response_list = [] if real_http else [kwargs] + + # NOTE(jamielennox): case_sensitive is not present as a kwarg because i + # think there would be an edge case where the adapter and register_uri + # had different values. + # Ideally case_sensitive would be a value passed to match() however + # this would change the contract of matchers so we pass ito to the + # proxy and the matcher separately. + responses = [_MatcherResponse(**k) for k in response_list] + matcher = _Matcher(method, + url, + responses, + case_sensitive=self._case_sensitive, + complete_qs=complete_qs, + additional_matcher=additional_matcher, + request_headers=request_headers, + real_http=real_http) + self.add_matcher(matcher) + return matcher + + def add_matcher(self, matcher): + """Register a custom matcher. + + A matcher is a callable that takes a `requests.Request` and returns a + `requests.Response` if it matches or None if not. + + :param callable matcher: The matcher to execute. + """ + self._matchers.append(matcher) + + def reset(self): + super(Adapter, self).reset() + for matcher in self._matchers: + matcher.reset() + + +__all__ = ['Adapter'] diff --git a/contrib/python/requests-mock/py2/requests_mock/adapter.pyi b/contrib/python/requests-mock/py2/requests_mock/adapter.pyi new file mode 100644 index 0000000000..dbeba496ba --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/adapter.pyi @@ -0,0 +1,75 @@ +# Stubs for requests_mock.adapter + +from http.cookiejar import CookieJar +from io import IOBase +from typing import Any, Callable, Dict, List, NewType, Optional, Pattern, Type, TypeVar, Union + +from requests import Response +from requests.adapters import BaseAdapter +from urllib3.response import HTTPResponse + +from requests_mock.request import Request +from requests_mock.response import Context + +AnyMatcher = NewType("AnyMatcher", object) + +ANY: AnyMatcher = ... + +T = TypeVar('T') +Callback = Callable[[Request, Context], T] +Matcher = Callable[[Request], Optional[Response]] +AdditionalMatcher = Callable[[Request], bool] + +class _RequestHistoryTracker: + request_history: List[Request] = ... + def __init__(self) -> None: ... + @property + def last_request(self) -> Optional[Request]: ... + @property + def called(self) -> bool: ... + @property + def called_once(self) -> bool: ... + @property + def call_count(self) -> int: ... + +class _RunRealHTTP(Exception): ... + +class _Matcher(_RequestHistoryTracker): + def __init__( + self, + method: Any, + url: Any, + responses: Any, + complete_qs: Any, + request_headers: Any, + additional_matcher: AdditionalMatcher, + real_http: Any, + case_sensitive: Any + ) -> None: ... + def __call__(self, request: Request) -> Optional[Response]: ... + +class Adapter(BaseAdapter, _RequestHistoryTracker): + def __init__(self, case_sensitive: bool = ...) -> None: ... + def register_uri( + self, + method: Union[str, AnyMatcher], + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + **kwargs: Any + ) -> _Matcher: ... + def add_matcher(self, matcher: Matcher) -> None: ... + def reset(self) -> None: ... diff --git a/contrib/python/requests-mock/py2/requests_mock/compat.py b/contrib/python/requests-mock/py2/requests_mock/compat.py new file mode 100644 index 0000000000..8b6293af15 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/compat.py @@ -0,0 +1,30 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class _FakeHTTPMessage(object): + + def __init__(self, headers): + self.headers = headers + + def getheaders(self, name): + try: + return [self.headers[name]] + except KeyError: + return [] + + def get_all(self, name, failobj=None): + # python 3 only, overrides email.message.Message.get_all + try: + return [self.headers[name]] + except KeyError: + return failobj diff --git a/contrib/python/requests-mock/py2/requests_mock/contrib/__init__.py b/contrib/python/requests-mock/py2/requests_mock/contrib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/contrib/__init__.py diff --git a/contrib/python/requests-mock/py2/requests_mock/contrib/_pytest_plugin.py b/contrib/python/requests-mock/py2/requests_mock/contrib/_pytest_plugin.py new file mode 100644 index 0000000000..bb6cd2b973 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/contrib/_pytest_plugin.py @@ -0,0 +1,86 @@ +import pytest + + +# RHEL 7 ships pytest 2.7 which doesn't have the 'bool' type to addini. This +# broke pytest for EPEL: https://bugzilla.redhat.com/show_bug.cgi?id=1605138 +# If it's older than 2.9 we handle bool conversion ourselves. Remove this when +# we can rely on a newer pytest. +# +# Version 3 is also where the @yield_fixture decorator was deprecated and you +# can now just use @fixture, so we handle both of those cases as well. + +try: + _pytest_version = tuple([ + int(x) for x in pytest.__version__.split('.')[:2] + ]) + _pytest29 = _pytest_version >= (2, 9) + _pytest30 = _pytest_version >= (3, 0) +except Exception: + _pytest29 = False + _pytest30 = False + + +if not _pytest29: + _case_type = None + _case_default = 'false' + + # Copied from pytest 2.9.0 where bool was introduced. It's what happens + # internally if we specify a bool type argument. + def _strtobool(val): + """Convert a string representation of truth to true (1) or false (0). + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + + .. note:: copied from distutils.util + """ + val = val.lower() + if val in ('y', 'yes', 't', 'true', 'on', '1'): + return 1 + elif val in ('n', 'no', 'f', 'false', 'off', '0'): + return 0 + else: + raise ValueError("invalid truth value %r" % (val,)) + + def _bool_value(value): + return bool(_strtobool(value.strip())) + +else: + _case_type = 'bool' + _case_default = False + + def _bool_value(value): + return value + + +if _pytest30: + _fixture_type = pytest.fixture +else: + _fixture_type = pytest.yield_fixture + + +def pytest_addoption(parser): + parser.addini('requests_mock_case_sensitive', + 'Use case sensitive matching in requests_mock', + type=_case_type, + default=_case_default) + + +@_fixture_type(scope='function') # executed on every test +def requests_mock(request): + """Mock out the requests component of your code with defined responses. + + Mocks out any requests made through the python requests library with useful + responses for unit testing. See: + https://requests-mock.readthedocs.io/en/latest/ + """ + # pytest plugins get loaded immediately. If we import requests_mock it + # imports requests and then SSL which prevents gevent patching. Late load. + import requests_mock as rm_module + + case_sensitive = request.config.getini('requests_mock_case_sensitive') + kw = {'case_sensitive': _bool_value(case_sensitive)} + + with rm_module.Mocker(**kw) as m: + yield m diff --git a/contrib/python/requests-mock/py2/requests_mock/contrib/_pytest_plugin.pyi b/contrib/python/requests-mock/py2/requests_mock/contrib/_pytest_plugin.pyi new file mode 100644 index 0000000000..3ffb573881 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/contrib/_pytest_plugin.pyi @@ -0,0 +1,6 @@ + +from typing import Literal, Optional, Union + + +_case_type = Optional[str] +_case_default = Union[Literal['false'], Literal[False]] diff --git a/contrib/python/requests-mock/py2/requests_mock/contrib/fixture.py b/contrib/python/requests-mock/py2/requests_mock/contrib/fixture.py new file mode 100644 index 0000000000..0c23947566 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/contrib/fixture.py @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import fixtures + +from requests_mock import mocker + + +class Fixture(fixtures.Fixture, mocker.MockerCore): + + def __init__(self, **kwargs): + fixtures.Fixture.__init__(self) + mocker.MockerCore.__init__(self, **kwargs) + + def setUp(self): + super(Fixture, self).setUp() + self.start() + self.addCleanup(self.stop) diff --git a/contrib/python/requests-mock/py2/requests_mock/exceptions.py b/contrib/python/requests-mock/py2/requests_mock/exceptions.py new file mode 100644 index 0000000000..feeb1aa312 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/exceptions.py @@ -0,0 +1,30 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class MockException(Exception): + """Base Exception for library""" + + +class NoMockAddress(MockException): + """The requested URL was not mocked""" + + def __init__(self, request): + self.request = request + + def __str__(self): + return "No mock address: %s %s" % (self.request.method, + self.request.url) + + +class InvalidRequest(MockException): + """This call cannot be made under a mocked environment""" diff --git a/contrib/python/requests-mock/py2/requests_mock/exceptions.pyi b/contrib/python/requests-mock/py2/requests_mock/exceptions.pyi new file mode 100644 index 0000000000..eb35447228 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/exceptions.pyi @@ -0,0 +1,13 @@ +# Stubs for requests_mock.exceptions + +from typing import Any + +from requests import Request + +class MockException(Exception): ... + +class NoMockAddress(MockException): + request: Any = ... + def __init__(self, request: Request) -> None: ... + +class InvalidRequest(MockException): ... diff --git a/contrib/python/requests-mock/py2/requests_mock/mocker.py b/contrib/python/requests-mock/py2/requests_mock/mocker.py new file mode 100644 index 0000000000..d3bc85538e --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/mocker.py @@ -0,0 +1,342 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import contextlib +import functools +import sys +import threading +import types + +import requests +import six + +from requests_mock import adapter +from requests_mock import exceptions + +DELETE = 'DELETE' +GET = 'GET' +HEAD = 'HEAD' +OPTIONS = 'OPTIONS' +PATCH = 'PATCH' +POST = 'POST' +PUT = 'PUT' + +_original_send = requests.Session.send + +# NOTE(phodge): we need to use an RLock (reentrant lock) here because +# requests.Session.send() is reentrant. See further comments where we +# monkeypatch get_adapter() +_send_lock = threading.RLock() + + +@contextlib.contextmanager +def threading_rlock(timeout): + kwargs = {} + if sys.version_info.major >= 3: + # python2 doesn't support the timeout argument + kwargs['timeout'] = timeout + + if not _send_lock.acquire(**kwargs): + m = "Could not acquire threading lock - possible deadlock scenario" + raise Exception(m) + + try: + yield + finally: + _send_lock.release() + + +def _is_bound_method(method): + """ + bound_method 's self is a obj + unbound_method 's self is None + """ + if isinstance(method, types.MethodType) and six.get_method_self(method): + return True + return False + + +def _set_method(target, name, method): + """ Set a mocked method onto the target. + + Target may be either an instance of a Session object of the + requests.Session class. First we Bind the method if it's an instance. + + If method is a bound_method, can direct setattr + """ + if not isinstance(target, type) and not _is_bound_method(method): + method = six.create_bound_method(method, target) + + setattr(target, name, method) + + +class MockerCore(object): + """A wrapper around common mocking functions. + + Automate the process of mocking the requests library. This will keep the + same general options available and prevent repeating code. + """ + + _PROXY_FUNCS = { + 'last_request', + 'add_matcher', + 'request_history', + 'called', + 'called_once', + 'call_count', + 'reset', + } + + case_sensitive = False + """case_sensitive handles a backwards incompatible bug. The URL used to + match against our matches and that is saved in request_history is always + lowercased. This is incorrect as it reports incorrect history to the user + and doesn't allow case sensitive path matching. + + Unfortunately fixing this change is backwards incompatible in the 1.X + series as people may rely on this behaviour. To work around this you can + globally set: + + requests_mock.mock.case_sensitive = True + + or for pytest set in your configuration: + + [pytest] + requests_mock_case_sensitive = True + + which will prevent the lowercase being executed and return case sensitive + url and query information. + + This will become the default in a 2.X release. See bug: #1584008. + """ + + def __init__(self, session=None, **kwargs): + if session and not isinstance(session, requests.Session): + raise TypeError("Only a requests.Session object can be mocked") + + self._mock_target = session or requests.Session + self.case_sensitive = kwargs.pop('case_sensitive', self.case_sensitive) + self._adapter = ( + kwargs.pop('adapter', None) or + adapter.Adapter(case_sensitive=self.case_sensitive) + ) + + self._json_encoder = kwargs.pop('json_encoder', None) + self.real_http = kwargs.pop('real_http', False) + self._last_send = None + + if kwargs: + raise TypeError('Unexpected Arguments: %s' % ', '.join(kwargs)) + + def start(self): + """Start mocking requests. + + Install the adapter and the wrappers required to intercept requests. + """ + if self._last_send: + raise RuntimeError('Mocker has already been started') + + # backup last `send` for restoration on `self.stop` + self._last_send = self._mock_target.send + self._last_get_adapter = self._mock_target.get_adapter + + def _fake_get_adapter(session, url): + return self._adapter + + def _fake_send(session, request, **kwargs): + # NOTE(phodge): we need to use a threading lock here in case there + # are multiple threads running - one thread could restore the + # original get_adapter() just as a second thread is about to + # execute _original_send() below + with threading_rlock(timeout=10): + # mock get_adapter + # + # NOTE(phodge): requests.Session.send() is actually + # reentrant due to how it resolves redirects with nested + # calls to send(), however the reentry occurs _after_ the + # call to self.get_adapter(), so it doesn't matter that we + # will restore _last_get_adapter before a nested send() has + # completed as long as we monkeypatch get_adapter() each + # time immediately before calling original send() like we + # are doing here. + _set_method(session, "get_adapter", _fake_get_adapter) + + # NOTE(jamielennox): self._last_send vs _original_send. Whilst + # it seems like here we would use _last_send there is the + # possibility that the user has messed up and is somehow + # nesting their mockers. If we call last_send at this point + # then we end up calling this function again and the outer + # level adapter ends up winning. All we really care about here + # is that our adapter is in place before calling send so we + # always jump directly to the real function so that our most + # recently patched send call ends up putting in the most recent + # adapter. It feels funny, but it works. + + try: + return _original_send(session, request, **kwargs) + except exceptions.NoMockAddress: + if not self.real_http: + raise + except adapter._RunRealHTTP: + # this mocker wants you to run the request through the real + # requests library rather than the mocking. Let it. + pass + finally: + # restore get_adapter + _set_method(session, "get_adapter", self._last_get_adapter) + + # if we are here it means we must run the real http request + # Or, with nested mocks, to the parent mock, that is why we use + # _last_send here instead of _original_send + if isinstance(self._mock_target, type): + return self._last_send(session, request, **kwargs) + else: + return self._last_send(request, **kwargs) + + _set_method(self._mock_target, "send", _fake_send) + + def stop(self): + """Stop mocking requests. + + This should have no impact if mocking has not been started. + When nesting mockers, make sure to stop the innermost first. + """ + if self._last_send: + self._mock_target.send = self._last_send + self._last_send = None + + # for familiarity with MagicMock + def reset_mock(self): + self.reset() + + def __getattr__(self, name): + if name in self._PROXY_FUNCS: + try: + return getattr(self._adapter, name) + except AttributeError: + pass + + raise AttributeError(name) + + def register_uri(self, *args, **kwargs): + # you can pass real_http here, but it's private to pass direct to the + # adapter, because if you pass direct to the adapter you'll see the exc + kwargs['_real_http'] = kwargs.pop('real_http', False) + kwargs.setdefault('json_encoder', self._json_encoder) + return self._adapter.register_uri(*args, **kwargs) + + def request(self, *args, **kwargs): + return self.register_uri(*args, **kwargs) + + def get(self, *args, **kwargs): + return self.request(GET, *args, **kwargs) + + def options(self, *args, **kwargs): + return self.request(OPTIONS, *args, **kwargs) + + def head(self, *args, **kwargs): + return self.request(HEAD, *args, **kwargs) + + def post(self, *args, **kwargs): + return self.request(POST, *args, **kwargs) + + def put(self, *args, **kwargs): + return self.request(PUT, *args, **kwargs) + + def patch(self, *args, **kwargs): + return self.request(PATCH, *args, **kwargs) + + def delete(self, *args, **kwargs): + return self.request(DELETE, *args, **kwargs) + + +class Mocker(MockerCore): + """The standard entry point for mock Adapter loading. + """ + + #: Defines with what should method name begin to be patched + TEST_PREFIX = 'test' + + def __init__(self, **kwargs): + """Create a new mocker adapter. + + :param str kw: Pass the mock object through to the decorated function + as this named keyword argument, rather than a positional argument. + :param bool real_http: True to send the request to the real requested + uri if there is not a mock installed for it. Defaults to False. + """ + self._kw = kwargs.pop('kw', None) + super(Mocker, self).__init__(**kwargs) + + def __enter__(self): + self.start() + return self + + def __exit__(self, type, value, traceback): + self.stop() + + def __call__(self, obj): + if isinstance(obj, type): + return self.decorate_class(obj) + + return self.decorate_callable(obj) + + def copy(self): + """Returns an exact copy of current mock + """ + m = type(self)( + kw=self._kw, + real_http=self.real_http, + case_sensitive=self.case_sensitive + ) + return m + + def decorate_callable(self, func): + """Decorates a callable + + :param callable func: callable to decorate + """ + @functools.wraps(func) + def inner(*args, **kwargs): + with self.copy() as m: + if self._kw: + kwargs[self._kw] = m + else: + args = list(args) + args.append(m) + + return func(*args, **kwargs) + + return inner + + def decorate_class(self, klass): + """Decorates methods in a class with request_mock + + Method will be decorated only if it name begins with `TEST_PREFIX` + + :param object klass: class which methods will be decorated + """ + for attr_name in dir(klass): + if not attr_name.startswith(self.TEST_PREFIX): + continue + + attr = getattr(klass, attr_name) + if not hasattr(attr, '__call__'): + continue + + m = self.copy() + setattr(klass, attr_name, m(attr)) + + return klass + + +mock = Mocker diff --git a/contrib/python/requests-mock/py2/requests_mock/mocker.pyi b/contrib/python/requests-mock/py2/requests_mock/mocker.pyi new file mode 100644 index 0000000000..891e7d6c9a --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/mocker.pyi @@ -0,0 +1,263 @@ +# Stubs for requests_mock.mocker + +from json import JSONEncoder +from http.cookiejar import CookieJar +from io import IOBase +from typing import Any, Callable, Dict, List, Optional, Pattern, Type, TypeVar, Union + +from requests import Response, Session +from urllib3.response import HTTPResponse + +from requests_mock.adapter import AnyMatcher, _Matcher, Callback, AdditionalMatcher +from requests_mock.request import Request + +DELETE: str +GET: str +HEAD: str +OPTIONS: str +PATCH: str +POST: str +PUT: str + +class MockerCore: + case_sensitive: bool = ... + def __init__(self, **kwargs: Any) -> None: ... + def start(self) -> None: ... + def stop(self) -> None: ... + def add_matcher(self, matcher: Callable[[Request], Optional[Response]]) -> None: ... + @property + def request_history(self) -> List[Request]: ... + @property + def last_request(self) -> Optional[Request]: ... + @property + def called(self) -> bool: ... + @property + def called_once(self) -> bool: ... + @property + def call_count(self) -> int: ... + def reset(self) -> None: ... + def reset_mock(self) -> None: ... + + def register_uri( + self, + method: Union[str, AnyMatcher], + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + **kwargs: Any, + ) -> _Matcher: ... + + def request( + self, + method: Union[str, AnyMatcher], + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + **kwargs: Any, + ) -> _Matcher: ... + + def get( + self, + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + **kwargs: Any, + ) -> _Matcher: ... + + def head( + self, + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + **kwargs: Any, + ) -> _Matcher: ... + + def options( + self, + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + **kwargs: Any, + ) -> _Matcher: ... + + def post( + self, + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + **kwargs: Any, + ) -> _Matcher: ... + + def put( + self, + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + **kwargs: Any, + ) -> _Matcher: ... + + def patch( + self, + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + **kwargs: Any, + ) -> _Matcher: ... + + def delete( + self, + url: Union[str, Pattern[str], AnyMatcher], + response_list: Optional[List[Dict[str, Any]]] = ..., + *, + request_headers: Dict[str, str] = ..., + complete_qs: bool = ..., + status_code: int = ..., + reason: str = ..., + headers: Dict[str, str] = ..., + cookies: Union[CookieJar, Dict[str, str]] = ..., + json: Union[Any, Callback[Any]] = ..., + text: Union[str, Callback[str]] = ..., + content: Union[bytes, Callback[bytes]] = ..., + body: Union[IOBase, Callback[IOBase]] = ..., + raw: HTTPResponse = ..., + exc: Union[Exception, Type[Exception]] = ..., + additional_matcher: AdditionalMatcher = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + **kwargs: Any, + ) -> _Matcher: ... + +_T = TypeVar('_T') + +class Mocker(MockerCore): + TEST_PREFIX: str = ... + real_http: bool = ... + + def __init__( + self, + kw: str = ..., + case_sensitive: bool = ..., + adapter: Any = ..., + session: Optional[Session] = ..., + real_http: bool = ..., + json_encoder: Optional[Type[JSONEncoder]] = ..., + ) -> None: ... + def __enter__(self) -> Any: ... + def __exit__(self, type: Any, value: Any, traceback: Any) -> None: ... + def __call__(self, obj: Any) -> Any: ... + def copy(self) -> Mocker: ... + def decorate_callable(self, func: Callable[..., _T]) -> Callable[..., _T]: ... + def decorate_class(self, klass: Type[_T]) -> Type[_T]: ... + +mock = Mocker diff --git a/contrib/python/requests-mock/py2/requests_mock/py.typed b/contrib/python/requests-mock/py2/requests_mock/py.typed new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/py.typed diff --git a/contrib/python/requests-mock/py2/requests_mock/request.py b/contrib/python/requests-mock/py2/requests_mock/request.py new file mode 100644 index 0000000000..05cbc3d4a3 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/request.py @@ -0,0 +1,178 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy +import json + +import requests +import six +from six.moves.urllib import parse as urlparse + + +class _RequestObjectProxy(object): + """A wrapper around a requests.Request that gives some extra information. + + This will be important both for matching and so that when it's save into + the request_history users will be able to access these properties. + """ + + def __init__(self, request, **kwargs): + self._request = request + self._matcher = None + self._url_parts_ = None + self._qs = None + + # All of these params should always exist but we use a default + # to make the test setup easier. + self._timeout = kwargs.pop('timeout', None) + self._allow_redirects = kwargs.pop('allow_redirects', None) + self._verify = kwargs.pop('verify', None) + self._stream = kwargs.pop('stream', None) + self._cert = kwargs.pop('cert', None) + self._proxies = copy.deepcopy(kwargs.pop('proxies', {})) + + # FIXME(jamielennox): This is part of bug #1584008 and should default + # to True (or simply removed) in a major version bump. + self._case_sensitive = kwargs.pop('case_sensitive', False) + + def __getattr__(self, name): + # there should be a better way to exclude this, but I don't want to + # implement __setstate__ just not forward it to the request. You can't + # actually define the method and raise AttributeError there either. + if name in ('__setstate__',): + raise AttributeError(name) + + return getattr(self._request, name) + + @property + def _url_parts(self): + if self._url_parts_ is None: + url = self._request.url + + if not self._case_sensitive: + url = url.lower() + + self._url_parts_ = urlparse.urlparse(url) + + return self._url_parts_ + + @property + def scheme(self): + return self._url_parts.scheme + + @property + def netloc(self): + return self._url_parts.netloc + + @property + def hostname(self): + try: + return self.netloc.split(':')[0] + except IndexError: + return '' + + @property + def port(self): + components = self.netloc.split(':') + + try: + return int(components[1]) + except (IndexError, ValueError): + pass + + if self.scheme == 'https': + return 443 + if self.scheme == 'http': + return 80 + + # The default return shouldn't matter too much because if you are + # wanting to test this value you really should be explicitly setting it + # somewhere. 0 at least is a boolean False and an int. + return 0 + + @property + def path(self): + return self._url_parts.path + + @property + def query(self): + return self._url_parts.query + + @property + def qs(self): + if self._qs is None: + self._qs = urlparse.parse_qs(self.query, keep_blank_values=True) + + return self._qs + + @property + def timeout(self): + return self._timeout + + @property + def allow_redirects(self): + return self._allow_redirects + + @property + def verify(self): + return self._verify + + @property + def stream(self): + return self._stream + + @property + def cert(self): + return self._cert + + @property + def proxies(self): + return self._proxies + + @classmethod + def _create(cls, *args, **kwargs): + return cls(requests.Request(*args, **kwargs).prepare()) + + @property + def text(self): + body = self.body + + if isinstance(body, six.binary_type): + body = body.decode('utf-8') + + return body + + def json(self, **kwargs): + return json.loads(self.text, **kwargs) + + def __getstate__(self): + # Can't pickle a weakref, but it's a weakref so ok to drop it. + d = self.__dict__.copy() + d['_matcher'] = None + return d + + @property + def matcher(self): + """The matcher that this request was handled by. + + The matcher object is handled by a weakref. It will return the matcher + object if it is still available - so if the mock is still in place. If + the matcher is not available it will return None. + """ + # if unpickled or not from a response this will be None + if self._matcher is None: + return None + + return self._matcher() + + def __str__(self): + return "{0.method} {0.url}".format(self._request) diff --git a/contrib/python/requests-mock/py2/requests_mock/request.pyi b/contrib/python/requests-mock/py2/requests_mock/request.pyi new file mode 100644 index 0000000000..5e2fb30d20 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/request.pyi @@ -0,0 +1,41 @@ +# Stubs for requests_mock.request + +from typing import Any, Dict, List + +class _RequestObjectProxy: + def __init__(self, request: Any, **kwargs: Any) -> None: ... + def __getattr__(self, name: str) -> Any: ... + @property + def scheme(self) -> str: ... + @property + def netloc(self) -> str: ... + @property + def hostname(self) -> str: ... + @property + def port(self) -> int: ... + @property + def path(self) -> str: ... + @property + def query(self) -> str: ... + @property + def qs(self) -> Dict[str, List[str]]: ... + @property + def timeout(self) -> int: ... + @property + def allow_redirects(self) -> bool: ... + @property + def verify(self) -> Any: ... + @property + def stream(self) -> Any: ... + @property + def cert(self) -> Any: ... + @property + def proxies(self) -> Any: ... + @property + def text(self) -> str: ... + def json(self, **kwargs: Any) -> Any: ... + @property + def matcher(self) -> Any: ... + + +Request = _RequestObjectProxy diff --git a/contrib/python/requests-mock/py2/requests_mock/response.py b/contrib/python/requests-mock/py2/requests_mock/response.py new file mode 100644 index 0000000000..5855539273 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/response.py @@ -0,0 +1,281 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json as jsonutils + +from requests.adapters import HTTPAdapter +from requests.cookies import MockRequest, MockResponse +from requests.cookies import RequestsCookieJar +from requests.cookies import merge_cookies, cookiejar_from_dict +from requests.packages.urllib3.response import HTTPResponse +from requests.utils import get_encoding_from_headers +import six + +from requests_mock import compat +from requests_mock import exceptions + +_BODY_ARGS = frozenset(['raw', 'body', 'content', 'text', 'json']) +_HTTP_ARGS = frozenset([ + 'status_code', + 'reason', + 'headers', + 'cookies', + 'json_encoder', +]) + +_DEFAULT_STATUS = 200 +_http_adapter = HTTPAdapter() + + +class CookieJar(RequestsCookieJar): + + def set(self, name, value, **kwargs): + """Add a cookie to the Jar. + + :param str name: cookie name/key. + :param str value: cookie value. + :param int version: Integer or None. Netscape cookies have version 0. + RFC 2965 and RFC 2109 cookies have a version cookie-attribute of 1. + However, note that cookielib may 'downgrade' RFC 2109 cookies to + Netscape cookies, in which case version is 0. + :param str port: String representing a port or a set of ports + (eg. '80', or '80,8080'), + :param str domain: The domain the cookie should apply to. + :param str path: Cookie path (a string, eg. '/acme/rocket_launchers'). + :param bool secure: True if cookie should only be returned over a + secure connection. + :param int expires: Integer expiry date in seconds since epoch or None. + :param bool discard: True if this is a session cookie. + :param str comment: String comment from the server explaining the + function of this cookie. + :param str comment_url: URL linking to a comment from the server + explaining the function of this cookie. + """ + # just here to provide the function documentation + return super(CookieJar, self).set(name, value, **kwargs) + + +def _check_body_arguments(**kwargs): + # mutual exclusion, only 1 body method may be provided + provided = [x for x in _BODY_ARGS if kwargs.pop(x, None) is not None] + + if len(provided) > 1: + raise RuntimeError('You may only supply one body element. You ' + 'supplied %s' % ', '.join(provided)) + + extra = [x for x in kwargs if x not in _HTTP_ARGS] + + if extra: + raise TypeError('Too many arguments provided. Unexpected ' + 'arguments %s.' % ', '.join(extra)) + + +class _FakeConnection(object): + """An object that can mock the necessary parts of a socket interface.""" + + def send(self, request, **kwargs): + msg = 'This response was created without a connection. You are ' \ + 'therefore unable to make a request directly on that connection.' + raise exceptions.InvalidRequest(msg) + + def close(self): + pass + + +def _extract_cookies(request, response, cookies): + """Add cookies to the response. + + Cookies in requests are extracted from the headers in the original_response + httplib.HTTPMessage which we don't create so we have to do this step + manually. + """ + # This will add cookies set manually via the Set-Cookie or Set-Cookie2 + # header but this only allows 1 cookie to be set. + http_message = compat._FakeHTTPMessage(response.headers) + response.cookies.extract_cookies(MockResponse(http_message), + MockRequest(request)) + + # This allows you to pass either a CookieJar or a dictionary to request_uri + # or directly to create_response. To allow more than one cookie to be set. + if cookies: + merge_cookies(response.cookies, cookies) + + +class _IOReader(six.BytesIO): + """A reader that makes a BytesIO look like a HTTPResponse. + + A HTTPResponse will return an empty string when you read from it after + the socket has been closed. A BytesIO will raise a ValueError. For + compatibility we want to do the same thing a HTTPResponse does. + """ + + def read(self, *args, **kwargs): + if self.closed: + return six.b('') + + # if the file is open, but you asked for zero bytes read you should get + # back zero without closing the stream. + if len(args) > 0 and args[0] == 0: + return six.b('') + + # not a new style object in python 2 + result = six.BytesIO.read(self, *args, **kwargs) + + # when using resp.iter_content(None) it'll go through a different + # request path in urllib3. This path checks whether the object is + # marked closed instead of the return value. see gh124. + if result == six.b(''): + self.close() + + return result + + +def create_response(request, **kwargs): + """ + :param int status_code: The status code to return upon a successful + match. Defaults to 200. + :param HTTPResponse raw: A HTTPResponse object to return upon a + successful match. + :param io.IOBase body: An IO object with a read() method that can + return a body on successful match. + :param bytes content: A byte string to return upon a successful match. + :param unicode text: A text string to return upon a successful match. + :param object json: A python object to be converted to a JSON string + and returned upon a successful match. + :param class json_encoder: Encoder object to use for JOSON. + :param dict headers: A dictionary object containing headers that are + returned upon a successful match. + :param CookieJar cookies: A cookie jar with cookies to set on the + response. + + :returns requests.Response: A response object that can + be returned to requests. + """ + connection = kwargs.pop('connection', _FakeConnection()) + + _check_body_arguments(**kwargs) + + raw = kwargs.pop('raw', None) + body = kwargs.pop('body', None) + content = kwargs.pop('content', None) + text = kwargs.pop('text', None) + json = kwargs.pop('json', None) + headers = kwargs.pop('headers', {}) + encoding = None + + if content is not None and not isinstance(content, six.binary_type): + raise TypeError('Content should be binary data') + if text is not None and not isinstance(text, six.string_types): + raise TypeError('Text should be string data') + + if json is not None: + encoder = kwargs.pop('json_encoder', None) or jsonutils.JSONEncoder + text = jsonutils.dumps(json, cls=encoder) + if text is not None: + encoding = get_encoding_from_headers(headers) or 'utf-8' + content = text.encode(encoding) + if content is not None: + body = _IOReader(content) + if not raw: + status = kwargs.get('status_code', _DEFAULT_STATUS) + reason = kwargs.get('reason', + six.moves.http_client.responses.get(status)) + + raw = HTTPResponse(status=status, + reason=reason, + headers=headers, + body=body or _IOReader(six.b('')), + decode_content=False, + enforce_content_length=False, + preload_content=False, + original_response=None) + + response = _http_adapter.build_response(request, raw) + response.connection = connection + + if encoding and not response.encoding: + response.encoding = encoding + + _extract_cookies(request, response, kwargs.get('cookies')) + + return response + + +class _Context(object): + """Stores the data being used to process a current URL match.""" + + def __init__(self, headers, status_code, reason, cookies): + self.headers = headers + self.status_code = status_code + self.reason = reason + self.cookies = cookies + + +class _MatcherResponse(object): + + def __init__(self, **kwargs): + self._exc = kwargs.pop('exc', None) + + # If the user is asking for an exception to be thrown then prevent them + # specifying any sort of body or status response as it won't be used. + # This may be protecting the user too much but can be removed later. + if self._exc and kwargs: + raise TypeError('Cannot provide other arguments with exc.') + + _check_body_arguments(**kwargs) + self._params = kwargs + + # whilst in general you shouldn't do type checking in python this + # makes sure we don't end up with differences between the way types + # are handled between python 2 and 3. + content = self._params.get('content') + text = self._params.get('text') + + if content is not None and not (callable(content) or + isinstance(content, six.binary_type)): + raise TypeError('Content should be a callback or binary data') + + if text is not None and not (callable(text) or + isinstance(text, six.string_types)): + raise TypeError('Text should be a callback or string data') + + def get_response(self, request): + # if an error was requested then raise that instead of doing response + if self._exc: + raise self._exc + + # If a cookie dict is passed convert it into a CookieJar so that the + # cookies object available in a callback context is always a jar. + cookies = self._params.get('cookies', CookieJar()) + if isinstance(cookies, dict): + cookies = cookiejar_from_dict(cookies, CookieJar()) + + context = _Context(self._params.get('headers', {}).copy(), + self._params.get('status_code', _DEFAULT_STATUS), + self._params.get('reason'), + cookies) + + # if a body element is a callback then execute it + def _call(f, *args, **kwargs): + return f(request, context, *args, **kwargs) if callable(f) else f + + return create_response(request, + json=_call(self._params.get('json')), + text=_call(self._params.get('text')), + content=_call(self._params.get('content')), + body=_call(self._params.get('body')), + raw=self._params.get('raw'), + json_encoder=self._params.get('json_encoder'), + status_code=context.status_code, + reason=context.reason, + headers=context.headers, + cookies=context.cookies) diff --git a/contrib/python/requests-mock/py2/requests_mock/response.pyi b/contrib/python/requests-mock/py2/requests_mock/response.pyi new file mode 100644 index 0000000000..e7c8977883 --- /dev/null +++ b/contrib/python/requests-mock/py2/requests_mock/response.pyi @@ -0,0 +1,38 @@ +# Stubs for requests_mock.response + +from typing import Any, Dict + +import six + +from requests import Request, Response +from requests.cookies import RequestsCookieJar + +class CookieJar(RequestsCookieJar): + def set(self, name: Any, value: Any, **kwargs: Any) -> Any: ... + +class _FakeConnection: + def send(self, request: Any, **kwargs: Any) -> None: ... + def close(self) -> None: ... + +class _IOReader(six.BytesIO): + def read(self, *args: Any, **kwargs: Any) -> Any: ... + +def create_response(request: Any, **kwargs: Any) -> Response: ... + +class _Context: + headers: Dict[str,str] = ... + status_code: int = ... + reason: str = ... + cookies: Any = ... + + def __init__(self, + headers: Dict[str, str], + status_code: int, + reason: str, + cookies: Any) -> None: ... + +class _MatcherResponse: + def __init__(self, **kwargs: Any) -> None: ... + def get_response(self, request: Request) -> Response: ... + +Context = _Context diff --git a/contrib/python/requests-mock/py2/tests/__init__.py b/contrib/python/requests-mock/py2/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/__init__.py diff --git a/contrib/python/requests-mock/py2/tests/base.py b/contrib/python/requests-mock/py2/tests/base.py new file mode 100644 index 0000000000..ecc872ecb7 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/base.py @@ -0,0 +1,17 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + + +class TestCase(unittest.TestCase): + pass diff --git a/contrib/python/requests-mock/py2/tests/pytest/__init__.py b/contrib/python/requests-mock/py2/tests/pytest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/pytest/__init__.py diff --git a/contrib/python/requests-mock/py2/tests/pytest/pytest.ini b/contrib/python/requests-mock/py2/tests/pytest/pytest.ini new file mode 100644 index 0000000000..58c87f21b7 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/pytest/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +requests_mock_case_sensitive=false diff --git a/contrib/python/requests-mock/py2/tests/pytest/test_with_pytest.py b/contrib/python/requests-mock/py2/tests/pytest/test_with_pytest.py new file mode 100644 index 0000000000..d74908bc38 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/pytest/test_with_pytest.py @@ -0,0 +1,119 @@ +try: + from http import HTTPStatus + HTTP_STATUS_FOUND = HTTPStatus.FOUND +except ImportError: + from httplib import FOUND as HTTP_STATUS_FOUND + +import pytest +import requests +import requests_mock + + +def test_simple(requests_mock): + requests_mock.get('https://httpbin.org/get', text='data') + assert 'data' == requests.get('https://httpbin.org/get').text + + +def test_redirect_and_nesting(): + url_inner = "inner-mock://example.test/" + url_middle = "middle-mock://example.test/" + url_outer = "outer-mock://example.test/" + url_base = "https://www.example.com/" + + text_middle = 'middle' + url_middle + text_outer = 'outer' + url_outer + text_base = 'outer' + url_base + + with requests_mock.Mocker() as outer_mock: + outer_mock.get(url_base, text=text_base) + outer_mock.get(url_outer, text=text_outer) + + with requests_mock.Mocker(real_http=True) as middle_mock: + middle_mock.get(url_middle, text=text_middle) + + with requests_mock.Mocker() as inner_mock: + inner_mock.post(url_inner, + status_code=HTTP_STATUS_FOUND, + headers={'location': url_base}) + inner_mock.get(url_base, real_http=True) + + assert text_base == requests.post(url_inner).text # nosec + + with pytest.raises(requests_mock.NoMockAddress): + requests.get(url_middle) + + with pytest.raises(requests_mock.NoMockAddress): + requests.get(url_outer) + + # back to middle mock + with pytest.raises(requests_mock.NoMockAddress): + requests.post(url_inner) + + assert text_middle == requests.get(url_middle).text # nosec + assert text_outer == requests.get(url_outer).text # nosec + + # back to outter mock + with pytest.raises(requests_mock.NoMockAddress): + requests.post(url_inner) + + with pytest.raises(requests_mock.NoMockAddress): + requests.get(url_middle) + + assert text_outer == requests.get(url_outer).text # nosec + + +def test_mixed_mocks(): + url = 'mock://example.test/' + with requests_mock.Mocker() as global_mock: + global_mock.get(url, text='global') + session = requests.Session() + text = session.get(url).text + assert text == 'global' # nosec + with requests_mock.Mocker(session=session) as session_mock: + session_mock.get(url, real_http=True) + text = session.get(url).text + assert text == 'global' # nosec + + +def test_threaded_sessions(): + """ + When using requests_futures.FuturesSession() with a ThreadPoolExecutor + there is a race condition where one threaded request removes the + monkeypatched get_adapter() method from the Session before another threaded + request is finished using it. + """ + from requests_futures.sessions import FuturesSession + + url1 = 'http://www.example.com/requests-mock-fake-url1' + url2 = 'http://www.example.com/requests-mock-fake-url2' + + with requests_mock.Mocker() as m: + # respond with 204 so we know its us + m.get(url1, status_code=204) + m.get(url2, status_code=204) + + # NOTE(phodge): just firing off two .get() requests right after each + # other was a pretty reliable way to reproduce the race condition on my + # intel Macbook Pro but YMMV. Guaranteeing the race condition to + # reappear might require replacing the Session.send() with a wrapper + # that delays kicking off the request for url1 until the request for + # url2 has restored the original session.get_adapter(), but replacing + # Session.send() could be difficult because the requests_mock.Mocker() + # context manager has *already* monkeypatched this method. + session = FuturesSession() + future1 = session.get(url1) + future2 = session.get(url2) + + # verify both requests were handled by the mock dispatcher + assert future1.result().status_code == 204 + assert future2.result().status_code == 204 + + +class TestClass(object): + + def configure(self, requests_mock): + requests_mock.get('https://httpbin.org/get', text='data') + + def test_one(self, requests_mock): + self.configure(requests_mock) + assert 'data' == requests.get('https://httpbin.org/get').text diff --git a/contrib/python/requests-mock/py2/tests/test_adapter.py b/contrib/python/requests-mock/py2/tests/test_adapter.py new file mode 100644 index 0000000000..2913727072 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/test_adapter.py @@ -0,0 +1,707 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import re + +import purl +import requests +import six +from six.moves.urllib import parse as urlparse + +import requests_mock +from . import base + + +class MyExc(Exception): + pass + + +class SessionAdapterTests(base.TestCase): + + PREFIX = "mock" + + def setUp(self): + super(SessionAdapterTests, self).setUp() + + self.adapter = requests_mock.Adapter() + self.session = requests.Session() + self.session.mount(self.PREFIX, self.adapter) + + self.url = '%s://example.com/test' % self.PREFIX + self.headers = {'header_a': 'A', 'header_b': 'B'} + + def assertHeaders(self, resp): + for k, v in six.iteritems(self.headers): + self.assertEqual(v, resp.headers[k]) + + def assertLastRequest(self, method='GET', body=None): + self.assertEqual(self.url, self.adapter.last_request.url) + self.assertEqual(method, self.adapter.last_request.method) + self.assertEqual(body, self.adapter.last_request.body) + + url_parts = urlparse.urlparse(self.url) + qs = urlparse.parse_qs(url_parts.query) + self.assertEqual(url_parts.scheme, self.adapter.last_request.scheme) + self.assertEqual(url_parts.netloc, self.adapter.last_request.netloc) + self.assertEqual(url_parts.path, self.adapter.last_request.path) + self.assertEqual(url_parts.query, self.adapter.last_request.query) + self.assertEqual(url_parts.query, self.adapter.last_request.query) + self.assertEqual(qs, self.adapter.last_request.qs) + + def test_content(self): + data = six.b('testdata') + + self.adapter.register_uri('GET', + self.url, + content=data, + headers=self.headers) + resp = self.session.get(self.url) + self.assertEqual(data, resp.content) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_content_callback(self): + status_code = 401 + data = six.b('testdata') + + def _content_cb(request, context): + context.status_code = status_code + context.headers.update(self.headers) + return data + + self.adapter.register_uri('GET', + self.url, + content=_content_cb) + resp = self.session.get(self.url) + self.assertEqual(status_code, resp.status_code) + self.assertEqual(data, resp.content) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_text(self): + data = 'testdata' + + self.adapter.register_uri('GET', + self.url, + text=data, + headers=self.headers) + resp = self.session.get(self.url) + self.assertEqual(six.b(data), resp.content) + self.assertEqual(six.u(data), resp.text) + self.assertEqual('utf-8', resp.encoding) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_text_callback(self): + status_code = 401 + data = 'testdata' + + def _text_cb(request, context): + context.status_code = status_code + context.headers.update(self.headers) + return six.u(data) + + self.adapter.register_uri('GET', self.url, text=_text_cb) + resp = self.session.get(self.url) + self.assertEqual(status_code, resp.status_code) + self.assertEqual(six.u(data), resp.text) + self.assertEqual(six.b(data), resp.content) + self.assertEqual('utf-8', resp.encoding) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_json(self): + json_data = {'hello': 'world'} + self.adapter.register_uri('GET', + self.url, + json=json_data, + headers=self.headers) + resp = self.session.get(self.url) + self.assertEqual(six.b('{"hello": "world"}'), resp.content) + self.assertEqual(six.u('{"hello": "world"}'), resp.text) + self.assertEqual(json_data, resp.json()) + self.assertEqual('utf-8', resp.encoding) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_json_callback(self): + status_code = 401 + json_data = {'hello': 'world'} + data = '{"hello": "world"}' + + def _json_cb(request, context): + context.status_code = status_code + context.headers.update(self.headers) + return json_data + + self.adapter.register_uri('GET', self.url, json=_json_cb) + resp = self.session.get(self.url) + self.assertEqual(status_code, resp.status_code) + self.assertEqual(json_data, resp.json()) + self.assertEqual(six.u(data), resp.text) + self.assertEqual(six.b(data), resp.content) + self.assertEqual('utf-8', resp.encoding) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_no_body(self): + self.adapter.register_uri('GET', self.url) + resp = self.session.get(self.url) + self.assertEqual(six.b(''), resp.content) + self.assertEqual(200, resp.status_code) + + def test_multiple_body_elements(self): + self.assertRaises(RuntimeError, + self.adapter.register_uri, + self.url, + 'GET', + content=six.b('b'), + text=six.u('u')) + + def test_multiple_responses(self): + inp = [{'status_code': 400, 'text': 'abcd'}, + {'status_code': 300, 'text': 'defg'}, + {'status_code': 200, 'text': 'hijk'}] + + self.adapter.register_uri('GET', self.url, inp) + out = [self.session.get(self.url) for i in range(0, len(inp))] + + for i, o in zip(inp, out): + for k, v in six.iteritems(i): + self.assertEqual(v, getattr(o, k)) + + last = self.session.get(self.url) + for k, v in six.iteritems(inp[-1]): + self.assertEqual(v, getattr(last, k)) + + def test_callback_optional_status(self): + headers = {'a': 'b'} + + def _test_cb(request, context): + context.headers.update(headers) + return '' + + self.adapter.register_uri('GET', + self.url, + text=_test_cb, + status_code=300) + resp = self.session.get(self.url) + self.assertEqual(300, resp.status_code) + + for k, v in six.iteritems(headers): + self.assertEqual(v, resp.headers[k]) + + def test_callback_optional_headers(self): + headers = {'a': 'b'} + + def _test_cb(request, context): + context.status_code = 300 + return '' + + self.adapter.register_uri('GET', + self.url, + text=_test_cb, + headers=headers) + + resp = self.session.get(self.url) + self.assertEqual(300, resp.status_code) + + for k, v in six.iteritems(headers): + self.assertEqual(v, resp.headers[k]) + + def test_latest_register_overrides(self): + self.adapter.register_uri('GET', self.url, text='abc') + self.adapter.register_uri('GET', self.url, text='def') + + resp = self.session.get(self.url) + self.assertEqual('def', resp.text) + + def test_no_last_request(self): + self.assertIsNone(self.adapter.last_request) + self.assertEqual(0, len(self.adapter.request_history)) + + def test_dont_pass_list_and_kwargs(self): + self.assertRaises(RuntimeError, + self.adapter.register_uri, + 'GET', + self.url, + [{'text': 'a'}], + headers={'a': 'b'}) + + def test_empty_string_return(self): + # '' evaluates as False, so make sure an empty string is not ignored. + self.adapter.register_uri('GET', self.url, text='') + resp = self.session.get(self.url) + self.assertEqual('', resp.text) + + def test_dont_pass_multiple_bodies(self): + self.assertRaises(RuntimeError, + self.adapter.register_uri, + 'GET', + self.url, + json={'abc': 'def'}, + text='ghi') + + def test_dont_pass_unexpected_kwargs(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + unknown='argument') + + def test_dont_pass_unicode_as_content(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + content=six.u('unicode')) + + def test_dont_pass_empty_string_as_content(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + content=six.u('')) + + def test_dont_pass_bytes_as_text(self): + if six.PY2: + self.skipTest('Cannot enforce byte behaviour in PY2') + + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + text=six.b('bytes')) + + def test_dont_pass_empty_string_as_text(self): + if six.PY2: + self.skipTest('Cannot enforce byte behaviour in PY2') + + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + text=six.b('')) + + def test_dont_pass_non_str_as_content(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + content=5) + + def test_dont_pass_non_str_as_text(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + text=5) + + def test_with_any_method(self): + self.adapter.register_uri(requests_mock.ANY, self.url, text='resp') + + for m in ('GET', 'HEAD', 'POST', 'UNKNOWN'): + resp = self.session.request(m, self.url) + self.assertEqual('resp', resp.text) + + def test_with_any_url(self): + self.adapter.register_uri('GET', requests_mock.ANY, text='resp') + + for u in ('mock://a', 'mock://b', 'mock://c'): + resp = self.session.get(u) + self.assertEqual('resp', resp.text) + + def test_with_regexp(self): + self.adapter.register_uri('GET', re.compile('tester.com'), text='resp') + + for u in ('mock://www.tester.com/a', 'mock://abc.tester.com'): + resp = self.session.get(u) + self.assertEqual('resp', resp.text) + + def test_with_purl(self): + self.adapter.register_uri('GET', + purl.URL('mock://www.tester.com/a'), + text='resp') + + resp = self.session.get('mock://www.tester.com/a') + self.assertEqual('resp', resp.text) + + def test_requests_in_history_on_no_match(self): + self.assertRaises(requests_mock.NoMockAddress, + self.session.get, + self.url) + + self.assertEqual(self.url, self.adapter.last_request.url) + + def test_requests_in_history_on_exception(self): + + def _test_cb(request, ctx): + raise MyExc() + + self.adapter.register_uri('GET', self.url, text=_test_cb) + + self.assertRaises(MyExc, + self.session.get, + self.url) + + self.assertEqual(self.url, self.adapter.last_request.url) + + def test_not_called_and_called_count(self): + m = self.adapter.register_uri('GET', self.url, text='resp') + self.assertEqual(0, m.call_count) + self.assertFalse(m.called) + self.assertFalse(m.called_once) + + self.assertEqual(0, self.adapter.call_count) + self.assertFalse(self.adapter.called) + self.assertFalse(m.called_once) + + def test_called_and_called_count(self): + m = self.adapter.register_uri('GET', self.url, text='resp') + + resps = [self.session.get(self.url) for i in range(0, 3)] + + for r in resps: + self.assertEqual('resp', r.text) + self.assertEqual(200, r.status_code) + + self.assertEqual(len(resps), m.call_count) + self.assertTrue(m.called) + self.assertFalse(m.called_once) + + self.assertEqual(len(resps), self.adapter.call_count) + self.assertTrue(self.adapter.called) + self.assertFalse(m.called_once) + + def test_reset_reverts_call_count(self): + # Create matchers and add calls to history + call_count = 3 + matcher_count = 3 + for i in range(matcher_count): + url = self.url + str(i) + self.adapter.register_uri('GET', url, text='resp') + for _ in range(call_count): + self.session.get(url) + + # Verify call counts on adapter and matchers + self.assertEqual(self.adapter.call_count, matcher_count * call_count) + for matcher in self.adapter._matchers: + self.assertEqual(matcher.call_count, call_count) + + self.adapter.reset() + + # Verify call counts are 0 after reset + self.assertEqual(self.adapter.call_count, 0) + for matcher in self.adapter._matchers: + self.assertEqual(matcher.call_count, 0) + + def test_adapter_picks_correct_adapter(self): + good = '%s://test3.url/' % self.PREFIX + self.adapter.register_uri('GET', + '%s://test1.url' % self.PREFIX, + text='bad') + self.adapter.register_uri('GET', + '%s://test2.url' % self.PREFIX, + text='bad') + self.adapter.register_uri('GET', good, text='good') + self.adapter.register_uri('GET', + '%s://test4.url' % self.PREFIX, + text='bad') + + resp = self.session.get(good) + + self.assertEqual('good', resp.text) + + def test_adapter_is_connection(self): + url = '%s://test.url' % self.PREFIX + text = 'text' + self.adapter.register_uri('GET', url, text=text) + resp = self.session.get(url) + + self.assertEqual(text, resp.text) + self.assertIs(self.adapter, resp.connection) + + def test_send_to_connection(self): + url1 = '%s://test1.url/' % self.PREFIX + url2 = '%s://test2.url/' % self.PREFIX + + text1 = 'text1' + text2 = 'text2' + + self.adapter.register_uri('GET', url1, text=text1) + self.adapter.register_uri('GET', url2, text=text2) + + req = requests.Request(method='GET', url=url2).prepare() + + resp1 = self.session.get(url1) + self.assertEqual(text1, resp1.text) + + resp2 = resp1.connection.send(req) + self.assertEqual(text2, resp2.text) + + def test_request_json_with_str_data(self): + dict_req = {'hello': 'world'} + dict_resp = {'goodbye': 'world'} + + m = self.adapter.register_uri('POST', self.url, json=dict_resp) + + data = json.dumps(dict_req) + resp = self.session.post(self.url, data=data) + + self.assertIs(data, m.last_request.body) + self.assertEqual(dict_resp, resp.json()) + self.assertEqual(dict_req, m.last_request.json()) + + def test_request_json_with_bytes_data(self): + dict_req = {'hello': 'world'} + dict_resp = {'goodbye': 'world'} + + m = self.adapter.register_uri('POST', self.url, json=dict_resp) + + data = json.dumps(dict_req).encode('utf-8') + resp = self.session.post(self.url, data=data) + + self.assertIs(data, m.last_request.body) + self.assertEqual(dict_resp, resp.json()) + self.assertEqual(dict_req, m.last_request.json()) + + def test_request_json_with_cb(self): + dict_req = {'hello': 'world'} + dict_resp = {'goodbye': 'world'} + data = json.dumps(dict_req) + + def _cb(req, context): + self.assertEqual(dict_req, req.json()) + return dict_resp + + m = self.adapter.register_uri('POST', self.url, json=_cb) + resp = self.session.post(self.url, data=data) + + self.assertEqual(1, m.call_count) + self.assertTrue(m.called_once) + self.assertEqual(dict_resp, resp.json()) + + def test_raises_exception(self): + self.adapter.register_uri('GET', self.url, exc=MyExc) + + self.assertRaises(MyExc, + self.session.get, + self.url) + + self.assertTrue(self.adapter.called_once) + self.assertEqual(self.url, self.adapter.last_request.url) + + def test_raises_exception_with_body_args_fails(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + exc=MyExc, + text='fail') + + def test_sets_request_matcher_in_history(self): + url1 = '%s://test1.url/' % self.PREFIX + url2 = '%s://test2.url/' % self.PREFIX + + text1 = 'text1' + text2 = 'text2' + + m1 = self.adapter.register_uri('GET', url1, text=text1) + m2 = self.adapter.register_uri('GET', url2, text=text2) + + resp1 = self.session.get(url1) + resp2 = self.session.get(url2) + + self.assertEqual(text1, resp1.text) + self.assertEqual(text2, resp2.text) + + self.assertEqual(2, self.adapter.call_count) + self.assertFalse(self.adapter.called_once) + + self.assertEqual(url1, self.adapter.request_history[0].url) + self.assertEqual(url2, self.adapter.request_history[1].url) + + self.assertIs(m1, self.adapter.request_history[0].matcher) + self.assertIs(m2, self.adapter.request_history[1].matcher) + + def test_sets_request_matcher_on_exception(self): + m = self.adapter.register_uri('GET', self.url, exc=MyExc) + + self.assertRaises(MyExc, + self.session.get, + self.url) + + self.assertEqual(self.url, self.adapter.last_request.url) + self.assertIs(m, self.adapter.last_request.matcher) + + def test_cookies_from_header(self): + headers = {'Set-Cookie': 'fig=newton; Path=/test; domain=.example.com'} + self.adapter.register_uri('GET', + self.url, + text='text', + headers=headers) + + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual(['/test'], resp.cookies.list_paths()) + self.assertEqual(['.example.com'], resp.cookies.list_domains()) + + def test_cookies_from_dict(self): + # This is a syntax we get from requests. I'm not sure i like it. + self.adapter.register_uri('GET', + self.url, + text='text', + cookies={'fig': 'newton', 'sugar': 'apple'}) + + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + + def test_cookies_with_jar(self): + jar = requests_mock.CookieJar() + jar.set('fig', 'newton', path='/foo', domain='.example.com') + jar.set('sugar', 'apple', path='/bar', domain='.example.com') + + self.adapter.register_uri('GET', self.url, text='text', cookies=jar) + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + self.assertEqual({'/foo', '/bar'}, set(resp.cookies.list_paths())) + self.assertEqual(['.example.com'], resp.cookies.list_domains()) + + def test_cookies_header_with_cb(self): + + def _cb(request, context): + val = 'fig=newton; Path=/test; domain=.example.com' + context.headers['Set-Cookie'] = val + return 'text' + + self.adapter.register_uri('GET', self.url, text=_cb) + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual(['/test'], resp.cookies.list_paths()) + self.assertEqual(['.example.com'], resp.cookies.list_domains()) + + def test_cookies_from_dict_with_cb(self): + def _cb(request, context): + # converted into a jar by now + context.cookies.set('sugar', 'apple', path='/test') + return 'text' + + self.adapter.register_uri('GET', + self.url, + text=_cb, + cookies={'fig': 'newton'}) + + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + self.assertEqual(['/', '/test'], resp.cookies.list_paths()) + + def test_cookies_with_jar_cb(self): + def _cb(request, context): + context.cookies.set('sugar', + 'apple', + path='/bar', + domain='.example.com') + return 'text' + + jar = requests_mock.CookieJar() + jar.set('fig', 'newton', path='/foo', domain='.example.com') + + self.adapter.register_uri('GET', self.url, text=_cb, cookies=jar) + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + self.assertEqual({'/foo', '/bar'}, set(resp.cookies.list_paths())) + self.assertEqual(['.example.com'], resp.cookies.list_domains()) + + def test_reading_closed_fp(self): + self.adapter.register_uri('GET', self.url, text='abc') + resp = self.session.get(self.url) + + # raw will have been closed during the request reading + self.assertTrue(resp.raw.closed) + + data = resp.raw.read() + + self.assertIsInstance(data, six.binary_type) + self.assertEqual(0, len(data)) + + def test_case_sensitive_headers(self): + data = 'testdata' + headers = {'aBcDe': 'FgHiJ'} + + self.adapter.register_uri('GET', self.url, text=data) + resp = self.session.get(self.url, headers=headers) + + self.assertEqual('GET', self.adapter.last_request.method) + self.assertEqual(200, resp.status_code) + self.assertEqual(data, resp.text) + + for k, v in headers.items(): + self.assertEqual(v, self.adapter.last_request.headers[k]) + + def test_case_sensitive_history(self): + self.adapter._case_sensitive = True + + data = 'testdata' + netloc = 'examPlE.CoM' + path = '/TesTER' + query = 'aBC=deF' + + mock_url = '%s://%s%s' % (self.PREFIX, netloc.lower(), path) + request_url = '%s://%s%s?%s' % (self.PREFIX, netloc, path, query) + + # test that the netloc is ignored when actually making the request + self.adapter.register_uri('GET', mock_url, text=data) + resp = self.session.get(request_url) + + self.assertEqual('GET', self.adapter.last_request.method) + self.assertEqual(200, resp.status_code) + self.assertEqual(data, resp.text) + + # but even still the mixed case parameters come out in history + self.assertEqual(netloc, self.adapter.last_request.netloc) + self.assertEqual(path, self.adapter.last_request.path) + self.assertEqual(query, self.adapter.last_request.query) + + def test_stream_none(self): + text = 'hello world' + + self.adapter.register_uri('GET', + self.url, + text=text, + headers=self.headers) + + resp = self.session.get(self.url, stream=True) + resps = [c for c in resp.iter_content(None, decode_unicode=True)] + self.assertEqual([text], resps) + + def test_stream_size(self): + text = 'hello world' + + self.adapter.register_uri('GET', + self.url, + text=text, + headers=self.headers) + + resp = self.session.get(self.url, stream=True) + resps = [c for c in resp.iter_content(3, decode_unicode=True)] + self.assertEqual(['hel', 'lo ', 'wor', 'ld'], resps) diff --git a/contrib/python/requests-mock/py2/tests/test_custom_matchers.py b/contrib/python/requests-mock/py2/tests/test_custom_matchers.py new file mode 100644 index 0000000000..546a63e2f1 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/test_custom_matchers.py @@ -0,0 +1,75 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import requests +import six + +import requests_mock +from . import base + + +class FailMatcher(object): + + def __init___(self): + self.called = False + + def __call__(self, request): + self.called = True + return None + + +def match_all(request): + return requests_mock.create_response(request, content=six.b('data')) + + +class CustomMatchersTests(base.TestCase): + + def assertMatchAll(self, resp): + self.assertEqual(200, resp.status_code) + self.assertEqual(resp.text, six.u('data')) + + @requests_mock.Mocker() + def test_custom_matcher(self, mocker): + mocker.add_matcher(match_all) + + resp = requests.get('http://any/thing') + self.assertMatchAll(resp) + + @requests_mock.Mocker() + def test_failing_matcher(self, mocker): + failer = FailMatcher() + + mocker.add_matcher(match_all) + mocker.add_matcher(failer) + + resp = requests.get('http://any/thing') + + self.assertMatchAll(resp) + self.assertTrue(failer.called) + + @requests_mock.Mocker() + def test_some_pass(self, mocker): + + def matcher_a(request): + if 'a' in request.url: + return match_all(request) + + return None + + mocker.add_matcher(matcher_a) + + resp = requests.get('http://any/thing') + self.assertMatchAll(resp) + + self.assertRaises(requests_mock.NoMockAddress, + requests.get, + 'http://other/thing') diff --git a/contrib/python/requests-mock/py2/tests/test_fixture.py b/contrib/python/requests-mock/py2/tests/test_fixture.py new file mode 100644 index 0000000000..63cbb99141 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/test_fixture.py @@ -0,0 +1,39 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import requests +import requests_mock +from requests_mock.contrib import fixture +from . import base + + +class MockingTests(base.TestCase): + + def setUp(self): + super(MockingTests, self).setUp() + self.mocker = self.useFixture(fixture.Fixture()) + + def test_failure(self): + self.assertRaises(requests_mock.NoMockAddress, + requests.get, + 'http://www.google.com') + + def test_basic(self): + test_url = 'http://www.google.com/' + self.mocker.register_uri('GET', test_url, text='response') + + resp = requests.get(test_url) + self.assertEqual('response', resp.text) + self.assertEqual(test_url, self.mocker.last_request.url) + + def test_fixture_has_normal_attr_error(self): + self.assertRaises(AttributeError, lambda: self.mocker.unknown) diff --git a/contrib/python/requests-mock/py2/tests/test_matcher.py b/contrib/python/requests-mock/py2/tests/test_matcher.py new file mode 100644 index 0000000000..a30e195966 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/test_matcher.py @@ -0,0 +1,324 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import re + +from requests_mock import adapter +from . import base +from requests_mock.response import _MatcherResponse + +ANY = adapter.ANY + + +class TestMatcher(base.TestCase): + + def match(self, + target, + url, + matcher_method='GET', + request_method='GET', + complete_qs=False, + headers=None, + request_data=None, + request_headers={}, + additional_matcher=None, + real_http=False, + case_sensitive=False): + matcher = adapter._Matcher(matcher_method, + target, + [], + complete_qs=complete_qs, + additional_matcher=additional_matcher, + request_headers=request_headers, + real_http=real_http, + case_sensitive=case_sensitive) + request = adapter._RequestObjectProxy._create(request_method, + url, + headers, + data=request_data) + return matcher._match(request) + + def assertMatch(self, + target=ANY, + url='http://example.com/requests-mock', + matcher_method='GET', + request_method='GET', + **kwargs): + self.assertEqual(True, + self.match(target, + url, + matcher_method=matcher_method, + request_method=request_method, + **kwargs), + 'Matcher %s %s failed to match %s %s' % + (matcher_method, target, request_method, url)) + + def assertMatchBoth(self, + target=ANY, + url='http://example.com/requests-mock', + matcher_method='GET', + request_method='GET', + **kwargs): + self.assertMatch(target, + url, + matcher_method=matcher_method, + request_method=request_method, + **kwargs) + self.assertMatch(url, + target, + matcher_method=request_method, + request_method=matcher_method, + **kwargs) + + def assertNoMatch(self, + target=ANY, + url='http://example.com/requests-mock', + matcher_method='GET', + request_method='GET', + **kwargs): + self.assertEqual(False, + self.match(target, + url, + matcher_method=matcher_method, + request_method=request_method, + **kwargs), + 'Matcher %s %s unexpectedly matched %s %s' % + (matcher_method, target, request_method, url)) + + def assertNoMatchBoth(self, + target=ANY, + url='http://example.com/requests-mock', + matcher_method='GET', + request_method='GET', + **kwargs): + self.assertNoMatch(target, + url, + matcher_method=matcher_method, + request_method=request_method, + **kwargs) + self.assertNoMatch(url, + target, + matcher_method=request_method, + request_method=matcher_method, + **kwargs) + + def assertMatchMethodBoth(self, matcher_method, request_method, **kwargs): + url = 'http://www.test.com' + + self.assertMatchBoth(url, + url, + request_method=request_method, + matcher_method=matcher_method, + **kwargs) + + def assertNoMatchMethodBoth(self, + matcher_method, + request_method, + **kwargs): + url = 'http://www.test.com' + + self.assertNoMatchBoth(url, + url, + request_method=request_method, + matcher_method=matcher_method, + **kwargs) + + def test_url_matching(self): + self.assertMatchBoth('http://www.test.com', + 'http://www.test.com') + self.assertMatchBoth('http://www.test.com', + 'http://www.test.com/') + self.assertMatchBoth('http://www.test.com/abc', + 'http://www.test.com/abc') + self.assertMatchBoth('http://www.test.com:5000/abc', + 'http://www.test.com:5000/abc') + self.assertNoMatchBoth('https://www.test.com', + 'http://www.test.com') + self.assertNoMatchBoth('http://www.test.com/abc', + 'http://www.test.com') + self.assertNoMatchBoth('http://test.com', + 'http://www.test.com') + self.assertNoMatchBoth('http://test.com', + 'http://www.test.com') + self.assertNoMatchBoth('http://test.com/abc', + 'http://www.test.com/abc/') + self.assertNoMatchBoth('http://test.com/abc/', + 'http://www.test.com/abc') + self.assertNoMatchBoth('http://test.com:5000/abc/', + 'http://www.test.com/abc') + self.assertNoMatchBoth('http://test.com/abc/', + 'http://www.test.com:5000/abc') + + def test_quotation(self): + self.assertMatchBoth('http://www.test.com/a string%url', + 'http://www.test.com/a string%url') + self.assertMatchBoth('http://www.test.com/ABC 123', + 'http://www.test.com/ABC%20123') + self.assertMatchBoth('http://www.test.com/user@example.com', + 'http://www.test.com/user@example.com') + + def test_subset_match(self): + self.assertMatch('/path', 'http://www.test.com/path') + self.assertMatch('/path', 'http://www.test.com/path') + self.assertMatch('//www.test.com/path', 'http://www.test.com/path') + self.assertMatch('//www.test.com/path', 'https://www.test.com/path') + + def test_query_string(self): + self.assertMatch('/path?a=1&b=2', + 'http://www.test.com/path?a=1&b=2') + self.assertMatch('/path?a=1', + 'http://www.test.com/path?a=1&b=2', + complete_qs=False) + self.assertNoMatch('/path?a=1', + 'http://www.test.com/path?a=1&b=2', + complete_qs=True) + self.assertNoMatch('/path?a=1&b=2', + 'http://www.test.com/path?a=1') + + def test_query_empty_string(self): + self.assertMatch('/path?a', + 'http://www.test.com/path?a') + self.assertMatch('/path?bob&paul', + 'http://www.test.com/path?paul&bob') + self.assertNoMatch('/path?bob', + 'http://www.test.com/path?paul') + self.assertNoMatch('/path?pual&bob', + 'http://www.test.com/path?bob') + + def test_method_match(self): + self.assertNoMatchMethodBoth('GET', 'POST') + self.assertMatchMethodBoth('GET', 'get') + self.assertMatchMethodBoth('GeT', 'geT') + + def test_match_ANY_url(self): + self.assertMatch(ANY, 'http://anything') + self.assertMatch(ANY, 'http://somethingelse') + self.assertNoMatch(ANY, 'http://somethingelse', request_method='POST') + + def test_match_ANY_method(self): + for m in ('GET', 'POST', 'HEAD', 'OPTION'): + self.assertMatch('http://www.test.com', + 'http://www.test.com', + matcher_method=ANY, + request_method=m) + + self.assertNoMatch('http://www.test.com', + 'http://another', + matcher_method=ANY) + + def test_match_with_regex(self): + r1 = re.compile('test.com/a') + r2 = re.compile('/b/c') + + self.assertMatch(r1, 'http://mock.test.com/a/b') + self.assertMatch(r1, 'http://test.com/a/') + self.assertMatch(r1, 'mock://test.com/a/b') + self.assertNoMatch(r1, 'mock://test.com/') + + self.assertMatch(r2, 'http://anything/a/b/c/d') + self.assertMatch(r2, 'mock://anything/a/b/c/d') + + def test_match_with_headers(self): + self.assertMatch('/path', + 'http://www.test.com/path', + headers={'A': 'abc', 'b': 'def'}, + request_headers={'a': 'abc'}) + + self.assertMatch('/path', + 'http://www.test.com/path', + headers={'A': 'abc', 'b': 'def'}) + + self.assertNoMatch('/path', + 'http://www.test.com/path', + headers={'A': 'abc', 'b': 'def'}, + request_headers={'b': 'abc'}) + + self.assertNoMatch('/path', + 'http://www.test.com/path', + headers={'A': 'abc', 'b': 'def'}, + request_headers={'c': 'ghi'}) + + # headers should be key insensitive and value sensitive, we have no + # choice here because they go into an insensitive dict. + self.assertMatch('/path', + 'http://www.test.com/path', + headers={'aBc': 'abc', 'DEF': 'def'}, + request_headers={'abC': 'abc'}) + + self.assertNoMatch('/path', + 'http://www.test.com/path', + headers={'abc': 'aBC', 'DEF': 'def'}, + request_headers={'abc': 'Abc'}) + + def test_case_sensitive_ignored_for_netloc_and_protocol(self): + for case_sensitive in (True, False): + self.assertMatch('http://AbC.CoM', + 'http://aBc.CoM', + case_sensitive=case_sensitive) + + self.assertMatch('htTP://abc.com', + 'hTTp://abc.com', + case_sensitive=case_sensitive) + + self.assertMatch('htTP://aBC.cOm', + 'hTTp://AbC.Com', + case_sensitive=case_sensitive) + + def assertSensitiveMatch(self, target, url, **kwargs): + self.assertMatch(target, url, case_sensitive=False, **kwargs) + self.assertNoMatch(target, url, case_sensitive=True, **kwargs) + + def test_case_sensitive_paths(self): + self.assertSensitiveMatch('http://abc.com/pAtH', 'http://abc.com/path') + self.assertSensitiveMatch('/pAtH', 'http://abc.com/path') + + def test_case_sensitive_query(self): + self.assertSensitiveMatch('http://abc.com/path?abCD=efGH', + 'http://abc.com/path?abCd=eFGH') + + self.assertSensitiveMatch('http://abc.com/path?abcd=efGH', + 'http://abc.com/path?abcd=eFGH') + + def test_additional_matcher(self): + + def test_match_body(request): + return 'hello' in request.text + + self.assertMatch(request_method='POST', + matcher_method='POST', + request_data='hello world', + additional_matcher=test_match_body) + + self.assertNoMatch(request_method='POST', + matcher_method='POST', + request_data='goodbye world', + additional_matcher=test_match_body) + + def test_reset_reverts_count(self): + url = 'mock://test/site/' + matcher = adapter._Matcher('GET', + url, + [_MatcherResponse()], + complete_qs=False, + additional_matcher=None, + request_headers={}, + real_http=False, + case_sensitive=False) + request = adapter._RequestObjectProxy._create('GET', url) + + call_count = 3 + for _ in range(call_count): + matcher(request) + + self.assertEqual(matcher.call_count, call_count) + matcher.reset() + self.assertEqual(matcher.call_count, 0) diff --git a/contrib/python/requests-mock/py2/tests/test_mocker.py b/contrib/python/requests-mock/py2/tests/test_mocker.py new file mode 100644 index 0000000000..7a4061c6bd --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/test_mocker.py @@ -0,0 +1,646 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import pickle + +try: + from unittest import mock +except ImportError: + import mock +import requests + +import requests_mock +from requests_mock import adapter +from requests_mock import exceptions +from requests_mock import response +from . import base + +original_send = requests.Session.send + + +class MockerTests(base.TestCase): + + def assertMockStarted(self): + self.assertNotEqual(original_send, requests.Session.send) + + def assertMockStopped(self): + self.assertEqual(original_send, requests.Session.send) + + def _do_test(self, m): + self.assertMockStarted() + matcher = m.register_uri('GET', 'http://www.test.com', text='resp') + resp = requests.get('http://www.test.com') + self.assertEqual('resp', resp.text) + return matcher + + def test_multiple_starts(self): + mocker = requests_mock.Mocker() + self.assertMockStopped() + mocker.start() + self.assertMockStarted() + self.assertRaises(RuntimeError, mocker.start) + mocker.stop() + self.assertMockStopped() + mocker.stop() + + def test_with_session(self): + url = 'http://test.url/path' + url_inner = 'http://test.url/inner' + url_outer = 'http://test.url/outer' + with requests_mock.Mocker() as global_mock: + global_mock.get(url_outer, text='global') + + session_a = requests.Session() + session_b = requests.Session() + + session_a_original_send = session_a.send + session_b_original_send = session_b.send + self.assertNotEqual(session_a_original_send, + session_b_original_send) + + mocker_a = requests_mock.Mocker(session=session_a) + mocker_b = requests_mock.Mocker(session=session_b) + + mocker_a.start() + mocker_b.start() + + mocker_a.register_uri('GET', url, text='resp_a') + mocker_a.register_uri('GET', url_outer, real_http=True) + mocker_b.register_uri('GET', url, text='resp_b') + + with requests_mock.Mocker(session=session_b) as mocker_b_inner: + mocker_b_inner.register_uri('GET', url, real_http=True) + mocker_b_inner.register_uri('GET', + url_inner, + text='resp_b_inner') + + self.assertEqual('resp_a', session_a.get(url).text) + self.assertEqual('resp_b', session_b.get(url).text) + self.assertRaises(exceptions.NoMockAddress, + session_a.get, + url_inner) + self.assertEqual('resp_b_inner', session_b.get(url_inner).text) + + self.assertEqual('resp_a', session_a.get(url).text) + self.assertEqual('resp_b', session_b.get(url).text) + self.assertRaises(exceptions.NoMockAddress, + session_a.get, + url_inner) + self.assertRaises(exceptions.NoMockAddress, + session_b.get, + url_inner) + self.assertEqual('global', session_a.get(url_outer).text) + self.assertRaises(exceptions.NoMockAddress, + session_b.get, + url_outer) + + self.assertNotEqual(session_a.send, session_a_original_send) + self.assertNotEqual(session_b.send, session_b_original_send) + self.assertNotEqual(session_a.send, session_b.send) + + mocker_a.stop() + mocker_b.stop() + + self.assertEqual(session_a.send, session_a_original_send) + self.assertEqual(session_b.send, session_b_original_send) + + self.assertEqual(requests.Session.send, original_send) + + def test_with_context_manager(self): + self.assertMockStopped() + with requests_mock.Mocker() as m: + self._do_test(m) + self.assertMockStopped() + + @mock.patch('requests.adapters.HTTPAdapter.send') + @requests_mock.mock(real_http=True) + def test_real_http(self, real_send, mocker): + url = 'http://www.google.com/' + test_text = 'real http data' + test_bytes = test_text.encode('utf-8') + + # using create_response is a bit bootstrappy here but so long as it's + # coming from HTTPAdapter.send it's ok + req = requests.Request(method='GET', url=url) + real_send.return_value = response.create_response(req.prepare(), + status_code=200, + content=test_bytes) + resp = requests.get(url) + + self.assertEqual(1, real_send.call_count) + self.assertEqual(url, real_send.call_args[0][0].url) + + self.assertEqual(test_text, resp.text) + self.assertEqual(test_bytes, resp.content) + + @mock.patch('requests.adapters.HTTPAdapter.send') + def test_real_http_changes(self, real_send): + url = 'http://www.google.com/' + test_text = 'real http data' + test_bytes = test_text.encode('utf-8') + + req = requests.Request(method='GET', url=url) + real_send.return_value = response.create_response(req.prepare(), + status_code=200, + content=test_bytes) + + with requests_mock.Mocker() as m: + # real_http defaults to false so should raise NoMockAddress + + self.assertRaises(exceptions.NoMockAddress, + requests.get, + url) + + self.assertEqual(1, m.call_count) + self.assertEqual(0, real_send.call_count) + + # change the value of real_http mid test + m.real_http = True + + # fetch the url again and it should go through to the real url that + # we've mocked out at a lower level. + resp = requests.get(url) + + self.assertEqual(1, real_send.call_count) + self.assertEqual(url, real_send.call_args[0][0].url) + + self.assertEqual(test_text, resp.text) + self.assertEqual(test_bytes, resp.content) + + @mock.patch('requests.adapters.HTTPAdapter.send') + def test_real_http_and_session(self, real_send): + url = 'http://www.google.com/' + test_text = 'real http data' + test_bytes = test_text.encode('utf-8') + + req = requests.Request(method='GET', url=url) + real_send.return_value = response.create_response(req.prepare(), + status_code=200, + content=test_bytes) + + session = requests.Session() + with requests_mock.Mocker(session=session, real_http=True): + resp = session.get(url) + + self.assertEqual(test_text, resp.text) + self.assertEqual(test_bytes, resp.content) + + @requests_mock.mock() + def test_with_test_decorator(self, m): + self._do_test(m) + + @requests_mock.mock(kw='mock') + def test_with_mocker_kwargs(self, **kwargs): + self._do_test(kwargs['mock']) + + def test_with_decorator(self): + + @requests_mock.mock() + def inner(m): + self.assertMockStarted() + self._do_test(m) + + self.assertMockStopped() + inner() + self.assertMockStopped() + + def test_with_decorator_called_multiple_times(self): + + @requests_mock.Mocker() + def inner(arg1, m): + self._do_test(m) + self.assertEquals( + len(m.request_history), 1, + "Failed to provide clean mock on subsequent calls" + ) + + inner('a') + # if we call the same decorated method again should get + # a new request mock + inner('b') + + def test_with_class_decorator(self): + outer = self + + @requests_mock.mock() + class Decorated(object): + + def test_will_be_decorated(self, m): + outer.assertMockStarted() + outer._do_test(m) + + def will_not_be_decorated(self): + outer.assertMockStopped() + + decorated_class = Decorated() + + self.assertMockStopped() + decorated_class.test_will_be_decorated() + self.assertMockStopped() + decorated_class.will_not_be_decorated() + self.assertMockStopped() + + def test_with_class_decorator_and_custom_kw(self): + outer = self + + @requests_mock.mock(kw='custom_m') + class Decorated(object): + + def test_will_be_decorated(self, **kwargs): + outer.assertMockStarted() + outer._do_test(kwargs['custom_m']) + + def will_not_be_decorated(self): + outer.assertMockStopped() + + decorated_class = Decorated() + + self.assertMockStopped() + decorated_class.test_will_be_decorated() + self.assertMockStopped() + decorated_class.will_not_be_decorated() + self.assertMockStopped() + + @mock.patch.object(requests_mock.mock, 'TEST_PREFIX', 'foo') + def test_with_class_decorator_and_custom_test_prefix(self): + outer = self + + @requests_mock.mock() + class Decorated(object): + + def foo_will_be_decorated(self, m): + outer.assertMockStarted() + outer._do_test(m) + + def will_not_be_decorated(self): + outer.assertMockStopped() + + decorated_class = Decorated() + + self.assertMockStopped() + decorated_class.foo_will_be_decorated() + self.assertMockStopped() + decorated_class.will_not_be_decorated() + self.assertMockStopped() + + @requests_mock.mock() + def test_query_string(self, m): + url = 'http://test.url/path' + qs = 'a=1&b=2' + m.register_uri('GET', url, text='resp') + resp = requests.get("%s?%s" % (url, qs)) + + self.assertEqual('resp', resp.text) + + self.assertEqual(qs, m.last_request.query) + self.assertEqual(['1'], m.last_request.qs['a']) + self.assertEqual(['2'], m.last_request.qs['b']) + + @requests_mock.mock() + def test_mock_matcher_attributes(self, m): + matcher = self._do_test(m) + + self.assertEqual(1, matcher.call_count) + self.assertEqual(1, m.call_count) + + self.assertTrue(matcher.called) + self.assertTrue(matcher.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + self.assertEqual(m.request_history, matcher.request_history) + self.assertIs(m.last_request, matcher.last_request) + + def test_copy(self): + mocker = requests_mock.mock(kw='foo', real_http=True) + copy_of_mocker = mocker.copy() + self.assertIsNot(copy_of_mocker, mocker) + self.assertEqual(copy_of_mocker._kw, mocker._kw) + self.assertEqual(copy_of_mocker.real_http, mocker.real_http) + + @requests_mock.mock() + def test_reset_mock_reverts_call_count(self, request_mock): + url = 'http://test.url/path' + request_mock.get(url, text='resp') + requests.get(url) + + self.assertEqual(request_mock.call_count, 1) + + # reset count and verify it is 0 + request_mock.reset_mock() + self.assertEqual(request_mock.call_count, 0) + + +class MockerHttpMethodsTests(base.TestCase): + + URL = 'http://test.com/path' + TEXT = 'resp' + + def assertResponse(self, resp): + self.assertEqual(self.TEXT, resp.text) + + @requests_mock.Mocker() + def test_mocker_request(self, m): + method = 'XXX' + mock_obj = m.request(method, self.URL, text=self.TEXT) + resp = requests.request(method, self.URL) + self.assertResponse(resp) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_get(self, m): + mock_obj = m.get(self.URL, text=self.TEXT) + self.assertResponse(requests.get(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_options(self, m): + mock_obj = m.options(self.URL, text=self.TEXT) + self.assertResponse(requests.options(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_head(self, m): + mock_obj = m.head(self.URL, text=self.TEXT) + self.assertResponse(requests.head(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_post(self, m): + mock_obj = m.post(self.URL, text=self.TEXT) + self.assertResponse(requests.post(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_put(self, m): + mock_obj = m.put(self.URL, text=self.TEXT) + self.assertResponse(requests.put(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_patch(self, m): + mock_obj = m.patch(self.URL, text=self.TEXT) + self.assertResponse(requests.patch(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_delete(self, m): + mock_obj = m.delete(self.URL, text=self.TEXT) + self.assertResponse(requests.delete(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_real_http_and_responses(self, m): + self.assertRaises(RuntimeError, + m.get, + self.URL, + text='abcd', + real_http=True) + + @requests_mock.Mocker() + def test_mocker_real_http(self, m): + data = 'testdata' + + uri1 = 'fake://example.com/foo' + uri2 = 'fake://example.com/bar' + uri3 = 'fake://example.com/baz' + + m.get(uri1, text=data) + m.get(uri2, real_http=True) + + self.assertEqual(data, requests.get(uri1).text) + + # This should fail because requests can't get an adapter for mock:// + # but it shows that it has tried and would have made a request. + self.assertRaises(requests.exceptions.InvalidSchema, + requests.get, + uri2) + + # This fails because real_http is not set on the mocker + self.assertRaises(exceptions.NoMockAddress, + requests.get, + uri3) + + # do it again to make sure the mock is still in place + self.assertEqual(data, requests.get(uri1).text) + + @requests_mock.Mocker(case_sensitive=True) + def test_case_sensitive_query(self, m): + data = 'testdata' + query = {'aBcDe': 'FgHiJ'} + + m.get(self.URL, text=data) + resp = requests.get(self.URL, params=query) + + self.assertEqual('GET', m.last_request.method) + self.assertEqual(200, resp.status_code) + self.assertEqual(data, resp.text) + + for k, v in query.items(): + self.assertEqual([v], m.last_request.qs[k]) + + @mock.patch.object(requests_mock.Mocker, 'case_sensitive', True) + def test_global_case_sensitive(self): + with requests_mock.mock() as m: + data = 'testdata' + query = {'aBcDe': 'FgHiJ'} + + m.get(self.URL, text=data) + resp = requests.get(self.URL, params=query) + + self.assertEqual('GET', m.last_request.method) + self.assertEqual(200, resp.status_code) + self.assertEqual(data, resp.text) + + for k, v in query.items(): + self.assertEqual([v], m.last_request.qs[k]) + + def test_nested_mocking(self): + url1 = 'http://url1.com/path1' + url2 = 'http://url2.com/path2' + url3 = 'http://url3.com/path3' + + data1 = 'data1' + data2 = 'data2' + data3 = 'data3' + + with requests_mock.mock() as m1: + + r1 = m1.get(url1, text=data1) + + resp1a = requests.get(url1) + self.assertRaises(exceptions.NoMockAddress, requests.get, url2) + self.assertRaises(exceptions.NoMockAddress, requests.get, url3) + + self.assertEqual(data1, resp1a.text) + + # call count = 3 because there are 3 calls above, url 1-3 + self.assertEqual(3, m1.call_count) + self.assertEqual(1, r1.call_count) + + with requests_mock.mock() as m2: + + r2 = m2.get(url2, text=data2) + + self.assertRaises(exceptions.NoMockAddress, requests.get, url1) + resp2a = requests.get(url2) + self.assertRaises(exceptions.NoMockAddress, requests.get, url3) + + self.assertEqual(data2, resp2a.text) + + with requests_mock.mock() as m3: + + r3 = m3.get(url3, text=data3) + + self.assertRaises(exceptions.NoMockAddress, + requests.get, + url1) + self.assertRaises(exceptions.NoMockAddress, + requests.get, + url2) + resp3 = requests.get(url3) + + self.assertEqual(data3, resp3.text) + + self.assertEqual(3, m3.call_count) + self.assertEqual(1, r3.call_count) + + resp2b = requests.get(url2) + self.assertRaises(exceptions.NoMockAddress, requests.get, url1) + self.assertEqual(data2, resp2b.text) + self.assertRaises(exceptions.NoMockAddress, requests.get, url3) + + self.assertEqual(3, m1.call_count) + self.assertEqual(1, r1.call_count) + self.assertEqual(6, m2.call_count) + self.assertEqual(2, r2.call_count) + self.assertEqual(3, m3.call_count) + self.assertEqual(1, r3.call_count) + + resp1b = requests.get(url1) + self.assertEqual(data1, resp1b.text) + self.assertRaises(exceptions.NoMockAddress, requests.get, url2) + self.assertRaises(exceptions.NoMockAddress, requests.get, url3) + + self.assertEqual(6, m1.call_count) + self.assertEqual(2, r1.call_count) + + @requests_mock.mock() + def test_mocker_additional(self, m): + url = 'http://www.example.com' + good_text = 'success' + + def additional_cb(req): + return 'hello' in req.text + + m.post(url, additional_matcher=additional_cb, text=good_text) + + self.assertEqual(good_text, + requests.post(url, data='hello world').text) + self.assertRaises(exceptions.NoMockAddress, + requests.post, + url, + data='goodbye world') + + @requests_mock.mock() + def test_mocker_pickle(self, m): + url = 'http://www.example.com' + text = 'hello world' + m.get(url, text=text) + + orig_resp = requests.get(url) + self.assertEqual(text, orig_resp.text) + + d = pickle.dumps(orig_resp) + new_resp = pickle.loads(d) + + self.assertEqual(text, new_resp.text) + self.assertIsInstance(orig_resp.request.matcher, adapter._Matcher) + self.assertIsNone(new_resp.request.matcher) + + @requests_mock.mock() + def test_stream_zero_bytes(self, m): + content = b'blah' + + m.get("http://test", content=content) + res = requests.get("http://test", stream=True) + zero_val = res.raw.read(0) + self.assertEqual(b'', zero_val) + self.assertFalse(res.raw.closed) + + full_val = res.raw.read() + self.assertEqual(content, full_val) + + def test_with_json_encoder_on_mocker(self): + test_val = 'hello world' + + class MyJsonEncoder(json.JSONEncoder): + def encode(s, o): + return test_val + + with requests_mock.Mocker(json_encoder=MyJsonEncoder) as m: + m.get("http://test", json={"a": "b"}) + res = requests.get("http://test") + self.assertEqual(test_val, res.text) + + @requests_mock.mock() + def test_with_json_encoder_on_endpoint(self, m): + test_val = 'hello world' + + class MyJsonEncoder(json.JSONEncoder): + def encode(s, o): + return test_val + + m.get("http://test", json={"a": "b"}, json_encoder=MyJsonEncoder) + res = requests.get("http://test") + self.assertEqual(test_val, res.text) + + @requests_mock.mock() + def test_mismatch_content_length_streaming(self, m): + url = "https://test/package.tar.gz" + + def f(request, context): + context.headers["Content-Length"] = "300810" + return None + + m.head( + url=url, + status_code=200, + text=f, + ) + + requests.head(url) diff --git a/contrib/python/requests-mock/py2/tests/test_request.py b/contrib/python/requests-mock/py2/tests/test_request.py new file mode 100644 index 0000000000..69a71556e2 --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/test_request.py @@ -0,0 +1,139 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +import requests +import requests_mock +from . import base + + +class RequestTests(base.TestCase): + + def setUp(self): + super(RequestTests, self).setUp() + + self.mocker = requests_mock.Mocker() + self.addCleanup(self.mocker.stop) + self.mocker.start() + + def do_request(self, **kwargs): + method = kwargs.pop('method', 'GET') + url = kwargs.pop('url', 'http://test.example.com/path') + status_code = kwargs.pop('status_code', 200) + data = uuid.uuid4().hex + + m = self.mocker.register_uri(method, + url, + text=data, + status_code=status_code) + + resp = requests.request(method, url, **kwargs) + + self.assertEqual(status_code, resp.status_code) + self.assertEqual(data, resp.text) + + self.assertTrue(m.called_once) + return m.last_request + + def test_base_params(self): + req = self.do_request(method='GET', status_code=200) + + self.assertIs(None, req.allow_redirects) + self.assertIs(None, req.timeout) + self.assertIs(True, req.verify) + self.assertIs(None, req.cert) + self.assertIs(False, req.stream) + + # actually it's an OrderedDict, but equality works fine + # Skipping this check - it's problematic based on people's environments + # and in CI systems where there are proxies set up at the environment + # level. gh #127 + # self.assertEqual({}, req.proxies) + + def test_allow_redirects(self): + req = self.do_request(allow_redirects=False, status_code=300) + self.assertFalse(req.allow_redirects) + + def test_timeout(self): + timeout = 300 + req = self.do_request(timeout=timeout) + self.assertEqual(timeout, req.timeout) + + def test_verify_false(self): + verify = False + req = self.do_request(verify=verify) + self.assertIs(verify, req.verify) + + def test_verify_path(self): + verify = '/path/to/cacerts.pem' + req = self.do_request(verify=verify) + self.assertEqual(verify, req.verify) + + def test_stream(self): + req = self.do_request() + self.assertIs(False, req.stream) + req = self.do_request(stream=False) + self.assertIs(False, req.stream) + req = self.do_request(stream=True) + self.assertIs(True, req.stream) + + def test_certs(self): + cert = ('/path/to/cert.pem', 'path/to/key.pem') + req = self.do_request(cert=cert) + self.assertEqual(cert, req.cert) + self.assertTrue(req.verify) + + def test_proxies(self): + proxies = {'http': 'foo.bar:3128', + 'http://host.name': 'foo.bar:4012'} + + req = self.do_request(proxies=proxies) + + self.assertEqual(proxies, req.proxies) + self.assertIsNot(proxies, req.proxies) + + def test_hostname_port_http(self): + req = self.do_request(url='http://host.example.com:81/path') + + self.assertEqual('host.example.com:81', req.netloc) + self.assertEqual('host.example.com', req.hostname) + self.assertEqual(81, req.port) + + def test_hostname_port_https(self): + req = self.do_request(url='https://host.example.com:8080/path') + + self.assertEqual('host.example.com:8080', req.netloc) + self.assertEqual('host.example.com', req.hostname) + self.assertEqual(8080, req.port) + + def test_hostname_default_port_http(self): + req = self.do_request(url='http://host.example.com/path') + + self.assertEqual('host.example.com', req.netloc) + self.assertEqual('host.example.com', req.hostname) + self.assertEqual(80, req.port) + + def test_hostname_default_port_https(self): + req = self.do_request(url='https://host.example.com/path') + + self.assertEqual('host.example.com', req.netloc) + self.assertEqual('host.example.com', req.hostname) + self.assertEqual(443, req.port) + + def test_to_string(self): + req = self.do_request(url='https://host.example.com/path') + self.assertEqual('GET https://host.example.com/path', str(req)) + + def test_empty_query_string(self): + req = self.do_request(url='https://host.example.com/path?key') + self.assertEqual([''], req.qs['key']) diff --git a/contrib/python/requests-mock/py2/tests/test_response.py b/contrib/python/requests-mock/py2/tests/test_response.py new file mode 100644 index 0000000000..2dc11c8f7a --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/test_response.py @@ -0,0 +1,154 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import pickle +import six + +from requests_mock import exceptions +from requests_mock import request +from requests_mock import response +from . import base + + +class ResponseTests(base.TestCase): + + def setUp(self): + super(ResponseTests, self).setUp() + self.method = 'GET' + self.url = 'http://test.url/path' + self.request = request._RequestObjectProxy._create(self.method, + self.url, + {}) + + def create_response(self, **kwargs): + return response.create_response(self.request, **kwargs) + + def test_create_response_body_args(self): + self.assertRaises(RuntimeError, + self.create_response, + raw='abc', + body='abc') + + self.assertRaises(RuntimeError, + self.create_response, + text='abc', + json={'a': 1}) + + def test_content_type(self): + self.assertRaises(TypeError, self.create_response, text=55) + self.assertRaises(TypeError, self.create_response, text={'a': 1}) + + # this only works on python 2 because bytes is a string + if six.PY3: + self.assertRaises(TypeError, self.create_response, text=six.b('')) + + def test_text_type(self): + self.assertRaises(TypeError, self.create_response, content=six.u('t')) + self.assertRaises(TypeError, self.create_response, content={'a': 1}) + self.assertRaises(TypeError, self.create_response, content=six.u('')) + + def test_json_body(self): + data = {'a': 1} + resp = self.create_response(json=data) + + self.assertEqual('{"a": 1}', resp.text) + self.assertIsInstance(resp.text, six.string_types) + self.assertIsInstance(resp.content, six.binary_type) + self.assertEqual(data, resp.json()) + + def test_body_body(self): + value = 'data' + body = six.BytesIO(six.b(value)) + resp = self.create_response(body=body) + + self.assertEqual(value, resp.text) + self.assertIsInstance(resp.text, six.string_types) + self.assertIsInstance(resp.content, six.binary_type) + + def test_setting_connection(self): + conn = object() + resp = self.create_response(connection=conn) + self.assertIs(conn, resp.connection) + + def test_send_from_no_connection(self): + resp = self.create_response() + self.assertRaises(exceptions.InvalidRequest, + resp.connection.send, self.request) + + def test_cookies_from_header(self): + # domain must be same as request url to pass policy check + headers = {'Set-Cookie': 'fig=newton; Path=/test; domain=.test.url'} + resp = self.create_response(headers=headers) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual(['/test'], resp.cookies.list_paths()) + self.assertEqual(['.test.url'], resp.cookies.list_domains()) + + def test_cookies_from_dict(self): + # This is a syntax we get from requests. I'm not sure i like it. + resp = self.create_response(cookies={'fig': 'newton', + 'sugar': 'apple'}) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + + def test_cookies_with_jar(self): + jar = response.CookieJar() + jar.set('fig', 'newton', path='/foo', domain='.test.url') + jar.set('sugar', 'apple', path='/bar', domain='.test.url') + resp = self.create_response(cookies=jar) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + self.assertEqual({'/foo', '/bar'}, set(resp.cookies.list_paths())) + self.assertEqual(['.test.url'], resp.cookies.list_domains()) + + def test_response_pickle(self): + text = 'hello world' + jar = response.CookieJar() + jar.set('fig', 'newton', path='/foo', domain='.test.url') + orig_resp = self.create_response(cookies=jar, text=text) + + d = pickle.dumps(orig_resp) + new_resp = pickle.loads(d) + + self.assertEqual(text, new_resp.text) + self.assertEqual('newton', new_resp.cookies['fig']) + self.assertIsNone(new_resp.request.matcher) + + def test_response_encoding(self): + headers = {"content-type": "text/html; charset=ISO-8859-1"} + resp = self.create_response(headers=headers, + text="<html><body></body></html") + self.assertEqual('ISO-8859-1', resp.encoding) + + def test_default_reason(self): + resp = self.create_response() + self.assertEqual('OK', resp.reason) + + def test_custom_reason(self): + reason = 'Live long and prosper' + resp = self.create_response(status_code=201, reason=reason) + + self.assertEqual(201, resp.status_code) + self.assertEqual(reason, resp.reason) + + def test_some_other_response_reasons(self): + reasons = { + 301: 'Moved Permanently', + 410: 'Gone', + 503: 'Service Unavailable', + } + + for code, reason in six.iteritems(reasons): + self.assertEqual(reason, + self.create_response(status_code=code).reason) diff --git a/contrib/python/requests-mock/py2/tests/ya.make b/contrib/python/requests-mock/py2/tests/ya.make new file mode 100644 index 0000000000..574578a75c --- /dev/null +++ b/contrib/python/requests-mock/py2/tests/ya.make @@ -0,0 +1,26 @@ +PY2TEST() + +SUBSCRIBER(g:python-contrib) + +PEERDIR( + contrib/python/mock + contrib/python/requests-futures + contrib/python/requests-mock +) + +TEST_SRCS( + base.py + pytest/__init__.py + pytest/test_with_pytest.py + #test_adapter.py - need purl + test_custom_matchers.py + #test_fixture.py - need fixtures + test_matcher.py + test_mocker.py + test_request.py + test_response.py +) + +NO_LINT() + +END() diff --git a/contrib/python/requests-mock/py2/ya.make b/contrib/python/requests-mock/py2/ya.make new file mode 100644 index 0000000000..d84a85f668 --- /dev/null +++ b/contrib/python/requests-mock/py2/ya.make @@ -0,0 +1,56 @@ +# Generated by devtools/yamaker (pypi). + +PY2_LIBRARY() + +SUBSCRIBER(g:python-contrib) + +VERSION(1.11.0) + +LICENSE(Apache-2.0) + +PEERDIR( + contrib/python/requests + contrib/python/six +) + +NO_LINT() + +NO_CHECK_IMPORTS( + requests_mock.contrib._pytest_plugin + requests_mock.contrib.fixture +) + +PY_SRCS( + TOP_LEVEL + requests_mock/__init__.py + requests_mock/__init__.pyi + requests_mock/adapter.py + requests_mock/adapter.pyi + requests_mock/compat.py + requests_mock/contrib/__init__.py + requests_mock/contrib/_pytest_plugin.py + requests_mock/contrib/_pytest_plugin.pyi + requests_mock/contrib/fixture.py + requests_mock/exceptions.py + requests_mock/exceptions.pyi + requests_mock/mocker.py + requests_mock/mocker.pyi + requests_mock/request.py + requests_mock/request.pyi + requests_mock/response.py + requests_mock/response.pyi +) + +RESOURCE_FILES( + PREFIX contrib/python/requests-mock/py2/ + .dist-info/METADATA + .dist-info/entry_points.txt + .dist-info/top_level.txt + requests_mock/py.typed +) + +END() + +RECURSE_FOR_TESTS( + tests +) diff --git a/contrib/python/requests-mock/py3/patches/01-fix-tests.patch b/contrib/python/requests-mock/py3/patches/01-fix-tests.patch new file mode 100644 index 0000000000..dc5164a523 --- /dev/null +++ b/contrib/python/requests-mock/py3/patches/01-fix-tests.patch @@ -0,0 +1,13 @@ +--- contrib/python/requests-mock/py3/tests/base.py (index) ++++ contrib/python/requests-mock/py3/tests/base.py (working tree) +@@ -10,8 +10,8 @@ + # License for the specific language governing permissions and limitations + # under the License. + +-import testtools ++import unittest + + +-class TestCase(testtools.TestCase): ++class TestCase(unittest.TestCase): + pass diff --git a/contrib/python/requests-mock/py3/patches/02-support-python-3.12.patch b/contrib/python/requests-mock/py3/patches/02-support-python-3.12.patch new file mode 100644 index 0000000000..c876bc8243 --- /dev/null +++ b/contrib/python/requests-mock/py3/patches/02-support-python-3.12.patch @@ -0,0 +1,5 @@ +--- contrib/python/requests-mock/py3/tests/test_mocker.py (index) ++++ contrib/python/requests-mock/py3/tests/test_mocker.py (working tree) +@@ -222 +222 @@ class MockerTests(base.TestCase): +- self.assertEquals( ++ self.assertEqual( diff --git a/contrib/python/requests-mock/py3/tests/__init__.py b/contrib/python/requests-mock/py3/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/__init__.py diff --git a/contrib/python/requests-mock/py3/tests/base.py b/contrib/python/requests-mock/py3/tests/base.py new file mode 100644 index 0000000000..ecc872ecb7 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/base.py @@ -0,0 +1,17 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + + +class TestCase(unittest.TestCase): + pass diff --git a/contrib/python/requests-mock/py3/tests/pytest/__init__.py b/contrib/python/requests-mock/py3/tests/pytest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/pytest/__init__.py diff --git a/contrib/python/requests-mock/py3/tests/pytest/pytest.ini b/contrib/python/requests-mock/py3/tests/pytest/pytest.ini new file mode 100644 index 0000000000..58c87f21b7 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/pytest/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +requests_mock_case_sensitive=false diff --git a/contrib/python/requests-mock/py3/tests/pytest/test_with_pytest.py b/contrib/python/requests-mock/py3/tests/pytest/test_with_pytest.py new file mode 100644 index 0000000000..d74908bc38 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/pytest/test_with_pytest.py @@ -0,0 +1,119 @@ +try: + from http import HTTPStatus + HTTP_STATUS_FOUND = HTTPStatus.FOUND +except ImportError: + from httplib import FOUND as HTTP_STATUS_FOUND + +import pytest +import requests +import requests_mock + + +def test_simple(requests_mock): + requests_mock.get('https://httpbin.org/get', text='data') + assert 'data' == requests.get('https://httpbin.org/get').text + + +def test_redirect_and_nesting(): + url_inner = "inner-mock://example.test/" + url_middle = "middle-mock://example.test/" + url_outer = "outer-mock://example.test/" + url_base = "https://www.example.com/" + + text_middle = 'middle' + url_middle + text_outer = 'outer' + url_outer + text_base = 'outer' + url_base + + with requests_mock.Mocker() as outer_mock: + outer_mock.get(url_base, text=text_base) + outer_mock.get(url_outer, text=text_outer) + + with requests_mock.Mocker(real_http=True) as middle_mock: + middle_mock.get(url_middle, text=text_middle) + + with requests_mock.Mocker() as inner_mock: + inner_mock.post(url_inner, + status_code=HTTP_STATUS_FOUND, + headers={'location': url_base}) + inner_mock.get(url_base, real_http=True) + + assert text_base == requests.post(url_inner).text # nosec + + with pytest.raises(requests_mock.NoMockAddress): + requests.get(url_middle) + + with pytest.raises(requests_mock.NoMockAddress): + requests.get(url_outer) + + # back to middle mock + with pytest.raises(requests_mock.NoMockAddress): + requests.post(url_inner) + + assert text_middle == requests.get(url_middle).text # nosec + assert text_outer == requests.get(url_outer).text # nosec + + # back to outter mock + with pytest.raises(requests_mock.NoMockAddress): + requests.post(url_inner) + + with pytest.raises(requests_mock.NoMockAddress): + requests.get(url_middle) + + assert text_outer == requests.get(url_outer).text # nosec + + +def test_mixed_mocks(): + url = 'mock://example.test/' + with requests_mock.Mocker() as global_mock: + global_mock.get(url, text='global') + session = requests.Session() + text = session.get(url).text + assert text == 'global' # nosec + with requests_mock.Mocker(session=session) as session_mock: + session_mock.get(url, real_http=True) + text = session.get(url).text + assert text == 'global' # nosec + + +def test_threaded_sessions(): + """ + When using requests_futures.FuturesSession() with a ThreadPoolExecutor + there is a race condition where one threaded request removes the + monkeypatched get_adapter() method from the Session before another threaded + request is finished using it. + """ + from requests_futures.sessions import FuturesSession + + url1 = 'http://www.example.com/requests-mock-fake-url1' + url2 = 'http://www.example.com/requests-mock-fake-url2' + + with requests_mock.Mocker() as m: + # respond with 204 so we know its us + m.get(url1, status_code=204) + m.get(url2, status_code=204) + + # NOTE(phodge): just firing off two .get() requests right after each + # other was a pretty reliable way to reproduce the race condition on my + # intel Macbook Pro but YMMV. Guaranteeing the race condition to + # reappear might require replacing the Session.send() with a wrapper + # that delays kicking off the request for url1 until the request for + # url2 has restored the original session.get_adapter(), but replacing + # Session.send() could be difficult because the requests_mock.Mocker() + # context manager has *already* monkeypatched this method. + session = FuturesSession() + future1 = session.get(url1) + future2 = session.get(url2) + + # verify both requests were handled by the mock dispatcher + assert future1.result().status_code == 204 + assert future2.result().status_code == 204 + + +class TestClass(object): + + def configure(self, requests_mock): + requests_mock.get('https://httpbin.org/get', text='data') + + def test_one(self, requests_mock): + self.configure(requests_mock) + assert 'data' == requests.get('https://httpbin.org/get').text diff --git a/contrib/python/requests-mock/py3/tests/test_adapter.py b/contrib/python/requests-mock/py3/tests/test_adapter.py new file mode 100644 index 0000000000..4d6a0b4df2 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/test_adapter.py @@ -0,0 +1,724 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import http.client +import io +import json +import re +import urllib.parse + +import purl +import requests +from urllib3 import HTTPResponse + +import requests_mock +from . import base + + +class MyExc(Exception): + pass + + +class SessionAdapterTests(base.TestCase): + + PREFIX = "mock" + + def setUp(self): + super(SessionAdapterTests, self).setUp() + + self.adapter = requests_mock.Adapter() + self.session = requests.Session() + self.session.mount(self.PREFIX, self.adapter) + + self.url = '%s://example.com/test' % self.PREFIX + self.headers = {'header_a': 'A', 'header_b': 'B'} + + def assertHeaders(self, resp): + for k, v in self.headers.items(): + self.assertEqual(v, resp.headers[k]) + + def assertLastRequest(self, method='GET', body=None): + self.assertEqual(self.url, self.adapter.last_request.url) + self.assertEqual(method, self.adapter.last_request.method) + self.assertEqual(body, self.adapter.last_request.body) + + url_parts = urllib.parse.urlparse(self.url) + qs = urllib.parse.parse_qs(url_parts.query) + self.assertEqual(url_parts.scheme, self.adapter.last_request.scheme) + self.assertEqual(url_parts.netloc, self.adapter.last_request.netloc) + self.assertEqual(url_parts.path, self.adapter.last_request.path) + self.assertEqual(url_parts.query, self.adapter.last_request.query) + self.assertEqual(url_parts.query, self.adapter.last_request.query) + self.assertEqual(qs, self.adapter.last_request.qs) + + def test_content(self): + data = b'testdata' + + self.adapter.register_uri('GET', + self.url, + content=data, + headers=self.headers) + resp = self.session.get(self.url) + self.assertEqual(data, resp.content) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_content_callback(self): + status_code = 401 + data = b'testdata' + + def _content_cb(request, context): + context.status_code = status_code + context.headers.update(self.headers) + return data + + self.adapter.register_uri('GET', + self.url, + content=_content_cb) + resp = self.session.get(self.url) + self.assertEqual(status_code, resp.status_code) + self.assertEqual(data, resp.content) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_text(self): + data = u'testdata' + + self.adapter.register_uri('GET', + self.url, + text=data, + headers=self.headers) + resp = self.session.get(self.url) + self.assertEqual(data.encode('utf-8'), resp.content) + self.assertEqual(data, resp.text) + self.assertEqual('utf-8', resp.encoding) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_text_callback(self): + status_code = 401 + data = u'testdata' + + def _text_cb(request, context): + context.status_code = status_code + context.headers.update(self.headers) + return data + + self.adapter.register_uri('GET', self.url, text=_text_cb) + resp = self.session.get(self.url) + self.assertEqual(status_code, resp.status_code) + self.assertEqual(data, resp.text) + self.assertEqual(data.encode('utf-8'), resp.content) + self.assertEqual('utf-8', resp.encoding) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_raw_callback(self): + status_code = 401 + data = 'testdata' + + def _raw_cb(request, context): + return HTTPResponse( + status=status_code, + headers=self.headers, + body=io.BytesIO(data.encode('utf-8')), + preload_content=False, + reason=http.client.responses.get(status_code), + ) + + self.adapter.register_uri('GET', self.url, raw=_raw_cb) + resp = self.session.get(self.url) + self.assertEqual(status_code, resp.status_code) + self.assertEqual(data, resp.text) + self.assertEqual(data.encode('utf-8'), resp.content) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_json(self): + json_data = {'hello': 'world'} + self.adapter.register_uri('GET', + self.url, + json=json_data, + headers=self.headers) + resp = self.session.get(self.url) + self.assertEqual(b'{"hello": "world"}', resp.content) + self.assertEqual(u'{"hello": "world"}', resp.text) + self.assertEqual(json_data, resp.json()) + self.assertEqual('utf-8', resp.encoding) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_json_callback(self): + status_code = 401 + json_data = {'hello': 'world'} + data = u'{"hello": "world"}' + + def _json_cb(request, context): + context.status_code = status_code + context.headers.update(self.headers) + return json_data + + self.adapter.register_uri('GET', self.url, json=_json_cb) + resp = self.session.get(self.url) + self.assertEqual(status_code, resp.status_code) + self.assertEqual(json_data, resp.json()) + self.assertEqual(data, resp.text) + self.assertEqual(data.encode('utf-8'), resp.content) + self.assertEqual('utf-8', resp.encoding) + self.assertHeaders(resp) + self.assertLastRequest() + + def test_no_body(self): + self.adapter.register_uri('GET', self.url) + resp = self.session.get(self.url) + self.assertEqual(b'', resp.content) + self.assertEqual(200, resp.status_code) + + def test_multiple_body_elements(self): + self.assertRaises(RuntimeError, + self.adapter.register_uri, + self.url, + 'GET', + content=b'b', + text=u'u') + + def test_multiple_responses(self): + inp = [{'status_code': 400, 'text': 'abcd'}, + {'status_code': 300, 'text': 'defg'}, + {'status_code': 200, 'text': 'hijk'}] + + self.adapter.register_uri('GET', self.url, inp) + out = [self.session.get(self.url) for i in range(0, len(inp))] + + for i, o in zip(inp, out): + for k, v in i.items(): + self.assertEqual(v, getattr(o, k)) + + last = self.session.get(self.url) + for k, v in inp[-1].items(): + self.assertEqual(v, getattr(last, k)) + + def test_callback_optional_status(self): + headers = {'a': 'b'} + + def _test_cb(request, context): + context.headers.update(headers) + return '' + + self.adapter.register_uri('GET', + self.url, + text=_test_cb, + status_code=300) + resp = self.session.get(self.url) + self.assertEqual(300, resp.status_code) + + for k, v in headers.items(): + self.assertEqual(v, resp.headers[k]) + + def test_callback_optional_headers(self): + headers = {'a': 'b'} + + def _test_cb(request, context): + context.status_code = 300 + return '' + + self.adapter.register_uri('GET', + self.url, + text=_test_cb, + headers=headers) + + resp = self.session.get(self.url) + self.assertEqual(300, resp.status_code) + + for k, v in headers.items(): + self.assertEqual(v, resp.headers[k]) + + def test_latest_register_overrides(self): + self.adapter.register_uri('GET', self.url, text='abc') + self.adapter.register_uri('GET', self.url, text='def') + + resp = self.session.get(self.url) + self.assertEqual('def', resp.text) + + def test_no_last_request(self): + self.assertIsNone(self.adapter.last_request) + self.assertEqual(0, len(self.adapter.request_history)) + + def test_dont_pass_list_and_kwargs(self): + self.assertRaises(RuntimeError, + self.adapter.register_uri, + 'GET', + self.url, + [{'text': 'a'}], + headers={'a': 'b'}) + + def test_empty_string_return(self): + # '' evaluates as False, so make sure an empty string is not ignored. + self.adapter.register_uri('GET', self.url, text='') + resp = self.session.get(self.url) + self.assertEqual('', resp.text) + + def test_dont_pass_multiple_bodies(self): + self.assertRaises(RuntimeError, + self.adapter.register_uri, + 'GET', + self.url, + json={'abc': 'def'}, + text='ghi') + + def test_dont_pass_unexpected_kwargs(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + unknown='argument') + + def test_dont_pass_unicode_as_content(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + content=u'unicode') + + def test_dont_pass_empty_string_as_content(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + content=u'') + + def test_dont_pass_bytes_as_text(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + text=b'bytes') + + def test_dont_pass_empty_string_as_text(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + text=b'') + + def test_dont_pass_non_str_as_content(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + content=5) + + def test_dont_pass_non_str_as_text(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + text=5) + + def test_with_any_method(self): + self.adapter.register_uri(requests_mock.ANY, self.url, text='resp') + + for m in ('GET', 'HEAD', 'POST', 'UNKNOWN'): + resp = self.session.request(m, self.url) + self.assertEqual('resp', resp.text) + + def test_with_any_url(self): + self.adapter.register_uri('GET', requests_mock.ANY, text='resp') + + for u in ('mock://a', 'mock://b', 'mock://c'): + resp = self.session.get(u) + self.assertEqual('resp', resp.text) + + def test_with_regexp(self): + self.adapter.register_uri('GET', re.compile('tester.com'), text='resp') + + for u in ('mock://www.tester.com/a', 'mock://abc.tester.com'): + resp = self.session.get(u) + self.assertEqual('resp', resp.text) + + def test_with_purl(self): + self.adapter.register_uri('GET', + purl.URL('mock://www.tester.com/a'), + text='resp') + + resp = self.session.get('mock://www.tester.com/a') + self.assertEqual('resp', resp.text) + + def test_requests_in_history_on_no_match(self): + self.assertRaises(requests_mock.NoMockAddress, + self.session.get, + self.url) + + self.assertEqual(self.url, self.adapter.last_request.url) + + def test_requests_in_history_on_exception(self): + + def _test_cb(request, ctx): + raise MyExc() + + self.adapter.register_uri('GET', self.url, text=_test_cb) + + self.assertRaises(MyExc, + self.session.get, + self.url) + + self.assertEqual(self.url, self.adapter.last_request.url) + + def test_not_called_and_called_count(self): + m = self.adapter.register_uri('GET', self.url, text='resp') + self.assertEqual(0, m.call_count) + self.assertFalse(m.called) + self.assertFalse(m.called_once) + + self.assertEqual(0, self.adapter.call_count) + self.assertFalse(self.adapter.called) + self.assertFalse(m.called_once) + + def test_called_and_called_count(self): + m = self.adapter.register_uri('GET', self.url, text='resp') + + resps = [self.session.get(self.url) for i in range(0, 3)] + + for r in resps: + self.assertEqual('resp', r.text) + self.assertEqual(200, r.status_code) + + self.assertEqual(len(resps), m.call_count) + self.assertTrue(m.called) + self.assertFalse(m.called_once) + + self.assertEqual(len(resps), self.adapter.call_count) + self.assertTrue(self.adapter.called) + self.assertFalse(m.called_once) + + def test_reset_reverts_call_count(self): + # Create matchers and add calls to history + call_count = 3 + matcher_count = 3 + for i in range(matcher_count): + url = self.url + str(i) + self.adapter.register_uri('GET', url, text='resp') + for _ in range(call_count): + self.session.get(url) + + # Verify call counts on adapter and matchers + self.assertEqual(self.adapter.call_count, matcher_count * call_count) + for matcher in self.adapter._matchers: + self.assertEqual(matcher.call_count, call_count) + + self.adapter.reset() + + # Verify call counts are 0 after reset + self.assertEqual(self.adapter.call_count, 0) + for matcher in self.adapter._matchers: + self.assertEqual(matcher.call_count, 0) + + def test_adapter_picks_correct_adapter(self): + good = '%s://test3.url/' % self.PREFIX + self.adapter.register_uri('GET', + '%s://test1.url' % self.PREFIX, + text='bad') + self.adapter.register_uri('GET', + '%s://test2.url' % self.PREFIX, + text='bad') + self.adapter.register_uri('GET', good, text='good') + self.adapter.register_uri('GET', + '%s://test4.url' % self.PREFIX, + text='bad') + + resp = self.session.get(good) + + self.assertEqual('good', resp.text) + + def test_adapter_is_connection(self): + url = '%s://test.url' % self.PREFIX + text = 'text' + self.adapter.register_uri('GET', url, text=text) + resp = self.session.get(url) + + self.assertEqual(text, resp.text) + self.assertIs(self.adapter, resp.connection) + + def test_send_to_connection(self): + url1 = '%s://test1.url/' % self.PREFIX + url2 = '%s://test2.url/' % self.PREFIX + + text1 = 'text1' + text2 = 'text2' + + self.adapter.register_uri('GET', url1, text=text1) + self.adapter.register_uri('GET', url2, text=text2) + + req = requests.Request(method='GET', url=url2).prepare() + + resp1 = self.session.get(url1) + self.assertEqual(text1, resp1.text) + + resp2 = resp1.connection.send(req) + self.assertEqual(text2, resp2.text) + + def test_request_json_with_str_data(self): + dict_req = {'hello': 'world'} + dict_resp = {'goodbye': 'world'} + + m = self.adapter.register_uri('POST', self.url, json=dict_resp) + + data = json.dumps(dict_req) + resp = self.session.post(self.url, data=data) + + self.assertIs(data, m.last_request.body) + self.assertEqual(dict_resp, resp.json()) + self.assertEqual(dict_req, m.last_request.json()) + + def test_request_json_with_bytes_data(self): + dict_req = {'hello': 'world'} + dict_resp = {'goodbye': 'world'} + + m = self.adapter.register_uri('POST', self.url, json=dict_resp) + + data = json.dumps(dict_req).encode('utf-8') + resp = self.session.post(self.url, data=data) + + self.assertIs(data, m.last_request.body) + self.assertEqual(dict_resp, resp.json()) + self.assertEqual(dict_req, m.last_request.json()) + + def test_request_json_with_cb(self): + dict_req = {'hello': 'world'} + dict_resp = {'goodbye': 'world'} + data = json.dumps(dict_req) + + def _cb(req, context): + self.assertEqual(dict_req, req.json()) + return dict_resp + + m = self.adapter.register_uri('POST', self.url, json=_cb) + resp = self.session.post(self.url, data=data) + + self.assertEqual(1, m.call_count) + self.assertTrue(m.called_once) + self.assertEqual(dict_resp, resp.json()) + + def test_raises_exception(self): + self.adapter.register_uri('GET', self.url, exc=MyExc) + + self.assertRaises(MyExc, + self.session.get, + self.url) + + self.assertTrue(self.adapter.called_once) + self.assertEqual(self.url, self.adapter.last_request.url) + + def test_raises_exception_with_body_args_fails(self): + self.assertRaises(TypeError, + self.adapter.register_uri, + 'GET', + self.url, + exc=MyExc, + text='fail') + + def test_sets_request_matcher_in_history(self): + url1 = '%s://test1.url/' % self.PREFIX + url2 = '%s://test2.url/' % self.PREFIX + + text1 = 'text1' + text2 = 'text2' + + m1 = self.adapter.register_uri('GET', url1, text=text1) + m2 = self.adapter.register_uri('GET', url2, text=text2) + + resp1 = self.session.get(url1) + resp2 = self.session.get(url2) + + self.assertEqual(text1, resp1.text) + self.assertEqual(text2, resp2.text) + + self.assertEqual(2, self.adapter.call_count) + self.assertFalse(self.adapter.called_once) + + self.assertEqual(url1, self.adapter.request_history[0].url) + self.assertEqual(url2, self.adapter.request_history[1].url) + + self.assertIs(m1, self.adapter.request_history[0].matcher) + self.assertIs(m2, self.adapter.request_history[1].matcher) + + def test_sets_request_matcher_on_exception(self): + m = self.adapter.register_uri('GET', self.url, exc=MyExc) + + self.assertRaises(MyExc, + self.session.get, + self.url) + + self.assertEqual(self.url, self.adapter.last_request.url) + self.assertIs(m, self.adapter.last_request.matcher) + + def test_cookies_from_header(self): + headers = {'Set-Cookie': 'fig=newton; Path=/test; domain=.example.com'} + self.adapter.register_uri('GET', + self.url, + text='text', + headers=headers) + + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual(['/test'], resp.cookies.list_paths()) + self.assertEqual(['.example.com'], resp.cookies.list_domains()) + + def test_cookies_from_dict(self): + # This is a syntax we get from requests. I'm not sure i like it. + self.adapter.register_uri('GET', + self.url, + text='text', + cookies={'fig': 'newton', 'sugar': 'apple'}) + + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + + def test_cookies_with_jar(self): + jar = requests_mock.CookieJar() + jar.set('fig', 'newton', path='/foo', domain='.example.com') + jar.set('sugar', 'apple', path='/bar', domain='.example.com') + + self.adapter.register_uri('GET', self.url, text='text', cookies=jar) + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + self.assertEqual({'/foo', '/bar'}, set(resp.cookies.list_paths())) + self.assertEqual(['.example.com'], resp.cookies.list_domains()) + + def test_cookies_header_with_cb(self): + + def _cb(request, context): + val = 'fig=newton; Path=/test; domain=.example.com' + context.headers['Set-Cookie'] = val + return 'text' + + self.adapter.register_uri('GET', self.url, text=_cb) + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual(['/test'], resp.cookies.list_paths()) + self.assertEqual(['.example.com'], resp.cookies.list_domains()) + + def test_cookies_from_dict_with_cb(self): + def _cb(request, context): + # converted into a jar by now + context.cookies.set('sugar', 'apple', path='/test') + return 'text' + + self.adapter.register_uri('GET', + self.url, + text=_cb, + cookies={'fig': 'newton'}) + + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + self.assertEqual(['/', '/test'], resp.cookies.list_paths()) + + def test_cookies_with_jar_cb(self): + def _cb(request, context): + context.cookies.set('sugar', + 'apple', + path='/bar', + domain='.example.com') + return 'text' + + jar = requests_mock.CookieJar() + jar.set('fig', 'newton', path='/foo', domain='.example.com') + + self.adapter.register_uri('GET', self.url, text=_cb, cookies=jar) + resp = self.session.get(self.url) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + self.assertEqual({'/foo', '/bar'}, set(resp.cookies.list_paths())) + self.assertEqual(['.example.com'], resp.cookies.list_domains()) + + def test_reading_closed_fp(self): + self.adapter.register_uri('GET', self.url, text='abc') + resp = self.session.get(self.url) + + # raw will have been closed during the request reading + self.assertTrue(resp.raw.closed) + + data = resp.raw.read() + + self.assertIsInstance(data, bytes) + self.assertEqual(0, len(data)) + + def test_case_sensitive_headers(self): + data = 'testdata' + headers = {'aBcDe': 'FgHiJ'} + + self.adapter.register_uri('GET', self.url, text=data) + resp = self.session.get(self.url, headers=headers) + + self.assertEqual('GET', self.adapter.last_request.method) + self.assertEqual(200, resp.status_code) + self.assertEqual(data, resp.text) + + for k, v in headers.items(): + self.assertEqual(v, self.adapter.last_request.headers[k]) + + def test_case_sensitive_history(self): + self.adapter._case_sensitive = True + + data = 'testdata' + netloc = 'examPlE.CoM' + path = '/TesTER' + query = 'aBC=deF' + + mock_url = '%s://%s%s' % (self.PREFIX, netloc.lower(), path) + request_url = '%s://%s%s?%s' % (self.PREFIX, netloc, path, query) + + # test that the netloc is ignored when actually making the request + self.adapter.register_uri('GET', mock_url, text=data) + resp = self.session.get(request_url) + + self.assertEqual('GET', self.adapter.last_request.method) + self.assertEqual(200, resp.status_code) + self.assertEqual(data, resp.text) + + # but even still the mixed case parameters come out in history + self.assertEqual(netloc, self.adapter.last_request.netloc) + self.assertEqual(path, self.adapter.last_request.path) + self.assertEqual(query, self.adapter.last_request.query) + + def test_stream_none(self): + text = 'hello world' + + self.adapter.register_uri('GET', + self.url, + text=text, + headers=self.headers) + + resp = self.session.get(self.url, stream=True) + resps = [c for c in resp.iter_content(None, decode_unicode=True)] + self.assertEqual([text], resps) + + def test_stream_size(self): + text = 'hello world' + + self.adapter.register_uri('GET', + self.url, + text=text, + headers=self.headers) + + resp = self.session.get(self.url, stream=True) + resps = [c for c in resp.iter_content(3, decode_unicode=True)] + self.assertEqual(['hel', 'lo ', 'wor', 'ld'], resps) diff --git a/contrib/python/requests-mock/py3/tests/test_custom_matchers.py b/contrib/python/requests-mock/py3/tests/test_custom_matchers.py new file mode 100644 index 0000000000..73e23822f5 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/test_custom_matchers.py @@ -0,0 +1,74 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import requests + +import requests_mock +from . import base + + +class FailMatcher(object): + + def __init___(self): + self.called = False + + def __call__(self, request): + self.called = True + return None + + +def match_all(request): + return requests_mock.create_response(request, content=b'data') + + +class CustomMatchersTests(base.TestCase): + + def assertMatchAll(self, resp): + self.assertEqual(200, resp.status_code) + self.assertEqual(resp.text, u'data') + + @requests_mock.Mocker() + def test_custom_matcher(self, mocker): + mocker.add_matcher(match_all) + + resp = requests.get('http://any/thing') + self.assertMatchAll(resp) + + @requests_mock.Mocker() + def test_failing_matcher(self, mocker): + failer = FailMatcher() + + mocker.add_matcher(match_all) + mocker.add_matcher(failer) + + resp = requests.get('http://any/thing') + + self.assertMatchAll(resp) + self.assertTrue(failer.called) + + @requests_mock.Mocker() + def test_some_pass(self, mocker): + + def matcher_a(request): + if 'a' in request.url: + return match_all(request) + + return None + + mocker.add_matcher(matcher_a) + + resp = requests.get('http://any/thing') + self.assertMatchAll(resp) + + self.assertRaises(requests_mock.NoMockAddress, + requests.get, + 'http://other/thing') diff --git a/contrib/python/requests-mock/py3/tests/test_fixture.py b/contrib/python/requests-mock/py3/tests/test_fixture.py new file mode 100644 index 0000000000..63cbb99141 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/test_fixture.py @@ -0,0 +1,39 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import requests +import requests_mock +from requests_mock.contrib import fixture +from . import base + + +class MockingTests(base.TestCase): + + def setUp(self): + super(MockingTests, self).setUp() + self.mocker = self.useFixture(fixture.Fixture()) + + def test_failure(self): + self.assertRaises(requests_mock.NoMockAddress, + requests.get, + 'http://www.google.com') + + def test_basic(self): + test_url = 'http://www.google.com/' + self.mocker.register_uri('GET', test_url, text='response') + + resp = requests.get(test_url) + self.assertEqual('response', resp.text) + self.assertEqual(test_url, self.mocker.last_request.url) + + def test_fixture_has_normal_attr_error(self): + self.assertRaises(AttributeError, lambda: self.mocker.unknown) diff --git a/contrib/python/requests-mock/py3/tests/test_matcher.py b/contrib/python/requests-mock/py3/tests/test_matcher.py new file mode 100644 index 0000000000..a30e195966 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/test_matcher.py @@ -0,0 +1,324 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import re + +from requests_mock import adapter +from . import base +from requests_mock.response import _MatcherResponse + +ANY = adapter.ANY + + +class TestMatcher(base.TestCase): + + def match(self, + target, + url, + matcher_method='GET', + request_method='GET', + complete_qs=False, + headers=None, + request_data=None, + request_headers={}, + additional_matcher=None, + real_http=False, + case_sensitive=False): + matcher = adapter._Matcher(matcher_method, + target, + [], + complete_qs=complete_qs, + additional_matcher=additional_matcher, + request_headers=request_headers, + real_http=real_http, + case_sensitive=case_sensitive) + request = adapter._RequestObjectProxy._create(request_method, + url, + headers, + data=request_data) + return matcher._match(request) + + def assertMatch(self, + target=ANY, + url='http://example.com/requests-mock', + matcher_method='GET', + request_method='GET', + **kwargs): + self.assertEqual(True, + self.match(target, + url, + matcher_method=matcher_method, + request_method=request_method, + **kwargs), + 'Matcher %s %s failed to match %s %s' % + (matcher_method, target, request_method, url)) + + def assertMatchBoth(self, + target=ANY, + url='http://example.com/requests-mock', + matcher_method='GET', + request_method='GET', + **kwargs): + self.assertMatch(target, + url, + matcher_method=matcher_method, + request_method=request_method, + **kwargs) + self.assertMatch(url, + target, + matcher_method=request_method, + request_method=matcher_method, + **kwargs) + + def assertNoMatch(self, + target=ANY, + url='http://example.com/requests-mock', + matcher_method='GET', + request_method='GET', + **kwargs): + self.assertEqual(False, + self.match(target, + url, + matcher_method=matcher_method, + request_method=request_method, + **kwargs), + 'Matcher %s %s unexpectedly matched %s %s' % + (matcher_method, target, request_method, url)) + + def assertNoMatchBoth(self, + target=ANY, + url='http://example.com/requests-mock', + matcher_method='GET', + request_method='GET', + **kwargs): + self.assertNoMatch(target, + url, + matcher_method=matcher_method, + request_method=request_method, + **kwargs) + self.assertNoMatch(url, + target, + matcher_method=request_method, + request_method=matcher_method, + **kwargs) + + def assertMatchMethodBoth(self, matcher_method, request_method, **kwargs): + url = 'http://www.test.com' + + self.assertMatchBoth(url, + url, + request_method=request_method, + matcher_method=matcher_method, + **kwargs) + + def assertNoMatchMethodBoth(self, + matcher_method, + request_method, + **kwargs): + url = 'http://www.test.com' + + self.assertNoMatchBoth(url, + url, + request_method=request_method, + matcher_method=matcher_method, + **kwargs) + + def test_url_matching(self): + self.assertMatchBoth('http://www.test.com', + 'http://www.test.com') + self.assertMatchBoth('http://www.test.com', + 'http://www.test.com/') + self.assertMatchBoth('http://www.test.com/abc', + 'http://www.test.com/abc') + self.assertMatchBoth('http://www.test.com:5000/abc', + 'http://www.test.com:5000/abc') + self.assertNoMatchBoth('https://www.test.com', + 'http://www.test.com') + self.assertNoMatchBoth('http://www.test.com/abc', + 'http://www.test.com') + self.assertNoMatchBoth('http://test.com', + 'http://www.test.com') + self.assertNoMatchBoth('http://test.com', + 'http://www.test.com') + self.assertNoMatchBoth('http://test.com/abc', + 'http://www.test.com/abc/') + self.assertNoMatchBoth('http://test.com/abc/', + 'http://www.test.com/abc') + self.assertNoMatchBoth('http://test.com:5000/abc/', + 'http://www.test.com/abc') + self.assertNoMatchBoth('http://test.com/abc/', + 'http://www.test.com:5000/abc') + + def test_quotation(self): + self.assertMatchBoth('http://www.test.com/a string%url', + 'http://www.test.com/a string%url') + self.assertMatchBoth('http://www.test.com/ABC 123', + 'http://www.test.com/ABC%20123') + self.assertMatchBoth('http://www.test.com/user@example.com', + 'http://www.test.com/user@example.com') + + def test_subset_match(self): + self.assertMatch('/path', 'http://www.test.com/path') + self.assertMatch('/path', 'http://www.test.com/path') + self.assertMatch('//www.test.com/path', 'http://www.test.com/path') + self.assertMatch('//www.test.com/path', 'https://www.test.com/path') + + def test_query_string(self): + self.assertMatch('/path?a=1&b=2', + 'http://www.test.com/path?a=1&b=2') + self.assertMatch('/path?a=1', + 'http://www.test.com/path?a=1&b=2', + complete_qs=False) + self.assertNoMatch('/path?a=1', + 'http://www.test.com/path?a=1&b=2', + complete_qs=True) + self.assertNoMatch('/path?a=1&b=2', + 'http://www.test.com/path?a=1') + + def test_query_empty_string(self): + self.assertMatch('/path?a', + 'http://www.test.com/path?a') + self.assertMatch('/path?bob&paul', + 'http://www.test.com/path?paul&bob') + self.assertNoMatch('/path?bob', + 'http://www.test.com/path?paul') + self.assertNoMatch('/path?pual&bob', + 'http://www.test.com/path?bob') + + def test_method_match(self): + self.assertNoMatchMethodBoth('GET', 'POST') + self.assertMatchMethodBoth('GET', 'get') + self.assertMatchMethodBoth('GeT', 'geT') + + def test_match_ANY_url(self): + self.assertMatch(ANY, 'http://anything') + self.assertMatch(ANY, 'http://somethingelse') + self.assertNoMatch(ANY, 'http://somethingelse', request_method='POST') + + def test_match_ANY_method(self): + for m in ('GET', 'POST', 'HEAD', 'OPTION'): + self.assertMatch('http://www.test.com', + 'http://www.test.com', + matcher_method=ANY, + request_method=m) + + self.assertNoMatch('http://www.test.com', + 'http://another', + matcher_method=ANY) + + def test_match_with_regex(self): + r1 = re.compile('test.com/a') + r2 = re.compile('/b/c') + + self.assertMatch(r1, 'http://mock.test.com/a/b') + self.assertMatch(r1, 'http://test.com/a/') + self.assertMatch(r1, 'mock://test.com/a/b') + self.assertNoMatch(r1, 'mock://test.com/') + + self.assertMatch(r2, 'http://anything/a/b/c/d') + self.assertMatch(r2, 'mock://anything/a/b/c/d') + + def test_match_with_headers(self): + self.assertMatch('/path', + 'http://www.test.com/path', + headers={'A': 'abc', 'b': 'def'}, + request_headers={'a': 'abc'}) + + self.assertMatch('/path', + 'http://www.test.com/path', + headers={'A': 'abc', 'b': 'def'}) + + self.assertNoMatch('/path', + 'http://www.test.com/path', + headers={'A': 'abc', 'b': 'def'}, + request_headers={'b': 'abc'}) + + self.assertNoMatch('/path', + 'http://www.test.com/path', + headers={'A': 'abc', 'b': 'def'}, + request_headers={'c': 'ghi'}) + + # headers should be key insensitive and value sensitive, we have no + # choice here because they go into an insensitive dict. + self.assertMatch('/path', + 'http://www.test.com/path', + headers={'aBc': 'abc', 'DEF': 'def'}, + request_headers={'abC': 'abc'}) + + self.assertNoMatch('/path', + 'http://www.test.com/path', + headers={'abc': 'aBC', 'DEF': 'def'}, + request_headers={'abc': 'Abc'}) + + def test_case_sensitive_ignored_for_netloc_and_protocol(self): + for case_sensitive in (True, False): + self.assertMatch('http://AbC.CoM', + 'http://aBc.CoM', + case_sensitive=case_sensitive) + + self.assertMatch('htTP://abc.com', + 'hTTp://abc.com', + case_sensitive=case_sensitive) + + self.assertMatch('htTP://aBC.cOm', + 'hTTp://AbC.Com', + case_sensitive=case_sensitive) + + def assertSensitiveMatch(self, target, url, **kwargs): + self.assertMatch(target, url, case_sensitive=False, **kwargs) + self.assertNoMatch(target, url, case_sensitive=True, **kwargs) + + def test_case_sensitive_paths(self): + self.assertSensitiveMatch('http://abc.com/pAtH', 'http://abc.com/path') + self.assertSensitiveMatch('/pAtH', 'http://abc.com/path') + + def test_case_sensitive_query(self): + self.assertSensitiveMatch('http://abc.com/path?abCD=efGH', + 'http://abc.com/path?abCd=eFGH') + + self.assertSensitiveMatch('http://abc.com/path?abcd=efGH', + 'http://abc.com/path?abcd=eFGH') + + def test_additional_matcher(self): + + def test_match_body(request): + return 'hello' in request.text + + self.assertMatch(request_method='POST', + matcher_method='POST', + request_data='hello world', + additional_matcher=test_match_body) + + self.assertNoMatch(request_method='POST', + matcher_method='POST', + request_data='goodbye world', + additional_matcher=test_match_body) + + def test_reset_reverts_count(self): + url = 'mock://test/site/' + matcher = adapter._Matcher('GET', + url, + [_MatcherResponse()], + complete_qs=False, + additional_matcher=None, + request_headers={}, + real_http=False, + case_sensitive=False) + request = adapter._RequestObjectProxy._create('GET', url) + + call_count = 3 + for _ in range(call_count): + matcher(request) + + self.assertEqual(matcher.call_count, call_count) + matcher.reset() + self.assertEqual(matcher.call_count, 0) diff --git a/contrib/python/requests-mock/py3/tests/test_mocker.py b/contrib/python/requests-mock/py3/tests/test_mocker.py new file mode 100644 index 0000000000..c2e88813ed --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/test_mocker.py @@ -0,0 +1,646 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import pickle + +try: + from unittest import mock +except ImportError: + import mock +import requests + +import requests_mock +from requests_mock import adapter +from requests_mock import exceptions +from requests_mock import response +from . import base + +original_send = requests.Session.send + + +class MockerTests(base.TestCase): + + def assertMockStarted(self): + self.assertNotEqual(original_send, requests.Session.send) + + def assertMockStopped(self): + self.assertEqual(original_send, requests.Session.send) + + def _do_test(self, m): + self.assertMockStarted() + matcher = m.register_uri('GET', 'http://www.test.com', text='resp') + resp = requests.get('http://www.test.com') + self.assertEqual('resp', resp.text) + return matcher + + def test_multiple_starts(self): + mocker = requests_mock.Mocker() + self.assertMockStopped() + mocker.start() + self.assertMockStarted() + self.assertRaises(RuntimeError, mocker.start) + mocker.stop() + self.assertMockStopped() + mocker.stop() + + def test_with_session(self): + url = 'http://test.url/path' + url_inner = 'http://test.url/inner' + url_outer = 'http://test.url/outer' + with requests_mock.Mocker() as global_mock: + global_mock.get(url_outer, text='global') + + session_a = requests.Session() + session_b = requests.Session() + + session_a_original_send = session_a.send + session_b_original_send = session_b.send + self.assertNotEqual(session_a_original_send, + session_b_original_send) + + mocker_a = requests_mock.Mocker(session=session_a) + mocker_b = requests_mock.Mocker(session=session_b) + + mocker_a.start() + mocker_b.start() + + mocker_a.register_uri('GET', url, text='resp_a') + mocker_a.register_uri('GET', url_outer, real_http=True) + mocker_b.register_uri('GET', url, text='resp_b') + + with requests_mock.Mocker(session=session_b) as mocker_b_inner: + mocker_b_inner.register_uri('GET', url, real_http=True) + mocker_b_inner.register_uri('GET', + url_inner, + text='resp_b_inner') + + self.assertEqual('resp_a', session_a.get(url).text) + self.assertEqual('resp_b', session_b.get(url).text) + self.assertRaises(exceptions.NoMockAddress, + session_a.get, + url_inner) + self.assertEqual('resp_b_inner', session_b.get(url_inner).text) + + self.assertEqual('resp_a', session_a.get(url).text) + self.assertEqual('resp_b', session_b.get(url).text) + self.assertRaises(exceptions.NoMockAddress, + session_a.get, + url_inner) + self.assertRaises(exceptions.NoMockAddress, + session_b.get, + url_inner) + self.assertEqual('global', session_a.get(url_outer).text) + self.assertRaises(exceptions.NoMockAddress, + session_b.get, + url_outer) + + self.assertNotEqual(session_a.send, session_a_original_send) + self.assertNotEqual(session_b.send, session_b_original_send) + self.assertNotEqual(session_a.send, session_b.send) + + mocker_a.stop() + mocker_b.stop() + + self.assertEqual(session_a.send, session_a_original_send) + self.assertEqual(session_b.send, session_b_original_send) + + self.assertEqual(requests.Session.send, original_send) + + def test_with_context_manager(self): + self.assertMockStopped() + with requests_mock.Mocker() as m: + self._do_test(m) + self.assertMockStopped() + + @mock.patch('requests.adapters.HTTPAdapter.send') + @requests_mock.mock(real_http=True) + def test_real_http(self, real_send, mocker): + url = 'http://www.google.com/' + test_text = 'real http data' + test_bytes = test_text.encode('utf-8') + + # using create_response is a bit bootstrappy here but so long as it's + # coming from HTTPAdapter.send it's ok + req = requests.Request(method='GET', url=url) + real_send.return_value = response.create_response(req.prepare(), + status_code=200, + content=test_bytes) + resp = requests.get(url) + + self.assertEqual(1, real_send.call_count) + self.assertEqual(url, real_send.call_args[0][0].url) + + self.assertEqual(test_text, resp.text) + self.assertEqual(test_bytes, resp.content) + + @mock.patch('requests.adapters.HTTPAdapter.send') + def test_real_http_changes(self, real_send): + url = 'http://www.google.com/' + test_text = 'real http data' + test_bytes = test_text.encode('utf-8') + + req = requests.Request(method='GET', url=url) + real_send.return_value = response.create_response(req.prepare(), + status_code=200, + content=test_bytes) + + with requests_mock.Mocker() as m: + # real_http defaults to false so should raise NoMockAddress + + self.assertRaises(exceptions.NoMockAddress, + requests.get, + url) + + self.assertEqual(1, m.call_count) + self.assertEqual(0, real_send.call_count) + + # change the value of real_http mid test + m.real_http = True + + # fetch the url again and it should go through to the real url that + # we've mocked out at a lower level. + resp = requests.get(url) + + self.assertEqual(1, real_send.call_count) + self.assertEqual(url, real_send.call_args[0][0].url) + + self.assertEqual(test_text, resp.text) + self.assertEqual(test_bytes, resp.content) + + @mock.patch('requests.adapters.HTTPAdapter.send') + def test_real_http_and_session(self, real_send): + url = 'http://www.google.com/' + test_text = 'real http data' + test_bytes = test_text.encode('utf-8') + + req = requests.Request(method='GET', url=url) + real_send.return_value = response.create_response(req.prepare(), + status_code=200, + content=test_bytes) + + session = requests.Session() + with requests_mock.Mocker(session=session, real_http=True): + resp = session.get(url) + + self.assertEqual(test_text, resp.text) + self.assertEqual(test_bytes, resp.content) + + @requests_mock.mock() + def test_with_test_decorator(self, m): + self._do_test(m) + + @requests_mock.mock(kw='mock') + def test_with_mocker_kwargs(self, **kwargs): + self._do_test(kwargs['mock']) + + def test_with_decorator(self): + + @requests_mock.mock() + def inner(m): + self.assertMockStarted() + self._do_test(m) + + self.assertMockStopped() + inner() + self.assertMockStopped() + + def test_with_decorator_called_multiple_times(self): + + @requests_mock.Mocker() + def inner(arg1, m): + self._do_test(m) + self.assertEqual( + len(m.request_history), 1, + "Failed to provide clean mock on subsequent calls" + ) + + inner('a') + # if we call the same decorated method again should get + # a new request mock + inner('b') + + def test_with_class_decorator(self): + outer = self + + @requests_mock.mock() + class Decorated(object): + + def test_will_be_decorated(self, m): + outer.assertMockStarted() + outer._do_test(m) + + def will_not_be_decorated(self): + outer.assertMockStopped() + + decorated_class = Decorated() + + self.assertMockStopped() + decorated_class.test_will_be_decorated() + self.assertMockStopped() + decorated_class.will_not_be_decorated() + self.assertMockStopped() + + def test_with_class_decorator_and_custom_kw(self): + outer = self + + @requests_mock.mock(kw='custom_m') + class Decorated(object): + + def test_will_be_decorated(self, **kwargs): + outer.assertMockStarted() + outer._do_test(kwargs['custom_m']) + + def will_not_be_decorated(self): + outer.assertMockStopped() + + decorated_class = Decorated() + + self.assertMockStopped() + decorated_class.test_will_be_decorated() + self.assertMockStopped() + decorated_class.will_not_be_decorated() + self.assertMockStopped() + + @mock.patch.object(requests_mock.mock, 'TEST_PREFIX', 'foo') + def test_with_class_decorator_and_custom_test_prefix(self): + outer = self + + @requests_mock.mock() + class Decorated(object): + + def foo_will_be_decorated(self, m): + outer.assertMockStarted() + outer._do_test(m) + + def will_not_be_decorated(self): + outer.assertMockStopped() + + decorated_class = Decorated() + + self.assertMockStopped() + decorated_class.foo_will_be_decorated() + self.assertMockStopped() + decorated_class.will_not_be_decorated() + self.assertMockStopped() + + @requests_mock.mock() + def test_query_string(self, m): + url = 'http://test.url/path' + qs = 'a=1&b=2' + m.register_uri('GET', url, text='resp') + resp = requests.get("%s?%s" % (url, qs)) + + self.assertEqual('resp', resp.text) + + self.assertEqual(qs, m.last_request.query) + self.assertEqual(['1'], m.last_request.qs['a']) + self.assertEqual(['2'], m.last_request.qs['b']) + + @requests_mock.mock() + def test_mock_matcher_attributes(self, m): + matcher = self._do_test(m) + + self.assertEqual(1, matcher.call_count) + self.assertEqual(1, m.call_count) + + self.assertTrue(matcher.called) + self.assertTrue(matcher.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + self.assertEqual(m.request_history, matcher.request_history) + self.assertIs(m.last_request, matcher.last_request) + + def test_copy(self): + mocker = requests_mock.mock(kw='foo', real_http=True) + copy_of_mocker = mocker.copy() + self.assertIsNot(copy_of_mocker, mocker) + self.assertEqual(copy_of_mocker._kw, mocker._kw) + self.assertEqual(copy_of_mocker.real_http, mocker.real_http) + + @requests_mock.mock() + def test_reset_mock_reverts_call_count(self, request_mock): + url = 'http://test.url/path' + request_mock.get(url, text='resp') + requests.get(url) + + self.assertEqual(request_mock.call_count, 1) + + # reset count and verify it is 0 + request_mock.reset_mock() + self.assertEqual(request_mock.call_count, 0) + + +class MockerHttpMethodsTests(base.TestCase): + + URL = 'http://test.com/path' + TEXT = 'resp' + + def assertResponse(self, resp): + self.assertEqual(self.TEXT, resp.text) + + @requests_mock.Mocker() + def test_mocker_request(self, m): + method = 'XXX' + mock_obj = m.request(method, self.URL, text=self.TEXT) + resp = requests.request(method, self.URL) + self.assertResponse(resp) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_get(self, m): + mock_obj = m.get(self.URL, text=self.TEXT) + self.assertResponse(requests.get(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_options(self, m): + mock_obj = m.options(self.URL, text=self.TEXT) + self.assertResponse(requests.options(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_head(self, m): + mock_obj = m.head(self.URL, text=self.TEXT) + self.assertResponse(requests.head(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_post(self, m): + mock_obj = m.post(self.URL, text=self.TEXT) + self.assertResponse(requests.post(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_put(self, m): + mock_obj = m.put(self.URL, text=self.TEXT) + self.assertResponse(requests.put(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_patch(self, m): + mock_obj = m.patch(self.URL, text=self.TEXT) + self.assertResponse(requests.patch(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_delete(self, m): + mock_obj = m.delete(self.URL, text=self.TEXT) + self.assertResponse(requests.delete(self.URL)) + self.assertTrue(mock_obj.called) + self.assertTrue(mock_obj.called_once) + self.assertTrue(m.called) + self.assertTrue(m.called_once) + + @requests_mock.Mocker() + def test_mocker_real_http_and_responses(self, m): + self.assertRaises(RuntimeError, + m.get, + self.URL, + text='abcd', + real_http=True) + + @requests_mock.Mocker() + def test_mocker_real_http(self, m): + data = 'testdata' + + uri1 = 'fake://example.com/foo' + uri2 = 'fake://example.com/bar' + uri3 = 'fake://example.com/baz' + + m.get(uri1, text=data) + m.get(uri2, real_http=True) + + self.assertEqual(data, requests.get(uri1).text) + + # This should fail because requests can't get an adapter for mock:// + # but it shows that it has tried and would have made a request. + self.assertRaises(requests.exceptions.InvalidSchema, + requests.get, + uri2) + + # This fails because real_http is not set on the mocker + self.assertRaises(exceptions.NoMockAddress, + requests.get, + uri3) + + # do it again to make sure the mock is still in place + self.assertEqual(data, requests.get(uri1).text) + + @requests_mock.Mocker(case_sensitive=True) + def test_case_sensitive_query(self, m): + data = 'testdata' + query = {'aBcDe': 'FgHiJ'} + + m.get(self.URL, text=data) + resp = requests.get(self.URL, params=query) + + self.assertEqual('GET', m.last_request.method) + self.assertEqual(200, resp.status_code) + self.assertEqual(data, resp.text) + + for k, v in query.items(): + self.assertEqual([v], m.last_request.qs[k]) + + @mock.patch.object(requests_mock.Mocker, 'case_sensitive', True) + def test_global_case_sensitive(self): + with requests_mock.mock() as m: + data = 'testdata' + query = {'aBcDe': 'FgHiJ'} + + m.get(self.URL, text=data) + resp = requests.get(self.URL, params=query) + + self.assertEqual('GET', m.last_request.method) + self.assertEqual(200, resp.status_code) + self.assertEqual(data, resp.text) + + for k, v in query.items(): + self.assertEqual([v], m.last_request.qs[k]) + + def test_nested_mocking(self): + url1 = 'http://url1.com/path1' + url2 = 'http://url2.com/path2' + url3 = 'http://url3.com/path3' + + data1 = 'data1' + data2 = 'data2' + data3 = 'data3' + + with requests_mock.mock() as m1: + + r1 = m1.get(url1, text=data1) + + resp1a = requests.get(url1) + self.assertRaises(exceptions.NoMockAddress, requests.get, url2) + self.assertRaises(exceptions.NoMockAddress, requests.get, url3) + + self.assertEqual(data1, resp1a.text) + + # call count = 3 because there are 3 calls above, url 1-3 + self.assertEqual(3, m1.call_count) + self.assertEqual(1, r1.call_count) + + with requests_mock.mock() as m2: + + r2 = m2.get(url2, text=data2) + + self.assertRaises(exceptions.NoMockAddress, requests.get, url1) + resp2a = requests.get(url2) + self.assertRaises(exceptions.NoMockAddress, requests.get, url3) + + self.assertEqual(data2, resp2a.text) + + with requests_mock.mock() as m3: + + r3 = m3.get(url3, text=data3) + + self.assertRaises(exceptions.NoMockAddress, + requests.get, + url1) + self.assertRaises(exceptions.NoMockAddress, + requests.get, + url2) + resp3 = requests.get(url3) + + self.assertEqual(data3, resp3.text) + + self.assertEqual(3, m3.call_count) + self.assertEqual(1, r3.call_count) + + resp2b = requests.get(url2) + self.assertRaises(exceptions.NoMockAddress, requests.get, url1) + self.assertEqual(data2, resp2b.text) + self.assertRaises(exceptions.NoMockAddress, requests.get, url3) + + self.assertEqual(3, m1.call_count) + self.assertEqual(1, r1.call_count) + self.assertEqual(6, m2.call_count) + self.assertEqual(2, r2.call_count) + self.assertEqual(3, m3.call_count) + self.assertEqual(1, r3.call_count) + + resp1b = requests.get(url1) + self.assertEqual(data1, resp1b.text) + self.assertRaises(exceptions.NoMockAddress, requests.get, url2) + self.assertRaises(exceptions.NoMockAddress, requests.get, url3) + + self.assertEqual(6, m1.call_count) + self.assertEqual(2, r1.call_count) + + @requests_mock.mock() + def test_mocker_additional(self, m): + url = 'http://www.example.com' + good_text = 'success' + + def additional_cb(req): + return 'hello' in req.text + + m.post(url, additional_matcher=additional_cb, text=good_text) + + self.assertEqual(good_text, + requests.post(url, data='hello world').text) + self.assertRaises(exceptions.NoMockAddress, + requests.post, + url, + data='goodbye world') + + @requests_mock.mock() + def test_mocker_pickle(self, m): + url = 'http://www.example.com' + text = 'hello world' + m.get(url, text=text) + + orig_resp = requests.get(url) + self.assertEqual(text, orig_resp.text) + + d = pickle.dumps(orig_resp) + new_resp = pickle.loads(d) + + self.assertEqual(text, new_resp.text) + self.assertIsInstance(orig_resp.request.matcher, adapter._Matcher) + self.assertIsNone(new_resp.request.matcher) + + @requests_mock.mock() + def test_stream_zero_bytes(self, m): + content = b'blah' + + m.get("http://test", content=content) + res = requests.get("http://test", stream=True) + zero_val = res.raw.read(0) + self.assertEqual(b'', zero_val) + self.assertFalse(res.raw.closed) + + full_val = res.raw.read() + self.assertEqual(content, full_val) + + def test_with_json_encoder_on_mocker(self): + test_val = 'hello world' + + class MyJsonEncoder(json.JSONEncoder): + def encode(s, o): + return test_val + + with requests_mock.Mocker(json_encoder=MyJsonEncoder) as m: + m.get("http://test", json={"a": "b"}) + res = requests.get("http://test") + self.assertEqual(test_val, res.text) + + @requests_mock.mock() + def test_with_json_encoder_on_endpoint(self, m): + test_val = 'hello world' + + class MyJsonEncoder(json.JSONEncoder): + def encode(s, o): + return test_val + + m.get("http://test", json={"a": "b"}, json_encoder=MyJsonEncoder) + res = requests.get("http://test") + self.assertEqual(test_val, res.text) + + @requests_mock.mock() + def test_mismatch_content_length_streaming(self, m): + url = "https://test/package.tar.gz" + + def f(request, context): + context.headers["Content-Length"] = "300810" + return None + + m.head( + url=url, + status_code=200, + text=f, + ) + + requests.head(url) diff --git a/contrib/python/requests-mock/py3/tests/test_request.py b/contrib/python/requests-mock/py3/tests/test_request.py new file mode 100644 index 0000000000..69a71556e2 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/test_request.py @@ -0,0 +1,139 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +import requests +import requests_mock +from . import base + + +class RequestTests(base.TestCase): + + def setUp(self): + super(RequestTests, self).setUp() + + self.mocker = requests_mock.Mocker() + self.addCleanup(self.mocker.stop) + self.mocker.start() + + def do_request(self, **kwargs): + method = kwargs.pop('method', 'GET') + url = kwargs.pop('url', 'http://test.example.com/path') + status_code = kwargs.pop('status_code', 200) + data = uuid.uuid4().hex + + m = self.mocker.register_uri(method, + url, + text=data, + status_code=status_code) + + resp = requests.request(method, url, **kwargs) + + self.assertEqual(status_code, resp.status_code) + self.assertEqual(data, resp.text) + + self.assertTrue(m.called_once) + return m.last_request + + def test_base_params(self): + req = self.do_request(method='GET', status_code=200) + + self.assertIs(None, req.allow_redirects) + self.assertIs(None, req.timeout) + self.assertIs(True, req.verify) + self.assertIs(None, req.cert) + self.assertIs(False, req.stream) + + # actually it's an OrderedDict, but equality works fine + # Skipping this check - it's problematic based on people's environments + # and in CI systems where there are proxies set up at the environment + # level. gh #127 + # self.assertEqual({}, req.proxies) + + def test_allow_redirects(self): + req = self.do_request(allow_redirects=False, status_code=300) + self.assertFalse(req.allow_redirects) + + def test_timeout(self): + timeout = 300 + req = self.do_request(timeout=timeout) + self.assertEqual(timeout, req.timeout) + + def test_verify_false(self): + verify = False + req = self.do_request(verify=verify) + self.assertIs(verify, req.verify) + + def test_verify_path(self): + verify = '/path/to/cacerts.pem' + req = self.do_request(verify=verify) + self.assertEqual(verify, req.verify) + + def test_stream(self): + req = self.do_request() + self.assertIs(False, req.stream) + req = self.do_request(stream=False) + self.assertIs(False, req.stream) + req = self.do_request(stream=True) + self.assertIs(True, req.stream) + + def test_certs(self): + cert = ('/path/to/cert.pem', 'path/to/key.pem') + req = self.do_request(cert=cert) + self.assertEqual(cert, req.cert) + self.assertTrue(req.verify) + + def test_proxies(self): + proxies = {'http': 'foo.bar:3128', + 'http://host.name': 'foo.bar:4012'} + + req = self.do_request(proxies=proxies) + + self.assertEqual(proxies, req.proxies) + self.assertIsNot(proxies, req.proxies) + + def test_hostname_port_http(self): + req = self.do_request(url='http://host.example.com:81/path') + + self.assertEqual('host.example.com:81', req.netloc) + self.assertEqual('host.example.com', req.hostname) + self.assertEqual(81, req.port) + + def test_hostname_port_https(self): + req = self.do_request(url='https://host.example.com:8080/path') + + self.assertEqual('host.example.com:8080', req.netloc) + self.assertEqual('host.example.com', req.hostname) + self.assertEqual(8080, req.port) + + def test_hostname_default_port_http(self): + req = self.do_request(url='http://host.example.com/path') + + self.assertEqual('host.example.com', req.netloc) + self.assertEqual('host.example.com', req.hostname) + self.assertEqual(80, req.port) + + def test_hostname_default_port_https(self): + req = self.do_request(url='https://host.example.com/path') + + self.assertEqual('host.example.com', req.netloc) + self.assertEqual('host.example.com', req.hostname) + self.assertEqual(443, req.port) + + def test_to_string(self): + req = self.do_request(url='https://host.example.com/path') + self.assertEqual('GET https://host.example.com/path', str(req)) + + def test_empty_query_string(self): + req = self.do_request(url='https://host.example.com/path?key') + self.assertEqual([''], req.qs['key']) diff --git a/contrib/python/requests-mock/py3/tests/test_response.py b/contrib/python/requests-mock/py3/tests/test_response.py new file mode 100644 index 0000000000..b560eea8d7 --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/test_response.py @@ -0,0 +1,151 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import io +import pickle + +from requests_mock import exceptions +from requests_mock import request +from requests_mock import response +from . import base + + +class ResponseTests(base.TestCase): + + def setUp(self): + super(ResponseTests, self).setUp() + self.method = 'GET' + self.url = 'http://test.url/path' + self.request = request._RequestObjectProxy._create(self.method, + self.url, + {}) + + def create_response(self, **kwargs): + return response.create_response(self.request, **kwargs) + + def test_create_response_body_args(self): + self.assertRaises(RuntimeError, + self.create_response, + raw='abc', + body='abc') + + self.assertRaises(RuntimeError, + self.create_response, + text='abc', + json={'a': 1}) + + def test_content_type(self): + self.assertRaises(TypeError, self.create_response, text=55) + self.assertRaises(TypeError, self.create_response, text={'a': 1}) + self.assertRaises(TypeError, self.create_response, text=b'') + + def test_text_type(self): + self.assertRaises(TypeError, self.create_response, content=u't') + self.assertRaises(TypeError, self.create_response, content={'a': 1}) + self.assertRaises(TypeError, self.create_response, content=u'') + + def test_json_body(self): + data = {'a': 1} + resp = self.create_response(json=data) + + self.assertEqual('{"a": 1}', resp.text) + self.assertIsInstance(resp.text, str) + self.assertIsInstance(resp.content, bytes) + self.assertEqual(data, resp.json()) + + def test_body_body(self): + value = b'data' + body = io.BytesIO(value) + resp = self.create_response(body=body) + + self.assertEqual(value.decode(), resp.text) + self.assertIsInstance(resp.text, str) + self.assertIsInstance(resp.content, bytes) + + def test_setting_connection(self): + conn = object() + resp = self.create_response(connection=conn) + self.assertIs(conn, resp.connection) + + def test_send_from_no_connection(self): + resp = self.create_response() + self.assertRaises(exceptions.InvalidRequest, + resp.connection.send, self.request) + + def test_cookies_from_header(self): + # domain must be same as request url to pass policy check + headers = {'Set-Cookie': 'fig=newton; Path=/test; domain=.test.url'} + resp = self.create_response(headers=headers) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual(['/test'], resp.cookies.list_paths()) + self.assertEqual(['.test.url'], resp.cookies.list_domains()) + + def test_cookies_from_dict(self): + # This is a syntax we get from requests. I'm not sure i like it. + resp = self.create_response(cookies={'fig': 'newton', + 'sugar': 'apple'}) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + + def test_cookies_with_jar(self): + jar = response.CookieJar() + jar.set('fig', 'newton', path='/foo', domain='.test.url') + jar.set('sugar', 'apple', path='/bar', domain='.test.url') + resp = self.create_response(cookies=jar) + + self.assertEqual('newton', resp.cookies['fig']) + self.assertEqual('apple', resp.cookies['sugar']) + self.assertEqual({'/foo', '/bar'}, set(resp.cookies.list_paths())) + self.assertEqual(['.test.url'], resp.cookies.list_domains()) + + def test_response_pickle(self): + text = 'hello world' + jar = response.CookieJar() + jar.set('fig', 'newton', path='/foo', domain='.test.url') + orig_resp = self.create_response(cookies=jar, text=text) + + d = pickle.dumps(orig_resp) + new_resp = pickle.loads(d) + + self.assertEqual(text, new_resp.text) + self.assertEqual('newton', new_resp.cookies['fig']) + self.assertIsNone(new_resp.request.matcher) + + def test_response_encoding(self): + headers = {"content-type": "text/html; charset=ISO-8859-1"} + resp = self.create_response(headers=headers, + text="<html><body></body></html") + self.assertEqual('ISO-8859-1', resp.encoding) + + def test_default_reason(self): + resp = self.create_response() + self.assertEqual('OK', resp.reason) + + def test_custom_reason(self): + reason = 'Live long and prosper' + resp = self.create_response(status_code=201, reason=reason) + + self.assertEqual(201, resp.status_code) + self.assertEqual(reason, resp.reason) + + def test_some_other_response_reasons(self): + reasons = { + 301: 'Moved Permanently', + 410: 'Gone', + 503: 'Service Unavailable', + } + + for code, reason in reasons.items(): + self.assertEqual(reason, + self.create_response(status_code=code).reason) diff --git a/contrib/python/requests-mock/py3/tests/ya.make b/contrib/python/requests-mock/py3/tests/ya.make new file mode 100644 index 0000000000..f52ec22dba --- /dev/null +++ b/contrib/python/requests-mock/py3/tests/ya.make @@ -0,0 +1,26 @@ +PY3TEST() + +SUBSCRIBER(g:python-contrib) + +PEERDIR( + contrib/python/mock + contrib/python/requests-futures + contrib/python/requests-mock +) + +TEST_SRCS( + base.py + pytest/__init__.py + pytest/test_with_pytest.py + #test_adapter.py - need purl + test_custom_matchers.py + #test_fixture.py - need fixtures + test_matcher.py + test_mocker.py + test_request.py + test_response.py +) + +NO_LINT() + +END() |