@@ -376,6 +376,105 @@ public void AddMetadata_WithMaxDepthReached_ReturnsMaxDepthMessage()
376376 _mockAwsXRayRecorder . Received ( 1 ) . AddMetadata ( "test" , "very_deep" , Arg . Any < object > ( ) ) ;
377377 }
378378
379+ [ Fact ]
380+ public void SanitizeValueForMetadata_WithObjectThatThrowsInToString_ReturnsSanitizationFailedMessage ( )
381+ {
382+ // Arrange - Create an object that throws during ToString and during sanitization
383+ var problematicObject = new ObjectThatThrowsEverywhere ( ) ;
384+
385+ // Act & Assert - Should not throw, should handle gracefully
386+ _xrayRecorder . AddMetadata ( "test" , "throws_everywhere" , problematicObject ) ;
387+
388+ // Verify the call was made with sanitized data
389+ _mockAwsXRayRecorder . Received ( 1 ) . AddMetadata ( "test" , "throws_everywhere" , Arg . Any < object > ( ) ) ;
390+ }
391+
392+ [ Fact ]
393+ public void SanitizeValueForMetadata_WithObjectThatThrowsInSanitization_CatchesException ( )
394+ {
395+ // Arrange - Create an object that will cause an exception during the sanitization process itself
396+ var objectThatCausesRecursionError = new ObjectWithCircularToStringReference ( ) ;
397+
398+ // Act & Assert - Should not throw, should return sanitization failed message
399+ _xrayRecorder . AddMetadata ( "test" , "recursion_error" , objectThatCausesRecursionError ) ;
400+
401+ // Verify the call was made (the sanitization error should be caught and handled)
402+ _mockAwsXRayRecorder . Received ( 1 ) . AddMetadata ( "test" , "recursion_error" , Arg . Any < object > ( ) ) ;
403+ }
404+
405+ [ Fact ]
406+ public void AddMetadata_WithUnsafeArrayElements_TriggersArraySanitization ( )
407+ {
408+ // Arrange - Create array with mixed safe and unsafe elements to trigger IsArrayElementsSafe check
409+ var mixedArray = new object [ ]
410+ {
411+ "safe_string" ,
412+ 42 ,
413+ 42ul , // This will trigger NeedsTypeSanitization = true
414+ new IntPtr ( 123 ) , // This will also trigger sanitization
415+ true
416+ } ;
417+
418+ // Act
419+ _xrayRecorder . AddMetadata ( "test" , "mixed_array" , mixedArray ) ;
420+
421+ // Assert - Should call with sanitized array
422+ _mockAwsXRayRecorder . Received ( 1 ) . AddMetadata ( "test" , "mixed_array" , Arg . Any < object > ( ) ) ;
423+ }
424+
425+ [ Fact ]
426+ public void AddMetadata_WithSpecificUnsafeArrayType_TriggersIsArrayElementsSafeCheck ( )
427+ {
428+ // Arrange - Create a typed array that is NOT in the known safe list but has unsafe elements
429+ // This will force the code to call IsArrayElementsSafe and return false
430+ var unsafeTypedArray = new uint [ ] { 1u , 2u , 3u } ; // uint[] is not in IsKnownSafeArrayType
431+
432+ // Act
433+ _xrayRecorder . AddMetadata ( "test" , "unsafe_typed_array" , unsafeTypedArray ) ;
434+
435+ // Assert - Should call with sanitized array
436+ _mockAwsXRayRecorder . Received ( 1 ) . AddMetadata ( "test" , "unsafe_typed_array" , Arg . Any < object > ( ) ) ;
437+ }
438+
439+ [ Fact ]
440+ public void AddMetadata_WithEntityContainingAnnotations_SanitizesAnnotations ( )
441+ {
442+ // This test will be handled in EntityLevelSanitizationTests since it requires entity manipulation
443+ // But we can test the annotation sanitization indirectly through EndSubsegment
444+
445+ // Arrange - Create a subsegment with annotations that need sanitization
446+ var subsegment = new Subsegment ( "TestSegment" ) ;
447+ subsegment . AddAnnotation ( "safe_annotation" , "safe_value" ) ;
448+ subsegment . AddAnnotation ( "numeric_annotation" , 42 ) ;
449+
450+ var mockTraceContext = Substitute . For < Amazon . XRay . Recorder . Core . Internal . Context . ITraceContext > ( ) ;
451+ mockTraceContext . GetEntity ( ) . Returns ( subsegment ) ;
452+ _mockAwsXRayRecorder . TraceContext . Returns ( mockTraceContext ) ;
453+
454+ // Act - This will trigger entity sanitization including annotations
455+ _xrayRecorder . EndSubsegment ( ) ;
456+
457+ // Assert
458+ _mockAwsXRayRecorder . Received ( 1 ) . EndSubsegment ( ) ;
459+ }
460+
461+ [ Fact ]
462+ public void AddMetadata_WithEntityContainingHttpInfo_SanitizesHttpInfo ( )
463+ {
464+ // Arrange - Create a subsegment (HTTP info will be tested in EntityLevelSanitizationTests)
465+ var subsegment = new Subsegment ( "TestSegment" ) ;
466+
467+ var mockTraceContext = Substitute . For < Amazon . XRay . Recorder . Core . Internal . Context . ITraceContext > ( ) ;
468+ mockTraceContext . GetEntity ( ) . Returns ( subsegment ) ;
469+ _mockAwsXRayRecorder . TraceContext . Returns ( mockTraceContext ) ;
470+
471+ // Act - This will trigger entity sanitization including HTTP info
472+ _xrayRecorder . EndSubsegment ( ) ;
473+
474+ // Assert
475+ _mockAwsXRayRecorder . Received ( 1 ) . EndSubsegment ( ) ;
476+ }
477+
379478 public enum TestEnum
380479 {
381480 Value1 ,
@@ -398,6 +497,54 @@ public override string ToString()
398497 }
399498 }
400499
500+ public class ObjectThatThrowsEverywhere
501+ {
502+ public string ProblematicProperty
503+ {
504+ get => throw new Exception ( "Property access failed" ) ;
505+ }
506+
507+ public override string ToString ( )
508+ {
509+ throw new Exception ( "ToString failed" ) ;
510+ }
511+
512+ public override int GetHashCode ( )
513+ {
514+ throw new Exception ( "GetHashCode failed" ) ;
515+ }
516+ }
517+
518+ public class ObjectWithCircularToStringReference
519+ {
520+ private static int _toStringCallCount = 0 ;
521+
522+ public ObjectWithCircularToStringReference Self { get ; set ; }
523+
524+ public ObjectWithCircularToStringReference ( )
525+ {
526+ Self = this ; // Create circular reference
527+ }
528+
529+ public override string ToString ( )
530+ {
531+ // Prevent infinite recursion by limiting calls
532+ if ( ++ _toStringCallCount > 5 )
533+ {
534+ throw new StackOverflowException ( "Simulated stack overflow during ToString" ) ;
535+ }
536+
537+ try
538+ {
539+ return $ "Object with self: { Self } ";
540+ }
541+ finally
542+ {
543+ _toStringCallCount -- ;
544+ }
545+ }
546+ }
547+
401548 public class TestObjectWithReference
402549 {
403550 public string Name { get ; set ; }
0 commit comments