@@ -31,6 +31,15 @@ import type { SessionStoreStrategyType } from './storeStrategies/sessionStoreStr
3131import type { SessionState } from './sessionState'
3232import { EXPIRED } from './sessionState'
3333
34+ // Flush several microtask cycles so chained awaits inside startSessionManager
35+ // (resolveInitialState's await, the .catch chain, and the post-await continuation)
36+ // have a chance to run before assertions.
37+ async function flushMicrotasks ( ) : Promise < void > {
38+ for ( let i = 0 ; i < 10 ; i += 1 ) {
39+ await Promise . resolve ( )
40+ }
41+ }
42+
3443describe ( 'startSessionManager' , ( ) => {
3544 const STORE_TYPE : SessionStoreStrategyType = { type : SessionPersistence . COOKIE , cookieOptions : { } }
3645 let fakeStrategy : ReturnType < typeof createFakeSessionStoreStrategy >
@@ -518,18 +527,50 @@ describe('startSessionManager', () => {
518527 expect ( fakeStrategy . getInternalState ( ) . anonymousId ) . toBeUndefined ( )
519528 } )
520529
521- it ( 'should expire the session when consent is revoked before initialization completes' , async ( ) => {
530+ it ( 'should not install the session manager while consent stays revoked after being revoked during init' , async ( ) => {
531+ const trackingConsentState = createTrackingConsentState ( TrackingConsent . GRANTED )
532+
533+ const initResolvers : Array < ( ) => void > = [ ]
534+ const delayedStrategy = createFakeSessionStoreStrategy ( )
535+ delayedStrategy . setSessionState = jasmine
536+ . createSpy ( 'setSessionState' )
537+ . and . callFake ( ( fn : ( state : SessionState ) => SessionState ) : Promise < void > => {
538+ fn ( { } )
539+ return new Promise < void > ( ( resolve ) => {
540+ initResolvers . push ( resolve )
541+ } )
542+ } )
543+
544+ fakeStrategy = delayedStrategy
545+
546+ const sessionManagerResolution = jasmine . createSpy ( 'sessionManagerResolution' )
547+ void startSessionManager (
548+ {
549+ sessionStoreStrategyType : STORE_TYPE ,
550+ sessionSampleRate : 100 ,
551+ trackAnonymousUser : false ,
552+ } as Configuration ,
553+ trackingConsentState
554+ ) . then ( sessionManagerResolution )
555+
556+ trackingConsentState . update ( TrackingConsent . NOT_GRANTED )
557+ initResolvers [ 0 ] ( )
558+ await flushMicrotasks ( )
559+
560+ expect ( sessionManagerResolution ) . not . toHaveBeenCalled ( )
561+ } )
562+
563+ it ( 'should install the session manager when consent is granted again after being revoked during init' , async ( ) => {
522564 const trackingConsentState = createTrackingConsentState ( TrackingConsent . GRANTED )
523565
524- // Create a strategy where setSessionState returns a pending promise (to simulate async init)
525- let resolveInit ! : ( ) => void
566+ const initResolvers : Array < ( ) => void > = [ ]
526567 const delayedStrategy = createFakeSessionStoreStrategy ( )
527568 delayedStrategy . setSessionState = jasmine
528569 . createSpy ( 'setSessionState' )
529570 . and . callFake ( ( fn : ( state : SessionState ) => SessionState ) : Promise < void > => {
530571 fn ( { } )
531572 return new Promise < void > ( ( resolve ) => {
532- resolveInit = resolve
573+ initResolvers . push ( resolve )
533574 } )
534575 } )
535576
@@ -544,15 +585,56 @@ describe('startSessionManager', () => {
544585 trackingConsentState
545586 )
546587
547- // Consent revoked while initialization promise is pending
548588 trackingConsentState . update ( TrackingConsent . NOT_GRANTED )
589+ initResolvers [ 0 ] ( )
590+ await flushMicrotasks ( )
591+
592+ trackingConsentState . update ( TrackingConsent . GRANTED )
593+ await flushMicrotasks ( )
549594
550- // Resolve the initialization promise
551- resolveInit ( )
595+ // After re-grant, the loop re-resolves the initial state
596+ expect ( initResolvers . length ) . toBeGreaterThanOrEqual ( 2 )
597+ initResolvers [ initResolvers . length - 1 ] ( )
552598
553- // Should resolve with undefined because consent was revoked
554599 const sessionManager = await sessionManagerPromise
555- expect ( sessionManager ) . toBeUndefined ( )
600+ expect ( sessionManager ) . toBeDefined ( )
601+ } )
602+
603+ it ( 'should expire the session in storage while consent stays revoked during init' , async ( ) => {
604+ const trackingConsentState = createTrackingConsentState ( TrackingConsent . GRANTED )
605+
606+ const initResolvers : Array < ( ) => void > = [ ]
607+ const setStateCalls : Array < ( state : SessionState ) => SessionState > = [ ]
608+ const delayedStrategy = createFakeSessionStoreStrategy ( )
609+ delayedStrategy . setSessionState = jasmine
610+ . createSpy ( 'setSessionState' )
611+ . and . callFake ( ( fn : ( state : SessionState ) => SessionState ) : Promise < void > => {
612+ setStateCalls . push ( fn )
613+ fn ( { } )
614+ return new Promise < void > ( ( resolve ) => {
615+ initResolvers . push ( resolve )
616+ } )
617+ } )
618+
619+ fakeStrategy = delayedStrategy
620+
621+ void startSessionManager (
622+ {
623+ sessionStoreStrategyType : STORE_TYPE ,
624+ sessionSampleRate : 100 ,
625+ trackAnonymousUser : false ,
626+ } as Configuration ,
627+ trackingConsentState
628+ )
629+
630+ const initialCallCount = setStateCalls . length
631+
632+ trackingConsentState . update ( TrackingConsent . NOT_GRANTED )
633+ initResolvers [ 0 ] ( )
634+ await flushMicrotasks ( )
635+
636+ // expire() should have triggered an additional setSessionState call to mark the cookie expired
637+ expect ( setStateCalls . length ) . toBeGreaterThan ( initialCallCount )
556638 } )
557639 } )
558640
0 commit comments