@@ -523,6 +523,161 @@ public void TryRecoverPersistedSession_HasRecoveredUpdateAndCrashedLastRunFailed
523523 TryRecoverPersistedSessionWithExceptionOnLastRun ( ) ;
524524 }
525525
526+ [ Fact ]
527+ public void MarkSessionAsUnhandled_ActiveSessionExists_MarksSessionAndPersists ( )
528+ {
529+ // Arrange
530+ var sut = _fixture . GetSut ( ) ;
531+ sut . StartSession ( ) ;
532+ var session = sut . CurrentSession ;
533+
534+ // Act
535+ sut . MarkSessionAsUnhandled ( ) ;
536+
537+ // Assert
538+ session . Should ( ) . NotBeNull ( ) ;
539+ session ! . IsMarkedAsPendingUnhandled . Should ( ) . BeTrue ( ) ;
540+
541+ // Session should still be active (not ended)
542+ sut . CurrentSession . Should ( ) . BeSameAs ( session ) ;
543+ }
544+
545+ [ Fact ]
546+ public void MarkSessionAsUnhandled_NoActiveSession_LogsDebug ( )
547+ {
548+ // Arrange
549+ var sut = _fixture . GetSut ( ) ;
550+
551+ // Act
552+ sut . MarkSessionAsUnhandled ( ) ;
553+
554+ // Assert
555+ _fixture . Logger . Entries . Should ( ) . Contain ( e =>
556+ e . Message == "There is no session active. Skipping marking session as unhandled." &&
557+ e . Level == SentryLevel . Debug ) ;
558+ }
559+
560+ [ Fact ]
561+ public void TryRecoverPersistedSession_WithPendingUnhandledAndNoCrash_EndsAsUnhandled ( )
562+ {
563+ // Arrange
564+ _fixture . Options . CrashedLastRun = ( ) => false ;
565+ _fixture . PersistedSessionProvider = _ => new PersistedSessionUpdate (
566+ AnySessionUpdate ( ) ,
567+ pauseTimestamp : null ,
568+ pendingUnhandled : true ) ;
569+
570+ var sut = _fixture . GetSut ( ) ;
571+
572+ // Act
573+ var persistedSessionUpdate = sut . TryRecoverPersistedSession ( ) ;
574+
575+ // Assert
576+ persistedSessionUpdate . Should ( ) . NotBeNull ( ) ;
577+ persistedSessionUpdate ! . EndStatus . Should ( ) . Be ( SessionEndStatus . Unhandled ) ;
578+ }
579+
580+ [ Fact ]
581+ public void TryRecoverPersistedSession_WithPendingUnhandledAndCrash_EscalatesToCrashed ( )
582+ {
583+ // Arrange
584+ _fixture . Options . CrashedLastRun = ( ) => true ;
585+ _fixture . PersistedSessionProvider = _ => new PersistedSessionUpdate (
586+ AnySessionUpdate ( ) ,
587+ pauseTimestamp : null ,
588+ pendingUnhandled : true ) ;
589+
590+ var sut = _fixture . GetSut ( ) ;
591+
592+ // Act
593+ var persistedSessionUpdate = sut . TryRecoverPersistedSession ( ) ;
594+
595+ // Assert
596+ persistedSessionUpdate . Should ( ) . NotBeNull ( ) ;
597+ persistedSessionUpdate ! . EndStatus . Should ( ) . Be ( SessionEndStatus . Crashed ) ;
598+ }
599+
600+ [ Fact ]
601+ public void TryRecoverPersistedSession_WithPendingUnhandledAndPauseTimestamp_EscalatesToCrashedIfCrashed ( )
602+ {
603+ // Arrange - Session was paused AND had pending unhandled, then crashed
604+ _fixture . Options . CrashedLastRun = ( ) => true ;
605+ var pausedTimestamp = DateTimeOffset . Now ;
606+ _fixture . PersistedSessionProvider = _ => new PersistedSessionUpdate (
607+ AnySessionUpdate ( ) ,
608+ pausedTimestamp ,
609+ pendingUnhandled : true ) ;
610+
611+ var sut = _fixture . GetSut ( ) ;
612+
613+ // Act
614+ var persistedSessionUpdate = sut . TryRecoverPersistedSession ( ) ;
615+
616+ // Assert
617+ // Crash takes priority over all other end statuses
618+ persistedSessionUpdate . Should ( ) . NotBeNull ( ) ;
619+ persistedSessionUpdate ! . EndStatus . Should ( ) . Be ( SessionEndStatus . Crashed ) ;
620+ }
621+
622+ [ Fact ]
623+ public void EndSession_WithPendingUnhandledException_PreservesUnhandledStatus ( )
624+ {
625+ // Arrange
626+ var sut = _fixture . GetSut ( ) ;
627+ sut . StartSession ( ) ;
628+ sut . MarkSessionAsUnhandled ( ) ;
629+
630+ // Act - Try to end normally with Exited status
631+ var sessionUpdate = sut . EndSession ( SessionEndStatus . Exited ) ;
632+
633+ // Assert - Should be overridden to Unhandled
634+ sessionUpdate . Should ( ) . NotBeNull ( ) ;
635+ sessionUpdate ! . EndStatus . Should ( ) . Be ( SessionEndStatus . Unhandled ) ;
636+ }
637+
638+ [ Fact ]
639+ public void EndSession_WithPendingUnhandledAndCrashedStatus_UsesCrashedStatus ( )
640+ {
641+ // Arrange
642+ var sut = _fixture . GetSut ( ) ;
643+ sut . StartSession ( ) ;
644+ sut . MarkSessionAsUnhandled ( ) ;
645+
646+ // Act - Explicitly end with Crashed status
647+ var sessionUpdate = sut . EndSession ( SessionEndStatus . Crashed ) ;
648+
649+ // Assert - Crashed status takes priority
650+ sessionUpdate . Should ( ) . NotBeNull ( ) ;
651+ sessionUpdate ! . EndStatus . Should ( ) . Be ( SessionEndStatus . Crashed ) ;
652+ sessionUpdate . ErrorCount . Should ( ) . Be ( 1 ) ;
653+ }
654+
655+ [ Fact ]
656+ public void SessionEscalation_CompleteFlow_UnhandledThenCrash ( )
657+ {
658+ // Arrange - Simulate complete flow
659+ var sut = _fixture . GetSut ( ) ;
660+ sut . StartSession ( ) ;
661+ var originalSessionId = sut . CurrentSession ! . Id ;
662+
663+ // Act 1: Mark as unhandled (game encounters exception but continues)
664+ sut . MarkSessionAsUnhandled ( ) ;
665+
666+ // Assert: Session still active with pending flag
667+ sut . CurrentSession . Should ( ) . NotBeNull ( ) ;
668+ sut . CurrentSession ! . Id . Should ( ) . Be ( originalSessionId ) ;
669+ sut . CurrentSession . IsMarkedAsPendingUnhandled . Should ( ) . BeTrue ( ) ;
670+
671+ // Act 2: Recover on next launch with crash detected
672+ _fixture . Options . CrashedLastRun = ( ) => true ;
673+ var recovered = sut . TryRecoverPersistedSession ( ) ;
674+
675+ // Assert: Session escalated from Unhandled to Crashed
676+ recovered . Should ( ) . NotBeNull ( ) ;
677+ recovered ! . EndStatus . Should ( ) . Be ( SessionEndStatus . Crashed ) ;
678+ recovered . Id . Should ( ) . Be ( originalSessionId ) ;
679+ }
680+
526681 // A session update (of which the state doesn't matter for the test):
527682 private static SessionUpdate AnySessionUpdate ( )
528683 => new (
0 commit comments