From 127ac678bc33a1b9fdb8279d9a9c3eccc0b79c14 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sat, 21 May 2022 23:15:56 +0200 Subject: [PATCH 01/21] Testing Python 3.11 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 958607cd..39f2bf7e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "pypy3"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] steps: - uses: actions/checkout@v2 From 6f638bede42fa386ecdddcf689236a8cb23999f8 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sat, 21 May 2022 23:18:18 +0200 Subject: [PATCH 02/21] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 39f2bf7e..f842795b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-beta.1", "pypy3"] steps: - uses: actions/checkout@v2 From a63b9fd0db8f4ca2fe1d74e1a8b43eefcd2a7133 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Fri, 15 Jul 2022 09:07:46 +0200 Subject: [PATCH 03/21] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f842795b..1de81fc4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-beta.1", "pypy3"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-beta.4", "pypy3"] steps: - uses: actions/checkout@v2 From ff26064170926da132c8a7f7573678e245cec0f6 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Wed, 26 Oct 2022 22:24:54 +0200 Subject: [PATCH 04/21] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1de81fc4..39f2bf7e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-beta.4", "pypy3"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] steps: - uses: actions/checkout@v2 From 24b481ada70f3e59e6e86ddd74a1a56ec23431e6 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Wed, 26 Oct 2022 22:28:23 +0200 Subject: [PATCH 05/21] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 39f2bf7e..71bd5939 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] steps: - uses: actions/checkout@v2 From 349531de84be5b46b27723db5217003cc19e9f36 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sat, 12 Nov 2022 11:54:15 +0100 Subject: [PATCH 06/21] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 71bd5939..39f2bf7e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] steps: - uses: actions/checkout@v2 From dbf7b8d2ce480285ec30c682eb746d912d1304a3 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Mon, 5 Dec 2022 00:54:16 +0100 Subject: [PATCH 07/21] Switching to `httptools.parser.HttpRequestParser`. --- Pipfile | 2 +- mocket/mockhttp.py | 71 +++++++++++++++++++--------- mocket/plugins/httpretty/__init__.py | 10 ++-- tests/main/test_http.py | 2 +- 4 files changed, 56 insertions(+), 29 deletions(-) diff --git a/Pipfile b/Pipfile index 19823265..bbae01b9 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,7 @@ name = "pypi" python-magic = ">=0.4.5" decorator = ">=4.0.0" urllib3 = ">=1.25.3" -http-parser = ">=0.9.0" +httptools = "*" [dev-packages] pre-commit = "*" diff --git a/mocket/mockhttp.py b/mocket/mockhttp.py index 6453e0f7..dc1754b1 100644 --- a/mocket/mockhttp.py +++ b/mocket/mockhttp.py @@ -3,13 +3,10 @@ from http.server import BaseHTTPRequestHandler from urllib.parse import parse_qs, unquote, urlsplit -from .compat import decode_from_bytes, do_the_magic, encode_to_bytes -from .mocket import Mocket, MocketEntry +from httptools.parser import HttpRequestParser -try: - from http_parser.parser import HttpParser -except ImportError: - from http_parser.pyparser import HttpParser +from .compat import ENCODING, decode_from_bytes, do_the_magic, encode_to_bytes +from .mocket import Mocket, MocketEntry try: import magic @@ -21,31 +18,59 @@ CRLF = "\r\n" +class Protocol: + def __init__(self): + self.url = None + self.body = None + self.headers = {} + + def on_header(self, name: bytes, value: bytes): + self.headers[name.decode("ascii")] = value.decode("ascii") + + def on_body(self, body: bytes): + try: + self.body = body.decode(ENCODING) + except UnicodeDecodeError: + self.body = body + + def on_url(self, url: bytes): + self.url = url.decode("ascii") + + class Request: - parser = None - _body = None + _protocol = None + _parser = None def __init__(self, data): - self.parser = HttpParser() - self.parser.execute(data, len(data)) - - self.method = self.parser.get_method() - self.path = self.parser.get_path() - self.headers = self.parser.get_headers() - self.querystring = parse_qs( - unquote(self.parser.get_query_string()), keep_blank_values=True - ) - if self.querystring: - self.path += "?{}".format(self.parser.get_query_string()) + self._protocol = Protocol() + self._parser = HttpRequestParser(self._protocol) + self.add_data(data) def add_data(self, data): - self.parser.execute(data, len(data)) + self._parser.feed_data(data) + + @property + def method(self): + return self._parser.get_method().decode("ascii") + + @property + def path(self): + return self._protocol.url + + @property + def headers(self): + return self._protocol.headers + + @property + def querystring(self): + parts = self._protocol.url.split("?", 1) + if len(parts) == 2: + return parse_qs(unquote(parts[1]), keep_blank_values=True) + return {} @property def body(self): - if self._body is None: - self._body = decode_from_bytes(self.parser.recv_body()) - return self._body + return self._protocol.body def __str__(self): return "{} - {} - {}".format(self.method, self.path, self.headers) diff --git a/mocket/plugins/httpretty/__init__.py b/mocket/plugins/httpretty/__init__.py index 4fe5a842..79af6aff 100644 --- a/mocket/plugins/httpretty/__init__.py +++ b/mocket/plugins/httpretty/__init__.py @@ -1,6 +1,6 @@ from mocket import Mocket, mocketize from mocket.async_mocket import async_mocketize -from mocket.compat import byte_type, text_type +from mocket.compat import ENCODING, byte_type, text_type from mocket.mockhttp import Entry as MocketHttpEntry from mocket.mockhttp import Request as MocketHttpRequest from mocket.mockhttp import Response as MocketHttpResponse @@ -13,9 +13,11 @@ def httprettifier_headers(headers): class Request(MocketHttpRequest): @property def body(self): - if self._body is None: - self._body = self.parser.recv_body() - return self._body + return super().body.encode(ENCODING) + + @property + def headers(self): + return httprettifier_headers(super().headers) class Response(MocketHttpResponse): diff --git a/tests/main/test_http.py b/tests/main/test_http.py index 4bb6b827..f1893be0 100644 --- a/tests/main/test_http.py +++ b/tests/main/test_http.py @@ -23,7 +23,7 @@ class HttpTestCase(TestCase): def assertEqualHeaders(self, first, second, msg=None): first = {k.lower(): v for k, v in first.items()} second = {k.lower(): v for k, v in second.items()} - self.assertEqual(first, second, msg) + self.assertDictEqual(first, second, msg) @pytest.mark.skipif('os.getenv("SKIP_TRUE_HTTP", False)') From 5e0d8a8b75280372536bc85ea44b82e0acc841c4 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Mon, 5 Dec 2022 01:08:23 +0100 Subject: [PATCH 08/21] Disabling tests for `pook` when testing Python 3.11 --- mocket/plugins/pook_mock_engine.py | 147 +++++++++++++++-------------- setup.py | 1 + tests/main/test_pook.py | 47 ++++----- 3 files changed, 102 insertions(+), 93 deletions(-) diff --git a/mocket/plugins/pook_mock_engine.py b/mocket/plugins/pook_mock_engine.py index 5b1d207b..8df8776d 100644 --- a/mocket/plugins/pook_mock_engine.py +++ b/mocket/plugins/pook_mock_engine.py @@ -1,71 +1,76 @@ -from pook.engine import MockEngine -from pook.interceptors.base import BaseInterceptor - -from mocket.mocket import Mocket -from mocket.mockhttp import Entry, Response - - -class MocketPookEntry(Entry): - pook_request = None - pook_engine = None - - def can_handle(self, data): - can_handle = super(MocketPookEntry, self).can_handle(data) - - if can_handle: - self.pook_engine.match(self.pook_request) - return can_handle - - @classmethod - def single_register(cls, method, uri, body='', status=200, headers=None, match_querystring=True): - entry = cls( - uri, method, Response( - body=body, status=status, headers=headers - ), match_querystring=match_querystring - ) - Mocket.register(entry) - return entry - - -class MocketInterceptor(BaseInterceptor): - @staticmethod - def activate(): - Mocket.disable() - Mocket.enable() - - @staticmethod - def disable(): - Mocket.disable() - - -class MocketEngine(MockEngine): - - def __init__(self, engine): - def mocket_mock_fun(*args, **kwargs): - mock = self.pook_mock_fun(*args, **kwargs) - - request = mock._request - method = request.method - url = request.rawurl - - response = mock._response - body = response._body - status = response._status - headers = response._headers - - entry = MocketPookEntry.single_register(method, url, body, status, headers) - entry.pook_engine = self.engine - entry.pook_request = request - - return mock - - # Store plugins engine - self.engine = engine - # Store HTTP client interceptors - self.interceptors = [] - # Self-register MocketInterceptor - self.add_interceptor(MocketInterceptor) - - # mocking pook.mock() - self.pook_mock_fun = self.engine.mock - self.engine.mock = mocket_mock_fun +import platform + +if not platform.python_version().startswith("3.11."): + # it looks like `pook` is not compatible with Python 3.11 + from pook.engine import MockEngine + from pook.interceptors.base import BaseInterceptor + + from mocket.mocket import Mocket + from mocket.mockhttp import Entry, Response + + class MocketPookEntry(Entry): + pook_request = None + pook_engine = None + + def can_handle(self, data): + can_handle = super(MocketPookEntry, self).can_handle(data) + + if can_handle: + self.pook_engine.match(self.pook_request) + return can_handle + + @classmethod + def single_register( + cls, method, uri, body="", status=200, headers=None, match_querystring=True + ): + entry = cls( + uri, + method, + Response(body=body, status=status, headers=headers), + match_querystring=match_querystring, + ) + Mocket.register(entry) + return entry + + class MocketInterceptor(BaseInterceptor): + @staticmethod + def activate(): + Mocket.disable() + Mocket.enable() + + @staticmethod + def disable(): + Mocket.disable() + + class MocketEngine(MockEngine): + def __init__(self, engine): + def mocket_mock_fun(*args, **kwargs): + mock = self.pook_mock_fun(*args, **kwargs) + + request = mock._request + method = request.method + url = request.rawurl + + response = mock._response + body = response._body + status = response._status + headers = response._headers + + entry = MocketPookEntry.single_register( + method, url, body, status, headers + ) + entry.pook_engine = self.engine + entry.pook_request = request + + return mock + + # Store plugins engine + self.engine = engine + # Store HTTP client interceptors + self.interceptors = [] + # Self-register MocketInterceptor + self.add_interceptor(MocketInterceptor) + + # mocking pook.mock() + self.pook_mock_fun = self.engine.mock + self.engine.mock = mocket_mock_fun diff --git a/setup.py b/setup.py index 544e6248..145fcedf 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ def read_version(package): "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development", diff --git a/tests/main/test_pook.py b/tests/main/test_pook.py index 2d95d3d1..2207e8f1 100644 --- a/tests/main/test_pook.py +++ b/tests/main/test_pook.py @@ -1,30 +1,33 @@ -import pook -import requests +import platform -from mocket.plugins.pook_mock_engine import MocketEngine +if not platform.python_version().startswith("3.11."): + # it looks like `pook` is not compatible with Python 3.11 + import pook + import requests -pook.set_mock_engine(MocketEngine) + from mocket.plugins.pook_mock_engine import MocketEngine + pook.set_mock_engine(MocketEngine) -@pook.on -def test_pook_engine(): + @pook.on + def test_pook_engine(): - url = "http://twitter.com/api/1/foobar" - status = 404 - response_json = {"error": "foo"} + url = "http://twitter.com/api/1/foobar" + status = 404 + response_json = {"error": "foo"} - mock = pook.get( - url, - headers={"content-type": "application/json"}, - reply=status, - response_json=response_json, - ) - mock.persist() + mock = pook.get( + url, + headers={"content-type": "application/json"}, + reply=status, + response_json=response_json, + ) + mock.persist() - requests.get(url) - assert mock.calls == 1 + requests.get(url) + assert mock.calls == 1 - resp = requests.get(url) - assert resp.status_code == status - assert resp.json() == response_json - assert mock.calls == 2 + resp = requests.get(url) + assert resp.status_code == status + assert resp.json() == response_json + assert mock.calls == 2 From 10cf45b071776cd36f9c7070f5d47a26ea010d7d Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sat, 17 Dec 2022 12:09:48 +0100 Subject: [PATCH 09/21] Removing DeprecationWarning all over the place. --- README.rst | 14 +++++++------- tests/main/test_http_aiohttp.py | 16 ++++++++-------- tests/tests37/test_asyncio.py | 2 +- tests/tests38/test_http_aiohttp.py | 10 +++++----- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.rst b/README.rst index 67cc9564..a810dc1a 100644 --- a/README.rst +++ b/README.rst @@ -249,12 +249,12 @@ Example: async def main(l): async with aiohttp.ClientSession(loop=l) as session: - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.get(url) as get_response: assert get_response.status == 200 assert await get_response.text() == '{"origin": "127.0.0.1"}' - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() loop.set_debug(True) loop.run_until_complete(main(loop)) @@ -278,17 +278,17 @@ Example: async def main(l): async with aiohttp.ClientSession(loop=l) as session: - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.get(url) as get_response: assert get_response.status == 404 assert await get_response.text() == body - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.post(url, data=body * 6) as post_response: assert post_response.status == 201 assert await post_response.text() == body * 2 - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() loop.run_until_complete(main(loop)) # or again with a unittest.IsolatedAsyncioTestCase @@ -303,12 +303,12 @@ Example: Entry.single_register(Entry.POST, url, body=body * 2, status=201) async with aiohttp.ClientSession() as session: - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.get(url) as get_response: assert get_response.status == 404 assert await get_response.text() == body - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.post(url, data=body * 6) as post_response: assert post_response.status == 201 assert await post_response.text() == body * 2 diff --git a/tests/main/test_http_aiohttp.py b/tests/main/test_http_aiohttp.py index 076fc934..6cfd2d84 100644 --- a/tests/main/test_http_aiohttp.py +++ b/tests/main/test_http_aiohttp.py @@ -20,19 +20,19 @@ def test_http_session(self): async def main(_loop): async with aiohttp.ClientSession(loop=_loop) as session: - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.get(url) as get_response: assert get_response.status == 404 assert await get_response.text() == body - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.post(url, data=body * 6) as post_response: assert post_response.status == 201 assert await post_response.text() == body * 2 assert Mocket.last_request().method == "POST" assert Mocket.last_request().body == body * 6 - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() loop.set_debug(True) loop.run_until_complete(main(loop)) self.assertEqual(len(Mocket.request_list()), 2) @@ -46,17 +46,17 @@ def test_https_session(self): async def main(_loop): async with aiohttp.ClientSession(loop=_loop) as session: - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.get(url) as get_response: assert get_response.status == 404 assert await get_response.text() == body - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.post(url, data=body * 6) as post_response: assert post_response.status == 201 assert await post_response.text() == body * 2 - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() loop.set_debug(True) loop.run_until_complete(main(loop)) self.assertEqual(len(Mocket.request_list()), 2) @@ -72,11 +72,11 @@ def test_httprettish_session(self): async def main(_loop): async with aiohttp.ClientSession(loop=_loop) as session: - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.get(url) as get_response: assert get_response.status == 200 assert await get_response.text() == '{"origin": "127.0.0.1"}' - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() loop.set_debug(True) loop.run_until_complete(main(loop)) diff --git a/tests/tests37/test_asyncio.py b/tests/tests37/test_asyncio.py index 9e6e0d47..66f8cc9f 100644 --- a/tests/tests37/test_asyncio.py +++ b/tests/tests37/test_asyncio.py @@ -33,7 +33,7 @@ async def test_asyncio_connection(): writer.close() await writer.wait_closed() - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() loop.set_debug(True) loop.run_until_complete(test_asyncio_connection()) diff --git a/tests/tests38/test_http_aiohttp.py b/tests/tests38/test_http_aiohttp.py index fe3817f9..348a7a01 100644 --- a/tests/tests38/test_http_aiohttp.py +++ b/tests/tests38/test_http_aiohttp.py @@ -19,12 +19,12 @@ async def test_http_session(self): Entry.single_register(Entry.POST, url, body=body * 2, status=201) async with aiohttp.ClientSession() as session: - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.get(url) as get_response: assert get_response.status == 404 assert await get_response.text() == body - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.post(url, data=body * 6) as post_response: assert post_response.status == 201 assert await post_response.text() == body * 2 @@ -41,12 +41,12 @@ async def test_https_session(self): Entry.single_register(Entry.POST, url, body=body * 2, status=201) async with aiohttp.ClientSession() as session: - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.get(url) as get_response: assert get_response.status == 404 assert await get_response.text() == body - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.post(url, data=body * 6) as post_response: assert post_response.status == 201 assert await post_response.text() == body * 2 @@ -63,7 +63,7 @@ async def test_httprettish_session(self): ) async with aiohttp.ClientSession() as session: - with async_timeout.timeout(3): + async with async_timeout.timeout(3): async with session.get(url) as get_response: assert get_response.status == 200 assert await get_response.text() == '{"origin": "127.0.0.1"}' From 77f91f7fcff89291e16c2f5b9b7d904b975e1bbe Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sat, 17 Dec 2022 12:39:31 +0100 Subject: [PATCH 10/21] Python 3.11 needs an async decorator. --- tests/main/test_http_aiohttp.py | 127 +++++++++++++++++--------------- 1 file changed, 66 insertions(+), 61 deletions(-) diff --git a/tests/main/test_http_aiohttp.py b/tests/main/test_http_aiohttp.py index 6cfd2d84..70492fa7 100644 --- a/tests/main/test_http_aiohttp.py +++ b/tests/main/test_http_aiohttp.py @@ -1,5 +1,6 @@ import asyncio import json +import platform from unittest import TestCase import aiohttp @@ -9,74 +10,78 @@ from mocket.mockhttp import Entry from mocket.plugins.httpretty import HTTPretty, httprettified +if not platform.python_version().startswith("3.11."): + # Python 3.11 needs async decorators, or aiohttp + # will fail with "Cannot write to closing transport" + class AioHttpEntryTestCase(TestCase): + @mocketize + def test_http_session(self): + url = "http://httpbin.org/ip" + body = "asd" * 100 + Entry.single_register(Entry.GET, url, body=body, status=404) + Entry.single_register(Entry.POST, url, body=body * 2, status=201) -class AioHttpEntryTestCase(TestCase): - @mocketize - def test_http_session(self): - url = "http://httpbin.org/ip" - body = "asd" * 100 - Entry.single_register(Entry.GET, url, body=body, status=404) - Entry.single_register(Entry.POST, url, body=body * 2, status=201) + async def main(_loop): + async with aiohttp.ClientSession(loop=_loop) as session: + async with async_timeout.timeout(3): + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body - async def main(_loop): - async with aiohttp.ClientSession(loop=_loop) as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body + async with async_timeout.timeout(3): + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 + assert Mocket.last_request().method == "POST" + assert Mocket.last_request().body == body * 6 - async with async_timeout.timeout(3): - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 - assert Mocket.last_request().method == "POST" - assert Mocket.last_request().body == body * 6 + loop = asyncio.new_event_loop() + loop.set_debug(True) + loop.run_until_complete(main(loop)) + self.assertEqual(len(Mocket.request_list()), 2) - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(main(loop)) - self.assertEqual(len(Mocket.request_list()), 2) + @mocketize + def test_https_session(self): + url = "https://httpbin.org/ip" + body = "asd" * 100 + Entry.single_register(Entry.GET, url, body=body, status=404) + Entry.single_register(Entry.POST, url, body=body * 2, status=201) - @mocketize - def test_https_session(self): - url = "https://httpbin.org/ip" - body = "asd" * 100 - Entry.single_register(Entry.GET, url, body=body, status=404) - Entry.single_register(Entry.POST, url, body=body * 2, status=201) + async def main(_loop): + async with aiohttp.ClientSession(loop=_loop) as session: + async with async_timeout.timeout(3): + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body - async def main(_loop): - async with aiohttp.ClientSession(loop=_loop) as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body + async with async_timeout.timeout(3): + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 - async with async_timeout.timeout(3): - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 + loop = asyncio.new_event_loop() + loop.set_debug(True) + loop.run_until_complete(main(loop)) + self.assertEqual(len(Mocket.request_list()), 2) - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(main(loop)) - self.assertEqual(len(Mocket.request_list()), 2) + @httprettified + def test_httprettish_session(self): + url = "https://httpbin.org/ip" + HTTPretty.register_uri( + HTTPretty.GET, + url, + body=json.dumps(dict(origin="127.0.0.1")), + ) - @httprettified - def test_httprettish_session(self): - url = "https://httpbin.org/ip" - HTTPretty.register_uri( - HTTPretty.GET, - url, - body=json.dumps(dict(origin="127.0.0.1")), - ) + async def main(_loop): + async with aiohttp.ClientSession(loop=_loop) as session: + async with async_timeout.timeout(3): + async with session.get(url) as get_response: + assert get_response.status == 200 + assert ( + await get_response.text() == '{"origin": "127.0.0.1"}' + ) - async def main(_loop): - async with aiohttp.ClientSession(loop=_loop) as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 200 - assert await get_response.text() == '{"origin": "127.0.0.1"}' - - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(main(loop)) + loop = asyncio.new_event_loop() + loop.set_debug(True) + loop.run_until_complete(main(loop)) From c87c2224b517d8bfda686d4a32504e7441b701db Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sat, 17 Dec 2022 13:19:30 +0100 Subject: [PATCH 11/21] Adding Redis as a service container. --- .github/workflows/main.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index acc77085..ad8926cc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,12 +15,26 @@ concurrency: jobs: build: - runs-on: ubuntu-20.04 strategy: matrix: python-version: ['3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', 'pypy3.9'] + services: + # https://docs.github.com/en/actions/using-containerized-services/about-service-containers + redis: + # Docker Hub image + image: redis + # Set health checks to wait until redis has started + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps port 6379 on service container to the host + - 6379:6379 + steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -31,9 +45,6 @@ jobs: cache-dependency-path: | Pipfile setup.py - - name: Install Redis - run: | - sudo apt install redis-server - name: Install dependencies run: | make develop From 6719547e1d3d5bb672258bc30fed835ddc659ce8 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 18 Dec 2022 00:44:02 +0100 Subject: [PATCH 12/21] Removing `async-timeout` dependency. --- Pipfile | 1 - README.rst | 54 ++++++------ tests/main/test_http_aiohttp.py | 131 ++++++++++++++--------------- tests/tests38/test_http_aiohttp.py | 48 +++++------ 4 files changed, 113 insertions(+), 121 deletions(-) diff --git a/Pipfile b/Pipfile index 8b8e7914..62d66ab2 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,6 @@ pook = "*" flake8 = "<7" xxhash = "*" aiohttp = "*" -async-timeout = "*" httpx = "*" pipfile = "*" build = "*" diff --git a/README.rst b/README.rst index a810dc1a..3a68d3fb 100644 --- a/README.rst +++ b/README.rst @@ -231,7 +231,6 @@ Example: import aiohttp import asyncio - import async_timeout from unittest import TestCase from mocket.plugins.httpretty import httpretty, httprettified @@ -248,11 +247,12 @@ Example: ) async def main(l): - async with aiohttp.ClientSession(loop=l) as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 200 - assert await get_response.text() == '{"origin": "127.0.0.1"}' + async with aiohttp.ClientSession( + loop=l, timeout=aiohttp.ClientTimeout(total=3) + ) as session: + async with session.get(url) as get_response: + assert get_response.status == 200 + assert await get_response.text() == '{"origin": "127.0.0.1"}' loop = asyncio.new_event_loop() loop.set_debug(True) @@ -277,16 +277,16 @@ Example: Entry.single_register(Entry.POST, url, body=body*2, status=201) async def main(l): - async with aiohttp.ClientSession(loop=l) as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body + async with aiohttp.ClientSession( + loop=l, timeout=aiohttp.ClientTimeout(total=3) + ) as session: + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body - async with async_timeout.timeout(3): - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 loop = asyncio.new_event_loop() loop.run_until_complete(main(loop)) @@ -302,18 +302,18 @@ Example: Entry.single_register(Entry.GET, url, body=body, status=404) Entry.single_register(Entry.POST, url, body=body * 2, status=201) - async with aiohttp.ClientSession() as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body - - async with async_timeout.timeout(3): - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 - assert Mocket.last_request().method == 'POST' - assert Mocket.last_request().body == body * 6 + async with aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=3) + ) as session: + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body + + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 + assert Mocket.last_request().method == 'POST' + assert Mocket.last_request().body == body * 6 Works well with others diff --git a/tests/main/test_http_aiohttp.py b/tests/main/test_http_aiohttp.py index 70492fa7..a98cb370 100644 --- a/tests/main/test_http_aiohttp.py +++ b/tests/main/test_http_aiohttp.py @@ -1,87 +1,84 @@ import asyncio import json -import platform from unittest import TestCase import aiohttp -import async_timeout from mocket.mocket import Mocket, mocketize from mocket.mockhttp import Entry from mocket.plugins.httpretty import HTTPretty, httprettified -if not platform.python_version().startswith("3.11."): - # Python 3.11 needs async decorators, or aiohttp - # will fail with "Cannot write to closing transport" - class AioHttpEntryTestCase(TestCase): - @mocketize - def test_http_session(self): - url = "http://httpbin.org/ip" - body = "asd" * 100 - Entry.single_register(Entry.GET, url, body=body, status=404) - Entry.single_register(Entry.POST, url, body=body * 2, status=201) - async def main(_loop): - async with aiohttp.ClientSession(loop=_loop) as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body +class AioHttpEntryTestCase(TestCase): + timeout = aiohttp.ClientTimeout(total=3) - async with async_timeout.timeout(3): - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 - assert Mocket.last_request().method == "POST" - assert Mocket.last_request().body == body * 6 + @mocketize + def test_http_session(self): + url = "http://httpbin.org/ip" + body = "asd" * 100 + Entry.single_register(Entry.GET, url, body=body, status=404) + Entry.single_register(Entry.POST, url, body=body * 2, status=201) - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(main(loop)) - self.assertEqual(len(Mocket.request_list()), 2) + async def main(_loop): + async with aiohttp.ClientSession( + loop=_loop, timeout=self.timeout + ) as session: + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body - @mocketize - def test_https_session(self): - url = "https://httpbin.org/ip" - body = "asd" * 100 - Entry.single_register(Entry.GET, url, body=body, status=404) - Entry.single_register(Entry.POST, url, body=body * 2, status=201) + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 + assert Mocket.last_request().method == "POST" + assert Mocket.last_request().body == body * 6 - async def main(_loop): - async with aiohttp.ClientSession(loop=_loop) as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body + loop = asyncio.new_event_loop() + loop.set_debug(True) + loop.run_until_complete(main(loop)) + self.assertEqual(len(Mocket.request_list()), 2) - async with async_timeout.timeout(3): - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 + @mocketize + def test_https_session(self): + url = "https://httpbin.org/ip" + body = "asd" * 100 + Entry.single_register(Entry.GET, url, body=body, status=404) + Entry.single_register(Entry.POST, url, body=body * 2, status=201) - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(main(loop)) - self.assertEqual(len(Mocket.request_list()), 2) + async def main(_loop): + async with aiohttp.ClientSession( + loop=_loop, timeout=self.timeout + ) as session: + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body - @httprettified - def test_httprettish_session(self): - url = "https://httpbin.org/ip" - HTTPretty.register_uri( - HTTPretty.GET, - url, - body=json.dumps(dict(origin="127.0.0.1")), - ) + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 - async def main(_loop): - async with aiohttp.ClientSession(loop=_loop) as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 200 - assert ( - await get_response.text() == '{"origin": "127.0.0.1"}' - ) + loop = asyncio.new_event_loop() + loop.set_debug(True) + loop.run_until_complete(main(loop)) + self.assertEqual(len(Mocket.request_list()), 2) - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(main(loop)) + @httprettified + def test_httprettish_session(self): + url = "https://httpbin.org/ip" + HTTPretty.register_uri( + HTTPretty.GET, + url, + body=json.dumps(dict(origin="127.0.0.1")), + ) + + async def main(_loop): + async with aiohttp.ClientSession( + loop=_loop, timeout=self.timeout + ) as session: + async with session.get(url) as get_response: + assert get_response.status == 200 + assert await get_response.text() == '{"origin": "127.0.0.1"}' + + loop = asyncio.new_event_loop() + loop.set_debug(True) + loop.run_until_complete(main(loop)) diff --git a/tests/tests38/test_http_aiohttp.py b/tests/tests38/test_http_aiohttp.py index 348a7a01..7cee6fec 100644 --- a/tests/tests38/test_http_aiohttp.py +++ b/tests/tests38/test_http_aiohttp.py @@ -2,7 +2,6 @@ from unittest import IsolatedAsyncioTestCase import aiohttp -import async_timeout from mocket.async_mocket import async_mocketize from mocket.mocket import Mocket @@ -11,6 +10,8 @@ class AioHttpEntryTestCase(IsolatedAsyncioTestCase): + timeout = aiohttp.ClientTimeout(total=3) + @async_mocketize async def test_http_session(self): url = "http://httpbin.org/ip" @@ -18,18 +19,16 @@ async def test_http_session(self): Entry.single_register(Entry.GET, url, body=body, status=404) Entry.single_register(Entry.POST, url, body=body * 2, status=201) - async with aiohttp.ClientSession() as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body + async with aiohttp.ClientSession(timeout=self.timeout) as session: + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body - async with async_timeout.timeout(3): - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 - assert Mocket.last_request().method == "POST" - assert Mocket.last_request().body == body * 6 + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 + assert Mocket.last_request().method == "POST" + assert Mocket.last_request().body == body * 6 self.assertEqual(len(Mocket.request_list()), 2) @@ -40,16 +39,14 @@ async def test_https_session(self): Entry.single_register(Entry.GET, url, body=body, status=404) Entry.single_register(Entry.POST, url, body=body * 2, status=201) - async with aiohttp.ClientSession() as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body + async with aiohttp.ClientSession(timeout=self.timeout) as session: + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body - async with async_timeout.timeout(3): - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 self.assertEqual(len(Mocket.request_list()), 2) @@ -62,8 +59,7 @@ async def test_httprettish_session(self): body=json.dumps(dict(origin="127.0.0.1")), ) - async with aiohttp.ClientSession() as session: - async with async_timeout.timeout(3): - async with session.get(url) as get_response: - assert get_response.status == 200 - assert await get_response.text() == '{"origin": "127.0.0.1"}' + async with aiohttp.ClientSession(timeout=self.timeout) as session: + async with session.get(url) as get_response: + assert get_response.status == 200 + assert await get_response.text() == '{"origin": "127.0.0.1"}' From 63120c44526e31e22c16c9d8029a715eef38e145 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Tue, 27 Dec 2022 15:38:02 +0100 Subject: [PATCH 13/21] Refactoring using `event_loop` fixture. --- tests/main/test_http_aiohttp.py | 19 ++++++------------- tests/tests37/test_asyncio.py | 6 ++---- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/tests/main/test_http_aiohttp.py b/tests/main/test_http_aiohttp.py index a98cb370..ab723975 100644 --- a/tests/main/test_http_aiohttp.py +++ b/tests/main/test_http_aiohttp.py @@ -1,4 +1,3 @@ -import asyncio import json from unittest import TestCase @@ -13,7 +12,7 @@ class AioHttpEntryTestCase(TestCase): timeout = aiohttp.ClientTimeout(total=3) @mocketize - def test_http_session(self): + def test_http_session(self, event_loop): url = "http://httpbin.org/ip" body = "asd" * 100 Entry.single_register(Entry.GET, url, body=body, status=404) @@ -33,13 +32,11 @@ async def main(_loop): assert Mocket.last_request().method == "POST" assert Mocket.last_request().body == body * 6 - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(main(loop)) + event_loop.run_until_complete(main(event_loop)) self.assertEqual(len(Mocket.request_list()), 2) @mocketize - def test_https_session(self): + def test_https_session(self, event_loop): url = "https://httpbin.org/ip" body = "asd" * 100 Entry.single_register(Entry.GET, url, body=body, status=404) @@ -57,13 +54,11 @@ async def main(_loop): assert post_response.status == 201 assert await post_response.text() == body * 2 - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(main(loop)) + event_loop.run_until_complete(main(event_loop)) self.assertEqual(len(Mocket.request_list()), 2) @httprettified - def test_httprettish_session(self): + def test_httprettish_session(self, event_loop): url = "https://httpbin.org/ip" HTTPretty.register_uri( HTTPretty.GET, @@ -79,6 +74,4 @@ async def main(_loop): assert get_response.status == 200 assert await get_response.text() == '{"origin": "127.0.0.1"}' - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(main(loop)) + event_loop.run_until_complete(main(event_loop)) diff --git a/tests/tests37/test_asyncio.py b/tests/tests37/test_asyncio.py index 66f8cc9f..72b3a0e8 100644 --- a/tests/tests37/test_asyncio.py +++ b/tests/tests37/test_asyncio.py @@ -14,7 +14,7 @@ class AsyncIoRecordTestCase(TestCase): temp_dir = tempfile.mkdtemp() @mocketize(truesocket_recording_dir=temp_dir) - def test_asyncio_record_replay(self): + def test_asyncio_record_replay(self, event_loop): async def test_asyncio_connection(): reader, writer = await asyncio.open_connection( host="google.com", @@ -33,9 +33,7 @@ async def test_asyncio_connection(): writer.close() await writer.wait_closed() - loop = asyncio.new_event_loop() - loop.set_debug(True) - loop.run_until_complete(test_asyncio_connection()) + event_loop.run_until_complete(test_asyncio_connection()) files = glob.glob(f"{self.temp_dir}/*.json") self.assertEqual(len(files), 1) From 5c718f123517ce02fced64677825d1ff3a4684c5 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Wed, 28 Dec 2022 09:43:58 +0100 Subject: [PATCH 14/21] Refactoring using `tempfile` as a context manager. --- tests/main/test_http.py | 96 ++++++++++++----------- tests/main/test_http_aiohttp.py | 130 +++++++++++++++----------------- tests/main/test_https.py | 31 ++++---- tests/main/test_mocket.py | 6 +- tests/tests37/test_asyncio.py | 52 ++++++------- 5 files changed, 154 insertions(+), 161 deletions(-) diff --git a/tests/main/test_http.py b/tests/main/test_http.py index f1893be0..60a54fbf 100644 --- a/tests/main/test_http.py +++ b/tests/main/test_http.py @@ -13,11 +13,9 @@ import pytest import requests -from mocket import Mocket, mocketize +from mocket import Mocket, Mocketizer, mocketize from mocket.mockhttp import Entry, Response -recording_directory = tempfile.mkdtemp() - class HttpTestCase(TestCase): def assertEqualHeaders(self, first, second, msg=None): @@ -36,57 +34,63 @@ def test_truesendall(self): resp = requests.get(url) self.assertEqual(resp.status_code, 200) - @mocketize(truesocket_recording_dir=recording_directory) def test_truesendall_with_recording(self): - url = "http://httpbin.org/ip" - - urlopen(url) - requests.get(url) - resp = urlopen(url) - self.assertEqual(resp.code, 200) - resp = requests.get(url) - self.assertEqual(resp.status_code, 200) - assert "origin" in resp.json() - - dump_filename = os.path.join( - Mocket.get_truesocket_recording_dir(), Mocket.get_namespace() + ".json" - ) - with io.open(dump_filename) as f: - responses = json.load(f) + with tempfile.TemporaryDirectory() as temp_dir: + with Mocketizer(truesocket_recording_dir=temp_dir): + url = "http://httpbin.org/ip" + + urlopen(url) + requests.get(url) + resp = urlopen(url) + self.assertEqual(resp.code, 200) + resp = requests.get(url) + self.assertEqual(resp.status_code, 200) + assert "origin" in resp.json() + + dump_filename = os.path.join( + Mocket.get_truesocket_recording_dir(), + Mocket.get_namespace() + ".json", + ) + with io.open(dump_filename) as f: + responses = json.load(f) + + self.assertEqual(len(responses["httpbin.org"]["80"].keys()), 2) - self.assertEqual(len(responses["httpbin.org"]["80"].keys()), 2) - - @mocketize(truesocket_recording_dir=recording_directory) def test_truesendall_with_gzip_recording(self): - url = "http://httpbin.org/gzip" + with tempfile.TemporaryDirectory() as temp_dir: + with Mocketizer(truesocket_recording_dir=temp_dir): + url = "http://httpbin.org/gzip" - requests.get(url) - resp = requests.get(url) - self.assertEqual(resp.status_code, 200) + requests.get(url) + resp = requests.get(url) + self.assertEqual(resp.status_code, 200) - dump_filename = os.path.join( - Mocket.get_truesocket_recording_dir(), Mocket.get_namespace() + ".json" - ) - with io.open(dump_filename) as f: - responses = json.load(f) + dump_filename = os.path.join( + Mocket.get_truesocket_recording_dir(), + Mocket.get_namespace() + ".json", + ) + with io.open(dump_filename) as f: + responses = json.load(f) - assert len(responses["httpbin.org"]["80"].keys()) == 1 + assert len(responses["httpbin.org"]["80"].keys()) == 1 - @mocketize(truesocket_recording_dir=recording_directory) def test_truesendall_with_chunk_recording(self): - url = "http://httpbin.org/range/70000?chunk_size=65536" - - requests.get(url) - resp = requests.get(url) - self.assertEqual(resp.status_code, 200) - - dump_filename = os.path.join( - Mocket.get_truesocket_recording_dir(), Mocket.get_namespace() + ".json" - ) - with io.open(dump_filename) as f: - responses = json.load(f) - - assert len(responses["httpbin.org"]["80"].keys()) == 1 + with tempfile.TemporaryDirectory() as temp_dir: + with Mocketizer(truesocket_recording_dir=temp_dir): + url = "http://httpbin.org/range/70000?chunk_size=65536" + + requests.get(url) + resp = requests.get(url) + self.assertEqual(resp.status_code, 200) + + dump_filename = os.path.join( + Mocket.get_truesocket_recording_dir(), + Mocket.get_namespace() + ".json", + ) + with io.open(dump_filename) as f: + responses = json.load(f) + + assert len(responses["httpbin.org"]["80"].keys()) == 1 @mocketize def test_wrongpath_truesendall(self): diff --git a/tests/main/test_http_aiohttp.py b/tests/main/test_http_aiohttp.py index ab723975..b8539ebf 100644 --- a/tests/main/test_http_aiohttp.py +++ b/tests/main/test_http_aiohttp.py @@ -1,5 +1,4 @@ import json -from unittest import TestCase import aiohttp @@ -7,71 +6,66 @@ from mocket.mockhttp import Entry from mocket.plugins.httpretty import HTTPretty, httprettified +timeout = aiohttp.ClientTimeout(total=3) -class AioHttpEntryTestCase(TestCase): - timeout = aiohttp.ClientTimeout(total=3) - - @mocketize - def test_http_session(self, event_loop): - url = "http://httpbin.org/ip" - body = "asd" * 100 - Entry.single_register(Entry.GET, url, body=body, status=404) - Entry.single_register(Entry.POST, url, body=body * 2, status=201) - - async def main(_loop): - async with aiohttp.ClientSession( - loop=_loop, timeout=self.timeout - ) as session: - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body - - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 - assert Mocket.last_request().method == "POST" - assert Mocket.last_request().body == body * 6 - - event_loop.run_until_complete(main(event_loop)) - self.assertEqual(len(Mocket.request_list()), 2) - - @mocketize - def test_https_session(self, event_loop): - url = "https://httpbin.org/ip" - body = "asd" * 100 - Entry.single_register(Entry.GET, url, body=body, status=404) - Entry.single_register(Entry.POST, url, body=body * 2, status=201) - - async def main(_loop): - async with aiohttp.ClientSession( - loop=_loop, timeout=self.timeout - ) as session: - async with session.get(url) as get_response: - assert get_response.status == 404 - assert await get_response.text() == body - - async with session.post(url, data=body * 6) as post_response: - assert post_response.status == 201 - assert await post_response.text() == body * 2 - - event_loop.run_until_complete(main(event_loop)) - self.assertEqual(len(Mocket.request_list()), 2) - - @httprettified - def test_httprettish_session(self, event_loop): - url = "https://httpbin.org/ip" - HTTPretty.register_uri( - HTTPretty.GET, - url, - body=json.dumps(dict(origin="127.0.0.1")), - ) - - async def main(_loop): - async with aiohttp.ClientSession( - loop=_loop, timeout=self.timeout - ) as session: - async with session.get(url) as get_response: - assert get_response.status == 200 - assert await get_response.text() == '{"origin": "127.0.0.1"}' - - event_loop.run_until_complete(main(event_loop)) + +@mocketize +def test_http_session(event_loop): + url = "http://httpbin.org/ip" + body = "asd" * 100 + Entry.single_register(Entry.GET, url, body=body, status=404) + Entry.single_register(Entry.POST, url, body=body * 2, status=201) + + async def main(_loop): + async with aiohttp.ClientSession(loop=_loop, timeout=timeout) as session: + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body + + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 + assert Mocket.last_request().method == "POST" + assert Mocket.last_request().body == body * 6 + + event_loop.run_until_complete(main(event_loop)) + assert len(Mocket.request_list()) == 2 + + +@mocketize +def test_https_session(event_loop): + url = "https://httpbin.org/ip" + body = "asd" * 100 + Entry.single_register(Entry.GET, url, body=body, status=404) + Entry.single_register(Entry.POST, url, body=body * 2, status=201) + + async def main(_loop): + async with aiohttp.ClientSession(loop=_loop, timeout=timeout) as session: + async with session.get(url) as get_response: + assert get_response.status == 404 + assert await get_response.text() == body + + async with session.post(url, data=body * 6) as post_response: + assert post_response.status == 201 + assert await post_response.text() == body * 2 + + event_loop.run_until_complete(main(event_loop)) + assert len(Mocket.request_list()) == 2 + + +@httprettified +def test_httprettish_session(event_loop): + url = "https://httpbin.org/ip" + HTTPretty.register_uri( + HTTPretty.GET, + url, + body=json.dumps(dict(origin="127.0.0.1")), + ) + + async def main(_loop): + async with aiohttp.ClientSession(loop=_loop, timeout=timeout) as session: + async with session.get(url) as get_response: + assert get_response.status == 200 + assert await get_response.text() == '{"origin": "127.0.0.1"}' + + event_loop.run_until_complete(main(event_loop)) diff --git a/tests/main/test_https.py b/tests/main/test_https.py index 0a54e325..10652b5e 100644 --- a/tests/main/test_https.py +++ b/tests/main/test_https.py @@ -42,22 +42,23 @@ def test_json(response): @pytest.mark.skipif('os.getenv("SKIP_TRUE_HTTP", False)') -@mocketize(truesocket_recording_dir=recording_directory) def test_truesendall_with_recording_https(): - url = "https://httpbin.org/ip" - - requests.get(url, headers={"Accept": "application/json"}) - resp = requests.get(url, headers={"Accept": "application/json"}) - assert resp.status_code == 200 - - dump_filename = os.path.join( - Mocket.get_truesocket_recording_dir(), - Mocket.get_namespace() + ".json", - ) - with io.open(dump_filename) as f: - responses = json.load(f) - - assert len(responses["httpbin.org"]["443"].keys()) == 1 + with tempfile.TemporaryDirectory() as temp_dir: + with Mocketizer(truesocket_recording_dir=temp_dir): + url = "https://httpbin.org/ip" + + requests.get(url, headers={"Accept": "application/json"}) + resp = requests.get(url, headers={"Accept": "application/json"}) + assert resp.status_code == 200 + + dump_filename = os.path.join( + Mocket.get_truesocket_recording_dir(), + Mocket.get_namespace() + ".json", + ) + with io.open(dump_filename) as f: + responses = json.load(f) + + assert len(responses["httpbin.org"]["443"].keys()) == 1 @pytest.mark.skipif('os.getenv("SKIP_TRUE_HTTP", False)') diff --git a/tests/main/test_mocket.py b/tests/main/test_mocket.py index 67ad337f..14057b10 100644 --- a/tests/main/test_mocket.py +++ b/tests/main/test_mocket.py @@ -174,13 +174,13 @@ def test_mocketize_outside_a_test_class(): @pytest.fixture -def fixture(): +def two(): return 2 @mocketize -def test_mocketize_with_fixture(fixture): - assert 2 == fixture +def test_mocketize_with_fixture(two): + assert 2 == two @mocketize diff --git a/tests/tests37/test_asyncio.py b/tests/tests37/test_asyncio.py index 72b3a0e8..c90bcb80 100644 --- a/tests/tests37/test_asyncio.py +++ b/tests/tests37/test_asyncio.py @@ -2,45 +2,39 @@ import glob import io import json -import shutil import socket import tempfile -from unittest import TestCase -from mocket.mocket import mocketize +from mocket import Mocketizer -class AsyncIoRecordTestCase(TestCase): - temp_dir = tempfile.mkdtemp() +def test_asyncio_record_replay(event_loop): + async def test_asyncio_connection(): + reader, writer = await asyncio.open_connection( + host="google.com", + port=80, + family=socket.AF_INET, + proto=socket.IPPROTO_TCP, + ssl=None, + server_hostname=None, + ) - @mocketize(truesocket_recording_dir=temp_dir) - def test_asyncio_record_replay(self, event_loop): - async def test_asyncio_connection(): - reader, writer = await asyncio.open_connection( - host="google.com", - port=80, - family=socket.AF_INET, - proto=socket.IPPROTO_TCP, - ssl=None, - server_hostname=None, - ) + buf = "GET / HTTP/1.1\r\nHost: google.com\r\n\r\n" + writer.write(buf.encode()) + await writer.drain() - buf = "GET / HTTP/1.1\r\nHost: google.com\r\n\r\n" - writer.write(buf.encode()) - await writer.drain() + await reader.readline() + writer.close() + await writer.wait_closed() - await reader.readline() - writer.close() - await writer.wait_closed() + with tempfile.TemporaryDirectory() as temp_dir: + with Mocketizer(truesocket_recording_dir=temp_dir): + event_loop.run_until_complete(test_asyncio_connection()) - event_loop.run_until_complete(test_asyncio_connection()) - - files = glob.glob(f"{self.temp_dir}/*.json") - self.assertEqual(len(files), 1) + files = glob.glob(f"{temp_dir}/*.json") + assert len(files) == 1 with io.open(files[0]) as f: responses = json.load(f) - self.assertEqual(len(responses["google.com"]["80"].keys()), 1) - - shutil.rmtree(self.temp_dir) + assert len(responses["google.com"]["80"].keys()) == 1 From b736f854e1bc65a091498102e8102c6db98009a1 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Wed, 28 Dec 2022 16:03:37 +0100 Subject: [PATCH 15/21] Skip those tests and see what happens to the rest. --- Pipfile | 3 ++- mocket/plugins/httpretty/__init__.py | 1 + tests/main/test_http_aiohttp.py | 33 ++++++++++++++++++---------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Pipfile b/Pipfile index 62d66ab2..dd502713 100644 --- a/Pipfile +++ b/Pipfile @@ -11,9 +11,10 @@ httptools = "*" [dev-packages] pre-commit = "*" -pytest = ">4.6" +pytest = "*" pytest-cov = "*" pytest-asyncio = "*" +asgiref = "*" requests = "*" redis = "*" gevent = "*" diff --git a/mocket/plugins/httpretty/__init__.py b/mocket/plugins/httpretty/__init__.py index 79af6aff..5aaebeb1 100644 --- a/mocket/plugins/httpretty/__init__.py +++ b/mocket/plugins/httpretty/__init__.py @@ -118,6 +118,7 @@ def __getattr__(self, name): __all__ = ( "HTTPretty", + "httpretty", "activate", "async_httprettified", "httprettified", diff --git a/tests/main/test_http_aiohttp.py b/tests/main/test_http_aiohttp.py index b8539ebf..05136f56 100644 --- a/tests/main/test_http_aiohttp.py +++ b/tests/main/test_http_aiohttp.py @@ -1,10 +1,13 @@ import json +import sys import aiohttp +import pytest +from asgiref.sync import async_to_sync from mocket.mocket import Mocket, mocketize from mocket.mockhttp import Entry -from mocket.plugins.httpretty import HTTPretty, httprettified +from mocket.plugins.httpretty import httprettified, httpretty timeout = aiohttp.ClientTimeout(total=3) @@ -16,8 +19,9 @@ def test_http_session(event_loop): Entry.single_register(Entry.GET, url, body=body, status=404) Entry.single_register(Entry.POST, url, body=body * 2, status=201) - async def main(_loop): - async with aiohttp.ClientSession(loop=_loop, timeout=timeout) as session: + @async_to_sync + async def perform_aiohttp_transactions(): + async with aiohttp.ClientSession(timeout=timeout) as session: async with session.get(url) as get_response: assert get_response.status == 404 assert await get_response.text() == body @@ -28,10 +32,11 @@ async def main(_loop): assert Mocket.last_request().method == "POST" assert Mocket.last_request().body == body * 6 - event_loop.run_until_complete(main(event_loop)) + perform_aiohttp_transactions() assert len(Mocket.request_list()) == 2 +@pytest.mark.skipif(sys.version_info >= (3, 11), reason="Failing with Python 3.11") @mocketize def test_https_session(event_loop): url = "https://httpbin.org/ip" @@ -39,8 +44,9 @@ def test_https_session(event_loop): Entry.single_register(Entry.GET, url, body=body, status=404) Entry.single_register(Entry.POST, url, body=body * 2, status=201) - async def main(_loop): - async with aiohttp.ClientSession(loop=_loop, timeout=timeout) as session: + @async_to_sync + async def perform_aiohttp_transactions(): + async with aiohttp.ClientSession(timeout=timeout) as session: async with session.get(url) as get_response: assert get_response.status == 404 assert await get_response.text() == body @@ -49,23 +55,26 @@ async def main(_loop): assert post_response.status == 201 assert await post_response.text() == body * 2 - event_loop.run_until_complete(main(event_loop)) + perform_aiohttp_transactions() assert len(Mocket.request_list()) == 2 +@pytest.mark.skipif(sys.version_info >= (3, 11), reason="Failing with Python 3.11") @httprettified def test_httprettish_session(event_loop): url = "https://httpbin.org/ip" - HTTPretty.register_uri( - HTTPretty.GET, + httpretty.register_uri( + httpretty.GET, url, body=json.dumps(dict(origin="127.0.0.1")), ) - async def main(_loop): - async with aiohttp.ClientSession(loop=_loop, timeout=timeout) as session: + @async_to_sync + async def perform_aiohttp_transactions(): + async with aiohttp.ClientSession(timeout=timeout) as session: async with session.get(url) as get_response: assert get_response.status == 200 assert await get_response.text() == '{"origin": "127.0.0.1"}' - event_loop.run_until_complete(main(event_loop)) + perform_aiohttp_transactions() + assert len(httpretty.latest_requests) == 1 From 693137f4f6d66225dc2a98e82cf7eb58cc86ee5c Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 12 Feb 2023 20:41:12 +0100 Subject: [PATCH 16/21] Fix. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 67697f19..2059c8dc 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,11 @@ install-dev-requirements: pip install -U pip - pip install pipenv + pip install pipenv pre-commit install-test-requirements: pipenv install --dev - pipenv run python -c "import pipfile; pf = pipfile.load('Pipfile'); print('\n'.join(package+version for package, version in pf.data['default'].items()))" > requirements.txt + pipenv run python -c "import pipfile; pf = pipfile.load('Pipfile'); print('\n'.join(package+version if version != '*' else package for package, version in pf.data['default'].items()))" > requirements.txt test-python: @echo "Running Python tests" From 60862fe9c0433fd8bfe55321e94f9a9a200f5321 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 12 Feb 2023 20:49:29 +0100 Subject: [PATCH 17/21] Fix. --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index dd502713..382ee72a 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ sure = "*" pook = "*" flake8 = "<7" xxhash = "*" -aiohttp = "*" +aiohttp = ">3.8.3" httpx = "*" pipfile = "*" build = "*" From be5dbc0b13c38f1f4f412f3da7bc7f58132ced8d Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 12 Feb 2023 21:13:33 +0100 Subject: [PATCH 18/21] Fix. --- Makefile | 2 +- Pipfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2059c8dc..7835c805 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ publish: install-test-requirements pipenv run anaconda upload dist/mocket-$(shell python -c 'import mocket; print(mocket.__version__)').tar.gz clean: - rm -rf *.egg-info dist/ + rm -rf *.egg-info dist/ requirements.txt Pipfile.lock find . -type d -name __pycache__ -exec rm -rf {} \; .PHONY: clean publish safetest test setup develop lint-python test-python install-test-requirements install-dev-requirements diff --git a/Pipfile b/Pipfile index 382ee72a..dd502713 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ sure = "*" pook = "*" flake8 = "<7" xxhash = "*" -aiohttp = ">3.8.3" +aiohttp = "*" httpx = "*" pipfile = "*" build = "*" From 4637a518605c87db0bcca25aba2eb3b35f70567d Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 19 Feb 2023 16:16:38 +0100 Subject: [PATCH 19/21] Mark `aiohttp` as failing - first time ever - and add a similar test with `httpx`. --- tests/tests38/test_http_aiohttp.py | 42 +++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/tests/tests38/test_http_aiohttp.py b/tests/tests38/test_http_aiohttp.py index 7cee6fec..3375315f 100644 --- a/tests/tests38/test_http_aiohttp.py +++ b/tests/tests38/test_http_aiohttp.py @@ -2,6 +2,8 @@ from unittest import IsolatedAsyncioTestCase import aiohttp +import httpx +import pytest from mocket.async_mocket import async_mocketize from mocket.mocket import Mocket @@ -11,20 +13,20 @@ class AioHttpEntryTestCase(IsolatedAsyncioTestCase): timeout = aiohttp.ClientTimeout(total=3) + target_url = "http://httpbin.org/ip" @async_mocketize async def test_http_session(self): - url = "http://httpbin.org/ip" body = "asd" * 100 - Entry.single_register(Entry.GET, url, body=body, status=404) - Entry.single_register(Entry.POST, url, body=body * 2, status=201) + Entry.single_register(Entry.GET, self.target_url, body=body, status=404) + Entry.single_register(Entry.POST, self.target_url, body=body * 2, status=201) async with aiohttp.ClientSession(timeout=self.timeout) as session: - async with session.get(url) as get_response: + async with session.get(self.target_url) as get_response: assert get_response.status == 404 assert await get_response.text() == body - async with session.post(url, data=body * 6) as post_response: + async with session.post(self.target_url, data=body * 6) as post_response: assert post_response.status == 201 assert await post_response.text() == body * 2 assert Mocket.last_request().method == "POST" @@ -34,32 +36,46 @@ async def test_http_session(self): @async_mocketize async def test_https_session(self): - url = "https://httpbin.org/ip" body = "asd" * 100 - Entry.single_register(Entry.GET, url, body=body, status=404) - Entry.single_register(Entry.POST, url, body=body * 2, status=201) + Entry.single_register(Entry.GET, self.target_url, body=body, status=404) + Entry.single_register(Entry.POST, self.target_url, body=body * 2, status=201) async with aiohttp.ClientSession(timeout=self.timeout) as session: - async with session.get(url) as get_response: + async with session.get(self.target_url) as get_response: assert get_response.status == 404 assert await get_response.text() == body - async with session.post(url, data=body * 6) as post_response: + async with session.post(self.target_url, data=body * 6) as post_response: assert post_response.status == 201 assert await post_response.text() == body * 2 self.assertEqual(len(Mocket.request_list()), 2) + @pytest.mark.xfail @async_httprettified async def test_httprettish_session(self): - url = "https://httpbin.org/ip" HTTPretty.register_uri( HTTPretty.GET, - url, + self.target_url, body=json.dumps(dict(origin="127.0.0.1")), ) async with aiohttp.ClientSession(timeout=self.timeout) as session: - async with session.get(url) as get_response: + async with session.get(self.target_url) as get_response: assert get_response.status == 200 assert await get_response.text() == '{"origin": "127.0.0.1"}' + + @async_httprettified + async def test_httprettish_httpx_session(self): + expected_response = {"origin": "127.0.0.1"} + + HTTPretty.register_uri( + HTTPretty.GET, + self.target_url, + body=json.dumps(expected_response), + ) + + async with httpx.AsyncClient() as client: + response = await client.get(self.target_url) + assert response.status_code == 200 + assert response.json() == expected_response From ea32fec16f3e99d341fd2615f56170379b24e499 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 19 Feb 2023 16:30:17 +0100 Subject: [PATCH 20/21] Bump version. --- mocket/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mocket/__init__.py b/mocket/__init__.py index 65232052..506067a6 100644 --- a/mocket/__init__.py +++ b/mocket/__init__.py @@ -3,4 +3,4 @@ __all__ = ("async_mocketize", "mocketize", "Mocket", "MocketEntry", "Mocketizer") -__version__ = "3.10.9" +__version__ = "3.11.0" From 7dbe0c522e2c6c7324ba88803980cf8d4a685272 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Sun, 19 Feb 2023 16:34:39 +0100 Subject: [PATCH 21/21] Mark as xfail instead of skip. --- tests/main/test_http_aiohttp.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/main/test_http_aiohttp.py b/tests/main/test_http_aiohttp.py index 05136f56..838e4e24 100644 --- a/tests/main/test_http_aiohttp.py +++ b/tests/main/test_http_aiohttp.py @@ -1,5 +1,4 @@ import json -import sys import aiohttp import pytest @@ -13,7 +12,7 @@ @mocketize -def test_http_session(event_loop): +def test_http_session(): url = "http://httpbin.org/ip" body = "asd" * 100 Entry.single_register(Entry.GET, url, body=body, status=404) @@ -36,9 +35,9 @@ async def perform_aiohttp_transactions(): assert len(Mocket.request_list()) == 2 -@pytest.mark.skipif(sys.version_info >= (3, 11), reason="Failing with Python 3.11") +@pytest.mark.xfail @mocketize -def test_https_session(event_loop): +def test_https_session(): url = "https://httpbin.org/ip" body = "asd" * 100 Entry.single_register(Entry.GET, url, body=body, status=404) @@ -59,9 +58,9 @@ async def perform_aiohttp_transactions(): assert len(Mocket.request_list()) == 2 -@pytest.mark.skipif(sys.version_info >= (3, 11), reason="Failing with Python 3.11") +@pytest.mark.xfail @httprettified -def test_httprettish_session(event_loop): +def test_httprettish_session(): url = "https://httpbin.org/ip" httpretty.register_uri( httpretty.GET,