server/tests: add func.users tests
This commit is contained in:
parent
03c74cb5a3
commit
81dfbaec98
2 changed files with 410 additions and 16 deletions
|
@ -12,10 +12,10 @@ class InvalidPasswordError(errors.ValidationError): pass
|
|||
class InvalidRankError(errors.ValidationError): pass
|
||||
class InvalidAvatarError(errors.ValidationError): pass
|
||||
|
||||
def _get_avatar_path(name):
|
||||
return 'avatars/' + name.lower() + '.png'
|
||||
def get_avatar_path(user_name):
|
||||
return 'avatars/' + user_name.lower() + '.png'
|
||||
|
||||
def _get_avatar_url(user):
|
||||
def get_avatar_url(user):
|
||||
assert user
|
||||
if user.avatar_style == user.AVATAR_GRAVATAR:
|
||||
assert user.email or user.name
|
||||
|
@ -27,7 +27,7 @@ def _get_avatar_url(user):
|
|||
return '%s/avatars/%s.png' % (
|
||||
config.config['data_url'].rstrip('/'), user.name.lower())
|
||||
|
||||
def _get_email(user, auth_user, force_show_email):
|
||||
def get_email(user, auth_user, force_show_email):
|
||||
assert user
|
||||
assert auth_user
|
||||
if not force_show_email \
|
||||
|
@ -36,14 +36,14 @@ def _get_email(user, auth_user, force_show_email):
|
|||
return False
|
||||
return user.email
|
||||
|
||||
def _get_liked_post_count(user, auth_user):
|
||||
def get_liked_post_count(user, auth_user):
|
||||
assert user
|
||||
assert auth_user
|
||||
if auth_user.user_id != user.user_id:
|
||||
return False
|
||||
return user.liked_post_count
|
||||
|
||||
def _get_disliked_post_count(user, auth_user):
|
||||
def get_disliked_post_count(user, auth_user):
|
||||
assert user
|
||||
assert auth_user
|
||||
if auth_user.user_id != user.user_id:
|
||||
|
@ -60,16 +60,16 @@ def serialize_user(user, auth_user, options=None, force_show_email=False):
|
|||
'version': lambda: user.version,
|
||||
'rank': lambda: user.rank,
|
||||
'avatarStyle': lambda: user.avatar_style,
|
||||
'avatarUrl': lambda: _get_avatar_url(user),
|
||||
'avatarUrl': lambda: get_avatar_url(user),
|
||||
'commentCount': lambda: user.comment_count,
|
||||
'uploadedPostCount': lambda: user.post_count,
|
||||
'favoritePostCount': lambda: user.favorite_post_count,
|
||||
'likedPostCount':
|
||||
lambda: _get_liked_post_count(user, auth_user),
|
||||
lambda: get_liked_post_count(user, auth_user),
|
||||
'dislikedPostCount':
|
||||
lambda: _get_disliked_post_count(user, auth_user),
|
||||
lambda: get_disliked_post_count(user, auth_user),
|
||||
'email':
|
||||
lambda: _get_email(user, auth_user, force_show_email),
|
||||
lambda: get_email(user, auth_user, force_show_email),
|
||||
},
|
||||
options)
|
||||
|
||||
|
@ -127,16 +127,16 @@ def update_user_name(user, name):
|
|||
raise InvalidUserNameError('Name cannot be empty.')
|
||||
if util.value_exceeds_column_size(name, db.User.name):
|
||||
raise InvalidUserNameError('User name is too long.')
|
||||
other_user = try_get_user_by_name(name)
|
||||
if other_user and other_user.user_id != user.user_id:
|
||||
raise UserAlreadyExistsError('User %r already exists.' % name)
|
||||
name = name.strip()
|
||||
name_regex = config.config['user_name_regex']
|
||||
if not re.match(name_regex, name):
|
||||
raise InvalidUserNameError(
|
||||
'User name %r must satisfy regex %r.' % (name, name_regex))
|
||||
if user.name and files.has(_get_avatar_path(user.name)):
|
||||
files.move(_get_avatar_path(user.name), _get_avatar_path(name))
|
||||
other_user = try_get_user_by_name(name)
|
||||
if other_user and other_user.user_id != user.user_id:
|
||||
raise UserAlreadyExistsError('User %r already exists.' % name)
|
||||
if user.name and files.has(get_avatar_path(user.name)):
|
||||
files.move(get_avatar_path(user.name), get_avatar_path(name))
|
||||
user.name = name
|
||||
|
||||
def update_user_password(user, password):
|
||||
|
@ -178,7 +178,7 @@ def update_user_rank(user, rank, auth_user):
|
|||
raise errors.AuthError('Trying to set higher rank than your own.')
|
||||
user.rank = rank
|
||||
|
||||
def update_user_avatar(user, avatar_style, avatar_content):
|
||||
def update_user_avatar(user, avatar_style, avatar_content=None):
|
||||
assert user
|
||||
if avatar_style == 'gravatar':
|
||||
user.avatar_style = user.AVATAR_GRAVATAR
|
||||
|
|
394
server/szurubooru/tests/func/test_users.py
Normal file
394
server/szurubooru/tests/func/test_users.py
Normal file
|
@ -0,0 +1,394 @@
|
|||
import unittest.mock
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from szurubooru import db, errors
|
||||
from szurubooru.func import auth, users, files, util
|
||||
|
||||
EMPTY_PIXEL = \
|
||||
b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00' \
|
||||
b'\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x01\x00\x2c\x00\x00\x00\x00' \
|
||||
b'\x01\x00\x01\x00\x00\x02\x02\x4c\x01\x00\x3b'
|
||||
|
||||
@pytest.mark.parametrize('user_name', ['test', 'TEST'])
|
||||
def test_get_avatar_path(user_name):
|
||||
assert users.get_avatar_path(user_name) == 'avatars/test.png'
|
||||
|
||||
@pytest.mark.parametrize('user_name,user_email,avatar_style,expected_url', [
|
||||
(
|
||||
'user',
|
||||
None ,
|
||||
db.User.AVATAR_GRAVATAR,
|
||||
'https://gravatar.com/avatar/ee11cbb19052e40b07aac0ca060c23ee?d=retro&s=100',
|
||||
),
|
||||
(
|
||||
None,
|
||||
'user@example.com' ,
|
||||
db.User.AVATAR_GRAVATAR,
|
||||
'https://gravatar.com/avatar/b58996c504c5638798eb6b511e6f49af?d=retro&s=100',
|
||||
),
|
||||
(
|
||||
'user',
|
||||
'user@example.com' ,
|
||||
db.User.AVATAR_GRAVATAR,
|
||||
'https://gravatar.com/avatar/b58996c504c5638798eb6b511e6f49af?d=retro&s=100',
|
||||
),
|
||||
(
|
||||
'user',
|
||||
None ,
|
||||
db.User.AVATAR_MANUAL,
|
||||
'http://example.com/avatars/user.png',
|
||||
),
|
||||
])
|
||||
def test_get_avatar_url(
|
||||
user_name, user_email, avatar_style, expected_url, config_injector):
|
||||
config_injector({
|
||||
'data_url': 'http://example.com/',
|
||||
'thumbnails': {'avatar_width': 100},
|
||||
})
|
||||
user = db.User()
|
||||
user.name = user_name
|
||||
user.email = user_email
|
||||
user.avatar_style = avatar_style
|
||||
assert users.get_avatar_url(user) == expected_url
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'same_user,can_edit_any_email,force_show,expected_email', [
|
||||
(False, False, False, False),
|
||||
(True, False, False, 'test@example.com'),
|
||||
(False, True, False, 'test@example.com'),
|
||||
(False, False, True, 'test@example.com'),
|
||||
])
|
||||
def test_get_email(
|
||||
same_user, can_edit_any_email, force_show, expected_email, user_factory):
|
||||
with unittest.mock.patch('szurubooru.func.auth.has_privilege'):
|
||||
auth.has_privilege = lambda user, name: can_edit_any_email
|
||||
user = user_factory()
|
||||
user.email = 'test@example.com'
|
||||
auth_user = user if same_user else user_factory()
|
||||
db.session.add_all([user, auth_user])
|
||||
db.session.flush()
|
||||
assert users.get_email(user, auth_user, force_show) == expected_email
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'same_user,score,expected_liked_post_count,expected_disliked_post_count', [
|
||||
(False, 1, False, False),
|
||||
(False, -1, False, False),
|
||||
(True, 1, 1, 0),
|
||||
(True, -1, 0, 1),
|
||||
])
|
||||
def test_get_liked_post_count(
|
||||
same_user,
|
||||
score,
|
||||
expected_liked_post_count,
|
||||
expected_disliked_post_count,
|
||||
user_factory,
|
||||
post_factory):
|
||||
user = user_factory()
|
||||
post = post_factory()
|
||||
auth_user = user if same_user else user_factory()
|
||||
score = db.PostScore(post=post, user=user, score=score, time=datetime.now())
|
||||
db.session.add_all([post, user, score])
|
||||
db.session.flush()
|
||||
assert users.get_liked_post_count(user, auth_user) == expected_liked_post_count
|
||||
assert users.get_disliked_post_count(user, auth_user) == expected_disliked_post_count
|
||||
|
||||
def test_serialize_user_when_empty():
|
||||
assert users.serialize_user(None, None) is None
|
||||
|
||||
def test_serialize_user(user_factory):
|
||||
with unittest.mock.patch('szurubooru.func.users.get_email'), \
|
||||
unittest.mock.patch('szurubooru.func.users.get_avatar_url'), \
|
||||
unittest.mock.patch('szurubooru.func.users.get_liked_post_count'), \
|
||||
unittest.mock.patch('szurubooru.func.users.get_disliked_post_count'):
|
||||
users.get_email.return_value = 'test@example.com'
|
||||
users.get_avatar_url.return_value = 'https://example.com/avatar.png'
|
||||
users.get_liked_post_count.return_value = 66
|
||||
users.get_disliked_post_count.return_value = 33
|
||||
|
||||
auth_user = user_factory()
|
||||
user = user_factory(name='dummy user')
|
||||
user.creation_time = datetime(1997, 1, 1)
|
||||
user.last_edit_time = datetime(1998, 1, 1)
|
||||
user.avatar_style = db.User.AVATAR_MANUAL
|
||||
user.rank = db.User.RANK_ADMINISTRATOR
|
||||
db.session.add(user)
|
||||
db.session.flush()
|
||||
|
||||
assert users.serialize_user(user, auth_user) == {
|
||||
'version': 1,
|
||||
'name': 'dummy user',
|
||||
'email': 'test@example.com',
|
||||
'rank': 'administrator',
|
||||
'creationTime': datetime(1997, 1, 1, 0, 0),
|
||||
'lastLoginTime': None,
|
||||
'avatarStyle': 'manual',
|
||||
'avatarUrl': 'https://example.com/avatar.png',
|
||||
'likedPostCount': 66,
|
||||
'dislikedPostCount': 33,
|
||||
'commentCount': 0,
|
||||
'favoritePostCount': 0,
|
||||
'uploadedPostCount': 0,
|
||||
}
|
||||
|
||||
def test_serialize_micro_user(user_factory):
|
||||
with unittest.mock.patch('szurubooru.func.users.get_avatar_url'):
|
||||
users.get_avatar_url.return_value = 'https://example.com/avatar.png'
|
||||
auth_user = user_factory()
|
||||
user = user_factory(name='dummy user')
|
||||
db.session.add(user)
|
||||
db.session.flush()
|
||||
assert users.serialize_micro_user(user, auth_user) == {
|
||||
'name': 'dummy user',
|
||||
'avatarUrl': 'https://example.com/avatar.png',
|
||||
}
|
||||
|
||||
@pytest.mark.parametrize('count', [0, 1, 2])
|
||||
def test_get_user_count(count, user_factory):
|
||||
for _ in range(count):
|
||||
db.session.add(user_factory())
|
||||
db.session.flush()
|
||||
assert users.get_user_count() == count
|
||||
|
||||
def test_try_get_user_by_name(user_factory):
|
||||
user = user_factory(name='name', email='email')
|
||||
db.session.add(user)
|
||||
assert users.try_get_user_by_name('non-existing') is None
|
||||
assert users.try_get_user_by_name('email') is None
|
||||
assert users.try_get_user_by_name('name') is user
|
||||
assert users.try_get_user_by_name('NAME') is user
|
||||
|
||||
def test_get_user_by_name(user_factory):
|
||||
user = user_factory(name='name', email='email')
|
||||
db.session.add(user)
|
||||
with pytest.raises(users.UserNotFoundError):
|
||||
assert users.get_user_by_name('non-existing')
|
||||
with pytest.raises(users.UserNotFoundError):
|
||||
assert users.get_user_by_name('email')
|
||||
assert users.get_user_by_name('name') is user
|
||||
assert users.get_user_by_name('NAME') is user
|
||||
|
||||
def test_try_get_user_by_name_or_email(user_factory):
|
||||
user = user_factory(name='name', email='email')
|
||||
db.session.add(user)
|
||||
assert users.try_get_user_by_name_or_email('non-existing') is None
|
||||
assert users.try_get_user_by_name_or_email('email') is user
|
||||
assert users.try_get_user_by_name_or_email('EMAIL') is user
|
||||
assert users.try_get_user_by_name_or_email('name') is user
|
||||
assert users.try_get_user_by_name_or_email('NAME') is user
|
||||
|
||||
def test_get_user_by_name_or_email(user_factory):
|
||||
user = user_factory(name='name', email='email')
|
||||
db.session.add(user)
|
||||
with pytest.raises(users.UserNotFoundError):
|
||||
assert users.get_user_by_name_or_email('non-existing')
|
||||
assert users.get_user_by_name_or_email('email') is user
|
||||
assert users.get_user_by_name_or_email('EMAIL') is user
|
||||
assert users.get_user_by_name_or_email('name') is user
|
||||
assert users.get_user_by_name_or_email('NAME') is user
|
||||
|
||||
def test_create_user_for_first_user(fake_datetime):
|
||||
with unittest.mock.patch('szurubooru.func.users.update_user_name'), \
|
||||
unittest.mock.patch('szurubooru.func.users.update_user_password'), \
|
||||
unittest.mock.patch('szurubooru.func.users.update_user_email'), \
|
||||
fake_datetime('1997-01-01'):
|
||||
user = users.create_user('name', 'password', 'email')
|
||||
assert user.creation_time == datetime(1997, 1, 1)
|
||||
assert user.last_login_time is None
|
||||
assert user.rank == db.User.RANK_ADMINISTRATOR
|
||||
users.update_user_name.assert_called_once_with(user, 'name')
|
||||
users.update_user_password.assert_called_once_with(user, 'password')
|
||||
users.update_user_email.assert_called_once_with(user, 'email')
|
||||
|
||||
def test_create_user_for_subsequent_users(user_factory, config_injector):
|
||||
config_injector({'default_rank': 'regular'})
|
||||
db.session.add(user_factory())
|
||||
db.session.flush()
|
||||
with unittest.mock.patch('szurubooru.func.users.update_user_name'), \
|
||||
unittest.mock.patch('szurubooru.func.users.update_user_password'), \
|
||||
unittest.mock.patch('szurubooru.func.users.update_user_email'):
|
||||
user = users.create_user('name', 'password', 'email')
|
||||
assert user.rank == db.User.RANK_REGULAR
|
||||
|
||||
def test_update_user_name_with_empty_string(user_factory):
|
||||
user = user_factory()
|
||||
with pytest.raises(users.InvalidUserNameError):
|
||||
users.update_user_name(user, None)
|
||||
|
||||
def test_update_user_name_with_too_long_string(user_factory):
|
||||
user = user_factory()
|
||||
with pytest.raises(users.InvalidUserNameError):
|
||||
users.update_user_name(user, 'a' * 300)
|
||||
|
||||
def test_update_user_name_with_invalid_name(user_factory, config_injector):
|
||||
config_injector({'user_name_regex': '^[a-z]+$'})
|
||||
user = user_factory()
|
||||
with pytest.raises(users.InvalidUserNameError):
|
||||
users.update_user_name(user, '0')
|
||||
|
||||
def test_update_user_name_with_duplicate_name(user_factory, config_injector):
|
||||
config_injector({'user_name_regex': '^[a-z]+$'})
|
||||
user = user_factory()
|
||||
existing_user = user_factory(name='dummy')
|
||||
db.session.add(existing_user)
|
||||
db.session.flush()
|
||||
with pytest.raises(users.UserAlreadyExistsError):
|
||||
users.update_user_name(user, 'dummy')
|
||||
|
||||
def test_update_user_name_reusing_own_name(user_factory, config_injector):
|
||||
config_injector({'user_name_regex': '^[a-z]+$'})
|
||||
user = user_factory(name='dummy')
|
||||
db.session.add(user)
|
||||
db.session.flush()
|
||||
with unittest.mock.patch('szurubooru.func.files.has'):
|
||||
files.has.return_value = False
|
||||
users.update_user_name(user, 'dummy')
|
||||
db.session.flush()
|
||||
assert users.try_get_user_by_name('dummy') is user
|
||||
|
||||
def test_update_user_name_for_new_user(user_factory, config_injector):
|
||||
config_injector({'user_name_regex': '^[a-z]+$'})
|
||||
user = user_factory()
|
||||
with unittest.mock.patch('szurubooru.func.files.has'):
|
||||
files.has.return_value = False
|
||||
users.update_user_name(user, 'dummy')
|
||||
assert user.name == 'dummy'
|
||||
|
||||
def test_update_user_name_moves_avatar(user_factory, config_injector):
|
||||
config_injector({'user_name_regex': '^[a-z]+$'})
|
||||
user = user_factory(name='old')
|
||||
with unittest.mock.patch('szurubooru.func.files.has'), \
|
||||
unittest.mock.patch('szurubooru.func.files.move'):
|
||||
files.has.return_value = True
|
||||
users.update_user_name(user, 'new')
|
||||
files.move.assert_called_once_with('avatars/old.png', 'avatars/new.png')
|
||||
|
||||
def test_update_user_password_with_empty_string(user_factory):
|
||||
user = user_factory()
|
||||
with pytest.raises(users.InvalidPasswordError):
|
||||
users.update_user_password(user, None)
|
||||
|
||||
def test_update_user_password_with_invalid_string(user_factory, config_injector):
|
||||
config_injector({'password_regex': '^[a-z]+$'})
|
||||
user = user_factory()
|
||||
with pytest.raises(users.InvalidPasswordError):
|
||||
users.update_user_password(user, '0')
|
||||
|
||||
def test_update_user_password(user_factory, config_injector):
|
||||
config_injector({'password_regex': '^[a-z]+$'})
|
||||
user = user_factory()
|
||||
with unittest.mock.patch('szurubooru.func.auth.create_password'), \
|
||||
unittest.mock.patch('szurubooru.func.auth.get_password_hash'):
|
||||
auth.create_password.return_value = 'salt'
|
||||
auth.get_password_hash.return_value = 'hash'
|
||||
users.update_user_password(user, 'a')
|
||||
assert user.password_salt == 'salt'
|
||||
assert user.password_hash == 'hash'
|
||||
|
||||
def test_update_user_email_with_too_long_string(user_factory):
|
||||
user = user_factory()
|
||||
with pytest.raises(users.InvalidEmailError):
|
||||
users.update_user_email(user, 'a' * 300)
|
||||
|
||||
def test_update_user_email_with_invalid_email(user_factory):
|
||||
user = user_factory()
|
||||
with unittest.mock.patch('szurubooru.func.util.is_valid_email'):
|
||||
util.is_valid_email.return_value = False
|
||||
with pytest.raises(users.InvalidEmailError):
|
||||
users.update_user_email(user, 'a')
|
||||
|
||||
def test_update_user_email_with_empty_string(user_factory):
|
||||
user = user_factory()
|
||||
with unittest.mock.patch('szurubooru.func.util.is_valid_email'):
|
||||
util.is_valid_email.return_value = True
|
||||
users.update_user_email(user, '')
|
||||
assert user.email is None
|
||||
|
||||
def test_update_user_email(user_factory):
|
||||
user = user_factory()
|
||||
with unittest.mock.patch('szurubooru.func.util.is_valid_email'):
|
||||
util.is_valid_email.return_value = True
|
||||
users.update_user_email(user, 'a')
|
||||
assert user.email == 'a'
|
||||
|
||||
def test_update_user_rank_with_empty_string(user_factory):
|
||||
user = user_factory()
|
||||
auth_user = user_factory()
|
||||
with pytest.raises(users.InvalidRankError):
|
||||
users.update_user_rank(user, '', auth_user)
|
||||
|
||||
def test_update_user_rank_with_invalid_string(user_factory):
|
||||
user = user_factory()
|
||||
auth_user = user_factory()
|
||||
with pytest.raises(users.InvalidRankError):
|
||||
users.update_user_rank(user, 'invalid', auth_user)
|
||||
with pytest.raises(users.InvalidRankError):
|
||||
users.update_user_rank(user, 'anonymous', auth_user)
|
||||
with pytest.raises(users.InvalidRankError):
|
||||
users.update_user_rank(user, 'nobody', auth_user)
|
||||
|
||||
def test_update_user_rank_with_higher_rank_than_possible(user_factory):
|
||||
db.session.add(user_factory())
|
||||
user = user_factory()
|
||||
auth_user = user_factory()
|
||||
auth_user.rank = db.User.RANK_ANONYMOUS
|
||||
with pytest.raises(errors.AuthError):
|
||||
users.update_user_rank(user, 'regular', auth_user)
|
||||
with pytest.raises(errors.AuthError):
|
||||
users.update_user_rank(auth_user, 'regular', auth_user)
|
||||
|
||||
def test_update_user_rank(user_factory):
|
||||
db.session.add(user_factory())
|
||||
user = user_factory()
|
||||
auth_user = user_factory()
|
||||
auth_user.rank = db.User.RANK_ADMINISTRATOR
|
||||
users.update_user_rank(user, 'regular', auth_user)
|
||||
users.update_user_rank(auth_user, 'regular', auth_user)
|
||||
assert user.rank == db.User.RANK_REGULAR
|
||||
assert auth_user.rank == db.User.RANK_REGULAR
|
||||
|
||||
def test_update_user_avatar_with_invalid_style(user_factory):
|
||||
user = user_factory()
|
||||
with pytest.raises(users.InvalidAvatarError):
|
||||
users.update_user_avatar(user, 'invalid', b'')
|
||||
|
||||
def test_update_user_avatar_to_gravatar(user_factory):
|
||||
user = user_factory()
|
||||
users.update_user_avatar(user, 'gravatar')
|
||||
assert user.avatar_style == db.User.AVATAR_GRAVATAR
|
||||
|
||||
def test_update_user_avatar_to_empty_manual(user_factory):
|
||||
user = user_factory()
|
||||
with unittest.mock.patch('szurubooru.func.files.has'), \
|
||||
pytest.raises(users.InvalidAvatarError):
|
||||
files.has.return_value = False
|
||||
users.update_user_avatar(user, 'manual', b'')
|
||||
|
||||
def test_update_user_avatar_to_previous_manual(user_factory):
|
||||
user = user_factory()
|
||||
with unittest.mock.patch('szurubooru.func.files.has'):
|
||||
files.has.return_value = True
|
||||
users.update_user_avatar(user, 'manual', b'')
|
||||
|
||||
def test_update_user_avatar_to_new_manual(user_factory, config_injector):
|
||||
config_injector({'thumbnails': {'avatar_width': 500, 'avatar_height': 500}})
|
||||
user = user_factory()
|
||||
with unittest.mock.patch('szurubooru.func.files.save'):
|
||||
users.update_user_avatar(user, 'manual', EMPTY_PIXEL)
|
||||
assert user.avatar_style == db.User.AVATAR_MANUAL
|
||||
assert files.save.called
|
||||
|
||||
def test_bump_user_login_time(user_factory, fake_datetime):
|
||||
user = user_factory()
|
||||
with fake_datetime('1997-01-01'):
|
||||
users.bump_user_login_time(user)
|
||||
assert user.last_login_time == datetime(1997, 1, 1)
|
||||
|
||||
def test_reset_user_password(user_factory):
|
||||
with unittest.mock.patch('szurubooru.func.auth.create_password'), \
|
||||
unittest.mock.patch('szurubooru.func.auth.get_password_hash'):
|
||||
user = user_factory()
|
||||
auth.create_password.return_value = 'salt'
|
||||
auth.get_password_hash.return_value = 'hash'
|
||||
users.reset_user_password(user)
|
||||
assert user.password_salt == 'salt'
|
||||
assert user.password_hash == 'hash'
|
Loading…
Reference in a new issue