Skip to content

Commit a1afb33

Browse files
committed
refactoring test_handlers
1 parent b54c785 commit a1afb33

File tree

5 files changed

+1011
-306
lines changed

5 files changed

+1011
-306
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
# pylint: disable=unused-variable
4+
# pylint: disable=too-many-arguments
5+
6+
from collections.abc import AsyncIterator
7+
8+
import httpx
9+
import pytest
10+
from fastapi import FastAPI
11+
from fixtures.fake_services import PushServicesCallable, ServiceInRegistryInfoDict
12+
from httpx._transports.asgi import ASGITransport
13+
14+
15+
@pytest.fixture
16+
async def client(app: FastAPI) -> AsyncIterator[httpx.AsyncClient]:
17+
# - Needed for app to trigger start/stop event handlers
18+
# - Prefer this client instead of fastapi.testclient.TestClient
19+
async with httpx.AsyncClient(
20+
app=app,
21+
base_url="http://director.testserver.io",
22+
headers={"Content-Type": "application/json"},
23+
) as client:
24+
assert isinstance(client._transport, ASGITransport)
25+
yield client
26+
27+
28+
@pytest.fixture
29+
async def created_services(
30+
push_services: PushServicesCallable,
31+
) -> list[ServiceInRegistryInfoDict]:
32+
return await push_services(
33+
number_of_computational_services=3, number_of_interactive_services=2
34+
)
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
# pylint: disable=unused-variable
4+
# pylint: disable=too-many-arguments
5+
6+
import uuid
7+
8+
import httpx
9+
import pytest
10+
from aioresponses.core import CallbackResult, aioresponses
11+
from fastapi import status
12+
13+
14+
def _assert_response_and_unwrap_envelope(got: httpx.Response):
15+
assert got.encoding == "application/json"
16+
17+
body = got.json()
18+
assert isinstance(body, dict)
19+
assert "data" in body or "error" in body
20+
return body.get("data"), body.get("error")
21+
22+
23+
@pytest.mark.skip(
24+
reason="docker_swarm fixture is a session fixture making it bad running together with other tests that require a swarm"
25+
)
26+
async def test_running_services_post_and_delete_no_swarm(
27+
configure_swarm_stack_name,
28+
client: httpx.AsyncClient,
29+
push_services,
30+
user_id,
31+
project_id,
32+
api_version_prefix,
33+
):
34+
params = {
35+
"user_id": "None",
36+
"project_id": "None",
37+
"service_uuid": "sdlfkj4",
38+
"service_key": "simcore/services/comp/some-key",
39+
}
40+
resp = await client.post(
41+
f"/{api_version_prefix}/running_interactive_services", params=params
42+
)
43+
data = resp.json()
44+
assert resp.status_code == 500, data
45+
46+
47+
@pytest.mark.parametrize(
48+
"save_state, expected_save_state_call", [(True, True), (False, False), (None, True)]
49+
)
50+
async def test_running_services_post_and_delete(
51+
configure_swarm_stack_name,
52+
client: httpx.AsyncClient,
53+
push_services,
54+
docker_swarm,
55+
user_id,
56+
project_id,
57+
api_version_prefix,
58+
save_state: bool | None,
59+
expected_save_state_call: bool,
60+
mocker,
61+
):
62+
params = {}
63+
resp = await client.post(
64+
f"/{api_version_prefix}/running_interactive_services", params=params
65+
)
66+
assert resp.status_code == status.HTTP_400_BAD_REQUEST
67+
68+
params = {
69+
"user_id": "None",
70+
"project_id": "None",
71+
"service_uuid": "sdlfkj4",
72+
"service_key": "None",
73+
"service_tag": "None", # optional
74+
"service_basepath": "None", # optional
75+
}
76+
resp = await client.post(
77+
f"/{api_version_prefix}/running_interactive_services", params=params
78+
)
79+
data = resp.json()
80+
assert resp.status_code == status.HTTP_400_BAD_REQUEST, data
81+
82+
params["service_key"] = "simcore/services/comp/somfunkyname-nhsd"
83+
params["service_tag"] = "1.2.3"
84+
resp = await client.post(
85+
f"/{api_version_prefix}/running_interactive_services", params=params
86+
)
87+
data = resp.json()
88+
assert resp.status_code == status.HTTP_404_NOT_FOUND, data
89+
90+
created_services = await push_services(0, 2)
91+
assert len(created_services) == 2
92+
for created_service in created_services:
93+
service_description = created_service["service_description"]
94+
params["user_id"] = user_id
95+
params["project_id"] = project_id
96+
params["service_key"] = service_description["key"]
97+
params["service_tag"] = service_description["version"]
98+
service_port = created_service["internal_port"]
99+
service_entry_point = created_service["entry_point"]
100+
params["service_basepath"] = "/i/am/a/basepath"
101+
params["service_uuid"] = str(uuid.uuid4())
102+
# start the service
103+
resp = await client.post(
104+
f"/{api_version_prefix}/running_interactive_services", params=params
105+
)
106+
assert resp.status_code == status.HTTP_201_CREATED
107+
assert resp.encoding == "application/json"
108+
running_service_enveloped = resp.json()
109+
assert isinstance(running_service_enveloped["data"], dict)
110+
assert all(
111+
k in running_service_enveloped["data"]
112+
for k in [
113+
"service_uuid",
114+
"service_key",
115+
"service_version",
116+
"published_port",
117+
"entry_point",
118+
"service_host",
119+
"service_port",
120+
"service_basepath",
121+
]
122+
)
123+
assert (
124+
running_service_enveloped["data"]["service_uuid"] == params["service_uuid"]
125+
)
126+
assert running_service_enveloped["data"]["service_key"] == params["service_key"]
127+
assert (
128+
running_service_enveloped["data"]["service_version"]
129+
== params["service_tag"]
130+
)
131+
assert running_service_enveloped["data"]["service_port"] == service_port
132+
service_published_port = running_service_enveloped["data"]["published_port"]
133+
assert not service_published_port
134+
assert service_entry_point == running_service_enveloped["data"]["entry_point"]
135+
service_host = running_service_enveloped["data"]["service_host"]
136+
assert service_host == f"test_{params['service_uuid']}"
137+
service_basepath = running_service_enveloped["data"]["service_basepath"]
138+
assert service_basepath == params["service_basepath"]
139+
140+
# get the service
141+
resp = await client.request(
142+
"GET",
143+
f"/{api_version_prefix}/running_interactive_services/{params['service_uuid']}",
144+
)
145+
assert resp.status_code == status.HTTP_200_OK
146+
text = resp.text
147+
assert resp.encoding == "application/json", f"Got {text=}"
148+
running_service_enveloped = resp.json()
149+
assert isinstance(running_service_enveloped["data"], dict)
150+
assert all(
151+
k in running_service_enveloped["data"]
152+
for k in [
153+
"service_uuid",
154+
"service_key",
155+
"service_version",
156+
"published_port",
157+
"entry_point",
158+
]
159+
)
160+
assert (
161+
running_service_enveloped["data"]["service_uuid"] == params["service_uuid"]
162+
)
163+
assert running_service_enveloped["data"]["service_key"] == params["service_key"]
164+
assert (
165+
running_service_enveloped["data"]["service_version"]
166+
== params["service_tag"]
167+
)
168+
assert (
169+
running_service_enveloped["data"]["published_port"]
170+
== service_published_port
171+
)
172+
assert running_service_enveloped["data"]["entry_point"] == service_entry_point
173+
assert running_service_enveloped["data"]["service_host"] == service_host
174+
assert running_service_enveloped["data"]["service_port"] == service_port
175+
assert running_service_enveloped["data"]["service_basepath"] == service_basepath
176+
177+
# stop the service
178+
query_params = {}
179+
if save_state:
180+
query_params.update({"save_state": "true" if save_state else "false"})
181+
182+
mocked_save_state_cb = mocker.MagicMock(
183+
return_value=CallbackResult(status=200, payload={})
184+
)
185+
PASSTHROUGH_REQUESTS_PREFIXES = [
186+
"http://127.0.0.1",
187+
"http://localhost",
188+
"unix://", # docker engine
189+
"ws://", # websockets
190+
]
191+
with aioresponses(passthrough=PASSTHROUGH_REQUESTS_PREFIXES) as mock:
192+
193+
# POST /http://service_host:service_port service_basepath/state -------------------------------------------------
194+
mock.post(
195+
f"http://{service_host}:{service_port}{service_basepath}/state",
196+
status=200,
197+
callback=mocked_save_state_cb,
198+
)
199+
resp = await client.delete(
200+
f"/{api_version_prefix}/running_interactive_services/{params['service_uuid']}",
201+
params=query_params,
202+
)
203+
if expected_save_state_call:
204+
mocked_save_state_cb.assert_called_once()
205+
206+
text = resp.text
207+
assert resp.status_code == status.HTTP_204_NO_CONTENT, text
208+
assert resp.encoding == "application/json"
209+
data = resp.json()
210+
assert data is None
211+
212+
213+
async def test_running_interactive_services_list_get(
214+
client: httpx.AsyncClient, push_services, docker_swarm
215+
):
216+
"""Test case for running_interactive_services_list_get
217+
218+
Returns a list of interactive services
219+
"""
220+
user_ids = ["first_user_id", "second_user_id"]
221+
project_ids = ["first_project_id", "second_project_id", "third_project_id"]
222+
# prepare services
223+
NUM_SERVICES = 1
224+
created_services = await push_services(0, NUM_SERVICES)
225+
assert len(created_services) == NUM_SERVICES
226+
# start the services
227+
for user_id in user_ids:
228+
for project_id in project_ids:
229+
for created_service in created_services:
230+
service_description = created_service["service_description"]
231+
params = {}
232+
params["user_id"] = user_id
233+
params["project_id"] = project_id
234+
params["service_key"] = service_description["key"]
235+
params["service_tag"] = service_description["version"]
236+
params["service_uuid"] = str(uuid.uuid4())
237+
# start the service
238+
resp = await client.post(
239+
"/v0/running_interactive_services", params=params
240+
)
241+
assert resp.status_code == 201
242+
# get the list of services
243+
for user_id in user_ids:
244+
for project_id in project_ids:
245+
params = {}
246+
# list by user_id
247+
params["user_id"] = user_id
248+
response = await client.get(
249+
"/v0/running_interactive_services", params=params
250+
)
251+
assert (
252+
response.status_code == status.HTTP_200_OK
253+
), f"Response body is : {response.text}"
254+
data, error = _assert_response_and_unwrap_envelope(response.json())
255+
assert data
256+
assert not error
257+
services_list = data
258+
assert len(services_list) == len(project_ids) * NUM_SERVICES
259+
# list by user_id and project_id
260+
params["project_id"] = project_id
261+
response = await client.get(
262+
"/v0/running_interactive_services", params=params
263+
)
264+
assert (
265+
response.status_code == status.HTTP_200_OK
266+
), f"Response body is : {response.text}"
267+
data, error = _assert_response_and_unwrap_envelope(response.json())
268+
assert data
269+
assert not error
270+
services_list = data
271+
assert len(services_list) == NUM_SERVICES
272+
# list by project_id
273+
params = {}
274+
params["project_id"] = project_id
275+
response = await client.get(
276+
"/v0/running_interactive_services", params=params
277+
)
278+
assert (
279+
response.status_code == status.HTTP_200_OK
280+
), f"Response body is : {response.text}"
281+
data, error = _assert_response_and_unwrap_envelope(response.json())
282+
assert data
283+
assert not error
284+
services_list = data
285+
assert len(services_list) == len(user_ids) * NUM_SERVICES

0 commit comments

Comments
 (0)