1
|
|
|
"""APIServer tests.""" |
2
|
1 |
|
import asyncio |
3
|
1 |
|
import json |
4
|
1 |
|
import warnings |
5
|
1 |
|
from datetime import datetime, timezone |
6
|
|
|
# Disable not-grouped imports that conflicts with isort |
7
|
1 |
|
from unittest.mock import AsyncMock, MagicMock |
8
|
1 |
|
from urllib.error import HTTPError, URLError |
9
|
|
|
|
10
|
1 |
|
import pytest |
11
|
1 |
|
from httpx import AsyncClient, RequestError |
12
|
|
|
|
13
|
1 |
|
from kytos.core.api_server import APIServer |
14
|
1 |
|
from kytos.core.napps import rest |
15
|
1 |
|
from kytos.core.rest_api import JSONResponse, Request |
16
|
|
|
|
17
|
|
|
|
18
|
1 |
|
def test_custom_encoder() -> None: |
19
|
|
|
"""Test json custom encoder.""" |
20
|
1 |
|
some_datetime = datetime(year=2022, month=1, day=30) |
21
|
1 |
|
resp = JSONResponse(some_datetime) |
22
|
1 |
|
assert json.loads(resp.body.decode()) == "2022-01-30T00:00:00" |
23
|
|
|
|
24
|
|
|
|
25
|
|
|
# pylint: disable=protected-access |
26
|
1 |
|
class TestAPIServer: |
27
|
|
|
"""Test the class APIServer.""" |
28
|
|
|
|
29
|
1 |
|
def setup_method(self): |
30
|
|
|
"""Instantiate a APIServer.""" |
31
|
1 |
|
self.api_server = APIServer() |
32
|
1 |
|
self.app = self.api_server.app |
33
|
1 |
|
self.napps_manager = MagicMock() |
34
|
1 |
|
self.api_server.server = MagicMock() |
35
|
1 |
|
self.api_server.napps_manager = self.napps_manager |
36
|
1 |
|
self.api_server.napps_dir = 'napps_dir' |
37
|
1 |
|
self.api_server.web_uir_dir = 'web_uir_dir' |
38
|
1 |
|
self.api_server.start_api() |
39
|
1 |
|
self.api_server.start_web_ui_static_files = MagicMock() |
40
|
1 |
|
self.api_server.start_web_ui() |
41
|
1 |
|
base_url = "http://127.0.0.1/api/kytos/core/" |
42
|
1 |
|
self.client = AsyncClient(app=self.app, base_url=base_url) |
43
|
|
|
|
44
|
1 |
|
def test_deprecation_warning(self): |
45
|
|
|
"""Deprecated method should suggest @rest decorator.""" |
46
|
1 |
|
with warnings.catch_warnings(record=True) as wrngs: |
47
|
1 |
|
warnings.simplefilter("always") # trigger all warnings |
48
|
1 |
|
self.api_server.register_rest_endpoint( |
49
|
|
|
'rule', lambda x: x, ['POST']) |
50
|
1 |
|
assert 1 == len(wrngs) |
51
|
1 |
|
warning = wrngs[0] |
52
|
1 |
|
assert warning.category == DeprecationWarning |
53
|
1 |
|
assert '@rest' in str(warning.message) |
54
|
|
|
|
55
|
1 |
|
async def test_serve(self): |
56
|
|
|
"""Test serve method.""" |
57
|
1 |
|
self.api_server.server = AsyncMock() |
58
|
1 |
|
await self.api_server.serve() |
59
|
1 |
|
self.api_server.server.serve.assert_called_with() |
60
|
|
|
|
61
|
1 |
|
async def test_status_api(self): |
62
|
|
|
"""Test status_api method.""" |
63
|
1 |
|
started_at = datetime.fromtimestamp(1.0, timezone.utc) |
64
|
1 |
|
now = datetime.now(timezone.utc) |
65
|
1 |
|
uptime = now - started_at |
66
|
1 |
|
self.napps_manager._controller.uptime.return_value = uptime |
67
|
1 |
|
self.napps_manager._controller.started_at = started_at |
68
|
1 |
|
response = await self.client.get("status/") |
69
|
1 |
|
assert response.status_code == 200, response.text |
70
|
1 |
|
response = response.json() |
71
|
1 |
|
expected = { |
72
|
|
|
"response": "running", |
73
|
|
|
"started_at": started_at.isoformat(), |
74
|
|
|
"uptime_seconds": uptime.seconds, |
75
|
|
|
} |
76
|
1 |
|
assert response == expected |
77
|
|
|
|
78
|
1 |
|
def test_stop(self): |
79
|
|
|
"""Test stop method.""" |
80
|
1 |
|
self.api_server.stop() |
81
|
1 |
|
assert self.api_server.server.should_exit |
82
|
|
|
|
83
|
1 |
|
@pytest.mark.parametrize("file_exist", [True, False]) |
84
|
1 |
|
async def test_static_web_ui(self, monkeypatch, file_exist): |
85
|
|
|
"""Test static_web_ui method to success case.""" |
86
|
1 |
|
monkeypatch.setattr("os.path.exists", lambda x: file_exist) |
87
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.FileResponse", |
88
|
|
|
lambda x: JSONResponse({})) |
89
|
|
|
|
90
|
1 |
|
endpoint = f"/ui/kytos/napp/{self.api_server.napps_dir}" |
91
|
1 |
|
client = AsyncClient(app=self.app, base_url="http://127.0.0.1") |
92
|
1 |
|
response = await client.get(endpoint) |
93
|
1 |
|
expected_status_code = {True: 200, False: 404} |
94
|
1 |
|
assert expected_status_code[file_exist] == response.status_code |
95
|
|
|
|
96
|
1 |
|
async def test_get_ui_components(self, monkeypatch): |
97
|
|
|
"""Test get_ui_components method.""" |
98
|
1 |
|
mock_glob = MagicMock() |
99
|
1 |
|
mock_glob.return_value = ['napps_dir/*/*/ui/*/*.kytos'] |
100
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.glob", mock_glob) |
101
|
1 |
|
expected_json = [{'name': '*-*-*-*', 'url': 'ui/*/*/*/*.kytos'}] |
102
|
|
|
|
103
|
1 |
|
endpoint = "/ui/k-toolbar/" |
104
|
1 |
|
client = AsyncClient(app=self.app, base_url="http://127.0.0.1") |
105
|
1 |
|
resp = await client.get(endpoint) |
106
|
1 |
|
assert resp.status_code == 200 |
107
|
1 |
|
assert resp.json() == expected_json |
108
|
|
|
|
109
|
1 |
|
@pytest.mark.parametrize("file_exist", [True, False]) |
110
|
1 |
|
async def test_web_ui(self, monkeypatch, file_exist): |
111
|
|
|
"""Test web_ui method.""" |
112
|
1 |
|
monkeypatch.setattr("os.path.exists", lambda x: file_exist) |
113
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.FileResponse", |
114
|
|
|
lambda x: JSONResponse({})) |
115
|
|
|
|
116
|
1 |
|
client = AsyncClient(app=self.app, base_url="http://127.0.0.1") |
117
|
1 |
|
coros = [client.get("/index.html"), client.get("/")] |
118
|
1 |
|
coros = asyncio.gather(*coros) |
119
|
1 |
|
responses = await coros |
120
|
1 |
|
expected_status_code = {True: 200, False: 404} |
121
|
1 |
|
for response in responses: |
122
|
1 |
|
assert expected_status_code[file_exist] == response.status_code |
123
|
|
|
|
124
|
1 |
|
def test_unzip_backup_web_ui(self, monkeypatch) -> None: |
125
|
|
|
"""Test _unzip_backup_web_ui.""" |
126
|
1 |
|
mock_log, mock_shutil = MagicMock(), MagicMock() |
127
|
1 |
|
mock_zipfile, zipfile = MagicMock(), MagicMock() |
128
|
1 |
|
zip_data, mock_mkdir = MagicMock(), MagicMock() |
129
|
1 |
|
zipfile.__enter__.return_value = zip_data |
130
|
1 |
|
zip_data.testzip.return_value = None |
131
|
1 |
|
mock_zipfile.return_value = zipfile |
132
|
|
|
|
133
|
1 |
|
monkeypatch.setattr("zipfile.ZipFile", mock_zipfile) |
134
|
1 |
|
monkeypatch.setattr("os.path.exists", lambda x: True) |
135
|
1 |
|
monkeypatch.setattr("os.mkdir", mock_mkdir) |
136
|
1 |
|
monkeypatch.setattr("shutil.move", mock_shutil) |
137
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.LOG", mock_log) |
138
|
1 |
|
package, uri = "/tmp/file", "http://localhost/some_file.zip" |
139
|
1 |
|
self.api_server._unzip_backup_web_ui(package, uri) |
140
|
1 |
|
assert mock_mkdir.call_count == 1 |
141
|
1 |
|
assert mock_shutil.call_count == 1 |
142
|
1 |
|
assert zip_data.extractall.call_count == 1 |
143
|
1 |
|
assert zip_data.close.call_count == 1 |
144
|
1 |
|
assert mock_log.info.call_count == 1 |
145
|
|
|
|
146
|
1 |
|
def test_unzip_backup_web_ui_error(self, monkeypatch) -> None: |
147
|
|
|
"""Test _unzip_backup_web_ui error.""" |
148
|
1 |
|
mock_log, mock_zipfile, zipdata = MagicMock(), MagicMock(), MagicMock() |
149
|
1 |
|
mock_zipfile.__enter__.return_value = zipdata |
150
|
1 |
|
monkeypatch.setattr("zipfile.ZipFile", mock_zipfile) |
151
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.LOG", mock_log) |
152
|
1 |
|
package, uri = "/tmp/file", "http://localhost/some_file.zip" |
153
|
1 |
|
with pytest.raises(ValueError) as exc: |
154
|
1 |
|
self.api_server._unzip_backup_web_ui(package, uri) |
155
|
1 |
|
assert "is corrupted" in str(exc) |
156
|
1 |
|
assert mock_log.error.call_count == 1 |
157
|
|
|
|
158
|
1 |
|
def test_fetch_latest_ui_tag(self, monkeypatch) -> None: |
159
|
|
|
"""Test fetch lastest ui.""" |
160
|
1 |
|
mock_get, mock_res, ver = MagicMock(), MagicMock(), "X.Y.Z" |
161
|
1 |
|
mock_get.return_value = mock_res |
162
|
1 |
|
mock_res.json.return_value = {"tag_name": ver} |
163
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.httpx.get", mock_get) |
164
|
1 |
|
version = self.api_server._fetch_latest_ui_tag() |
165
|
1 |
|
assert version == ver |
166
|
1 |
|
url = 'https://api.github.com/repos/kytos-ng/' \ |
167
|
|
|
'ui/releases/latest' |
168
|
1 |
|
mock_get.assert_called_with(url, timeout=10) |
169
|
|
|
|
170
|
1 |
|
def test_fetch_latest_ui_tag_fallback(self, monkeypatch) -> None: |
171
|
|
|
"""Test fetch lastest ui fallback tag.""" |
172
|
1 |
|
mock_get, mock_log = MagicMock(), MagicMock() |
173
|
1 |
|
mock_get.side_effect = RequestError("some error") |
174
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.httpx.get", mock_get) |
175
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.LOG", mock_log) |
176
|
1 |
|
version = self.api_server._fetch_latest_ui_tag() |
177
|
1 |
|
assert version == "2022.3.0" |
178
|
1 |
|
assert mock_log.warning.call_count == 1 |
179
|
|
|
|
180
|
1 |
|
async def test_update_web_ui(self, monkeypatch): |
181
|
|
|
"""Test update_web_ui method.""" |
182
|
1 |
|
mock_log, mock_urlretrieve = MagicMock(), MagicMock() |
183
|
1 |
|
mock_urlretrieve.return_value = ["/tmp/file"] |
184
|
1 |
|
monkeypatch.setattr("os.path.exists", lambda x: False) |
185
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.LOG", mock_log) |
186
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.urlretrieve", |
187
|
|
|
mock_urlretrieve) |
188
|
|
|
|
189
|
1 |
|
version = "2022.3.0" |
190
|
1 |
|
mock_fetch = MagicMock(return_value=version) |
191
|
1 |
|
self.api_server._fetch_latest_ui_tag = mock_fetch |
192
|
1 |
|
self.api_server._unzip_backup_web_ui = MagicMock() |
193
|
1 |
|
response = await self.client.post("web/update") |
194
|
1 |
|
assert response.status_code == 200 |
195
|
1 |
|
assert response.json() == "Web UI was updated" |
196
|
|
|
|
197
|
1 |
|
assert self.api_server._fetch_latest_ui_tag.call_count == 1 |
198
|
1 |
|
assert self.api_server._unzip_backup_web_ui.call_count == 1 |
199
|
1 |
|
assert mock_log.info.call_count == 2 |
200
|
|
|
|
201
|
1 |
|
url = 'https://github.com/kytos-ng/ui/releases/' + \ |
202
|
|
|
f'download/{version}/latest.zip' |
203
|
1 |
|
mock_urlretrieve.assert_called_with(url) |
204
|
|
|
|
205
|
1 |
|
@pytest.mark.parametrize( |
206
|
|
|
"err_name,exp_msg", |
207
|
|
|
[ |
208
|
|
|
("HTTPError", "Uri not found"), |
209
|
|
|
("URLError", "Error accessing"), |
210
|
|
|
], |
211
|
|
|
) |
212
|
1 |
|
async def test_update_web_ui_error(self, monkeypatch, err_name, exp_msg): |
213
|
|
|
"""Test update_web_ui method error. |
214
|
|
|
|
215
|
|
|
HTTPError had issues when being initialized in the decorator args, |
216
|
|
|
so it it's being instantiated dynamically in the test body, see: |
217
|
|
|
https://bugs.python.org/issue45955 |
218
|
|
|
""" |
219
|
1 |
|
mock_log, mock_urlretrieve = MagicMock(), MagicMock() |
220
|
1 |
|
http_error = HTTPError("errorx", 500, "errorx", {}, None) |
221
|
1 |
|
errs = {"HTTPError": http_error, "URLError": URLError("some reason")} |
222
|
1 |
|
mock_urlretrieve.side_effect = errs[err_name] |
223
|
1 |
|
monkeypatch.setattr("os.path.exists", lambda x: False) |
224
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.LOG", mock_log) |
225
|
1 |
|
monkeypatch.setattr("kytos.core.api_server.urlretrieve", |
226
|
|
|
mock_urlretrieve) |
227
|
|
|
|
228
|
1 |
|
version = "2022.3.0" |
229
|
1 |
|
self.api_server._unzip_backup_web_ui = MagicMock() |
230
|
1 |
|
response = await self.client.post(f"web/update/{version}") |
231
|
1 |
|
assert response.status_code == 500 |
232
|
1 |
|
assert exp_msg in response.json() |
233
|
1 |
|
assert mock_log.error.call_count == 1 |
234
|
|
|
|
235
|
1 |
|
url = 'https://github.com/kytos-ng/ui/releases/' + \ |
236
|
|
|
f'download/{version}/latest.zip' |
237
|
1 |
|
mock_urlretrieve.assert_called_with(url) |
238
|
|
|
|
239
|
1 |
|
async def test_enable_napp__error_not_installed(self): |
240
|
|
|
"""Test _enable_napp method error case when napp is not installed.""" |
241
|
1 |
|
self.napps_manager.is_installed.return_value = False |
242
|
1 |
|
resp = await self.client.get("/napps/kytos/napp/enable") |
243
|
1 |
|
assert resp.status_code == 400 |
244
|
1 |
|
assert resp.json() == {"response": "not installed"} |
245
|
|
|
|
246
|
1 |
|
async def test_enable_napp__error_not_enabling(self): |
247
|
|
|
"""Test _enable_napp method error case when napp is not enabling.""" |
248
|
1 |
|
self.napps_manager.is_installed.return_value = True |
249
|
1 |
|
self.napps_manager.is_enabled.side_effect = [False, False] |
250
|
1 |
|
resp = await self.client.get("napps/kytos/napp/enable") |
251
|
1 |
|
assert resp.status_code == 500 |
252
|
1 |
|
assert resp.json() == {"response": "error"} |
253
|
|
|
|
254
|
1 |
|
async def test_enable_napp__success(self): |
255
|
|
|
"""Test _enable_napp method success case.""" |
256
|
1 |
|
self.napps_manager.is_installed.return_value = True |
257
|
1 |
|
self.napps_manager.is_enabled.side_effect = [False, True] |
258
|
1 |
|
resp = await self.client.get("napps/kytos/napp/enable") |
259
|
1 |
|
assert resp.status_code == 200 |
260
|
1 |
|
assert resp.json() == {"response": "enabled"} |
261
|
|
|
|
262
|
1 |
|
async def test_disable_napp__error_not_installed(self): |
263
|
|
|
"""Test _disable_napp method error case when napp is not installed.""" |
264
|
1 |
|
self.napps_manager.is_installed.return_value = False |
265
|
1 |
|
resp = await self.client.get("napps/kytos/napp/disable") |
266
|
1 |
|
assert resp.status_code == 400 |
267
|
1 |
|
assert resp.json() == {"response": "not installed"} |
268
|
|
|
|
269
|
1 |
|
async def test_disable_napp__error_not_enabling(self): |
270
|
|
|
"""Test _disable_napp method error case when napp is not enabling.""" |
271
|
1 |
|
self.napps_manager.is_installed.return_value = True |
272
|
1 |
|
self.napps_manager.is_enabled.side_effect = [True, True] |
273
|
1 |
|
resp = await self.client.get("napps/kytos/napp/disable") |
274
|
1 |
|
assert resp.status_code == 500 |
275
|
1 |
|
assert resp.json() == {"response": "error"} |
276
|
|
|
|
277
|
1 |
|
async def test_disable_napp__success(self): |
278
|
|
|
"""Test _disable_napp method success case.""" |
279
|
1 |
|
self.napps_manager.is_installed.return_value = True |
280
|
1 |
|
self.napps_manager.is_enabled.side_effect = [True, False] |
281
|
1 |
|
resp = await self.client.get("napps/kytos/napp/disable") |
282
|
1 |
|
assert resp.status_code == 200 |
283
|
1 |
|
assert resp.json() == {"response": "disabled"} |
284
|
|
|
|
285
|
1 |
|
async def test_install_napp__error_not_installing(self): |
286
|
|
|
"""Test _install_napp method error case when napp is not installing.""" |
287
|
1 |
|
self.napps_manager.is_installed.return_value = False |
288
|
1 |
|
self.napps_manager.install.return_value = False |
289
|
1 |
|
resp = await self.client.get("napps/kytos/napp/install") |
290
|
1 |
|
assert resp.status_code == 500 |
291
|
1 |
|
assert resp.json() == {"response": "error"} |
292
|
|
|
|
293
|
1 |
|
async def test_install_napp__http_error(self): |
294
|
|
|
"""Test _install_napp method to http error case.""" |
295
|
1 |
|
self.napps_manager.is_installed.return_value = False |
296
|
1 |
|
self.napps_manager.install.side_effect = HTTPError('url', 123, 'msg', |
297
|
|
|
'hdrs', MagicMock()) |
298
|
|
|
|
299
|
1 |
|
resp = await self.client.get("napps/kytos/napp/install") |
300
|
1 |
|
assert resp.status_code == 123 |
301
|
1 |
|
assert resp.json() == {"response": "error"} |
302
|
1 |
|
self.napps_manager.install.side_effect = None |
303
|
|
|
|
304
|
1 |
|
async def test_install_napp__success_is_installed(self): |
305
|
|
|
"""Test _install_napp method success case when napp is installed.""" |
306
|
1 |
|
self.napps_manager.is_installed.return_value = True |
307
|
1 |
|
resp = await self.client.get("napps/kytos/napp/install") |
308
|
1 |
|
assert resp.status_code == 200 |
309
|
1 |
|
assert resp.json() == {"response": "installed"} |
310
|
|
|
|
311
|
1 |
|
async def test_install_napp__success(self): |
312
|
|
|
"""Test _install_napp method success case.""" |
313
|
1 |
|
self.napps_manager.is_installed.return_value = False |
314
|
1 |
|
self.napps_manager.install.return_value = True |
315
|
1 |
|
resp = await self.client.get("napps/kytos/napp/install") |
316
|
1 |
|
assert resp.status_code == 200 |
317
|
1 |
|
assert resp.json() == {"response": "installed"} |
318
|
|
|
|
319
|
1 |
|
async def test_uninstall_napp__error_not_uninstalling(self): |
320
|
|
|
"""Test _uninstall_napp method error case when napp is not |
321
|
|
|
uninstalling. |
322
|
|
|
""" |
323
|
1 |
|
self.napps_manager.is_installed.return_value = True |
324
|
1 |
|
self.napps_manager.uninstall.return_value = False |
325
|
1 |
|
resp = await self.client.get("napps/kytos/napp/uninstall") |
326
|
1 |
|
assert resp.status_code == 500 |
327
|
1 |
|
assert resp.json() == {"response": "error"} |
328
|
|
|
|
329
|
1 |
|
async def test_uninstall_napp__success_not_installed(self): |
330
|
|
|
"""Test _uninstall_napp method success case when napp is not |
331
|
|
|
installed. |
332
|
|
|
""" |
333
|
1 |
|
self.napps_manager.is_installed.return_value = False |
334
|
1 |
|
resp = await self.client.get("napps/kytos/napp/uninstall") |
335
|
1 |
|
assert resp.status_code == 200 |
336
|
1 |
|
assert resp.json() == {"response": "uninstalled"} |
337
|
|
|
|
338
|
1 |
|
async def test_uninstall_napp__success(self): |
339
|
|
|
"""Test _uninstall_napp method success case.""" |
340
|
1 |
|
self.napps_manager.is_installed.return_value = True |
341
|
1 |
|
self.napps_manager.uninstall.return_value = True |
342
|
1 |
|
resp = await self.client.get("napps/kytos/napp/uninstall") |
343
|
1 |
|
assert resp.status_code == 200 |
344
|
1 |
|
assert resp.json() == {"response": "uninstalled"} |
345
|
|
|
|
346
|
1 |
|
async def test_list_enabled_napps(self): |
347
|
|
|
"""Test _list_enabled_napps method.""" |
348
|
1 |
|
napp = MagicMock() |
349
|
1 |
|
napp.username = "kytos" |
350
|
1 |
|
napp.name = "name" |
351
|
1 |
|
self.napps_manager.get_enabled_napps.return_value = [napp] |
352
|
1 |
|
resp = await self.client.get("napps_enabled") |
353
|
1 |
|
assert resp.status_code == 200 |
354
|
1 |
|
assert resp.json() == {"napps": [["kytos", "name"]]} |
355
|
|
|
|
356
|
1 |
|
async def test_list_installed_napps(self): |
357
|
|
|
"""Test _list_installed_napps method.""" |
358
|
1 |
|
napp = MagicMock() |
359
|
1 |
|
napp.username = "kytos" |
360
|
1 |
|
napp.name = "name" |
361
|
1 |
|
self.napps_manager.get_installed_napps.return_value = [napp] |
362
|
1 |
|
resp = await self.client.get("napps_installed") |
363
|
1 |
|
assert resp.status_code == 200 |
364
|
1 |
|
assert resp.json() == {"napps": [["kytos", "name"]]} |
365
|
|
|
|
366
|
1 |
|
async def test_get_napp_metadata__not_installed(self): |
367
|
|
|
"""Test _get_napp_metadata method to error case when napp is not |
368
|
|
|
installed.""" |
369
|
1 |
|
self.napps_manager.is_installed.return_value = False |
370
|
1 |
|
resp = await self.client.get("napps/kytos/napp/metadata/version") |
371
|
1 |
|
assert resp.status_code == 400 |
372
|
1 |
|
assert resp.json() == "NApp is not installed." |
373
|
|
|
|
374
|
1 |
|
async def test_get_napp_metadata__invalid_key(self): |
375
|
|
|
"""Test _get_napp_metadata method to error case when key is invalid.""" |
376
|
1 |
|
self.napps_manager.is_installed.return_value = True |
377
|
1 |
|
resp = await self.client.get("napps/kytos/napp/metadata/any") |
378
|
1 |
|
assert resp.status_code == 400 |
379
|
1 |
|
assert resp.json() == "Invalid key." |
380
|
|
|
|
381
|
1 |
|
async def test_get_napp_metadata(self): |
382
|
|
|
"""Test _get_napp_metadata method.""" |
383
|
1 |
|
value = "1.0" |
384
|
1 |
|
self.napps_manager.is_installed.return_value = True |
385
|
1 |
|
self.napps_manager.get_napp_metadata.return_value = value |
386
|
1 |
|
resp = await self.client.get("napps/kytos/napp/metadata/version") |
387
|
1 |
|
assert resp.status_code == 200 |
388
|
1 |
|
assert resp.json() == {"version": value} |
389
|
|
|
|
390
|
|
|
|
391
|
1 |
|
class RESTNApp: # pylint: disable=too-few-public-methods |
392
|
|
|
"""Bare minimum for the decorator to work. Not a functional NApp.""" |
393
|
|
|
|
394
|
1 |
|
def __init__(self): |
395
|
1 |
|
self.username = 'test' |
396
|
1 |
|
self.name = 'MyNApp' |
397
|
1 |
|
self.napp_id = 'test/MyNApp' |
398
|
|
|
|
399
|
|
|
|
400
|
1 |
|
class TestAPIDecorator: |
401
|
|
|
"""Test suite for @rest decorator.""" |
402
|
|
|
|
403
|
1 |
|
async def test_sync_route(self, controller, api_client): |
404
|
|
|
"""Test rest decorator sync route.""" |
405
|
1 |
|
class MyNApp(RESTNApp): |
406
|
|
|
"""API decorator example usage.""" |
407
|
|
|
|
408
|
1 |
|
@rest("some_route/", methods=["POST"]) |
409
|
1 |
|
def some_endpoint(self, _request: Request) -> JSONResponse: |
410
|
|
|
"""Some endpoint.""" |
411
|
1 |
|
return JSONResponse({"some_response": "some_value"}) |
412
|
|
|
|
413
|
1 |
|
napp = MyNApp() |
414
|
1 |
|
controller.api_server.register_napp_endpoints(napp) |
415
|
|
|
|
416
|
1 |
|
routes = ["test/MyNApp/some_route/", "test/MyNApp/some_route"] |
417
|
1 |
|
coros = [api_client.post(route) for route in routes] |
418
|
1 |
|
responses = await asyncio.gather(*coros) |
419
|
1 |
|
for resp in responses: |
420
|
1 |
|
assert resp.status_code == 200 |
421
|
1 |
|
assert resp.json() == {"some_response": "some_value"} |
422
|
|
|
|
423
|
1 |
|
resp = await api_client.get("test/MyNApp/some_route/") |
424
|
1 |
|
assert resp.status_code == 405 |
425
|
|
|
|
426
|
1 |
|
async def test_async_route(self, controller, api_client): |
427
|
|
|
"""Test rest decorator async route.""" |
428
|
1 |
|
class MyNApp(RESTNApp): |
429
|
|
|
"""API decorator example usage.""" |
430
|
|
|
|
431
|
1 |
|
@rest("some_route/", methods=["POST"]) |
432
|
1 |
|
async def some_endpoint(self, _request: Request) -> JSONResponse: |
433
|
|
|
"""Some endpoint.""" |
434
|
1 |
|
await asyncio.sleep(0) |
435
|
1 |
|
return JSONResponse({}) |
436
|
|
|
|
437
|
1 |
|
napp = MyNApp() |
438
|
1 |
|
controller.api_server.register_napp_endpoints(napp) |
439
|
1 |
|
resp = await api_client.post("test/MyNApp/some_route/") |
440
|
1 |
|
assert resp.status_code == 200 |
441
|
1 |
|
assert resp.json() == {} |
442
|
|
|
|
443
|
1 |
|
async def test_route_ordering(self, controller, api_client): |
444
|
|
|
"""Test rest decorator route ordering.""" |
445
|
1 |
|
class MyNApp(RESTNApp): |
446
|
|
|
"""API decorator example usage.""" |
447
|
|
|
|
448
|
1 |
|
@rest("some_route/hello", methods=["POST"]) |
449
|
1 |
|
async def hello(self, _request: Request) -> JSONResponse: |
450
|
|
|
"""Hello endpoint.""" |
451
|
1 |
|
await asyncio.sleep(0) |
452
|
1 |
|
return JSONResponse({"hello": "world"}) |
453
|
|
|
|
454
|
1 |
|
@rest("some_route/{some_arg}", methods=["POST"]) |
455
|
1 |
|
async def ahello_arg(self, request: Request) -> JSONResponse: |
456
|
|
|
"""Hello endpoint.""" |
457
|
1 |
|
arg = request.path_params["some_arg"] |
458
|
1 |
|
await asyncio.sleep(0) |
459
|
1 |
|
return JSONResponse({"hello": arg}) |
460
|
|
|
|
461
|
1 |
|
napp = MyNApp() |
462
|
1 |
|
controller.api_server.register_napp_endpoints(napp) |
463
|
1 |
|
coros = [ |
464
|
|
|
api_client.post("test/MyNApp/some_route/abc"), |
465
|
|
|
api_client.post("test/MyNApp/some_route/hello") |
466
|
|
|
] |
467
|
1 |
|
responses = await asyncio.gather(*coros) |
468
|
1 |
|
assert responses[0].status_code == 200 |
469
|
1 |
|
assert responses[1].status_code == 200 |
470
|
1 |
|
assert responses[0].json() == {"hello": "abc"} |
471
|
1 |
|
assert responses[1].json() == {"hello": "world"} |
472
|
|
|
|
473
|
1 |
|
async def test_remove_napp_endpoints(self, controller, api_client): |
474
|
|
|
"""Test remove napp endpoints.""" |
475
|
1 |
|
class MyNApp(RESTNApp): # pylint: disable=too-few-public-methods |
476
|
|
|
"""API decorator example usage.""" |
477
|
|
|
|
478
|
1 |
|
@rest("some_route/", methods=["POST"]) |
479
|
1 |
|
def some_endpoint(self, _request: Request) -> JSONResponse: |
480
|
|
|
"""Some endpoint.""" |
481
|
1 |
|
return JSONResponse({}) |
482
|
|
|
|
483
|
1 |
|
napp = MyNApp() |
484
|
1 |
|
controller.api_server.register_napp_endpoints(napp) |
485
|
1 |
|
resp = await api_client.post("test/MyNApp/some_route/") |
486
|
1 |
|
assert resp.status_code == 200 |
487
|
|
|
|
488
|
1 |
|
controller.api_server.remove_napp_endpoints(napp) |
489
|
1 |
|
resp = await api_client.post("test/MyNApp/some_route/") |
490
|
1 |
|
assert resp.status_code == 404 |
491
|
|
|
|
492
|
1 |
|
async def test_route_from_classmethod(self, controller, api_client): |
493
|
|
|
"""Test route from classmethod.""" |
494
|
1 |
|
class MyNApp(RESTNApp): # pylint: disable=too-few-public-methods |
495
|
|
|
"""API decorator example usage.""" |
496
|
|
|
|
497
|
1 |
|
@rest("some_route/", methods=["POST"]) |
498
|
1 |
|
@classmethod |
499
|
1 |
|
def some_endpoint(cls, _request: Request) -> JSONResponse: |
500
|
|
|
"""Some endpoint.""" |
501
|
1 |
|
return JSONResponse({}) |
502
|
|
|
|
503
|
1 |
|
napp = MyNApp() |
504
|
1 |
|
controller.api_server.register_napp_endpoints(napp) |
505
|
1 |
|
resp = await api_client.post("test/MyNApp/some_route/") |
506
|
1 |
|
assert resp.status_code == 200 |
507
|
|
|
|
508
|
1 |
|
async def test_route_from_staticmethod(self, controller, api_client): |
509
|
|
|
"""Test route from staticmethod.""" |
510
|
1 |
|
class MyNApp(RESTNApp): # pylint: disable=too-few-public-methods |
511
|
|
|
"""API decorator example usage.""" |
512
|
|
|
|
513
|
1 |
|
@rest("some_route/", methods=["POST"]) |
514
|
1 |
|
@staticmethod |
515
|
1 |
|
def some_endpoint(_request: Request) -> JSONResponse: |
516
|
|
|
"""Some endpoint.""" |
517
|
1 |
|
return JSONResponse({}) |
518
|
|
|
|
519
|
1 |
|
napp = MyNApp() |
520
|
1 |
|
controller.api_server.register_napp_endpoints(napp) |
521
|
1 |
|
resp = await api_client.post("test/MyNApp/some_route/") |
522
|
1 |
|
assert resp.status_code == 200 |
523
|
|
|
|
524
|
1 |
|
def test_get_route_index(self) -> None: |
525
|
|
|
"""Test _get_next_route_index.""" |
526
|
1 |
|
index = APIServer._get_next_route_index() |
527
|
|
|
assert APIServer._get_next_route_index() == index + 1 |
528
|
|
|
|