Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 131 additions & 138 deletions packages/core/src/domain/session/sessionManager.spec.ts
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: replace ?. with !. or maybe const sessionManager = (await ...)!.

All those ?. are making tests less precise, because in cases like this:

expect(sessionManager?.findSession()!.anonymousId).toBeUndefined()

the assertion can pass when sessionManager is undefined, but it shouldn't.

Large diffs are not rendered by default.

50 changes: 24 additions & 26 deletions packages/core/src/domain/session/sessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,10 @@ export const VISIBILITY_CHECK_DELAY = ONE_MINUTE
const SESSION_CONTEXT_TIMEOUT_DELAY = SESSION_TIME_OUT_DELAY
let stopCallbacks: Array<() => void> = []

export function startSessionManager(
export async function startSessionManager(
configuration: Configuration,
trackingConsentState: TrackingConsentState,
onReady: (sessionManager: SessionManager) => void
) {
trackingConsentState: TrackingConsentState
): Promise<SessionManager | undefined> {
const renewObservable = new Observable<SessionRenewalEvent>()
const expireObservable = new Observable<void>()
let expireContext: SessionDebugContext | undefined
Expand All @@ -84,29 +83,31 @@ export function startSessionManager(
}, ONE_SECOND)
stopCallbacks.push(cancelExpandOrRenew)

let expirationTimeoutId: ReturnType<typeof setTimeout> | undefined
stopCallbacks.push(() => clearTimeout(expirationTimeoutId))

let stopped = false
stopCallbacks.push(() => {
stopped = true
})
;(async () => {
const initialState = await resolveInitialState()
if (stopped) {
return
}

// Consent is always granted when the session manager is started, but it may
// be revoked during the async initialization (e.g., while waiting for cookie lock).
if (!trackingConsentState.isGranted()) {
expire()
return
}
const initialState = await resolveInitialState().catch(monitorError)
if (!initialState || stopped) {
return
}

// Consent is always granted when the session manager is started, but it may
// be revoked during the async initialization (e.g., while waiting for cookie lock).
if (!trackingConsentState.isGranted()) {
expire()
return
}

sessionContextHistory.add(buildSessionContext(initialState), clocksOrigin().relative)
scheduleExpirationTimeout(initialState)
subscribeToSessionChanges()
setupSessionTracking()
onReady(buildSessionManager())
})().catch(monitorError)
sessionContextHistory.add(buildSessionContext(initialState), clocksOrigin().relative)
scheduleExpirationTimeout(initialState)
subscribeToSessionChanges()
setupSessionTracking()
return buildSessionManager()

async function resolveInitialState() {
let state: SessionState = {}
Expand All @@ -126,8 +127,6 @@ export function startSessionManager(
stopCallbacks.push(() => subscription.unsubscribe())
}

let expirationTimeoutId: ReturnType<typeof setTimeout> | undefined

function scheduleExpirationTimeout(state: SessionState) {
clearTimeout(expirationTimeoutId)
if (state.expire && !isSessionInExpiredState(state)) {
Expand All @@ -149,7 +148,6 @@ export function startSessionManager(
}, delay)
}
}
stopCallbacks.push(() => clearTimeout(expirationTimeoutId))

function setupSessionTracking() {
trackingConsentState.observable.subscribe(() => {
Expand Down Expand Up @@ -276,14 +274,14 @@ export function startSessionManager(
}
}

export function startSessionManagerStub(onReady: (sessionManager: SessionManager) => void): void {
export function startSessionManagerStub(): Promise<SessionManager> {
const stubSessionId = generateUUID()
let sessionContext: SessionContext = {
id: stubSessionId,
isReplayForced: false,
anonymousId: undefined,
}
onReady({
return Promise.resolve({
findSession: () => sessionContext,
findTrackedSession: () => sessionContext,
renewObservable: new Observable(),
Expand Down
4 changes: 1 addition & 3 deletions packages/core/test/mockSessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,5 @@ export function createSessionManagerMock(): SessionManagerMock {
}

export function createStartSessionManagerMock(): typeof startSessionManager {
return (_config, _consent, onReady) => {
void Promise.resolve().then(() => onReady(createSessionManagerMock()))
}
return () => Promise.resolve(createSessionManagerMock())
}
8 changes: 5 additions & 3 deletions packages/logs/src/boot/preStartLogs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,11 @@ describe('preStartLogs', () => {
mockEventBridge()
})

it('init should accept empty client token', () => {
it('init should accept empty client token', async () => {
const hybridInitConfiguration: HybridInitConfiguration = {}
strategy.init(hybridInitConfiguration as LogsInitConfiguration)

await collectAsyncCalls(doStartLogsSpy, 1)
expect(displaySpy).not.toHaveBeenCalled()
expect(doStartLogsSpy).toHaveBeenCalled()
})
Expand Down Expand Up @@ -135,9 +136,9 @@ describe('preStartLogs', () => {
})

describe('save context when submitting a log', () => {
it('saves the date', () => {
it('saves the date', async () => {
mockEventBridge()
const { strategy, getLoggedMessage } = createPreStartStrategyWithDefaults()
const { strategy, getLoggedMessage, handleLogSpy } = createPreStartStrategyWithDefaults()
strategy.handleLog(
{
status: StatusType.info,
Expand All @@ -147,6 +148,7 @@ describe('preStartLogs', () => {
)
clock.tick(ONE_SECOND)
strategy.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(handleLogSpy, 1)

expect(getLoggedMessage(0).savedDate).toEqual((Date.now() - ONE_SECOND) as TimeStamp)
})
Expand Down
16 changes: 9 additions & 7 deletions packages/logs/src/boot/preStartLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,19 @@ export function createPreStartStrategy(
trackingConsentState.onGrantedOnce(() => {
startTrackingConsentContext(hooks, trackingConsentState)
mockable(startTelemetry)(TelemetryService.LOGS, configuration, hooks)
const onSessionManagerReady = (newSessionManager: SessionManager) => {
const sessionManagerPromise = canUseEventBridge()
? startSessionManagerStub()
: mockable(startSessionManager)(configuration, trackingConsentState)

void sessionManagerPromise.then((newSessionManager) => {
Comment thread
thomas-lebeau marked this conversation as resolved.
Outdated
if (!newSessionManager) {
return
}
sessionManager = newSessionManager
startTelemetrySessionContext(hooks, sessionManager)
addTelemetryConfiguration(serializeLogsConfiguration(initConfiguration))
tryStartLogs()
}
if (canUseEventBridge()) {
startSessionManagerStub(onSessionManagerReady)
} else {
mockable(startSessionManager)(configuration, trackingConsentState, onSessionManagerReady)
}
})
})
},

Expand Down
6 changes: 4 additions & 2 deletions packages/rum-core/src/boot/preStartRum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,11 @@ describe('preStartRum', () => {
)
})

it('should initialize even if session cannot be handled', () => {
it('should initialize even if session cannot be handled', async () => {
mockEventBridge()
spyOnProperty(document, 'cookie', 'get').and.returnValue('')
strategy.init(DEFAULT_INIT_CONFIGURATION, PUBLIC_API)
await collectAsyncCalls(doStartRumSpy, 1)
expect(doStartRumSpy).toHaveBeenCalled()
})
})
Expand Down Expand Up @@ -252,7 +253,7 @@ describe('preStartRum', () => {
expect(doStartRumSpy).not.toHaveBeenCalled()
})

it('if message bridge is present, does not create a deflate worker instance', () => {
it('if message bridge is present, does not create a deflate worker instance', async () => {
mockEventBridge()

strategy.init(
Expand All @@ -263,6 +264,7 @@ describe('preStartRum', () => {
PUBLIC_API
)

await collectAsyncCalls(doStartRumSpy, 1)
expect(startDeflateWorkerSpy).not.toHaveBeenCalled()
expect(doStartRumSpy).toHaveBeenCalledTimes(1)
})
Expand Down
16 changes: 9 additions & 7 deletions packages/rum-core/src/boot/preStartRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,18 +182,20 @@ export function createPreStartStrategy(
return
}

const onSessionManagerReady = (newSessionManager: SessionManager) => {
const sessionManagerPromise = canUseEventBridge()
? startSessionManagerStub()
: mockable(startSessionManager)(configuration, trackingConsentState)

void sessionManagerPromise.then((newSessionManager) => {
Comment thread
thomas-lebeau marked this conversation as resolved.
Outdated
if (!newSessionManager) {
return
}
sessionManager = newSessionManager
startTelemetrySessionContext(hooks, sessionManager, { application: { id: configuration.applicationId } })
addTelemetryConfiguration(serializeRumConfiguration(initConfiguration))

tryStartRum()
}
if (canUseEventBridge()) {
startSessionManagerStub(onSessionManagerReady)
} else {
mockable(startSessionManager)(configuration, trackingConsentState, onSessionManagerReady)
}
})
})
}

Expand Down
15 changes: 10 additions & 5 deletions packages/rum-core/src/boot/rumPublicApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,12 @@ describe('rum public api', () => {
mockClock()
})

it('allows sending actions before init', () => {
it('allows sending actions before init', async () => {
rumPublicApi.addAction('foo', { bar: 'baz' })

expect(addActionSpy).not.toHaveBeenCalled()
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(addActionSpy, 1)

expect(addActionSpy).toHaveBeenCalledTimes(1)
expect(addActionSpy.calls.argsFor(0)).toEqual([
Expand All @@ -187,7 +188,7 @@ describe('rum public api', () => {
])
})

it('should generate a handling stack', () => {
it('should generate a handling stack', async () => {
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)

function triggerAction() {
Expand All @@ -196,6 +197,7 @@ describe('rum public api', () => {

triggerAction()

await collectAsyncCalls(addActionSpy, 1)
expect(addActionSpy).toHaveBeenCalledTimes(1)
const stacktrace = addActionSpy.calls.argsFor(0)[0].handlingStack
expect(stacktrace).toMatch(/^HandlingStack: action\s+at triggerAction @/)
Expand All @@ -218,11 +220,12 @@ describe('rum public api', () => {
}))
})

it('allows capturing an error before init', () => {
it('allows capturing an error before init', async () => {
rumPublicApi.addError(new Error('foo'))

expect(addErrorSpy).not.toHaveBeenCalled()
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(addErrorSpy, 1)

expect(addErrorSpy).toHaveBeenCalledTimes(1)
expect(addErrorSpy.calls.argsFor(0)).toEqual([
Expand All @@ -235,26 +238,28 @@ describe('rum public api', () => {
])
})

it('should generate a handling stack', () => {
it('should generate a handling stack', async () => {
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)

function triggerError() {
rumPublicApi.addError(new Error('message'))
}

triggerError()
await collectAsyncCalls(addErrorSpy, 1)
expect(addErrorSpy).toHaveBeenCalledTimes(1)
const stacktrace = addErrorSpy.calls.argsFor(0)[0].handlingStack
expect(stacktrace).toMatch(/^HandlingStack: error\s+at triggerError (.|\n)*$/)
})

describe('save context when capturing an error', () => {
it('saves the date', () => {
it('saves the date', async () => {
clock.tick(ONE_SECOND)
rumPublicApi.addError(new Error('foo'))

clock.tick(ONE_SECOND)
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(addErrorSpy, 1)

expect(addErrorSpy.calls.argsFor(0)[0].startClocks.relative as number).toEqual(clock.relative(ONE_SECOND))
})
Expand Down
Loading