1111SERVER_URL = os .environ .get ('TEST_MERGIN_URL' )
1212API_USER = os .environ .get ('TEST_API_USERNAME' )
1313USER_PWD = os .environ .get ('TEST_API_PASSWORD' )
14+ API_USER2 = os .environ .get ('TEST_API_USERNAME2' )
15+ USER_PWD2 = os .environ .get ('TEST_API_PASSWORD2' )
1416TMP_DIR = tempfile .gettempdir ()
1517TEST_DATA_DIR = os .path .join (os .path .dirname (os .path .realpath (__file__ )), 'test_data' )
1618CHANGED_SCHEMA_DIR = os .path .join (os .path .dirname (os .path .realpath (__file__ )), 'modified_schema' )
@@ -22,8 +24,16 @@ def toggle_geodiff(enabled):
2224
2325@pytest .fixture (scope = 'function' )
2426def mc ():
25- assert SERVER_URL and SERVER_URL .rstrip ('/' ) != 'https://public.cloudmergin.com' and API_USER and USER_PWD
26- return MerginClient (SERVER_URL , login = API_USER , password = USER_PWD )
27+ return create_client (API_USER , USER_PWD )
28+
29+ @pytest .fixture (scope = 'function' )
30+ def mc2 ():
31+ return create_client (API_USER2 , USER_PWD2 )
32+
33+
34+ def create_client (user , pwd ):
35+ assert SERVER_URL and SERVER_URL .rstrip ('/' ) != 'https://public.cloudmergin.com' and user and pwd
36+ return MerginClient (SERVER_URL , login = user , password = pwd )
2737
2838
2939def cleanup (mc , project , dirs ):
@@ -32,6 +42,11 @@ def cleanup(mc, project, dirs):
3242 mc .delete_project (project )
3343 except ClientError :
3444 pass
45+ remove_folders (dirs )
46+
47+
48+ def remove_folders (dirs ):
49+ # clean given directories
3550 for d in dirs :
3651 if os .path .exists (d ):
3752 shutil .rmtree (d )
@@ -461,6 +476,7 @@ def test_empty_file_in_subdir(mc):
461476 mc .pull_project (project_dir_2 )
462477 assert os .path .exists (os .path .join (project_dir_2 , 'subdir2' , 'empty2.txt' ))
463478
479+
464480def test_clone_project (mc ):
465481 test_project = 'test_clone_project'
466482 test_project_fullname = API_USER + '/' + test_project
@@ -483,3 +499,148 @@ def test_clone_project(mc):
483499 mc .clone_project (test_project_fullname , cloned_project_name , API_USER )
484500 projects = mc .projects_list (flag = 'created' )
485501 assert any (p for p in projects if p ['name' ] == cloned_project_name and p ['namespace' ] == API_USER )
502+
503+
504+ def test_set_read_write_access (mc ):
505+ test_project = 'test_set_read_write_access'
506+ test_project_fullname = API_USER + '/' + test_project
507+
508+ # cleanups
509+ project_dir = os .path .join (TMP_DIR , test_project , API_USER )
510+ cleanup (mc , test_project_fullname , [project_dir ])
511+
512+ # create new (empty) project on server
513+ mc .create_project (test_project )
514+
515+ # Add writer access to another client
516+ project_info = get_project_info (mc , API_USER , test_project )
517+ access = project_info ['access' ]
518+ access ['writersnames' ].append (API_USER2 )
519+ access ['readersnames' ].append (API_USER2 )
520+ mc .set_project_access (test_project_fullname , access )
521+
522+ # check access
523+ project_info = get_project_info (mc , API_USER , test_project )
524+ access = project_info ['access' ]
525+ assert API_USER2 in access ['writersnames' ]
526+ assert API_USER2 in access ['readersnames' ]
527+
528+
529+ def test_available_storage_validation (mc ):
530+ """
531+ Testing of storage limit - applies to user pushing changes into own project (namespace matching username).
532+ This test also tests giving read and write access to another user. Additionally tests also uploading of big file.
533+ """
534+ test_project = 'test_available_storage_validation'
535+ test_project_fullname = API_USER + '/' + test_project
536+
537+ # cleanups
538+ project_dir = os .path .join (TMP_DIR , test_project , API_USER )
539+ cleanup (mc , test_project_fullname , [project_dir ])
540+
541+ # create new (empty) project on server
542+ mc .create_project (test_project )
543+
544+ # download project
545+ mc .download_project (test_project_fullname , project_dir )
546+
547+ # get user_info about storage capacity
548+ user_info = mc .user_info ()
549+ storage_remaining = user_info ['storage' ] - user_info ['disk_usage' ]
550+
551+ # generate dummy data (remaining storage + extra 1024b)
552+ dummy_data_path = project_dir + "/data"
553+ file_size = storage_remaining + 1024
554+ _generate_big_file (dummy_data_path , file_size )
555+
556+ # try to upload
557+ got_right_err = False
558+ try :
559+ mc .push_project (project_dir )
560+ except ClientError as e :
561+ # Expecting "Storage limit has been reached" error msg.
562+ assert str (e ).startswith ("Storage limit has been reached" )
563+ got_right_err = True
564+ assert got_right_err
565+
566+ # Expecting empty project
567+ project_info = get_project_info (mc , API_USER , test_project )
568+ assert project_info ['meta' ]['files_count' ] == 0
569+ assert project_info ['meta' ]['size' ] == 0
570+
571+
572+ def test_available_storage_validation2 (mc , mc2 ):
573+ """
574+ Testing of storage limit - should not be applied for user pushing changes into project with different namespace.
575+ This should cover the exception of mergin-py-client that a user can push changes to someone else's project regardless
576+ the user's own storage limitation. Of course, other limitations are still applied (write access, owner of
577+ a modified project has to have enough free storage).
578+
579+ Therefore NOTE that there are following assumptions:
580+ - API_USER2's free storage >= API_USER's free storage + 1024b (size of changes to be pushed)
581+ - both accounts should ideally have a free plan
582+ """
583+ test_project = 'test_available_storage_validation2'
584+ test_project_fullname = API_USER2 + '/' + test_project
585+
586+ # cleanups
587+ project_dir = os .path .join (TMP_DIR , test_project , API_USER )
588+ cleanup (mc , test_project_fullname , [project_dir ])
589+ cleanup (mc2 , test_project_fullname , [project_dir ])
590+
591+ # create new (empty) project on server
592+ mc2 .create_project (test_project )
593+
594+ # Add writer access to another client
595+ project_info = get_project_info (mc2 , API_USER2 , test_project )
596+ access = project_info ['access' ]
597+ access ['writersnames' ].append (API_USER )
598+ access ['readersnames' ].append (API_USER )
599+ mc2 .set_project_access (test_project_fullname , access )
600+
601+ # download project
602+ mc .download_project (test_project_fullname , project_dir )
603+
604+ # get user_info about storage capacity
605+ user_info = mc .user_info ()
606+ storage_remaining = user_info ['storage' ] - user_info ['disk_usage' ]
607+
608+ # generate dummy data (remaining storage + extra 1024b)
609+ dummy_data_path = project_dir + "/data"
610+ file_size = storage_remaining + 1024
611+ _generate_big_file (dummy_data_path , file_size )
612+
613+ # try to upload
614+ mc .push_project (project_dir )
615+
616+ # Check project content
617+ project_info = get_project_info (mc2 , API_USER2 , test_project )
618+ assert project_info ['meta' ]['files_count' ] == 1
619+ assert project_info ['meta' ]['size' ] == file_size
620+
621+ # remove dummy big file from a disk
622+ remove_folders ([project_dir ])
623+
624+
625+ def get_project_info (mc , namespace , project_name ):
626+ """
627+ Returns first (and suppose to be just one) project info dict of project matching given namespace and name.
628+ :param mc: MerginClient instance
629+ :param namespace: project's namespace
630+ :param project_name: project's name
631+ :return: dict with project info
632+ """
633+ projects = mc .projects_list (flag = 'created' )
634+ test_project_list = [p for p in projects if p ['name' ] == project_name and p ['namespace' ] == namespace ]
635+ assert len (test_project_list ) == 1
636+ return test_project_list [0 ]
637+
638+
639+ def _generate_big_file (filepath , size ):
640+ """
641+ generate big binary file with the specified size in bytes
642+ :param filepath: full filepath
643+ :param size: the size in bytes
644+ """
645+ with open (filepath , 'wb' ) as fout :
646+ fout .write (b"\0 " * size )
0 commit comments