@@ -6,7 +6,7 @@ import '../../utils/mock-internal-setTimeout';
66import type { Event } from '@sentry/core' ;
77import { getClient } from '@sentry/core' ;
88import { afterEach , beforeAll , beforeEach , describe , expect , it , vi } from 'vitest' ;
9- import { REPLAY_EVENT_NAME , SESSION_IDLE_EXPIRE_DURATION } from '../../../src/constants' ;
9+ import { MAX_REPLAY_DURATION , REPLAY_EVENT_NAME , SESSION_IDLE_EXPIRE_DURATION } from '../../../src/constants' ;
1010import { handleGlobalEventListener } from '../../../src/coreHandlers/handleGlobalEvent' ;
1111import type { ReplayContainer } from '../../../src/replay' ;
1212import { makeSession } from '../../../src/session/Session' ;
@@ -435,4 +435,154 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
435435
436436 expect ( resetReplayIdSpy ) . toHaveBeenCalledTimes ( 2 ) ;
437437 } ) ;
438+
439+ it ( 'resets replayId on DSC when replay is paused and session has expired' , ( ) => {
440+ const now = Date . now ( ) ;
441+
442+ replay . session = makeSession ( {
443+ id : 'test-session-id' ,
444+ segmentId : 0 ,
445+ lastActivity : now - SESSION_IDLE_EXPIRE_DURATION - 1 ,
446+ started : now - SESSION_IDLE_EXPIRE_DURATION - 1 ,
447+ sampled : 'session' ,
448+ } ) ;
449+
450+ replay [ '_isPaused' ] = true ;
451+
452+ const resetReplayIdSpy = vi . spyOn (
453+ resetReplayIdOnDynamicSamplingContextModule ,
454+ 'resetReplayIdOnDynamicSamplingContext' ,
455+ ) ;
456+
457+ const errorEvent = Error ( ) ;
458+ handleGlobalEventListener ( replay ) ( errorEvent , { } ) ;
459+
460+ // Should have been called even though replay is paused
461+ expect ( resetReplayIdSpy ) . toHaveBeenCalledTimes ( 1 ) ;
462+ } ) ;
463+
464+ it ( 'does not reset replayId on DSC when replay is paused but session is still valid' , ( ) => {
465+ const now = Date . now ( ) ;
466+
467+ replay . session = makeSession ( {
468+ id : 'test-session-id' ,
469+ segmentId : 0 ,
470+ lastActivity : now ,
471+ started : now ,
472+ sampled : 'session' ,
473+ } ) ;
474+
475+ replay [ '_isPaused' ] = true ;
476+
477+ const resetReplayIdSpy = vi . spyOn (
478+ resetReplayIdOnDynamicSamplingContextModule ,
479+ 'resetReplayIdOnDynamicSamplingContext' ,
480+ ) ;
481+
482+ const errorEvent = Error ( ) ;
483+ handleGlobalEventListener ( replay ) ( errorEvent , { } ) ;
484+
485+ // Should NOT have been called because session is still valid
486+ expect ( resetReplayIdSpy ) . not . toHaveBeenCalled ( ) ;
487+ } ) ;
488+
489+ it ( 'resets replayId on DSC when replay is paused and session exceeds max duration' , ( ) => {
490+ const now = Date . now ( ) ;
491+
492+ replay . session = makeSession ( {
493+ id : 'test-session-id' ,
494+ segmentId : 0 ,
495+ // Recent activity, but session started too long ago
496+ lastActivity : now ,
497+ started : now - MAX_REPLAY_DURATION - 1 ,
498+ sampled : 'session' ,
499+ } ) ;
500+
501+ replay [ '_isPaused' ] = true ;
502+
503+ const resetReplayIdSpy = vi . spyOn (
504+ resetReplayIdOnDynamicSamplingContextModule ,
505+ 'resetReplayIdOnDynamicSamplingContext' ,
506+ ) ;
507+
508+ const errorEvent = Error ( ) ;
509+ handleGlobalEventListener ( replay ) ( errorEvent , { } ) ;
510+
511+ expect ( resetReplayIdSpy ) . toHaveBeenCalledTimes ( 1 ) ;
512+ } ) ;
513+
514+ it ( 'does not reset replayId on DSC for expired buffer session with segmentId 0' , ( ) => {
515+ const now = Date . now ( ) ;
516+
517+ replay . session = makeSession ( {
518+ id : 'test-session-id' ,
519+ segmentId : 0 ,
520+ lastActivity : now - SESSION_IDLE_EXPIRE_DURATION - 1 ,
521+ started : now - SESSION_IDLE_EXPIRE_DURATION - 1 ,
522+ sampled : 'buffer' ,
523+ } ) ;
524+
525+ replay [ '_isPaused' ] = true ;
526+
527+ const resetReplayIdSpy = vi . spyOn (
528+ resetReplayIdOnDynamicSamplingContextModule ,
529+ 'resetReplayIdOnDynamicSamplingContext' ,
530+ ) ;
531+
532+ const errorEvent = Error ( ) ;
533+ handleGlobalEventListener ( replay ) ( errorEvent , { } ) ;
534+
535+ // Should NOT reset DSC: buffer sessions with segmentId 0 are kept alive
536+ // even when time-expired (shouldRefreshSession carve-out)
537+ expect ( resetReplayIdSpy ) . not . toHaveBeenCalled ( ) ;
538+ } ) ;
539+
540+ it ( 'resets replayId on DSC for expired buffer session with segmentId > 0' , ( ) => {
541+ const now = Date . now ( ) ;
542+
543+ replay . session = makeSession ( {
544+ id : 'test-session-id' ,
545+ segmentId : 1 ,
546+ lastActivity : now - SESSION_IDLE_EXPIRE_DURATION - 1 ,
547+ started : now - SESSION_IDLE_EXPIRE_DURATION - 1 ,
548+ sampled : 'buffer' ,
549+ } ) ;
550+
551+ replay [ '_isPaused' ] = true ;
552+
553+ const resetReplayIdSpy = vi . spyOn (
554+ resetReplayIdOnDynamicSamplingContextModule ,
555+ 'resetReplayIdOnDynamicSamplingContext' ,
556+ ) ;
557+
558+ const errorEvent = Error ( ) ;
559+ handleGlobalEventListener ( replay ) ( errorEvent , { } ) ;
560+
561+ // Buffer session with segmentId > 0 that is expired SHOULD have DSC reset
562+ expect ( resetReplayIdSpy ) . toHaveBeenCalledTimes ( 1 ) ;
563+ } ) ;
564+
565+ it ( 'resets replayId on DSC when replay is disabled and session has expired' , ( ) => {
566+ const now = Date . now ( ) ;
567+
568+ replay . session = makeSession ( {
569+ id : 'test-session-id' ,
570+ segmentId : 0 ,
571+ lastActivity : now - SESSION_IDLE_EXPIRE_DURATION - 1 ,
572+ started : now - SESSION_IDLE_EXPIRE_DURATION - 1 ,
573+ sampled : 'session' ,
574+ } ) ;
575+
576+ replay [ '_isEnabled' ] = false ;
577+
578+ const resetReplayIdSpy = vi . spyOn (
579+ resetReplayIdOnDynamicSamplingContextModule ,
580+ 'resetReplayIdOnDynamicSamplingContext' ,
581+ ) ;
582+
583+ const errorEvent = Error ( ) ;
584+ handleGlobalEventListener ( replay ) ( errorEvent , { } ) ;
585+
586+ expect ( resetReplayIdSpy ) . toHaveBeenCalledTimes ( 1 ) ;
587+ } ) ;
438588} ) ;
0 commit comments