feat(usermodel): add User parameter validation methods
This commit is contained in:
parent
e6ef20cd96
commit
16fb570cfd
@ -33,18 +33,180 @@ class User:
|
|||||||
The user's profile picture as a PIL Image object.
|
The user's profile picture as a PIL Image object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_username(input_str: str) -> bool:
|
||||||
|
"""Checks whether the input string is a valid username.
|
||||||
|
|
||||||
|
Valid usernames can contain only uppercase/lowercase latin characters,
|
||||||
|
numbers, hyphens, underscores and periods. A username must start with a
|
||||||
|
latin character.
|
||||||
|
Minimum length is 1, maximum length is 64 characters.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
input_str : str
|
||||||
|
The string whose validity as a username to check.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if input_str is a valid username and False otherwise.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
TypeError
|
||||||
|
If input_str is not of type string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(input_str, str):
|
||||||
|
raise TypeError(f"Expected a string but got: '{type(input_str)}'.")
|
||||||
|
|
||||||
|
# TODO implement
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_password_hash(input_str: str) -> bool:
|
||||||
|
"""Checks whether the input string is a valid password hash.
|
||||||
|
|
||||||
|
Password hashes are base64-encoded bytes, and the hashing algorithm used
|
||||||
|
to create the hash is hard to determine, but we can verify that the
|
||||||
|
provided hash is a non-empty string containing base64-decodeable text.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
input_str : str
|
||||||
|
The string whose validity as a password hash to check.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if input_str is a valid password hash and False otherwise.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
TypeError
|
||||||
|
If input_str is not of type string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(input_str, str):
|
||||||
|
raise TypeError(f"Expected a string but got: '{type(input_str)}'.")
|
||||||
|
|
||||||
|
# TODO implement
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_email(input_str: str) -> bool:
|
||||||
|
"""Checks whether the input string is a valid email address.
|
||||||
|
|
||||||
|
A valid email address contains no whitespace and at least one '@'-char.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
input_str : str
|
||||||
|
The string whose validity as an email address to check.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if input_str is a valid email address and False otherwise.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
TypeError
|
||||||
|
If input_str is not of type string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(input_str, str):
|
||||||
|
raise TypeError(f"Expected a string but got: '{type(input_str)}'.")
|
||||||
|
|
||||||
|
# TODO implement
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_person_name(input_str: str) -> bool:
|
||||||
|
"""Checks whether the input string is valid as a first/last/display name.
|
||||||
|
|
||||||
|
Valid names cannot contain whitespace and must be at least one character
|
||||||
|
long.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
input_str : str
|
||||||
|
The string whose validity as a first-/last-/displayname to check.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if input_str is a valid name and False otherwise.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
TypeError
|
||||||
|
If input_str is not of type string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(input_str, str):
|
||||||
|
raise TypeError(f"Expected a string but got: '{type(input_str)}'.")
|
||||||
|
|
||||||
|
# TODO implement
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_picture(input_image: Image) -> bool:
|
||||||
|
"""Checks whether the input image is a valid Image object.
|
||||||
|
|
||||||
|
TBD - unsure which formats and filesizes to allow here.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
input_image : PIL.Image
|
||||||
|
The Image whose validity to check.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if input_image is a valid Image and False otherwise.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
TypeError
|
||||||
|
If input_image is not of type PIL.Image.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(input_image, Image):
|
||||||
|
raise TypeError(f"Expected a PIL Image but got: '{type(input_image)}'.")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
username: str, password_hash: str, email: str,
|
username: str, password_hash: str, email: str,
|
||||||
first_name: str, last_name: str, display_name: str,
|
first_name: str, last_name: str, display_name: str,
|
||||||
picture: Image,
|
picture: Image,
|
||||||
):
|
):
|
||||||
|
|
||||||
|
if not User.is_valid_username(username):
|
||||||
|
raise ValueError(f"Not a valid username: '{username}'.")
|
||||||
self.username = username
|
self.username = username
|
||||||
|
|
||||||
|
if not User.is_valid_password_hash(password_hash):
|
||||||
|
raise ValueError(f"Not a valid password hash: '{password_hash}'.")
|
||||||
self.password_hash = password_hash
|
self.password_hash = password_hash
|
||||||
|
|
||||||
|
if not User.is_valid_email(email):
|
||||||
|
raise ValueError(f"Not a valid email address: '{email}'.")
|
||||||
self.email = email
|
self.email = email
|
||||||
|
|
||||||
|
for name in [first_name, last_name, display_name]:
|
||||||
|
if not User.is_valid_person_name(name):
|
||||||
|
raise ValueError(f"Not a valid name: '{name}'.")
|
||||||
self.first_name = first_name
|
self.first_name = first_name
|
||||||
self.last_name = last_name
|
self.last_name = last_name
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.picture = picture
|
|
||||||
|
|
||||||
# TODO validate params
|
if not User.is_valid_picture(picture):
|
||||||
|
raise ValueError(f"Not a valid image: '{picture}'.")
|
||||||
|
self.picture = picture
|
||||||
|
94
tests/test_usermodel.py
Normal file
94
tests/test_usermodel.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
"""Unit tests for the lumi2.usermodel module."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from lumi2.usermodel import User
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_valid_username():
|
||||||
|
for invalid_type in [None, 0, True, ("x",), ["x"]]:
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
User.is_valid_username(invalid_type)
|
||||||
|
|
||||||
|
invalid_usernames = [
|
||||||
|
"", " ", "\u1337", "\t", "\n",
|
||||||
|
"alice?", "alice=", "alice*",
|
||||||
|
"1alice", "alice bob",
|
||||||
|
]
|
||||||
|
for username in invalid_usernames:
|
||||||
|
assert not User.is_valid_username(username)
|
||||||
|
|
||||||
|
valid_usernames = [
|
||||||
|
"alice", "Al1ce",
|
||||||
|
"Aa0-_.", "a",
|
||||||
|
]
|
||||||
|
for username in valid_usernames:
|
||||||
|
assert User.is_valid_username(username)
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_valid_password_hash():
|
||||||
|
for invalid_type in [None, 0, True, ("x",), ["x"]]:
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
User.is_valid_password_hash(invalid_type)
|
||||||
|
|
||||||
|
invalid_hashes = [
|
||||||
|
"", " ", "\t", "\n",
|
||||||
|
"foobar===", "ou=foobar", "foobar*",
|
||||||
|
"foobar$", "foo bar",
|
||||||
|
]
|
||||||
|
for invalid_hash in invalid_hashes:
|
||||||
|
assert not User.is_valid_password_hash(invalid_hash)
|
||||||
|
|
||||||
|
valid_hashes = [
|
||||||
|
# can contain [A-Za-z0-9+/] and up to two '=' at the end
|
||||||
|
"foobar", "EzM3",
|
||||||
|
"abcABC123+/=", "a",
|
||||||
|
]
|
||||||
|
for valid_hash in valid_hashes:
|
||||||
|
assert User.is_valid_password_hash(valid_hash)
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_valid_email():
|
||||||
|
for invalid_type in [None, 0, True, ("x",), ["x"]]:
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
User.is_valid_email(invalid_type)
|
||||||
|
|
||||||
|
invalid_emails = [
|
||||||
|
"", " ", "\t", "\n",
|
||||||
|
" alice@example.com", "alice", "@", "@.",
|
||||||
|
"alice@example.com ", "alice@ex ample.com"
|
||||||
|
]
|
||||||
|
for invalid_email in invalid_emails:
|
||||||
|
assert not User.is_valid_email(invalid_email)
|
||||||
|
|
||||||
|
valid_emails = [
|
||||||
|
# can contain [A-Za-z0-9+/] and up to two '=' at the end
|
||||||
|
"alice@example.com", "a@b.c", "Alice.1337$&@Fo0.xyz",
|
||||||
|
]
|
||||||
|
for valid_email in valid_emails:
|
||||||
|
assert User.is_valid_email(valid_email)
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_valid_person_name():
|
||||||
|
for invalid_type in [None, 0, True, ("x",), ["x"]]:
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
User.is_valid_person_name(invalid_type)
|
||||||
|
|
||||||
|
invalid_names = [
|
||||||
|
"", " ", "\t", "\n",
|
||||||
|
"Alice Jones", " Alice",
|
||||||
|
]
|
||||||
|
for invalid_name in invalid_names:
|
||||||
|
assert not User.is_valid_person_name(invalid_name)
|
||||||
|
|
||||||
|
valid_names = [
|
||||||
|
"Alice", "Älice", "Böb", "A1lic3$",
|
||||||
|
"a", "1",
|
||||||
|
]
|
||||||
|
for valid_name in valid_names:
|
||||||
|
assert User.is_valid_person_name(valid_name)
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_valid_picture():
|
||||||
|
# TODO implement
|
||||||
|
pass
|
Loading…
x
Reference in New Issue
Block a user