@@ -811,6 +811,155 @@ def test_init_with_tenant_and_cert_args_auth(
811811 name = "Unknown" , id = "mocked_tenant_id"
812812 )
813813
814+ def test_init_with_device_code_auth_command_line (
815+ self , mock_fab_auth , mock_fab_context
816+ ):
817+ # Arrange
818+ args = prepare_auth_args ({"use_device_code" : True })
819+
820+ # Act
821+ result = fab_auth .init (args )
822+
823+ # Assert
824+ mock_fab_auth_instance = mock_fab_auth .get ("instance" )
825+ mock_fab_auth_instance .set_access_mode .assert_called_with ("user" , None )
826+ assert_get_access_token (mock_fab_auth_instance )
827+ assert result is True
828+
829+ assert_fab_context (mock_fab_context )
830+
831+ def test_init_with_device_code_auth_command_line_with_tenant (
832+ self , mock_fab_auth , mock_fab_context
833+ ):
834+ # Arrange
835+ args = prepare_auth_args ({"use_device_code" : True , "tenant" : "mock_tenant" })
836+
837+ # Act
838+ result = fab_auth .init (args )
839+
840+ # Assert
841+ mock_fab_auth_instance = mock_fab_auth .get ("instance" )
842+ mock_fab_auth_instance .set_access_mode .assert_called_with ("user" , "mock_tenant" )
843+ assert_get_access_token (mock_fab_auth_instance )
844+ assert result is True
845+
846+ assert_fab_context (mock_fab_context )
847+
848+ def test_init_with_device_code_auth_interactive (
849+ self , mock_fab_auth , mock_fab_context
850+ ):
851+ # Arrange
852+ with patch (
853+ "fabric_cli.utils.fab_ui.prompt_select_item" ,
854+ return_value = "Device code" ,
855+ ):
856+ args = prepare_auth_args ()
857+
858+ # Act
859+ result = fab_auth .init (args )
860+
861+ # Assert
862+ mock_fab_auth_instance = mock_fab_auth .get ("instance" )
863+ mock_fab_auth_instance .set_access_mode .assert_called_with ("user" , None )
864+ assert_get_access_token (mock_fab_auth_instance )
865+ assert result is True
866+
867+ assert_fab_context (mock_fab_context )
868+
869+ def test_init_with_device_code_takes_precedence_over_identity (
870+ self , mock_fab_auth , mock_fab_context
871+ ):
872+ """When both --use-device-code and --identity are passed, device code wins."""
873+ # Arrange
874+ args = prepare_auth_args ({"use_device_code" : True , "identity" : True })
875+
876+ # Act
877+ result = fab_auth .init (args )
878+
879+ # Assert: device code path was taken (set_access_mode("user")), not managed identity
880+ mock_fab_auth_instance = mock_fab_auth .get ("instance" )
881+ mock_fab_auth_instance .set_access_mode .assert_called_with ("user" , None )
882+ mock_fab_auth_instance .set_managed_identity .assert_not_called ()
883+ assert_get_access_token (mock_fab_auth_instance )
884+ assert result is True
885+
886+ assert_fab_context (mock_fab_context )
887+
888+ def test_init_with_device_code_takes_precedence_over_spn_args (
889+ self , mock_fab_auth , mock_fab_context
890+ ):
891+ """When --use-device-code is passed alongside -u/-p, device code wins."""
892+ # Arrange
893+ args = prepare_auth_args (
894+ {
895+ "use_device_code" : True ,
896+ "username" : "some_client_id" ,
897+ "password" : "some_secret" ,
898+ "tenant" : "some_tenant" ,
899+ }
900+ )
901+
902+ # Act
903+ result = fab_auth .init (args )
904+
905+ # Assert: device code path was taken, not SPN
906+ mock_fab_auth_instance = mock_fab_auth .get ("instance" )
907+ mock_fab_auth_instance .set_access_mode .assert_called_with ("user" , "some_tenant" )
908+ mock_fab_auth_instance .set_spn .assert_not_called ()
909+ assert_get_access_token (mock_fab_auth_instance )
910+ assert result is True
911+
912+ assert_fab_context (mock_fab_context )
913+
914+ def test_init_with_device_code_resets_flag_on_success (
915+ self , mock_fab_auth , mock_fab_context
916+ ):
917+ """Verify _use_device_code flag is reset to False after successful login."""
918+ # Arrange
919+ args = prepare_auth_args ({"use_device_code" : True })
920+
921+ # Act
922+ fab_auth .init (args )
923+
924+ # Assert: flag should be reset
925+ mock_fab_auth_instance = mock_fab_auth .get ("instance" )
926+ assert mock_fab_auth_instance ._use_device_code is False
927+
928+ def test_init_with_device_code_resets_flag_on_exception (self , mock_fab_auth ):
929+ """Verify _use_device_code flag is reset even when get_access_token raises."""
930+ # Arrange
931+ mock_fab_auth_instance = mock_fab_auth .get ("instance" )
932+ mock_fab_auth_instance .get_access_token .side_effect = FabricCLIError (
933+ "Token acquisition failed" ,
934+ fab_constant .ERROR_AUTHENTICATION_FAILED ,
935+ )
936+ args = prepare_auth_args ({"use_device_code" : True })
937+
938+ # Act
939+ with pytest .raises (FabricCLIError ):
940+ fab_auth .init (args )
941+
942+ # Assert: flag should be reset thanks to try/finally
943+ assert mock_fab_auth_instance ._use_device_code is False
944+
945+ def test_init_with_device_code_interactive_resets_flag_on_success (
946+ self , mock_fab_auth , mock_fab_context
947+ ):
948+ """Verify _use_device_code flag is reset after interactive device code login."""
949+ # Arrange
950+ with patch (
951+ "fabric_cli.utils.fab_ui.prompt_select_item" ,
952+ return_value = "Device code" ,
953+ ):
954+ args = prepare_auth_args ()
955+
956+ # Act
957+ fab_auth .init (args )
958+
959+ # Assert: flag should be reset
960+ mock_fab_auth_instance = mock_fab_auth .get ("instance" )
961+ assert mock_fab_auth_instance ._use_device_code is False
962+
814963 def test_init_with_args_auth_missing_arg_raise_exception (self ):
815964 # Test without tenant
816965 # Arrange
@@ -821,6 +970,7 @@ def test_init_with_args_auth_missing_arg_raise_exception(self):
821970 identity = None ,
822971 certificate = None ,
823972 federated_token = None ,
973+ use_device_code = None ,
824974 )
825975
826976 # Act
@@ -837,6 +987,7 @@ def test_init_with_args_auth_missing_arg_raise_exception(self):
837987 identity = None ,
838988 certificate = None ,
839989 federated_token = None ,
990+ use_device_code = None ,
840991 )
841992
842993 # Act
@@ -853,6 +1004,7 @@ def test_init_with_args_auth_missing_arg_raise_exception(self):
8531004 identity = None ,
8541005 certificate = None ,
8551006 federated_token = None ,
1007+ use_device_code = None ,
8561008 )
8571009
8581010 # Act
@@ -971,6 +1123,7 @@ def prepare_auth_args(args=None):
9711123 "identity" ,
9721124 "certificate" ,
9731125 "federated_token" ,
1126+ "use_device_code" ,
9741127 ]
9751128 }
9761129 )
0 commit comments