API - init privacy policy
This commit is contained in:
parent
789aa4dddf
commit
4e3d2f98cf
@ -1,3 +1,4 @@
|
|||||||
|
from datetime import datetime
|
||||||
from typing import Dict, Union
|
from typing import Dict, Union
|
||||||
|
|
||||||
from flask import Blueprint, current_app, request
|
from flask import Blueprint, current_app, request
|
||||||
@ -40,6 +41,7 @@ def get_application_config() -> Union[Dict, HttpResponse]:
|
|||||||
|
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
|
"about": null,
|
||||||
"admin_contact": "admin@example.com",
|
"admin_contact": "admin@example.com",
|
||||||
"gpx_limit_import": 10,
|
"gpx_limit_import": 10,
|
||||||
"is_email_sending_enabled": true,
|
"is_email_sending_enabled": true,
|
||||||
@ -48,6 +50,8 @@ def get_application_config() -> Union[Dict, HttpResponse]:
|
|||||||
"max_users": 0,
|
"max_users": 0,
|
||||||
"max_zip_file_size": 10485760,
|
"max_zip_file_size": 10485760,
|
||||||
"map_attribution": "© <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors",
|
"map_attribution": "© <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors",
|
||||||
|
"privacy_policy": null,
|
||||||
|
"privacy_policy_date": null,
|
||||||
"version": "0.7.12",
|
"version": "0.7.12",
|
||||||
"weather_provider": null
|
"weather_provider": null
|
||||||
},
|
},
|
||||||
@ -93,6 +97,7 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
|
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
|
"about": null,
|
||||||
"admin_contact": "admin@example.com",
|
"admin_contact": "admin@example.com",
|
||||||
"gpx_limit_import": 10,
|
"gpx_limit_import": 10,
|
||||||
"is_email_sending_enabled": true,
|
"is_email_sending_enabled": true,
|
||||||
@ -101,18 +106,22 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
"max_users": 10,
|
"max_users": 10,
|
||||||
"max_zip_file_size": 10485760,
|
"max_zip_file_size": 10485760,
|
||||||
"map_attribution": "© <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors",
|
"map_attribution": "© <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors",
|
||||||
|
"privacy_policy": null,
|
||||||
|
"privacy_policy_date": null,
|
||||||
"version": "0.7.12",
|
"version": "0.7.12",
|
||||||
"weather_provider": null
|
"weather_provider": null
|
||||||
},
|
},
|
||||||
"status": "success"
|
"status": "success"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:<json string about: instance information
|
||||||
:<json string admin_contact: email to contact the administrator
|
:<json string admin_contact: email to contact the administrator
|
||||||
:<json integer gpx_limit_import: max number of files in zip archive
|
:<json integer gpx_limit_import: max number of files in zip archive
|
||||||
:<json boolean is_registration_enabled: is registration enabled?
|
:<json boolean is_registration_enabled: is registration enabled?
|
||||||
:<json integer max_single_file_size: max size of a single file
|
:<json integer max_single_file_size: max size of a single file
|
||||||
:<json integer max_users: max users allowed to register on instance
|
:<json integer max_users: max users allowed to register on instance
|
||||||
:<json integer max_zip_file_size: max size of a zip archive
|
:<json integer max_zip_file_size: max size of a zip archive
|
||||||
|
:<json string privacy_policy: instance privacy policy
|
||||||
|
|
||||||
:reqheader Authorization: OAuth 2.0 Bearer Token
|
:reqheader Authorization: OAuth 2.0 Bearer Token
|
||||||
|
|
||||||
@ -151,6 +160,11 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
config.max_users = config_data.get('max_users')
|
config.max_users = config_data.get('max_users')
|
||||||
if 'admin_contact' in config_data:
|
if 'admin_contact' in config_data:
|
||||||
config.admin_contact = admin_contact if admin_contact else None
|
config.admin_contact = admin_contact if admin_contact else None
|
||||||
|
if 'about' in config_data:
|
||||||
|
config.about = config_data.get('about')
|
||||||
|
if 'privacy_policy' in config_data:
|
||||||
|
config.privacy_policy = config_data.get('privacy_policy')
|
||||||
|
config.privacy_policy_date = datetime.utcnow()
|
||||||
|
|
||||||
if config.max_zip_file_size < config.max_single_file_size:
|
if config.max_zip_file_size < config.max_single_file_size:
|
||||||
return InvalidPayloadErrorResponse(
|
return InvalidPayloadErrorResponse(
|
||||||
|
@ -25,6 +25,9 @@ class AppConfig(BaseModel):
|
|||||||
)
|
)
|
||||||
max_zip_file_size = db.Column(db.Integer, default=10485760, nullable=False)
|
max_zip_file_size = db.Column(db.Integer, default=10485760, nullable=False)
|
||||||
admin_contact = db.Column(db.String(255), nullable=True)
|
admin_contact = db.Column(db.String(255), nullable=True)
|
||||||
|
privacy_policy_date = db.Column(db.DateTime, nullable=True)
|
||||||
|
privacy_policy = db.Column(db.Text, nullable=True)
|
||||||
|
about = db.Column(db.Text, nullable=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_registration_enabled(self) -> bool:
|
def is_registration_enabled(self) -> bool:
|
||||||
@ -46,6 +49,7 @@ class AppConfig(BaseModel):
|
|||||||
def serialize(self) -> Dict:
|
def serialize(self) -> Dict:
|
||||||
weather_provider = os.getenv('WEATHER_API_PROVIDER', '').lower()
|
weather_provider = os.getenv('WEATHER_API_PROVIDER', '').lower()
|
||||||
return {
|
return {
|
||||||
|
'about': self.about,
|
||||||
'admin_contact': self.admin_contact,
|
'admin_contact': self.admin_contact,
|
||||||
'gpx_limit_import': self.gpx_limit_import,
|
'gpx_limit_import': self.gpx_limit_import,
|
||||||
'is_email_sending_enabled': current_app.config['CAN_SEND_EMAILS'],
|
'is_email_sending_enabled': current_app.config['CAN_SEND_EMAILS'],
|
||||||
@ -54,6 +58,8 @@ class AppConfig(BaseModel):
|
|||||||
'max_zip_file_size': self.max_zip_file_size,
|
'max_zip_file_size': self.max_zip_file_size,
|
||||||
'max_users': self.max_users,
|
'max_users': self.max_users,
|
||||||
'map_attribution': self.map_attribution,
|
'map_attribution': self.map_attribution,
|
||||||
|
'privacy_policy': self.privacy_policy,
|
||||||
|
'privacy_policy_date': self.privacy_policy_date,
|
||||||
'version': VERSION,
|
'version': VERSION,
|
||||||
'weather_provider': (
|
'weather_provider': (
|
||||||
weather_provider
|
weather_provider
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
"""add privacy policy
|
||||||
|
|
||||||
|
Revision ID: 374a670efe23
|
||||||
|
Revises: 0f375c44e659
|
||||||
|
Create Date: 2023-02-25 11:08:08.977217
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '374a670efe23'
|
||||||
|
down_revision = '0f375c44e659'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('app_config', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('privacy_policy_date', sa.DateTime(), nullable=True))
|
||||||
|
batch_op.add_column(sa.Column('privacy_policy', sa.Text(), nullable=True))
|
||||||
|
batch_op.add_column(sa.Column('about', sa.Text(), nullable=True))
|
||||||
|
|
||||||
|
with op.batch_alter_table('users', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('accepted_policy_date', sa.DateTime(), nullable=True))
|
||||||
|
batch_op.alter_column('date_format',
|
||||||
|
existing_type=sa.VARCHAR(length=50),
|
||||||
|
nullable=True)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('users', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('date_format',
|
||||||
|
existing_type=sa.VARCHAR(length=50),
|
||||||
|
nullable=False)
|
||||||
|
batch_op.drop_column('accepted_policy_date')
|
||||||
|
|
||||||
|
with op.batch_alter_table('app_config', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('about')
|
||||||
|
batch_op.drop_column('privacy_policy')
|
||||||
|
batch_op.drop_column('privacy_policy_date')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
@ -1,5 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
@ -296,7 +298,7 @@ class TestUpdateConfig(ApiTestCaseMixin):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'input_description,input_email', [('input string', ''), ('None', None)]
|
'input_description,input_email', [('input string', ''), ('None', None)]
|
||||||
)
|
)
|
||||||
def test_it_empties_error_if_admin_contact_is_an_empty(
|
def test_it_empties_contact_if_provided_admin_contact_is_an_empty(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1_admin: User,
|
user_1_admin: User,
|
||||||
@ -325,6 +327,66 @@ class TestUpdateConfig(ApiTestCaseMixin):
|
|||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
assert data['data']['admin_contact'] is None
|
assert data['data']['admin_contact'] is None
|
||||||
|
|
||||||
|
def test_it_updates_about(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
user_1_admin: User,
|
||||||
|
) -> None:
|
||||||
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
|
app, user_1_admin.email
|
||||||
|
)
|
||||||
|
about = self.random_string()
|
||||||
|
|
||||||
|
response = client.patch(
|
||||||
|
'/api/config',
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(
|
||||||
|
dict(
|
||||||
|
about=about,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = json.loads(response.data.decode())
|
||||||
|
assert 'success' in data['status']
|
||||||
|
assert data['data']['about'] == about
|
||||||
|
|
||||||
|
def test_it_updates_privacy_policy(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
user_1_admin: User,
|
||||||
|
) -> None:
|
||||||
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
|
app, user_1_admin.email
|
||||||
|
)
|
||||||
|
privacy_policy = self.random_string()
|
||||||
|
privacy_policy_date = datetime.utcnow()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
'fittrackee.application.app_config.datetime'
|
||||||
|
) as datetime_mock:
|
||||||
|
datetime_mock.utcnow = Mock(return_value=privacy_policy_date)
|
||||||
|
response = client.patch(
|
||||||
|
'/api/config',
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(
|
||||||
|
dict(
|
||||||
|
privacy_policy=privacy_policy,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = json.loads(response.data.decode())
|
||||||
|
assert 'success' in data['status']
|
||||||
|
assert data['data']['privacy_policy'] == privacy_policy
|
||||||
|
assert data['data'][
|
||||||
|
'privacy_policy_date'
|
||||||
|
] == privacy_policy_date.strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'client_scope, can_access',
|
'client_scope, can_access',
|
||||||
[
|
[
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
@ -5,6 +7,8 @@ from fittrackee import VERSION
|
|||||||
from fittrackee.application.models import AppConfig
|
from fittrackee.application.models import AppConfig
|
||||||
from fittrackee.users.models import User
|
from fittrackee.users.models import User
|
||||||
|
|
||||||
|
from ..utils import random_string
|
||||||
|
|
||||||
|
|
||||||
class TestConfigModel:
|
class TestConfigModel:
|
||||||
def test_application_config(
|
def test_application_config(
|
||||||
@ -88,3 +92,26 @@ class TestConfigModel:
|
|||||||
serialized_app_config['weather_provider']
|
serialized_app_config['weather_provider']
|
||||||
== expected_weather_provider
|
== expected_weather_provider
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_it_returns_privacy_policy(self, app: Flask) -> None:
|
||||||
|
app_config = AppConfig.query.first()
|
||||||
|
privacy_policy = random_string()
|
||||||
|
privacy_policy_date = datetime.now()
|
||||||
|
app_config.privacy_policy = privacy_policy
|
||||||
|
app_config.privacy_policy_date = privacy_policy_date
|
||||||
|
|
||||||
|
serialized_app_config = app_config.serialize()
|
||||||
|
|
||||||
|
assert serialized_app_config["privacy_policy"] == privacy_policy
|
||||||
|
assert (
|
||||||
|
serialized_app_config["privacy_policy_date"] == privacy_policy_date
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_it_returns_about(self, app: Flask) -> None:
|
||||||
|
app_config = AppConfig.query.first()
|
||||||
|
about = random_string()
|
||||||
|
app_config.about = about
|
||||||
|
|
||||||
|
serialized_app_config = app_config.serialize()
|
||||||
|
|
||||||
|
assert serialized_app_config["about"] == about
|
||||||
|
9
fittrackee/tests/fixtures/fixtures_users.py
vendored
9
fittrackee/tests/fixtures/fixtures_users.py
vendored
@ -13,6 +13,7 @@ from ..utils import random_string
|
|||||||
def user_1() -> User:
|
def user_1() -> User:
|
||||||
user = User(username='test', email='test@test.com', password='12345678')
|
user = User(username='test', email='test@test.com', password='12345678')
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
|
user.accepted_policy = datetime.datetime.utcnow()
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user
|
return user
|
||||||
@ -22,6 +23,7 @@ def user_1() -> User:
|
|||||||
def user_1_upper() -> User:
|
def user_1_upper() -> User:
|
||||||
user = User(username='TEST', email='TEST@TEST.COM', password='12345678')
|
user = User(username='TEST', email='TEST@TEST.COM', password='12345678')
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
|
user.accepted_policy = datetime.datetime.utcnow()
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user
|
return user
|
||||||
@ -34,6 +36,7 @@ def user_1_admin() -> User:
|
|||||||
)
|
)
|
||||||
admin.admin = True
|
admin.admin = True
|
||||||
admin.is_active = True
|
admin.is_active = True
|
||||||
|
admin.accepted_policy = datetime.datetime.utcnow()
|
||||||
db.session.add(admin)
|
db.session.add(admin)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return admin
|
return admin
|
||||||
@ -50,6 +53,7 @@ def user_1_full() -> User:
|
|||||||
user.timezone = 'America/New_York'
|
user.timezone = 'America/New_York'
|
||||||
user.birth_date = datetime.datetime.strptime('01/01/1980', '%d/%m/%Y')
|
user.birth_date = datetime.datetime.strptime('01/01/1980', '%d/%m/%Y')
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
|
user.accepted_policy = datetime.datetime.utcnow()
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user
|
return user
|
||||||
@ -60,6 +64,7 @@ def user_1_paris() -> User:
|
|||||||
user = User(username='test', email='test@test.com', password='12345678')
|
user = User(username='test', email='test@test.com', password='12345678')
|
||||||
user.timezone = 'Europe/Paris'
|
user.timezone = 'Europe/Paris'
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
|
user.accepted_policy = datetime.datetime.utcnow()
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user
|
return user
|
||||||
@ -69,6 +74,7 @@ def user_1_paris() -> User:
|
|||||||
def user_2() -> User:
|
def user_2() -> User:
|
||||||
user = User(username='toto', email='toto@toto.com', password='12345678')
|
user = User(username='toto', email='toto@toto.com', password='12345678')
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
|
user.accepted_policy = datetime.datetime.utcnow()
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user
|
return user
|
||||||
@ -79,6 +85,7 @@ def user_2_admin() -> User:
|
|||||||
user = User(username='toto', email='toto@toto.com', password='12345678')
|
user = User(username='toto', email='toto@toto.com', password='12345678')
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
user.admin = True
|
user.admin = True
|
||||||
|
user.accepted_policy = datetime.datetime.utcnow()
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user
|
return user
|
||||||
@ -89,6 +96,7 @@ def user_3() -> User:
|
|||||||
user = User(username='sam', email='sam@test.com', password='12345678')
|
user = User(username='sam', email='sam@test.com', password='12345678')
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
user.weekm = True
|
user.weekm = True
|
||||||
|
user.accepted_policy = datetime.datetime.utcnow()
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user
|
return user
|
||||||
@ -100,6 +108,7 @@ def inactive_user() -> User:
|
|||||||
username='inactive', email='inactive@example.com', password='12345678'
|
username='inactive', email='inactive@example.com', password='12345678'
|
||||||
)
|
)
|
||||||
user.confirmation_token = random_string()
|
user.confirmation_token = random_string()
|
||||||
|
user.accepted_policy = datetime.datetime.utcnow()
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user
|
return user
|
||||||
|
@ -33,6 +33,48 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
|
|
||||||
self.assert_400(response)
|
self.assert_400(response)
|
||||||
|
|
||||||
|
def test_it_returns_error_if_accepted_policy_is_missing(
|
||||||
|
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',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_400(response)
|
||||||
|
|
||||||
|
def test_it_returns_error_if_accepted_policy_is_false(
|
||||||
|
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(),
|
||||||
|
accepted_policy=False,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
content_type='application/json',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_400(
|
||||||
|
response,
|
||||||
|
'sorry, you must agree privacy policy to register',
|
||||||
|
)
|
||||||
|
|
||||||
def test_it_returns_error_if_username_is_missing(self, app: Flask) -> None:
|
def test_it_returns_error_if_username_is_missing(self, app: Flask) -> None:
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
|
|
||||||
@ -42,6 +84,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
dict(
|
dict(
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -65,6 +108,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
username=self.random_string(length=input_username_length),
|
username=self.random_string(length=input_username_length),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -91,6 +135,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
username=input_username,
|
username=input_username,
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_email(),
|
password=self.random_email(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -121,6 +166,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
),
|
),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -137,6 +183,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
dict(
|
dict(
|
||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -156,6 +203,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(length=7),
|
password=self.random_string(length=7),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -172,6 +220,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
dict(
|
dict(
|
||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -189,6 +238,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_string(),
|
email=self.random_string(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -207,6 +257,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
dict(
|
dict(
|
||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_string(),
|
email=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -224,6 +275,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -248,6 +300,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
username=username,
|
username=username,
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -269,7 +322,10 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
username = self.random_string()
|
username = self.random_string()
|
||||||
email = self.random_email()
|
email = self.random_email()
|
||||||
|
accepted_policy_date = datetime.utcnow()
|
||||||
|
|
||||||
|
with patch('fittrackee.users.auth.datetime.datetime') as datetime_mock:
|
||||||
|
datetime_mock.utcnow = Mock(return_value=accepted_policy_date)
|
||||||
client.post(
|
client.post(
|
||||||
'/api/auth/register',
|
'/api/auth/register',
|
||||||
data=json.dumps(
|
data=json.dumps(
|
||||||
@ -278,6 +334,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
email=email,
|
email=email,
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
language=input_language,
|
language=input_language,
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -288,6 +345,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
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
|
assert new_user.language == expected_language
|
||||||
|
assert new_user.accepted_policy_date == accepted_policy_date
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'input_language,expected_language',
|
'input_language,expected_language',
|
||||||
@ -314,6 +372,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
email=email,
|
email=email,
|
||||||
password='12345678',
|
password='12345678',
|
||||||
language=input_language,
|
language=input_language,
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -353,6 +412,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
username=username,
|
username=username,
|
||||||
email=email,
|
email=email,
|
||||||
password='12345678',
|
password='12345678',
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -381,6 +441,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
else user_1.email.lower()
|
else user_1.email.lower()
|
||||||
),
|
),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -404,6 +465,7 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=user_1.email,
|
email=user_1.email,
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -1983,6 +2045,7 @@ class TestRegistrationConfiguration(ApiTestCaseMixin):
|
|||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -1995,6 +2058,7 @@ class TestRegistrationConfiguration(ApiTestCaseMixin):
|
|||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -2015,6 +2079,7 @@ class TestRegistrationConfiguration(ApiTestCaseMixin):
|
|||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -2027,6 +2092,7 @@ class TestRegistrationConfiguration(ApiTestCaseMixin):
|
|||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
|
@ -1624,7 +1624,7 @@ class TestDeleteUser(ApiTestCaseMixin):
|
|||||||
'you can not delete your account, no other user has admin rights',
|
'you can not delete your account, no other user has admin rights',
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_it_enables_registration_after_user_delete(
|
def test_it_enables_registration_after_user_delete_when_users_count_is_below_limit( # noqa
|
||||||
self,
|
self,
|
||||||
app_with_3_users_max: Flask,
|
app_with_3_users_max: Flask,
|
||||||
user_1_admin: User,
|
user_1_admin: User,
|
||||||
@ -1646,6 +1646,7 @@ class TestDeleteUser(ApiTestCaseMixin):
|
|||||||
username=self.random_string(),
|
username=self.random_string(),
|
||||||
email=self.random_email(),
|
email=self.random_email(),
|
||||||
password=self.random_string(),
|
password=self.random_string(),
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
@ -1653,7 +1654,7 @@ class TestDeleteUser(ApiTestCaseMixin):
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
def test_it_does_not_enable_registration_on_user_delete(
|
def test_it_does_not_enable_registration_on_user_delete_when_users_count_is_not_below_limit( # noqa
|
||||||
self,
|
self,
|
||||||
app_with_3_users_max: Flask,
|
app_with_3_users_max: Flask,
|
||||||
user_1_admin: User,
|
user_1_admin: User,
|
||||||
@ -1677,6 +1678,7 @@ class TestDeleteUser(ApiTestCaseMixin):
|
|||||||
email='test@test.com',
|
email='test@test.com',
|
||||||
password='12345678',
|
password='12345678',
|
||||||
password_conf='12345678',
|
password_conf='12345678',
|
||||||
|
accepted_policy=True,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
|
@ -78,6 +78,16 @@ class TestUserSerializeAsAuthUser(UserModelAssertMixin):
|
|||||||
|
|
||||||
self.assert_workouts_keys_are_present(serialized_user)
|
self.assert_workouts_keys_are_present(serialized_user)
|
||||||
|
|
||||||
|
def test_it_returns_accepted_privacy_policy_date(
|
||||||
|
self, app: Flask, user_1: User
|
||||||
|
) -> None:
|
||||||
|
serialized_user = user_1.serialize(user_1)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
serialized_user['accepted_policy_date']
|
||||||
|
== user_1.accepted_policy_date
|
||||||
|
)
|
||||||
|
|
||||||
def test_it_does_not_return_confirmation_token(
|
def test_it_does_not_return_confirmation_token(
|
||||||
self, app: Flask, user_1_admin: User, user_2: User
|
self, app: Flask, user_1_admin: User, user_2: User
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -118,6 +128,13 @@ class TestUserSerializeAsAdmin(UserModelAssertMixin):
|
|||||||
|
|
||||||
self.assert_workouts_keys_are_present(serialized_user)
|
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_policy_date' not in serialized_user
|
||||||
|
|
||||||
def test_it_does_not_return_confirmation_token(
|
def test_it_does_not_return_confirmation_token(
|
||||||
self, app: Flask, user_1_admin: User, user_2: User
|
self, app: Flask, user_1_admin: User, user_2: User
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -115,6 +115,7 @@ def register_user() -> Union[Tuple[Dict, int], HttpResponse]:
|
|||||||
:<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,
|
:<json string lang: user language preferences (if not provided or invalid,
|
||||||
fallback to 'en' (english))
|
fallback to 'en' (english))
|
||||||
|
:<json boolean accepted_policy: true if user accepted privacy policy
|
||||||
|
|
||||||
:statuscode 200: success
|
:statuscode 200: success
|
||||||
:statuscode 400:
|
:statuscode 400:
|
||||||
@ -141,8 +142,16 @@ def register_user() -> Union[Tuple[Dict, int], HttpResponse]:
|
|||||||
or post_data.get('username') is None
|
or post_data.get('username') is None
|
||||||
or post_data.get('email') is None
|
or post_data.get('email') is None
|
||||||
or post_data.get('password') is None
|
or post_data.get('password') is None
|
||||||
|
or post_data.get('accepted_policy') is None
|
||||||
):
|
):
|
||||||
return InvalidPayloadErrorResponse()
|
return InvalidPayloadErrorResponse()
|
||||||
|
|
||||||
|
accepted_policy = post_data.get('accepted_policy') is True
|
||||||
|
if not accepted_policy:
|
||||||
|
return InvalidPayloadErrorResponse(
|
||||||
|
'sorry, you must agree privacy policy to register'
|
||||||
|
)
|
||||||
|
|
||||||
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')
|
||||||
@ -176,6 +185,7 @@ def register_user() -> Union[Tuple[Dict, int], HttpResponse]:
|
|||||||
new_user.date_format = 'MM/dd/yyyy'
|
new_user.date_format = 'MM/dd/yyyy'
|
||||||
new_user.confirmation_token = secrets.token_urlsafe(30)
|
new_user.confirmation_token = secrets.token_urlsafe(30)
|
||||||
new_user.language = language
|
new_user.language = language
|
||||||
|
new_user.accepted_policy_date = datetime.datetime.utcnow()
|
||||||
db.session.add(new_user)
|
db.session.add(new_user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@ -288,6 +298,7 @@ def get_authenticated_user_profile(
|
|||||||
|
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
|
"accepted_privacy_policy": "Sat, 25 Fev 2023 13:52:58 GMT",
|
||||||
"admin": false,
|
"admin": false,
|
||||||
"bio": null,
|
"bio": null,
|
||||||
"birth_date": null,
|
"birth_date": null,
|
||||||
|
@ -52,6 +52,7 @@ class User(BaseModel):
|
|||||||
email_to_confirm = db.Column(db.String(255), nullable=True)
|
email_to_confirm = db.Column(db.String(255), nullable=True)
|
||||||
confirmation_token = db.Column(db.String(255), nullable=True)
|
confirmation_token = db.Column(db.String(255), nullable=True)
|
||||||
display_ascent = db.Column(db.Boolean, default=True, nullable=False)
|
display_ascent = db.Column(db.Boolean, default=True, nullable=False)
|
||||||
|
accepted_policy_date = db.Column(db.DateTime, nullable=True)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'<User {self.username!r}>'
|
return f'<User {self.username!r}>'
|
||||||
@ -191,6 +192,7 @@ class User(BaseModel):
|
|||||||
serialized_user = {
|
serialized_user = {
|
||||||
**serialized_user,
|
**serialized_user,
|
||||||
**{
|
**{
|
||||||
|
'accepted_policy_date': self.accepted_policy_date,
|
||||||
'date_format': self.date_format,
|
'date_format': self.date_format,
|
||||||
'display_ascent': self.display_ascent,
|
'display_ascent': self.display_ascent,
|
||||||
'imperial_units': self.imperial_units,
|
'imperial_units': self.imperial_units,
|
||||||
|
Loading…
Reference in New Issue
Block a user