Passed
Pull Request — master (#375)
by Vinicius
08:19
created

tests.unit.test_core.test_api_server   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 517
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 368
dl 0
loc 517
rs 6.96
c 0
b 0
f 0
ccs 353
cts 353
cp 1
wmc 53

1 Function

Rating   Name   Duplication   Size   Complexity  
A test_custom_encoder() 0 5 1

40 Methods

Rating   Name   Duplication   Size   Complexity  
A TestAPIServer.setup_method() 0 14 1
A TestAPIServer.test_deprecation_warning() 0 10 3
A TestAPIServer.test_serve() 0 5 1
A TestAPIServer.test_enable_napp__success() 0 7 1
A TestAPIServer.test_update_web_ui_error() 0 33 2
A TestAPIServer.test_get_napp_metadata() 0 8 1
A TestAPIDecorator.test_get_route_index() 0 4 1
A TestAPIServer.test_fetch_latest_ui_tag() 0 11 1
A TestAPIServer.test_fetch_latest_ui_tag_fallback() 0 9 1
A TestAPIServer.test_install_napp__http_error() 0 10 1
A TestAPIServer.test_uninstall_napp__success_not_installed() 0 8 1
A TestAPIServer.test_status_api() 0 5 1
A TestAPIServer.test_disable_napp__error_not_enabling() 0 7 1
A TestAPIServer.test_web_ui() 0 14 4
A TestAPIServer.test_get_ui_components() 0 12 1
A TestAPIDecorator.test_route_ordering() 0 29 1
A TestAPIServer.test_enable_napp__error_not_installed() 0 6 1
A TestAPIDecorator.test_route_from_classmethod() 0 15 1
A TestAPIDecorator.test_sync_route() 0 22 2
A TestAPIServer.test_install_napp__error_not_installing() 0 7 1
A TestAPIServer.test_install_napp__success() 0 7 1
A TestAPIDecorator.test_remove_napp_endpoints() 0 18 1
A TestAPIServer.test_install_napp__success_is_installed() 0 6 1
A TestAPIServer.test_static_web_ui() 0 12 3
A TestAPIServer.test_list_enabled_napps() 0 9 1
A TestAPIDecorator.test_route_from_staticmethod() 0 15 1
A RESTNApp.__init__() 0 4 1
A TestAPIServer.test_get_napp_metadata__not_installed() 0 7 1
A TestAPIServer.test_get_napp_metadata__invalid_key() 0 6 1
A TestAPIServer.test_list_installed_napps() 0 9 1
A TestAPIServer.test_unzip_backup_web_ui_error() 0 11 2
A TestAPIDecorator.test_async_route() 0 16 1
A TestAPIServer.test_disable_napp__success() 0 7 1
A TestAPIServer.test_disable_napp__error_not_installed() 0 6 1
A TestAPIServer.test_enable_napp__error_not_enabling() 0 7 1
A TestAPIServer.test_unzip_backup_web_ui() 0 21 2
A TestAPIServer.test_uninstall_napp__error_not_uninstalling() 0 9 1
A TestAPIServer.test_uninstall_napp__success() 0 7 1
A TestAPIServer.test_update_web_ui() 0 24 2
A TestAPIServer.test_stop() 0 4 1

How to fix   Complexity   

Complexity

Complex classes like tests.unit.test_core.test_api_server often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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