from __future__ import annotations import unittest from unittest.mock import patch from os import chdir from pathlib import Path from typing import Optional from pydantic import Field from uuid import UUID, uuid4 from time import time import json print(__name__) print(__package__) from src.pyrestresource import ( register_rest_rootpoint, RestResourceBase, rsrc_verb, RestRequestParams_GET, RestRequestParams_POST, RestRequestParams_Dict_GET, T_SupportedRESTFields, ) from pprint import pprint testdir_path = Path(__file__).parent.resolve() chdir(testdir_path.parent.resolve()) # to allow mock-ing, all the tested classes are in a function def init_classes(): class Info(RestResourceBase): version: str api_version: str class Patch(RestResourceBase): uuid: UUID = Field(default_factory=uuid4, primary_key=True) shortname: str name: Optional[str] = None description: Optional[str] = None class Profile(RestResourceBase): uuid: UUID = Field(default_factory=uuid4, primary_key=True) shortname: str name: Optional[str] = None description: Optional[str] = None class Game(RestResourceBase): uuid: UUID = Field(default_factory=uuid4, primary_key=True) shortname: str name: Optional[str] = None description: Optional[str] = None profiles: dict[UUID, Profile] = {} patchs: dict[UUID, Patch] = {} Patch_1 = Patch(uuid="cee1e870-65fa-11ee-8c99-0242ac120002", shortname="testPatch1") Patch_2 = Patch(uuid="d385a1d2-65fa-11ee-8c99-0242ac120002", shortname="testPatch2") class User(RestResourceBase): uuid: UUID = Field(default_factory=uuid4, primary_key=True) name: str secret: str = Field(..., exclude=True) User1 = User( uuid="8da57a3c-661f-11ee-8c99-0242ac120002", name="chacha", secret="la blanquette est bonne", ) ext_patchs: dict[UUID, Patch] = {} class Patch2(RestResourceBase): uuid: UUID = Field(default_factory=uuid4, primary_key=True) shortname: str name: Optional[str] = None description: Optional[str] = None @register_rest_rootpoint class RootApp(RestResourceBase): testValueRoot: float = 3.14 info: Info = Info(version="0.0.1", api_version="0.0.2") games: dict[UUID, Game] = { UUID("9b0381d4-65f6-11ee-8c99-0242ac120002"): Game( uuid="9b0381d4-65f6-11ee-8c99-0242ac120002", shortname="testGame", patchs={Patch_1.uuid: Patch_1}, profiles={ UUID("aee1e870-65fa-11ee-8c99-0242ac120002"): Profile( uuid="aee1e870-65fa-11ee-8c99-0242ac120002", shortname="testprofile", ) }, ) } patchs: dict[UUID, Patch] = {Patch_1.uuid: Patch_1, Patch_2.uuid: Patch_2} users: dict[UUID, User] = {User1.uuid: User1} patchs2: dict[UUID, Patch2] = {} # this add the classes to globals to allow using them later on # => this is only for uinit-testing purpose and is not needed in real use globals()[Info.__name__] = Info globals()[Game.__name__] = Game globals()[User.__name__] = User globals()[Profile.__name__] = Profile globals()[Patch.__name__] = Patch globals()[Patch2.__name__] = Patch2 globals()[RootApp.__name__] = RootApp class Test_RestAPI_GET(unittest.TestCase): def setUp(self) -> None: chdir(testdir_path.parent.resolve()) init_classes() self.testapp = RootApp() def test_get_root(self): result = self.testapp.process_request("/", rsrc_verb.GET) self.assertEqual(result, '{"testValueRoot": 3.14}') def test_get_root__multiple_slash(self): result = self.testapp.process_request("/////", rsrc_verb.GET) self.assertEqual(result, '{"testValueRoot": 3.14}') result = self.testapp.process_request("////", rsrc_verb.GET) self.assertEqual(result, '{"testValueRoot": 3.14}') def test_get_root__nested_value(self): result = self.testapp.process_request("/testValueRoot", rsrc_verb.GET) self.assertEqual(result, "3.14") def test_get_root__nested_value__trailing_slash(self): result = self.testapp.process_request("/testValueRoot/", rsrc_verb.GET) self.assertEqual(result, "3.14") result = self.testapp.process_request("/testValueRoot//", rsrc_verb.GET) self.assertEqual(result, "3.14") result = self.testapp.process_request("/testValueRoot///", rsrc_verb.GET) self.assertEqual(result, "3.14") def test_get_root__nested_value__multiple_slash(self): result = self.testapp.process_request("//testValueRoot", rsrc_verb.GET) self.assertEqual(result, "3.14") result = self.testapp.process_request("///testValueRoot", rsrc_verb.GET) self.assertEqual(result, "3.14") def test_get_version(self): result = self.testapp.process_request("/info", rsrc_verb.GET) self.assertEqual(result, '{"version": "0.0.1", "api_version": "0.0.2"}') def test_get_version__trailing_slash(self): result = self.testapp.process_request("/info/", rsrc_verb.GET) self.assertEqual(result, '{"version": "0.0.1", "api_version": "0.0.2"}') result = self.testapp.process_request("/info//", rsrc_verb.GET) self.assertEqual(result, '{"version": "0.0.1", "api_version": "0.0.2"}') result = self.testapp.process_request("/info///", rsrc_verb.GET) self.assertEqual(result, '{"version": "0.0.1", "api_version": "0.0.2"}') def test_get_version__multiple_slash(self): result = self.testapp.process_request("//info", rsrc_verb.GET) self.assertEqual(result, '{"version": "0.0.1", "api_version": "0.0.2"}') result = self.testapp.process_request("///info", rsrc_verb.GET) self.assertEqual(result, '{"version": "0.0.1", "api_version": "0.0.2"}') def test_get_version__nested_value(self): result = self.testapp.process_request("/info/api_version", rsrc_verb.GET) self.assertEqual(result, '"0.0.2"') result = self.testapp.process_request("/info/version", rsrc_verb.GET) self.assertEqual(result, '"0.0.1"') def test_get_dict_games(self): result = self.testapp.process_request("/games", rsrc_verb.GET) self.assertEqual(result, '["9b0381d4-65f6-11ee-8c99-0242ac120002"]') def test_get_dict_patchs(self): result = self.testapp.process_request("/patchs", rsrc_verb.GET) self.assertEqual( result, '["cee1e870-65fa-11ee-8c99-0242ac120002", "d385a1d2-65fa-11ee-8c99-0242ac120002"]', ) def test_get_dict_patch_element(self): result = self.testapp.process_request("/patchs/cee1e870-65fa-11ee-8c99-0242ac120002", rsrc_verb.GET) self.assertEqual( result, '{"uuid": "cee1e870-65fa-11ee-8c99-0242ac120002", "shortname": "testPatch1", "name": null, "description": null}', ) def test_get_dict_game_element(self): result = self.testapp.process_request("/games/9b0381d4-65f6-11ee-8c99-0242ac120002", rsrc_verb.GET) expected = '{"uuid": "9b0381d4-65f6-11ee-8c99-0242ac120002", "shortname": "testGame", "name": null, "description": null}' self.assertEqual(result, expected) def test_get_dict_game_element__nested_value(self): result = self.testapp.process_request("/games/9b0381d4-65f6-11ee-8c99-0242ac120002/shortname", rsrc_verb.GET) expected = '"testGame"' self.assertEqual(result, expected) def test_get_dict_game_element__nested_value2(self): result = self.testapp.process_request("/games/9b0381d4-65f6-11ee-8c99-0242ac120002/uuid", rsrc_verb.GET) expected = '"9b0381d4-65f6-11ee-8c99-0242ac120002"' self.assertEqual(result, expected) def test_get_nested_dict_games_patchs(self): result = self.testapp.process_request("/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs", rsrc_verb.GET) self.assertEqual(result, '["cee1e870-65fa-11ee-8c99-0242ac120002"]') def test_get_nested_dict_games_patch_element(self): result = self.testapp.process_request( "/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e870-65fa-11ee-8c99-0242ac120002", rsrc_verb.GET, ) expected = '{"uuid": "cee1e870-65fa-11ee-8c99-0242ac120002", "shortname": "testPatch1", "name": null, "description": null}' self.assertEqual(result, expected) def test_get_nested_dict_games_patch_element__nested_value(self): result = self.testapp.process_request( "/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e870-65fa-11ee-8c99-0242ac120002/uuid", rsrc_verb.GET, ) self.assertEqual(result, '"cee1e870-65fa-11ee-8c99-0242ac120002"') def test_get_dict_game_element__API_nested(self): result = self.testapp.process_request("/games/9b0381d4-65f6-11ee-8c99-0242ac120002?API_nested=True", rsrc_verb.GET) expected = '{"uuid": "9b0381d4-65f6-11ee-8c99-0242ac120002", "shortname": "testGame", "name": null, "description": null}' self.assertEqual(result, expected) def test_get_dict_users(self): result = self.testapp.process_request("/users", rsrc_verb.GET) self.assertEqual(result, '["8da57a3c-661f-11ee-8c99-0242ac120002"]') def test_get_dict_user_element(self): result = self.testapp.process_request("/users/8da57a3c-661f-11ee-8c99-0242ac120002", rsrc_verb.GET) self.assertEqual( result, '{"uuid": "8da57a3c-661f-11ee-8c99-0242ac120002", "name": "chacha"}', "no secret seen", ) def test_get_dict_user_element2(self): result = self.testapp.process_request("/users/8da57a3c-661f-11ee-8c99-0242ac120002?API_nested=True", rsrc_verb.GET) self.assertEqual( result, '{"uuid": "8da57a3c-661f-11ee-8c99-0242ac120002", "name": "chacha"}', "no secret seen", ) def test_get_dict_user_element__nested_value(self): result = self.testapp.process_request("/users/8da57a3c-661f-11ee-8c99-0242ac120002/name", rsrc_verb.GET) self.assertEqual(result, '"chacha"') def test_get_dict_user_element__nested_value__forbiden(self): with self.assertRaises(RuntimeError): # TODO: custom exception self.testapp.process_request("/users/8da57a3c-661f-11ee-8c99-0242ac120002/secret", rsrc_verb.GET) def test_get_dict_user_element__nested_value__forbiden2(self): with self.assertRaises(RuntimeError): # TODO: custom exception self.testapp.process_request( "/users/8da57a3c-661f-11ee-8c99-0242ac120002/secret?API_nested=True", rsrc_verb.GET, ) class Test_RestAPI_PUT(unittest.TestCase): def setUp(self) -> None: chdir(testdir_path.parent.resolve()) init_classes() self.testapp = RootApp() def test_put_info(self): self.testapp.process_request("/info", rsrc_verb.PUT, '{"version": "1.2.3", "api_version": "3.2.1"}') result = self.testapp.process_request("/info", rsrc_verb.GET) self.assertEqual(result, '{"version": "1.2.3", "api_version": "3.2.1"}') def test_put_dict_user_nested_value(self): self.testapp.process_request( "/users/8da57a3c-661f-11ee-8c99-0242ac120002/name", rsrc_verb.PUT, '"chacha2"', ) result = self.testapp.process_request("/users/8da57a3c-661f-11ee-8c99-0242ac120002/name", rsrc_verb.GET) self.assertEqual(result, '"chacha2"') def test_put_user_nested_value__forbiden(self): with self.assertRaises(RuntimeError): # TODO: custom exception self.testapp.process_request( "/users/8da57a3c-661f-11ee-8c99-0242ac120002/secret", rsrc_verb.PUT, '"test"', ) def test_put_dict_user_element(self): self.testapp.process_request( "/users/8da57a3c-661f-11ee-8c99-0242ac120002", rsrc_verb.PUT, '{"name": "testUser4", "secret": "test5"}', ) result = self.testapp.process_request("/users", rsrc_verb.GET) expected = '["8da57a3c-661f-11ee-8c99-0242ac120002"]' self.assertEqual(result, expected) result = self.testapp.process_request("/users/8da57a3c-661f-11ee-8c99-0242ac120002", rsrc_verb.GET) expected = '{"uuid": "8da57a3c-661f-11ee-8c99-0242ac120002", "name": "testUser4"}' self.assertEqual(result, expected) def test_put_dict_patch__nested(self): self.testapp.process_request( "/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e870-65fa-11ee-8c99-0242ac120002", rsrc_verb.PUT, '{"shortname": "testPatch998", "name": "MyPatch", "description": "MyDescription123"}', ) result = self.testapp.process_request( "/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e870-65fa-11ee-8c99-0242ac120002", rsrc_verb.GET, ) expected = '{"uuid": "cee1e870-65fa-11ee-8c99-0242ac120002", "shortname": "testPatch998", "name": "MyPatch", "description": "MyDescription123"}' self.assertEqual(result, expected) class Test_RestAPI_POST(unittest.TestCase): def setUp(self) -> None: chdir(testdir_path.parent.resolve()) init_classes() self.testapp = RootApp() def test_post_dict_user__API_key(self): result = self.testapp.process_request( "/users?API_key=e5e87d32-662b-11ee-8c99-0242ac120002", rsrc_verb.POST, '{"name": "testUser", "secret": "test"}', ) self.assertEqual(result, '"e5e87d32-662b-11ee-8c99-0242ac120002"') result = self.testapp.process_request("/users", rsrc_verb.GET) expected = '["8da57a3c-661f-11ee-8c99-0242ac120002", "e5e87d32-662b-11ee-8c99-0242ac120002"]' self.assertEqual(result, expected) result = self.testapp.process_request("/users/e5e87d32-662b-11ee-8c99-0242ac120002", rsrc_verb.GET) expected = '{"uuid": "e5e87d32-662b-11ee-8c99-0242ac120002", "name": "testUser"}' self.assertEqual(result, expected) def test_post_dict_user__nested_key(self): result = self.testapp.process_request( "/users", rsrc_verb.POST, '{"name": "testUser2", "secret": "test", "uuid":"e7e86d32-662b-11ee-8c99-0242ac120002"}', ) self.assertEqual(result, '"e7e86d32-662b-11ee-8c99-0242ac120002"') result = self.testapp.process_request("/users", rsrc_verb.GET) expected = '["8da57a3c-661f-11ee-8c99-0242ac120002", "e7e86d32-662b-11ee-8c99-0242ac120002"]' self.assertEqual(result, expected) result = self.testapp.process_request("/users/e7e86d32-662b-11ee-8c99-0242ac120002", rsrc_verb.GET) expected = '{"uuid": "e7e86d32-662b-11ee-8c99-0242ac120002", "name": "testUser2"}' self.assertEqual(result, expected) @patch(f"{__loader__.name }.uuid4") def test_post_dict_user__auto_key(self, mock_uuid4): mock_uuid4.return_value = UUID("5faccb2e-69aa-11ee-8c99-0242ac120002") # recreating classes & objects to force using the Mock-ed uuid4 init_classes() self.testapp = RootApp() result = self.testapp.process_request("/users", rsrc_verb.POST, '{"name": "testUser3", "secret": "test"}') self.assertEqual(result, '"5faccb2e-69aa-11ee-8c99-0242ac120002"') result = self.testapp.process_request("/users", rsrc_verb.GET) expected = '["8da57a3c-661f-11ee-8c99-0242ac120002", "5faccb2e-69aa-11ee-8c99-0242ac120002"]' self.assertEqual(result, expected) result = self.testapp.process_request("/users/5faccb2e-69aa-11ee-8c99-0242ac120002", rsrc_verb.GET) expected = '{"uuid": "5faccb2e-69aa-11ee-8c99-0242ac120002", "name": "testUser3"}' self.assertEqual(result, expected) def test_post_dict_patch__nested_API_key(self): self.testapp.process_request( "/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs?API_key=cee1e971-65fa-11ee-8c99-0242ac120002", rsrc_verb.POST, '{"shortname": "testPatch99", "name": "MyPatch", "description": "MyDescription"}', ) result = self.testapp.process_request( "/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e971-65fa-11ee-8c99-0242ac120002", rsrc_verb.GET, ) expected = '{"uuid": "cee1e971-65fa-11ee-8c99-0242ac120002", "shortname": "testPatch99", "name": "MyPatch", "description": "MyDescription"}' self.assertEqual(result, expected) class Test_RestAPI_DELETE(unittest.TestCase): def setUp(self) -> None: chdir(testdir_path.parent.resolve()) init_classes() self.testapp = RootApp() def test_delete_dict_user__API_key(self): self.testapp.process_request("/users?API_key=8da57a3c-661f-11ee-8c99-0242ac120002", rsrc_verb.DELETE) result = self.testapp.process_request("/users", rsrc_verb.GET) expected = "[]" self.assertEqual(result, expected) def test_delete_dict_user__All(self): result = self.testapp.process_request( "/users?API_key=e5e87d32-662b-11ee-8c99-0242ac120002", rsrc_verb.POST, '{"name": "testUser", "secret": "test"}', ) self.assertEqual(result, '"e5e87d32-662b-11ee-8c99-0242ac120002"') result = self.testapp.process_request("/users", rsrc_verb.GET) expected = '["8da57a3c-661f-11ee-8c99-0242ac120002", "e5e87d32-662b-11ee-8c99-0242ac120002"]' self.assertEqual(result, expected) self.testapp.process_request("/users", rsrc_verb.DELETE) result = self.testapp.process_request("/users", rsrc_verb.GET) expected = "[]" self.assertEqual(result, expected) def test_delete_dict_user_element(self): self.testapp.process_request("/users/8da57a3c-661f-11ee-8c99-0242ac120002", rsrc_verb.DELETE) result = self.testapp.process_request("/users", rsrc_verb.GET) expected = "[]" self.assertEqual(result, expected) def test_delete_nested_dict_games_patch_element(self): self.testapp.process_request( "/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e870-65fa-11ee-8c99-0242ac120002", rsrc_verb.DELETE, ) result = self.testapp.process_request("/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs", rsrc_verb.GET) expected = "[]" self.assertEqual(result, expected) def test_delete_nested_dict_games_patch_API_key(self): self.testapp.process_request( "/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs?API_key=cee1e870-65fa-11ee-8c99-0242ac120002", rsrc_verb.DELETE, ) result = self.testapp.process_request("/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs", rsrc_verb.GET) expected = "[]" self.assertEqual(result, expected) def test_delete_nested_dict_games_patch_All(self): self.testapp.process_request("/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs", rsrc_verb.DELETE) result = self.testapp.process_request("/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs", rsrc_verb.GET) expected = "[]" self.assertEqual(result, expected) class Test_RestAPI_PERFO(unittest.TestCase): def setUp(self) -> None: chdir(testdir_path.parent.resolve()) init_classes() self.testapp = RootApp() @unittest.skip def test_perf_dict(self): print(f"LIB INTERNAL PERF TEST") n_loop = 10000 start = time() for i in range(n_loop): self.testapp.process_request(f"/users/8da57a3c-661f-11ee-8c99-0242ac120002", rsrc_verb.GET) end = time() print(f"GET 1st level dict: {int(n_loop/(end-start))} Req/s") start = time() for i in range(n_loop): newUUID = uuid4() self.testapp.process_request( f"/users?API_key={newUUID}", rsrc_verb.POST, '{"name": "testUser", "secret": "test"}', ) end = time() print(f"POST 1st level dict (API_key): {int(n_loop/(end-start))} Req/s") start = time() for i in range(n_loop): newUUID = uuid4() self.testapp.process_request( f"/users?API_key={newUUID}", rsrc_verb.POST, '{"name": "testUser", "secret": "test"}', ) self.testapp.process_request(f"/users/{newUUID}", rsrc_verb.GET) end = time() print(f"POST/GET 1st level dict (API_key): {int(n_loop/(end-start))} Req/s") start = time() for i in range(n_loop): result = self.testapp.process_request(f"/users", rsrc_verb.POST, '{"name": "testUser", "secret": "test"}') self.testapp.process_request(f"/users/{json.loads(result)}", rsrc_verb.GET) end = time() print(f"POST/GET 1st level dict (autokey): {int(n_loop/(end-start))} Req/s") start = time() for i in range(n_loop): self.testapp.process_request( f"/games/9b0381d4-65f6-11ee-8c99-0242ac120002/shortname", rsrc_verb.PUT, '"TestValue!!"', ) self.testapp.process_request(f"/games/9b0381d4-65f6-11ee-8c99-0242ac120002/shortname", rsrc_verb.GET) end = time() print(f"PUT/GET 1st level (value) dict: {int(n_loop/(end-start))} Req/s") start = time() for i in range(n_loop): self.testapp.process_request( f"/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e870-65fa-11ee-8c99-0242ac120002", rsrc_verb.GET, ) end = time() print(f"GET 2nd level dict: {int(n_loop/(end-start))} Req/s") start = time() for i in range(n_loop): self.testapp.process_request( f"/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e870-65fa-11ee-8c99-0242ac120002/shortname", rsrc_verb.GET, ) end = time() print(f"GET 2nd level (value) dict: {int(n_loop/(end-start))} Req/s") start = time() for i in range(n_loop): self.testapp.process_request( f"/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e870-65fa-11ee-8c99-0242ac120002/shortname", rsrc_verb.PUT, '"TestValue!!"', ) self.testapp.process_request( f"/games/9b0381d4-65f6-11ee-8c99-0242ac120002/patchs/cee1e870-65fa-11ee-8c99-0242ac120002/shortname", rsrc_verb.GET, ) end = time() print(f"PUT/GET 2nd level (value) dict: {int(n_loop/(end-start))} Req/s")