|
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, headers: 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
|
|
|
|