From 3b6bbe0cef9251f02d1904b29c17661b43ef8cd3 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 9 Jan 2022 17:33:24 +0100 Subject: [PATCH 1/2] Add Mocket "strict mode". --- README.rst | 20 ++++++++++-- mocket/async_mocket.py | 14 +++------ mocket/exceptions.py | 6 ++++ mocket/mocket.py | 68 ++++++++++++++++++++++++++--------------- mocket/utils.py | 8 +++++ tests/main/test_mode.py | 28 +++++++++++++++++ 6 files changed, 108 insertions(+), 36 deletions(-) create mode 100644 mocket/exceptions.py create mode 100644 tests/main/test_mode.py diff --git a/README.rst b/README.rst index 6cb1e862..6e35a2a4 100644 --- a/README.rst +++ b/README.rst @@ -141,9 +141,25 @@ Let's fire our example test:: $ py.test example.py +How to make Mocket fail when it tries to write to a real `socket`? +================================================================== +NEW!!! Sometimes you just want your tests to fail when they attempt to use the network. -How to be sure that all the calls are properly mocked? -====================================================== +.. code-block:: python + + with Mocketizer(strict_mode=True): + with pytest.raises(StrictMocketException): + requests.get("https://duckduckgo.com/") + + # OR + + @mocketize(strict_mode=True) + def test_get(): + with pytest.raises(StrictMocketException): + requests.get("https://duckduckgo.com/") + +How to be sure that all the Entry instances have been served? +============================================================= Add this instruction at the end of the test execution: .. code-block:: python diff --git a/mocket/async_mocket.py b/mocket/async_mocket.py index 3cae3f1f..5ebe7348 100644 --- a/mocket/async_mocket.py +++ b/mocket/async_mocket.py @@ -2,16 +2,10 @@ from .utils import get_mocketize -async def wrapper(test, cls=Mocketizer, truesocket_recording_dir=None, *args, **kwargs): - instance = args[0] if args else None - namespace = None - if truesocket_recording_dir: - namespace = Mocketizer.get_namespace(test, instance) - async with cls( - instance, - namespace=namespace, - truesocket_recording_dir=truesocket_recording_dir, - ): +async def wrapper( + test, truesocket_recording_dir=None, strict_mode=False, *args, **kwargs +): + async with Mocketizer.factory(test, truesocket_recording_dir, strict_mode, args): return await test(*args, **kwargs) diff --git a/mocket/exceptions.py b/mocket/exceptions.py new file mode 100644 index 00000000..f5537568 --- /dev/null +++ b/mocket/exceptions.py @@ -0,0 +1,6 @@ +class MocketException(Exception): + pass + + +class StrictMocketException(MocketException): + pass diff --git a/mocket/mocket.py b/mocket/mocket.py index 5e454abb..0a393982 100644 --- a/mocket/mocket.py +++ b/mocket/mocket.py @@ -18,7 +18,15 @@ from urllib3.util.ssl_ import wrap_socket as urllib3_wrap_socket from .compat import basestring, byte_type, decode_from_bytes, encode_to_bytes, text_type -from .utils import SSL_PROTOCOL, MocketSocketCore, get_mocketize, hexdump, hexload +from .exceptions import StrictMocketException +from .utils import ( + SSL_PROTOCOL, + MocketMode, + MocketSocketCore, + get_mocketize, + hexdump, + hexload, +) xxh32 = None try: @@ -286,6 +294,9 @@ def recv(self, buffersize, flags=None): raise exc def true_sendall(self, data, *args, **kwargs): + if MocketMode().STRICT: + raise StrictMocketException("Mocket tried to use the real `socket` module.") + req = decode_from_bytes(data) # make request unique again req_signature = _hash_request(hasher, req) @@ -597,20 +608,17 @@ def get_response(self): class Mocketizer: - def __init__(self, instance=None, namespace=None, truesocket_recording_dir=None): + def __init__( + self, + instance=None, + namespace=None, + truesocket_recording_dir=None, + strict_mode=False, + ): self.instance = instance self.truesocket_recording_dir = truesocket_recording_dir self.namespace = namespace or text_type(id(self)) - - @staticmethod - def get_namespace(test, instance): - return ".".join( - ( - instance.__class__.__module__, - instance.__class__.__name__, - test.__name__, - ) - ) + MocketMode().STRICT = strict_mode def enter(self): Mocket.enable( @@ -639,22 +647,34 @@ async def __aenter__(self, *args, **kwargs): async def __aexit__(self, *args, **kwargs): self.exit() - def check_and_call(self, method): - method = getattr(self.instance, method, None) + def check_and_call(self, method_name): + method = getattr(self.instance, method_name, None) if callable(method): method() + @staticmethod + def factory(test, truesocket_recording_dir, strict_mode, args): + instance = args[0] if args else None + namespace = None + if truesocket_recording_dir: + namespace = ".".join( + ( + instance.__class__.__module__, + instance.__class__.__name__, + test.__name__, + ) + ) -def wrapper(test, cls=Mocketizer, truesocket_recording_dir=None, *args, **kwargs): - instance = args[0] if args else None - namespace = None - if truesocket_recording_dir: - namespace = Mocketizer.get_namespace(test, instance) - with cls( - instance, - namespace=namespace, - truesocket_recording_dir=truesocket_recording_dir, - ): + return Mocketizer( + instance, + namespace=namespace, + truesocket_recording_dir=truesocket_recording_dir, + strict_mode=strict_mode, + ) + + +def wrapper(test, truesocket_recording_dir=None, strict_mode=False, *args, **kwargs): + with Mocketizer.factory(test, truesocket_recording_dir, strict_mode, args): return test(*args, **kwargs) diff --git a/mocket/utils.py b/mocket/utils.py index 354b0cbb..a0299718 100644 --- a/mocket/utils.py +++ b/mocket/utils.py @@ -42,3 +42,11 @@ def get_mocketize(wrapper_): if decorator.__version__ < "5": # pragma: no cover return decorator.decorator(wrapper_) return decorator.decorator(wrapper_, kwsyntax=True) + + +class MocketMode: + __shared_state = {} + STRICT = None + + def __init__(self): + self.__dict__ = self.__shared_state diff --git a/tests/main/test_mode.py b/tests/main/test_mode.py new file mode 100644 index 00000000..67b61f48 --- /dev/null +++ b/tests/main/test_mode.py @@ -0,0 +1,28 @@ +import pytest +import requests + +from mocket import Mocketizer, mocketize +from mocket.exceptions import StrictMocketException + + +@mocketize(strict_mode=True) +def test_strict_mode_fails(): + url = "https://httpbin.org/ip" + + with pytest.raises(StrictMocketException): + requests.get(url, headers={"Accept": "application/json"}) + + +@pytest.mark.skipif('os.getenv("SKIP_TRUE_HTTP", False)') +def test_intermittent_strict_mode(): + url = "https://httpbin.org/ip" + + with Mocketizer(strict_mode=False): + requests.get(url, headers={"Accept": "application/json"}) + + with Mocketizer(strict_mode=True): + with pytest.raises(StrictMocketException): + requests.get(url, headers={"Accept": "application/json"}) + + with Mocketizer(strict_mode=False): + requests.get(url, headers={"Accept": "application/json"}) From fb3496951f6e0402a1e80263ef6df8e87dec9790 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 9 Jan 2022 17:39:37 +0100 Subject: [PATCH 2/2] Bump version. --- mocket/__init__.py | 2 +- tests/main/test_mode.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mocket/__init__.py b/mocket/__init__.py index d4ddbb5d..38b726da 100644 --- a/mocket/__init__.py +++ b/mocket/__init__.py @@ -3,4 +3,4 @@ __all__ = ("async_mocketize", "mocketize", "Mocket", "MocketEntry", "Mocketizer") -__version__ = "3.10.3" +__version__ = "3.10.4" diff --git a/tests/main/test_mode.py b/tests/main/test_mode.py index 67b61f48..6f2d8f7a 100644 --- a/tests/main/test_mode.py +++ b/tests/main/test_mode.py @@ -10,7 +10,7 @@ def test_strict_mode_fails(): url = "https://httpbin.org/ip" with pytest.raises(StrictMocketException): - requests.get(url, headers={"Accept": "application/json"}) + requests.get(url) @pytest.mark.skipif('os.getenv("SKIP_TRUE_HTTP", False)') @@ -18,11 +18,11 @@ def test_intermittent_strict_mode(): url = "https://httpbin.org/ip" with Mocketizer(strict_mode=False): - requests.get(url, headers={"Accept": "application/json"}) + requests.get(url) with Mocketizer(strict_mode=True): with pytest.raises(StrictMocketException): - requests.get(url, headers={"Accept": "application/json"}) + requests.get(url) with Mocketizer(strict_mode=False): - requests.get(url, headers={"Accept": "application/json"}) + requests.get(url)