diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/FeaturesContextResolver.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/FeaturesContextResolver.kt new file mode 100644 index 0000000000..abd20e1795 --- /dev/null +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/FeaturesContextResolver.kt @@ -0,0 +1,22 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal + +import com.datadog.android.sessionreplay.internal.SessionReplayFeature +import com.datadog.android.v2.api.context.DatadogContext + +internal class FeaturesContextResolver { + + fun resolveHasReplay(context: DatadogContext): Boolean { + val sessionReplayContext = + context.featuresContext[SessionReplayFeature.SESSION_REPLAY_FEATURE_NAME] + return ( + sessionReplayContext?.get(SessionReplayFeature.IS_RECORDING_CONTEXT_KEY) + as? Boolean + ) ?: false + } +} diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt index 1a13035a8a..874b9e7231 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt @@ -10,6 +10,7 @@ import androidx.annotation.WorkerThread import com.datadog.android.core.internal.persistence.DataWriter import com.datadog.android.rum.GlobalRum import com.datadog.android.rum.RumActionType +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.event.RumEventSourceProvider @@ -31,7 +32,8 @@ internal class RumActionScope( inactivityThresholdMs: Long = ACTION_INACTIVITY_MS, maxDurationMs: Long = ACTION_MAX_DURATION_MS, private val rumEventSourceProvider: RumEventSourceProvider, - private val contextProvider: ContextProvider + private val contextProvider: ContextProvider, + private val featuresContextResolver: FeaturesContextResolver = FeaturesContextResolver() ) : RumScope { private val inactivityThresholdNs = TimeUnit.MILLISECONDS.toNanos(inactivityThresholdMs) @@ -179,6 +181,7 @@ internal class RumActionScope( longTaskCount++ } + @Suppress("LongMethod") @WorkerThread private fun sendAction( endNanos: Long, @@ -192,6 +195,7 @@ internal class RumActionScope( val rumContext = getRumContext() val sdkContext = contextProvider.context val user = sdkContext.userInfo + val hasReplay = featuresContextResolver.resolveHasReplay(sdkContext) val frustrations = mutableListOf() if (errorCount > 0 && actualType == RumActionType.TAP) { @@ -219,7 +223,8 @@ internal class RumActionScope( application = ActionEvent.Application(rumContext.applicationId), session = ActionEvent.ActionEventSession( id = rumContext.sessionId, - type = ActionEvent.ActionEventSessionType.USER + type = ActionEvent.ActionEventSessionType.USER, + hasReplay = hasReplay ), source = rumEventSourceProvider.actionEventSource, usr = ActionEvent.Usr( @@ -260,7 +265,8 @@ internal class RumActionScope( event: RumRawEvent.StartAction, timestampOffset: Long, eventSourceProvider: RumEventSourceProvider, - contextProvider: ContextProvider + contextProvider: ContextProvider, + featuresContextResolver: FeaturesContextResolver ): RumScope { return RumActionScope( parentScope, @@ -271,7 +277,8 @@ internal class RumActionScope( event.attributes, timestampOffset, rumEventSourceProvider = eventSourceProvider, - contextProvider = contextProvider + contextProvider = contextProvider, + featuresContextResolver = featuresContextResolver ) } } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt index 537dcb0064..e25b7ea28e 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt @@ -15,6 +15,7 @@ import com.datadog.android.rum.GlobalRum import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.event.ResourceTiming @@ -37,7 +38,8 @@ internal class RumResourceScope( serverTimeOffsetInMs: Long, internal val firstPartyHostDetector: FirstPartyHostDetector, private val rumEventSourceProvider: RumEventSourceProvider, - private val contextProvider: ContextProvider + private val contextProvider: ContextProvider, + private val featuresContextResolver: FeaturesContextResolver ) : RumScope { internal val resourceId: String = UUID.randomUUID().toString() @@ -173,6 +175,7 @@ internal class RumResourceScope( val sdkContext = contextProvider.context val rumContext = getRumContext() val user = sdkContext.userInfo + val hasReplay = featuresContextResolver.resolveHasReplay(sdkContext) @Suppress("UNCHECKED_CAST") val finalTiming = timing ?: extractResourceTiming( @@ -212,7 +215,8 @@ internal class RumResourceScope( application = ResourceEvent.Application(rumContext.applicationId), session = ResourceEvent.ResourceEventSession( id = rumContext.sessionId, - type = ResourceEvent.ResourceEventSessionType.USER + type = ResourceEvent.ResourceEventSessionType.USER, + hasReplay = hasReplay ), source = rumEventSourceProvider.resourceEventSource, os = ResourceEvent.Os( @@ -259,7 +263,7 @@ internal class RumResourceScope( } } - @SuppressWarnings("LongParameterList") + @SuppressWarnings("LongParameterList", "LongMethod") @WorkerThread private fun sendError( message: String, @@ -274,6 +278,7 @@ internal class RumResourceScope( val rumContext = getRumContext() val sdkContext = contextProvider.context val user = sdkContext.userInfo + val hasReplay = featuresContextResolver.resolveHasReplay(sdkContext) val errorEvent = ErrorEvent( date = eventTimestamp, @@ -307,7 +312,8 @@ internal class RumResourceScope( application = ErrorEvent.Application(rumContext.applicationId), session = ErrorEvent.ErrorEventSession( id = rumContext.sessionId, - type = ErrorEvent.ErrorEventSessionType.USER + type = ErrorEvent.ErrorEventSessionType.USER, + hasReplay = hasReplay ), source = rumEventSourceProvider.errorEventSource, os = ErrorEvent.Os( @@ -363,7 +369,8 @@ internal class RumResourceScope( firstPartyHostDetector: FirstPartyHostDetector, timestampOffset: Long, rumEventSourceProvider: RumEventSourceProvider, - contextProvider: ContextProvider + contextProvider: ContextProvider, + featuresContextResolver: FeaturesContextResolver ): RumScope { return RumResourceScope( parentScope, @@ -375,7 +382,8 @@ internal class RumResourceScope( timestampOffset, firstPartyHostDetector, rumEventSourceProvider, - contextProvider + contextProvider, + featuresContextResolver ) } } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index 30bfe0858b..76453bf63a 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -25,6 +25,7 @@ import com.datadog.android.log.internal.utils.debugWithTelemetry import com.datadog.android.rum.GlobalRum import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumAttributes +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.event.RumEventSourceProvider @@ -58,6 +59,7 @@ internal open class RumViewScope( private val contextProvider: ContextProvider, private val buildSdkVersionProvider: BuildSdkVersionProvider = DefaultBuildSdkVersionProvider(), private val viewUpdatePredicate: ViewUpdatePredicate = DefaultViewUpdatePredicate(), + private val featuresContextResolver: FeaturesContextResolver = FeaturesContextResolver(), internal val type: RumViewType = RumViewType.FOREGROUND ) : RumScope { @@ -281,7 +283,8 @@ internal open class RumViewScope( event, serverTimeOffsetInMs, rumEventSourceProvider, - contextProvider + contextProvider, + featuresContextResolver ) pendingActionCount++ customActionScope.handleEvent(RumRawEvent.SendCustomActionNow(), writer) @@ -298,7 +301,8 @@ internal open class RumViewScope( event, serverTimeOffsetInMs, rumEventSourceProvider, - contextProvider + contextProvider, + featuresContextResolver ) ) pendingActionCount++ @@ -321,7 +325,8 @@ internal open class RumViewScope( firstPartyHostDetector, serverTimeOffsetInMs, rumEventSourceProvider, - contextProvider + contextProvider, + featuresContextResolver ) pendingResourceCount++ } @@ -344,6 +349,7 @@ internal open class RumViewScope( val networkInfo = sdkContext.networkInfo val errorType = event.type ?: event.throwable?.javaClass?.canonicalName val throwableMessage = event.throwable?.message ?: "" + val hasReplay = featuresContextResolver.resolveHasReplay(sdkContext) val message = if (throwableMessage.isNotBlank() && event.message != throwableMessage) { "${event.message}: $throwableMessage" } else { @@ -375,7 +381,8 @@ internal open class RumViewScope( application = ErrorEvent.Application(rumContext.applicationId), session = ErrorEvent.ErrorEventSession( id = rumContext.sessionId, - type = ErrorEvent.ErrorEventSessionType.USER + type = ErrorEvent.ErrorEventSessionType.USER, + hasReplay = hasReplay ), source = rumEventSourceProvider.errorEventSource, os = ErrorEvent.Os( @@ -581,6 +588,7 @@ internal open class RumViewScope( val timings = resolveCustomTimings() val memoryInfo = lastMemoryInfo val refreshRateInfo = lastFrameRateInfo + val hasReplay = featuresContextResolver.resolveHasReplay(sdkContext) val isSlowRendered = resolveRefreshRateInfo(refreshRateInfo) val viewEvent = ViewEvent( date = eventTimestamp, @@ -617,7 +625,8 @@ internal open class RumViewScope( application = ViewEvent.Application(rumContext.applicationId), session = ViewEvent.ViewEventSession( id = rumContext.sessionId, - type = ViewEvent.ViewEventSessionType.USER + type = ViewEvent.ViewEventSessionType.USER, + hasReplay = hasReplay ), source = rumEventSourceProvider.viewEventSource, os = ViewEvent.Os( @@ -718,7 +727,8 @@ internal open class RumViewScope( application = ActionEvent.Application(rumContext.applicationId), session = ActionEvent.ActionEventSession( id = rumContext.sessionId, - type = ActionEvent.ActionEventSessionType.USER + type = ActionEvent.ActionEventSessionType.USER, + hasReplay = false ), source = rumEventSourceProvider.actionEventSource, os = ActionEvent.Os( @@ -760,6 +770,7 @@ internal open class RumViewScope( val networkInfo = sdkContext.networkInfo val timestamp = event.eventTime.timestamp + serverTimeOffsetInMs val isFrozenFrame = event.durationNs > FROZEN_FRAME_THRESHOLD_NS + val hasReplay = featuresContextResolver.resolveHasReplay(sdkContext) val longTaskEvent = LongTaskEvent( date = timestamp - TimeUnit.NANOSECONDS.toMillis(event.durationNs), longTask = LongTaskEvent.LongTask( @@ -782,7 +793,8 @@ internal open class RumViewScope( application = LongTaskEvent.Application(rumContext.applicationId), session = LongTaskEvent.LongTaskEventSession( id = rumContext.sessionId, - type = LongTaskEvent.LongTaskEventSessionType.USER + type = LongTaskEvent.LongTaskEventSessionType.USER, + hasReplay = hasReplay ), source = rumEventSourceProvider.longTaskEventSource, os = LongTaskEvent.Os( diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index 69cfda1d20..3bf5f7e131 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -8,17 +8,18 @@ package com.datadog.android.sessionreplay.internal import android.app.Application import android.content.Context +import com.datadog.android.Datadog import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.internal.CoreFeature import com.datadog.android.core.internal.SdkFeature -import com.datadog.android.core.internal.net.DataUploader -import com.datadog.android.core.internal.net.NoOpDataUploader import com.datadog.android.core.internal.persistence.PersistenceStrategy import com.datadog.android.core.internal.utils.sdkLogger import com.datadog.android.sessionreplay.LifecycleCallback import com.datadog.android.sessionreplay.SessionReplayLifecycleCallback import com.datadog.android.sessionreplay.internal.domain.SessionReplayRecordPersistenceStrategy +import com.datadog.android.sessionreplay.internal.domain.SessionReplayRequestFactory import com.datadog.android.sessionreplay.internal.domain.SessionReplaySerializedRecordWriter +import com.datadog.android.v2.api.RequestFactory import java.util.concurrent.atomic.AtomicBoolean internal class SessionReplayFeature( @@ -27,9 +28,10 @@ internal class SessionReplayFeature( private val sessionReplayCallbackProvider: (PersistenceStrategy) -> LifecycleCallback = { SessionReplayLifecycleCallback( - SessionReplayContextProvider(), + SessionReplayRumContextProvider(), configuration.privacy, - SessionReplaySerializedRecordWriter(it.getWriter()) + SessionReplaySerializedRecordWriter(it.getWriter()), + SessionReplayRecordCallback(Datadog.globalSDKCore) ) } ) : SdkFeature(coreFeature) { @@ -61,6 +63,7 @@ internal class SessionReplayFeature( configuration: Configuration.Feature.SessionReplay ): PersistenceStrategy { return SessionReplayRecordPersistenceStrategy( + coreFeature.contextProvider, coreFeature.trackingConsentProvider, coreFeature.storageDir, coreFeature.persistenceExecutorService, @@ -69,9 +72,9 @@ internal class SessionReplayFeature( ) } - override fun createUploader(configuration: Configuration.Feature.SessionReplay): DataUploader { - // TODO: This will be added later in RUMM-2273 - return NoOpDataUploader() + override fun createRequestFactory(configuration: Configuration.Feature.SessionReplay): + RequestFactory { + return SessionReplayRequestFactory() } // endregion @@ -110,5 +113,6 @@ internal class SessionReplayFeature( companion object { const val SESSION_REPLAY_FEATURE_NAME = "session-replay" + const val IS_RECORDING_CONTEXT_KEY = "is_recording" } } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRecordCallback.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRecordCallback.kt new file mode 100644 index 0000000000..fad2b27377 --- /dev/null +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRecordCallback.kt @@ -0,0 +1,28 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.internal + +import com.datadog.android.sessionreplay.RecordCallback +import com.datadog.android.v2.api.SDKCore + +internal class SessionReplayRecordCallback(private val datadogCore: SDKCore) : RecordCallback { + override fun onStartRecording() { + updateRecording(true) + } + + override fun onStopRecording() { + updateRecording(false) + } + + private fun updateRecording(isRecording:Boolean){ + val featureContext = mapOf(SessionReplayFeature.IS_RECORDING_CONTEXT_KEY to isRecording) + datadogCore.setFeatureContext( + SessionReplayFeature.SESSION_REPLAY_FEATURE_NAME, + featureContext + ) + } +} diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayContextProvider.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRumContextProvider.kt similarity index 92% rename from dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayContextProvider.kt rename to dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRumContextProvider.kt index 962738f977..5ea4a0acc0 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayContextProvider.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRumContextProvider.kt @@ -11,7 +11,7 @@ import com.datadog.android.rum.internal.domain.RumContext.Companion.NULL_UUID import com.datadog.android.sessionreplay.utils.RumContextProvider import com.datadog.android.sessionreplay.utils.SessionReplayRumContext -internal class SessionReplayContextProvider : RumContextProvider { +internal class SessionReplayRumContextProvider : RumContextProvider { override fun getRumContext(): SessionReplayRumContext { return GlobalRum.getRumContext().let { SessionReplayRumContext( diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRecordPersistenceStrategy.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRecordPersistenceStrategy.kt index 81f915db5e..0555f8f6a7 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRecordPersistenceStrategy.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRecordPersistenceStrategy.kt @@ -8,6 +8,7 @@ package com.datadog.android.sessionreplay.internal.domain import com.datadog.android.core.internal.persistence.PayloadDecoration import com.datadog.android.core.internal.persistence.file.FileMover +import com.datadog.android.core.internal.persistence.file.FileReaderWriter import com.datadog.android.core.internal.persistence.file.advanced.FeatureFileOrchestrator import com.datadog.android.core.internal.persistence.file.batch.BatchFilePersistenceStrategy import com.datadog.android.core.internal.persistence.file.batch.BatchFileReaderWriter @@ -15,16 +16,19 @@ import com.datadog.android.core.internal.privacy.ConsentProvider import com.datadog.android.log.Logger import com.datadog.android.security.Encryption import com.datadog.android.sessionreplay.internal.SessionReplayFeature +import com.datadog.android.v2.core.internal.ContextProvider import java.io.File import java.util.concurrent.ExecutorService internal class SessionReplayRecordPersistenceStrategy( + contextProvider: ContextProvider, consentProvider: ConsentProvider, storageDir: File, executorService: ExecutorService, internalLogger: Logger, localDataEncryption: Encryption? ) : BatchFilePersistenceStrategy( + contextProvider, FeatureFileOrchestrator( consentProvider, storageDir, @@ -37,5 +41,6 @@ internal class SessionReplayRecordPersistenceStrategy( PayloadDecoration.NEW_LINE_DECORATION, internalLogger, BatchFileReaderWriter.create(internalLogger, localDataEncryption), + FileReaderWriter.create(internalLogger, localDataEncryption), FileMover(internalLogger) ) diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRequestFactory.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRequestFactory.kt new file mode 100644 index 0000000000..35e19f87aa --- /dev/null +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/sessionreplay/internal/domain/SessionReplayRequestFactory.kt @@ -0,0 +1,22 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.internal.domain + +import com.datadog.android.v2.api.Request +import com.datadog.android.v2.api.RequestFactory +import com.datadog.android.v2.api.context.DatadogContext + +internal class SessionReplayRequestFactory : RequestFactory { + override fun create( + context: DatadogContext, + batchData: List, + batchMetadata: ByteArray? + ): Request { + // TODO: RUMM-2275 + return Request("", "", "", emptyMap(), ByteArray(0)) + } +} diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ActionEventAssert.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ActionEventAssert.kt index 70fa08eb7b..88f590cb51 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ActionEventAssert.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ActionEventAssert.kt @@ -392,6 +392,15 @@ internal class ActionEventAssert(actual: ActionEvent) : return this } + fun hasReplay(hasReplay: Boolean) { + assertThat(actual.session.hasReplay) + .overridingErrorMessage( + "Expected event data to have hasReplay $hasReplay " + + "but was ${actual.session.hasReplay}" + ) + .isEqualTo(hasReplay) + } + companion object { internal const val TIMESTAMP_THRESHOLD_MS = 50L internal fun assertThat(actual: ActionEvent): ActionEventAssert = diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt index de8dba8029..60fd825c9e 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt @@ -508,6 +508,15 @@ internal class ErrorEventAssert(actual: ErrorEvent) : return this } + fun hasReplay(hasReplay: Boolean) { + assertThat(actual.session.hasReplay) + .overridingErrorMessage( + "Expected event data to have hasReplay $hasReplay " + + "but was ${actual.session.hasReplay}" + ) + .isEqualTo(hasReplay) + } + companion object { internal const val TIMESTAMP_THRESHOLD_MS = 50L internal fun assertThat(actual: ErrorEvent): ErrorEventAssert = diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/LongTaskEventAssert.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/LongTaskEventAssert.kt index e5f2c24714..8d4b8c463b 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/LongTaskEventAssert.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/LongTaskEventAssert.kt @@ -355,6 +355,15 @@ internal class LongTaskEventAssert(actual: LongTaskEvent) : return this } + fun hasReplay(hasReplay: Boolean) { + assertThat(actual.session.hasReplay) + .overridingErrorMessage( + "Expected event data to have hasReplay $hasReplay " + + "but was ${actual.session.hasReplay}" + ) + .isEqualTo(hasReplay) + } + companion object { internal const val TIMESTAMP_THRESHOLD_MS = 50L internal fun assertThat(actual: LongTaskEvent): LongTaskEventAssert = diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt index af48fdbdaa..07fd5dfae5 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt @@ -608,6 +608,15 @@ internal class ResourceEventAssert(actual: ResourceEvent) : return this } + fun hasReplay(hasReplay: Boolean) { + assertThat(actual.session.hasReplay) + .overridingErrorMessage( + "Expected event data to have hasReplay $hasReplay " + + "but was ${actual.session.hasReplay}" + ) + .isEqualTo(hasReplay) + } + companion object { internal const val TIMESTAMP_THRESHOLD_MS = 50L diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ViewEventAssert.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ViewEventAssert.kt index e2c2e298df..7b9ab14a14 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ViewEventAssert.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/assertj/ViewEventAssert.kt @@ -508,6 +508,15 @@ internal class ViewEventAssert(actual: ViewEvent) : return this } + fun hasReplay(hasReplay: Boolean) { + assertThat(actual.session.hasReplay) + .overridingErrorMessage( + "Expected event data to have hasReplay $hasReplay " + + "but was ${actual.session.hasReplay}" + ) + .isEqualTo(hasReplay) + } + companion object { internal val ONE_SECOND_NS = TimeUnit.SECONDS.toNanos(1) diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/FeaturesContextResolverTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/FeaturesContextResolverTest.kt new file mode 100644 index 0000000000..035f162a4d --- /dev/null +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/FeaturesContextResolverTest.kt @@ -0,0 +1,99 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal + +import com.datadog.android.sessionreplay.internal.SessionReplayFeature +import com.datadog.android.utils.forge.Configurator +import com.datadog.android.v2.api.context.DatadogContext +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class FeaturesContextResolverTest { + + lateinit var testedFeaturesContextResolver: FeaturesContextResolver + + @BeforeEach + fun `set up`() { + testedFeaturesContextResolver = FeaturesContextResolver() + } + + @Test + fun `M return true W resolveHasReplay {sessionReplayContext isRecording}`(forge: Forge) { + // Given + val fakeSdkContext = forge.getForgery() + .copy( + featuresContext = mapOf( + SessionReplayFeature.SESSION_REPLAY_FEATURE_NAME to + mapOf(SessionReplayFeature.IS_RECORDING_CONTEXT_KEY to true) + ) + + ) + + // When + val hasReplay = testedFeaturesContextResolver.resolveHasReplay(fakeSdkContext) + assertThat(hasReplay).isTrue + } + + @Test + fun `M return false W resolveHasReplay {sessionReplayContext isNotRecording}`(forge: Forge) { + // Given + val fakeSdkContext = forge.getForgery() + .copy( + featuresContext = mapOf( + SessionReplayFeature.SESSION_REPLAY_FEATURE_NAME to + mapOf(SessionReplayFeature.IS_RECORDING_CONTEXT_KEY to false) + ) + + ) + + // When + val hasReplay = testedFeaturesContextResolver.resolveHasReplay(fakeSdkContext) + assertThat(hasReplay).isFalse + } + + @Test + fun `M return false W resolveHasReplay {sessionReplayContext does not exist}`(forge: Forge) { + // Given + val fakeSdkContext = forge.getForgery() + .copy(featuresContext = emptyMap()) + + // When + val hasReplay = testedFeaturesContextResolver.resolveHasReplay(fakeSdkContext) + assertThat(hasReplay).isFalse + } + + @Test + fun `M return false W resolveHasReplay {isRecording context key does not exist}`(forge: Forge) { + // Given + val fakeSdkContext = forge.getForgery() + .copy( + featuresContext = mapOf( + SessionReplayFeature.SESSION_REPLAY_FEATURE_NAME to + emptyMap() + ) + ) + + // When + val hasReplay = testedFeaturesContextResolver.resolveHasReplay(fakeSdkContext) + assertThat(hasReplay).isFalse + } +} diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt index 8cd489997b..9c9e062ef5 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt @@ -12,6 +12,7 @@ import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind import com.datadog.android.rum.assertj.ActionEventAssert.Companion.assertThat +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.event.RumEventSourceProvider @@ -35,6 +36,7 @@ import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions import com.nhaarman.mockitokotlin2.verifyZeroInteractions import com.nhaarman.mockitokotlin2.whenever import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery @@ -96,8 +98,16 @@ internal class RumActionScopeTest { @Mock lateinit var mockRumEventSourceProvider: RumEventSourceProvider + @BoolForgery + var fakeHasReplay: Boolean = false + + @Mock + lateinit var mockFeaturesContextResolver: FeaturesContextResolver + @BeforeEach fun `set up`(forge: Forge) { + whenever(mockFeaturesContextResolver.resolveHasReplay(fakeDatadogContext)) + .thenReturn(fakeHasReplay) fakeSourceActionEvent = forge.aNullable { aValueFrom(ActionEvent.Source::class.java) } whenever(mockRumEventSourceProvider.actionEventSource) .thenReturn(fakeSourceActionEvent) @@ -128,7 +138,8 @@ internal class RumActionScopeTest { TEST_INACTIVITY_MS, TEST_MAX_DURATION_MS, mockRumEventSourceProvider, - mockContextProvider + mockContextProvider, + mockFeaturesContextResolver ) } @@ -190,6 +201,7 @@ internal class RumActionScopeTest { hasCrashCount(0) hasLongTaskCount(0) hasNoFrustration() + hasReplay(fakeHasReplay) hasView(fakeParentContext) hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) @@ -294,6 +306,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -372,6 +385,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -503,6 +517,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -568,6 +583,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -620,6 +636,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -682,6 +699,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -738,6 +756,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -791,6 +810,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -843,6 +863,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -900,6 +921,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -959,6 +981,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1010,6 +1033,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1061,6 +1085,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1117,6 +1142,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1175,6 +1201,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1219,7 +1246,8 @@ internal class RumActionScopeTest { TEST_INACTIVITY_MS, TEST_MAX_DURATION_MS, mockRumEventSourceProvider, - mockContextProvider + mockContextProvider, + mockFeaturesContextResolver ) fakeGlobalAttributes.keys.forEach { GlobalRum.globalAttributes.remove(it) } @@ -1247,6 +1275,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1305,6 +1334,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1354,6 +1384,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1406,6 +1437,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1462,6 +1494,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1519,6 +1552,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1567,6 +1601,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1622,6 +1657,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1675,6 +1711,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1726,6 +1763,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1777,6 +1815,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1830,6 +1869,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1913,6 +1953,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -1961,6 +2002,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -2009,6 +2051,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -2057,6 +2100,7 @@ internal class RumActionScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt index 1f818d7ba0..b8fbb34484 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt @@ -16,6 +16,7 @@ import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind import com.datadog.android.rum.assertj.ErrorEventAssert.Companion.assertThat import com.datadog.android.rum.assertj.ResourceEventAssert.Companion.assertThat +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.event.ResourceTiming @@ -44,6 +45,7 @@ import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions import com.nhaarman.mockitokotlin2.whenever import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery @@ -110,6 +112,12 @@ internal class RumResourceScopeTest { @Mock lateinit var mockRumEventSourceProvider: RumEventSourceProvider + @BoolForgery + var fakeHasReplay: Boolean = false + + @Mock + lateinit var mockFeaturesContextResolver: FeaturesContextResolver + @BeforeEach fun `set up`(forge: Forge) { fakeSourceResourceEvent = forge.aNullable { aValueFrom(ResourceEvent.Source::class.java) } @@ -133,6 +141,8 @@ internal class RumResourceScopeTest { whenever(mockContextProvider.context) doReturn fakeDatadogContext whenever(mockParentScope.getRumContext()) doReturn fakeParentContext doAnswer { false }.whenever(mockDetector).isFirstPartyUrl(any()) + whenever(mockFeaturesContextResolver.resolveHasReplay(fakeDatadogContext)) + .thenReturn(fakeHasReplay) testedScope = RumResourceScope( mockParentScope, @@ -144,7 +154,8 @@ internal class RumResourceScopeTest { fakeServerOffset, mockDetector, mockRumEventSourceProvider, - mockContextProvider + mockContextProvider, + mockFeaturesContextResolver ) } @@ -210,6 +221,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(null) hasSpanId(null) + hasReplay(fakeHasReplay) doesNotHaveAResourceProvider() hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) @@ -272,6 +284,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(null) hasSpanId(null) + hasReplay(fakeHasReplay) hasProviderType(ResourceEvent.ProviderType.FIRST_PARTY) hasProviderDomain(URL(fakeUrl).host) hasLiteSessionPlan() @@ -315,7 +328,8 @@ internal class RumResourceScopeTest { fakeServerOffset, mockDetector, mockRumEventSourceProvider, - mockContextProvider + mockContextProvider, + mockFeaturesContextResolver ) doAnswer { true }.whenever(mockDetector).isFirstPartyUrl(brokenUrl) val attributes = forge.exhaustiveAttributes(excludedKeys = fakeAttributes.keys) @@ -348,6 +362,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(null) hasSpanId(null) + hasReplay(fakeHasReplay) hasProviderType(ResourceEvent.ProviderType.FIRST_PARTY) hasProviderDomain(brokenUrl) hasLiteSessionPlan() @@ -415,6 +430,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(fakeTraceId) hasSpanId(fakeSpanId) + hasReplay(fakeHasReplay) doesNotHaveAResourceProvider() hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) @@ -478,6 +494,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(null) hasSpanId(null) + hasReplay(fakeHasReplay) doesNotHaveAResourceProvider() hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) @@ -532,6 +549,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(null) hasSpanId(null) + hasReplay(fakeHasReplay) doesNotHaveAResourceProvider() hasLiteSessionPlan() containsExactlyContextAttributes(fakeAttributes) @@ -580,6 +598,7 @@ internal class RumResourceScopeTest { hasSessionId(fakeParentContext.sessionId) hasActionId(fakeParentContext.actionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceResourceEvent) hasDeviceInfo( @@ -667,7 +686,8 @@ internal class RumResourceScopeTest { fakeServerOffset, mockDetector, mockRumEventSourceProvider, - mockContextProvider + mockContextProvider, + mockFeaturesContextResolver ) fakeGlobalAttributes.keys.forEach { GlobalRum.removeAttribute(it) } @@ -696,6 +716,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(null) hasSpanId(null) + hasReplay(fakeHasReplay) doesNotHaveAResourceProvider() hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) @@ -760,6 +781,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(null) hasSpanId(null) + hasReplay(fakeHasReplay) doesNotHaveAResourceProvider() hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) @@ -825,6 +847,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(null) hasSpanId(null) + hasReplay(fakeHasReplay) doesNotHaveAResourceProvider() hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) @@ -891,6 +914,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) hasTraceId(null) hasSpanId(null) + hasReplay(fakeHasReplay) doesNotHaveAResourceProvider() hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) @@ -960,6 +984,7 @@ internal class RumResourceScopeTest { hasErrorType(throwable.javaClass.canonicalName) hasErrorSourceType(ErrorEvent.SourceType.ANDROID) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -1028,6 +1053,7 @@ internal class RumResourceScopeTest { hasErrorType(errorType) hasErrorSourceType(ErrorEvent.SourceType.ANDROID) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -1067,7 +1093,8 @@ internal class RumResourceScopeTest { fakeServerOffset, mockDetector, mockRumEventSourceProvider, - mockContextProvider + mockContextProvider, + mockFeaturesContextResolver ) doAnswer { true }.whenever(mockDetector).isFirstPartyUrl(brokenUrl) val attributes = forge.exhaustiveAttributes(excludedKeys = fakeAttributes.keys) @@ -1109,6 +1136,7 @@ internal class RumResourceScopeTest { hasErrorType(throwable.javaClass.canonicalName) hasErrorSourceType(ErrorEvent.SourceType.ANDROID) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -1150,7 +1178,8 @@ internal class RumResourceScopeTest { fakeServerOffset, mockDetector, mockRumEventSourceProvider, - mockContextProvider + mockContextProvider, + mockFeaturesContextResolver ) doAnswer { true }.whenever(mockDetector).isFirstPartyUrl(brokenUrl) val attributes = forge.exhaustiveAttributes(excludedKeys = fakeAttributes.keys) @@ -1193,6 +1222,7 @@ internal class RumResourceScopeTest { hasErrorType(errorType) hasErrorSourceType(ErrorEvent.SourceType.ANDROID) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -1262,6 +1292,7 @@ internal class RumResourceScopeTest { hasErrorType(throwable.javaClass.canonicalName) hasErrorSourceType(ErrorEvent.SourceType.ANDROID) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -1333,6 +1364,7 @@ internal class RumResourceScopeTest { hasErrorType(errorType) hasErrorSourceType(ErrorEvent.SourceType.ANDROID) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -1401,6 +1433,7 @@ internal class RumResourceScopeTest { hasErrorSourceType(ErrorEvent.SourceType.ANDROID) doesNotHaveAResourceProvider() hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -1472,6 +1505,7 @@ internal class RumResourceScopeTest { hasErrorSourceType(ErrorEvent.SourceType.ANDROID) doesNotHaveAResourceProvider() hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -1546,6 +1580,7 @@ internal class RumResourceScopeTest { hasErrorSourceType(ErrorEvent.SourceType.ANDROID) doesNotHaveAResourceProvider() hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -1623,6 +1658,7 @@ internal class RumResourceScopeTest { hasErrorSourceType(ErrorEvent.SourceType.ANDROID) doesNotHaveAResourceProvider() hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -1775,6 +1811,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) doesNotHaveAResourceProvider() hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceResourceEvent) hasDeviceInfo( @@ -1836,6 +1873,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) doesNotHaveAResourceProvider() hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceResourceEvent) hasDeviceInfo( @@ -1899,6 +1937,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) doesNotHaveAResourceProvider() hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceResourceEvent) hasDeviceInfo( @@ -2011,6 +2050,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) doesNotHaveAResourceProvider() hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceResourceEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -2071,6 +2111,7 @@ internal class RumResourceScopeTest { hasActionId(fakeParentContext.actionId) doesNotHaveAResourceProvider() hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceResourceEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt index a246787f1f..184d05ec44 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt @@ -26,6 +26,7 @@ import com.datadog.android.rum.assertj.ActionEventAssert.Companion.assertThat import com.datadog.android.rum.assertj.ErrorEventAssert.Companion.assertThat import com.datadog.android.rum.assertj.LongTaskEventAssert.Companion.assertThat import com.datadog.android.rum.assertj.ViewEventAssert.Companion.assertThat +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time @@ -160,6 +161,12 @@ internal class RumViewScopeTest { @Mock lateinit var mockViewUpdatePredicate: ViewUpdatePredicate + @BoolForgery + var fakeHasReplay: Boolean = false + + @Mock + lateinit var mockFeaturesContextResolver: FeaturesContextResolver + @BeforeEach fun `set up`(forge: Forge) { fakeSourceViewEvent = forge.aNullable { aValueFrom(ViewEvent.Source::class.java) } @@ -202,7 +209,8 @@ internal class RumViewScopeTest { whenever(mockActionScope.actionId) doReturn fakeActionId whenever(mockBuildSdkVersionProvider.version()) doReturn Build.VERSION_CODES.BASE whenever(mockViewUpdatePredicate.canUpdateView(any(), any())).thenReturn(true) - + whenever(mockFeaturesContextResolver.resolveHasReplay(fakeDatadogContext)) + .thenReturn(fakeHasReplay) testedScope = RumViewScope( mockParentScope, fakeKey, @@ -216,7 +224,8 @@ internal class RumViewScopeTest { mockRumEventSourceProvider, mockContextProvider, mockBuildSdkVersionProvider, - mockViewUpdatePredicate + mockViewUpdatePredicate, + mockFeaturesContextResolver ) assertThat(GlobalRum.getRumContext()).isEqualTo(testedScope.getRumContext()) @@ -304,6 +313,7 @@ internal class RumViewScopeTest { mockContextProvider, mockBuildSdkVersionProvider, mockViewUpdatePredicate, + mockFeaturesContextResolver, type = fakeViewEventType ) @@ -373,6 +383,7 @@ internal class RumViewScopeTest { mockContextProvider, mockBuildSdkVersionProvider, mockViewUpdatePredicate, + mockFeaturesContextResolver, type = expectedViewType ) @@ -538,6 +549,7 @@ internal class RumViewScopeTest { mockContextProvider, mockBuildSdkVersionProvider, mockViewUpdatePredicate, + mockFeaturesContextResolver, type = viewType ) @@ -582,6 +594,7 @@ internal class RumViewScopeTest { mockContextProvider, mockBuildSdkVersionProvider, mockViewUpdatePredicate, + mockFeaturesContextResolver, type = viewType ) @@ -653,6 +666,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceViewEvent) containsExactlyContextAttributes(fakeAttributes) hasDeviceInfo( @@ -719,6 +733,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) containsExactlyContextAttributes(fakeAttributes) hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceViewEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -784,6 +799,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) containsExactlyContextAttributes(expectedAttributes) hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceViewEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -855,6 +871,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) + hasReplay(fakeHasReplay) hasSource(fakeSourceViewEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -923,6 +940,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) + hasReplay(fakeHasReplay) hasSource(fakeSourceViewEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -991,6 +1009,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() containsExactlyContextAttributes(expectedAttributes) + hasReplay(fakeHasReplay) hasSource(fakeSourceViewEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -1046,6 +1065,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceViewEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -1090,7 +1110,8 @@ internal class RumViewScopeTest { mockFrameRateVitalMonitor, mockRumEventSourceProvider, mockContextProvider, - viewUpdatePredicate = mockViewUpdatePredicate + viewUpdatePredicate = mockViewUpdatePredicate, + featuresContextResolver = mockFeaturesContextResolver ) fakeGlobalAttributes.keys.forEach { GlobalRum.removeAttribute(it) } @@ -1128,6 +1149,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1195,6 +1217,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1237,7 +1260,8 @@ internal class RumViewScopeTest { mockFrameRateVitalMonitor, mockRumEventSourceProvider, mockContextProvider, - viewUpdatePredicate = mockViewUpdatePredicate + viewUpdatePredicate = mockViewUpdatePredicate, + featuresContextResolver = mockFeaturesContextResolver ) val expectedAttributes = mutableMapOf() expectedAttributes.putAll(fakeAttributes) @@ -1278,6 +1302,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1322,7 +1347,8 @@ internal class RumViewScopeTest { mockFrameRateVitalMonitor, mockRumEventSourceProvider, mockContextProvider, - viewUpdatePredicate = mockViewUpdatePredicate + viewUpdatePredicate = mockViewUpdatePredicate, + featuresContextResolver = mockFeaturesContextResolver ) val expectedAttributes = mutableMapOf() expectedAttributes.putAll(fakeAttributes) @@ -1363,6 +1389,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1431,6 +1458,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1498,6 +1526,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1558,6 +1587,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1661,6 +1691,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1741,6 +1772,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1822,6 +1854,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1905,6 +1938,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -1968,6 +2002,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -2043,6 +2078,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(false) hasSource(fakeSourceActionEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -2100,6 +2136,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -2180,6 +2217,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -2262,6 +2300,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -2343,6 +2382,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -2419,6 +2459,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(false) hasSource(fakeSourceActionEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -2471,6 +2512,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(false) hasSource(fakeSourceActionEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -2542,6 +2584,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -2802,6 +2845,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceActionEvent) hasDeviceInfo( @@ -3250,6 +3294,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasActionId(fakeActionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -3310,6 +3355,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasActionId(fakeActionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -3372,6 +3418,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasActionId(fakeActionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -3436,6 +3483,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasActionId(fakeActionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -3493,6 +3541,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasActionId(fakeActionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) } @@ -3545,6 +3594,7 @@ internal class RumViewScopeTest { hasErrorType(throwable.javaClass.canonicalName) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -3608,6 +3658,7 @@ internal class RumViewScopeTest { hasErrorType(null) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -3670,6 +3721,7 @@ internal class RumViewScopeTest { hasErrorType(null) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -3712,6 +3764,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -3778,6 +3831,7 @@ internal class RumViewScopeTest { hasErrorType(throwable.javaClass.canonicalName) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -3842,6 +3896,7 @@ internal class RumViewScopeTest { hasErrorType(throwable.javaClass.canonicalName) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -3884,6 +3939,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -3950,6 +4006,7 @@ internal class RumViewScopeTest { hasErrorType(throwable.javaClass.canonicalName) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -4015,6 +4072,7 @@ internal class RumViewScopeTest { hasErrorType(throwable.javaClass.canonicalName) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -4082,6 +4140,7 @@ internal class RumViewScopeTest { hasErrorType(errorType) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -4147,6 +4206,7 @@ internal class RumViewScopeTest { hasErrorType(throwable.javaClass.canonicalName) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -4189,6 +4249,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes + attributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -4379,6 +4440,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceLongTaskEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -4426,6 +4488,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceLongTaskEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -4482,6 +4545,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasActionId(fakeActionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceLongTaskEvent) hasDeviceInfo( @@ -4539,6 +4603,7 @@ internal class RumViewScopeTest { hasSessionId(fakeParentContext.sessionId) hasActionId(fakeActionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(expectedAttributes) hasSource(fakeSourceLongTaskEvent) hasDeviceInfo( @@ -4769,6 +4834,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -4834,6 +4900,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -4918,6 +4985,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceViewEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -4985,6 +5053,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5031,6 +5100,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5109,6 +5179,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5175,6 +5246,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5251,6 +5323,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5327,6 +5400,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5374,6 +5448,7 @@ internal class RumViewScopeTest { mockRumEventSourceProvider, mockContextProvider, mockBuildSdkVersionProvider, + featuresContextResolver = mockFeaturesContextResolver, viewUpdatePredicate = mockViewUpdatePredicate ) val listenerCaptor = argumentCaptor { @@ -5414,6 +5489,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5461,7 +5537,8 @@ internal class RumViewScopeTest { mockRumEventSourceProvider, mockContextProvider, mockBuildSdkVersionProvider, - mockViewUpdatePredicate + mockViewUpdatePredicate, + mockFeaturesContextResolver ) val listenerCaptor = argumentCaptor { verify(mockFrameRateVitalMonitor).register(capture()) @@ -5501,6 +5578,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5550,7 +5628,8 @@ internal class RumViewScopeTest { mockRumEventSourceProvider, mockContextProvider, mockBuildSdkVersionProvider, - mockViewUpdatePredicate + mockViewUpdatePredicate, + mockFeaturesContextResolver ) val listenerCaptor = argumentCaptor { verify(mockFrameRateVitalMonitor).register(capture()) @@ -5590,6 +5669,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5639,7 +5719,8 @@ internal class RumViewScopeTest { mockRumEventSourceProvider, mockContextProvider, mockBuildSdkVersionProvider, - mockViewUpdatePredicate + mockViewUpdatePredicate, + mockFeaturesContextResolver ) val listenerCaptor = argumentCaptor { verify(mockFrameRateVitalMonitor).register(capture()) @@ -5679,6 +5760,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5729,7 +5811,8 @@ internal class RumViewScopeTest { mockRumEventSourceProvider, mockContextProvider, mockBuildSdkVersionProvider, - mockViewUpdatePredicate + mockViewUpdatePredicate, + mockFeaturesContextResolver ) val listenerCaptor = argumentCaptor { verify(mockFrameRateVitalMonitor).register(capture()) @@ -5769,6 +5852,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5819,7 +5903,8 @@ internal class RumViewScopeTest { mockRumEventSourceProvider, mockContextProvider, mockBuildSdkVersionProvider, - mockViewUpdatePredicate + mockViewUpdatePredicate, + mockFeaturesContextResolver ) val listenerCaptor = argumentCaptor { verify(mockFrameRateVitalMonitor).register(capture()) @@ -5859,6 +5944,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(fakeAttributes) hasSource(fakeSourceViewEvent) hasDeviceInfo( @@ -5980,6 +6066,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(false) hasSource(fakeSourceActionEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -6028,6 +6115,7 @@ internal class RumViewScopeTest { hasApplicationId(fakeParentContext.applicationId) hasSessionId(fakeParentContext.sessionId) hasLiteSessionPlan() + hasReplay(fakeHasReplay) hasSource(fakeSourceLongTaskEvent) hasDeviceInfo( fakeDatadogContext.deviceInfo.deviceName, @@ -6090,6 +6178,7 @@ internal class RumViewScopeTest { hasActionId(fakeActionId) hasErrorSourceType(sourceType.toSchemaSourceType()) hasLiteSessionPlan() + hasReplay(fakeHasReplay) containsExactlyContextAttributes(attributes) hasSource(fakeSourceErrorEvent) hasDeviceInfo( @@ -6133,7 +6222,8 @@ internal class RumViewScopeTest { mockRumEventSourceProvider, mockContextProvider, mockBuildSdkVersionProvider, - mockViewUpdatePredicate + mockViewUpdatePredicate, + mockFeaturesContextResolver ) // When diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRecordCallbackTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRecordCallbackTest.kt new file mode 100644 index 0000000000..e0c85da3e3 --- /dev/null +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRecordCallbackTest.kt @@ -0,0 +1,58 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.internal + +import com.datadog.android.v2.api.SDKCore +import com.nhaarman.mockitokotlin2.verify +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(MockitoExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +internal class SessionReplayRecordCallbackTest { + + @Mock + lateinit var mockDatadogCore: SDKCore + lateinit var testedRecordCallback: SessionReplayRecordCallback + + @BeforeEach + fun `set up`() { + testedRecordCallback = SessionReplayRecordCallback(mockDatadogCore) + } + + @Test + fun `M update session replay context W onStartRecording`() { + // When + testedRecordCallback.onStartRecording() + + // Then + verify(mockDatadogCore).setFeatureContext( + SessionReplayFeature.SESSION_REPLAY_FEATURE_NAME, + mapOf(SessionReplayFeature.IS_RECORDING_CONTEXT_KEY to true) + ) + } + + @Test + fun `M update session replay context W onStopRecording`() { + // When + testedRecordCallback.onStopRecording() + + // Then + verify(mockDatadogCore).setFeatureContext( + SessionReplayFeature.SESSION_REPLAY_FEATURE_NAME, + mapOf(SessionReplayFeature.IS_RECORDING_CONTEXT_KEY to false) + ) + } +} diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayContextProviderTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRumContextProviderTest.kt similarity index 94% rename from dd-sdk-android/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayContextProviderTest.kt rename to dd-sdk-android/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRumContextProviderTest.kt index 1576abbbce..b101295750 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayContextProviderTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayRumContextProviderTest.kt @@ -32,13 +32,13 @@ import org.mockito.quality.Strictness ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) -internal class SessionReplayContextProviderTest { +internal class SessionReplayRumContextProviderTest { - lateinit var testedSessionReplayContextProvider: SessionReplayContextProvider + lateinit var testedSessionReplayContextProvider: SessionReplayRumContextProvider @BeforeEach fun `set up`() { - testedSessionReplayContextProvider = SessionReplayContextProvider() + testedSessionReplayContextProvider = SessionReplayRumContextProvider() } @Test diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/v2/core/DatadogCoreTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/v2/core/DatadogCoreTest.kt index 7e79eab75b..92264464eb 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/v2/core/DatadogCoreTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/v2/core/DatadogCoreTest.kt @@ -286,7 +286,6 @@ internal class DatadogCoreTest { assertThat(testedCore.webViewRumFeature).isNull() assertThat(testedCore.crashReportsFeature).isNull() assertThat(testedCore.sessionReplayFeature).isNull() - assertThat(testedCore.contextProvider).isNull() } companion object { diff --git a/detekt.yml b/detekt.yml index bd9de0937d..6c4f9b5713 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1059,6 +1059,7 @@ datadog: - "kotlin.collections.Map.filterKeys(kotlin.Function1)" - "kotlin.collections.Map.filterValues(kotlin.Function1)" - "kotlin.collections.Map.forEach(kotlin.Function1)" + - "kotlin.collections.Map.get(kotlin.String)" - "kotlin.collections.Map.isEmpty()" - "kotlin.collections.Map.isNotEmpty()" - "kotlin.collections.Map.map(kotlin.Function1)" diff --git a/library/dd-sdk-android-session-replay/apiSurface b/library/dd-sdk-android-session-replay/apiSurface index 06e02c2068..0f93fccd39 100644 --- a/library/dd-sdk-android-session-replay/apiSurface +++ b/library/dd-sdk-android-session-replay/apiSurface @@ -1,10 +1,13 @@ interface com.datadog.android.sessionreplay.LifecycleCallback : android.app.Application.ActivityLifecycleCallbacks fun register(android.app.Application) fun unregisterAndStopRecorders(android.app.Application) +interface com.datadog.android.sessionreplay.RecordCallback + fun onStartRecording() + fun onStopRecording() interface com.datadog.android.sessionreplay.SerializedRecordWriter fun write(String) class com.datadog.android.sessionreplay.SessionReplayLifecycleCallback : LifecycleCallback - constructor(com.datadog.android.sessionreplay.utils.RumContextProvider, SessionReplayPrivacy, SerializedRecordWriter) + constructor(com.datadog.android.sessionreplay.utils.RumContextProvider, SessionReplayPrivacy, SerializedRecordWriter, RecordCallback = NoOpRecordCallback()) override fun onActivityCreated(android.app.Activity, android.os.Bundle?) override fun onActivityStarted(android.app.Activity) override fun onActivityResumed(android.app.Activity) @@ -227,3 +230,4 @@ interface com.datadog.android.sessionreplay.utils.RumContextProvider data class com.datadog.android.sessionreplay.utils.SessionReplayRumContext constructor(String = NULL_UUID, String = NULL_UUID, String = NULL_UUID) companion object +class com.datadog.tools.annotation.NoOpImplementation diff --git a/library/dd-sdk-android-session-replay/build.gradle.kts b/library/dd-sdk-android-session-replay/build.gradle.kts index 3f0ba17d49..74c9ffd922 100644 --- a/library/dd-sdk-android-session-replay/build.gradle.kts +++ b/library/dd-sdk-android-session-replay/build.gradle.kts @@ -18,6 +18,7 @@ plugins { // Build id("com.android.library") kotlin("android") + id("com.google.devtools.ksp") // Publish `maven-publish` @@ -45,6 +46,13 @@ android { setLibraryVersion() } + libraryVariants.configureEach { + addJavaSourceFoldersToModel( + layout.buildDirectory + .dir("generated/ksp/$name/kotlin").get().asFile + ) + } + sourceSets.named("main") { java.srcDir("src/main/kotlin") } @@ -81,6 +89,9 @@ dependencies { testImplementation(libs.bundles.jUnit5) testImplementation(libs.bundles.testTools) + // Generate NoOp implementations + ksp(project(":tools:noopfactory")) + detekt(project(":tools:detekt")) detekt(libs.detektCli) } diff --git a/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/RecordCallback.kt b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/RecordCallback.kt new file mode 100644 index 0000000000..fa7855c5c5 --- /dev/null +++ b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/RecordCallback.kt @@ -0,0 +1,27 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay + +import com.datadog.tools.annotation.NoOpImplementation + +/** + * Notifies the receiver whenever the screen is recorded or not. + * For internal usage only. + */ +@NoOpImplementation +interface RecordCallback { + + /** + * Called when we started recording the current screen. + */ + fun onStartRecording() + + /** + * Called when we stopped recording the current screen. + */ + fun onStopRecording() +} diff --git a/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayLifecycleCallback.kt b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayLifecycleCallback.kt index cbe26b5dc9..a372bad03f 100644 --- a/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayLifecycleCallback.kt +++ b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayLifecycleCallback.kt @@ -28,7 +28,8 @@ import java.util.concurrent.TimeUnit class SessionReplayLifecycleCallback( rumContextProvider: RumContextProvider, privacy: SessionReplayPrivacy, - serializedRecordWriter: SerializedRecordWriter + serializedRecordWriter: SerializedRecordWriter, + private val recordCallback: RecordCallback = NoOpRecordCallback() ) : LifecycleCallback { private val timeProvider = SessionReplayTimeProvider() @@ -65,11 +66,13 @@ class SessionReplayLifecycleCallback( override fun onActivityResumed(activity: Activity) { recorder.startRecording(activity) + recordCallback.onStartRecording() resumedActivities[activity] = null } override fun onActivityPaused(activity: Activity) { recorder.stopRecording(activity) + recordCallback.onStopRecording() resumedActivities.remove(activity) } diff --git a/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/tools/annotation/NoOpImplementation.kt b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/tools/annotation/NoOpImplementation.kt new file mode 100644 index 0000000000..71eb8af057 --- /dev/null +++ b/library/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/tools/annotation/NoOpImplementation.kt @@ -0,0 +1,14 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.tools.annotation + +/** + * Adding this annotation on an interface will generate a No-Op implementation class. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +annotation class NoOpImplementation diff --git a/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayLifecycleCallbackTest.kt b/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayLifecycleCallbackTest.kt index 0a7c37e09a..644f34d856 100644 --- a/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayLifecycleCallbackTest.kt +++ b/library/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayLifecycleCallbackTest.kt @@ -45,12 +45,16 @@ class SessionReplayLifecycleCallbackTest { @Forgery private lateinit var fakePrivacy: SessionReplayPrivacy + @Mock + private lateinit var mockRecordCallback: RecordCallback + @BeforeEach fun `set up`() { testedCallback = SessionReplayLifecycleCallback( mockRumContextProvider, fakePrivacy, - mockSerializedRecordWriter + mockSerializedRecordWriter, + mockRecordCallback ) testedCallback.recorder = mockRecoder } @@ -136,4 +140,30 @@ class SessionReplayLifecycleCallbackTest { verify(mockRecoder).stopRecording(mockActivity2) assertThat(testedCallback.resumedActivities).isEmpty() } + + @Test + fun `M notify the record callback W startedRecording`() { + // Given + val mockActivity: Activity = mock() + + // When + testedCallback.onActivityResumed(mockActivity) + + // Then + verify(mockRecoder).startRecording(mockActivity) + verify(mockRecordCallback).onStartRecording() + } + + @Test + fun `M notify the record callback W stoppedRecording`() { + // Given + val mockActivity: Activity = mock() + + // When + testedCallback.onActivityPaused(mockActivity) + + // Then + verify(mockRecoder).stopRecording(mockActivity) + verify(mockRecordCallback).onStopRecording() + } }