22#
33# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
44
5- import datetime
65import json
76import os
8- from unittest .mock import patch
9-
7+ import shutil
108import pytest
9+ from unittest .mock import patch
10+ from pathlib import Path
11+ from datetime import datetime , timedelta
1112from flask import url_for
1213
13- from ..app import db
14+ from ..app import db , current_app
1415from ..sync .models import AccessRequest , Project , ProjectRole , RequestStatus
1516from ..auth .models import User
1617from ..config import Configuration
2021 login ,
2122 create_project ,
2223 create_workspace ,
24+ modify_file_times ,
2325)
2426
2527
@@ -174,7 +176,7 @@ def test_project_access_request(client):
174176
175177 # try with inactive project
176178 rp = create_project ("removed_project" , test_workspace , user )
177- rp .removed_at = datetime .datetime . utcnow ()
179+ rp .removed_at = datetime .utcnow ()
178180 db .session .commit ()
179181
180182 resp = client .post (
@@ -193,7 +195,7 @@ def test_project_access_request(client):
193195 assert resp .status_code == 200
194196 resp = client .get ("/app/project/access-requests?page=1&per_page=10" )
195197 assert resp .json .get ("count" ) == 1
196- rp .removed_at = datetime .datetime . utcnow ()
198+ rp .removed_at = datetime .utcnow ()
197199 db .session .commit ()
198200 access_request = AccessRequest .query .filter (
199201 AccessRequest .project_id == rp .id
@@ -375,7 +377,7 @@ def test_admin_project_list(client):
375377 assert resp .json .get ("count" ) == 1
376378 # mark as inactive
377379 p = Project .query .get (resp .json ["items" ][0 ]["id" ])
378- p .removed_at = datetime .datetime . utcnow ()
380+ p .removed_at = datetime .utcnow ()
379381 p .removed_by = user .id
380382 db .session .commit ()
381383
@@ -423,37 +425,72 @@ def test_admin_project_list(client):
423425
424426
425427test_download_proj_data = [
426- (None , 202 ),
427- ("v1" , 202 ),
428- ("v100" , 404 ),
429- (None , 200 ),
428+ # zips do not exist, version not specified -> call celery task to create zip with latest version
429+ (0 , 0 , 0 , None , 202 , 1 ),
430+ # expired partial zip exists -> call celery task
431+ (0 , 1 , 1 , None , 202 , 1 ),
432+ # valid partial zip exists -> return, do not call celery
433+ (0 , 1 , 0 , None , 202 , 0 ),
434+ # zips do not exist, version specified -> call celery task with specified version
435+ (0 , 0 , 0 , "v1" , 202 , 1 ),
436+ # specified version does not exist -> 404
437+ (0 , 0 , 0 , "v100" , 404 , 0 ),
438+ # zip is ready to download
439+ (1 , 0 , 0 , None , 200 , 0 ),
430440]
431441
432442
433- @pytest .mark .parametrize ("version,expected" , test_download_proj_data )
443+ @pytest .mark .parametrize (
444+ "zipfile,partial,expired,version,exp_resp,exp_call" , test_download_proj_data
445+ )
434446@patch ("mergin.sync.tasks.create_project_version_zip.delay" )
435- def test_download_project (mock_create_zip , client , version , expected , diff_project ):
436- if expected == 200 :
437- project_version = diff_project .get_latest_version ()
438- os .mknod (project_version .zip_path )
447+ def test_download_project (
448+ mock_create_zip ,
449+ client ,
450+ zipfile ,
451+ partial ,
452+ expired ,
453+ version ,
454+ exp_resp ,
455+ exp_call ,
456+ diff_project ,
457+ ):
458+ """Test download endpoint responses and celery task calling"""
459+ # prepare initial state according to testcase
460+ project_version = diff_project .get_latest_version ()
461+ if zipfile :
462+ zip_path = Path (project_version .zip_path )
463+ if zip_path .parent .exists ():
464+ shutil .rmtree (zip_path .parent , ignore_errors = True )
465+ zip_path .parent .mkdir (parents = True , exist_ok = True )
466+ zip_path .touch ()
467+ if partial :
468+ temp_zip_path = Path (project_version .zip_path + ".partial" )
469+ shutil .rmtree (temp_zip_path .parent , ignore_errors = True )
470+ temp_zip_path .parent .mkdir (parents = True , exist_ok = True )
471+ temp_zip_path .touch ()
472+ if expired :
473+ new_time = datetime .now () - timedelta (
474+ seconds = current_app .config ["PARTIAL_ZIP_EXPIRATION" ] + 1
475+ )
476+ modify_file_times (temp_zip_path , new_time )
439477 resp = client .get (
440478 url_for (
441479 "/app.mergin_sync_private_api_controller_download_project" ,
442480 id = diff_project .id ,
443481 version = version if version else "" ,
444482 )
445483 )
446- if expected == 200 :
447- os .remove (project_version .zip_path )
448- assert resp .status_code == expected
449- assert mock_create_zip .called if expected == 202 else not mock_create_zip .called
450- if not version and expected != 200 :
484+ assert resp .status_code == exp_resp
485+ assert mock_create_zip .called == exp_call
486+ if not version and exp_call :
451487 call_args , _ = mock_create_zip .call_args
452488 args = call_args [0 ]
453489 assert args == diff_project .latest_version
454490
455491
456492def test_large_project_download_fail (client , diff_project ):
493+ """Test downloading too large project is refused"""
457494 resp = client .get (
458495 url_for (
459496 "/app.mergin_sync_private_api_controller_download_project" ,
@@ -494,4 +531,24 @@ def test_remove_abandoned_zip(mock_prepare_zip, client, diff_project):
494531 )
495532 assert mock_prepare_zip .called
496533 assert resp .status_code == 202
497- assert not os .path .exists (partial_zip_path )
534+
535+
536+ @patch ("mergin.sync.tasks.create_project_version_zip.delay" )
537+ def test_download_project_request_method (mock_prepare_zip , client , diff_project ):
538+ """Test head request does not create a celery job"""
539+ resp = client .head (
540+ url_for (
541+ "/app.mergin_sync_private_api_controller_download_project" ,
542+ id = diff_project .id ,
543+ )
544+ )
545+ assert not mock_prepare_zip .called
546+ assert resp .status_code == 202
547+ resp = client .get (
548+ url_for (
549+ "/app.mergin_sync_private_api_controller_download_project" ,
550+ id = diff_project .id ,
551+ )
552+ )
553+ assert mock_prepare_zip .called
554+ assert resp .status_code == 202
0 commit comments