API - refacto + remove unused endpoint for now

This commit is contained in:
Sam 2022-03-19 20:34:36 +01:00
parent dfe50b5287
commit b5b4ac8f92
19 changed files with 499 additions and 577 deletions

View File

@ -5,7 +5,7 @@ from flask import Flask
import fittrackee import fittrackee
from fittrackee.users.models import User from fittrackee.users.models import User
from ..api_test_case import ApiTestCaseMixin from ..mixins import ApiTestCaseMixin
class TestGetConfig(ApiTestCaseMixin): class TestGetConfig(ApiTestCaseMixin):

View File

@ -7,7 +7,7 @@ from fittrackee import email_service
from fittrackee.emails.email import EmailMessage from fittrackee.emails.email import EmailMessage
from fittrackee.emails.exceptions import InvalidEmailUrlScheme 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 from .template_results.password_reset_request import expected_en_text_body

View File

@ -30,6 +30,12 @@ def user_password_change_email_mock() -> Iterator[MagicMock]:
yield mock yield mock
@pytest.fixture()
def reset_password_email() -> Iterator[MagicMock]:
with patch('fittrackee.users.auth.reset_password_email') as mock:
yield mock
@pytest.fixture() @pytest.fixture()
def user_reset_password_email() -> Iterator[MagicMock]: def user_reset_password_email() -> Iterator[MagicMock]:
with patch('fittrackee.users.users.reset_password_email') as mock: with patch('fittrackee.users.users.reset_password_email') as mock:

View File

@ -60,7 +60,7 @@ def user_1_paris() -> User:
@pytest.fixture() @pytest.fixture()
def user_2() -> User: 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.add(user)
db.session.commit() db.session.commit()
return user return user
@ -68,7 +68,7 @@ def user_2() -> User:
@pytest.fixture() @pytest.fixture()
def user_2_admin() -> User: 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 user.admin = True
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()

View File

@ -6,25 +6,35 @@ from flask.testing import FlaskClient
from werkzeug.test import TestResponse from werkzeug.test import TestResponse
from .custom_asserts import assert_errored_response 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 @staticmethod
def get_test_client_and_auth_token( def get_test_client_and_auth_token(
app: Flask, user_email: str app: Flask, user_email: str
) -> Tuple[FlaskClient, str]: ) -> Tuple[FlaskClient, str]:
"""user_email must be user_1 or user_2 email"""
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
'/api/auth/login', '/api/auth/login',
data=json.dumps( data=json.dumps(
dict( dict(
email=user_email, email=user_email,
password=( password='12345678',
'87654321'
if user_email == 'toto@toto.com'
else '12345678'
),
) )
), ),
content_type='application/json', content_type='application/json',

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,10 @@ from unittest.mock import MagicMock, patch
from flask import Flask from flask import Flask
from fittrackee.users.models import User, UserSportPreference from fittrackee.users.models import User, UserSportPreference
from fittrackee.users.utils.random import random_string
from fittrackee.utils import get_readable_duration from fittrackee.utils import get_readable_duration
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from ..api_test_case import ApiTestCaseMixin from ..mixins import ApiTestCaseMixin
class TestGetUser(ApiTestCaseMixin): class TestGetUser(ApiTestCaseMixin):
@ -1108,7 +1107,7 @@ class TestUpdateUser(ApiTestCaseMixin):
response = client.patch( response = client.patch(
f'/api/users/{user_2.username}', f'/api/users/{user_2.username}',
content_type='application/json', 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}'), headers=dict(Authorization=f'Bearer {auth_token}'),
) )
@ -1128,7 +1127,7 @@ class TestUpdateUser(ApiTestCaseMixin):
client.patch( client.patch(
f'/api/users/{user_2.username}', f'/api/users/{user_2.username}',
content_type='application/json', 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}'), headers=dict(Authorization=f'Bearer {auth_token}'),
) )
@ -1166,7 +1165,7 @@ class TestUpdateUser(ApiTestCaseMixin):
app, user_1_admin.email app, user_1_admin.email
) )
new_email = 'new.' + user_2.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): with patch('secrets.token_urlsafe', return_value=expected_token):
response = client.patch( response = client.patch(

View File

@ -3,6 +3,7 @@ from unittest.mock import patch
import pytest import pytest
from flask import Flask from flask import Flask
from fittrackee.tests.utils import random_string
from fittrackee.users.exceptions import UserNotFoundException from fittrackee.users.exceptions import UserNotFoundException
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.users.utils.admin import set_admin_rights from fittrackee.users.utils.admin import set_admin_rights
@ -12,7 +13,6 @@ from fittrackee.users.utils.controls import (
is_valid_email, is_valid_email,
register_controls, register_controls,
) )
from fittrackee.users.utils.random import random_string
class TestSetAdminRights: class TestSetAdminRights:

25
fittrackee/tests/utils.py Normal file
View File

@ -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')

View File

@ -5,7 +5,7 @@ from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from ..api_test_case import ApiTestCaseMixin from ..mixins import ApiTestCaseMixin
class TestGetRecords(ApiTestCaseMixin): class TestGetRecords(ApiTestCaseMixin):

View File

@ -6,7 +6,7 @@ from fittrackee import db
from fittrackee.users.models import User, UserSportPreference from fittrackee.users.models import User, UserSportPreference
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from ..api_test_case import ApiTestCaseMixin from ..mixins import ApiTestCaseMixin
expected_sport_1_cycling_result = { expected_sport_1_cycling_result = {
'id': 1, 'id': 1,

View File

@ -5,7 +5,7 @@ from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from ..api_test_case import ApiTestCaseMixin from ..mixins import ApiTestCaseMixin
class TestGetStatsByTime(ApiTestCaseMixin): class TestGetStatsByTime(ApiTestCaseMixin):

View File

@ -7,7 +7,7 @@ from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from ..api_test_case import ApiTestCaseMixin from ..mixins import ApiTestCaseMixin
from .utils import get_random_short_id from .utils import get_random_short_id
@ -73,19 +73,13 @@ class TestGetWorkouts(ApiTestCaseMixin):
workout_cycling_user_1: Workout, workout_cycling_user_1: Workout,
workout_running_user_1: Workout, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client, auth_token = self.get_test_client_and_auth_token(
resp_login = client.post( app, user_2.email
'/api/auth/login',
data=json.dumps(dict(email='toto@toto.com', password='87654321')),
content_type='application/json',
) )
response = client.get( response = client.get(
'/api/workouts', '/api/workouts',
headers=dict( headers=dict(Authorization=f'Bearer {auth_token}'),
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())

View File

@ -12,7 +12,7 @@ from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.utils.short_id import decode_short_id 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: def assert_workout_data_with_gpx(data: Dict) -> None:
@ -1018,18 +1018,13 @@ class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin):
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
workout_short_id = data['data']['workouts'][0]['id'] workout_short_id = data['data']['workouts'][0]['id']
client, auth_token = self.get_test_client_and_auth_token(
resp_login = client.post( app, user_2.email
'/api/auth/login',
data=json.dumps(dict(email='toto@toto.com', password='87654321')),
content_type='application/json',
) )
response = client.get( response = client.get(
f'/api/workouts/{workout_short_id}/chart_data', f'/api/workouts/{workout_short_id}/chart_data',
headers=dict( headers=dict(Authorization=f'Bearer {auth_token}'),
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
) )
self.assert_403(response) self.assert_403(response)

View File

@ -8,7 +8,7 @@ from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout 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 from .utils import get_random_short_id, post_an_workout
@ -152,21 +152,15 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
gpx_file: str, gpx_file: str,
) -> None: ) -> None:
_, workout_short_id = post_an_workout(app, gpx_file) _, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client, auth_token = self.get_test_client_and_auth_token(
resp_login = client.post( app, user_2.email
'/api/auth/login',
data=json.dumps(dict(email='toto@toto.com', password='87654321')),
content_type='application/json',
) )
response = client.patch( response = client.patch(
f'/api/workouts/{workout_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(sport_id=2, title="Workout test")), data=json.dumps(dict(sport_id=2, title="Workout test")),
headers=dict( headers=dict(Authorization=f'Bearer {auth_token}'),
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
) )
self.assert_403(response) self.assert_403(response)

View File

@ -1,4 +1,3 @@
import json
import os import os
from flask import Flask from flask import Flask
@ -7,7 +6,7 @@ from fittrackee.files import get_absolute_file_path
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout 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 from .utils import get_random_short_id, post_an_workout
@ -39,19 +38,13 @@ class TestDeleteWorkoutWithGpx(ApiTestCaseMixin):
gpx_file: str, gpx_file: str,
) -> None: ) -> None:
_, workout_short_id = post_an_workout(app, gpx_file) _, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client, auth_token = self.get_test_client_and_auth_token(
resp_login = client.post( app, user_2.email
'/api/auth/login',
data=json.dumps(dict(email='toto@toto.com', password='87654321')),
content_type='application/json',
) )
response = client.delete( response = client.delete(
f'/api/workouts/{workout_short_id}', f'/api/workouts/{workout_short_id}',
headers=dict( headers=dict(Authorization=f'Bearer {auth_token}'),
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
) )
self.assert_403(response) self.assert_403(response)
@ -113,18 +106,13 @@ class TestDeleteWorkoutWithoutGpx(ApiTestCaseMixin):
sport_1_cycling: Sport, sport_1_cycling: Sport,
workout_cycling_user_1: Workout, workout_cycling_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client, auth_token = self.get_test_client_and_auth_token(
resp_login = client.post( app, user_2.email
'/api/auth/login',
data=json.dumps(dict(email='toto@toto.com', password='87654321')),
content_type='application/json',
) )
response = client.delete( response = client.delete(
f'/api/workouts/{workout_cycling_user_1.short_id}', f'/api/workouts/{workout_cycling_user_1.short_id}',
headers=dict( headers=dict(Authorization=f'Bearer {auth_token}'),
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
) )
self.assert_403(response) self.assert_403(response)

View File

@ -227,67 +227,6 @@ def login_user() -> Union[Dict, HttpResponse]:
return handle_error_and_return_response(e, db=db) 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']) @auth_blueprint.route('/auth/profile', methods=['GET'])
@authenticate @authenticate
def get_authenticated_user_profile( def get_authenticated_user_profile(

View File

@ -31,7 +31,6 @@ from .decorators import authenticate, authenticate_as_admin
from .exceptions import UserNotFoundException from .exceptions import UserNotFoundException
from .models import User, UserSportPreference from .models import User, UserSportPreference
from .utils.admin import set_admin_rights from .utils.admin import set_admin_rights
from .utils.random import random_string
users_blueprint = Blueprint('users', __name__) 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 'reset_password' in user_data
and user_data['reset_password'] is True 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( user.password = bcrypt.generate_password_hash(
new_password, current_app.config.get('BCRYPT_LOG_ROUNDS') new_password, current_app.config.get('BCRYPT_LOG_ROUNDS')
).decode() ).decode()

View File

@ -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)
)