Skip to content

Commit

Permalink
Added tests for SR FGM configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-saia-datadog committed Nov 19, 2024
1 parent 13b3c14 commit 5fb15a2
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 260 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,24 @@ class DdSessionReplayImplementation(
* Enable session replay and start recording session.
* @param replaySampleRate The sample rate applied for session replay.
* @param customEndpoint Custom server url for sending replay data.
* @param imagePrivacyLevel Defines the way images should be masked.
* @param touchPrivacyLevel Defines the way user touches should be masked.
* @param textAndInputPrivacyLevel Defines the way text and input should be masked.
* @param privacySettings Defines the way visual elements should be masked.
* @param customEndpoint Custom server url for sending replay data.
* @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled.
*/
fun enable(
replaySampleRate: Double,
customEndpoint: String,
imagePrivacyLevel: String,
touchPrivacyLevel: String,
textAndInputPrivacyLevel: String,
privacySettings: SessionReplayPrivacySettings,
startRecordingImmediately: Boolean,
promise: Promise
) {
val sdkCore = DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
val logger = sdkCore.internalLogger
val configuration = SessionReplayConfiguration.Builder(replaySampleRate.toFloat())
.startRecordingImmediately(startRecordingImmediately)
.setImagePrivacy(convertImagePrivacyLevel(imagePrivacyLevel))
.setTouchPrivacy(convertTouchPrivacyLevel(touchPrivacyLevel))
.setTextAndInputPrivacy(convertTextAndInputPrivacyLevel(textAndInputPrivacyLevel))
.setImagePrivacy(privacySettings.imagePrivacyLevel)
.setTouchPrivacy(privacySettings.touchPrivacyLevel)
.setTextAndInputPrivacy(privacySettings.textAndInputPrivacyLevel)
.addExtensionSupport(ReactNativeSessionReplayExtensionSupport(reactContext, logger))

if (customEndpoint != "") {
Expand Down Expand Up @@ -80,68 +77,7 @@ class DdSessionReplayImplementation(
promise.resolve(null)
}

@Deprecated("Privacy should be set with separate properties mapped to " +
"`setImagePrivacy`, `setTouchPrivacy`, `setTextAndInputPrivacy`, but they are" +
" currently unavailable.")
private fun SessionReplayConfiguration.Builder.configurePrivacy(
defaultPrivacyLevel: String
): SessionReplayConfiguration.Builder {
when (defaultPrivacyLevel.lowercase(Locale.US)) {
"mask" -> {
this.setTextAndInputPrivacy(TextAndInputPrivacy.MASK_ALL)
this.setImagePrivacy(ImagePrivacy.MASK_ALL)
this.setTouchPrivacy(TouchPrivacy.HIDE)
}
"mask_user_input" -> {
this.setTextAndInputPrivacy(TextAndInputPrivacy.MASK_ALL_INPUTS)
this.setImagePrivacy(ImagePrivacy.MASK_NONE)
this.setTouchPrivacy(TouchPrivacy.HIDE)
}
"allow" -> {
this.setTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS)
this.setImagePrivacy(ImagePrivacy.MASK_NONE)
this.setTouchPrivacy(TouchPrivacy.SHOW)
}
}
return this
}

companion object {
internal const val NAME = "DdSessionReplay"

internal fun convertImagePrivacyLevel(imagePrivacyLevel: String): ImagePrivacy {
return when (imagePrivacyLevel) {
"MASK_NON_BUNDLED_ONLY" -> ImagePrivacy.MASK_LARGE_ONLY
"MASK_ALL" -> ImagePrivacy.MASK_ALL
"MASK_NONE" -> ImagePrivacy.MASK_NONE
else -> {
// TODO: Log wrong usage / mapping.
ImagePrivacy.MASK_ALL
}
}
}

internal fun convertTouchPrivacyLevel(touchPrivacyLevel: String): TouchPrivacy {
return when (touchPrivacyLevel) {
"SHOW" -> TouchPrivacy.SHOW
"HIDE" -> TouchPrivacy.HIDE
else -> {
// TODO: Log wrong usage / mapping.
TouchPrivacy.HIDE
}
}
}

internal fun convertTextAndInputPrivacyLevel(textAndInputPrivacyLevel: String): TextAndInputPrivacy {
return when (textAndInputPrivacyLevel) {
"MASK_SENSITIVE_INPUTS" -> TextAndInputPrivacy.MASK_SENSITIVE_INPUTS
"MASK_ALL_INPUTS" -> TextAndInputPrivacy.MASK_ALL_INPUTS
"MASK_ALL" -> TextAndInputPrivacy.MASK_ALL
else -> {
// TODO: Log wrong usage / mapping
TextAndInputPrivacy.MASK_ALL
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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.reactnative.sessionreplay

import com.datadog.android.sessionreplay.ImagePrivacy
import com.datadog.android.sessionreplay.TextAndInputPrivacy
import com.datadog.android.sessionreplay.TouchPrivacy

/**
* A utility class to store Session Replay privacy settings, and convert them from string to
* enum values.
*
* @param imagePrivacyLevel Defines the way images should be masked.
* @param touchPrivacyLevel Defines the way user touches should be masked.
* @param textAndInputPrivacyLevel Defines the way text and input should be masked.
*/
class SessionReplayPrivacySettings(
imagePrivacyLevel: String,
touchPrivacyLevel: String,
textAndInputPrivacyLevel: String
){
/**
* Defines the way images should be masked.
*/
val imagePrivacyLevel = getImagePrivacy(imagePrivacyLevel)

/**
* Defines the way user touches should be masked.
*/
val touchPrivacyLevel = getTouchPrivacy(touchPrivacyLevel)

/**
* Defines the way text and input should be masked.
*/
val textAndInputPrivacyLevel = getTextAndInputPrivacy(textAndInputPrivacyLevel)

companion object {
internal fun getImagePrivacy(imagePrivacyLevel: String): ImagePrivacy {
return when (imagePrivacyLevel) {
"MASK_NON_BUNDLED_ONLY" -> ImagePrivacy.MASK_LARGE_ONLY
"MASK_ALL" -> ImagePrivacy.MASK_ALL
"MASK_NONE" -> ImagePrivacy.MASK_NONE
else -> {
// TODO: Log wrong usage / mapping.
ImagePrivacy.MASK_ALL
}
}
}

internal fun getTouchPrivacy(touchPrivacyLevel: String): TouchPrivacy {
return when (touchPrivacyLevel) {
"SHOW" -> TouchPrivacy.SHOW
"HIDE" -> TouchPrivacy.HIDE
else -> {
// TODO: Log wrong usage / mapping.
TouchPrivacy.HIDE
}
}
}

internal fun getTextAndInputPrivacy(textAndInputPrivacyLevel: String): TextAndInputPrivacy {
return when (textAndInputPrivacyLevel) {
"MASK_SENSITIVE_INPUTS" -> TextAndInputPrivacy.MASK_SENSITIVE_INPUTS
"MASK_ALL_INPUTS" -> TextAndInputPrivacy.MASK_ALL_INPUTS
"MASK_ALL" -> TextAndInputPrivacy.MASK_ALL
else -> {
// TODO: Log wrong usage / mapping
TextAndInputPrivacy.MASK_ALL
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ class DdSessionReplay(
implementation.enable(
replaySampleRate,
customEndpoint,
imagePrivacyLevel,
touchPrivacyLevel,
textAndInputPrivacyLevel,
SessionReplayPrivacySettings(
imagePrivacyLevel = imagePrivacyLevel,
touchPrivacyLevel = touchPrivacyLevel,
textAndInputPrivacyLevel = textAndInputPrivacyLevel
),
promise
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ class DdSessionReplay(
implementation.enable(
replaySampleRate,
customEndpoint,
imagePrivacyLevel,
touchPrivacyLevel,
textAndInputPrivacyLevel,
SessionReplayPrivacySettings(
imagePrivacyLevel = imagePrivacyLevel,
touchPrivacyLevel = touchPrivacyLevel,
textAndInputPrivacyLevel = textAndInputPrivacyLevel
),
startRecordingImmediately,
promise
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import fr.xgouchet.elmyr.annotation.BoolForgery
import fr.xgouchet.elmyr.annotation.DoubleForgery
import fr.xgouchet.elmyr.annotation.StringForgery
import fr.xgouchet.elmyr.junit5.ForgeExtension
import java.util.Locale
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -57,6 +56,23 @@ internal class DdSessionReplayImplementationTest {
@Mock
lateinit var mockUiManagerModule: UIManagerModule

private val imagePrivacyMap = mapOf(
"MASK_ALL" to ImagePrivacy.MASK_ALL,
"MASK_NON_BUNDLED_ONLY" to ImagePrivacy.MASK_LARGE_ONLY,
"MASK_NONE" to ImagePrivacy.MASK_NONE
)

private val touchPrivacyMap = mapOf(
"SHOW" to TouchPrivacy.SHOW,
"HIDE" to TouchPrivacy.HIDE
)

private val inputPrivacyMap = mapOf(
"MASK_ALL" to TextAndInputPrivacy.MASK_ALL,
"MASK_ALL_INPUTS" to TextAndInputPrivacy.MASK_ALL_INPUTS,
"MASK_SENSITIVE_INPUTS" to TextAndInputPrivacy.MASK_SENSITIVE_INPUTS
)

@BeforeEach
fun `set up`() {
whenever(mockReactContext.getNativeModule(any<Class<NativeModule>>()))
Expand All @@ -71,51 +87,31 @@ internal class DdSessionReplayImplementationTest {
}

@Test
fun `M enable session replay W privacy = ALLOW`(
fun `M enable session replay W random privacy settings`(
@DoubleForgery(min = 0.0, max = 100.0) replaySampleRate: Double,
@StringForgery(regex = ".+") customEndpoint: String,
@BoolForgery startRecordingImmediately: Boolean
) {
testSessionReplayEnable(
"ALLOW",
replaySampleRate,
customEndpoint,
startRecordingImmediately
)
}
val imagePrivacy = imagePrivacyMap.keys.random()
val touchPrivacy = touchPrivacyMap.keys.random()
val textAndInputPrivacy = inputPrivacyMap.keys.random()

@Test
fun `M enable session replay W privacy = MASK`(
@DoubleForgery(min = 0.0, max = 100.0) replaySampleRate: Double,
@StringForgery(regex = ".+") customEndpoint: String,
@BoolForgery startRecordingImmediately: Boolean
) {
testSessionReplayEnable(
"MASK",
replaySampleRate,
customEndpoint,
startRecordingImmediately
)
}

@Test
fun `M enable session replay W privacy = MASK_USER_INPUT`(
@DoubleForgery(min = 0.0, max = 100.0) replaySampleRate: Double,
@StringForgery(regex = ".+") customEndpoint: String,
@BoolForgery startRecordingImmediately: Boolean
) {
testSessionReplayEnable(
"MASK_USER_INPUT",
replaySampleRate,
customEndpoint,
startRecordingImmediately
replaySampleRate = replaySampleRate,
customEndpoint = customEndpoint,
imagePrivacy = imagePrivacy,
touchPrivacy = touchPrivacy,
textAndInputPrivacy = textAndInputPrivacy,
startRecordingImmediately = startRecordingImmediately
)
}

private fun testSessionReplayEnable(
privacy: String,
replaySampleRate: Double,
customEndpoint: String,
imagePrivacy: String,
touchPrivacy: String,
textAndInputPrivacy: String,
startRecordingImmediately: Boolean
) {
// Given
Expand All @@ -124,8 +120,8 @@ internal class DdSessionReplayImplementationTest {
// When
testedSessionReplay.enable(
replaySampleRate,
privacy,
customEndpoint,
SessionReplayPrivacySettings(imagePrivacy, touchPrivacy, textAndInputPrivacy),
startRecordingImmediately,
mockPromise
)
Expand All @@ -135,47 +131,31 @@ internal class DdSessionReplayImplementationTest {
assertThat(sessionReplayConfigCaptor.firstValue)
.hasFieldEqualTo("sampleRate", replaySampleRate.toFloat())
.hasFieldEqualTo("customEndpointUrl", customEndpoint)

when (privacy.lowercase(Locale.US)) {
"mask_user_input" -> {
assertThat(sessionReplayConfigCaptor.firstValue)
.hasFieldEqualTo("textAndInputPrivacy", TextAndInputPrivacy.MASK_ALL_INPUTS)
.hasFieldEqualTo("imagePrivacy", ImagePrivacy.MASK_NONE)
.hasFieldEqualTo("touchPrivacy", TouchPrivacy.HIDE)
}
"allow" -> {
assertThat(sessionReplayConfigCaptor.firstValue)
.hasFieldEqualTo(
"textAndInputPrivacy",
TextAndInputPrivacy.MASK_SENSITIVE_INPUTS
)
.hasFieldEqualTo("imagePrivacy", ImagePrivacy.MASK_NONE)
.hasFieldEqualTo("touchPrivacy", TouchPrivacy.SHOW)
}
else -> {
assertThat(sessionReplayConfigCaptor.firstValue)
.hasFieldEqualTo("textAndInputPrivacy", TextAndInputPrivacy.MASK_ALL)
.hasFieldEqualTo("imagePrivacy", ImagePrivacy.MASK_ALL)
.hasFieldEqualTo("touchPrivacy", TouchPrivacy.HIDE)
}
}
.hasFieldEqualTo("textAndInputPrivacy", inputPrivacyMap[textAndInputPrivacy])
.hasFieldEqualTo("imagePrivacy", imagePrivacyMap[imagePrivacy])
.hasFieldEqualTo("touchPrivacy", touchPrivacyMap[touchPrivacy])
}

@Test
fun `M enable session replay without custom endpoint W empty string()`(
@DoubleForgery(min = 0.0, max = 100.0) replaySampleRate: Double,
// Not ALLOW nor MASK_USER_INPUT
@StringForgery(regex = "^/(?!ALLOW|MASK_USER_INPUT)([a-z0-9]+)$/i") privacy: String,
@BoolForgery startRecordingImmediately: Boolean
) {
// Given
val imagePrivacy = imagePrivacyMap.keys.random()
val touchPrivacy = touchPrivacyMap.keys.random()
val textAndInputPrivacy = inputPrivacyMap.keys.random()
val sessionReplayConfigCaptor = argumentCaptor<SessionReplayConfiguration>()

// When
testedSessionReplay.enable(
replaySampleRate,
privacy,
"",
SessionReplayPrivacySettings(
imagePrivacyLevel = imagePrivacy,
touchPrivacyLevel = touchPrivacy,
textAndInputPrivacyLevel = textAndInputPrivacy
),
startRecordingImmediately,
mockPromise
)
Expand Down
Loading

0 comments on commit 5fb15a2

Please sign in to comment.