Skip to content

Commit 2f8c4b2

Browse files
committed
@sanderegg review: creates aioresponse_mocker and used to mock catalog service responses
1 parent d8055be commit 2f8c4b2

File tree

2 files changed

+83
-74
lines changed

2 files changed

+83
-74
lines changed

packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py

+68-46
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
# pylint: disable=unused-variable
4+
15
import re
26
from pathlib import Path
3-
from typing import Dict, List
7+
from typing import Any, Dict, List
48

59
import pytest
6-
from aioresponses import aioresponses
10+
from aioresponses import aioresponses as AioResponsesMock
711
from aioresponses.core import CallbackResult
812
from models_library.projects_state import RunningState
913
from yarl import URL
1014

15+
# WARNING: any request done through the client will go through aioresponses. It is
16+
# unfortunate but that means any valid request (like calling the test server) prefix must be set as passthrough.
17+
# Other than that it seems to behave nicely
18+
PASSTHROUGH_REQUESTS_PREFIXES = ["http://127.0.0.1", "ws://"]
19+
20+
1121
# The adjacency list is defined as a dictionary with the key to the node and its list of successors
1222
FULL_PROJECT_PIPELINE_ADJACENCY: Dict[str, List[str]] = {
1323
"62bca361-8594-48c8-875e-b8577e868aec": [
@@ -21,7 +31,7 @@
2131
"e83a359a-1efe-41d3-83aa-a285afbfaf12": [],
2232
}
2333

24-
FULL_PROJECT_NODE_STATES: Dict[str, Dict[str, str]] = {
34+
FULL_PROJECT_NODE_STATES: Dict[str, Dict[str, Any]] = {
2535
"62bca361-8594-48c8-875e-b8577e868aec": {"modified": True, "dependencies": []},
2636
"e0d7a1a5-0700-42c7-b033-97f72ac4a5cd": {
2737
"modified": True,
@@ -46,6 +56,25 @@
4656
}
4757

4858

59+
@pytest.fixture
60+
def aioresponses_mocker() -> AioResponsesMock:
61+
"""Generick aioresponses mock
62+
63+
SEE https://github.com/pnuckowski/aioresponses
64+
65+
Usage
66+
67+
async def test_this(aioresponses_mocker):
68+
aioresponses_mocker.get("https://foo.io")
69+
70+
async with aiohttp.ClientSession() as session:
71+
async with session.get("https://foo.aio") as response:
72+
assert response.status == 200
73+
"""
74+
with AioResponsesMock(passthrough=PASSTHROUGH_REQUESTS_PREFIXES) as mock:
75+
yield mock
76+
77+
4978
def creation_cb(url, **kwargs) -> CallbackResult:
5079

5180
assert "json" in kwargs, f"missing body in call to {url}"
@@ -113,14 +142,10 @@ def get_computation_cb(url, **kwargs) -> CallbackResult:
113142

114143

115144
@pytest.fixture
116-
async def director_v2_service_mock() -> aioresponses:
117-
118-
"""uses aioresponses to mock all calls of an aiohttpclient
119-
WARNING: any request done through the client will go through aioresponses. It is
120-
unfortunate but that means any valid request (like calling the test server) prefix must be set as passthrough.
121-
Other than that it seems to behave nicely
122-
"""
123-
PASSTHROUGH_REQUESTS_PREFIXES = ["http://127.0.0.1", "ws://"]
145+
async def director_v2_service_mock(
146+
aioresponses_mocker: AioResponsesMock,
147+
) -> AioResponsesMock:
148+
"""mocks responses of director-v2"""
124149
create_computation_pattern = re.compile(
125150
r"^http://[a-z\-_]*director-v2:[0-9]+/v2/computations$"
126151
)
@@ -132,37 +157,33 @@ async def director_v2_service_mock() -> aioresponses:
132157
r"^http://[a-z\-_]*director-v2:[0-9]+/v2/computations/.*:stop$"
133158
)
134159
delete_computation_pattern = get_computation_pattern
135-
with aioresponses(passthrough=PASSTHROUGH_REQUESTS_PREFIXES) as mock:
136-
mock.post(
137-
create_computation_pattern,
138-
callback=creation_cb,
139-
repeat=True,
140-
)
141-
mock.post(
142-
stop_computation_pattern,
143-
status=204,
144-
repeat=True,
145-
)
146-
mock.get(
147-
get_computation_pattern,
148-
status=202,
149-
callback=get_computation_cb,
150-
repeat=True,
151-
)
152-
mock.delete(delete_computation_pattern, status=204, repeat=True)
153160

154-
yield mock
161+
aioresponses_mocker.post(
162+
create_computation_pattern,
163+
callback=creation_cb,
164+
repeat=True,
165+
)
166+
aioresponses_mocker.post(
167+
stop_computation_pattern,
168+
status=204,
169+
repeat=True,
170+
)
171+
aioresponses_mocker.get(
172+
get_computation_pattern,
173+
status=202,
174+
callback=get_computation_cb,
175+
repeat=True,
176+
)
177+
aioresponses_mocker.delete(delete_computation_pattern, status=204, repeat=True)
155178

179+
return aioresponses_mocker
156180

157-
@pytest.fixture
158-
async def storage_v0_service_mock() -> aioresponses:
159181

160-
"""uses aioresponses to mock all calls of an aiohttpclient
161-
WARNING: any request done through the client will go through aioresponses. It is
162-
unfortunate but that means any valid request (like calling the test server) prefix must be set as passthrough.
163-
Other than that it seems to behave nicely
164-
"""
165-
PASSTHROUGH_REQUESTS_PREFIXES = ["http://127.0.0.1", "ws://"]
182+
@pytest.fixture
183+
async def storage_v0_service_mock(
184+
aioresponses_mocker: AioResponsesMock,
185+
) -> AioResponsesMock:
186+
"""mocks responses of storage API"""
166187

167188
def get_download_link_cb(url: URL, **kwargs) -> CallbackResult:
168189
file_id = url.path.rsplit("/files/")[1]
@@ -179,11 +200,12 @@ def get_download_link_cb(url: URL, **kwargs) -> CallbackResult:
179200
r"^http://[a-z\-_]*storage:[0-9]+/v0/locations.*$"
180201
)
181202

182-
with aioresponses(passthrough=PASSTHROUGH_REQUESTS_PREFIXES) as mock:
183-
mock.get(get_download_link_pattern, callback=get_download_link_cb, repeat=True)
184-
mock.get(
185-
get_locations_link_pattern,
186-
status=200,
187-
payload={"data": [{"name": "simcore.s3", "id": "0"}]},
188-
)
189-
yield mock
203+
aioresponses_mocker.get(
204+
get_download_link_pattern, callback=get_download_link_cb, repeat=True
205+
)
206+
aioresponses_mocker.get(
207+
get_locations_link_pattern,
208+
status=200,
209+
payload={"data": [{"name": "simcore.s3", "id": "0"}]},
210+
)
211+
return aioresponses_mocker

services/web/server/tests/unit/with_dbs/04/test_catalog_api.py

+15-28
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
# pylint:disable=unused-argument
33
# pylint:disable=redefined-outer-name
44

5+
import re
56
from copy import deepcopy
67

78
import pytest
89
from aiohttp import web
910
from pytest_simcore.helpers.utils_assert import assert_status
10-
from pytest_simcore.helpers.utils_login import LoggedUser
1111
from simcore_service_webserver.application import (
1212
create_safe_application,
1313
setup_catalog,
@@ -18,6 +18,7 @@
1818
setup_security,
1919
setup_session,
2020
)
21+
from simcore_service_webserver.catalog_client import KCATALOG_ORIGIN
2122
from simcore_service_webserver.db_models import UserRole
2223

2324

@@ -49,36 +50,18 @@ def client(loop, app_cfg, aiohttp_client, postgres_db):
4950

5051

5152
@pytest.fixture
52-
def patch_app_client_session(client, mocker):
53-
async def create_200_empty_response(*args, **kwargs):
54-
# Mocks aiohttp.ClientResponse
55-
# https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientResponse
56-
resp = mocker.MagicMock(name="SuccessfulEmptyResponse")
53+
def mock_catalog_service_api_responses(client, aioresponses_mocker):
54+
origin = client.app[KCATALOG_ORIGIN]
5755

58-
resp.json = mocker.AsyncMock(return_value={})
56+
url_pattern = re.compile(f"^{origin}+/.*$")
5957

60-
p = mocker.PropertyMock(return_value=200)
61-
type(resp).status = p
62-
return resp
63-
64-
context_mock = mocker.Mock(name="Session Context Manager")
65-
context_mock.__aenter__ = mocker.AsyncMock(
66-
name="QueryResponse", side_effect=create_200_empty_response
67-
)
68-
context_mock.__aexit__ = mocker.AsyncMock(name="Exit", return_value=False)
69-
70-
client_session = mocker.Mock()
71-
client_session.get.return_value = context_mock
72-
client_session.post.return_value = context_mock
73-
client_session.request.return_value = context_mock
74-
75-
mocker.patch(
76-
"simcore_service_webserver.catalog_client.get_client_session",
77-
return_value=client_session,
78-
)
58+
aioresponses_mocker.get(url_pattern, payload={"data": {}})
59+
aioresponses_mocker.post(url_pattern, payload={"data": {}})
60+
aioresponses_mocker.put(url_pattern, payload={"data": {}})
61+
aioresponses_mocker.patch(url_pattern, payload={"data": {}})
62+
aioresponses_mocker.delete(url_pattern)
7963

8064

81-
# TODO: with different user roles, i.e. access rights
8265
@pytest.mark.parametrize(
8366
"user_role,expected",
8467
[
@@ -89,7 +72,11 @@ async def create_200_empty_response(*args, **kwargs):
8972
],
9073
)
9174
async def test_dag_entrypoints(
92-
client, logged_user, api_version_prefix, patch_app_client_session, expected
75+
client,
76+
logged_user,
77+
api_version_prefix,
78+
mock_catalog_service_api_responses,
79+
expected,
9380
):
9481
vx = api_version_prefix
9582

0 commit comments

Comments
 (0)