API - set language on account creation and use it in confirmation email

This commit is contained in:
Sam 2022-07-03 13:29:50 +02:00
parent c50c3c1f92
commit 5549eff08b
3 changed files with 57 additions and 18 deletions

View File

@ -1,6 +1,7 @@
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
from io import BytesIO from io import BytesIO
from typing import Optional
from unittest.mock import MagicMock, Mock, patch from unittest.mock import MagicMock, Mock, patch
import pytest import pytest
@ -233,7 +234,16 @@ class TestUserRegistration(ApiTestCaseMixin):
assert data['status'] == 'success' assert data['status'] == 'success'
assert 'auth_token' not in data assert 'auth_token' not in data
def test_it_creates_user_with_inactive_account(self, app: Flask) -> None: @pytest.mark.parametrize(
'input_language,expected_language',
[('en', 'en'), ('fr', 'fr'), ('invalid', 'en'), (None, 'en')],
)
def test_it_creates_user_with_inactive_account(
self,
app: Flask,
input_language: Optional[str],
expected_language: str,
) -> None:
client = app.test_client() client = app.test_client()
username = self.random_string() username = self.random_string()
email = self.random_email() email = self.random_email()
@ -245,6 +255,7 @@ class TestUserRegistration(ApiTestCaseMixin):
username=username, username=username,
email=email, email=email,
password=self.random_string(), password=self.random_string(),
language=input_language,
) )
), ),
content_type='application/json', content_type='application/json',
@ -254,9 +265,18 @@ class TestUserRegistration(ApiTestCaseMixin):
assert new_user.email == email assert new_user.email == email
assert new_user.password is not None assert new_user.password is not None
assert new_user.is_active is False assert new_user.is_active is False
assert new_user.language == expected_language
def test_it_calls_account_confirmation_email_if_payload_is_valid( @pytest.mark.parametrize(
self, app: Flask, account_confirmation_email_mock: Mock 'input_language,expected_language',
[('en', 'en'), ('fr', 'fr'), ('invalid', 'en'), (None, 'en')],
)
def test_it_calls_account_confirmation_email_when_payload_is_valid(
self,
app: Flask,
account_confirmation_email_mock: Mock,
input_language: Optional[str],
expected_language: str,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
email = self.random_email() email = self.random_email()
@ -271,6 +291,7 @@ class TestUserRegistration(ApiTestCaseMixin):
username=username, username=username,
email=email, email=email,
password='12345678', password='12345678',
language=input_language,
) )
), ),
content_type='application/json', content_type='application/json',
@ -279,7 +300,7 @@ class TestUserRegistration(ApiTestCaseMixin):
account_confirmation_email_mock.send.assert_called_once_with( account_confirmation_email_mock.send.assert_called_once_with(
{ {
'language': 'en', 'language': expected_language,
'email': email, 'email': email,
}, },
{ {
@ -1227,8 +1248,16 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
self.assert_400(response) self.assert_400(response)
@pytest.mark.parametrize(
'input_language,expected_language',
[('en', 'en'), ('fr', 'fr'), ('invalid', 'en'), (None, 'en')],
)
def test_it_updates_user_preferences( def test_it_updates_user_preferences(
self, app: Flask, user_1: User self,
app: Flask,
user_1: User,
input_language: Optional[str],
expected_language: str,
) -> None: ) -> None:
client, auth_token = self.get_test_client_and_auth_token( client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email app, user_1.email
@ -1241,7 +1270,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
dict( dict(
timezone='America/New_York', timezone='America/New_York',
weekm=True, weekm=True,
language='fr', language=input_language,
imperial_units=True, imperial_units=True,
) )
), ),
@ -1252,6 +1281,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert data['status'] == 'success' assert data['status'] == 'success'
assert data['message'] == 'user preferences updated' assert data['message'] == 'user preferences updated'
assert data['data']['language'] == expected_language
assert data['data'] == jsonify_dict(user_1.serialize(user_1)) assert data['data'] == jsonify_dict(user_1.serialize(user_1))
@ -2237,6 +2267,7 @@ class TestResendAccountConfirmationEmail(ApiTestCaseMixin):
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
expected_token = self.random_string() expected_token = self.random_string()
inactive_user.language = 'fr'
with patch('secrets.token_urlsafe', return_value=expected_token): with patch('secrets.token_urlsafe', return_value=expected_token):
client.post( client.post(
@ -2248,7 +2279,7 @@ class TestResendAccountConfirmationEmail(ApiTestCaseMixin):
account_confirmation_email_mock.send.assert_called_once_with( account_confirmation_email_mock.send.assert_called_once_with(
{ {
'language': 'en', 'language': inactive_user.language,
'email': inactive_user.email, 'email': inactive_user.email,
}, },
{ {

View File

@ -2,7 +2,7 @@ import datetime
import os import os
import re import re
import secrets import secrets
from typing import Dict, Tuple, Union from typing import Dict, Optional, Tuple, Union
import jwt import jwt
from flask import Blueprint, current_app, request from flask import Blueprint, current_app, request
@ -43,6 +43,13 @@ HEX_COLOR_REGEX = regex = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
NOT_FOUND_MESSAGE = 'the requested URL was not found on the server' NOT_FOUND_MESSAGE = 'the requested URL was not found on the server'
def get_language(language: Optional[str]) -> str:
# Note: some users may not have language preferences set
if not language or language not in current_app.config['LANGUAGES']:
language = 'en'
return language
def send_account_confirmation_email(user: User) -> None: def send_account_confirmation_email(user: User) -> None:
if current_app.config['CAN_SEND_EMAILS']: if current_app.config['CAN_SEND_EMAILS']:
ui_url = current_app.config['UI_URL'] ui_url = current_app.config['UI_URL']
@ -57,7 +64,7 @@ def send_account_confirmation_email(user: User) -> None:
), ),
} }
user_data = { user_data = {
'language': 'en', 'language': get_language(user.language),
'email': user.email, 'email': user.email,
} }
account_confirmation_email.send(user_data, email_data) account_confirmation_email.send(user_data, email_data)
@ -106,6 +113,8 @@ def register_user() -> Union[Tuple[Dict, int], HttpResponse]:
:<json string username: username (3 to 30 characters required) :<json string username: username (3 to 30 characters required)
:<json string email: user email :<json string email: user email
:<json string password: password (8 characters required) :<json string password: password (8 characters required)
:<json string lang: user language preferences (if not provided or invalid,
fallback to 'en' (english))
:statuscode 200: success :statuscode 200: success
:statuscode 400: :statuscode 400:
@ -138,6 +147,7 @@ def register_user() -> Union[Tuple[Dict, int], HttpResponse]:
username = post_data.get('username') username = post_data.get('username')
email = post_data.get('email') email = post_data.get('email')
password = post_data.get('password') password = post_data.get('password')
language = get_language(post_data.get('language'))
try: try:
ret = register_controls(username, email, password) ret = register_controls(username, email, password)
@ -165,6 +175,7 @@ def register_user() -> Union[Tuple[Dict, int], HttpResponse]:
new_user = User(username=username, email=email, password=password) new_user = User(username=username, email=email, password=password)
new_user.timezone = 'Europe/Paris' new_user.timezone = 'Europe/Paris'
new_user.confirmation_token = secrets.token_urlsafe(30) new_user.confirmation_token = secrets.token_urlsafe(30)
new_user.language = language
db.session.add(new_user) db.session.add(new_user)
db.session.commit() db.session.commit()
@ -661,9 +672,7 @@ def update_user_account(auth_user: User) -> Union[Dict, HttpResponse]:
if current_app.config['CAN_SEND_EMAILS']: if current_app.config['CAN_SEND_EMAILS']:
ui_url = current_app.config['UI_URL'] ui_url = current_app.config['UI_URL']
user_data = { user_data = {
'language': ( 'language': get_language(auth_user.language),
'en' if auth_user.language is None else auth_user.language
),
'email': auth_user.email, 'email': auth_user.email,
} }
data = { data = {
@ -830,7 +839,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
return InvalidPayloadErrorResponse() return InvalidPayloadErrorResponse()
imperial_units = post_data.get('imperial_units') imperial_units = post_data.get('imperial_units')
language = post_data.get('language') language = get_language(post_data.get('language'))
timezone = post_data.get('timezone') timezone = post_data.get('timezone')
weekm = post_data.get('weekm') weekm = post_data.get('weekm')
@ -1189,7 +1198,7 @@ def request_password_reset() -> Union[Dict, HttpResponse]:
if user: if user:
password_reset_token = user.encode_password_reset_token(user.id) password_reset_token = user.encode_password_reset_token(user.id)
ui_url = current_app.config['UI_URL'] ui_url = current_app.config['UI_URL']
user_language = 'en' if user.language is None else user.language user_language = get_language(user.language)
email_data = { email_data = {
'expiration_delay': get_readable_duration( 'expiration_delay': get_readable_duration(
current_app.config['PASSWORD_TOKEN_EXPIRATION_SECONDS'], current_app.config['PASSWORD_TOKEN_EXPIRATION_SECONDS'],
@ -1280,9 +1289,7 @@ def update_password() -> Union[Dict, HttpResponse]:
if current_app.config['CAN_SEND_EMAILS']: if current_app.config['CAN_SEND_EMAILS']:
password_change_email.send( password_change_email.send(
{ {
'language': ( 'language': get_language(user.language),
'en' if user.language is None else user.language
),
'email': user.email, 'email': user.email,
}, },
{ {

View File

@ -23,6 +23,7 @@ from fittrackee.responses import (
from fittrackee.utils import get_readable_duration from fittrackee.utils import get_readable_duration
from fittrackee.workouts.models import Record, Workout, WorkoutSegment from fittrackee.workouts.models import Record, Workout, WorkoutSegment
from .auth import get_language
from .decorators import authenticate, authenticate_as_admin from .decorators import authenticate, authenticate_as_admin
from .exceptions import InvalidEmailException, UserNotFoundException from .exceptions import InvalidEmailException, UserNotFoundException
from .models import User, UserSportPreference from .models import User, UserSportPreference
@ -530,7 +531,7 @@ def update_user(auth_user: User, user_name: str) -> Union[Dict, HttpResponse]:
) )
if current_app.config['CAN_SEND_EMAILS']: if current_app.config['CAN_SEND_EMAILS']:
user_language = 'en' if user.language is None else user.language user_language = get_language(user.language)
ui_url = current_app.config['UI_URL'] ui_url = current_app.config['UI_URL']
if reset_password: if reset_password:
user_data = { user_data = {