Browse Source

Continue working on project, start working on API.

Cixo Develop 7 months ago
parent
commit
e25c25f6cf

+ 7 - 2
assets/__init__.py

@@ -2,7 +2,6 @@ from .password import password
 from .user import user
 from .user import user
 from .user import user_builder
 from .user import user_builder
 from .user_loader import user_loader
 from .user_loader import user_loader
-from .database import database
 from .apikey import apikey
 from .apikey import apikey
 from .secret import secret
 from .secret import secret
 from .secret import secret_builder
 from .secret import secret_builder
@@ -14,4 +13,10 @@ from .code_key import code_key
 from .code_key import code_key_generator
 from .code_key import code_key_generator
 from .code_key import code_key_manager
 from .code_key import code_key_manager
 from .settings import settings
 from .settings import settings
-from .application import application
+from .application_part import application_part
+from .application_user import application_user
+from .validators import validator
+from .validators import validator_dumper
+from .validators import validator_result
+from .validators import password_validator
+from .validators import nick_validator

+ 0 - 46
assets/application.py

@@ -1,46 +0,0 @@
-import sqlmodel
-import sqlalchemy.engine.base
-
-from .settings import settings
-from .user import user
-from .user import user_builder
-from .user_loader import user_loader
-
-class application:
-    def __init__(self, config: settings) -> None:
-        self.__config = config
-        self.__init_database(config.database)
-
-    def get_provider(self) -> dict:
-        return {
-            "app_name": self.config.app_name,
-            "description": self.config.app_description,
-            "organization_name": self.config.organization_name
-        }
-
-    def post_register(self, nick: str, password: str) -> dict:
-        builder = user_builder()
-        builder.nick = nick
-        builder.set_password(password)
-
-        with user_loader(self.database) as loader:
-            if loader.register(builder.result):
-                return {
-                    "status": True
-                }
-
-            return {
-                "status": False
-            }
-
-    @property
-    def config(self) -> settings:
-        return self.__config
-
-    @property
-    def database(self) -> sqlalchemy.engine.base.Engine:
-        return self.__database
-
-    def __init_database(self, url: str) -> None:
-        self.__database = sqlmodel.create_engine(url)
-        sqlmodel.SQLModel.metadata.create_all(self.__database)

+ 88 - 0
assets/application_part.py

@@ -0,0 +1,88 @@
+import sqlmodel
+import sqlalchemy.engine.base
+
+from .validators import validator
+from .validators import validator_result
+
+class application_part:
+    """
+    This class is parent for parts of the applications. It define responses
+    and method for access to database.
+    """
+
+    def __init__(self, database: sqlalchemy.engine.base.Engine) -> None:
+        """
+        This initialize part of application, database connection is required
+        to have access to it in the application parts.
+        
+        Parameters:
+            database (Engine): Database connection
+        """
+
+        self.__connector = database
+
+    @property
+    def _connector(self) -> sqlalchemy.engine.base.Engine:
+        """ It return connection to database. """
+
+        return self.__connector
+
+    def _apikey_response(self, apikey: str) -> dict:
+        """ It return response with apikey. """
+
+        return self._success_response(apikey = apikey)
+
+    def _validation(self, name: str, target: validator) -> dict | None:
+        """
+        This help validating. It require name of the validation, and 
+        validator. Then validate, and if validator is fine, return None
+        or when something is bad, then return response.
+
+        Parameters:
+            name (str): Name of the validation
+            target (validator): Validator to check
+
+        Returns:
+            (dict | None): Response with error or None on success
+        """
+
+        result = target.result
+
+        if result == validator_result.valid:
+            return None
+
+        return self._fail_response(
+            code = int(result),
+            description = validator_result.name(result),
+            validating = name
+        )
+
+    def _success_response(self, **kwargs) -> dict:
+        """ It returns success response, with additional params. """
+        
+        base = dict()
+        base.update(kwargs)
+        base["status"] = "success"
+
+        return base
+
+    def _fail_no_apikey(self) -> dict:
+        """ This return error response for not founded ApiKey. """
+
+        return self._fail_response(cause = "ApiKey not exists.")
+
+    def _fail_bad_password(self) -> dict:
+        """ This return error response to use when bad password provided. """
+
+        return self._fail_response(cause = "Password validation incorrect.")
+
+    def _fail_response(self, **kwargs) -> dict:
+        """ It return fail response, with additions content. """
+
+        base = dict()
+        base.update(kwargs)
+        base["status"] = "fail"
+
+        return base
+
+    

+ 245 - 0
assets/application_user.py

@@ -0,0 +1,245 @@
+from .user import user
+from .user import user_builder
+from .user_loader import user_loader
+from .application_part import application_part
+from .validators import nick_validator
+from .validators import password_validator
+
+class application_user(application_part):
+    """
+    This class is part of the app, which is responsible for user api 
+    endpoints, like registering users, login to app, change nick and
+    others. To full responses and parameters documentation, see api
+    documentation.
+    """
+
+    def get(self, apikey: str) -> dict:
+        """
+        This return information about user, like nick or code_key, to use
+        in end-to-end encryption.
+
+        Parameters:
+            apikey (str): Apikey of the user
+
+        Returns:
+            (dict): Information about user
+        """
+
+        with self.__loader as loader:
+            user = loader.get_by_apikey(apikey)
+
+            if user is None:
+                return self._fail_no_apikey()
+
+            return self._success_response(
+                nick = user.nick,
+                password = user.password,
+                apikey = user.apikey,
+                code_key = user.code_key
+            )
+
+    def register(self, nick: str, password: str) -> dict:
+        """
+        This register new user, by password and nick. It check that nick is
+        not in use, also validate password and nick. ApiKey and code_key 
+        would be generate by operating system random generator.
+
+        Parameters:
+            nick (str): Nick of the new user
+            password (str): Password of the new user
+
+        Returns:
+            (dict): Response to generate json
+        """
+
+        validate = self._validation(
+            "nick", 
+            nick_validator(nick)
+        )
+
+        validate = validate or self._validation(
+            "password", 
+            password_validator(password)
+        )
+
+        if validate is not None:
+            return validate
+
+        builder = user_builder()
+        builder.nick = nick
+        builder.set_password(password)
+
+        with self.__loader as loader:
+            new_user = builder.result
+
+            if loader.nick_in_use(new_user.nick):
+                return self._fail_response(cause = "Nick already in use.")
+
+            if loader.register(new_user):
+                return self._apikey_response(new_user.apikey)
+
+            return self._fail_response(cause = "Other database error.")
+
+    def login(self, nick: str, password: str) -> dict:
+        """
+        This function login user. To operate client apps require apikey. This 
+        endpoint would return apikey, when valid nick and password had been
+        provided.
+
+        Parameters:
+            nick (str): Nick of the user
+            password (str): Passworrd of the user
+
+        Returns:
+            (dict): Result with apikey or error on fail
+        """
+
+        with self.__loader as loader:
+            user = loader.login(nick, password)
+
+            if user is None:
+                return self._fail_response(cause = "Bad login or password.")
+
+            return self._apikey_response(user.apikey)
+
+    def unregister(self, apikey: str, password: str) -> dict:
+        """
+        This function drop user from database. It require password as second
+        validation, and of course apikey to identify user.
+
+        Parameters:
+            apikey (str): ApiKey of the user to drop
+            password (str): Password of the user to drop
+
+        Returns:
+            (dict): Result of the operation as to generate json response
+        """
+
+        with self.__loader as loader:
+            user = loader.get_by_apikey(apikey)
+
+            if user is None:
+                return self._fail_no_apikey()
+
+            if not user_builder(user).check_password(password):
+                return self._fail_bad_password()
+
+            loader.unregister(user)
+
+            return self._success_response()
+
+    def apikey_refresh(self, apikey: str) -> dict:
+        """
+        This function refresh apikey. It is useable when want to logout all
+        devices which store apikey to stay logged in.
+
+        Parameters:
+            apikey (str): ApiKey of the user to refresh apikey
+
+        Returns:
+            (dict): Result of the operation, with new apikey when success
+        """
+
+        with self.__loader as loader:
+            user = loader.get_by_apikey(apikey)
+
+            if user is None:
+                return self._fail_no_apikey()
+
+            builder = user_builder(user)
+            builder.refresh_apikey()
+            new_user = builder.result
+
+            if not loader.save(new_user):
+                return self._fail_response(cause = "Database error.")
+
+            return self._apikey_response(new_user.apikey)
+
+    def change_password(
+        self,
+        apikey: str,
+        old_password: str,
+        new_password: str
+    ) -> dict:
+        """
+        This function change password of the user. It require also old 
+        password, to decrypt key, which would be re-encrypt by new password.
+        Of course new password would be validated before set.
+
+        Parameters:
+            apikey (str): ApiKey of the user to work on
+            old_password (str): Old password of the user
+            new_password (str): New password which would be set
+
+        Returns:
+            (dict): Result of the operation to create response
+        """
+
+        validate = self._validation(
+            "password", 
+            password_validator(new_password)
+        )
+
+        if validate is not None:
+            return validate
+
+        with self.__loader as loader:
+            user = loader.get_by_apikey(apikey)
+
+            if user is None:
+                return self._fail_no_apikey()
+
+            builder = user_builder(user)
+
+            if not builder.set_password(new_password, old_password):
+                return self._fail_bad_password()
+
+            new_user = builder.result
+            loader.save(new_user)
+
+            return self._apikey_response(new_user.apikey)
+
+    def change_nick(self, apikey: str, nick: str) -> dict:
+        """
+        This function would change nick of the user. It also check that nick
+        is not in use, and validate that nick is not bad formated.
+
+        Parameters:
+            apikey (str): ApiKey of the user to work on
+            nick (str): New nick for the user
+
+        Returns:
+            (dict): Result of the operation to create response
+        """
+
+        validation = self._validation(
+            "nick",
+            nick_validator(nick)
+        )
+
+        if validation is not None:
+            return validation
+
+        with self.__loader as loader:
+            user = loader.get_by_apikey(apikey)
+
+            if user is None:
+                return self._fail_no_apikey()
+
+            if loader.nick_in_use(nick):
+                return self._fail_response(cause = "Nick already in use.")
+
+            builder = user_builder(user)
+            builder.nick = nick
+            new_user = builder.result
+
+            if not loader.save(new_user):
+                return self._fail_response(cause = "Other database error.")
+
+            return self._success_response()
+
+    @property
+    def __loader(self) -> user_loader:
+        """ This return new user_loader with database connector. """
+
+        return user_loader(self._connector)

+ 0 - 4
assets/database.py

@@ -1,4 +0,0 @@
-from .user import user
-
-class database:
-    pass

+ 48 - 10
assets/user.py

@@ -5,6 +5,7 @@ from .password import password
 from .builder import builder
 from .builder import builder
 from .code_key import code_key
 from .code_key import code_key
 from .code_key import code_key_manager
 from .code_key import code_key_manager
+from .secret_coder import bad_password
 
 
 class user(sqlmodel.SQLModel, table = True):
 class user(sqlmodel.SQLModel, table = True):
     """
     """
@@ -99,6 +100,16 @@ class user_builder(builder, target_type = user):
         """
         """
 
 
         super().__init__(target)
         super().__init__(target)
+
+        if target is None:
+            self.refresh_apikey()
+
+    def refresh_apikey(self) -> None:
+        """
+        This function refresh apikey of the user. It could be useable to
+        logout all of the devices which use user account.
+        """
+
         self._target.apikey = apikey()
         self._target.apikey = apikey()
 
 
     @property
     @property
@@ -125,11 +136,26 @@ class user_builder(builder, target_type = user):
 
 
         self._target.nick = target.upper()
         self._target.nick = target.upper()
 
 
+    def check_password(self, target: str) -> bool:
+        """
+        This function check that given password is correct with current user
+        password. It is usefull when trying to have second factor, password + 
+        ApiKey.
+
+        Parameters:
+            target (str): Password to check
+
+        Returns:
+            (bool): True when given password is correct, False when not
+        """
+
+        return password(target).validate(self._target.password)
+
     def set_password(
     def set_password(
         self, 
         self, 
         password: str, 
         password: str, 
         old_password: str | None = None
         old_password: str | None = None
-    ) -> None:
+    ) -> bool:
         """
         """
         This function set password to user. When only password is given, then 
         This function set password to user. When only password is given, then 
         it try to init user, which not have password and crypto key yet. User
         it try to init user, which not have password and crypto key yet. User
@@ -139,13 +165,16 @@ class user_builder(builder, target_type = user):
         Parameters:
         Parameters:
             password (str): New password to set
             password (str): New password to set
             old_password (str | None) = None: Old password, require to recrypt
             old_password (str | None) = None: Old password, require to recrypt
+        
+        Returns:
+            (bool): True when changed success, False when old password is bad
         """
         """
 
 
         if old_password is None:
         if old_password is None:
             self.__init_password(password)
             self.__init_password(password)
-            return
+            return True
 
 
-        self.__change_password(old_password, password)
+        return self.__change_password(old_password, password)
 
 
     def __init_password(self, target: str) -> None:
     def __init_password(self, target: str) -> None:
         """
         """
@@ -167,7 +196,7 @@ class user_builder(builder, target_type = user):
         self._target.password = password(target).result
         self._target.password = password(target).result
         self._target.code_key = code_key(password = target)
         self._target.code_key = code_key(password = target)
 
 
-    def __change_password(self, old_password: str, new_password: str) -> None:
+    def __change_password(self, old_password: str, new_password: str) -> bool:
         """
         """
         This change password, when user already have password and code key. It
         This change password, when user already have password and code key. It
         recrypt crypto key, to could use new password for secret decrypting.
         recrypt crypto key, to could use new password for secret decrypting.
@@ -175,15 +204,24 @@ class user_builder(builder, target_type = user):
         Parameters:
         Parameters:
             old_password (str): Old password
             old_password (str): Old password
             new_password (str): New password to set
             new_password (str): New password to set
+        
+        Returns:
+            (bool): True when changed success, False when old password is bad
         """
         """
 
 
         if old_password == new_password:
         if old_password == new_password:
-            raise Exception("New password is same as old password.")
+            return True
+
+        try:
+            key = self._target.key(old_password)
 
 
-        key = self._target.key(old_password)
+            if key is None or self._target.password is None:
+                raise Exception("User crypto key is not initialized yet.")
 
 
-        if key is None or self._target.password is None:
-            raise Exception("User crypto key is not initialized yet.")
+            self._target.code_key = key.recrypt(new_password).encrypted
+            self._target.password = password(new_password).result
 
 
-        self._target.code_key = key.recrypt(new_password).encrypted
-        self._target.password = password(new_password).result
+            return True
+
+        except bad_password:
+            return False

+ 8 - 3
assets/user_loader.py

@@ -79,7 +79,11 @@ class user_loader(sqlmodel.Session):
         return: bool - True when saved successfull, False when failed
         return: bool - True when saved successfull, False when failed
         """
         """
 
 
-        if not self.is_registered(user):
+        try:
+            if not self.is_registered(target):
+                return False
+
+        except:
             return False
             return False
 
 
         self.add(target)
         self.add(target)
@@ -96,7 +100,7 @@ class user_loader(sqlmodel.Session):
         return: bool - True when nick in use, False if not
         return: bool - True when nick in use, False if not
         """
         """
 
 
-        return self.get_by_nick(nick.upper()) is not None
+        return self.get_by_nick(nick) is not None
 
 
     def get_by_apikey(self, apikey: str) -> user | None:
     def get_by_apikey(self, apikey: str) -> user | None:
         """
         """
@@ -129,6 +133,7 @@ class user_loader(sqlmodel.Session):
         return: user | None - Loaded user or None when not exists.
         return: user | None - Loaded user or None when not exists.
         """
         """
 
 
+        nick = nick.upper()
         query = sqlmodel.select(user).where(user.nick == nick).limit(1)
         query = sqlmodel.select(user).where(user.nick == nick).limit(1)
         result = self.exec(query)
         result = self.exec(query)
 
 
@@ -155,7 +160,7 @@ class user_loader(sqlmodel.Session):
         return: bool - True when user is registered, False when not
         return: bool - True when user is registered, False when not
         """
         """
 
 
-        if target.id is None:
+        if not target.in_database:
             return False
             return False
 
 
         return self.get_by_id(target.id) is not None
         return self.get_by_id(target.id) is not None

+ 286 - 0
assets/validators.py

@@ -0,0 +1,286 @@
+import re
+import enum
+
+class validator_result(enum.IntEnum):
+    """
+    This is class, which provide validator_results. Validator results is 
+    presents as int, and this class define which int represent which error.
+    """
+
+    """ Valid result. """    
+    valid = 0
+
+    """ Given string is too long. """
+    too_long = 1
+
+    """ Given string is too short. """
+    too_short = 2
+
+    """ Given string not contain one of required chars. """
+    add_required_char = 3
+
+    """ Given string contain one of blocked chars. """
+    contain_invalid_char = 4
+
+    def name(code: int) -> str | None:
+        """
+        This function convert given error code to readable string. When given
+        code means, that string is valid, return None.
+
+        Parameters:
+            code (int): Error code number
+
+        Returns:
+            (str | None): Error as string, None when code is valid
+        """
+
+        if code == validator_result.valid:
+            return None
+
+        if code == validator_result.too_long:
+            return "Given string is too long."
+
+        if code == validator_result.too_short:
+            return "Given string is too short."
+
+        if code == validator_result.add_required_char:
+            return "Given string not contain one of required chars."
+
+        if code == validator_result.contain_invalid_char:
+            return "Given string contain char, which is blocker."
+
+        return "Not known error number."
+
+class validator:
+    """
+    This is validator class. It is responsible for checking, that given string
+    meets the organisation policy. For example, password is not too short, or
+    contain required string.
+    """
+
+    def __init__(self, content: str | None = None) -> None:
+        """
+        This is class initialiser, it get string to check. It also could get
+        None, then information validator is builded. Information validator is
+        validator, which not validate anything, but provide information about
+        validation, like max_lenght.
+
+        Parameters:
+            content (str | None): Target string to check, or None
+        """
+
+        self.__content = content
+
+    @property
+    def content(self) -> str:
+        """ 
+        String to check, provided in the constructor. When content is None,
+        it raise TypeError.
+
+        Returns:
+            (str): Content of the validator
+        """
+
+        if self.__content is None:
+            error = "This is only validator, which provide informations. "
+            error = error + "It has not any content."
+            raise TypeError(error)
+
+        return self.__content
+
+    @property
+    def is_valid(self) -> bool:
+        """
+        This check that provided string is valid.
+
+        Returns:
+            (bool): True when provided string is valid, False if not
+        """
+
+        return self.result == validator_result.valid
+
+    @property
+    def result(self) -> int:
+        """
+        This return result of the check, as number from validator_result.
+
+        Returns:
+            (int): Result of the validation.
+        """
+        
+        lenght = len(self.content)
+        invalid_regex = False
+        contain_regex = False
+
+        if len(self.must_contain) > 0:
+            contain_regex = "[" + "".join(self.must_contain) + "]"
+        
+        if len(self.invalid_chars) > 0:
+            invalid_regex = "[" + "".join(self.invalid_chars) + "]"
+
+        if lenght > self.max_lenght:
+            return validator_result.too_long
+
+        if lenght < self.min_lenght:
+            return validator_result.too_short
+
+        if invalid_regex and len(re.findall(invalid_regex, self.content)) > 0:
+            return validator_result.contain_invalid_char
+
+        if contain_regex and len(re.findall(contain_regex, self.content)) == 0:
+            return validator_result.add_required_char
+
+        return self._end_final(self.content)
+
+    def _end_final(self, content: str) -> int:
+        """
+        This function is end check. It could be overwriten to make custom 
+        validation. For example check for additional reguls.
+
+        Parameters:
+            content (str): Content of the string, which must be validate
+
+        Returns:
+            (int): Number from validator_results enum
+        """
+
+        return validator_result.valid
+
+    @property        
+    def info(self) -> str | None:
+        """ This return additional info about validator, for frontend. """
+
+        return None
+
+    @property
+    def min_lenght(self) -> int:
+        """ This return minimum lenght of the string. """
+
+        raise TypeError("Property min_lenght must be overwrite.")
+
+    @property
+    def max_lenght(self) -> int:
+        """ This return maximum lenght of the string. """
+
+        raise TypeError("Property max_lenght must be overwrite.")
+
+    @property
+    def must_contain(self) -> list:
+        """ This return list of chars, one of them must being in content """
+
+        raise TypeError("Property must_contain must be overwrite.")
+
+    @property
+    def invalid_chars(self) -> list:
+        """ This return chars, which can not being in the content. """
+
+        raise TypeError("Property invalid_chars must be overwrite.")
+
+class validator_dumper:
+    """
+    This class is responsible for validators info dumps, required for example
+    on application frontend, to presents information.
+    """
+
+    def __init__(self, target: type) -> None:
+        """
+        This set target validator.
+
+        Parameters:
+            target (type): This is validator to setup, as class
+        """
+
+        self.__target = target()
+
+    @property
+    def target(self) -> validator:
+        """ Target validator. """
+
+        return self.__target
+
+    @property
+    def route(self) -> dict:
+        """ 
+        This is dump of all informations as dictionary.
+         * max-lenght (int) -> validator.max_lenght 
+         * min-lenght (int) -> validator.min_lenght 
+         * invalid-chars (list) -> validator.invalid_chars
+         * required-chars (list) -> validator.must_contain
+         * readable-info (str) -> validator.info or ""
+
+        Returns:
+            (dict): Info as described up
+        """
+        return {
+            "max-lenght": self.target.max_lenght,
+            "min-lenght": self.target.min_lenght,
+            "invalid-chars": self.target.invalid_chars,
+            "required-chars": self.target.must_contain,
+            "readable-info": self.target.info or ""
+        }
+
+class password_validator(validator):
+    """
+    This is validator for main app password.
+    """
+
+    @property
+    def max_lenght(self) -> int:
+        return 256
+
+    @property
+    def must_contain(self) -> list:
+        return []
+
+    @property
+    def invalid_chars(self) -> list:
+        return ["\"", "'", " ", "\t", "\n", "`"]
+
+    @property
+    def info(self) -> str:
+        return "Password can not have white chars and quotation marks."
+
+    @property
+    def min_lenght(self) -> int:
+        return 8
+
+class nick_validator(validator):
+    """
+    This is validator for nick in app.
+    """
+
+    @property
+    def max_lenght(self) -> int:
+        return 256
+
+    @property
+    def must_contain(self) -> list:
+        return []
+
+    @property
+    def invalid_chars(self) -> list:
+        return []
+
+    @property
+    def min_lenght(self) -> int:
+        return 4
+
+    @property
+    def info(self) -> str | None:
+        return "Nick can contain only letters, digits, and \"-, _\" chars."
+
+    def _end_final(self, content: str) -> int:
+        for letter in content:
+            if letter.isalpha():
+                continue
+        
+            if letter.isdigit():
+                continue
+
+            if letter == "_" or letter == "-":
+                continue
+
+            return validator_result.contain_invalid_char
+
+        return validator_result.valid
+

+ 98 - 0
tests/006-application.py

@@ -0,0 +1,98 @@
+import pathlib
+
+current = pathlib.Path(__file__).parent
+root = current.parent
+
+import sys
+sys.path.append(str(root))
+
+import assets
+import sqlmodel
+
+def drop_db() -> None:
+    db = pathlib.Path("./006-application.db")
+
+    if db.is_file():
+        db.unlink()
+
+drop_db()
+
+connection = sqlmodel.create_engine("sqlite:///006-application.db")
+app = assets.application_user(connection)
+
+sqlmodel.SQLModel.metadata.create_all(connection)
+
+print("Register.")
+print("With success:")
+print(app.register("user1", "password"))
+print(app.register("user2", "password"))
+print("With fail:")
+print(app.register("user2", "password"))
+print(app.register("user3", "pas"))
+print(app.register("user3", "paswword\""))
+
+print()
+
+print("Login.")
+print("With success:")
+print(app.login("user1", "password"))
+print(app.login("user2", "password"))
+print("With fail:")
+print(app.login("user3", "password"))
+print(app.login("user1", "password_bad"))
+
+print()
+
+test_apikey = app.login("user1", "password")["apikey"]
+
+print("Get user.")
+print("With success:")
+print(app.get(test_apikey))
+print("With fail:")
+print(app.get("not exists"))
+
+print("Unregister.")
+print("Registering new user...")
+
+to_drop_apikey = app.register("user_to_drop", "password1")["apikey"]
+
+print("With fail:")
+print(app.unregister("jeriojeroi", "password1"))
+print(app.unregister(to_drop_apikey, "bad_password"))
+print("With success:")
+print(app.unregister(to_drop_apikey, "password1"))
+
+print()
+
+print("Apikey refresh.")
+print("With success:")
+print(app.apikey_refresh(test_apikey))
+print("With fail (old apikey):")
+print(app.apikey_refresh(test_apikey))
+
+test_apikey = app.login("user1", "password")["apikey"]
+
+print()
+
+print("Change password.")
+print("With success:")
+print(app.change_password(test_apikey, "password", "password1"))
+print(app.change_password(test_apikey, "password1", "password"))
+print("With fail:")
+print(app.change_password("fjljsdkl", "password1", "password"))
+print(app.change_password(test_apikey, "password1", "password"))
+print(app.change_password(test_apikey, "password1", "password\'"))
+
+print()
+
+print("Change nick.")
+print("With success:")
+print(app.change_nick(test_apikey, "test_user"))
+print("Result:")
+print(app.get(test_apikey))
+print("With fail:")
+print(app.change_nick(test_apikey, "SAMpl\'"))
+print(app.change_nick("jjsfdfjskl", "nick1"))
+print(app.change_nick(test_apikey, "user2"))
+
+drop_db()

+ 26 - 0
tests/007-validator.py

@@ -0,0 +1,26 @@
+import pathlib
+
+current = pathlib.Path(__file__).parent
+root = current.parent
+
+import sys
+sys.path.append(str(root))
+
+import assets
+
+def check(validator: assets.validator) -> None:
+    if validator.is_valid:
+        print("Validator for: \"" + validator.content + "\" is valid.")
+        return 
+
+    result = str(validator.result) + " \""
+    result = result + assets.validator_result.name(validator.result) + "\""
+
+    print("Validator for \"" + validator.content + "\" return " + result + ".")
+
+check(assets.password_validator("OwO"))
+check(assets.password_validator("OwOOwOWWOwO"))
+check(assets.password_validator("OwOOwOW'WOwO"))
+
+print("Dump dict of validator info for route: ")
+print(assets.validator_dumper(assets.password_validator).route) 

+ 1 - 0
tests/config_sample.json

@@ -0,0 +1 @@
+{}