diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 7d6a99ad81..1b410a0900 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -158,6 +158,8 @@ internal class RumSessionScope( } else if (isTimedOut) { renewSession(nanoTime) } + + updateSessionStateForSessionReplay(sessionState, sessionId) } private fun renewSession(nanoTime: Long) { @@ -165,14 +167,16 @@ internal class RumSessionScope( sessionState = if (keepSession) State.TRACKED else State.NOT_TRACKED sessionId = UUID.randomUUID().toString() sessionStartNs.set(nanoTime) - sdkCore.updateFeatureContext(Feature.RUM_FEATURE_NAME) { - it.putAll(getRumContext().toMap()) - } sessionListener?.onSessionStarted(sessionId, !keepSession) + } + + private fun updateSessionStateForSessionReplay(state: State, sessionId: String) { + val keepSession = (state == State.TRACKED) sdkCore.getFeature(Feature.SESSION_REPLAY_FEATURE_NAME)?.sendEvent( mapOf( SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RUM_SESSION_RENEWED_BUS_MESSAGE, - RUM_KEEP_SESSION_BUS_MESSAGE_KEY to keepSession + RUM_KEEP_SESSION_BUS_MESSAGE_KEY to keepSession, + RUM_SESSION_ID_BUS_MESSAGE_KEY to sessionId ) ) } @@ -184,6 +188,7 @@ internal class RumSessionScope( internal const val SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY = "type" internal const val RUM_SESSION_RENEWED_BUS_MESSAGE = "rum_session_renewed" internal const val RUM_KEEP_SESSION_BUS_MESSAGE_KEY = "keepSession" + internal const val RUM_SESSION_ID_BUS_MESSAGE_KEY = "sessionId" internal val DEFAULT_SESSION_INACTIVITY_NS = TimeUnit.MINUTES.toNanos(15) internal val DEFAULT_SESSION_MAX_DURATION_NS = TimeUnit.HOURS.toNanos(4) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index 035eb4d688..32e6abfa9d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -819,16 +819,96 @@ internal class RumSessionScopeTest { // region Session Replay Event Bus + @Test + fun `𝕄 notify Session Replay feature 𝕎 new interaction event received`( + forge: Forge + ) { + // Given + val fakeInteractionEvent1 = forge.interactiveRumRawEvent() + val fakeInteractionEvent2 = forge.interactiveRumRawEvent() + testedScope.handleEvent(fakeInteractionEvent1, mockWriter) + testedScope.handleEvent(fakeInteractionEvent2, mockWriter) + + // Then + val argumentCaptor = argumentCaptor() + verify(mockSessionReplayFeatureScope, times(2)) + .sendEvent(argumentCaptor.capture()) + assertThat(argumentCaptor.firstValue).isEqualTo( + mapOf( + RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to + testedScope.getRumContext().sessionId + ) + ) + assertThat(argumentCaptor.secondValue).isEqualTo( + mapOf( + RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to + testedScope.getRumContext().sessionId + ) + ) + } + + @Test + fun `𝕄 notify Session Replay feature 𝕎 new non-interaction event received`( + forge: Forge + ) { + // Given + val fakeNonInteractionEvent1 = forge.anyRumEvent( + excluding = listOf( + RumRawEvent.StartView::class.java, + RumRawEvent.StartAction::class.java + ) + ) + val fakeNonInteractionEvent2 = forge.anyRumEvent( + excluding = listOf( + RumRawEvent.StartView::class.java, + RumRawEvent.StartAction::class.java + ) + ) + testedScope.handleEvent(fakeNonInteractionEvent1, mockWriter) + testedScope.handleEvent(fakeNonInteractionEvent2, mockWriter) + + // Then + val argumentCaptor = argumentCaptor() + verify(mockSessionReplayFeatureScope, times(2)) + .sendEvent(argumentCaptor.capture()) + assertThat(argumentCaptor.firstValue).isEqualTo( + mapOf( + RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to + testedScope.getRumContext().sessionId + ) + ) + assertThat(argumentCaptor.secondValue).isEqualTo( + mapOf( + RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to + testedScope.getRumContext().sessionId + ) + ) + } + @Test fun `𝕄 notify Session Replay feature 𝕎 session is updated {tracked, timed out}`( forge: Forge ) { // Given testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val firstSessionId = testedScope.getRumContext().sessionId // When Thread.sleep(TEST_MAX_DURATION_MS) testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val secondSessionId = testedScope.getRumContext().sessionId // Then val argumentCaptor = argumentCaptor() @@ -838,14 +918,16 @@ internal class RumSessionScopeTest { mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to firstSessionId ) ) assertThat(argumentCaptor.secondValue).isEqualTo( mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId ) ) } @@ -856,10 +938,12 @@ internal class RumSessionScopeTest { ) { // Given testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val firstSessionId = testedScope.getRumContext().sessionId // When Thread.sleep(TEST_INACTIVITY_MS) testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val secondSessionId = testedScope.getRumContext().sessionId // Then val argumentCaptor = argumentCaptor() @@ -869,14 +953,18 @@ internal class RumSessionScopeTest { mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to + firstSessionId ) ) assertThat(argumentCaptor.secondValue).isEqualTo( mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to + secondSessionId ) ) } @@ -887,27 +975,42 @@ internal class RumSessionScopeTest { ) { // Given testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val firstSessionId = testedScope.getRumContext().sessionId // When testedScope.handleEvent(RumRawEvent.ResetSession(), mockWriter) testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val secondSessionId = testedScope.getRumContext().sessionId // Then val argumentCaptor = argumentCaptor() - verify(mockSessionReplayFeatureScope, times(2)) + verify(mockSessionReplayFeatureScope, times(3)) .sendEvent(argumentCaptor.capture()) assertThat(argumentCaptor.firstValue).isEqualTo( mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to + firstSessionId ) ) assertThat(argumentCaptor.secondValue).isEqualTo( mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to + secondSessionId + ) + ) + assertThat(argumentCaptor.thirdValue).isEqualTo( + mapOf( + RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to + secondSessionId ) ) } @@ -919,10 +1022,12 @@ internal class RumSessionScopeTest { // Given initializeTestedScope(0f) testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val firstSessionId = testedScope.getRumContext().sessionId // When Thread.sleep(TEST_MAX_DURATION_MS) testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val secondSessionId = testedScope.getRumContext().sessionId // Then val argumentCaptor = argumentCaptor() @@ -932,14 +1037,17 @@ internal class RumSessionScopeTest { mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to firstSessionId ) ) assertThat(argumentCaptor.secondValue).isEqualTo( mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId + ) ) } @@ -951,10 +1059,12 @@ internal class RumSessionScopeTest { // Given initializeTestedScope(0f) testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val firstSessionId = testedScope.getRumContext().sessionId // When Thread.sleep(TEST_INACTIVITY_MS) testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val secondSessionId = testedScope.getRumContext().sessionId // Then val argumentCaptor = argumentCaptor() @@ -964,14 +1074,16 @@ internal class RumSessionScopeTest { mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to firstSessionId ) ) assertThat(argumentCaptor.secondValue).isEqualTo( mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId ) ) } @@ -983,27 +1095,39 @@ internal class RumSessionScopeTest { // Given initializeTestedScope(0f) testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val firstSessionId = testedScope.getRumContext().sessionId // When testedScope.handleEvent(RumRawEvent.ResetSession(), mockWriter) testedScope.handleEvent(forge.startViewEvent(), mockWriter) + val secondSessionId = testedScope.getRumContext().sessionId // Then val argumentCaptor = argumentCaptor() - verify(mockSessionReplayFeatureScope, times(2)) + verify(mockSessionReplayFeatureScope, times(3)) .sendEvent(argumentCaptor.capture()) assertThat(argumentCaptor.firstValue).isEqualTo( mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to firstSessionId ) ) assertThat(argumentCaptor.secondValue).isEqualTo( mapOf( RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, - RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId + ) + ) + assertThat(argumentCaptor.thirdValue).isEqualTo( + mapOf( + RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE, + RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false, + RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId ) ) } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index 80f8a47945..f2e5e3ebc1 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -31,6 +31,7 @@ import com.datadog.android.sessionreplay.internal.storage.SessionReplayRecordWri import com.datadog.android.sessionreplay.internal.time.SessionReplayTimeProvider import java.util.Locale import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference /** * Session Replay feature class, which needs to be registered with Datadog SDK instance. @@ -43,6 +44,8 @@ internal class SessionReplayFeature constructor( private val sessionReplayRecorderProvider: (RecordWriter, Application) -> Recorder ) : StorageBackedFeature, FeatureEventReceiver { + private val currentRumSessionId = AtomicReference() + internal constructor( sdkCore: FeatureSdkCore, customEndpointUrl: String?, @@ -155,8 +158,9 @@ internal class SessionReplayFeature constructor( private fun checkStatusAndApplySample(sessionMetadata: Map<*, *>) { val keepSession = sessionMetadata[RUM_KEEP_SESSION_BUS_MESSAGE_KEY] as? Boolean + val sessionId = sessionMetadata[RUM_SESSION_ID_BUS_MESSAGE_KEY] as? String - if (keepSession == null) { + if (keepSession == null || sessionId == null) { sdkCore.internalLogger.log( InternalLogger.Level.WARN, InternalLogger.Target.USER, @@ -165,6 +169,11 @@ internal class SessionReplayFeature constructor( return } + if (currentRumSessionId.get() == sessionId) { + // we already handled this session + return + } + if (keepSession && rateBasedSampler.sample()) { startRecording() } else { @@ -175,6 +184,8 @@ internal class SessionReplayFeature constructor( ) stopRecording() } + + currentRumSessionId.set(sessionId) } /** @@ -246,5 +257,6 @@ internal class SessionReplayFeature constructor( const val SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY = "type" const val RUM_SESSION_RENEWED_BUS_MESSAGE = "rum_session_renewed" const val RUM_KEEP_SESSION_BUS_MESSAGE_KEY = "keepSession" + const val RUM_SESSION_ID_BUS_MESSAGE_KEY = "sessionId" } } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt index be01f5fd8f..8f609f1fcf 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt @@ -24,6 +24,7 @@ import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -45,6 +46,7 @@ import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.Locale +import java.util.UUID import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @Extensions( @@ -73,8 +75,11 @@ internal class SessionReplayFeatureTest { @Mock lateinit var mockSampler: Sampler + lateinit var fakeSessionId: String + @BeforeEach fun `set up`() { + fakeSessionId = UUID.randomUUID().toString() whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger testedFeature = SessionReplayFeature( sdkCore = mockSdkCore, @@ -305,7 +310,8 @@ internal class SessionReplayFeatureTest { SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - true + true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId ) // When @@ -319,6 +325,27 @@ internal class SessionReplayFeatureTest { verifyNoMoreInteractions(mockRecorder) } + @Test + fun `M doNothing W rum session updated { keep, sessionId is null }`() { + // Given + whenever(mockSampler.sample()).thenReturn(true) + testedFeature.onInitialize(appContext.mockInstance) + testedFeature.stopRecording() + val rumSessionUpdateBusMessage = mapOf( + SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, + SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to + true + ) + + // When + testedFeature.onReceive(rumSessionUpdateBusMessage) + + // Then + verify(mockRecorder).registerCallbacks() + verifyNoMoreInteractions(mockRecorder) + } + @Test fun `M do nothing W rum session updated { keep false, sampled in }`() { // Given @@ -329,7 +356,8 @@ internal class SessionReplayFeatureTest { SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - false + false, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId ) // When @@ -345,6 +373,40 @@ internal class SessionReplayFeatureTest { ) } + @Test + fun `M only startRecording once W rum session updated { same session Id }`() { + // Given + whenever(mockSampler.sample()).thenReturn(true) + testedFeature.onInitialize(appContext.mockInstance) + val rumSessionUpdateBusMessage1 = mapOf( + SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, + SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to + true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to + fakeSessionId + ) + val rumSessionUpdateBusMessage2 = mapOf( + SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, + SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to + false, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to + fakeSessionId + ) + + // When + testedFeature.onReceive(rumSessionUpdateBusMessage1) + testedFeature.onReceive(rumSessionUpdateBusMessage2) + + // Then + inOrder(mockRecorder) { + verify(mockRecorder).registerCallbacks() + verify(mockRecorder).resumeRecorders() + } + verifyNoMoreInteractions(mockRecorder) + } + @Test fun `M do nothing W rum session updated { keep true, sampled out }`() { // Given @@ -355,7 +417,8 @@ internal class SessionReplayFeatureTest { SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - true + true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId ) // When @@ -374,19 +437,24 @@ internal class SessionReplayFeatureTest { @Test fun `M stopRecording W rum session updated { keep false, sample in }`() { // Given + val fakeSessionId2 = UUID.randomUUID().toString() whenever(mockSampler.sample()).thenReturn(true) testedFeature.onInitialize(appContext.mockInstance) val rumSessionUpdateBusMessage1 = mapOf( SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - true + true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to + fakeSessionId ) val rumSessionUpdateBusMessage2 = mapOf( SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - false + false, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to + fakeSessionId2 ) // When @@ -410,19 +478,22 @@ internal class SessionReplayFeatureTest { @Test fun `M stopRecording W rum session updated { keep true, sample out }`() { // Given + val fakeSessionId2 = UUID.randomUUID().toString() whenever(mockSampler.sample()).thenReturn(true).thenReturn(false) testedFeature.onInitialize(appContext.mockInstance) val rumSessionUpdateBusMessage1 = mapOf( SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - true + true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId ) val rumSessionUpdateBusMessage2 = mapOf( SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - true + true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId2 ) // When @@ -446,19 +517,24 @@ internal class SessionReplayFeatureTest { @Test fun `M stopRecording W rum session updated { keep false, sample out }`() { // Given + val fakeSessionId2 = UUID.randomUUID().toString() whenever(mockSampler.sample()).thenReturn(true).thenReturn(false) testedFeature.onInitialize(appContext.mockInstance) val rumSessionUpdateBusMessage1 = mapOf( SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - true + true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to + fakeSessionId ) val rumSessionUpdateBusMessage2 = mapOf( SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - false + false, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to + fakeSessionId2 ) // When @@ -543,6 +619,52 @@ internal class SessionReplayFeatureTest { verifyNoInteractions(mockRecorder) } + @Test + fun `𝕄 log warning and do nothing 𝕎 onReceive() { missing keep state field }`() { + // Given + val event = mapOf( + SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId + ) + + // When + testedFeature.onReceive(event) + + // Then + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + SessionReplayFeature.EVENT_MISSING_MANDATORY_FIELDS + ) + + verifyNoInteractions(mockRecorder) + } + + @Test + fun `𝕄 log warning and do nothing 𝕎 onReceive() { missing session id field }`( + @BoolForgery fakeKeep: Boolean + ) { + // Given + val event = mapOf( + SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, + SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to fakeKeep + ) + + // When + testedFeature.onReceive(event) + + // Then + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + SessionReplayFeature.EVENT_MISSING_MANDATORY_FIELDS + ) + + verifyNoInteractions(mockRecorder) + } + @Test fun `𝕄 log warning and do nothing 𝕎 onReceive() { mandatory fields have wrong format }`( forge: Forge @@ -552,7 +674,9 @@ internal class SessionReplayFeatureTest { SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to - forge.anAlphabeticalString() + forge.anAlphabeticalString(), + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to + fakeSessionId ) // When