From b5b4ac8f92c70039275c93253a9c674d93e52055 Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 19 Mar 2022 20:34:36 +0100 Subject: [PATCH] API - refacto + remove unused endpoint for now --- .../tests/application/test_app_config_api.py | 2 +- fittrackee/tests/emails/test_email_service.py | 2 +- fittrackee/tests/fixtures/fixtures_emails.py | 6 + fittrackee/tests/fixtures/fixtures_users.py | 4 +- .../tests/{api_test_case.py => mixins.py} | 24 +- fittrackee/tests/users/test_auth_api.py | 849 +++++++++--------- fittrackee/tests/users/test_users_api.py | 9 +- fittrackee/tests/users/test_users_utils.py | 2 +- fittrackee/tests/utils.py | 25 + fittrackee/tests/workouts/test_records_api.py | 2 +- fittrackee/tests/workouts/test_sports_api.py | 2 +- fittrackee/tests/workouts/test_stats_api.py | 2 +- .../tests/workouts/test_workouts_api_0_get.py | 14 +- .../workouts/test_workouts_api_1_post.py | 15 +- .../workouts/test_workouts_api_2_patch.py | 14 +- .../workouts/test_workouts_api_3_delete.py | 28 +- fittrackee/users/auth.py | 61 -- fittrackee/users/users.py | 3 +- fittrackee/users/utils/random.py | 12 - 19 files changed, 499 insertions(+), 577 deletions(-) rename fittrackee/tests/{api_test_case.py => mixins.py} (87%) create mode 100644 fittrackee/tests/utils.py delete mode 100644 fittrackee/users/utils/random.py diff --git a/fittrackee/tests/application/test_app_config_api.py b/fittrackee/tests/application/test_app_config_api.py index 9cfddaa0..0e83d68d 100644 --- a/fittrackee/tests/application/test_app_config_api.py +++ b/fittrackee/tests/application/test_app_config_api.py @@ -5,7 +5,7 @@ from flask import Flask import fittrackee from fittrackee.users.models import User -from ..api_test_case import ApiTestCaseMixin +from ..mixins import ApiTestCaseMixin class TestGetConfig(ApiTestCaseMixin): diff --git a/fittrackee/tests/emails/test_email_service.py b/fittrackee/tests/emails/test_email_service.py index f3904121..cf319b60 100644 --- a/fittrackee/tests/emails/test_email_service.py +++ b/fittrackee/tests/emails/test_email_service.py @@ -7,7 +7,7 @@ from fittrackee import email_service from fittrackee.emails.email import EmailMessage from fittrackee.emails.exceptions import InvalidEmailUrlScheme -from ..api_test_case import CallArgsMixin +from ..mixins import CallArgsMixin from .template_results.password_reset_request import expected_en_text_body diff --git a/fittrackee/tests/fixtures/fixtures_emails.py b/fittrackee/tests/fixtures/fixtures_emails.py index 398217d5..e7d2bb0c 100644 --- a/fittrackee/tests/fixtures/fixtures_emails.py +++ b/fittrackee/tests/fixtures/fixtures_emails.py @@ -30,6 +30,12 @@ def user_password_change_email_mock() -> Iterator[MagicMock]: yield mock +@pytest.fixture() +def reset_password_email() -> Iterator[MagicMock]: + with patch('fittrackee.users.auth.reset_password_email') as mock: + yield mock + + @pytest.fixture() def user_reset_password_email() -> Iterator[MagicMock]: with patch('fittrackee.users.users.reset_password_email') as mock: diff --git a/fittrackee/tests/fixtures/fixtures_users.py b/fittrackee/tests/fixtures/fixtures_users.py index d1f7ce15..2928d7e6 100644 --- a/fittrackee/tests/fixtures/fixtures_users.py +++ b/fittrackee/tests/fixtures/fixtures_users.py @@ -60,7 +60,7 @@ def user_1_paris() -> User: @pytest.fixture() def user_2() -> User: - user = User(username='toto', email='toto@toto.com', password='87654321') + user = User(username='toto', email='toto@toto.com', password='12345678') db.session.add(user) db.session.commit() return user @@ -68,7 +68,7 @@ def user_2() -> User: @pytest.fixture() def user_2_admin() -> User: - user = User(username='toto', email='toto@toto.com', password='87654321') + user = User(username='toto', email='toto@toto.com', password='12345678') user.admin = True db.session.add(user) db.session.commit() diff --git a/fittrackee/tests/api_test_case.py b/fittrackee/tests/mixins.py similarity index 87% rename from fittrackee/tests/api_test_case.py rename to fittrackee/tests/mixins.py index 9683a933..7d06ff73 100644 --- a/fittrackee/tests/api_test_case.py +++ b/fittrackee/tests/mixins.py @@ -6,25 +6,35 @@ from flask.testing import FlaskClient from werkzeug.test import TestResponse from .custom_asserts import assert_errored_response +from .utils import random_email, random_string -class ApiTestCaseMixin: +class RandomMixin: + @staticmethod + def random_string( + length: Optional[int] = None, + prefix: Optional[str] = None, + suffix: Optional[str] = None, + ) -> str: + return random_string(length, prefix, suffix) + + @staticmethod + def random_email() -> str: + return random_email() + + +class ApiTestCaseMixin(RandomMixin): @staticmethod def get_test_client_and_auth_token( app: Flask, user_email: str ) -> Tuple[FlaskClient, str]: - """user_email must be user_1 or user_2 email""" client = app.test_client() resp_login = client.post( '/api/auth/login', data=json.dumps( dict( email=user_email, - password=( - '87654321' - if user_email == 'toto@toto.com' - else '12345678' - ), + password='12345678', ) ), content_type='application/json', diff --git a/fittrackee/tests/users/test_auth_api.py b/fittrackee/tests/users/test_auth_api.py index 0ba3e1f3..5ac077fe 100644 --- a/fittrackee/tests/users/test_auth_api.py +++ b/fittrackee/tests/users/test_auth_api.py @@ -8,11 +8,10 @@ from flask import Flask from freezegun import freeze_time from fittrackee.users.models import User, UserSportPreference -from fittrackee.users.utils.random import random_string from fittrackee.users.utils.token import get_user_token from fittrackee.workouts.models import Sport, Workout -from ..api_test_case import ApiTestCaseMixin +from ..mixins import ApiTestCaseMixin USER_AGENT = ( 'Mozilla/5.0 (X11; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0' @@ -20,74 +19,39 @@ USER_AGENT = ( class TestUserRegistration(ApiTestCaseMixin): - def test_user_can_register(self, app: Flask) -> None: + def test_it_returns_error_if_payload_is_empty(self, app: Flask) -> None: + client = app.test_client() + + response = client.post( + '/api/auth/register', + data=json.dumps(dict()), + content_type='application/json', + ) + + self.assert_400(response) + + def test_it_returns_error_if_username_is_missing(self, app: Flask) -> None: client = app.test_client() response = client.post( '/api/auth/register', data=json.dumps( dict( - username='justatest', - email='test@test.com', - password='12345678', + email=self.random_email(), + password=self.random_string(), ) ), content_type='application/json', ) - data = json.loads(response.data.decode()) - assert data['status'] == 'success' - assert data['message'] == 'successfully registered' - assert data['auth_token'] - assert response.content_type == 'application/json' - assert response.status_code == 201 + self.assert_400(response) @pytest.mark.parametrize( - 'input_username', - ['test', 'TEST'], + 'input_username_length', + [1, 31], ) - def test_it_returns_error_if_user_already_exists_with_same_username( - self, app: Flask, user_1: User, input_username: str - ) -> None: - client = app.test_client() - response = client.post( - '/api/auth/register', - data=json.dumps( - dict( - username=input_username, - email='another_email@test.com', - password='12345678', - ) - ), - content_type='application/json', - ) - - self.assert_400(response, 'sorry, that user already exists') - - @pytest.mark.parametrize( - 'input_email', - ['test@test.com', 'TEST@TEST.COM'], - ) - def test_it_returns_error_if_user_already_exists_with_same_email( - self, app: Flask, user_1: User, input_email: str - ) -> None: - client = app.test_client() - response = client.post( - '/api/auth/register', - data=json.dumps( - dict( - username='test', - email='test@test.com', - password='12345678', - ) - ), - content_type='application/json', - ) - - self.assert_400(response, 'sorry, that user already exists') - - def test_it_returns_error_if_username_is_too_short( - self, app: Flask + def test_it_returns_error_if_username_length_is_invalid( + self, app: Flask, input_username_length: int ) -> None: client = app.test_client() @@ -95,41 +59,15 @@ class TestUserRegistration(ApiTestCaseMixin): '/api/auth/register', data=json.dumps( dict( - username='', - email='test@test.com', - password='12345678', + username=self.random_string(length=input_username_length), + email=self.random_email(), + password=self.random_string(), ) ), content_type='application/json', ) - self.assert_400( - response, - ( - 'username: 3 to 30 characters required\n' - 'username: only alphanumeric characters and ' - 'the underscore character "_" allowed\n' - ), - ) - - def test_it_returns_error_if_username_is_too_long( - self, app: Flask - ) -> None: - client = app.test_client() - - response = client.post( - '/api/auth/register', - data=json.dumps( - dict( - username='a' * 31, - email='test@test.com', - password='12345678', - ) - ), - content_type='application/json', - ) - - self.assert_400(response, "username: 3 to 30 characters required\n") + self.assert_400(response, 'username: 3 to 30 characters required\n') @pytest.mark.parametrize( 'input_description,input_username', @@ -148,8 +86,8 @@ class TestUserRegistration(ApiTestCaseMixin): data=json.dumps( dict( username=input_username, - email='test@test.com', - password='12345678', + email=self.random_email(), + password=self.random_email(), ) ), content_type='application/json', @@ -161,22 +99,47 @@ class TestUserRegistration(ApiTestCaseMixin): 'the underscore character "_" allowed\n', ) - def test_it_returns_error_if_email_is_invalid(self, app: Flask) -> None: + @pytest.mark.parametrize( + 'text_transformation', + ['upper', 'lower'], + ) + def test_it_returns_error_if_user_already_exists_with_same_username( + self, app: Flask, user_1: User, text_transformation: str + ) -> None: + client = app.test_client() + response = client.post( + '/api/auth/register', + data=json.dumps( + dict( + username=( + user_1.username.upper() + if text_transformation == 'upper' + else user_1.username.lower() + ), + email=self.random_email(), + password=self.random_string(), + ) + ), + content_type='application/json', + ) + + self.assert_400(response, 'sorry, that user already exists') + + def test_it_returns_error_if_password_is_missing(self, app: Flask) -> None: client = app.test_client() response = client.post( '/api/auth/register', data=json.dumps( dict( - username='test', - email='test@test', - password='12345678', + username=self.random_string(), + email=self.random_email(), ) ), content_type='application/json', ) - self.assert_400(response, "email: valid email must be provided\n") + self.assert_400(response) def test_it_returns_error_if_password_is_too_short( self, app: Flask @@ -187,43 +150,15 @@ class TestUserRegistration(ApiTestCaseMixin): '/api/auth/register', data=json.dumps( dict( - username='test', - email='test@test.com', - password='1234567', + username=self.random_string(), + email=self.random_email(), + password=self.random_string(length=7), ) ), content_type='application/json', ) - self.assert_400(response, "password: 8 characters required\n") - - def test_it_returns_error_if_payload_is_invalid(self, app: Flask) -> None: - client = app.test_client() - response = client.post( - '/api/auth/register', - data=json.dumps(dict()), - content_type='application/json', - ) - data = json.loads(response.data.decode()) - assert response.status_code, 400 - assert 'invalid payload', data['message'] - assert 'error', data['status'] - - def test_it_returns_error_if_username_is_missing(self, app: Flask) -> None: - client = app.test_client() - - response = client.post( - '/api/auth/register', - data=json.dumps( - dict( - email='test@test.com', - password='12345678', - ) - ), - content_type='application/json', - ) - - self.assert_400(response) + self.assert_400(response, 'password: 8 characters required\n') def test_it_returns_error_if_email_is_missing(self, app: Flask) -> None: client = app.test_client() @@ -232,8 +167,8 @@ class TestUserRegistration(ApiTestCaseMixin): '/api/auth/register', data=json.dumps( dict( - username='test', - password='12345678', + username=self.random_string(), + password=self.random_string(), ) ), content_type='application/json', @@ -241,82 +176,74 @@ class TestUserRegistration(ApiTestCaseMixin): self.assert_400(response) - def test_it_returns_error_if_password_is_missing(self, app: Flask) -> None: + def test_it_returns_error_if_email_is_invalid(self, app: Flask) -> None: client = app.test_client() response = client.post( '/api/auth/register', data=json.dumps( dict( - username='test', - email='test@test.com', + username=self.random_string(), + email=self.random_string(), + password=self.random_string(), ) ), content_type='application/json', ) - self.assert_400(response) + self.assert_400(response, 'email: valid email must be provided\n') + + @pytest.mark.parametrize( + 'text_transformation', + ['upper', 'lower'], + ) + def test_it_returns_error_if_user_already_exists_with_same_email( + self, app: Flask, user_1: User, text_transformation: str + ) -> None: + client = app.test_client() + response = client.post( + '/api/auth/register', + data=json.dumps( + dict( + username=self.random_string(), + email=( + user_1.email.upper() + if text_transformation == 'upper' + else user_1.email.lower() + ), + password=self.random_string(), + ) + ), + content_type='application/json', + ) + + self.assert_400(response, 'sorry, that user already exists') + + def test_user_can_register(self, app: Flask) -> None: + client = app.test_client() + + response = client.post( + '/api/auth/register', + data=json.dumps( + dict( + username=self.random_string(), + email=self.random_email(), + password=self.random_string(), + ) + ), + content_type='application/json', + ) + + assert response.status_code == 201 + assert response.content_type == 'application/json' + data = json.loads(response.data.decode()) + assert data['status'] == 'success' + assert data['message'] == 'successfully registered' + assert data['auth_token'] class TestUserLogin(ApiTestCaseMixin): - @pytest.mark.parametrize( - 'input_email', - ['test@test.com', 'TEST@TEST.COM'], - ) - def test_user_can_login( - self, app: Flask, user_1: User, input_email: str - ) -> None: - client = app.test_client() - - response = client.post( - '/api/auth/login', - data=json.dumps(dict(email=input_email, password='12345678')), - content_type='application/json', - ) - - assert response.content_type == 'application/json' - assert response.status_code == 200 - data = json.loads(response.data.decode()) - assert data['status'] == 'success' - assert data['message'] == 'successfully logged in' - assert data['auth_token'] - - @pytest.mark.parametrize( - 'input_email', - ['test@test.com', 'TEST@TEST.COM'], - ) - def test_user_can_login_when_user_email_is_uppercase( - self, app: Flask, user_1_upper: User, input_email: str - ) -> None: - client = app.test_client() - - response = client.post( - '/api/auth/login', - data=json.dumps(dict(email=input_email, password='12345678')), - content_type='application/json', - ) - - assert response.content_type == 'application/json' - assert response.status_code == 200 - data = json.loads(response.data.decode()) - assert data['status'] == 'success' - assert data['message'] == 'successfully logged in' - assert data['auth_token'] - - def test_it_returns_error_if_user_does_not_exists( - self, app: Flask - ) -> None: - client = app.test_client() - - response = client.post( - '/api/auth/login', - data=json.dumps(dict(email='test@test.com', password='12345678')), - content_type='application/json', - ) - - self.assert_401(response, 'invalid credentials') - - def test_it_returns_error_on_invalid_payload(self, app: Flask) -> None: + def test_it_returns_error_if_payload_is_empty(self, app: Flask) -> None: client = app.test_client() response = client.post( @@ -327,6 +254,21 @@ class TestUserLogin(ApiTestCaseMixin): self.assert_400(response) + def test_it_returns_error_if_user_does_not_exists( + self, app: Flask + ) -> None: + client = app.test_client() + + response = client.post( + '/api/auth/login', + data=json.dumps( + dict(email=self.random_email(), password=self.random_string()) + ), + content_type='application/json', + ) + + self.assert_401(response, 'invalid credentials') + def test_it_returns_error_if_password_is_invalid( self, app: Flask, user_1: User ) -> None: @@ -334,64 +276,67 @@ class TestUserLogin(ApiTestCaseMixin): response = client.post( '/api/auth/login', - data=json.dumps(dict(email='test@test.com', password='123456789')), + data=json.dumps( + dict(email=user_1.email, password=self.random_email()) + ), content_type='application/json', ) self.assert_401(response, 'invalid credentials') + @pytest.mark.parametrize( + 'text_transformation', + ['upper', 'lower'], + ) + def test_user_can_login_regardless_username_case( + self, app: Flask, user_1: User, text_transformation: str + ) -> None: + client = app.test_client() -class TestUserLogout(ApiTestCaseMixin): - def test_user_can_logout(self, app: Flask, user_1: User) -> None: - - client, auth_token = self.get_test_client_and_auth_token( - app, user_1.email - ) - - response = client.get( - '/api/auth/logout', - headers=dict(Authorization=f'Bearer {auth_token}'), + response = client.post( + '/api/auth/login', + data=json.dumps( + dict( + email=( + user_1.email.upper() + if text_transformation == 'upper' + else user_1.email.lower() + ), + password='12345678', + ) + ), + content_type='application/json', ) + assert response.status_code == 200 + assert response.content_type == 'application/json' data = json.loads(response.data.decode()) assert data['status'] == 'success' - assert data['message'] == 'successfully logged out' - assert response.status_code == 200 + assert data['message'] == 'successfully logged in' + assert data['auth_token'] - def test_it_returns_error_with_expired_token( - self, app: Flask, user_1: User + +class TestUserProfile(ApiTestCaseMixin): + def test_it_returns_error_if_auth_token_is_missing( + self, app: Flask ) -> None: - now = datetime.utcnow() - client, auth_token = self.get_test_client_and_auth_token( - app, user_1.email - ) - with freeze_time(now + timedelta(seconds=4)): + client = app.test_client() - response = client.get( - '/api/auth/logout', - headers=dict(Authorization=f'Bearer {auth_token}'), - ) + response = client.get('/api/auth/profile') - self.assert_401(response, 'signature expired, please log in again') + self.assert_401(response, 'provide a valid auth token') - def test_it_returns_error_with_invalid_token(self, app: Flask) -> None: + def test_it_returns_error_if_auth_token_is_invalid( + self, app: Flask + ) -> None: client = app.test_client() response = client.get( - '/api/auth/logout', headers=dict(Authorization='Bearer invalid') + '/api/auth/profile', headers=dict(Authorization='Bearer invalid') ) self.assert_401(response, 'invalid token, please log in again') - def test_it_returns_error_with_invalid_headers(self, app: Flask) -> None: - client = app.test_client() - - response = client.get('/api/auth/logout', headers=dict()) - - self.assert_401(response, 'provide a valid auth token') - - -class TestUserProfile(ApiTestCaseMixin): def test_it_returns_user_minimal_profile( self, app: Flask, user_1: User ) -> None: @@ -404,6 +349,7 @@ class TestUserProfile(ApiTestCaseMixin): headers=dict(Authorization=f'Bearer {auth_token}'), ) + assert response.status_code == 200 data = json.loads(response.data.decode()) assert data['status'] == 'success' assert data['data'] is not None @@ -421,7 +367,6 @@ class TestUserProfile(ApiTestCaseMixin): assert data['data']['sports_list'] == [] assert data['data']['total_distance'] == 0 assert data['data']['total_duration'] == '0:00:00' - assert response.status_code == 200 def test_it_returns_user_full_profile( self, app: Flask, user_1_full: User @@ -435,6 +380,7 @@ class TestUserProfile(ApiTestCaseMixin): headers=dict(Authorization=f'Bearer {auth_token}'), ) + assert response.status_code == 200 data = json.loads(response.data.decode()) assert data['status'] == 'success' assert data['data'] is not None @@ -457,7 +403,6 @@ class TestUserProfile(ApiTestCaseMixin): assert data['data']['sports_list'] == [] assert data['data']['total_distance'] == 0 assert data['data']['total_duration'] == '0:00:00' - assert response.status_code == 200 def test_it_returns_user_profile_with_workouts( self, @@ -477,6 +422,7 @@ class TestUserProfile(ApiTestCaseMixin): headers=dict(Authorization=f'Bearer {auth_token}'), ) + assert response.status_code == 200 data = json.loads(response.data.decode()) assert data['status'] == 'success' assert data['data'] is not None @@ -492,79 +438,9 @@ class TestUserProfile(ApiTestCaseMixin): assert data['data']['sports_list'] == [1, 2] assert data['data']['total_distance'] == 22 assert data['data']['total_duration'] == '2:40:00' - assert response.status_code == 200 - - def test_it_returns_error_if_headers_are_invalid(self, app: Flask) -> None: - client = app.test_client() - - response = client.get( - '/api/auth/profile', headers=dict(Authorization='Bearer invalid') - ) - - self.assert_401(response, 'invalid token, please log in again') class TestUserProfileUpdate(ApiTestCaseMixin): - def test_it_updates_user_profile(self, app: Flask, user_1: User) -> None: - client, auth_token = self.get_test_client_and_auth_token( - app, user_1.email - ) - - response = client.post( - '/api/auth/profile/edit', - content_type='application/json', - data=json.dumps( - dict( - first_name='John', - last_name='Doe', - location='Somewhere', - bio='Nothing to tell', - birth_date='1980-01-01', - ) - ), - headers=dict(Authorization=f'Bearer {auth_token}'), - ) - - data = json.loads(response.data.decode()) - assert data['status'] == 'success' - assert data['message'] == 'user profile updated' - assert response.status_code == 200 - assert data['data']['username'] == 'test' - assert data['data']['email'] == 'test@test.com' - assert not data['data']['admin'] - assert data['data']['created_at'] - assert data['data']['first_name'] == 'John' - assert data['data']['last_name'] == 'Doe' - assert data['data']['birth_date'] - assert data['data']['bio'] == 'Nothing to tell' - assert data['data']['imperial_units'] is False - assert data['data']['location'] == 'Somewhere' - assert data['data']['timezone'] is None - assert data['data']['weekm'] is False - assert data['data']['language'] is None - assert data['data']['nb_sports'] == 0 - assert data['data']['nb_workouts'] == 0 - assert data['data']['records'] == [] - assert data['data']['sports_list'] == [] - assert data['data']['total_distance'] == 0 - assert data['data']['total_duration'] == '0:00:00' - - def test_it_returns_error_if_fields_are_missing( - self, app: Flask, user_1: User - ) -> None: - client, auth_token = self.get_test_client_and_auth_token( - app, user_1.email - ) - - response = client.post( - '/api/auth/profile/edit', - content_type='application/json', - data=json.dumps(dict(first_name='John')), - headers=dict(Authorization=f'Bearer {auth_token}'), - ) - - self.assert_400(response) - def test_it_returns_error_if_payload_is_empty( self, app: Flask, user_1: User ) -> None: @@ -581,6 +457,71 @@ class TestUserProfileUpdate(ApiTestCaseMixin): self.assert_400(response) + def test_it_returns_error_if_fields_are_missing( + self, app: Flask, user_1: User + ) -> None: + client, auth_token = self.get_test_client_and_auth_token( + app, user_1.email + ) + + response = client.post( + '/api/auth/profile/edit', + content_type='application/json', + data=json.dumps(dict(first_name=self.random_string())), + headers=dict(Authorization=f'Bearer {auth_token}'), + ) + + self.assert_400(response) + + def test_it_updates_user_profile(self, app: Flask, user_1: User) -> None: + client, auth_token = self.get_test_client_and_auth_token( + app, user_1.email + ) + first_name = self.random_string() + last_name = self.random_string() + location = self.random_string() + bio = self.random_string() + birth_date = '1980-01-01' + + response = client.post( + '/api/auth/profile/edit', + content_type='application/json', + data=json.dumps( + dict( + first_name=first_name, + last_name=last_name, + location=location, + bio=bio, + birth_date=birth_date, + ) + ), + headers=dict(Authorization=f'Bearer {auth_token}'), + ) + + assert response.status_code == 200 + data = json.loads(response.data.decode()) + assert data['status'] == 'success' + assert data['message'] == 'user profile updated' + assert data['data']['username'] == user_1.username + assert data['data']['email'] == user_1.email + assert not data['data']['admin'] + assert data['data']['created_at'] + assert data['data']['first_name'] == first_name + assert data['data']['last_name'] == last_name + assert data['data']['birth_date'] == 'Tue, 01 Jan 1980 00:00:00 GMT' + assert data['data']['bio'] == bio + assert data['data']['imperial_units'] is False + assert data['data']['location'] == location + assert data['data']['timezone'] is None + assert data['data']['weekm'] is False + assert data['data']['language'] is None + assert data['data']['nb_sports'] == 0 + assert data['data']['nb_workouts'] == 0 + assert data['data']['records'] == [] + assert data['data']['sports_list'] == [] + assert data['data']['total_distance'] == 0 + assert data['data']['total_duration'] == '0:00:00' + class TestUserAccountUpdate(ApiTestCaseMixin): @staticmethod @@ -622,7 +563,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): data=json.dumps( dict( email=user_1.email, - new_password=random_string(), + new_password=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -643,7 +584,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): data=json.dumps( dict( password='12345678', - new_password=random_string(), + new_password=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -664,8 +605,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin): data=json.dumps( dict( email=user_1.email, - password=random_string(), - new_password=random_string(), + password=self.random_string(), + new_password=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -691,8 +632,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin): data=json.dumps( dict( email=user_1.email, - password=random_string(), - new_password=random_string(), + password=self.random_string(), + new_password=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -780,7 +721,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): content_type='application/json', data=json.dumps( dict( - email=random_string(), + email=self.random_string(), password='12345678', ) ), @@ -872,7 +813,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): app, user_1.email ) new_email = 'new.email@example.com' - expected_token = random_string() + expected_token = self.random_string() with patch('secrets.token_urlsafe', return_value=expected_token): client.patch( @@ -916,7 +857,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): app, user_1.email ) new_email = 'new.email@example.com' - expected_token = random_string() + expected_token = self.random_string() with patch('secrets.token_urlsafe', return_value=expected_token): client.patch( @@ -953,7 +894,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): dict( email=user_1.email, password='12345678', - new_password=random_string(length=3), + new_password=self.random_string(length=3), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -981,7 +922,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): dict( email=user_1.email, password='12345678', - new_password=random_string(), + new_password=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -1004,7 +945,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): client, auth_token = self.get_test_client_and_auth_token( app, user_1.email ) - new_password = random_string() + new_password = self.random_string() response = client.patch( '/api/auth/profile/edit/account', @@ -1041,7 +982,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): dict( email=user_1.email, password='12345678', - new_password=random_string(), + new_password=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -1080,7 +1021,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): dict( email=user_1.email, password='12345678', - new_password=random_string(), + new_password=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -1111,7 +1052,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): dict( email=new_email, password='12345678', - new_password=random_string(), + new_password=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -1144,7 +1085,7 @@ class TestUserAccountUpdate(ApiTestCaseMixin): dict( email='new.email@example.com', password='12345678', - new_password=random_string(), + new_password=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -1156,6 +1097,38 @@ class TestUserAccountUpdate(ApiTestCaseMixin): class TestUserPreferencesUpdate(ApiTestCaseMixin): + def test_it_returns_error_if_payload_is_empty( + self, app: Flask, user_1: User + ) -> None: + client, auth_token = self.get_test_client_and_auth_token( + app, user_1.email + ) + + response = client.post( + '/api/auth/profile/edit/preferences', + 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_fields_are_missing( + self, app: Flask, user_1: User + ) -> None: + client, auth_token = self.get_test_client_and_auth_token( + app, user_1.email + ) + + response = client.post( + '/api/auth/profile/edit/preferences', + content_type='application/json', + data=json.dumps(dict(weekm=True)), + headers=dict(Authorization=f'Bearer {auth_token}'), + ) + + self.assert_400(response) + def test_it_updates_user_preferences( self, app: Flask, user_1: User ) -> None: @@ -1177,12 +1150,12 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin): headers=dict(Authorization=f'Bearer {auth_token}'), ) + assert response.status_code == 200 data = json.loads(response.data.decode()) assert data['status'] == 'success' assert data['message'] == 'user preferences updated' - assert response.status_code == 200 - assert data['data']['username'] == 'test' - assert data['data']['email'] == 'test@test.com' + assert data['data']['username'] == user_1.username + assert data['data']['email'] == user_1.email assert not data['data']['admin'] assert data['data']['created_at'] assert data['data']['first_name'] is None @@ -1201,38 +1174,6 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin): assert data['data']['total_distance'] == 0 assert data['data']['total_duration'] == '0:00:00' - def test_it_returns_error_if_fields_are_missing( - self, app: Flask, user_1: User - ) -> None: - client, auth_token = self.get_test_client_and_auth_token( - app, user_1.email - ) - - response = client.post( - '/api/auth/profile/edit/preferences', - content_type='application/json', - data=json.dumps(dict(weekm=True)), - headers=dict(Authorization=f'Bearer {auth_token}'), - ) - - self.assert_400(response) - - def test_it_returns_error_if_payload_is_empty( - self, app: Flask, user_1: User - ) -> None: - client, auth_token = self.get_test_client_and_auth_token( - app, user_1.email - ) - - response = client.post( - '/api/auth/profile/edit/preferences', - content_type='application/json', - data=json.dumps(dict()), - headers=dict(Authorization=f'Bearer {auth_token}'), - ) - - self.assert_400(response) - class TestUserSportPreferencesUpdate(ApiTestCaseMixin): def test_it_returns_error_if_payload_is_empty( @@ -1312,7 +1253,7 @@ class TestUserSportPreferencesUpdate(ApiTestCaseMixin): data=json.dumps( dict( sport_id=sport_1_cycling.id, - color='invalid', + color=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), @@ -1472,42 +1413,6 @@ class TestUserSportPreferencesReset(ApiTestCaseMixin): class TestUserPicture(ApiTestCaseMixin): - def test_it_updates_user_picture(self, app: Flask, user_1: User) -> None: - client, auth_token = self.get_test_client_and_auth_token( - app, user_1.email - ) - - response = 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}', - ), - ) - - data = json.loads(response.data.decode()) - assert data['status'] == 'success' - assert data['message'] == 'user picture updated' - assert response.status_code == 200 - assert 'avatar.png' in user_1.picture - - response = client.post( - '/api/auth/picture', - data=dict(file=(BytesIO(b'avatar2'), 'avatar2.png')), - headers=dict( - content_type='multipart/form-data', - Authorization=f'Bearer {auth_token}', - ), - ) - - data = json.loads(response.data.decode()) - assert data['status'] == 'success' - assert data['message'] == 'user picture updated' - assert response.status_code == 200 - assert 'avatar.png' not in user_1.picture - assert 'avatar2.png' in user_1.picture - def test_it_returns_error_if_file_is_missing( self, app: Flask, user_1: User ) -> None: @@ -1599,6 +1504,42 @@ class TestUserPicture(ApiTestCaseMixin): ) assert 'data' not in data + def test_it_updates_user_picture(self, app: Flask, user_1: User) -> None: + client, auth_token = self.get_test_client_and_auth_token( + app, user_1.email + ) + + response = 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}', + ), + ) + + data = json.loads(response.data.decode()) + assert data['status'] == 'success' + assert data['message'] == 'user picture updated' + assert response.status_code == 200 + assert 'avatar.png' in user_1.picture + + response = client.post( + '/api/auth/picture', + data=dict(file=(BytesIO(b'avatar2'), 'avatar2.png')), + headers=dict( + content_type='multipart/form-data', + Authorization=f'Bearer {auth_token}', + ), + ) + + data = json.loads(response.data.decode()) + assert data['status'] == 'success' + assert data['message'] == 'user picture updated' + assert response.status_code == 200 + assert 'avatar.png' not in user_1.picture + assert 'avatar2.png' in user_1.picture + class TestRegistrationConfiguration(ApiTestCaseMixin): def test_it_returns_error_if_it_exceeds_max_users( @@ -1614,9 +1555,9 @@ class TestRegistrationConfiguration(ApiTestCaseMixin): '/api/auth/register', data=json.dumps( dict( - username='user4', - email='user4@test.com', - password='12345678', + username=self.random_string(), + email=self.random_email(), + password=self.random_string(), ) ), content_type='application/json', @@ -1635,9 +1576,9 @@ class TestRegistrationConfiguration(ApiTestCaseMixin): '/api/auth/register', data=json.dumps( dict( - username='sam', - email='sam@test.com', - password='12345678', + username=self.random_string(), + email=self.random_email(), + password=self.random_string(), ) ), content_type='application/json', @@ -1647,9 +1588,9 @@ class TestRegistrationConfiguration(ApiTestCaseMixin): '/api/auth/register', data=json.dumps( dict( - username='new', - email='new@test.com', - password='12345678', + username=self.random_string(), + email=self.random_email(), + password=self.random_string(), ) ), content_type='application/json', @@ -1657,7 +1598,7 @@ class TestRegistrationConfiguration(ApiTestCaseMixin): self.assert_403(response, 'error, registration is disabled') - def test_it_does_not_disable_registration_on_user_registration( + def test_it_does_not_disable_registration_if_users_count_below_limit( self, app_with_3_users_max: Flask, user_1: User, @@ -1667,34 +1608,57 @@ class TestRegistrationConfiguration(ApiTestCaseMixin): '/api/auth/register', data=json.dumps( dict( - username='sam', - email='sam@test.com', - password='12345678', + username=self.random_string(), + email=self.random_email(), + password=self.random_string(), ) ), content_type='application/json', ) + response = client.post( '/api/auth/register', data=json.dumps( dict( - username='new', - email='new@test.com', - password='12345678', + username=self.random_string(), + email=self.random_email(), + password=self.random_string(), ) ), content_type='application/json', ) + assert response.status_code == 201 class TestPasswordResetRequest(ApiTestCaseMixin): - @patch('smtplib.SMTP_SSL') - @patch('smtplib.SMTP') + def test_it_returns_error_on_empty_payload(self, app: Flask) -> None: + client = app.test_client() + + response = client.post( + '/api/auth/password/reset-request', + data=json.dumps(dict()), + content_type='application/json', + ) + + self.assert_400(response) + + def test_it_returns_error_on_invalid_payload(self, app: Flask) -> None: + client = app.test_client() + + response = client.post( + '/api/auth/password/reset-request', + data=json.dumps(dict(username=self.random_string())), + content_type='application/json', + ) + + self.assert_400(response) + def test_it_requests_password_reset_when_user_exists( - self, mock_smtp: Mock, mock_smtp_ssl: Mock, app: Flask, user_1: User + self, app: Flask, user_1: User, user_reset_password_email: Mock ) -> None: client = app.test_client() + response = client.post( '/api/auth/password/reset-request', data=json.dumps(dict(email='test@test.com')), @@ -1706,6 +1670,37 @@ class TestPasswordResetRequest(ApiTestCaseMixin): assert data['status'] == 'success' assert data['message'] == 'password reset request processed' + def test_it_calls_reset_password_email_when_user_exists( + self, app: Flask, user_1: User, reset_password_email: Mock + ) -> None: + client = app.test_client() + token = self.random_string() + + with patch('jwt.encode', return_value=token): + client.post( + '/api/auth/password/reset-request', + data=json.dumps(dict(email='test@test.com')), + content_type='application/json', + environ_base={'HTTP_USER_AGENT': USER_AGENT}, + ) + + reset_password_email.send.assert_called_once_with( + { + 'language': 'en', + 'email': user_1.email, + }, + { + 'expiration_delay': '3 seconds', + 'username': user_1.username, + 'password_reset_url': ( + f'http://0.0.0.0:5000/password-reset?token={token}' + ), + 'fittrackee_url': 'http://0.0.0.0:5000', + 'operating_system': 'linux', + 'browser_name': 'firefox', + }, + ) + def test_it_does_not_return_error_when_user_does_not_exist( self, app: Flask ) -> None: @@ -1722,27 +1717,18 @@ class TestPasswordResetRequest(ApiTestCaseMixin): assert data['status'] == 'success' assert data['message'] == 'password reset request processed' - def test_it_returns_error_on_invalid_payload(self, app: Flask) -> None: + def test_it_does_not_call_reset_password_email_when_user_does_not_exist( + self, app: Flask, reset_password_email: Mock + ) -> None: client = app.test_client() - response = client.post( + client.post( '/api/auth/password/reset-request', - data=json.dumps(dict(usernmae='test')), + data=json.dumps(dict(email='test@test.com')), content_type='application/json', ) - self.assert_400(response) - - def test_it_returns_error_on_empty_payload(self, app: Flask) -> None: - client = app.test_client() - - response = client.post( - '/api/auth/password/reset-request', - data=json.dumps(dict()), - content_type='application/json', - ) - - self.assert_400(response) + reset_password_email.assert_not_called() class TestPasswordUpdate(ApiTestCaseMixin): @@ -1764,7 +1750,7 @@ class TestPasswordUpdate(ApiTestCaseMixin): '/api/auth/password/update', data=json.dumps( dict( - password='12345678', + password=self.random_string(), ) ), content_type='application/json', @@ -1779,7 +1765,7 @@ class TestPasswordUpdate(ApiTestCaseMixin): '/api/auth/password/update', data=json.dumps( dict( - token='xxx', + token=self.random_string(), ) ), content_type='application/json', @@ -1796,8 +1782,7 @@ class TestPasswordUpdate(ApiTestCaseMixin): data=json.dumps( dict( token=token, - password='12345678', - password_conf='12345678', + password=self.random_string(), ) ), content_type='application/json', @@ -1818,7 +1803,7 @@ class TestPasswordUpdate(ApiTestCaseMixin): data=json.dumps( dict( token=token, - password='12345678', + password=self.random_string(), ) ), content_type='application/json', @@ -1839,7 +1824,7 @@ class TestPasswordUpdate(ApiTestCaseMixin): data=json.dumps( dict( token=token, - password='1234567', + password=self.random_string(length=7), ) ), content_type='application/json', @@ -1861,7 +1846,7 @@ class TestPasswordUpdate(ApiTestCaseMixin): data=json.dumps( dict( token=token, - password='1234567', + password=self.random_string(length=7), ) ), content_type='application/json', @@ -1883,7 +1868,7 @@ class TestPasswordUpdate(ApiTestCaseMixin): data=json.dumps( dict( token=token, - password='12345678', + password=self.random_string(), ) ), content_type='application/json', @@ -1908,7 +1893,7 @@ class TestPasswordUpdate(ApiTestCaseMixin): data=json.dumps( dict( token=token, - password='12345678', + password=self.random_string(), ) ), content_type='application/json', @@ -1947,7 +1932,7 @@ class TestEmailUpdateWitUnauthenticatedUser(ApiTestCaseMixin): response = client.post( '/api/auth/email/update', - data=json.dumps(dict(token=random_string())), + data=json.dumps(dict(token=self.random_string())), content_type='application/json', ) @@ -1956,21 +1941,21 @@ class TestEmailUpdateWitUnauthenticatedUser(ApiTestCaseMixin): def test_it_does_not_update_email_if_token_mismatches( self, app: Flask, user_1: User ) -> None: - user_1.confirmation_token = random_string() + user_1.confirmation_token = self.random_string() new_email = 'new.email@example.com' user_1.email_to_confirm = new_email client = app.test_client() response = client.post( '/api/auth/email/update', - data=json.dumps(dict(token=random_string())), + data=json.dumps(dict(token=self.random_string())), content_type='application/json', ) self.assert_400(response) def test_it_updates_email(self, app: Flask, user_1: User) -> None: - token = random_string() + token = self.random_string() user_1.confirmation_token = token new_email = 'new.email@example.com' user_1.email_to_confirm = new_email diff --git a/fittrackee/tests/users/test_users_api.py b/fittrackee/tests/users/test_users_api.py index a109a1af..e05a34da 100644 --- a/fittrackee/tests/users/test_users_api.py +++ b/fittrackee/tests/users/test_users_api.py @@ -6,11 +6,10 @@ from unittest.mock import MagicMock, patch from flask import Flask from fittrackee.users.models import User, UserSportPreference -from fittrackee.users.utils.random import random_string from fittrackee.utils import get_readable_duration from fittrackee.workouts.models import Sport, Workout -from ..api_test_case import ApiTestCaseMixin +from ..mixins import ApiTestCaseMixin class TestGetUser(ApiTestCaseMixin): @@ -1108,7 +1107,7 @@ class TestUpdateUser(ApiTestCaseMixin): response = client.patch( f'/api/users/{user_2.username}', content_type='application/json', - data=json.dumps(dict(new_email=random_string())), + data=json.dumps(dict(new_email=self.random_string())), headers=dict(Authorization=f'Bearer {auth_token}'), ) @@ -1128,7 +1127,7 @@ class TestUpdateUser(ApiTestCaseMixin): client.patch( f'/api/users/{user_2.username}', content_type='application/json', - data=json.dumps(dict(new_email=random_string())), + data=json.dumps(dict(new_email=self.random_string())), headers=dict(Authorization=f'Bearer {auth_token}'), ) @@ -1166,7 +1165,7 @@ class TestUpdateUser(ApiTestCaseMixin): app, user_1_admin.email ) new_email = 'new.' + user_2.email - expected_token = random_string() + expected_token = self.random_string() with patch('secrets.token_urlsafe', return_value=expected_token): response = client.patch( diff --git a/fittrackee/tests/users/test_users_utils.py b/fittrackee/tests/users/test_users_utils.py index 03e4eb15..dd972ebf 100644 --- a/fittrackee/tests/users/test_users_utils.py +++ b/fittrackee/tests/users/test_users_utils.py @@ -3,6 +3,7 @@ from unittest.mock import patch import pytest from flask import Flask +from fittrackee.tests.utils import random_string from fittrackee.users.exceptions import UserNotFoundException from fittrackee.users.models import User from fittrackee.users.utils.admin import set_admin_rights @@ -12,7 +13,6 @@ from fittrackee.users.utils.controls import ( is_valid_email, register_controls, ) -from fittrackee.users.utils.random import random_string class TestSetAdminRights: diff --git a/fittrackee/tests/utils.py b/fittrackee/tests/utils.py new file mode 100644 index 00000000..9f513a3b --- /dev/null +++ b/fittrackee/tests/utils.py @@ -0,0 +1,25 @@ +import random +import string +from typing import Optional + + +def random_string( + length: Optional[int] = None, + prefix: Optional[str] = None, + suffix: Optional[str] = None, +) -> str: + if length is None: + length = 10 + random_str = ''.join( + random.choice(string.ascii_letters + string.digits) + for _ in range(length) + ) + return ( + f'{"" if prefix is None else prefix}' + f'{random_str}' + f'{"" if suffix is None else suffix}' + ) + + +def random_email() -> str: + return random_string(suffix='@example.com') diff --git a/fittrackee/tests/workouts/test_records_api.py b/fittrackee/tests/workouts/test_records_api.py index eb7ce221..0985af7d 100644 --- a/fittrackee/tests/workouts/test_records_api.py +++ b/fittrackee/tests/workouts/test_records_api.py @@ -5,7 +5,7 @@ from flask import Flask from fittrackee.users.models import User from fittrackee.workouts.models import Sport, Workout -from ..api_test_case import ApiTestCaseMixin +from ..mixins import ApiTestCaseMixin class TestGetRecords(ApiTestCaseMixin): diff --git a/fittrackee/tests/workouts/test_sports_api.py b/fittrackee/tests/workouts/test_sports_api.py index 4cc76157..b2d3f2e7 100644 --- a/fittrackee/tests/workouts/test_sports_api.py +++ b/fittrackee/tests/workouts/test_sports_api.py @@ -6,7 +6,7 @@ from fittrackee import db from fittrackee.users.models import User, UserSportPreference from fittrackee.workouts.models import Sport, Workout -from ..api_test_case import ApiTestCaseMixin +from ..mixins import ApiTestCaseMixin expected_sport_1_cycling_result = { 'id': 1, diff --git a/fittrackee/tests/workouts/test_stats_api.py b/fittrackee/tests/workouts/test_stats_api.py index b9ed5442..bbf3d6ce 100644 --- a/fittrackee/tests/workouts/test_stats_api.py +++ b/fittrackee/tests/workouts/test_stats_api.py @@ -5,7 +5,7 @@ from flask import Flask from fittrackee.users.models import User from fittrackee.workouts.models import Sport, Workout -from ..api_test_case import ApiTestCaseMixin +from ..mixins import ApiTestCaseMixin class TestGetStatsByTime(ApiTestCaseMixin): diff --git a/fittrackee/tests/workouts/test_workouts_api_0_get.py b/fittrackee/tests/workouts/test_workouts_api_0_get.py index d6d537ff..cad7428f 100644 --- a/fittrackee/tests/workouts/test_workouts_api_0_get.py +++ b/fittrackee/tests/workouts/test_workouts_api_0_get.py @@ -7,7 +7,7 @@ from flask import Flask from fittrackee.users.models import User from fittrackee.workouts.models import Sport, Workout -from ..api_test_case import ApiTestCaseMixin +from ..mixins import ApiTestCaseMixin from .utils import get_random_short_id @@ -73,19 +73,13 @@ class TestGetWorkouts(ApiTestCaseMixin): workout_cycling_user_1: Workout, workout_running_user_1: Workout, ) -> None: - client = app.test_client() - resp_login = client.post( - '/api/auth/login', - data=json.dumps(dict(email='toto@toto.com', password='87654321')), - content_type='application/json', + client, auth_token = self.get_test_client_and_auth_token( + app, user_2.email ) response = client.get( '/api/workouts', - headers=dict( - Authorization='Bearer ' - + json.loads(resp_login.data.decode())['auth_token'] - ), + headers=dict(Authorization=f'Bearer {auth_token}'), ) data = json.loads(response.data.decode()) diff --git a/fittrackee/tests/workouts/test_workouts_api_1_post.py b/fittrackee/tests/workouts/test_workouts_api_1_post.py index a6e7693c..6957bbba 100644 --- a/fittrackee/tests/workouts/test_workouts_api_1_post.py +++ b/fittrackee/tests/workouts/test_workouts_api_1_post.py @@ -12,7 +12,7 @@ from fittrackee.users.models import User from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.utils.short_id import decode_short_id -from ..api_test_case import ApiTestCaseMixin, CallArgsMixin +from ..mixins import ApiTestCaseMixin, CallArgsMixin def assert_workout_data_with_gpx(data: Dict) -> None: @@ -1018,18 +1018,13 @@ class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin): ) data = json.loads(response.data.decode()) workout_short_id = data['data']['workouts'][0]['id'] - - resp_login = client.post( - '/api/auth/login', - data=json.dumps(dict(email='toto@toto.com', password='87654321')), - content_type='application/json', + client, auth_token = self.get_test_client_and_auth_token( + app, user_2.email ) + response = client.get( f'/api/workouts/{workout_short_id}/chart_data', - headers=dict( - Authorization='Bearer ' - + json.loads(resp_login.data.decode())['auth_token'] - ), + headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_403(response) diff --git a/fittrackee/tests/workouts/test_workouts_api_2_patch.py b/fittrackee/tests/workouts/test_workouts_api_2_patch.py index df84d2e9..63bd919f 100644 --- a/fittrackee/tests/workouts/test_workouts_api_2_patch.py +++ b/fittrackee/tests/workouts/test_workouts_api_2_patch.py @@ -8,7 +8,7 @@ from flask import Flask from fittrackee.users.models import User from fittrackee.workouts.models import Sport, Workout -from ..api_test_case import ApiTestCaseMixin +from ..mixins import ApiTestCaseMixin from .utils import get_random_short_id, post_an_workout @@ -152,21 +152,15 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin): gpx_file: str, ) -> None: _, workout_short_id = post_an_workout(app, gpx_file) - client = app.test_client() - resp_login = client.post( - '/api/auth/login', - data=json.dumps(dict(email='toto@toto.com', password='87654321')), - content_type='application/json', + client, auth_token = self.get_test_client_and_auth_token( + app, user_2.email ) response = client.patch( f'/api/workouts/{workout_short_id}', content_type='application/json', data=json.dumps(dict(sport_id=2, title="Workout test")), - headers=dict( - Authorization='Bearer ' - + json.loads(resp_login.data.decode())['auth_token'] - ), + headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_403(response) diff --git a/fittrackee/tests/workouts/test_workouts_api_3_delete.py b/fittrackee/tests/workouts/test_workouts_api_3_delete.py index fae8e6c5..d6c9adfe 100644 --- a/fittrackee/tests/workouts/test_workouts_api_3_delete.py +++ b/fittrackee/tests/workouts/test_workouts_api_3_delete.py @@ -1,4 +1,3 @@ -import json import os from flask import Flask @@ -7,7 +6,7 @@ from fittrackee.files import get_absolute_file_path from fittrackee.users.models import User from fittrackee.workouts.models import Sport, Workout -from ..api_test_case import ApiTestCaseMixin +from ..mixins import ApiTestCaseMixin from .utils import get_random_short_id, post_an_workout @@ -39,19 +38,13 @@ class TestDeleteWorkoutWithGpx(ApiTestCaseMixin): gpx_file: str, ) -> None: _, workout_short_id = post_an_workout(app, gpx_file) - client = app.test_client() - resp_login = client.post( - '/api/auth/login', - data=json.dumps(dict(email='toto@toto.com', password='87654321')), - content_type='application/json', + client, auth_token = self.get_test_client_and_auth_token( + app, user_2.email ) response = client.delete( f'/api/workouts/{workout_short_id}', - headers=dict( - Authorization='Bearer ' - + json.loads(resp_login.data.decode())['auth_token'] - ), + headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_403(response) @@ -113,18 +106,13 @@ class TestDeleteWorkoutWithoutGpx(ApiTestCaseMixin): sport_1_cycling: Sport, workout_cycling_user_1: Workout, ) -> None: - client = app.test_client() - resp_login = client.post( - '/api/auth/login', - data=json.dumps(dict(email='toto@toto.com', password='87654321')), - content_type='application/json', + client, auth_token = self.get_test_client_and_auth_token( + app, user_2.email ) + response = client.delete( f'/api/workouts/{workout_cycling_user_1.short_id}', - headers=dict( - Authorization='Bearer ' - + json.loads(resp_login.data.decode())['auth_token'] - ), + headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_403(response) diff --git a/fittrackee/users/auth.py b/fittrackee/users/auth.py index f0ec89f5..5e968509 100644 --- a/fittrackee/users/auth.py +++ b/fittrackee/users/auth.py @@ -227,67 +227,6 @@ def login_user() -> Union[Dict, HttpResponse]: return handle_error_and_return_response(e, db=db) -@auth_blueprint.route('/auth/logout', methods=['GET']) -@authenticate -def logout_user(auth_user: User) -> Union[Dict, HttpResponse]: - """ - user logout - - **Example request**: - - .. sourcecode:: http - - GET /api/auth/logout HTTP/1.1 - Content-Type: application/json - - **Example responses**: - - - successful logout - - .. sourcecode:: http - - HTTP/1.1 200 OK - Content-Type: application/json - - { - "message": "successfully logged out", - "status": "success" - } - - - error on login - - .. sourcecode:: http - - HTTP/1.1 401 UNAUTHORIZED - Content-Type: application/json - - { - "message": "provide a valid auth token", - "status": "error" - } - - :reqheader Authorization: OAuth 2.0 Bearer Token - - :statuscode 200: successfully logged out - :statuscode 401: provide a valid auth token - - """ - # get auth token - auth_header = request.headers.get('Authorization') - if not auth_header: - return UnauthorizedErrorResponse('provide a valid auth token') - - auth_token = auth_header.split(' ')[1] - resp = User.decode_auth_token(auth_token) - if isinstance(resp, str): - return UnauthorizedErrorResponse(resp) - - return { - 'status': 'success', - 'message': 'successfully logged out', - } - - @auth_blueprint.route('/auth/profile', methods=['GET']) @authenticate def get_authenticated_user_profile( diff --git a/fittrackee/users/users.py b/fittrackee/users/users.py index 0c61b912..1c41231e 100644 --- a/fittrackee/users/users.py +++ b/fittrackee/users/users.py @@ -31,7 +31,6 @@ from .decorators import authenticate, authenticate_as_admin from .exceptions import UserNotFoundException from .models import User, UserSportPreference from .utils.admin import set_admin_rights -from .utils.random import random_string users_blueprint = Blueprint('users', __name__) @@ -514,7 +513,7 @@ def update_user(auth_user: User, user_name: str) -> Union[Dict, HttpResponse]: 'reset_password' in user_data and user_data['reset_password'] is True ): - new_password = random_string(length=random.randint(10, 20)) + new_password = secrets.token_urlsafe(random.randint(16, 20)) user.password = bcrypt.generate_password_hash( new_password, current_app.config.get('BCRYPT_LOG_ROUNDS') ).decode() diff --git a/fittrackee/users/utils/random.py b/fittrackee/users/utils/random.py deleted file mode 100644 index ac0989f0..00000000 --- a/fittrackee/users/utils/random.py +++ /dev/null @@ -1,12 +0,0 @@ -import random -import string -from typing import Optional - - -def random_string(length: Optional[int] = None) -> str: - if length is None: - length = 10 - return ''.join( - random.choice(string.ascii_letters + string.digits) - for _ in range(length) - )