API - init user account activation
This commit is contained in:
@@ -9,17 +9,12 @@ from ..mixins import ApiTestCaseMixin
|
||||
|
||||
|
||||
class TestGetConfig(ApiTestCaseMixin):
|
||||
def test_it_gets_application_config(
|
||||
self, app: Flask, user_1: User
|
||||
def test_it_gets_application_config_for_unauthenticated_user(
|
||||
self, app: Flask
|
||||
) -> None:
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app, user_1.email
|
||||
)
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get(
|
||||
'/api/config',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
)
|
||||
response = client.get('/api/config')
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 200
|
||||
@@ -36,6 +31,22 @@ class TestGetConfig(ApiTestCaseMixin):
|
||||
)
|
||||
assert data['data']['version'] == fittrackee.__version__
|
||||
|
||||
def test_it_gets_application_config(
|
||||
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/config',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
|
||||
def test_it_returns_error_if_application_has_no_config(
|
||||
self, app_no_config: Flask, user_1_admin: User
|
||||
) -> None:
|
||||
|
@@ -0,0 +1,174 @@
|
||||
# flake8: noqa
|
||||
|
||||
expected_en_text_body = """Hi test,
|
||||
|
||||
You have created an account on FitTrackee account. Use the link below to confirm your address email.
|
||||
|
||||
Verify your email: http://localhost/account-confirmation?token=xxx
|
||||
|
||||
For security, this request was received from a Linux device using Firefox.
|
||||
If this account creation wasn't initiated by you, please ignore this email.
|
||||
|
||||
Thanks,
|
||||
The FitTrackee Team
|
||||
http://localhost"""
|
||||
|
||||
expected_fr_text_body = """Bonjour test,
|
||||
|
||||
Vous avez créé un sur FitTrackee.
|
||||
Cliquez sur le lien ci-dessous pour confirmer votre adresse email.
|
||||
|
||||
Vérifier l'adresse email : http://localhost/account-confirmation?token=xxx
|
||||
|
||||
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
|
||||
Si vous n'êtes pas à l'origine de la création de ce compte, vous pouvez ignorer cet e-mail.
|
||||
|
||||
Merci,
|
||||
L'équipe FitTrackee
|
||||
http://localhost"""
|
||||
|
||||
expected_en_html_body = """ <body>
|
||||
<span class="preheader">Use this link to confirm your account.</span>
|
||||
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="email-masthead">
|
||||
<a href="http://localhost" class="f-fallback email-masthead-name">
|
||||
FitTrackee
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
|
||||
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="content-cell">
|
||||
<div class="f-fallback">
|
||||
<h1>Hi test,</h1>
|
||||
<p>You have created an account on FitTrackee account. Use the link below to confirm your address email.</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="http://localhost/account-confirmation?token=xxx" class="f-fallback button button--green" target="_blank">Verify your email</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
For security, this request was received from a Linux device using Firefox.
|
||||
If this account creation wasn't initiated by you, please ignore this email.
|
||||
</p>
|
||||
<p>Thanks,
|
||||
<br>The FitTrackee Team</p>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
||||
<p class="f-fallback sub">http://localhost/account-confirmation?token=xxx</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="content-cell" align="center">
|
||||
<p class="f-fallback sub align-center">© FitTrackee.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
expected_fr_html_body = """ <body>
|
||||
<span class="preheader">Utiliser ce lien pour confirmer votre inscription.</span>
|
||||
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="email-masthead">
|
||||
<a href="http://localhost" class="f-fallback email-masthead-name">
|
||||
FitTrackee
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
|
||||
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="content-cell">
|
||||
<div class="f-fallback">
|
||||
<h1>Bonjour test,</h1>
|
||||
<p>Vous avez créé un sur FitTrackee.
|
||||
Cliquez sur le lien ci-dessous pour confirmer votre adresse email.
|
||||
</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="http://localhost/account-confirmation?token=xxx" class="f-fallback button button--green" target="_blank">Vérifier l'adresse email</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
|
||||
Si vous n'êtes pas à l'origine de la création de ce compte, vous pouvez ignorer cet e-mail.
|
||||
</p>
|
||||
<p>Merci,
|
||||
<br>L'équipe FitTrackee</p>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">Si vous avez des problèmes avec le bouton, vous pouvez copier et coller le lien suivant dans votre navigateur</p>
|
||||
<p class="f-fallback sub">http://localhost/account-confirmation?token=xxx</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="content-cell" align="center">
|
||||
<p class="f-fallback sub align-center">© FitTrackee.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>"""
|
77
fittrackee/tests/emails/test_email_account_confirmation.py
Normal file
77
fittrackee/tests/emails/test_email_account_confirmation.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
from fittrackee.emails.email import EmailTemplate
|
||||
|
||||
from .template_results.email_account_confirmation import (
|
||||
expected_en_html_body,
|
||||
expected_en_text_body,
|
||||
expected_fr_html_body,
|
||||
expected_fr_text_body,
|
||||
)
|
||||
|
||||
|
||||
class TestEmailTemplateForAccountConfirmation:
|
||||
EMAIL_DATA = {
|
||||
'username': 'test',
|
||||
'account_confirmation_url': (
|
||||
'http://localhost/account-confirmation?token=xxx'
|
||||
),
|
||||
'operating_system': 'Linux',
|
||||
'browser_name': 'Firefox',
|
||||
'fittrackee_url': 'http://localhost',
|
||||
}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'lang, expected_subject',
|
||||
[
|
||||
('en', 'FitTrackee - Confirm your account'),
|
||||
('fr', 'FitTrackee - Confirmer votre inscription'),
|
||||
],
|
||||
)
|
||||
def test_it_gets_subject(
|
||||
self, app: Flask, lang: str, expected_subject: str
|
||||
) -> None:
|
||||
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
|
||||
|
||||
subject = email_template.get_content(
|
||||
'account_confirmation', lang, 'subject.txt', {}
|
||||
)
|
||||
|
||||
assert subject == expected_subject
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'lang, expected_text_body',
|
||||
[
|
||||
('en', expected_en_text_body),
|
||||
('fr', expected_fr_text_body),
|
||||
],
|
||||
)
|
||||
def test_it_gets_text_body(
|
||||
self, app: Flask, lang: str, expected_text_body: str
|
||||
) -> None:
|
||||
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
|
||||
|
||||
text_body = email_template.get_content(
|
||||
'account_confirmation', lang, 'body.txt', self.EMAIL_DATA
|
||||
)
|
||||
|
||||
assert text_body == expected_text_body
|
||||
|
||||
def test_it_gets_en_html_body(self, app: Flask) -> None:
|
||||
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
|
||||
|
||||
text_body = email_template.get_content(
|
||||
'account_confirmation', 'en', 'body.html', self.EMAIL_DATA
|
||||
)
|
||||
|
||||
assert expected_en_html_body in text_body
|
||||
|
||||
def test_it_gets_fr_html_body(self, app: Flask) -> None:
|
||||
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
|
||||
|
||||
text_body = email_template.get_content(
|
||||
'account_confirmation', 'fr', 'body.html', self.EMAIL_DATA
|
||||
)
|
||||
|
||||
assert expected_fr_html_body in text_body
|
6
fittrackee/tests/fixtures/fixtures_emails.py
vendored
6
fittrackee/tests/fixtures/fixtures_emails.py
vendored
@@ -46,3 +46,9 @@ def user_reset_password_email() -> Iterator[MagicMock]:
|
||||
def user_email_updated_to_new_address_mock() -> Iterator[MagicMock]:
|
||||
with patch('fittrackee.users.users.email_updated_to_new_address') as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def account_confirmation_email_mock() -> Iterator[MagicMock]:
|
||||
with patch('fittrackee.users.auth.account_confirmation_email') as mock:
|
||||
yield mock
|
||||
|
18
fittrackee/tests/fixtures/fixtures_users.py
vendored
18
fittrackee/tests/fixtures/fixtures_users.py
vendored
@@ -10,6 +10,7 @@ from fittrackee.workouts.models import Sport
|
||||
@pytest.fixture()
|
||||
def user_1() -> User:
|
||||
user = User(username='test', email='test@test.com', password='12345678')
|
||||
user.is_active = True
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
@@ -18,6 +19,7 @@ def user_1() -> User:
|
||||
@pytest.fixture()
|
||||
def user_1_upper() -> User:
|
||||
user = User(username='TEST', email='TEST@TEST.COM', password='12345678')
|
||||
user.is_active = True
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
@@ -29,6 +31,7 @@ def user_1_admin() -> User:
|
||||
username='admin', email='admin@example.com', password='12345678'
|
||||
)
|
||||
admin.admin = True
|
||||
admin.is_active = True
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
return admin
|
||||
@@ -44,6 +47,7 @@ def user_1_full() -> User:
|
||||
user.language = 'en'
|
||||
user.timezone = 'America/New_York'
|
||||
user.birth_date = datetime.datetime.strptime('01/01/1980', '%d/%m/%Y')
|
||||
user.is_active = True
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
@@ -53,6 +57,7 @@ def user_1_full() -> User:
|
||||
def user_1_paris() -> User:
|
||||
user = User(username='test', email='test@test.com', password='12345678')
|
||||
user.timezone = 'Europe/Paris'
|
||||
user.is_active = True
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
@@ -61,6 +66,7 @@ def user_1_paris() -> User:
|
||||
@pytest.fixture()
|
||||
def user_2() -> User:
|
||||
user = User(username='toto', email='toto@toto.com', password='12345678')
|
||||
user.is_active = True
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
@@ -69,6 +75,7 @@ def user_2() -> User:
|
||||
@pytest.fixture()
|
||||
def user_2_admin() -> User:
|
||||
user = User(username='toto', email='toto@toto.com', password='12345678')
|
||||
user.is_active = True
|
||||
user.admin = True
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
@@ -78,12 +85,23 @@ def user_2_admin() -> User:
|
||||
@pytest.fixture()
|
||||
def user_3() -> User:
|
||||
user = User(username='sam', email='sam@test.com', password='12345678')
|
||||
user.is_active = True
|
||||
user.weekm = True
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def inactive_user() -> User:
|
||||
user = User(
|
||||
username='inactive', email='inactive@example.com', password='12345678'
|
||||
)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def user_sport_1_preference(
|
||||
user_1: User, sport_1_cycling: Sport
|
||||
|
@@ -53,7 +53,10 @@ class ApiTestCaseMixin(RandomMixin):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def assert_401(response: TestResponse, error_message: str) -> Dict:
|
||||
def assert_401(
|
||||
response: TestResponse,
|
||||
error_message: Optional[str] = 'provide a valid auth token',
|
||||
) -> Dict:
|
||||
return assert_errored_response(
|
||||
response, 401, error_message=error_message
|
||||
)
|
||||
|
@@ -123,7 +123,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
self.assert_400(response, 'sorry, that user already exists')
|
||||
self.assert_400(response, 'sorry, that username is already taken')
|
||||
|
||||
def test_it_returns_error_if_password_is_missing(self, app: Flask) -> None:
|
||||
client = app.test_client()
|
||||
@@ -193,11 +193,111 @@ class TestUserRegistration(ApiTestCaseMixin):
|
||||
|
||||
self.assert_400(response, 'email: valid email must be provided\n')
|
||||
|
||||
def test_it_does_not_send_email_after_error(
|
||||
self, app: Flask, account_confirmation_email_mock: Mock
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
username=self.random_string(),
|
||||
email=self.random_string(),
|
||||
)
|
||||
),
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
account_confirmation_email_mock.send.assert_not_called()
|
||||
|
||||
def test_it_returns_success_if_payload_is_valid(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 == 200
|
||||
assert response.content_type == 'application/json'
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert 'auth_token' not in data
|
||||
|
||||
def test_it_creates_user_with_inactive_account(self, app: Flask) -> None:
|
||||
client = app.test_client()
|
||||
username = self.random_string()
|
||||
email = self.random_email()
|
||||
|
||||
client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
username=username,
|
||||
email=email,
|
||||
password=self.random_string(),
|
||||
)
|
||||
),
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
new_user = User.query.filter_by(username=username).first()
|
||||
assert new_user.email == email
|
||||
assert new_user.password is not None
|
||||
assert new_user.is_active is False
|
||||
|
||||
def test_it_calls_account_confirmation_email_if_payload_is_valid(
|
||||
self, app: Flask, account_confirmation_email_mock: Mock
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
email = self.random_email()
|
||||
username = self.random_string()
|
||||
expected_token = self.random_string()
|
||||
|
||||
with patch('secrets.token_urlsafe', return_value=expected_token):
|
||||
client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
username=username,
|
||||
email=email,
|
||||
password='12345678',
|
||||
)
|
||||
),
|
||||
content_type='application/json',
|
||||
environ_base={'HTTP_USER_AGENT': USER_AGENT},
|
||||
)
|
||||
|
||||
account_confirmation_email_mock.send.assert_called_once_with(
|
||||
{
|
||||
'language': 'en',
|
||||
'email': email,
|
||||
},
|
||||
{
|
||||
'username': username,
|
||||
'fittrackee_url': 'http://0.0.0.0:5000',
|
||||
'operating_system': 'linux',
|
||||
'browser_name': 'firefox',
|
||||
'account_confirmation_url': (
|
||||
'http://0.0.0.0:5000/account-confirmation'
|
||||
f'?token={expected_token}'
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'text_transformation',
|
||||
['upper', 'lower'],
|
||||
)
|
||||
def test_it_returns_error_if_user_already_exists_with_same_email(
|
||||
def test_it_does_not_return_error_if_a_user_already_exists_with_same_email(
|
||||
self, app: Flask, user_1: User, text_transformation: str
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
@@ -217,29 +317,30 @@ class TestUserRegistration(ApiTestCaseMixin):
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
self.assert_400(response, 'sorry, that user already exists')
|
||||
assert response.status_code == 200
|
||||
assert response.content_type == 'application/json'
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert 'auth_token' not in data
|
||||
|
||||
def test_user_can_register(self, app: Flask) -> None:
|
||||
def test_it_does_not_call_account_confirmation_email_if_user_already_exists( # noqa
|
||||
self, app: Flask, user_1: User, account_confirmation_email_mock: Mock
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.post(
|
||||
client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
username=self.random_string(),
|
||||
email=self.random_email(),
|
||||
email=user_1.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']
|
||||
account_confirmation_email_mock.send.assert_not_called()
|
||||
|
||||
|
||||
class TestUserLogin(ApiTestCaseMixin):
|
||||
@@ -269,6 +370,21 @@ class TestUserLogin(ApiTestCaseMixin):
|
||||
|
||||
self.assert_401(response, 'invalid credentials')
|
||||
|
||||
def test_it_returns_error_if_user_account_is_inactive(
|
||||
self, app: Flask, inactive_user: User
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(
|
||||
dict(email=inactive_user.email, password='12345678')
|
||||
),
|
||||
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:
|
||||
@@ -1628,7 +1744,7 @@ class TestRegistrationConfiguration(ApiTestCaseMixin):
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
class TestPasswordResetRequest(ApiTestCaseMixin):
|
||||
@@ -1974,3 +2090,47 @@ class TestEmailUpdateWitUnauthenticatedUser(ApiTestCaseMixin):
|
||||
assert user_1.email == new_email
|
||||
assert user_1.email_to_confirm is None
|
||||
assert user_1.confirmation_token is None
|
||||
|
||||
|
||||
class TestConfirmationAccount(ApiTestCaseMixin):
|
||||
def test_it_returns_error_if_token_is_missing(self, app: Flask) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.post(
|
||||
'/api/auth/account/confirm',
|
||||
data=json.dumps(dict()),
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
self.assert_400(response)
|
||||
|
||||
def test_it_returns_error_if_token_is_invalid(self, app: Flask) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.post(
|
||||
'/api/auth/account/confirm',
|
||||
data=json.dumps(dict(token=self.random_string())),
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
self.assert_400(response)
|
||||
|
||||
def test_it_activates_user_account(
|
||||
self, app: Flask, inactive_user: User
|
||||
) -> None:
|
||||
token = self.random_string()
|
||||
inactive_user.confirmation_token = token
|
||||
client = app.test_client()
|
||||
|
||||
response = client.post(
|
||||
'/api/auth/account/confirm',
|
||||
data=json.dumps(dict(token=token)),
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['message'] == 'account confirmation successful'
|
||||
assert inactive_user.is_active is True
|
||||
assert inactive_user.confirmation_token is None
|
||||
|
@@ -28,6 +28,41 @@ class TestGetUser(ApiTestCaseMixin):
|
||||
|
||||
self.assert_403(response)
|
||||
|
||||
def test_it_gets_inactive_user(
|
||||
self, app: Flask, user_1_admin: User, inactive_user: User
|
||||
) -> None:
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app, user_1_admin.email
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f'/api/users/{inactive_user.username}',
|
||||
content_type='application/json',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 200
|
||||
assert data['status'] == 'success'
|
||||
assert len(data['data']['users']) == 1
|
||||
user = data['data']['users'][0]
|
||||
assert user['username'] == inactive_user.username
|
||||
assert user['email'] == inactive_user.email
|
||||
assert user['created_at']
|
||||
assert not user['admin']
|
||||
assert not user['is_active']
|
||||
assert user['first_name'] is None
|
||||
assert user['last_name'] is None
|
||||
assert user['birth_date'] is None
|
||||
assert user['bio'] is None
|
||||
assert user['location'] is None
|
||||
assert user['nb_sports'] == 0
|
||||
assert user['nb_workouts'] == 0
|
||||
assert user['records'] == []
|
||||
assert user['sports_list'] == []
|
||||
assert user['total_distance'] == 0
|
||||
assert user['total_duration'] == '0:00:00'
|
||||
|
||||
def test_it_gets_single_user_without_workouts(
|
||||
self, app: Flask, user_1_admin: User, user_2: User
|
||||
) -> None:
|
||||
@@ -46,10 +81,11 @@ class TestGetUser(ApiTestCaseMixin):
|
||||
assert data['status'] == 'success'
|
||||
assert len(data['data']['users']) == 1
|
||||
user = data['data']['users'][0]
|
||||
assert user['username'] == 'toto'
|
||||
assert user['email'] == 'toto@toto.com'
|
||||
assert user['username'] == user_2.username
|
||||
assert user['email'] == user_2.email
|
||||
assert user['created_at']
|
||||
assert not user['admin']
|
||||
assert user['is_active']
|
||||
assert user['first_name'] is None
|
||||
assert user['last_name'] is None
|
||||
assert user['birth_date'] is None
|
||||
@@ -87,10 +123,11 @@ class TestGetUser(ApiTestCaseMixin):
|
||||
assert data['status'] == 'success'
|
||||
assert len(data['data']['users']) == 1
|
||||
user = data['data']['users'][0]
|
||||
assert user['username'] == 'test'
|
||||
assert user['email'] == 'test@test.com'
|
||||
assert user['username'] == user_1.username
|
||||
assert user['email'] == user_1.email
|
||||
assert user['created_at']
|
||||
assert not user['admin']
|
||||
assert user['is_active']
|
||||
assert user['first_name'] is None
|
||||
assert user['last_name'] is None
|
||||
assert user['birth_date'] is None
|
||||
@@ -134,8 +171,8 @@ class TestGetUsers(ApiTestCaseMixin):
|
||||
|
||||
self.assert_403(response)
|
||||
|
||||
def test_it_get_users_list(
|
||||
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
|
||||
def test_it_get_users_list_regardless_their_account_status(
|
||||
self, app: Flask, user_1_admin: User, inactive_user: User, user_3: User
|
||||
) -> None:
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app, user_1_admin.email
|
||||
@@ -154,11 +191,14 @@ class TestGetUsers(ApiTestCaseMixin):
|
||||
assert 'created_at' in data['data']['users'][1]
|
||||
assert 'created_at' in data['data']['users'][2]
|
||||
assert 'admin' in data['data']['users'][0]['username']
|
||||
assert 'toto' in data['data']['users'][1]['username']
|
||||
assert 'inactive' in data['data']['users'][1]['username']
|
||||
assert 'sam' in data['data']['users'][2]['username']
|
||||
assert 'admin@example.com' in data['data']['users'][0]['email']
|
||||
assert 'toto@toto.com' in data['data']['users'][1]['email']
|
||||
assert 'inactive@example.com' in data['data']['users'][1]['email']
|
||||
assert 'sam@test.com' in data['data']['users'][2]['email']
|
||||
assert data['data']['users'][0]['is_active']
|
||||
assert not data['data']['users'][1]['is_active']
|
||||
assert data['data']['users'][2]['is_active']
|
||||
assert data['data']['users'][0]['imperial_units'] is False
|
||||
assert data['data']['users'][0]['timezone'] is None
|
||||
assert data['data']['users'][0]['weekm'] is False
|
||||
@@ -223,6 +263,9 @@ class TestGetUsers(ApiTestCaseMixin):
|
||||
assert 'admin@example.com' in data['data']['users'][0]['email']
|
||||
assert 'toto@toto.com' in data['data']['users'][1]['email']
|
||||
assert 'sam@test.com' in data['data']['users'][2]['email']
|
||||
assert data['data']['users'][0]['is_active']
|
||||
assert data['data']['users'][1]['is_active']
|
||||
assert data['data']['users'][2]['is_active']
|
||||
assert data['data']['users'][0]['imperial_units'] is False
|
||||
assert data['data']['users'][0]['timezone'] is None
|
||||
assert data['data']['users'][0]['weekm'] is False
|
||||
@@ -1344,7 +1387,7 @@ class TestDeleteUser(ApiTestCaseMixin):
|
||||
'you can not delete your account, no other user has admin rights',
|
||||
)
|
||||
|
||||
def test_it_enables_registration_on_user_delete(
|
||||
def test_it_enables_registration_after_user_delete(
|
||||
self,
|
||||
app_with_3_users_max: Flask,
|
||||
user_1_admin: User,
|
||||
@@ -1363,15 +1406,15 @@ class TestDeleteUser(ApiTestCaseMixin):
|
||||
'/api/auth/register',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
username='justatest',
|
||||
email='test@test.com',
|
||||
password='12345678',
|
||||
password_conf='12345678',
|
||||
username=self.random_string(),
|
||||
email=self.random_email(),
|
||||
password=self.random_string(),
|
||||
)
|
||||
),
|
||||
content_type='application/json',
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_it_does_not_enable_registration_on_user_delete(
|
||||
self,
|
||||
|
@@ -14,6 +14,7 @@ class TestUserModel:
|
||||
assert 'created_at' in serialized_user
|
||||
assert serialized_user['admin'] is False
|
||||
assert serialized_user['first_name'] is None
|
||||
assert serialized_user['is_active']
|
||||
assert serialized_user['last_name'] is None
|
||||
assert serialized_user['bio'] is None
|
||||
assert serialized_user['location'] is None
|
||||
@@ -99,6 +100,15 @@ class TestUserModel:
|
||||
)
|
||||
assert serialized_user['records'][0]['workout_date']
|
||||
|
||||
def test_it_returns_is_active_to_false_fot_inactive_user(
|
||||
self,
|
||||
app: Flask,
|
||||
inactive_user: User,
|
||||
) -> None:
|
||||
serialized_user = inactive_user.serialize(inactive_user)
|
||||
|
||||
assert serialized_user['is_active'] is False
|
||||
|
||||
|
||||
class TestUserSportModel:
|
||||
def test_user_model(
|
||||
|
@@ -887,3 +887,13 @@ class TestGetRecords(ApiTestCaseMixin):
|
||||
assert workout_4_short_id == data['data']['records'][7]['workout_id']
|
||||
assert 'MS' == data['data']['records'][7]['record_type']
|
||||
assert 12.0 == data['data']['records'][7]['value']
|
||||
|
||||
def test_it_returns_error_if_user_is_not_authenticated(
|
||||
self,
|
||||
app: Flask,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get('/api/records')
|
||||
|
||||
self.assert_401(response)
|
||||
|
@@ -45,6 +45,16 @@ expected_sport_1_cycling_inactive_admin_result['has_workouts'] = False
|
||||
|
||||
|
||||
class TestGetSports(ApiTestCaseMixin):
|
||||
def test_it_returns_error_if_user_is_not_authenticated(
|
||||
self,
|
||||
app: Flask,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get('/api/sports')
|
||||
|
||||
self.assert_401(response)
|
||||
|
||||
def test_it_gets_all_sports(
|
||||
self,
|
||||
app: Flask,
|
||||
|
@@ -9,6 +9,17 @@ from ..mixins import ApiTestCaseMixin
|
||||
|
||||
|
||||
class TestGetStatsByTime(ApiTestCaseMixin):
|
||||
def test_it_returns_error_if_user_is_not_authenticated(
|
||||
self, app: Flask, user_1: User
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.username}/by_time',
|
||||
)
|
||||
|
||||
self.assert_401(response)
|
||||
|
||||
def test_it_gets_no_stats_when_user_has_no_workouts(
|
||||
self, app: Flask, user_1: User
|
||||
) -> None:
|
||||
@@ -853,6 +864,17 @@ class TestGetStatsByTime(ApiTestCaseMixin):
|
||||
|
||||
|
||||
class TestGetStatsBySport(ApiTestCaseMixin):
|
||||
def test_it_returns_error_if_user_is_not_authenticated(
|
||||
self, app: Flask, user_1: User
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.username}/by_sport',
|
||||
)
|
||||
|
||||
self.assert_401(response)
|
||||
|
||||
def test_it_gets_stats_by_sport(
|
||||
self,
|
||||
app: Flask,
|
||||
@@ -987,6 +1009,15 @@ class TestGetStatsBySport(ApiTestCaseMixin):
|
||||
|
||||
|
||||
class TestGetAllStats(ApiTestCaseMixin):
|
||||
def test_it_returns_error_if_user_is_not_authenticated(
|
||||
self, app: Flask, user_1: User
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get('/api/stats/all')
|
||||
|
||||
self.assert_401(response)
|
||||
|
||||
def test_it_returns_all_stats_when_users_have_no_workouts(
|
||||
self, app: Flask, user_1_admin: User, user_2: User
|
||||
) -> None:
|
||||
|
@@ -12,6 +12,15 @@ from .utils import get_random_short_id
|
||||
|
||||
|
||||
class TestGetWorkouts(ApiTestCaseMixin):
|
||||
def test_it_returns_error_if_user_is_not_authenticated(
|
||||
self, app: Flask
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get('/api/workouts')
|
||||
|
||||
self.assert_401(response)
|
||||
|
||||
def test_it_gets_all_workouts_for_authenticated_user(
|
||||
self,
|
||||
app: Flask,
|
||||
|
@@ -206,6 +206,22 @@ def assert_workout_data_wo_gpx(data: Dict) -> None:
|
||||
|
||||
|
||||
class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
|
||||
def test_it_returns_error_if_user_is_not_authenticated(
|
||||
self, app: Flask, sport_1_cycling: Sport, gpx_file: str
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.post(
|
||||
'/api/workouts',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}',
|
||||
),
|
||||
headers=dict(content_type='multipart/form-data'),
|
||||
)
|
||||
|
||||
self.assert_401(response)
|
||||
|
||||
def test_it_adds_an_workout_with_gpx_file(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||
) -> None:
|
||||
@@ -569,6 +585,27 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
|
||||
|
||||
|
||||
class TestPostWorkoutWithoutGpx(ApiTestCaseMixin):
|
||||
def test_it_returns_error_if_user_is_not_authenticated(
|
||||
self, app: Flask, sport_1_cycling: Sport, gpx_file: str
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
|
||||
response = client.post(
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
workout_date='2018-05-15 14:05',
|
||||
distance=10,
|
||||
)
|
||||
),
|
||||
headers=dict(content_type='multipart/form-data'),
|
||||
)
|
||||
|
||||
self.assert_401(response)
|
||||
|
||||
def test_it_adds_an_workout_without_gpx(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport
|
||||
) -> None:
|
||||
|
Reference in New Issue
Block a user