From fa5933ccb984a4f3fdddfa3a49cfe2f9337e968c Mon Sep 17 00:00:00 2001 From: ento Date: Fri, 15 Dec 2023 23:41:33 -0800 Subject: [PATCH 1/3] Allow requests to specified host/ports to go through in strict mode --- mocket/async_mocket.py | 11 +++++++++-- mocket/mocket.py | 23 +++++++++++++++++++---- mocket/utils.py | 12 ++++++++++++ tests/main/test_mode.py | 11 +++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/mocket/async_mocket.py b/mocket/async_mocket.py index 5ebe7348..936ec22d 100644 --- a/mocket/async_mocket.py +++ b/mocket/async_mocket.py @@ -3,9 +3,16 @@ async def wrapper( - test, truesocket_recording_dir=None, strict_mode=False, *args, **kwargs + test, + truesocket_recording_dir=None, + strict_mode=False, + strict_mode_allowed=None, + *args, + **kwargs ): - async with Mocketizer.factory(test, truesocket_recording_dir, strict_mode, args): + async with Mocketizer.factory( + test, truesocket_recording_dir, strict_mode, strict_mode_allowed, args + ): return await test(*args, **kwargs) diff --git a/mocket/mocket.py b/mocket/mocket.py index 2b4d0c0f..95825b37 100644 --- a/mocket/mocket.py +++ b/mocket/mocket.py @@ -311,7 +311,10 @@ def recv(self, buffersize, flags=None): def true_sendall(self, data, *args, **kwargs): if MocketMode().STRICT: - raise StrictMocketException("Mocket tried to use the real `socket` module.") + if not MocketMode().allowed((self._host, self._port)): + raise StrictMocketException( + "Mocket tried to use the real `socket` module." + ) req = decode_from_bytes(data) # make request unique again @@ -639,11 +642,13 @@ def __init__( namespace=None, truesocket_recording_dir=None, strict_mode=False, + strict_mode_allowed=None, ): self.instance = instance self.truesocket_recording_dir = truesocket_recording_dir self.namespace = namespace or text_type(id(self)) MocketMode().STRICT = strict_mode + MocketMode().STRICT_ALLOWED = strict_mode_allowed def enter(self): Mocket.enable( @@ -678,7 +683,7 @@ def check_and_call(self, method_name): method() @staticmethod - def factory(test, truesocket_recording_dir, strict_mode, args): + def factory(test, truesocket_recording_dir, strict_mode, strict_mode_allowed, args): instance = args[0] if args else None namespace = None if truesocket_recording_dir: @@ -695,11 +700,21 @@ def factory(test, truesocket_recording_dir, strict_mode, args): namespace=namespace, truesocket_recording_dir=truesocket_recording_dir, strict_mode=strict_mode, + strict_mode_allowed=strict_mode_allowed, ) -def wrapper(test, truesocket_recording_dir=None, strict_mode=False, *args, **kwargs): - with Mocketizer.factory(test, truesocket_recording_dir, strict_mode, args): +def wrapper( + test, + truesocket_recording_dir=None, + strict_mode=False, + strict_mode_allowed=None, + *args, + **kwargs +): + with Mocketizer.factory( + test, truesocket_recording_dir, strict_mode, strict_mode_allowed, args + ): return test(*args, **kwargs) diff --git a/mocket/utils.py b/mocket/utils.py index 64a2c18e..88d7868c 100644 --- a/mocket/utils.py +++ b/mocket/utils.py @@ -47,6 +47,18 @@ def get_mocketize(wrapper_): class MocketMode: __shared_state = {} STRICT = None + STRICT_ALLOWED = None def __init__(self): self.__dict__ = self.__shared_state + + def allowed(self, location): + if not self.STRICT_ALLOWED: + return False + host, port = location + for allowed in self.STRICT_ALLOWED: + if isinstance(allowed, str) and host == allowed: + return True + elif location == allowed: + return True + return False diff --git a/tests/main/test_mode.py b/tests/main/test_mode.py index b104589c..0e9a7893 100644 --- a/tests/main/test_mode.py +++ b/tests/main/test_mode.py @@ -26,3 +26,14 @@ def test_intermittent_strict_mode(): with Mocketizer(strict_mode=False): requests.get(url) + + +@pytest.mark.skipif('os.getenv("SKIP_TRUE_HTTP", False)') +def test_strict_mode_exceptions(): + url = "http://httpbin.local/ip" + + with Mocketizer(strict_mode=True, strict_mode_allowed=["httpbin.local"]): + requests.get(url) + + with Mocketizer(strict_mode=True, strict_mode_allowed=[("httpbin.local", 80)]): + requests.get(url) From cca9d00852a98dbcd5351c9da5da55f7ce9d2dcd Mon Sep 17 00:00:00 2001 From: ento Date: Mon, 18 Dec 2023 10:41:18 -0800 Subject: [PATCH 2/3] More verbose error message for StrictMocketException --- mocket/mocket.py | 16 +++++++++++++++- mocket/mockhttp.py | 12 ++++++++++++ tests/main/test_mode.py | 20 ++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/mocket/mocket.py b/mocket/mocket.py index 95825b37..2100590e 100644 --- a/mocket/mocket.py +++ b/mocket/mocket.py @@ -312,8 +312,19 @@ def recv(self, buffersize, flags=None): def true_sendall(self, data, *args, **kwargs): if MocketMode().STRICT: if not MocketMode().allowed((self._host, self._port)): + current_entries = [ + (location, "\n ".join(map(str, entries))) + for location, entries in Mocket._entries.items() + ] + formatted_entries = "\n".join( + [ + f" {location}:\n {entries}" + for location, entries in current_entries + ] + ) raise StrictMocketException( - "Mocket tried to use the real `socket` module." + "Mocket tried to use the real `socket` module while strict mode is active.\n" + f"Registered entries:\n{formatted_entries}" ) req = decode_from_bytes(data) @@ -614,6 +625,9 @@ def __init__(self, location, responses): r = self.response_cls(r) self.responses.append(r) + def __repr__(self): + return "{}(location={})".format(self.__class__.__name__, self.location) + @staticmethod def can_handle(data): return True diff --git a/mocket/mockhttp.py b/mocket/mockhttp.py index 8cb5cdc6..60b134a1 100644 --- a/mocket/mockhttp.py +++ b/mocket/mockhttp.py @@ -175,6 +175,18 @@ def __init__(self, uri, method, responses, match_querystring=True): self._sent_data = b"" self._match_querystring = match_querystring + def __repr__(self): + return ( + "{}(method={!r}, schema={!r}, location={!r}, path={!r}, query={!r})".format( + self.__class__.__name__, + self.method, + self.schema, + self.location, + self.path, + self.query, + ) + ) + def collect(self, data): consume_response = True diff --git a/tests/main/test_mode.py b/tests/main/test_mode.py index 0e9a7893..e3a909f3 100644 --- a/tests/main/test_mode.py +++ b/tests/main/test_mode.py @@ -3,6 +3,7 @@ from mocket import Mocketizer, mocketize from mocket.exceptions import StrictMocketException +from mocket.mockhttp import Entry, Response @mocketize(strict_mode=True) @@ -37,3 +38,22 @@ def test_strict_mode_exceptions(): with Mocketizer(strict_mode=True, strict_mode_allowed=[("httpbin.local", 80)]): requests.get(url) + + +def test_strict_mode_error_message(): + url = "http://httpbin.local/ip" + + Entry.register(Entry.GET, "http://httpbin.local/user.agent", Response(status=404)) + + with Mocketizer(strict_mode=True): + with pytest.raises(StrictMocketException) as exc_info: + requests.get(url) + assert ( + str(exc_info.value) + == """ +Mocket tried to use the real `socket` module while strict mode is active. +Registered entries: + ('httpbin.local', 80): + Entry(method='GET', schema='http', location=('httpbin.local', 80), path='/user.agent', query='') +""".strip() + ) From 72534fb2a006d86113bd69d78100f1b9d0000255 Mon Sep 17 00:00:00 2001 From: ento Date: Fri, 12 Jan 2024 13:48:19 -0800 Subject: [PATCH 3/3] Add usage example to README --- README.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.rst b/README.rst index f6e027c7..d8732c62 100644 --- a/README.rst +++ b/README.rst @@ -169,6 +169,20 @@ NEW!!! Sometimes you just want your tests to fail when they attempt to use the n with pytest.raises(StrictMocketException): requests.get("https://duckduckgo.com/") +You can specify exceptions as a list of hosts or host-port pairs. + +.. code-block:: python + + with Mocketizer(strict_mode=True, strict_mode_allowed=["localhost", ("intake.ourmetrics.net", 443)]): + ... + + # OR + + @mocketize(strict_mode=True, strict_mode_allowed=["localhost", ("intake.ourmetrics.net", 443)]) + def test_get(): + ... + + How to be sure that all the Entry instances have been served? ============================================================= Add this instruction at the end of the test execution: