TestAPIServer.setup_method()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 14
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 13
nop 1
dl 0
loc 14
rs 9.75
c 0
b 0
f 0
ccs 13
cts 13
cp 1
crap 1
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