from datetime import datetime, timedelta from typing import Dict import pytest from flask import Flask from freezegun import freeze_time from fittrackee import db from fittrackee.tests.utils import random_int, random_string from fittrackee.users.exceptions import UserNotFoundException from fittrackee.users.models import ( BlacklistedToken, User, UserDataExport, UserSportPreference, ) from fittrackee.workouts.models import Sport, Workout class TestUserModel: def test_it_returns_username_in_string_value( self, app: Flask, user_1: User ) -> None: assert '' == str(user_1) def test_it_returns_workout_count_when_no_workouts( self, app: Flask, user_1: User, user_2: User, sport_1_cycling: Sport, workout_cycling_user_2: Workout, ) -> None: assert user_1.workouts_count == 0 def test_it_returns_workout_count_when_user_has_workout( self, app: Flask, user_1: User, user_2: User, sport_1_cycling: Sport, sport_2_running: Sport, workout_cycling_user_1: Workout, workout_running_user_1: Workout, workout_cycling_user_2: Workout, ) -> None: assert user_1.workouts_count == 2 class UserModelAssertMixin: @staticmethod def assert_user_account(serialized_user: Dict, user: User) -> None: assert serialized_user['admin'] == user.admin assert serialized_user['email_to_confirm'] == user.email_to_confirm assert serialized_user['is_active'] == user.is_active assert serialized_user['username'] == user.username @staticmethod def assert_user_profile(serialized_user: Dict, user: User) -> None: assert serialized_user['bio'] == user.bio assert serialized_user['birth_date'] == user.birth_date assert serialized_user['first_name'] == user.first_name assert serialized_user['last_name'] == user.last_name assert serialized_user['location'] == user.location assert serialized_user['picture'] is False @staticmethod def assert_workouts_keys_are_present(serialized_user: Dict) -> None: assert 'nb_sports' in serialized_user assert 'nb_workouts' in serialized_user assert 'records' in serialized_user assert 'sports_list' in serialized_user assert 'total_ascent' in serialized_user assert 'total_distance' in serialized_user assert 'total_duration' in serialized_user class TestUserSerializeAsAuthUser(UserModelAssertMixin): def test_it_returns_user_account_infos( self, app: Flask, user_1: User ) -> None: serialized_user = user_1.serialize(user_1) self.assert_user_account(serialized_user, user_1) def test_it_returns_user_profile_infos( self, app: Flask, user_1: User ) -> None: serialized_user = user_1.serialize(user_1) self.assert_user_profile(serialized_user, user_1) def test_it_returns_user_preferences( self, app: Flask, user_1: User ) -> None: serialized_user = user_1.serialize(user_1) assert serialized_user['imperial_units'] == user_1.imperial_units assert serialized_user['language'] == user_1.language assert serialized_user['timezone'] == user_1.timezone assert serialized_user['weekm'] == user_1.weekm assert serialized_user['display_ascent'] == user_1.display_ascent def test_it_returns_workouts_infos(self, app: Flask, user_1: User) -> None: serialized_user = user_1.serialize(user_1) self.assert_workouts_keys_are_present(serialized_user) def test_it_returns_user_did_not_accept_default_privacy_policy( self, app: Flask, user_1: User ) -> None: # default privacy policy app.config['privacy_policy_date'] = None user_1.accepted_policy_date = None serialized_user = user_1.serialize(user_1) assert serialized_user['accepted_privacy_policy'] is False def test_it_returns_user_did_accept_default_privacy_policy( self, app: Flask, user_1: User ) -> None: # default privacy policy app.config['privacy_policy_date'] = None user_1.accepted_policy_date = datetime.utcnow() serialized_user = user_1.serialize(user_1) assert serialized_user['accepted_privacy_policy'] is True def test_it_returns_user_did_not_accept_last_policy( self, app: Flask, user_1: User ) -> None: user_1.accepted_policy_date = datetime.utcnow() # custom privacy policy app.config['privacy_policy_date'] = datetime.utcnow() serialized_user = user_1.serialize(user_1) assert serialized_user['accepted_privacy_policy'] is False def test_it_returns_user_did_accept_last_policy( self, app: Flask, user_1: User ) -> None: # custom privacy policy app.config['privacy_policy_date'] = datetime.utcnow() user_1.accepted_policy_date = datetime.utcnow() serialized_user = user_1.serialize(user_1) assert serialized_user['accepted_privacy_policy'] is True def test_it_does_not_return_confirmation_token( self, app: Flask, user_1_admin: User, user_2: User ) -> None: serialized_user = user_2.serialize(user_1_admin) assert 'confirmation_token' not in serialized_user class TestUserSerializeAsAdmin(UserModelAssertMixin): def test_it_returns_user_account_infos( self, app: Flask, user_1_admin: User, user_2: User ) -> None: serialized_user = user_2.serialize(user_1_admin) self.assert_user_account(serialized_user, user_2) def test_it_returns_user_profile_infos( self, app: Flask, user_1_admin: User, user_2: User ) -> None: serialized_user = user_2.serialize(user_1_admin) self.assert_user_profile(serialized_user, user_1_admin) def test_it_does_return_user_preferences( self, app: Flask, user_1_admin: User, user_2: User ) -> None: serialized_user = user_2.serialize(user_1_admin) assert 'imperial_units' not in serialized_user assert 'language' not in serialized_user assert 'timezone' not in serialized_user assert 'weekm' not in serialized_user def test_it_returns_workouts_infos( self, app: Flask, user_1_admin: User, user_2: User ) -> None: serialized_user = user_2.serialize(user_1_admin) self.assert_workouts_keys_are_present(serialized_user) def test_it_does_not_return_accepted_privacy_policy_date( self, app: Flask, user_1_admin: User, user_2: User ) -> None: serialized_user = user_2.serialize(user_1_admin) assert 'accepted_privacy_policy' not in serialized_user def test_it_does_not_return_confirmation_token( self, app: Flask, user_1_admin: User, user_2: User ) -> None: serialized_user = user_2.serialize(user_1_admin) assert 'confirmation_token' not in serialized_user class TestInactiveUserSerialize(UserModelAssertMixin): def test_it_returns_is_active_to_false_for_inactive_user( self, app: Flask, inactive_user: User, ) -> None: serialized_user = inactive_user.serialize(inactive_user) assert serialized_user['is_active'] is False class TestUserSerializeAsRegularUser(UserModelAssertMixin): def test_user_model_as_regular_user( self, app: Flask, user_1: User, user_2: User ) -> None: with pytest.raises(UserNotFoundException): user_2.serialize(user_1) class TestUserRecords(UserModelAssertMixin): def test_it_returns_empty_list_when_no_workouts( self, app: Flask, user_1: User, ) -> None: serialized_user = user_1.serialize(user_1) assert serialized_user['records'] == [] def test_it_returns_user_records( self, app: Flask, user_1: User, sport_1_cycling: Sport, workout_cycling_user_1: Workout, ) -> None: serialized_user = user_1.serialize(user_1) assert len(serialized_user['records']) == 4 assert serialized_user['records'][0]['record_type'] == 'AS' assert serialized_user['records'][0]['sport_id'] == sport_1_cycling.id assert serialized_user['records'][0]['user'] == user_1.username assert serialized_user['records'][0]['value'] > 0 assert ( serialized_user['records'][0]['workout_id'] == workout_cycling_user_1.short_id ) assert serialized_user['records'][0]['workout_date'] def test_it_returns_totals_when_user_has_workout_without_ascent( self, app: Flask, user_1: User, sport_1_cycling: Sport, workout_cycling_user_1: Workout, ) -> None: serialized_user = user_1.serialize(user_1) assert serialized_user['total_ascent'] == 0 assert serialized_user['total_distance'] == 10 assert serialized_user['total_duration'] == '1:00:00' def test_it_returns_totals_when_user_has_workout_with_ascent( self, app: Flask, user_1: User, sport_1_cycling: Sport, workout_cycling_user_1: Workout, ) -> None: workout_cycling_user_1.ascent = 100 serialized_user = user_1.serialize(user_1) assert serialized_user['total_ascent'] == 100 assert serialized_user['total_distance'] == 10 assert serialized_user['total_duration'] == '1:00:00' def test_it_returns_totals_when_user_has_multiple_workouts( self, app: Flask, user_1: User, sport_1_cycling: Sport, sport_2_running: Sport, workout_cycling_user_1: Workout, workout_running_user_1: Workout, ) -> None: workout_cycling_user_1.ascent = 100 serialized_user = user_1.serialize(user_1) assert serialized_user['total_ascent'] == 100 assert serialized_user['total_distance'] == 22 assert serialized_user['total_duration'] == '2:40:00' class TestUserWorkouts(UserModelAssertMixin): def test_it_returns_infos_when_no_workouts( self, app: Flask, user_1: User, ) -> None: serialized_user = user_1.serialize(user_1) assert serialized_user['nb_sports'] == 0 assert serialized_user['nb_workouts'] == 0 assert serialized_user['sports_list'] == [] assert serialized_user['total_distance'] == 0 assert serialized_user['total_duration'] == '0:00:00' def test_it_returns_infos_when_only_one_workout_exists( self, app: Flask, user_1: User, sport_1_cycling: Sport, workout_cycling_user_1: Workout, ) -> None: serialized_user = user_1.serialize(user_1) assert serialized_user['nb_sports'] == 1 assert serialized_user['nb_workouts'] == 1 assert serialized_user['sports_list'] == [sport_1_cycling.id] assert ( serialized_user['total_distance'] == workout_cycling_user_1.distance ) assert serialized_user['total_duration'] == str( workout_cycling_user_1.duration ) def test_it_returns_infos_when_several_sports( self, app: Flask, user_1: User, sport_1_cycling: Sport, sport_2_running: Sport, workout_cycling_user_1: Workout, workout_running_user_1: Workout, ) -> None: serialized_user = user_1.serialize(user_1) assert serialized_user['nb_sports'] == 2 assert serialized_user['nb_workouts'] == 2 assert serialized_user['sports_list'] == [ sport_1_cycling.id, sport_2_running.id, ] assert serialized_user['total_distance'] == ( workout_cycling_user_1.distance + workout_running_user_1.distance ) assert serialized_user['total_duration'] == str( workout_cycling_user_1.duration + workout_running_user_1.duration ) class TestUserModelToken: def test_encode_auth_token(self, app: Flask, user_1: User) -> None: auth_token = user_1.encode_auth_token(user_1.id) assert isinstance(auth_token, str) def test_encode_password_token(self, app: Flask, user_1: User) -> None: password_token = user_1.encode_password_reset_token(user_1.id) assert isinstance(password_token, str) def test_decode_auth_token(self, app: Flask, user_1: User) -> None: auth_token = user_1.encode_auth_token(user_1.id) assert isinstance(auth_token, str) assert User.decode_auth_token(auth_token) == user_1.id def test_it_returns_error_when_token_is_invalid( self, app: Flask, user_1: User ) -> None: assert ( User.decode_auth_token(random_string()) == 'invalid token, please log in again' ) def test_it_returns_error_when_token_is_expired( self, app: Flask, user_1: User ) -> None: auth_token = user_1.encode_auth_token(user_1.id) now = datetime.utcnow() with freeze_time(now + timedelta(seconds=4)): assert ( User.decode_auth_token(auth_token) == 'signature expired, please log in again' ) def test_it_returns_error_when_token_is_blacklisted( self, app: Flask, user_1: User ) -> None: auth_token = user_1.encode_auth_token(user_1.id) db.session.add(BlacklistedToken(token=auth_token)) db.session.commit() assert ( User.decode_auth_token(auth_token) == 'blacklisted token, please log in again' ) class TestUserSportModel: def test_user_model( self, app: Flask, user_1: User, sport_1_cycling: Sport, user_sport_1_preference: UserSportPreference, ) -> None: serialized_user_sport = user_sport_1_preference.serialize() assert serialized_user_sport['user_id'] == user_1.id assert serialized_user_sport['sport_id'] == sport_1_cycling.id assert serialized_user_sport['color'] is None assert serialized_user_sport['is_active'] assert serialized_user_sport['stopped_speed_threshold'] == 1 class TestUserDataExportSerializer: def test_it_returns_ongoing_export(self, app: Flask, user_1: User) -> None: created_at = datetime.utcnow() data_export = UserDataExport(user_id=user_1.id, created_at=created_at) serialized_data_export = data_export.serialize() assert serialized_data_export["created_at"] == created_at assert serialized_data_export["status"] == "in_progress" assert serialized_data_export["file_name"] is None assert serialized_data_export["file_size"] is None def test_it_returns_successful_export( self, app: Flask, user_1: User ) -> None: created_at = datetime.utcnow() data_export = UserDataExport(user_id=user_1.id, created_at=created_at) data_export.completed = True data_export.file_name = random_string() data_export.file_size = random_int() serialized_data_export = data_export.serialize() assert serialized_data_export["created_at"] == created_at assert serialized_data_export["status"] == "successful" assert serialized_data_export["file_name"] == data_export.file_name assert serialized_data_export["file_size"] == data_export.file_size def test_it_returns_errored_export(self, app: Flask, user_1: User) -> None: created_at = datetime.utcnow() data_export = UserDataExport(user_id=user_1.id, created_at=created_at) data_export.completed = True serialized_data_export = data_export.serialize() assert serialized_data_export["created_at"] == created_at assert serialized_data_export["status"] == "errored" assert serialized_data_export["file_name"] is None assert serialized_data_export["file_size"] is None