@@ -733,6 +733,138 @@ void MyActivityImpl(TaskActivityContext ctx) =>
733733 Assert . Null ( innerFailure . Properties [ "NullProperty" ] ) ;
734734 }
735735
736+ /// <summary>
737+ /// Tests that exception properties are included in FailureDetails when an orchestration
738+ /// throws exception directly without calling any other functions and a custom provider is set.
739+ /// </summary>
740+ [ Fact ]
741+ public async Task OrchestrationDirectArgumentOutOfRangeExceptionProperties ( )
742+ {
743+ TaskName orchestratorName = "OrchestrationWithDirectArgumentException" ;
744+ string paramName = "testParameter" ;
745+ string actualValue = "invalidValue" ;
746+ string errorMessage = $ "Parameter '{ paramName } ' is out of range.";
747+
748+ void MyOrchestrationImpl ( TaskOrchestrationContext ctx ) =>
749+ throw new ArgumentOutOfRangeException ( paramName , actualValue , errorMessage ) ;
750+
751+ await using HostTestLifetime server = await this . StartWorkerAsync ( b =>
752+ {
753+ // Register the custom exception properties provider
754+ b . Services . AddSingleton < IExceptionPropertiesProvider , TestExceptionPropertiesProvider > ( ) ;
755+
756+ b . AddTasks ( tasks => tasks
757+ . AddOrchestratorFunc ( orchestratorName , MyOrchestrationImpl ) ) ;
758+ } ) ;
759+
760+ string instanceId = await server . Client . ScheduleNewOrchestrationInstanceAsync ( orchestratorName ) ;
761+ OrchestrationMetadata metadata = await server . Client . WaitForInstanceCompletionAsync (
762+ instanceId , getInputsAndOutputs : true , this . TimeoutToken ) ;
763+
764+ Assert . NotNull ( metadata ) ;
765+ Assert . Equal ( instanceId , metadata . InstanceId ) ;
766+ Assert . Equal ( OrchestrationRuntimeStatus . Failed , metadata . RuntimeStatus ) ;
767+
768+ Assert . NotNull ( metadata . FailureDetails ) ;
769+ TaskFailureDetails failureDetails = metadata . FailureDetails ! ;
770+ Assert . Equal ( typeof ( ArgumentOutOfRangeException ) . FullName , failureDetails . ErrorType ) ;
771+ Assert . Contains ( errorMessage , failureDetails . ErrorMessage ) ;
772+
773+ // Check that custom properties are included for ArgumentOutOfRangeException
774+ Assert . NotNull ( failureDetails . Properties ) ;
775+ Assert . Equal ( 2 , failureDetails . Properties . Count ) ;
776+
777+ // Verify parameter name property
778+ Assert . True ( failureDetails . Properties . ContainsKey ( "Name" ) ) ;
779+ Assert . Equal ( paramName , failureDetails . Properties [ "Name" ] ) ;
780+
781+ // Verify actual value property
782+ Assert . True ( failureDetails . Properties . ContainsKey ( "Value" ) ) ;
783+ Assert . Equal ( actualValue , failureDetails . Properties [ "Value" ] ) ;
784+
785+ // Verify the exception type is correctly identified
786+ Assert . True ( failureDetails . IsCausedBy < ArgumentOutOfRangeException > ( ) ) ;
787+ }
788+
789+ /// <summary>
790+ /// Tests that exception properties are included through nested orchestration calls when
791+ /// a provider is set.
792+ /// </summary>
793+ [ Fact ]
794+ public async Task NestedOrchestrationArgumentOutOfRangeExceptionProperties ( )
795+ {
796+ TaskName parentOrchestratorName = "ParentOrchestrationWithNestedArgumentException" ;
797+ TaskName subOrchestratorName = "SubOrchestrationWithArgumentException" ;
798+ TaskName activityName = "ActivityWithArgumentException" ;
799+ string paramName = "nestedParameter" ;
800+ string actualValue = "badNestedValue" ;
801+ string errorMessage = $ "Nested parameter '{ paramName } ' is out of range.";
802+
803+ async Task ParentOrchestrationImpl ( TaskOrchestrationContext ctx ) =>
804+ await ctx . CallSubOrchestratorAsync ( subOrchestratorName ) ;
805+
806+ async Task SubOrchestrationImpl ( TaskOrchestrationContext ctx ) =>
807+ await ctx . CallActivityAsync ( activityName ) ;
808+
809+ void ActivityImpl ( TaskActivityContext ctx ) =>
810+ throw new ArgumentOutOfRangeException ( paramName , actualValue , errorMessage ) ;
811+
812+ await using HostTestLifetime server = await this . StartWorkerAsync ( b =>
813+ {
814+ // Register the custom exception properties provider
815+ b . Services . AddSingleton < IExceptionPropertiesProvider , TestExceptionPropertiesProvider > ( ) ;
816+
817+ b . AddTasks ( tasks => tasks
818+ . AddOrchestratorFunc ( parentOrchestratorName , ParentOrchestrationImpl )
819+ . AddOrchestratorFunc ( subOrchestratorName , SubOrchestrationImpl )
820+ . AddActivityFunc ( activityName , ActivityImpl ) ) ;
821+ } ) ;
822+
823+ string instanceId = await server . Client . ScheduleNewOrchestrationInstanceAsync ( parentOrchestratorName ) ;
824+ OrchestrationMetadata metadata = await server . Client . WaitForInstanceCompletionAsync (
825+ instanceId , getInputsAndOutputs : true , this . TimeoutToken ) ;
826+
827+ Assert . NotNull ( metadata ) ;
828+ Assert . Equal ( instanceId , metadata . InstanceId ) ;
829+ Assert . Equal ( OrchestrationRuntimeStatus . Failed , metadata . RuntimeStatus ) ;
830+
831+ Assert . NotNull ( metadata . FailureDetails ) ;
832+ TaskFailureDetails failureDetails = metadata . FailureDetails ! ;
833+
834+ // The parent orchestration failed due to a TaskFailedException from the sub-orchestration
835+ Assert . Equal ( typeof ( TaskFailedException ) . FullName , failureDetails . ErrorType ) ;
836+ Assert . Contains ( subOrchestratorName , failureDetails . ErrorMessage ) ;
837+
838+ // Check the first level inner failure (sub-orchestration failure)
839+ Assert . NotNull ( failureDetails . InnerFailure ) ;
840+ TaskFailureDetails subOrchestrationFailure = failureDetails . InnerFailure ! ;
841+ Assert . Equal ( typeof ( TaskFailedException ) . FullName , subOrchestrationFailure . ErrorType ) ;
842+ Assert . Contains ( activityName , subOrchestrationFailure . ErrorMessage ) ;
843+
844+ // Check the second level inner failure (activity failure with ArgumentOutOfRangeException)
845+ Assert . NotNull ( subOrchestrationFailure . InnerFailure ) ;
846+ TaskFailureDetails activityFailure = subOrchestrationFailure . InnerFailure ! ;
847+ Assert . Equal ( typeof ( ArgumentOutOfRangeException ) . FullName , activityFailure . ErrorType ) ;
848+ Assert . Contains ( errorMessage , activityFailure . ErrorMessage ) ;
849+
850+ // Verify that the original ArgumentOutOfRangeException properties are preserved
851+ Assert . NotNull ( activityFailure . Properties ) ;
852+ Assert . Equal ( 2 , activityFailure . Properties . Count ) ;
853+
854+ // Verify parameter name property
855+ Assert . True ( activityFailure . Properties . ContainsKey ( "Name" ) ) ;
856+ Assert . Equal ( paramName , activityFailure . Properties [ "Name" ] ) ;
857+
858+ // Verify actual value property
859+ Assert . True ( activityFailure . Properties . ContainsKey ( "Value" ) ) ;
860+ Assert . Equal ( actualValue , activityFailure . Properties [ "Value" ] ) ;
861+
862+ // Verify the exception type hierarchy is correctly identified
863+ Assert . True ( failureDetails . IsCausedBy < TaskFailedException > ( ) ) ;
864+ Assert . True ( subOrchestrationFailure . IsCausedBy < TaskFailedException > ( ) ) ;
865+ Assert . True ( activityFailure . IsCausedBy < ArgumentOutOfRangeException > ( ) ) ;
866+ }
867+
736868 [ Serializable ]
737869 class CustomException : Exception
738870 {
0 commit comments