server/tests: add func.users tests

This commit is contained in:
rr- 2016-08-14 11:50:48 +02:00
parent 03c74cb5a3
commit 81dfbaec98
2 changed files with 410 additions and 16 deletions

View file

@ -12,10 +12,10 @@ class InvalidPasswordError(errors.ValidationError): pass
class InvalidRankError(errors.ValidationError): pass class InvalidRankError(errors.ValidationError): pass
class InvalidAvatarError(errors.ValidationError): pass class InvalidAvatarError(errors.ValidationError): pass
def _get_avatar_path(name): def get_avatar_path(user_name):
return 'avatars/' + name.lower() + '.png' return 'avatars/' + user_name.lower() + '.png'
def _get_avatar_url(user): def get_avatar_url(user):
assert user assert user
if user.avatar_style == user.AVATAR_GRAVATAR: if user.avatar_style == user.AVATAR_GRAVATAR:
assert user.email or user.name assert user.email or user.name
@ -27,7 +27,7 @@ def _get_avatar_url(user):
return '%s/avatars/%s.png' % ( return '%s/avatars/%s.png' % (
config.config['data_url'].rstrip('/'), user.name.lower()) 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 user
assert auth_user assert auth_user
if not force_show_email \ if not force_show_email \
@ -36,14 +36,14 @@ def _get_email(user, auth_user, force_show_email):
return False return False
return user.email return user.email
def _get_liked_post_count(user, auth_user): def get_liked_post_count(user, auth_user):
assert user assert user
assert auth_user assert auth_user
if auth_user.user_id != user.user_id: if auth_user.user_id != user.user_id:
return False return False
return user.liked_post_count return user.liked_post_count
def _get_disliked_post_count(user, auth_user): def get_disliked_post_count(user, auth_user):
assert user assert user
assert auth_user assert auth_user
if auth_user.user_id != user.user_id: 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, 'version': lambda: user.version,
'rank': lambda: user.rank, 'rank': lambda: user.rank,
'avatarStyle': lambda: user.avatar_style, 'avatarStyle': lambda: user.avatar_style,
'avatarUrl': lambda: _get_avatar_url(user), 'avatarUrl': lambda: get_avatar_url(user),
'commentCount': lambda: user.comment_count, 'commentCount': lambda: user.comment_count,
'uploadedPostCount': lambda: user.post_count, 'uploadedPostCount': lambda: user.post_count,
'favoritePostCount': lambda: user.favorite_post_count, 'favoritePostCount': lambda: user.favorite_post_count,
'likedPostCount': 'likedPostCount':
lambda: _get_liked_post_count(user, auth_user), lambda: get_liked_post_count(user, auth_user),
'dislikedPostCount': 'dislikedPostCount':
lambda: _get_disliked_post_count(user, auth_user), lambda: get_disliked_post_count(user, auth_user),
'email': 'email':
lambda: _get_email(user, auth_user, force_show_email), lambda: get_email(user, auth_user, force_show_email),
}, },
options) options)
@ -127,16 +127,16 @@ def update_user_name(user, name):
raise InvalidUserNameError('Name cannot be empty.') raise InvalidUserNameError('Name cannot be empty.')
if util.value_exceeds_column_size(name, db.User.name): if util.value_exceeds_column_size(name, db.User.name):
raise InvalidUserNameError('User name is too long.') 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 = name.strip()
name_regex = config.config['user_name_regex'] name_regex = config.config['user_name_regex']
if not re.match(name_regex, name): if not re.match(name_regex, name):
raise InvalidUserNameError( raise InvalidUserNameError(
'User name %r must satisfy regex %r.' % (name, name_regex)) 'User name %r must satisfy regex %r.' % (name, name_regex))
if user.name and files.has(_get_avatar_path(user.name)): other_user = try_get_user_by_name(name)
files.move(_get_avatar_path(user.name), _get_avatar_path(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 user.name = name
def update_user_password(user, password): 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.') raise errors.AuthError('Trying to set higher rank than your own.')
user.rank = rank user.rank = rank
def update_user_avatar(user, avatar_style, avatar_content): def update_user_avatar(user, avatar_style, avatar_content=None):
assert user assert user
if avatar_style == 'gravatar': if avatar_style == 'gravatar':
user.avatar_style = user.AVATAR_GRAVATAR user.avatar_style = user.AVATAR_GRAVATAR

View 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'