API & Client - max sizes and max number of files must be greater than 0 - #71
This commit is contained in:
		@@ -196,11 +196,11 @@
 | 
				
			|||||||
</dd>
 | 
					</dd>
 | 
				
			||||||
<dt class="field-even">Request JSON Object</dt>
 | 
					<dt class="field-even">Request JSON Object</dt>
 | 
				
			||||||
<dd class="field-even"><ul class="simple">
 | 
					<dd class="field-even"><ul class="simple">
 | 
				
			||||||
<li><p><strong>gpx_limit_import</strong> (<em>integrer</em>) – max number of files in zip archive</p></li>
 | 
					<li><p><strong>gpx_limit_import</strong> (<em>integer</em>) – max number of files in zip archive</p></li>
 | 
				
			||||||
<li><p><strong>is_registration_enabled</strong> (<em>boolean</em>) – is registration enabled ?</p></li>
 | 
					<li><p><strong>is_registration_enabled</strong> (<em>boolean</em>) – is registration enabled ?</p></li>
 | 
				
			||||||
<li><p><strong>max_single_file_size</strong> (<em>integrer</em>) – max size of a single file</p></li>
 | 
					<li><p><strong>max_single_file_size</strong> (<em>integer</em>) – max size of a single file</p></li>
 | 
				
			||||||
<li><p><strong>max_zip_file_size</strong> (<em>integrer</em>) – max size of a zip archive</p></li>
 | 
					<li><p><strong>max_zip_file_size</strong> (<em>integer</em>) – max size of a zip archive</p></li>
 | 
				
			||||||
<li><p><strong>max_users</strong> (<em>integrer</em>) – max users allowed to register on instance</p></li>
 | 
					<li><p><strong>max_users</strong> (<em>integer</em>) – max users allowed to register on instance</p></li>
 | 
				
			||||||
</ul>
 | 
					</ul>
 | 
				
			||||||
</dd>
 | 
					</dd>
 | 
				
			||||||
<dt class="field-odd">Request Headers</dt>
 | 
					<dt class="field-odd">Request Headers</dt>
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -12,7 +12,7 @@ from fittrackee.responses import (
 | 
				
			|||||||
from fittrackee.users.decorators import authenticate_as_admin
 | 
					from fittrackee.users.decorators import authenticate_as_admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import AppConfig
 | 
					from .models import AppConfig
 | 
				
			||||||
from .utils import update_app_config_from_database
 | 
					from .utils import update_app_config_from_database, verify_app_config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config_blueprint = Blueprint('config', __name__)
 | 
					config_blueprint = Blueprint('config', __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -96,11 +96,11 @@ def update_application_config(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
					    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :<json integrer 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 integrer max_single_file_size: max size of a single file
 | 
					    :<json integer max_single_file_size: max size of a single file
 | 
				
			||||||
    :<json integrer max_zip_file_size: max size of a zip archive
 | 
					    :<json integer max_zip_file_size: max size of a zip archive
 | 
				
			||||||
    :<json integrer max_users: max users allowed to register on instance
 | 
					    :<json integer max_users: max users allowed to register on instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
					    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -117,6 +117,10 @@ def update_application_config(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
				
			|||||||
    if not config_data:
 | 
					    if not config_data:
 | 
				
			||||||
        return InvalidPayloadErrorResponse()
 | 
					        return InvalidPayloadErrorResponse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret = verify_app_config(config_data)
 | 
				
			||||||
 | 
					    if ret:
 | 
				
			||||||
 | 
					        return InvalidPayloadErrorResponse(message=ret)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        config = AppConfig.query.one()
 | 
					        config = AppConfig.query.one()
 | 
				
			||||||
        if 'gpx_limit_import' in config_data:
 | 
					        if 'gpx_limit_import' in config_data:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
from typing import Tuple
 | 
					from typing import Dict, List, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from flask import Flask
 | 
					from flask import Flask
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,3 +49,30 @@ def update_app_config_from_database(
 | 
				
			|||||||
    current_app.config[
 | 
					    current_app.config[
 | 
				
			||||||
        'is_registration_enabled'
 | 
					        'is_registration_enabled'
 | 
				
			||||||
    ] = db_config.is_registration_enabled
 | 
					    ] = db_config.is_registration_enabled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def verify_app_config(config_data: Dict) -> List:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Verify if application config is valid.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If not, it returns not empty string
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    ret = []
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        'gpx_limit_import' in config_data
 | 
				
			||||||
 | 
					        and config_data['gpx_limit_import'] <= 0
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        ret.append('Max. files in a zip archive must be greater than 0')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        'max_single_file_size' in config_data
 | 
				
			||||||
 | 
					        and config_data['max_single_file_size'] <= 0
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        ret.append('Max. size of uploaded files must be greater than 0')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        'max_zip_file_size' in config_data
 | 
				
			||||||
 | 
					        and config_data['max_zip_file_size'] <= 0
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        ret.append('Max. size of zip archive must be greater than 0')
 | 
				
			||||||
 | 
					    return ret
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								fittrackee/dist/asset-manifest.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								fittrackee/dist/asset-manifest.json
									
									
									
									
										vendored
									
									
								
							@@ -1,8 +1,8 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "files": {
 | 
					  "files": {
 | 
				
			||||||
    "main.css": "/static/css/main.376b8924.chunk.css",
 | 
					    "main.css": "/static/css/main.376b8924.chunk.css",
 | 
				
			||||||
    "main.js": "/static/js/main.7c5c861a.chunk.js",
 | 
					    "main.js": "/static/js/main.4387b246.chunk.js",
 | 
				
			||||||
    "main.js.map": "/static/js/main.7c5c861a.chunk.js.map",
 | 
					    "main.js.map": "/static/js/main.4387b246.chunk.js.map",
 | 
				
			||||||
    "runtime-main.js": "/static/js/runtime-main.1240af94.js",
 | 
					    "runtime-main.js": "/static/js/runtime-main.1240af94.js",
 | 
				
			||||||
    "runtime-main.js.map": "/static/js/runtime-main.1240af94.js.map",
 | 
					    "runtime-main.js.map": "/static/js/runtime-main.1240af94.js.map",
 | 
				
			||||||
    "static/js/2.301144a0.chunk.js": "/static/js/2.301144a0.chunk.js",
 | 
					    "static/js/2.301144a0.chunk.js": "/static/js/2.301144a0.chunk.js",
 | 
				
			||||||
@@ -19,6 +19,6 @@
 | 
				
			|||||||
    "static/js/runtime-main.1240af94.js",
 | 
					    "static/js/runtime-main.1240af94.js",
 | 
				
			||||||
    "static/js/2.301144a0.chunk.js",
 | 
					    "static/js/2.301144a0.chunk.js",
 | 
				
			||||||
    "static/css/main.376b8924.chunk.css",
 | 
					    "static/css/main.376b8924.chunk.css",
 | 
				
			||||||
    "static/js/main.7c5c861a.chunk.js"
 | 
					    "static/js/main.4387b246.chunk.js"
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								fittrackee/dist/index.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								fittrackee/dist/index.html
									
									
									
									
										vendored
									
									
								
							@@ -1 +1 @@
 | 
				
			|||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation-icons/3.0/foundation-icons.min.css"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""><title>FitTrackee</title><link href="/static/css/main.376b8924.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script><script type="text/javascript">$(document).ready((function(){$("li.nav-item").click((function(){$("button.navbar-toggler").toggleClass("collapsed"),$("#navbarSupportedContent").toggleClass("show")}))}))</script><script>!function(e){function t(t){for(var n,i,l=t[0],f=t[1],a=t[2],p=0,s=[];p<l.length;p++)i=l[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var f=r[l];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={1:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/";var l=this.webpackJsonpfittrackee_client=this.webpackJsonpfittrackee_client||[],f=l.push.bind(l);l.push=t,l=l.slice();for(var a=0;a<l.length;a++)t(l[a]);var c=f;r()}([])</script><script src="/static/js/2.301144a0.chunk.js"></script><script src="/static/js/main.7c5c861a.chunk.js"></script></body></html>
 | 
					<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation-icons/3.0/foundation-icons.min.css"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""><title>FitTrackee</title><link href="/static/css/main.376b8924.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script><script type="text/javascript">$(document).ready((function(){$("li.nav-item").click((function(){$("button.navbar-toggler").toggleClass("collapsed"),$("#navbarSupportedContent").toggleClass("show")}))}))</script><script>!function(e){function t(t){for(var n,i,l=t[0],f=t[1],a=t[2],p=0,s=[];p<l.length;p++)i=l[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var f=r[l];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={1:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/";var l=this.webpackJsonpfittrackee_client=this.webpackJsonpfittrackee_client||[],f=l.push.bind(l);l.push=t,l=l.slice();for(var a=0;a<l.length;a++)t(l[a]);var c=f;r()}([])</script><script src="/static/js/2.301144a0.chunk.js"></script><script src="/static/js/main.4387b246.chunk.js"></script></body></html>
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								fittrackee/dist/static/js/main.4387b246.chunk.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fittrackee/dist/static/js/main.4387b246.chunk.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -32,7 +32,10 @@ class HttpResponse(Response):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class GenericErrorResponse(HttpResponse):
 | 
					class GenericErrorResponse(HttpResponse):
 | 
				
			||||||
    def __init__(
 | 
					    def __init__(
 | 
				
			||||||
        self, status_code: int, message: str, status: Optional[str] = None
 | 
					        self,
 | 
				
			||||||
 | 
					        status_code: int,
 | 
				
			||||||
 | 
					        message: Union[str, List],
 | 
				
			||||||
 | 
					        status: Optional[str] = None,
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        response = {
 | 
					        response = {
 | 
				
			||||||
            'status': 'error' if status is None else status,
 | 
					            'status': 'error' if status is None else status,
 | 
				
			||||||
@@ -46,7 +49,9 @@ class GenericErrorResponse(HttpResponse):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class InvalidPayloadErrorResponse(GenericErrorResponse):
 | 
					class InvalidPayloadErrorResponse(GenericErrorResponse):
 | 
				
			||||||
    def __init__(
 | 
					    def __init__(
 | 
				
			||||||
        self, message: Optional[str] = None, status: Optional[str] = None
 | 
					        self,
 | 
				
			||||||
 | 
					        message: Optional[Union[str, List]] = None,
 | 
				
			||||||
 | 
					        status: Optional[str] = None,
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        message = 'Invalid payload.' if message is None else message
 | 
					        message = 'Invalid payload.' if message is None else message
 | 
				
			||||||
        super().__init__(status_code=400, message=message, status=status)
 | 
					        super().__init__(status_code=400, message=message, status=status)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -276,3 +276,105 @@ class TestUpdateConfig:
 | 
				
			|||||||
            'Max. size of zip archive must be equal or greater than max. size '
 | 
					            'Max. size of zip archive must be equal or greater than max. size '
 | 
				
			||||||
            'of uploaded files'
 | 
					            'of uploaded files'
 | 
				
			||||||
        ) in data['message']
 | 
					        ) in data['message']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_it_raises_error_if_archive_max_size_equals_0(
 | 
				
			||||||
 | 
					        self, app_with_max_single_file_size: Flask, user_1_admin: User
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					        client = app_with_max_single_file_size.test_client()
 | 
				
			||||||
 | 
					        resp_login = client.post(
 | 
				
			||||||
 | 
					            '/api/auth/login',
 | 
				
			||||||
 | 
					            data=json.dumps(
 | 
				
			||||||
 | 
					                dict(email='admin@example.com', password='12345678')
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            content_type='application/json',
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = client.patch(
 | 
				
			||||||
 | 
					            '/api/config',
 | 
				
			||||||
 | 
					            content_type='application/json',
 | 
				
			||||||
 | 
					            data=json.dumps(
 | 
				
			||||||
 | 
					                dict(
 | 
				
			||||||
 | 
					                    max_zip_file_size=0,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            headers=dict(
 | 
				
			||||||
 | 
					                Authorization='Bearer '
 | 
				
			||||||
 | 
					                + json.loads(resp_login.data.decode())['auth_token']
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = json.loads(response.data.decode())
 | 
				
			||||||
 | 
					        assert response.status_code == 400
 | 
				
			||||||
 | 
					        assert 'error' in data['status']
 | 
				
			||||||
 | 
					        assert (
 | 
				
			||||||
 | 
					            'Max. size of zip archive must be greater than 0'
 | 
				
			||||||
 | 
					            in data['message']
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_it_raises_error_if_files_max_size_equals_0(
 | 
				
			||||||
 | 
					        self, app: Flask, user_1_admin: User
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					        client = app.test_client()
 | 
				
			||||||
 | 
					        resp_login = client.post(
 | 
				
			||||||
 | 
					            '/api/auth/login',
 | 
				
			||||||
 | 
					            data=json.dumps(
 | 
				
			||||||
 | 
					                dict(email='admin@example.com', password='12345678')
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            content_type='application/json',
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = client.patch(
 | 
				
			||||||
 | 
					            '/api/config',
 | 
				
			||||||
 | 
					            content_type='application/json',
 | 
				
			||||||
 | 
					            data=json.dumps(
 | 
				
			||||||
 | 
					                dict(
 | 
				
			||||||
 | 
					                    max_single_file_size=0,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            headers=dict(
 | 
				
			||||||
 | 
					                Authorization='Bearer '
 | 
				
			||||||
 | 
					                + json.loads(resp_login.data.decode())['auth_token']
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = json.loads(response.data.decode())
 | 
				
			||||||
 | 
					        assert response.status_code == 400
 | 
				
			||||||
 | 
					        assert 'error' in data['status']
 | 
				
			||||||
 | 
					        assert (
 | 
				
			||||||
 | 
					            'Max. size of uploaded files must be greater than 0'
 | 
				
			||||||
 | 
					            in data['message']
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_it_raises_error_if_gpx_limit_import_equals_0(
 | 
				
			||||||
 | 
					        self, app: Flask, user_1_admin: User
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					        client = app.test_client()
 | 
				
			||||||
 | 
					        resp_login = client.post(
 | 
				
			||||||
 | 
					            '/api/auth/login',
 | 
				
			||||||
 | 
					            data=json.dumps(
 | 
				
			||||||
 | 
					                dict(email='admin@example.com', password='12345678')
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            content_type='application/json',
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = client.patch(
 | 
				
			||||||
 | 
					            '/api/config',
 | 
				
			||||||
 | 
					            content_type='application/json',
 | 
				
			||||||
 | 
					            data=json.dumps(
 | 
				
			||||||
 | 
					                dict(
 | 
				
			||||||
 | 
					                    gpx_limit_import=0,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            headers=dict(
 | 
				
			||||||
 | 
					                Authorization='Bearer '
 | 
				
			||||||
 | 
					                + json.loads(resp_login.data.decode())['auth_token']
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = json.loads(response.data.decode())
 | 
				
			||||||
 | 
					        assert response.status_code == 400
 | 
				
			||||||
 | 
					        assert 'error' in data['status']
 | 
				
			||||||
 | 
					        assert (
 | 
				
			||||||
 | 
					            'Max. files in a zip archive must be greater than 0'
 | 
				
			||||||
 | 
					            in data['message']
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								fittrackee/tests/fixtures/fixtures_app.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								fittrackee/tests/fixtures/fixtures_app.py
									
									
									
									
										vendored
									
									
								
							@@ -11,11 +11,16 @@ from fittrackee.application.utils import update_app_config_from_database
 | 
				
			|||||||
def get_app_config(
 | 
					def get_app_config(
 | 
				
			||||||
    with_config: Optional[bool] = False,
 | 
					    with_config: Optional[bool] = False,
 | 
				
			||||||
    max_workouts: Optional[int] = None,
 | 
					    max_workouts: Optional[int] = None,
 | 
				
			||||||
 | 
					    max_single_file_size: Optional[int] = None,
 | 
				
			||||||
) -> Optional[AppConfig]:
 | 
					) -> Optional[AppConfig]:
 | 
				
			||||||
    if with_config:
 | 
					    if with_config:
 | 
				
			||||||
        config = AppConfig()
 | 
					        config = AppConfig()
 | 
				
			||||||
        config.gpx_limit_import = 10 if max_workouts is None else max_workouts
 | 
					        config.gpx_limit_import = 10 if max_workouts is None else max_workouts
 | 
				
			||||||
        config.max_single_file_size = 1 * 1024 * 1024
 | 
					        config.max_single_file_size = (
 | 
				
			||||||
 | 
					            1 * 1024 * 1024
 | 
				
			||||||
 | 
					            if max_single_file_size is None
 | 
				
			||||||
 | 
					            else max_single_file_size
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        config.max_zip_file_size = 1 * 1024 * 1024 * 10
 | 
					        config.max_zip_file_size = 1 * 1024 * 1024 * 10
 | 
				
			||||||
        config.max_users = 100
 | 
					        config.max_users = 100
 | 
				
			||||||
        db.session.add(config)
 | 
					        db.session.add(config)
 | 
				
			||||||
@@ -27,6 +32,7 @@ def get_app_config(
 | 
				
			|||||||
def get_app(
 | 
					def get_app(
 | 
				
			||||||
    with_config: Optional[bool] = False,
 | 
					    with_config: Optional[bool] = False,
 | 
				
			||||||
    max_workouts: Optional[int] = None,
 | 
					    max_workouts: Optional[int] = None,
 | 
				
			||||||
 | 
					    max_single_file_size: Optional[int] = None,
 | 
				
			||||||
) -> Generator:
 | 
					) -> Generator:
 | 
				
			||||||
    app = create_app()
 | 
					    app = create_app()
 | 
				
			||||||
    with app.app_context():
 | 
					    with app.app_context():
 | 
				
			||||||
@@ -64,6 +70,14 @@ def app_with_max_workouts(monkeypatch: pytest.MonkeyPatch) -> Generator:
 | 
				
			|||||||
    yield from get_app(with_config=True, max_workouts=2)
 | 
					    yield from get_app(with_config=True, max_workouts=2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def app_with_max_single_file_size(
 | 
				
			||||||
 | 
					    monkeypatch: pytest.MonkeyPatch,
 | 
				
			||||||
 | 
					) -> Generator:
 | 
				
			||||||
 | 
					    monkeypatch.setenv('EMAIL_URL', 'smtp://none:none@0.0.0.0:1025')
 | 
				
			||||||
 | 
					    yield from get_app(with_config=True, max_single_file_size=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
def app_no_config() -> Generator:
 | 
					def app_no_config() -> Generator:
 | 
				
			||||||
    yield from get_app(with_config=False)
 | 
					    yield from get_app(with_config=False)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import FitTrackeeGenericApi from '../fitTrackeeApi'
 | 
					import FitTrackeeGenericApi from '../fitTrackeeApi'
 | 
				
			||||||
import { history } from '../index'
 | 
					import { history } from '../index'
 | 
				
			||||||
import { setError } from './index'
 | 
					import { generateIds } from '../utils'
 | 
				
			||||||
 | 
					import { emptyMessages, setError } from './index'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setAppConfig = data => ({
 | 
					export const setAppConfig = data => ({
 | 
				
			||||||
  type: 'SET_APP_CONFIG',
 | 
					  type: 'SET_APP_CONFIG',
 | 
				
			||||||
@@ -12,6 +13,8 @@ export const setAppStats = data => ({
 | 
				
			|||||||
  data,
 | 
					  data,
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SetAppErrors = messages => ({ type: 'APP_ERRORS', messages })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getAppData = target => dispatch =>
 | 
					export const getAppData = target => dispatch =>
 | 
				
			||||||
  FitTrackeeGenericApi.getData(target)
 | 
					  FitTrackeeGenericApi.getData(target)
 | 
				
			||||||
    .then(ret => {
 | 
					    .then(ret => {
 | 
				
			||||||
@@ -27,14 +30,18 @@ export const getAppData = target => dispatch =>
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
    .catch(error => dispatch(setError(`application|${error}`)))
 | 
					    .catch(error => dispatch(setError(`application|${error}`)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const updateAppConfig = formData => dispatch =>
 | 
					export const updateAppConfig = formData => dispatch => {
 | 
				
			||||||
 | 
					  dispatch(emptyMessages())
 | 
				
			||||||
  FitTrackeeGenericApi.updateData('config', formData)
 | 
					  FitTrackeeGenericApi.updateData('config', formData)
 | 
				
			||||||
    .then(ret => {
 | 
					    .then(ret => {
 | 
				
			||||||
      if (ret.status === 'success') {
 | 
					      if (ret.status === 'success') {
 | 
				
			||||||
        dispatch(setAppConfig(ret.data))
 | 
					        dispatch(setAppConfig(ret.data))
 | 
				
			||||||
        history.push('/admin/application')
 | 
					        history.push('/admin/application')
 | 
				
			||||||
 | 
					      } else if (Array.isArray(ret.message)) {
 | 
				
			||||||
 | 
					        dispatch(SetAppErrors(generateIds(ret.message)))
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        dispatch(setError(`application|${ret.message}`))
 | 
					        dispatch(setError(ret.message))
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .catch(error => dispatch(setError(`application|${error}`)))
 | 
					    .catch(error => dispatch(setError(`application|${error}`)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,16 @@ import i18next from 'i18next'
 | 
				
			|||||||
import FitTrackeeApi from '../fitTrackeeApi/index'
 | 
					import FitTrackeeApi from '../fitTrackeeApi/index'
 | 
				
			||||||
import { history } from '../index'
 | 
					import { history } from '../index'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const emptyMessages = () => ({
 | 
				
			||||||
 | 
					  type: 'CLEAN_ALL_MESSAGES',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setData = (target, data) => ({
 | 
					export const setData = (target, data) => ({
 | 
				
			||||||
  type: 'SET_DATA',
 | 
					  type: 'SET_DATA',
 | 
				
			||||||
  data,
 | 
					  data,
 | 
				
			||||||
  target,
 | 
					  target,
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setPaginatedData = (target, data, pagination) => ({
 | 
					export const setPaginatedData = (target, data, pagination) => ({
 | 
				
			||||||
  type: 'SET_PAGINATED_DATA',
 | 
					  type: 'SET_PAGINATED_DATA',
 | 
				
			||||||
  data,
 | 
					  data,
 | 
				
			||||||
@@ -51,7 +56,7 @@ export const getOrUpdateData = (
 | 
				
			|||||||
    dispatch(setLoading(false))
 | 
					    dispatch(setLoading(false))
 | 
				
			||||||
    return dispatch(setError(`${target}|Incorrect id`))
 | 
					    return dispatch(setError(`${target}|Incorrect id`))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  dispatch(setError(''))
 | 
					  dispatch(emptyMessages())
 | 
				
			||||||
  return FitTrackeeApi[action](target, data)
 | 
					  return FitTrackeeApi[action](target, data)
 | 
				
			||||||
    .then(ret => {
 | 
					    .then(ret => {
 | 
				
			||||||
      if (ret.status === 'success') {
 | 
					      if (ret.status === 'success') {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,13 +48,16 @@ class AdminApplication extends React.Component {
 | 
				
			|||||||
      isInEdition,
 | 
					      isInEdition,
 | 
				
			||||||
      loadAppConfig,
 | 
					      loadAppConfig,
 | 
				
			||||||
      message,
 | 
					      message,
 | 
				
			||||||
 | 
					      messages,
 | 
				
			||||||
      onHandleConfigFormSubmit,
 | 
					      onHandleConfigFormSubmit,
 | 
				
			||||||
      t,
 | 
					      t,
 | 
				
			||||||
    } = this.props
 | 
					    } = this.props
 | 
				
			||||||
    const { formData } = this.state
 | 
					    const { formData } = this.state
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        {message && <Message message={message} t={t} />}
 | 
					        {(message || messages) && (
 | 
				
			||||||
 | 
					          <Message message={message} messages={messages} t={t} />
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
        {Object.keys(formData).length > 0 && (
 | 
					        {Object.keys(formData).length > 0 && (
 | 
				
			||||||
          <div className="row">
 | 
					          <div className="row">
 | 
				
			||||||
            <div className="col-md-12">
 | 
					            <div className="col-md-12">
 | 
				
			||||||
@@ -209,6 +212,7 @@ class AdminApplication extends React.Component {
 | 
				
			|||||||
export default connect(
 | 
					export default connect(
 | 
				
			||||||
  state => ({
 | 
					  state => ({
 | 
				
			||||||
    message: state.message,
 | 
					    message: state.message,
 | 
				
			||||||
 | 
					    messages: state.messages,
 | 
				
			||||||
  }),
 | 
					  }),
 | 
				
			||||||
  dispatch => ({
 | 
					  dispatch => ({
 | 
				
			||||||
    loadAppConfig: () => {
 | 
					    loadAppConfig: () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,10 @@
 | 
				
			|||||||
  "Invalid credentials.": "Invalid credentials.",
 | 
					  "Invalid credentials.": "Invalid credentials.",
 | 
				
			||||||
  "Invalid payload.": "Invalid payload.",
 | 
					  "Invalid payload.": "Invalid payload.",
 | 
				
			||||||
  "Invalid token. Please log in again.": "Invalid token. Please log in again.",
 | 
					  "Invalid token. Please log in again.": "Invalid token. Please log in again.",
 | 
				
			||||||
 | 
					  "Max. files in a zip archive must be greater than 0": "Max. files in a zip archive must be greater than 0",
 | 
				
			||||||
 | 
					  "Max. size of uploaded files must be greater than 0": "Max. size of uploaded files must be greater than 0",
 | 
				
			||||||
  "Max. size of zip archive must be equal or greater than max. size of uploaded files": "Max. size of zip archive must be equal or greater than max. size of uploaded files",
 | 
					  "Max. size of zip archive must be equal or greater than max. size of uploaded files": "Max. size of zip archive must be equal or greater than max. size of uploaded files",
 | 
				
			||||||
 | 
					  "Max. size of zip archive must be greater than 0": "Max. size of zip archive must be greater than 0",
 | 
				
			||||||
  "No file part.": "No file part.",
 | 
					  "No file part.": "No file part.",
 | 
				
			||||||
  "No picture.": "No picture.",
 | 
					  "No picture.": "No picture.",
 | 
				
			||||||
  "No selected file.": "No selected file.",
 | 
					  "No selected file.": "No selected file.",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,10 @@
 | 
				
			|||||||
  "Invalid credentials.": "Identifiants invalides.",
 | 
					  "Invalid credentials.": "Identifiants invalides.",
 | 
				
			||||||
  "Invalid payload.": "Données incorrectes.",
 | 
					  "Invalid payload.": "Données incorrectes.",
 | 
				
			||||||
  "Invalid token. Please log in again.": "Jeton invalide. Merci de vous reconnecter.",
 | 
					  "Invalid token. Please log in again.": "Jeton invalide. Merci de vous reconnecter.",
 | 
				
			||||||
 | 
					  "Max. files in a zip archive must be greater than 0": "Le nombre max. de fichiers dans une archive doit être supérieur à 0",
 | 
				
			||||||
 | 
					  "Max. size of uploaded files must be greater than 0": "La taille max. des fichiers doit être supérieure à 0",
 | 
				
			||||||
  "Max. size of zip archive must be equal or greater than max. size of uploaded files": "La taille max. d'une archive doit être supérieure ou égale à la taille max. d'un fichier",
 | 
					  "Max. size of zip archive must be equal or greater than max. size of uploaded files": "La taille max. d'une archive doit être supérieure ou égale à la taille max. d'un fichier",
 | 
				
			||||||
 | 
					  "Max. size of zip archive must be greater than 0": "La taille max. d'une archive doit être supérieure à 0",
 | 
				
			||||||
  "No file part.": "Pas de fichier fourni.",
 | 
					  "No file part.": "Pas de fichier fourni.",
 | 
				
			||||||
  "No picture.": "Pas d'image.",
 | 
					  "No picture.": "Pas d'image.",
 | 
				
			||||||
  "No selected file.": "Pas de fichier sélectionné.",
 | 
					  "No selected file.": "Pas de fichier sélectionné.",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -108,6 +108,7 @@ const message = (state = initial.message, action) => {
 | 
				
			|||||||
    case 'PICTURE_ERROR':
 | 
					    case 'PICTURE_ERROR':
 | 
				
			||||||
    case 'SET_ERROR':
 | 
					    case 'SET_ERROR':
 | 
				
			||||||
      return action.message
 | 
					      return action.message
 | 
				
			||||||
 | 
					    case 'CLEAN_ALL_MESSAGES':
 | 
				
			||||||
    case 'LOGOUT':
 | 
					    case 'LOGOUT':
 | 
				
			||||||
    case 'PROFILE_SUCCESS':
 | 
					    case 'PROFILE_SUCCESS':
 | 
				
			||||||
    case 'SET_APP_CONFIG':
 | 
					    case 'SET_APP_CONFIG':
 | 
				
			||||||
@@ -122,7 +123,9 @@ const message = (state = initial.message, action) => {
 | 
				
			|||||||
const messages = (state = initial.messages, action) => {
 | 
					const messages = (state = initial.messages, action) => {
 | 
				
			||||||
  switch (action.type) {
 | 
					  switch (action.type) {
 | 
				
			||||||
    case 'AUTH_ERRORS':
 | 
					    case 'AUTH_ERRORS':
 | 
				
			||||||
 | 
					    case 'APP_ERRORS':
 | 
				
			||||||
      return action.messages
 | 
					      return action.messages
 | 
				
			||||||
 | 
					    case 'CLEAN_ALL_MESSAGES':
 | 
				
			||||||
    case 'LOGOUT':
 | 
					    case 'LOGOUT':
 | 
				
			||||||
    case 'PROFILE_SUCCESS':
 | 
					    case 'PROFILE_SUCCESS':
 | 
				
			||||||
    case '@@router/LOCATION_CHANGE':
 | 
					    case '@@router/LOCATION_CHANGE':
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user