Skip to content

Commit

Permalink
RUMM-2651 SR - skip new lines and spaces when obfuscating texts
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusc83 committed Oct 12, 2022
1 parent 946cc7e commit 754eefd
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 20 deletions.
1 change: 1 addition & 0 deletions detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,7 @@ datadog:
- "kotlin.Byte.toInt()"
- "kotlin.ByteArray.constructor(kotlin.Int)"
- "kotlin.Char.isLowerCase()"
- "kotlin.Char.isWhitespace()"
- "kotlin.Char.titlecase(java.util.Locale)"
- "kotlin.CharArray.constructor(kotlin.Int, kotlin.Function1)"
- "kotlin.Double.isNaN()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@ package com.datadog.android.sessionreplay.recorder.mapper
import android.widget.TextView

internal class MaskAllTextWireframeMapper(
viewWireframeMapper: ViewWireframeMapper = ViewWireframeMapper()
viewWireframeMapper: ViewWireframeMapper = ViewWireframeMapper(),
private val stringObfuscator: StringObfuscator = StringObfuscator()
) : TextWireframeMapper(viewWireframeMapper) {

override fun resolveTextValue(textView: TextView): String {
return String(CharArray(textView.text.length) { CHARACTER_MASK })
}

companion object {
private const val CHARACTER_MASK = 'x'
return stringObfuscator.obfuscate(textView.text.toString())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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.recorder.mapper

internal class StringObfuscator {
fun obfuscate(stringValue: String): String {
return String(
CharArray(stringValue.length) {
val character = stringValue[it]
if (character.isWhitespace()) {
character
} else {
CHARACTER_MASK
}
}
)
}

companion object {
private const val CHARACTER_MASK = 'x'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.datadog.android.sessionreplay.recorder.densityNormalized
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.annotation.StringForgery
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
Expand All @@ -25,6 +26,9 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes

lateinit var testedTextWireframeMapper: TextWireframeMapper

@StringForgery
lateinit var fakeText: String

@BeforeEach
fun `set up`() {
testedTextWireframeMapper = initTestedMapper()
Expand All @@ -44,7 +48,6 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes
// Given
val fakeFontSize = forge.aFloat(min = 0f)
val fakeStyleColor = forge.aStringMatching("#[0-9a-f]{6}ff")
val fakeText = forge.aString()
val fakeFontColor = fakeStyleColor
.substring(1)
.toLong(16)
Expand Down Expand Up @@ -81,7 +84,7 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes
) {
// Given
val mockTextView: TextView = forge.aMockView<TextView>().apply {
whenever(this.text).thenReturn(forge.aString())
whenever(this.text).thenReturn(fakeText)
whenever(this.typeface).thenReturn(mock())
whenever(this.textAlignment).thenReturn(fakeTextAlignment)
}
Expand All @@ -108,7 +111,6 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes
forge: Forge
) {
// Given
val fakeText = forge.aString()
val mockTextView: TextView = forge.aMockView<TextView>().apply {
whenever(this.text).thenReturn(fakeText)
whenever(this.typeface).thenReturn(mock())
Expand All @@ -133,7 +135,6 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes
@Test
fun `M resolve a TextWireframe W map() { TextView with textPadding }`(forge: Forge) {
// Given
val fakeText = forge.aString()
val fakeTextPaddingTop = forge.anInt()
val fakeTextPaddingBottom = forge.anInt()
val fakeTextPaddingStart = forge.anInt()
Expand Down Expand Up @@ -192,7 +193,7 @@ internal abstract class BaseTextViewWireframeMapperTest : BaseWireframeMapperTes
}
val mockTextView = forge.aMockView<TextView>().apply {
whenever(this.background).thenReturn(mockDrawable)
whenever(this.text).thenReturn(forge.aString())
whenever(this.text).thenReturn(fakeText)
whenever(this.typeface).thenReturn(mock())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@

package com.datadog.android.sessionreplay.recorder.mapper

import android.widget.Button
import android.widget.TextView
import com.datadog.android.sessionreplay.recorder.aMockView
import com.datadog.android.sessionreplay.utils.ForgeConfigurator
import com.datadog.tools.unit.extensions.ApiLevelExtension
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.annotation.StringForgery
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
import fr.xgouchet.elmyr.junit5.ForgeExtension
import org.assertj.core.api.Assertions.assertThat
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
Expand All @@ -33,34 +34,43 @@ import org.mockito.quality.Strictness
@ForgeConfiguration(ForgeConfigurator::class)
internal class MaskAllTextViewWireframeMapperTest : BaseTextViewWireframeMapperTest() {

@Mock
lateinit var mockStringObfuscator: StringObfuscator

@StringForgery
lateinit var fakeMaskedStringValue: String

// region super

override fun initTestedMapper(): TextWireframeMapper {
return MaskAllTextWireframeMapper()
whenever(mockStringObfuscator.obfuscate(fakeText)).thenReturn(fakeMaskedStringValue)
return MaskAllTextWireframeMapper(stringObfuscator = mockStringObfuscator)
}

override fun resolveTextValue(textView: TextView): String {
return String(CharArray(textView.text.length) { 'x' })
return fakeMaskedStringValue
}

// endregion

// region Unit tests

@Test
fun `M resolve a TextWireframe with masked text W map() { TextView with text }`(forge: Forge) {
fun `M resolve a TextWireframe with masked text W map(){TextView}`(
forge: Forge
) {
// Given
val fakeText = forge.aString { 'x' }
val mockButton: TextView = forge.aMockView<Button>().apply {
whenever(this.text).thenReturn(forge.aString(fakeText.length))
whenever(mockStringObfuscator.obfuscate(fakeText)).thenReturn(fakeMaskedStringValue)
val mockTextView: TextView = forge.aMockView<TextView>().apply {
whenever(this.text).thenReturn(fakeText)
whenever(this.typeface).thenReturn(mock())
}

// When
val textWireframe = testedTextWireframeMapper.map(mockButton, fakePixelDensity)
val textWireframe = testedTextWireframeMapper.map(mockTextView, fakePixelDensity)

// Then
val expectedWireframe = mockButton.toTextWireframe().copy(text = fakeText)
val expectedWireframe = mockTextView.toTextWireframe().copy(text = fakeMaskedStringValue)
assertThat(textWireframe).isEqualTo(expectedWireframe)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* 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.recorder.mapper

import com.datadog.android.sessionreplay.utils.ForgeConfigurator
import com.datadog.tools.unit.extensions.ApiLevelExtension
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),
ExtendWith(ApiLevelExtension::class)
)
@MockitoSettings(strictness = Strictness.LENIENT)
@ForgeConfiguration(ForgeConfigurator::class)
internal class StringObfuscatorTest {

lateinit var testedObfuscator: StringObfuscator

@BeforeEach
fun `set up`() {
testedObfuscator = StringObfuscator()
}

@Test
fun `M mask String W maskString(){string with newline}`(
forge: Forge
) {
// Given
val fakeExpectedChunk1 = forge.aString(size = forge.anInt(1, max = 10)) { '\n' }
val fakeExpectedChunk2 = forge.aString { 'x' }
val fakeExpectedChunk3 = forge.aString(size = forge.anInt(1, max = 10)) { '\n' }
val fakeExpectedChunk4 = forge.aString { 'x' }
val fakeExpectedChunk5 = forge.aString(size = forge.anInt(1, max = 10)) { '\n' }
val n = forge.anInt(min = 1, max = 10)
val fakeExpectedText = (
fakeExpectedChunk1 +
fakeExpectedChunk2 +
fakeExpectedChunk3 +
fakeExpectedChunk4 +
fakeExpectedChunk5
).times(n)
val fakeText = (
fakeExpectedChunk1 +
forge.aString(fakeExpectedChunk2.length) { forge.anAlphaNumericalChar() } +
fakeExpectedChunk3 +
forge.aString(fakeExpectedChunk4.length) { forge.anAlphaNumericalChar() } +
fakeExpectedChunk5
).times(n)

// When
val obfuscatedText = testedObfuscator.obfuscate(fakeText)

// Then
assertThat(obfuscatedText).isEqualTo(fakeExpectedText)
}

@Test
fun `M mask String W maskString(){string with carriage return character}`(
forge: Forge
) {
// Given
val fakeExpectedChunk1 = forge.aString(size = forge.anInt(1, max = 10)) { '\r' }
val fakeExpectedChunk2 = forge.aString { 'x' }
val fakeExpectedChunk3 = forge.aString(size = forge.anInt(1, max = 10)) { '\r' }
val fakeExpectedChunk4 = forge.aString { 'x' }
val fakeExpectedChunk5 = forge.aString(size = forge.anInt(1, max = 10)) { '\r' }
val n = forge.anInt(min = 1, max = 10)
val fakeExpectedText = (
fakeExpectedChunk1 +
fakeExpectedChunk2 +
fakeExpectedChunk3 +
fakeExpectedChunk4 +
fakeExpectedChunk5
).times(n)
val fakeText = (
fakeExpectedChunk1 +
forge.aString(fakeExpectedChunk2.length) { forge.anAlphaNumericalChar() } +
fakeExpectedChunk3 +
forge.aString(fakeExpectedChunk4.length) { forge.anAlphaNumericalChar() } +
fakeExpectedChunk5
).times(n)

// When
val obfuscatedText = testedObfuscator.obfuscate(fakeText)

// Then
assertThat(obfuscatedText).isEqualTo(fakeExpectedText)
}

@Test
fun `M mask String W maskString(){string with whitespace character}`(
forge: Forge
) {
// Given
val fakeExpectedChunk1 = forge.aWhitespaceString()
val fakeExpectedChunk2 = forge.aString { 'x' }
val fakeExpectedChunk3 = forge.aWhitespaceString()
val fakeExpectedChunk4 = forge.aString { 'x' }
val fakeExpectedChunk5 = forge.aWhitespaceString()
val n = forge.anInt(min = 1, max = 10)
val fakeExpectedText = (
fakeExpectedChunk1 +
fakeExpectedChunk2 +
fakeExpectedChunk3 +
fakeExpectedChunk4 +
fakeExpectedChunk5
).times(n)
val fakeText = (
fakeExpectedChunk1 +
forge.aString(fakeExpectedChunk2.length) { forge.anAlphaNumericalChar() } +
fakeExpectedChunk3 +
forge.aString(fakeExpectedChunk4.length) { forge.anAlphaNumericalChar() } +
fakeExpectedChunk5
).times(n)

// When
val obfuscatedText = testedObfuscator.obfuscate(fakeText)

// Then
assertThat(obfuscatedText).isEqualTo(fakeExpectedText)
}

private fun String.times(n: Int): String {
val builder = StringBuilder(n * length)
repeat(n) {
builder.append(this)
}
return builder.toString()
}
}

0 comments on commit 754eefd

Please sign in to comment.