@@ -759,5 +759,292 @@ def __init__(self, status_code):
759759 self .status_code = status_code
760760
761761
762+ class TestWebappDeployWithSrcUrl (unittest .TestCase ):
763+ """Tests for webapp deploy with --src-url sync/async behavior"""
764+
765+ @mock .patch ('azure.cli.command_modules.appservice.custom._poll_deployment_runtime_status' )
766+ @mock .patch ('azure.cli.command_modules.appservice.custom._build_deploymentstatus_url' )
767+ @mock .patch ('azure.cli.command_modules.appservice.custom.send_raw_request' )
768+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_url' )
769+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_request_body' )
770+ @mock .patch ('azure.cli.command_modules.appservice.custom._build_onedeploy_url' )
771+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_status_url' )
772+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_ondeploy_headers' )
773+ def test_src_url_sync_deployment_default (self , headers_mock , status_url_mock , build_url_mock_onedeploy ,
774+ body_mock , get_url_mock , send_raw_mock , build_url_mock , poll_mock ):
775+ """Test that --src-url defaults to sync deployment (polls for completion)"""
776+ from azure .cli .command_modules .appservice .custom import _make_onedeploy_request
777+
778+ # Mock helper functions
779+ body_mock .return_value = ('{"type": "zip"}' , None )
780+ build_url_mock_onedeploy .return_value = 'https://management.azure.com/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/myapp/extensions/onedeploy'
781+ status_url_mock .return_value = 'https://myapp.scm.azurewebsites.net/api/deployments/latest'
782+ headers_mock .return_value = {'Content-Type' : 'application/json' }
783+
784+ # Mock the ARM response with deployment ID
785+ class MockResponse :
786+ status_code = 200
787+ headers = {'content-type' : 'application/json' }
788+ text = '{"id": "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/myapp/extensions/onedeploy/123456"}'
789+
790+ def json (self ):
791+ return {
792+ 'id' : '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/myapp/extensions/onedeploy/123456' ,
793+ 'properties' : {'provisioningState' : 'InProgress' }
794+ }
795+
796+ send_raw_mock .return_value = MockResponse ()
797+ build_url_mock .return_value = 'https://management.azure.com/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/myapp/deploymentStatus/123456'
798+ poll_mock .return_value = {'status' : 'RuntimeSuccessful' }
799+ get_url_mock .return_value = 'https://myapp.azurewebsites.net'
800+
801+ # Create params object
802+ class Params :
803+ src_url = 'https://example.com/myapp.zip'
804+ src_path = None
805+ is_async_deployment = None # Default should be sync
806+ cmd = _get_test_cmd ()
807+ resource_group_name = 'test-rg'
808+ webapp_name = 'test-app'
809+ slot = None
810+ timeout = None
811+ is_linux_webapp = False
812+ is_functionapp = False
813+ enable_kudu_warmup = False
814+
815+ params = Params ()
816+
817+ # Execute
818+ result = _make_onedeploy_request (params )
819+
820+ # Assert polling was called
821+ poll_mock .assert_called_once ()
822+ # Verify deployment ID was extracted correctly
823+ build_url_mock .assert_called_with (params .cmd , 'test-rg' , 'test-app' , None , '123456' )
824+
825+ @mock .patch ('azure.cli.command_modules.appservice.custom._poll_deployment_runtime_status' )
826+ @mock .patch ('azure.cli.command_modules.appservice.custom._build_deploymentstatus_url' )
827+ @mock .patch ('azure.cli.command_modules.appservice.custom.send_raw_request' )
828+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_url' )
829+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_request_body' )
830+ @mock .patch ('azure.cli.command_modules.appservice.custom._build_onedeploy_url' )
831+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_status_url' )
832+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_ondeploy_headers' )
833+ def test_src_url_sync_deployment_explicit_false (self , headers_mock , status_url_mock , build_url_mock_onedeploy ,
834+ body_mock , get_url_mock , send_raw_mock , build_url_mock , poll_mock ):
835+ """Test that --src-url with --async false triggers polling"""
836+ from azure .cli .command_modules .appservice .custom import _make_onedeploy_request
837+
838+ # Mock helper functions
839+ body_mock .return_value = ('{"type": "zip"}' , None )
840+ build_url_mock_onedeploy .return_value = 'https://management.azure.com/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/myapp/extensions/onedeploy'
841+ status_url_mock .return_value = 'https://myapp.scm.azurewebsites.net/api/deployments/latest'
842+ headers_mock .return_value = {'Content-Type' : 'application/json' }
843+
844+ # Mock the ARM response with deployment ID in properties
845+ class MockResponse :
846+ status_code = 200
847+ headers = {'content-type' : 'application/json' }
848+ text = '{"properties": {"deploymentId": "dep-789"}}'
849+
850+ def json (self ):
851+ return {
852+ 'properties' : {'deploymentId' : 'dep-789' , 'provisioningState' : 'InProgress' }
853+ }
854+
855+ send_raw_mock .return_value = MockResponse ()
856+ build_url_mock .return_value = 'https://management.azure.com/.../deploymentStatus/dep-789'
857+ poll_mock .return_value = {'status' : 'RuntimeSuccessful' }
858+ get_url_mock .return_value = 'https://myapp.azurewebsites.net'
859+
860+ # Create params object with explicit async=false
861+ class Params :
862+ src_url = 'https://example.com/myapp.zip'
863+ src_path = None
864+ is_async_deployment = False # Explicitly set to False
865+ cmd = _get_test_cmd ()
866+ resource_group_name = 'test-rg'
867+ webapp_name = 'test-app'
868+ slot = None
869+ timeout = None
870+ is_linux_webapp = False
871+ is_functionapp = False
872+ enable_kudu_warmup = False
873+
874+ params = Params ()
875+
876+ # Execute
877+ result = _make_onedeploy_request (params )
878+
879+ # Assert polling was called
880+ poll_mock .assert_called_once ()
881+ build_url_mock .assert_called_with (params .cmd , 'test-rg' , 'test-app' , None , 'dep-789' )
882+
883+ @mock .patch ('azure.cli.command_modules.appservice.custom.send_raw_request' )
884+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_url' )
885+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_request_body' )
886+ @mock .patch ('azure.cli.command_modules.appservice.custom._build_onedeploy_url' )
887+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_status_url' )
888+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_ondeploy_headers' )
889+ def test_src_url_async_deployment (self , headers_mock , status_url_mock , build_url_mock_onedeploy ,
890+ body_mock , get_url_mock , send_raw_mock ):
891+ """Test that --src-url with --async true returns immediately without polling"""
892+ from azure .cli .command_modules .appservice .custom import _make_onedeploy_request
893+
894+ # Mock helper functions
895+ body_mock .return_value = ('{"type": "zip"}' , None )
896+ build_url_mock_onedeploy .return_value = 'https://management.azure.com/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/myapp/extensions/onedeploy'
897+ status_url_mock .return_value = 'https://myapp.scm.azurewebsites.net/api/deployments/latest'
898+ headers_mock .return_value = {'Content-Type' : 'application/json' }
899+
900+ # Mock the ARM response
901+ class MockResponse :
902+ status_code = 200
903+ headers = {'content-type' : 'application/json' }
904+ text = '{"id": "/subscriptions/sub/.../123456"}'
905+
906+ def json (self ):
907+ return {
908+ 'id' : '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/myapp/extensions/onedeploy/123456' ,
909+ 'properties' : {'provisioningState' : 'InProgress' }
910+ }
911+
912+ send_raw_mock .return_value = MockResponse ()
913+ get_url_mock .return_value = 'https://myapp.azurewebsites.net'
914+
915+ # Create params object with async=true
916+ class Params :
917+ src_url = 'https://example.com/myapp.zip'
918+ src_path = None
919+ is_async_deployment = True # Async mode
920+ cmd = _get_test_cmd ()
921+ resource_group_name = 'test-rg'
922+ webapp_name = 'test-app'
923+ slot = None
924+ timeout = None
925+ is_linux_webapp = False
926+ is_functionapp = False
927+ enable_kudu_warmup = False
928+
929+ params = Params ()
930+
931+ # Execute
932+ result = _make_onedeploy_request (params )
933+
934+ # Assert result is immediate response (provisioningState returned)
935+ self .assertEqual (result .get ('provisioningState' ), 'InProgress' )
936+
937+ @mock .patch ('azure.cli.command_modules.appservice.custom.send_raw_request' )
938+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_url' )
939+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_request_body' )
940+ @mock .patch ('azure.cli.command_modules.appservice.custom._build_onedeploy_url' )
941+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_status_url' )
942+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_ondeploy_headers' )
943+ def test_src_url_no_deployment_id (self , headers_mock , status_url_mock , build_url_mock_onedeploy ,
944+ body_mock , get_url_mock , send_raw_mock ):
945+ """Test that --src-url falls back gracefully when no deployment ID is found"""
946+ from azure .cli .command_modules .appservice .custom import _make_onedeploy_request
947+
948+ # Mock helper functions
949+ body_mock .return_value = ('{"type": "zip"}' , None )
950+ build_url_mock_onedeploy .return_value = 'https://management.azure.com/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/myapp/extensions/onedeploy'
951+ status_url_mock .return_value = 'https://myapp.scm.azurewebsites.net/api/deployments/latest'
952+ headers_mock .return_value = {'Content-Type' : 'application/json' }
953+
954+ # Mock the ARM response without deployment ID
955+ class MockResponse :
956+ status_code = 200
957+ headers = {'content-type' : 'application/json' }
958+ text = '{}'
959+
960+ def json (self ):
961+ return {
962+ 'properties' : {'provisioningState' : 'Succeeded' }
963+ }
964+
965+ send_raw_mock .return_value = MockResponse ()
966+ get_url_mock .return_value = 'https://myapp.azurewebsites.net'
967+
968+ # Create params object
969+ class Params :
970+ src_url = 'https://example.com/myapp.zip'
971+ src_path = None
972+ is_async_deployment = None # Default
973+ cmd = _get_test_cmd ()
974+ resource_group_name = 'test-rg'
975+ webapp_name = 'test-app'
976+ slot = None
977+ timeout = None
978+ is_linux_webapp = False
979+ is_functionapp = False
980+ enable_kudu_warmup = False
981+
982+ params = Params ()
983+
984+ # Execute
985+ result = _make_onedeploy_request (params )
986+
987+ # Assert result is immediate response (no polling)
988+ self .assertEqual (result .get ('provisioningState' ), 'Succeeded' )
989+
990+
991+ @mock .patch ('azure.cli.command_modules.appservice.custom._poll_deployment_runtime_status' ,
992+ side_effect = RuntimeError ("Simulated polling failure" ))
993+ @mock .patch ('azure.cli.command_modules.appservice.custom._build_deploymentstatus_url' ,
994+ return_value = 'https://management.azure.com/.../deploymentStatus/abc123' )
995+ @mock .patch ('azure.cli.command_modules.appservice.custom.send_raw_request' )
996+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_url' ,
997+ return_value = 'https://test-app.azurewebsites.net' )
998+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_request_body' ,
999+ return_value = ('{"type": "zip"}' , None ))
1000+ @mock .patch ('azure.cli.command_modules.appservice.custom._build_onedeploy_url' ,
1001+ return_value = 'https://management.azure.com/.../onedeploy' )
1002+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_onedeploy_status_url' ,
1003+ return_value = 'https://myapp.scm.azurewebsites.net/api/deployments/latest' )
1004+ @mock .patch ('azure.cli.command_modules.appservice.custom._get_ondeploy_headers' ,
1005+ return_value = {'Content-Type' : 'application/json' })
1006+ def test_src_url_fallback_on_poll_exception (self , headers_mock , status_url_mock ,
1007+ build_url_mock_onedeploy , body_mock ,
1008+ get_url_mock , send_raw_mock ,
1009+ build_status_url_mock , poll_mock ):
1010+ """Test that when _poll_deployment_runtime_status raises, the fallback returns the ARM response."""
1011+ from azure .cli .command_modules .appservice .custom import _make_onedeploy_request
1012+
1013+ class MockResponse :
1014+ status_code = 200
1015+ headers = {'content-type' : 'application/json' }
1016+ text = '{"id": "/subs/sub/rg/rg/providers/Microsoft.Web/sites/app/extensions/onedeploy/abc123"}'
1017+
1018+ def json (self ):
1019+ return {
1020+ 'id' : '/subs/sub/rg/rg/providers/Microsoft.Web/sites/app/extensions/onedeploy/abc123' ,
1021+ 'properties' : {'provisioningState' : 'InProgress' , 'deployer' : 'ZipDeploy' }
1022+ }
1023+
1024+ send_raw_mock .return_value = MockResponse ()
1025+
1026+ class Params :
1027+ src_url = 'https://example.com/myapp.zip'
1028+ src_path = None
1029+ is_async_deployment = None # sync mode
1030+ cmd = _get_test_cmd ()
1031+ resource_group_name = 'test-rg'
1032+ webapp_name = 'test-app'
1033+ slot = None
1034+ timeout = None
1035+ is_linux_webapp = False
1036+ is_functionapp = False
1037+ enable_kudu_warmup = False
1038+
1039+ params = Params ()
1040+ result = _make_onedeploy_request (params )
1041+
1042+ # Polling was attempted and raised
1043+ poll_mock .assert_called_once ()
1044+ # Fallback: ARM response properties are returned instead of crashing
1045+ self .assertIsNotNone (result )
1046+ self .assertEqual (result .get ('provisioningState' ), 'InProgress' )
1047+ self .assertEqual (result .get ('deployer' ), 'ZipDeploy' )
1048+
7621049if __name__ == '__main__' :
7631050 unittest .main ()
0 commit comments