import json from datetime import datetime from typing import Optional from unittest.mock import Mock, patch import pytest from flask import Flask from fittrackee import db from fittrackee.application.models import AppConfig from fittrackee.users.models import User from ..mixins import ApiTestCaseMixin from ..utils import jsonify_dict class TestGetConfig(ApiTestCaseMixin): def test_it_gets_application_config_for_unauthenticated_user( self, app: Flask ) -> None: app_config = AppConfig.query.first() client = app.test_client() response = client.get('/api/config') data = json.loads(response.data.decode()) assert response.status_code == 200 assert 'success' in data['status'] assert data['data'] == jsonify_dict(app_config.serialize()) 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: client, auth_token = self.get_test_client_and_auth_token( app_no_config, user_1_admin.email ) response = client.get( '/api/config', content_type='application/json', headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_500(response, 'error on getting configuration') def test_it_returns_error_if_application_has_several_config( self, app: Flask, app_config: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) response = client.get( '/api/config', content_type='application/json', headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_500(response, 'error on getting configuration') class TestUpdateConfig(ApiTestCaseMixin): def test_it_updates_config_when_user_is_admin( self, app: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps(dict(gpx_limit_import=100, max_users=10)), headers=dict(Authorization=f'Bearer {auth_token}'), ) data = json.loads(response.data.decode()) assert response.status_code == 200 assert 'success' in data['status'] assert data['data']['gpx_limit_import'] == 100 assert data['data']['is_registration_enabled'] is True assert data['data']['max_single_file_size'] == 1048576 assert data['data']['max_zip_file_size'] == 10485760 assert data['data']['max_users'] == 10 def test_it_updates_all_config( self, app: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) admin_email = self.random_email() response = client.patch( '/api/config', content_type='application/json', data=json.dumps( dict( admin_contact=admin_email, gpx_limit_import=20, max_single_file_size=10000, max_zip_file_size=25000, max_users=50, ) ), 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']['admin_contact'] == admin_email assert data['data']['gpx_limit_import'] == 20 assert data['data']['is_registration_enabled'] is True assert data['data']['max_single_file_size'] == 10000 assert data['data']['max_zip_file_size'] == 25000 assert data['data']['max_users'] == 50 def test_it_returns_403_when_user_is_not_an_admin( self, app: Flask, user_1: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps(dict(gpx_limit_import=100, max_users=10)), headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_403(response) def test_it_returns_400_if_invalid_is_payload( self, app: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps(dict()), headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_400(response) def test_it_returns_error_on_update_if_application_has_no_config( self, app_no_config: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app_no_config, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps(dict(gpx_limit_import=100, max_users=10)), headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_500(response, 'error when updating configuration') def test_it_raises_error_if_archive_max_size_is_below_files_max_size( self, app: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps( dict( gpx_limit_import=20, max_single_file_size=10000, max_zip_file_size=1000, max_users=50, ) ), headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_400( response, ( 'Max. size of zip archive must be equal or greater than max.' ' size of uploaded files' ), ) def test_it_raises_error_if_archive_max_size_equals_0( self, app_with_max_file_size_equals_0: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app_with_max_file_size_equals_0, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps( dict( max_zip_file_size=0, ) ), headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_400( response, 'Max. size of zip archive must be greater than 0' ) def test_it_raises_error_if_files_max_size_equals_0( self, app: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps( dict( max_single_file_size=0, ) ), headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_400( response, 'Max. size of uploaded files must be greater than 0' ) def test_it_raises_error_if_gpx_limit_import_equals_0( self, app: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps( dict( gpx_limit_import=0, ) ), headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_400( response, 'Max. files in a zip archive must be greater than 0' ) def test_it_raises_error_if_admin_contact_is_invalid( self, app: Flask, user_1_admin: User ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps( dict( admin_contact=self.random_string(), ) ), headers=dict(Authorization=f'Bearer {auth_token}'), ) self.assert_400( response, 'valid email must be provided for admin contact' ) @pytest.mark.parametrize( 'input_description,input_email', [('input string', ''), ('None', None)] ) def test_it_empties_contact_if_provided_admin_contact_is_an_empty( self, app: Flask, user_1_admin: User, input_description: str, input_email: Optional[str], ) -> None: client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) app_config = AppConfig.query.first() app_config.admin_contact = self.random_email() response = client.patch( '/api/config', content_type='application/json', data=json.dumps( dict( admin_contact=input_email, ) ), 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']['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_empties_about_text_when_text_is_an_empty_string( self, app: Flask, user_1_admin: User ) -> None: app_config = AppConfig.query.first() app_config.about = self.random_string() db.session.commit() client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps(dict(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'] is None 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('input_privacy_policy', ['', None]) def test_it_empties_privacy_policy_date_when_no_privacy_policy( self, app: Flask, user_1_admin: User, input_privacy_policy: Optional[str], ) -> None: app_config = AppConfig.query.first() app_config.privacy_policy = self.random_string() app_config.privacy_policy_date = datetime.utcnow() db.session.commit() client, auth_token = self.get_test_client_and_auth_token( app, user_1_admin.email ) response = client.patch( '/api/config', content_type='application/json', data=json.dumps(dict(privacy_policy=input_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'] is None assert data['data']['privacy_policy_date'] is None @pytest.mark.parametrize( 'client_scope, can_access', [ ('application:write', True), ('profile:read', False), ('profile:write', False), ('users:read', False), ('users:write', False), ('workouts:read', False), ('workouts:write', False), ], ) def test_expected_scopes_are_defined( self, app: Flask, user_1_admin: User, client_scope: str, can_access: bool, ) -> None: ( client, oauth_client, access_token, _, ) = self.create_oauth2_client_and_issue_token( app, user_1_admin, scope=client_scope ) response = client.patch( '/api/config', content_type='application/json', headers=dict(Authorization=f'Bearer {access_token}'), ) self.assert_response_scope(response, can_access)