Skip to content

Commit

Permalink
Additional tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmos committed Feb 14, 2024
1 parent 3bcfe49 commit de66c39
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.datadog.android.api.InternalLogger
import com.datadog.android.api.storage.RawBatchEvent
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.APPLICATION_ID_INTERNAL_KEY
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.APPLICATION_ID_OUTER_KEY
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.APPLICATION_ID_RESOURCE_KEY
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.FILENAME_KEY
import com.datadog.android.sessionreplay.internal.utils.MiscUtils
import com.google.gson.JsonObject
Expand Down Expand Up @@ -91,7 +92,7 @@ internal class ResourceRequestBodyFactory(
val applicationId = MiscUtils.safeGetStringFromJsonObject(
internalLogger,
resourceMetadata,
APPLICATION_ID_OUTER_KEY
APPLICATION_ID_RESOURCE_KEY
)
val filename = MiscUtils.safeGetStringFromJsonObject(
internalLogger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal data class EnrichedResource(
}

internal companion object {
internal const val APPLICATION_ID_RESOURCE_KEY = "applicationId"
internal const val APPLICATION_ID_OUTER_KEY = "application"
internal const val APPLICATION_ID_INTERNAL_KEY = "id"
internal const val FILENAME_KEY = "filename"
Expand All @@ -42,7 +43,7 @@ internal fun EnrichedResource.asBinaryMetadata(): ByteArray {
val applicationId = this.applicationId
val filename = this.filename
val jsonObject = JsonObject()
jsonObject.addProperty(EnrichedResource.APPLICATION_ID_OUTER_KEY, applicationId)
jsonObject.addProperty(EnrichedResource.APPLICATION_ID_RESOURCE_KEY, applicationId)
jsonObject.addProperty(EnrichedResource.FILENAME_KEY, filename)
return jsonObject.toString().toByteArray(Charsets.UTF_8)
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ internal class Base64Serializer private constructor(
var base64String = "".toByteArray(Charsets.UTF_8)
var cacheData = CacheData(base64String, resourceId?.toByteArray(Charsets.UTF_8))

if (shouldCacheBitmap) {
bitmapPool?.put(bitmap)
}

if (RESOURCE_ENDPOINT_FEATURE_FLAG) {
if (resourceId == null) {
// resourceId is mandatory for resource endpoint
Expand All @@ -160,9 +164,7 @@ internal class Base64Serializer private constructor(
}
} else {
base64String = convertBitmapToBase64(
byteArray = byteArray,
bitmap = bitmap,
shouldCacheBitmap = shouldCacheBitmap
byteArray = byteArray
).toByteArray(Charsets.UTF_8)
cacheData = CacheData(base64String, resourceId?.toByteArray(Charsets.UTF_8))
}
Expand Down Expand Up @@ -201,17 +203,9 @@ internal class Base64Serializer private constructor(

@WorkerThread
private fun convertBitmapToBase64(
byteArray: ByteArray,
bitmap: Bitmap,
shouldCacheBitmap: Boolean
byteArray: ByteArray
): String {
val base64Result = base64Utils.serializeToBase64String(byteArray)

if (shouldCacheBitmap) {
bitmapPool?.put(bitmap)
}

return base64Result
return base64Utils.serializeToBase64String(byteArray)
}

private fun tryToDrawNewBitmap(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ internal class DrawableUtils(
fun onFailure()
}

private fun getBitmapSizeLimit(requestedSizeInBytes: Int?): Int {
@VisibleForTesting
internal fun getBitmapSizeLimit(requestedSizeInBytes: Int?): Int {
if (requestedSizeInBytes != null) {
return requestedSizeInBytes
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import com.datadog.android.sessionreplay.internal.storage.NoOpResourcesWriter
import com.datadog.android.sessionreplay.internal.storage.RecordWriter
import com.datadog.android.sessionreplay.internal.storage.ResourcesWriter
import com.datadog.android.sessionreplay.internal.storage.SessionReplayRecordWriter
import com.datadog.android.sessionreplay.internal.storage.SessionReplayResourcesWriter
import com.datadog.android.sessionreplay.utils.config.ApplicationContextTestConfiguration
import com.datadog.android.sessionreplay.utils.verifyLog
import com.datadog.tools.unit.annotations.TestConfigurationsProvider
Expand Down Expand Up @@ -184,13 +183,10 @@ internal class SessionReplayFeatureTest {

// Then
verify(lambda).invoke(captor.capture(), any(), any())
if (ResourcesFeature.RESOURCE_ENDPOINT_FEATURE_FLAG) {
assertThat(captor.firstValue)
.isInstanceOf(SessionReplayResourcesWriter::class.java)
} else {
assertThat(captor.firstValue)
.isInstanceOf(NoOpResourcesWriter::class.java)
}
// always a noop, since even when the resource feature flag is enabled
// it only changes to the implementation datawriter after onInitialise
assertThat(captor.firstValue)
.isInstanceOf(NoOpResourcesWriter::class.java)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.android.utils.verifyLog
import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.annotation.Forgery
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
Expand Down Expand Up @@ -99,6 +100,9 @@ internal class RecordedDataQueueHandlerTest {
@Forgery
lateinit var fakeTouchEventItem: TouchEventRecordedDataQueueItem

@Forgery
lateinit var fakeResourceItem: ResourceRecordedDataQueueItem

@Spy
private lateinit var fakeRecordedDataQueue: ConcurrentLinkedQueue<RecordedDataQueueItem>

Expand All @@ -108,6 +112,7 @@ internal class RecordedDataQueueHandlerTest {

private val snapshotItemCaptor = argumentCaptor<SnapshotRecordedDataQueueItem>()
private val touchEventItemCaptor = argumentCaptor<TouchEventRecordedDataQueueItem>()
private val resourceEventItemCaptor = argumentCaptor<ResourceRecordedDataQueueItem>()

@BeforeEach
fun setup(forge: Forge) {
Expand Down Expand Up @@ -408,6 +413,38 @@ internal class RecordedDataQueueHandlerTest {
verifyNoMoreInteractions(mockProcessor)
}

@Test
fun `M remove item from queue W tryToConsumeItems() { invalid resource item }`() {
// Given
val spy = spy(fakeResourceItem)

doReturn(false).whenever(spy).isValid()
testedHandler.recordedDataQueue.offer(spy)

val spyTimestamp = spy.recordedQueuedItemContext.timestamp
whenever(mockTimeProvider.getDeviceTimestamp())
.thenReturn(spyTimestamp)

// When
testedHandler.tryToConsumeItems()
spyExecutorService.shutdown()
spyExecutorService.awaitTermination(1, TimeUnit.SECONDS)

// Then
assertThat(testedHandler.recordedDataQueue).isEmpty()
val expectedLogMessage = ITEM_DROPPED_FROM_QUEUE_ERROR_MESSAGE
.format(Locale.US, false)
mockInternalLogger.verifyLog(
InternalLogger.Level.WARN,
listOf(
InternalLogger.Target.MAINTAINER,
InternalLogger.Target.TELEMETRY
),
expectedLogMessage
)
verifyNoMoreInteractions(mockProcessor)
}

@Test
fun `M do nothing W tryToConsumeItems() { snapshot item not ready }`() {
// Given
Expand Down Expand Up @@ -472,6 +509,37 @@ internal class RecordedDataQueueHandlerTest {
assertThat(touchEventItemCaptor.firstValue.touchData).isEqualTo(fakeTouchData)
}

@Test
fun `M call processor W tryToConsumeItems() { valid Resource Event item }`(
@StringForgery fakeIdentifier: String,
@StringForgery fakeApplicationId: String,
@StringForgery fakePayload: String
) {
// Given
val item = testedHandler.addResourceItem(
fakeIdentifier,
fakeApplicationId,
fakePayload.toByteArray()
) ?: fail("item is null")

whenever(mockTimeProvider.getDeviceTimestamp())
.thenReturn(item.recordedQueuedItemContext.timestamp)

// When
testedHandler.tryToConsumeItems()
spyExecutorService.shutdown()
spyExecutorService.awaitTermination(1, TimeUnit.SECONDS)

// Then
verify(mockProcessor).processResources(resourceEventItemCaptor.capture())

assertThat(resourceEventItemCaptor.firstValue.recordedQueuedItemContext)
.isEqualTo(item.recordedQueuedItemContext)
assertThat(resourceEventItemCaptor.firstValue.identifier).isEqualTo(fakeIdentifier)
assertThat(resourceEventItemCaptor.firstValue.applicationId).isEqualTo(fakeApplicationId)
assertThat(resourceEventItemCaptor.firstValue.resourceData).isEqualTo(fakePayload.toByteArray())
}

@Test
fun `M consume items in the correct order W tryToConsumeItems() { spawn multiple threads }`() {
// Given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.datadog.android.sessionreplay.internal.net.ResourceRequestBodyFactory
import com.datadog.android.sessionreplay.internal.net.ResourceRequestBodyFactory.Companion.TYPE_RESOURCE
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.APPLICATION_ID_INTERNAL_KEY
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.APPLICATION_ID_OUTER_KEY
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.APPLICATION_ID_RESOURCE_KEY
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.FILENAME_KEY
import com.datadog.android.utils.verifyLog
import com.google.gson.JsonObject
Expand Down Expand Up @@ -67,7 +68,7 @@ internal class ResourceRequestBodyFactoryTest {
@BeforeEach
fun `set up`() {
fakeMetaData = JsonObject()
fakeMetaData.addProperty(APPLICATION_ID_OUTER_KEY, fakeApplicationId)
fakeMetaData.addProperty(APPLICATION_ID_RESOURCE_KEY, fakeApplicationId)
fakeMetaData.addProperty(FILENAME_KEY, fakeFilename)

testedRequestBodyFactory = ResourceRequestBodyFactory(mockInternalLogger)
Expand Down Expand Up @@ -139,7 +140,7 @@ internal class ResourceRequestBodyFactoryTest {
val missingApplicationIdData = fakeMetaData.deepCopy()
val missingFilenameData = fakeMetaData.deepCopy()

missingApplicationIdData.remove(APPLICATION_ID_OUTER_KEY)
missingApplicationIdData.remove(APPLICATION_ID_RESOURCE_KEY)
val missingApplicationIdBatchEvent = RawBatchEvent(
data = fakeImageRepresentation.toByteArray(),
metadata = missingApplicationIdData.toString().toByteArray(Charsets.UTF_8)
Expand Down Expand Up @@ -173,7 +174,7 @@ internal class ResourceRequestBodyFactoryTest {
}

@Test
fun `M throw exception and return largest group W create() { multiple applicationIds }`(forge: Forge) {
fun `M throw exception and return last group W create() { multiple applicationIds }`(forge: Forge) {
// Given
val fakeSecondApplicationId = forge.anAsciiString()
val fakeSecondFilename = forge.anAsciiString()
Expand All @@ -182,8 +183,8 @@ internal class ResourceRequestBodyFactoryTest {
metadata = fakeMetaData.toString().toByteArray(Charsets.UTF_8)
)

fakeMetaData.remove(APPLICATION_ID_OUTER_KEY)
fakeMetaData.addProperty(APPLICATION_ID_OUTER_KEY, fakeSecondApplicationId)
fakeMetaData.remove(APPLICATION_ID_RESOURCE_KEY)
fakeMetaData.addProperty(APPLICATION_ID_RESOURCE_KEY, fakeSecondApplicationId)
fakeMetaData.remove(FILENAME_KEY)
fakeMetaData.addProperty(FILENAME_KEY, fakeSecondFilename)

Expand Down Expand Up @@ -250,7 +251,7 @@ internal class ResourceRequestBodyFactoryTest {
private fun generateValidRawBatchEvent(forge: Forge): RawBatchEvent {
val fakeEvent = forge.getForgery<RawBatchEvent>()
val fakeMetadata = JsonObject()
fakeMetadata.addProperty(APPLICATION_ID_OUTER_KEY, forge.getForgery<UUID>().toString())
fakeMetadata.addProperty(APPLICATION_ID_RESOURCE_KEY, forge.getForgery<UUID>().toString())
fakeMetadata.addProperty(FILENAME_KEY, forge.getForgery<UUID>().toString())
return fakeEvent.copy(
metadata = fakeMetadata.toString().toByteArray(Charsets.UTF_8)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@

package com.datadog.android.sessionreplay.internal.processor

import com.datadog.android.api.InternalLogger
import com.datadog.android.sessionreplay.forge.ForgeConfigurator
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.APPLICATION_ID_RESOURCE_KEY
import com.datadog.android.sessionreplay.internal.processor.EnrichedResource.Companion.FILENAME_KEY
import com.datadog.android.sessionreplay.internal.utils.MiscUtils.safeDeserializeToJsonObject
import com.datadog.android.sessionreplay.internal.utils.MiscUtils.safeGetStringFromJsonObject
import com.datadog.tools.unit.ObjectTest
import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.annotation.Forgery
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 @@ -24,6 +33,10 @@ import org.mockito.quality.Strictness
@MockitoSettings(strictness = Strictness.LENIENT)
@ForgeConfiguration(ForgeConfigurator::class)
internal class EnrichedResourceTest : ObjectTest<EnrichedResource>() {

@Mock
lateinit var mockInternalLogger: InternalLogger

override fun createInstance(forge: Forge): EnrichedResource {
return forge.getForgery()
}
Expand All @@ -39,4 +52,39 @@ internal class EnrichedResourceTest : ObjectTest<EnrichedResource>() {
override fun createUnequalInstance(source: EnrichedResource, forge: Forge): EnrichedResource {
return forge.getForgery()
}

@Test
fun `M return valid binary metadata W asBinaryMetadat`(
@Forgery enrichedResource: EnrichedResource
) {
// Given
val expectedIdentifier = enrichedResource.filename
val expectedApplicationId = enrichedResource.applicationId

// When
val metadata = enrichedResource.asBinaryMetadata()

// Then
val deserializedData = safeDeserializeToJsonObject(
mockInternalLogger,
metadata
)

requireNotNull(deserializedData)

val actualIdentifier = safeGetStringFromJsonObject(
mockInternalLogger,
deserializedData,
FILENAME_KEY
)

val actualApplicationId = safeGetStringFromJsonObject(
mockInternalLogger,
deserializedData,
APPLICATION_ID_RESOURCE_KEY
)

assertThat(actualIdentifier).isEqualTo(expectedIdentifier)
assertThat(actualApplicationId).isEqualTo(expectedApplicationId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1230,7 +1230,7 @@ internal class RecordedDataProcessorTest {
assertThat(capturedResource.resource).isEqualTo(fakeByteArray)
val jsonString = capturedResource.asBinaryMetadata().toString(Charsets.UTF_8)
val metadataJson = JsonParser.parseString(jsonString).asJsonObject
val itemApplicationId = metadataJson.get(EnrichedResource.APPLICATION_ID_OUTER_KEY).asString
val itemApplicationId = metadataJson.get(EnrichedResource.APPLICATION_ID_RESOURCE_KEY).asString
val itemFilename = metadataJson.get(EnrichedResource.FILENAME_KEY).asString
assertThat(itemApplicationId).isEqualTo(fakeRumContext.applicationId)
assertThat(itemFilename).isEqualTo(fakeIdentifier)
Expand Down

0 comments on commit de66c39

Please sign in to comment.