FitTrackee/fittrackee/tests/users/test_users_api.py
2023-05-21 16:13:14 +02:00

1718 lines
56 KiB
Python

import json
from datetime import datetime, timedelta
from io import BytesIO
from unittest.mock import MagicMock, patch
import pytest
from flask import Flask
from fittrackee import db
from fittrackee.users.models import User, UserDataExport, UserSportPreference
from fittrackee.utils import get_readable_duration
from fittrackee.workouts.models import Sport, Workout
from ..mixins import ApiTestCaseMixin
from ..utils import OAUTH_SCOPES, jsonify_dict
class TestGetUser(ApiTestCaseMixin):
def test_it_returns_error_if_user_has_no_admin_rights(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.get(
f'/api/users/{user_2.username}',
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_403(response)
def test_user_can_access_his_profile(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.get(
f'/api/users/{user_1.username}',
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert data['status'] == 'success'
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user['username'] == user_1.username
def test_it_gets_inactive_user(
self, app: Flask, user_1_admin: User, inactive_user: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
f'/api/users/{inactive_user.username}',
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert data['status'] == 'success'
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user == jsonify_dict(inactive_user.serialize(user_1_admin))
def test_it_gets_single_user_without_workouts(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
f'/api/users/{user_2.username}',
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert data['status'] == 'success'
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user == jsonify_dict(user_2.serialize(user_1_admin))
def test_it_gets_single_user_with_workouts(
self,
app: Flask,
user_1: User,
user_2_admin: User,
sport_1_cycling: Sport,
sport_2_running: Sport,
workout_cycling_user_1: Workout,
workout_running_user_1: Workout,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_2_admin.email
)
response = client.get(
f'/api/users/{user_1.username}',
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert data['status'] == 'success'
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user == jsonify_dict(user_1.serialize(user_2_admin))
def test_it_returns_error_if_user_does_not_exist(
self, app: Flask, user_1_admin: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users/not_existing',
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_404_with_entity(response, 'user')
@pytest.mark.parametrize(
'client_scope, can_access',
{**OAUTH_SCOPES, 'users:read': True}.items(),
)
def test_expected_scopes_are_defined(
self,
app: Flask,
user_1_admin: User,
client_scope: str,
can_access: bool,
) -> None:
(
client,
oauth_client,
access_token,
_,
) = self.create_oauth2_client_and_issue_token(
app, user_1_admin, scope=client_scope
)
response = client.get(
'/api/users/not_existing',
content_type='application/json',
headers=dict(Authorization=f'Bearer {access_token}'),
)
self.assert_response_scope(response, can_access)
class TestGetUsers(ApiTestCaseMixin):
def test_it_returns_error_if_user_has_no_admin_rights(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.get(
'/api/users',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_403(response)
def test_it_get_users_list_regardless_their_account_status(
self, app: Flask, user_1_admin: User, inactive_user: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert data['data']['users'][0] == jsonify_dict(
user_1_admin.serialize(user_1_admin)
)
assert data['data']['users'][1] == jsonify_dict(
inactive_user.serialize(user_1_admin)
)
assert data['data']['users'][2] == jsonify_dict(
user_3.serialize(user_1_admin)
)
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
@patch('fittrackee.users.users.USER_PER_PAGE', 2)
def test_it_gets_first_page_on_users_list(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?page=1',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 2
assert data['pagination'] == {
'has_next': True,
'has_prev': False,
'page': 1,
'pages': 2,
'total': 3,
}
@patch('fittrackee.users.users.USER_PER_PAGE', 2)
def test_it_gets_next_page_on_users_list(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?page=2',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 1
assert data['pagination'] == {
'has_next': False,
'has_prev': True,
'page': 2,
'pages': 2,
'total': 3,
}
def test_it_gets_empty_next_page_on_users_list(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?page=2',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 0
assert data['pagination'] == {
'has_next': False,
'has_prev': True,
'page': 2,
'pages': 1,
'total': 3,
}
def test_it_gets_user_list_with_2_per_page(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?per_page=2',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 2
assert data['pagination'] == {
'has_next': True,
'has_prev': False,
'page': 1,
'pages': 2,
'total': 3,
}
def test_it_gets_next_page_on_user_list_with_2_per_page(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?page=2&per_page=2',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 1
assert data['pagination'] == {
'has_next': False,
'has_prev': True,
'page': 2,
'pages': 2,
'total': 3,
}
def test_it_gets_users_list_ordered_by_username(
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=username',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'admin' in data['data']['users'][0]['username']
assert 'sam' in data['data']['users'][1]['username']
assert 'toto' in data['data']['users'][2]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_username_ascending(
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=username&order=asc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'admin' in data['data']['users'][0]['username']
assert 'sam' in data['data']['users'][1]['username']
assert 'toto' in data['data']['users'][2]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_username_descending(
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=username&order=desc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'toto' in data['data']['users'][0]['username']
assert 'sam' in data['data']['users'][1]['username']
assert 'admin' in data['data']['users'][2]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_creation_date(
self, app: Flask, user_2: User, user_3: User, user_1_admin: User
) -> None:
user_2.created_at = datetime.utcnow() - timedelta(days=1)
user_3.created_at = datetime.utcnow() - timedelta(hours=1)
user_1_admin.created_at = datetime.utcnow()
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=created_at',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'toto' in data['data']['users'][0]['username']
assert 'sam' in data['data']['users'][1]['username']
assert 'admin' in data['data']['users'][2]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_creation_date_ascending(
self, app: Flask, user_2: User, user_3: User, user_1_admin: User
) -> None:
user_2.created_at = datetime.utcnow() - timedelta(days=1)
user_3.created_at = datetime.utcnow() - timedelta(hours=1)
user_1_admin.created_at = datetime.utcnow()
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=created_at&order=asc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'toto' in data['data']['users'][0]['username']
assert 'sam' in data['data']['users'][1]['username']
assert 'admin' in data['data']['users'][2]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_creation_date_descending(
self, app: Flask, user_2: User, user_3: User, user_1_admin: User
) -> None:
user_2.created_at = datetime.utcnow() - timedelta(days=1)
user_3.created_at = datetime.utcnow() - timedelta(hours=1)
user_1_admin.created_at = datetime.utcnow()
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=created_at&order=desc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'admin' in data['data']['users'][0]['username']
assert 'sam' in data['data']['users'][1]['username']
assert 'toto' in data['data']['users'][2]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_admin_rights(
self, app: Flask, user_2: User, user_1_admin: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=admin',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'toto' in data['data']['users'][0]['username']
assert 'sam' in data['data']['users'][1]['username']
assert 'admin' in data['data']['users'][2]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_admin_rights_ascending(
self, app: Flask, user_2: User, user_1_admin: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=admin&order=asc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'toto' in data['data']['users'][0]['username']
assert 'sam' in data['data']['users'][1]['username']
assert 'admin' in data['data']['users'][2]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_admin_rights_descending(
self, app: Flask, user_2: User, user_3: User, user_1_admin: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=admin&order=desc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'admin' in data['data']['users'][0]['username']
assert 'toto' in data['data']['users'][1]['username']
assert 'sam' in data['data']['users'][2]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_workouts_count(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
sport_1_cycling: Sport,
workout_cycling_user_2: Workout,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=workouts_count',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'admin' in data['data']['users'][0]['username']
assert 0 == data['data']['users'][0]['nb_workouts']
assert 'sam' in data['data']['users'][1]['username']
assert 0 == data['data']['users'][1]['nb_workouts']
assert 'toto' in data['data']['users'][2]['username']
assert 1 == data['data']['users'][2]['nb_workouts']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_workouts_count_ascending(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
sport_1_cycling: Sport,
workout_cycling_user_2: Workout,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=workouts_count&order=asc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'admin' in data['data']['users'][0]['username']
assert 0 == data['data']['users'][0]['nb_workouts']
assert 'sam' in data['data']['users'][1]['username']
assert 0 == data['data']['users'][1]['nb_workouts']
assert 'toto' in data['data']['users'][2]['username']
assert 1 == data['data']['users'][2]['nb_workouts']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_ordered_by_account_status(
self,
app: Flask,
user_1_admin: User,
inactive_user: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=is_active',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 2
assert data['data']['users'][0]['username'] == inactive_user.username
assert not data['data']['users'][0]['is_active']
assert data['data']['users'][1]['username'] == user_1_admin.username
assert data['data']['users'][1]['is_active']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 2,
}
def test_it_gets_users_list_ordered_by_account_status_ascending(
self,
app: Flask,
user_1_admin: User,
inactive_user: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=is_active&order=asc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 2
assert data['data']['users'][0]['username'] == inactive_user.username
assert not data['data']['users'][0]['is_active']
assert data['data']['users'][1]['username'] == user_1_admin.username
assert data['data']['users'][1]['is_active']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 2,
}
def test_it_gets_users_list_ordered_by_account_status_descending(
self,
app: Flask,
user_1_admin: User,
inactive_user: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=is_active&order=desc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 2
assert data['data']['users'][0]['username'] == user_1_admin.username
assert data['data']['users'][0]['is_active']
assert data['data']['users'][1]['username'] == inactive_user.username
assert not data['data']['users'][1]['is_active']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 2,
}
def test_it_gets_users_list_ordered_by_workouts_count_descending(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
sport_1_cycling: Sport,
workout_cycling_user_2: Workout,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=workouts_count&order=desc',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'toto' in data['data']['users'][0]['username']
assert 1 == data['data']['users'][0]['nb_workouts']
assert 'admin' in data['data']['users'][1]['username']
assert 0 == data['data']['users'][1]['nb_workouts']
assert 'sam' in data['data']['users'][2]['username']
assert 0 == data['data']['users'][2]['nb_workouts']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_filtering_on_username(
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?q=toto',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 1
assert 'toto' in data['data']['users'][0]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 1,
}
def test_it_returns_username_matching_query(
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?q=oto',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 1
assert 'toto' in data['data']['users'][0]['username']
def test_it_filtering_on_username_is_case_insensitive(
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?q=TOTO',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 1
assert 'toto' in data['data']['users'][0]['username']
def test_it_returns_empty_users_list_filtering_on_username(
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?q=not_existing',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 0
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 0,
'total': 0,
}
def test_it_users_list_with_complex_query(
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.get(
'/api/users?order_by=username&order=desc&page=2&per_page=2',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 1
assert 'admin' in data['data']['users'][0]['username']
assert data['pagination'] == {
'has_next': False,
'has_prev': True,
'page': 2,
'pages': 2,
'total': 3,
}
@pytest.mark.parametrize(
'client_scope, can_access',
{**OAUTH_SCOPES, 'users:read': True}.items(),
)
def test_expected_scopes_are_defined(
self,
app: Flask,
user_1_admin: User,
client_scope: str,
can_access: bool,
) -> None:
(
client,
oauth_client,
access_token,
_,
) = self.create_oauth2_client_and_issue_token(
app, user_1_admin, scope=client_scope
)
response = client.get(
'/api/users',
content_type='application/json',
headers=dict(Authorization=f'Bearer {access_token}'),
)
self.assert_response_scope(response, can_access)
class TestGetUserPicture(ApiTestCaseMixin):
def test_it_return_error_if_user_has_no_picture(
self, app: Flask, user_1: User
) -> None:
client = app.test_client()
response = client.get(f'/api/users/{user_1.username}/picture')
self.assert_404_with_message(response, 'No picture.')
def test_it_returns_error_if_user_does_not_exist(
self, app: Flask, user_1: User
) -> None:
client = app.test_client()
response = client.get('/api/users/not_existing/picture')
self.assert_404_with_entity(response, 'user')
class TestUpdateUser(ApiTestCaseMixin):
def test_it_returns_error_if_payload_is_empty(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict()),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_400(response)
def test_it_returns_error_if_payload_for_admin_rights_is_invalid(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(admin="")),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 500
data = json.loads(response.data.decode())
assert 'error' in data['status']
assert (
'error, please try again or contact the administrator'
in data['message']
)
def test_it_returns_error_if_user_can_not_change_admin_rights(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(admin=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_403(response)
def test_it_adds_admin_rights_to_a_user(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(admin=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user['email'] == 'toto@toto.com'
assert user['admin'] is True
def test_it_removes_admin_rights_to_a_user(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(admin=False)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user['email'] == 'toto@toto.com'
assert user['admin'] is False
def test_it_does_not_send_email_when_only_admin_rights_update(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_password_change_email_mock: MagicMock,
user_reset_password_email: MagicMock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(admin=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
user_password_change_email_mock.send.assert_not_called()
user_reset_password_email.send.assert_not_called()
def test_it_resets_user_password(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
user_2_password = user_2.password
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(reset_password=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
assert user_2.password != user_2_password
def test_it_calls_password_change_email_when_password_reset_is_successful(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_password_change_email_mock: MagicMock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(reset_password=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
user_password_change_email_mock.send.assert_called_once_with(
{
'language': 'en',
'email': user_2.email,
},
{
'username': user_2.username,
'fittrackee_url': 'http://0.0.0.0:5000',
},
)
def test_it_does_not_call_password_change_email_when_email_sending_is_disabled( # noqa
self,
app_wo_email_activation: Flask,
user_1_admin: User,
user_2: User,
user_password_change_email_mock: MagicMock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app_wo_email_activation, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(reset_password=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
user_password_change_email_mock.send.assert_not_called()
def test_it_calls_reset_password_email_when_password_reset_is_successful(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_reset_password_email: MagicMock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
with patch(
'fittrackee.users.users.User.encode_password_reset_token',
return_value='xxx',
):
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(reset_password=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
user_reset_password_email.send.assert_called_once_with(
{
'language': 'en',
'email': user_2.email,
},
{
'expiration_delay': get_readable_duration(
app.config['PASSWORD_TOKEN_EXPIRATION_SECONDS'],
'en',
),
'username': user_2.username,
'password_reset_url': (
'http://0.0.0.0:5000/password-reset?token=xxx'
),
'fittrackee_url': 'http://0.0.0.0:5000',
},
)
def test_it_does_not_call_reset_password_email_when_email_sending_is_disabled( # noqa
self,
app_wo_email_activation: Flask,
user_1_admin: User,
user_2: User,
user_reset_password_email: MagicMock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app_wo_email_activation, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(reset_password=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
user_reset_password_email.send.assert_not_called()
def test_it_returns_error_when_updating_email_with_invalid_address(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(new_email=self.random_string())),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_400(response, 'valid email must be provided')
def test_it_returns_error_when_new_email_is_same_as_current_email(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(new_email=user_2.email)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_400(
response, 'new email must be different than curent email'
)
def test_it_does_not_send_email_when_error_on_updating_email(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_email_updated_to_new_address_mock: MagicMock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(new_email=self.random_string())),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
user_email_updated_to_new_address_mock.send.assert_not_called()
def test_it_updates_user_email_to_confirm_when_email_sending_is_enabled(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
new_email = 'new.' + user_2.email
user_2_email = user_2.email
user_2_confirmation_token = user_2.confirmation_token
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(new_email=new_email)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
assert user_2.email == user_2_email
assert user_2.email_to_confirm == new_email
assert user_2.confirmation_token != user_2_confirmation_token
def test_it_updates_user_email_when_email_sending_is_disabled(
self, app_wo_email_activation: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app_wo_email_activation, user_1_admin.email
)
new_email = 'new.' + user_2.email
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(new_email=new_email)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
assert user_2.email == new_email
assert user_2.email_to_confirm is None
assert user_2.confirmation_token is None
def test_it_calls_email_updated_to_new_address_when_password_reset_is_successful( # noqa
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_email_updated_to_new_address_mock: MagicMock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
new_email = 'new.' + user_2.email
expected_token = self.random_string()
with patch('secrets.token_urlsafe', return_value=expected_token):
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(new_email=new_email)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
user_email_updated_to_new_address_mock.send.assert_called_once_with(
{
'language': 'en',
'email': new_email,
},
{
'username': user_2.username,
'fittrackee_url': 'http://0.0.0.0:5000',
'email_confirmation_url': (
f'http://0.0.0.0:5000/email-update?token={expected_token}'
),
},
)
def test_it_does_not_call_email_updated_to_new_address_when_email_sending_is_disabled( # noqa
self,
app_wo_email_activation: Flask,
user_1_admin: User,
user_2: User,
user_email_updated_to_new_address_mock: MagicMock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app_wo_email_activation, user_1_admin.email
)
new_email = 'new.' + user_2.email
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(new_email=new_email)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
user_email_updated_to_new_address_mock.send.assert_not_called()
def test_it_activates_user_account(
self, app: Flask, user_1_admin: User, inactive_user: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{inactive_user.username}',
content_type='application/json',
data=json.dumps(dict(activate=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user['email'] == inactive_user.email
assert user['is_active'] is True
assert inactive_user.confirmation_token is None
def test_it_can_only_activate_user_account(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
data=json.dumps(dict(activate=False)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user['email'] == user_2.email
assert user['is_active'] is True
assert user_2.confirmation_token is None
@pytest.mark.parametrize(
'client_scope, can_access',
{**OAUTH_SCOPES, 'users:write': True}.items(),
)
def test_expected_scopes_are_defined(
self,
app: Flask,
user_1_admin: User,
user_2: User,
client_scope: str,
can_access: bool,
) -> None:
(
client,
oauth_client,
access_token,
_,
) = self.create_oauth2_client_and_issue_token(
app, user_1_admin, scope=client_scope
)
response = client.patch(
f'/api/users/{user_2.username}',
content_type='application/json',
headers=dict(Authorization=f'Bearer {access_token}'),
)
self.assert_response_scope(response, can_access)
class TestDeleteUser(ApiTestCaseMixin):
def test_user_can_delete_its_own_account(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.delete(
'/api/users/test',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
def test_user_with_workout_can_delete_its_own_account(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
client.post(
'/api/workouts',
data=dict(
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
data='{"sport_id": 1}',
),
headers=dict(
content_type='multipart/form-data',
Authorization=f'Bearer {auth_token}',
),
)
response = client.delete(
'/api/users/test',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
def test_user_with_preferences_can_delete_its_own_account(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
user_sport_1_preference: UserSportPreference,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.delete(
'/api/users/test',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
def test_user_with_picture_can_delete_its_own_account(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
client.post(
'/api/auth/picture',
data=dict(file=(BytesIO(b'avatar'), 'avatar.png')),
headers=dict(
content_type='multipart/form-data',
Authorization=f'Bearer {auth_token}',
),
)
response = client.delete(
'/api/users/test',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
def test_user_with_export_request_can_delete_its_own_account(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
db.session.add(UserDataExport(user_1.id))
db.session.commit()
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
client.post(
'/api/auth/picture',
data=dict(file=(BytesIO(b'avatar'), 'avatar.png')),
headers=dict(
content_type='multipart/form-data',
Authorization=f'Bearer {auth_token}',
),
)
response = client.delete(
'/api/users/test',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
def test_user_can_not_delete_another_user_account(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.delete(
'/api/users/toto',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_403(response)
def test_it_returns_error_when_deleting_non_existing_user(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.delete(
'/api/users/not_existing',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_404_with_entity(response, 'user')
def test_admin_can_delete_another_user_account(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.delete(
'/api/users/toto',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
def test_admin_can_delete_its_own_account(
self, app: Flask, user_1_admin: User, user_2_admin: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.delete(
'/api/users/admin',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
def test_admin_can_not_delete_its_own_account_if_no_other_admin(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.delete(
'/api/users/admin',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_403(
response,
'you can not delete your account, no other user has admin rights',
)
def test_it_enables_registration_after_user_delete_when_users_count_is_below_limit( # noqa
self,
app_with_3_users_max: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app_with_3_users_max, user_1_admin.email
)
client.delete(
'/api/users/toto',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
response = client.post(
'/api/auth/register',
data=json.dumps(
dict(
username=self.random_string(),
email=self.random_email(),
password=self.random_string(),
accepted_policy=True,
)
),
content_type='application/json',
)
assert response.status_code == 200
def test_it_does_not_enable_registration_on_user_delete_when_users_count_is_not_below_limit( # noqa
self,
app_with_3_users_max: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
user_1_paris: User,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app_with_3_users_max, user_1_admin.email
)
client.delete(
'/api/users/toto',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
response = client.post(
'/api/auth/register',
data=json.dumps(
dict(
username='justatest',
email='test@test.com',
password='12345678',
password_conf='12345678',
accepted_policy=True,
)
),
content_type='application/json',
)
self.assert_403(response, 'error, registration is disabled')
@pytest.mark.parametrize(
'client_scope, can_access',
{**OAUTH_SCOPES, 'users:write': True}.items(),
)
def test_expected_scopes_are_defined(
self,
app: Flask,
user_1_admin: User,
user_2: User,
client_scope: str,
can_access: bool,
) -> None:
(
client,
oauth_client,
access_token,
_,
) = self.create_oauth2_client_and_issue_token(
app, user_1_admin, scope=client_scope
)
response = client.delete(
f'/api/users/{user_2.username}',
content_type='application/json',
headers=dict(Authorization=f'Bearer {access_token}'),
)
self.assert_response_scope(response, can_access)