diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f8743c8f2..15728d6451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 2.2.0 / 2023-10-02 +# 2.2.0 / 2023-10-04 * [FEATURE] Session Replay: Serialize TextViews/Buttons to base64. See [#1592](https://github.com/DataDog/dd-sdk-android/pull/1592) * [FEATURE] WebView Tracking: Add sampler to `WebViewLogEventConsumer`. See [#1629](https://github.com/DataDog/dd-sdk-android/pull/1629) @@ -32,8 +32,8 @@ * [IMPROVEMENT] Trace: Use `networkInfoEnabled` to serialize or not network info in spans. See [#1637](https://github.com/DataDog/dd-sdk-android/pull/1637) * [IMPROVEMENT] Telemetry: Add more information into the batch telemetry. See [#1641](https://github.com/DataDog/dd-sdk-android/pull/1641) * [IMPROVEMENT] Session Replay: Implement heuristic image classification. See [#1640](https://github.com/DataDog/dd-sdk-android/pull/1640) -* [IMPROVEMENT] Session Replay: Implement Base64 for `ImageViews`. See [#1644](https://github.com/DataDog/dd-sdk-android/pull/1644) * [IMPROVEMENT] Global: `DataUploadWorker` is scheduled every time and on non-roaming network. See [#1647](https://github.com/DataDog/dd-sdk-android/pull/1647) +* [IMPROVEMENT] RUM: Use enum for HTTP method parameter of `RumMonitor#startResource API`. See [#1653](https://github.com/DataDog/dd-sdk-android/pull/1653) * [MAINTENANCE] Align the Base64 feature branch with develop. See [#1594](https://github.com/DataDog/dd-sdk-android/pull/1594) * [MAINTENANCE] Integrate latest changes from develop into base64 feature. See [#1599](https://github.com/DataDog/dd-sdk-android/pull/1599) * [MAINTENANCE] Base64 feature branch integration. See [#1597](https://github.com/DataDog/dd-sdk-android/pull/1597) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/receiver/ThreadSafeReceiver.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/receiver/ThreadSafeReceiver.kt index a26dd30a2e..c054b0309d 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/receiver/ThreadSafeReceiver.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/receiver/ThreadSafeReceiver.kt @@ -6,21 +6,28 @@ package com.datadog.android.core.internal.receiver +import android.annotation.SuppressLint import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.os.Build import java.util.concurrent.atomic.AtomicBoolean internal abstract class ThreadSafeReceiver : BroadcastReceiver() { val isRegistered = AtomicBoolean(false) + @SuppressLint("UnspecifiedRegisterReceiverFlag") fun registerReceiver( context: Context, filter: IntentFilter ): Intent? { - val intent = context.registerReceiver(this, filter) + val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED) + } else { + context.registerReceiver(this, filter) + } isRegistered.set(true) return intent } diff --git a/detekt_custom.yml b/detekt_custom.yml index f59b2b5933..ec99d84d0f 100644 --- a/detekt_custom.yml +++ b/detekt_custom.yml @@ -286,6 +286,7 @@ datadog: - "android.content.Context.getSystemService(kotlin.String)" - "android.content.Context.registerComponentCallbacks(android.content.ComponentCallbacks)" - "android.content.Context.registerReceiver(android.content.BroadcastReceiver?, android.content.IntentFilter)" + - "android.content.Context.registerReceiver(android.content.BroadcastReceiver?, android.content.IntentFilter, kotlin.Int)" - "android.content.Context.unregisterReceiver(android.content.BroadcastReceiver)" - "android.content.Intent.getBooleanExtra(kotlin.String, kotlin.Boolean)" - "android.content.Intent.getIntExtra(kotlin.String, kotlin.Int)" diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index e45e68974f..9b16b6482d 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -93,7 +93,8 @@ interface com.datadog.android.rum.RumMonitor fun addAction(RumActionType, String, Map) fun startAction(RumActionType, String, Map) fun stopAction(RumActionType, String, Map = emptyMap()) - fun startResource(String, String, String, Map = emptyMap()) + DEPRECATED fun startResource(String, String, String, Map = emptyMap()) + fun startResource(String, RumResourceMethod, String, Map = emptyMap()) fun stopResource(String, Int?, Long?, RumResourceKind, Map) fun stopResourceWithError(String, Int?, String, RumErrorSource, Throwable, Map = emptyMap()) fun stopResourceWithError(String, Int?, String, RumErrorSource, String, String?, Map = emptyMap()) @@ -130,6 +131,13 @@ enum com.datadog.android.rum.RumResourceKind - OTHER companion object fun fromMimeType(String): RumResourceKind +enum com.datadog.android.rum.RumResourceMethod + - POST + - GET + - HEAD + - PUT + - DELETE + - PATCH interface com.datadog.android.rum.RumSessionListener fun onSessionStarted(String, Boolean) class com.datadog.android.rum._RumInternalProxy diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 46b5c0667b..ab7c7df157 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -144,6 +144,7 @@ public abstract interface class com/datadog/android/rum/RumMonitor { public abstract fun removeAttribute (Ljava/lang/String;)V public abstract fun setDebug (Z)V public abstract fun startAction (Lcom/datadog/android/rum/RumActionType;Ljava/lang/String;Ljava/util/Map;)V + public abstract fun startResource (Ljava/lang/String;Lcom/datadog/android/rum/RumResourceMethod;Ljava/lang/String;Ljava/util/Map;)V public abstract fun startResource (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V public abstract fun startView (Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;)V public abstract fun stopAction (Lcom/datadog/android/rum/RumActionType;Ljava/lang/String;Ljava/util/Map;)V @@ -155,6 +156,7 @@ public abstract interface class com/datadog/android/rum/RumMonitor { } public final class com/datadog/android/rum/RumMonitor$DefaultImpls { + public static synthetic fun startResource$default (Lcom/datadog/android/rum/RumMonitor;Ljava/lang/String;Lcom/datadog/android/rum/RumResourceMethod;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V public static synthetic fun startResource$default (Lcom/datadog/android/rum/RumMonitor;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V public static synthetic fun startView$default (Lcom/datadog/android/rum/RumMonitor;Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V public static synthetic fun stopAction$default (Lcom/datadog/android/rum/RumMonitor;Lcom/datadog/android/rum/RumActionType;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V @@ -197,6 +199,17 @@ public final class com/datadog/android/rum/RumResourceKind$Companion { public final fun fromMimeType (Ljava/lang/String;)Lcom/datadog/android/rum/RumResourceKind; } +public final class com/datadog/android/rum/RumResourceMethod : java/lang/Enum { + public static final field DELETE Lcom/datadog/android/rum/RumResourceMethod; + public static final field GET Lcom/datadog/android/rum/RumResourceMethod; + public static final field HEAD Lcom/datadog/android/rum/RumResourceMethod; + public static final field PATCH Lcom/datadog/android/rum/RumResourceMethod; + public static final field POST Lcom/datadog/android/rum/RumResourceMethod; + public static final field PUT Lcom/datadog/android/rum/RumResourceMethod; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/RumResourceMethod; + public static fun values ()[Lcom/datadog/android/rum/RumResourceMethod; +} + public abstract interface class com/datadog/android/rum/RumSessionListener { public abstract fun onSessionStarted (Ljava/lang/String;Z)V } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt index ce9dae6648..ffa6abd682 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt @@ -111,6 +111,10 @@ interface RumMonitor { * @see [stopResource] * @see [stopResourceWithError] */ + @Deprecated( + "This method is deprecated and will be removed in the future versions." + + " Use `startResource` method which takes `RumHttpMethod` as `method` parameter instead." + ) fun startResource( key: String, method: String, @@ -118,6 +122,24 @@ interface RumMonitor { attributes: Map = emptyMap() ) + /** + * Notify that a new Resource is being loaded, linked with the [key] instance. + * @param key the instance that represents the resource being loaded (usually your + * request or network call instance). + * @param method the method used to load the resource (E.g., for network: "GET" or "POST") + * @param url the url or local path of the resource being loaded + * @param attributes additional custom attributes to attach to the resource. Attributes can be + * nested up to 9 levels deep. Keys using more than 9 levels will be sanitized by SDK. + * @see [stopResource] + * @see [stopResourceWithError] + */ + fun startResource( + key: String, + method: RumResourceMethod, + url: String, + attributes: Map = emptyMap() + ) + /** * Stops a previously started Resource, linked with the [key] instance. * @param key the instance that represents the active view (usually your diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumResourceMethod.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumResourceMethod.kt new file mode 100644 index 0000000000..07a96e1cd2 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumResourceMethod.kt @@ -0,0 +1,43 @@ +/* + * 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 + +/** + * Describes the type of a method associated with resource call. + * @see [RumMonitor] + */ +enum class RumResourceMethod { + /** + * POST Method. + */ + POST, + + /** + * GET Method. + */ + GET, + + /** + * HEAD Method. + */ + HEAD, + + /** + * PUT Method. + */ + PUT, + + /** + * DELETE Method. + */ + DELETE, + + /** + * PATCH Method. + */ + PATCH +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt index 66acb47952..3c418fafc5 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt @@ -13,6 +13,7 @@ import com.datadog.android.api.context.NetworkInfo import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.domain.event.ResourceTiming import com.datadog.android.rum.model.ActionEvent @@ -22,31 +23,25 @@ import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.model.ViewEvent import java.util.Locale -internal fun String.toMethod(internalLogger: InternalLogger): ResourceEvent.Method { - return try { - ResourceEvent.Method.valueOf(this.uppercase(Locale.US)) - } catch (e: IllegalArgumentException) { - internalLogger.log( - InternalLogger.Level.ERROR, - listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - { "Unable to convert [$this] to a valid http method" }, - e - ) - ResourceEvent.Method.GET +internal fun RumResourceMethod.toResourceMethod(): ResourceEvent.Method { + return when (this) { + RumResourceMethod.GET -> ResourceEvent.Method.GET + RumResourceMethod.POST -> ResourceEvent.Method.POST + RumResourceMethod.HEAD -> ResourceEvent.Method.HEAD + RumResourceMethod.PUT -> ResourceEvent.Method.PUT + RumResourceMethod.DELETE -> ResourceEvent.Method.DELETE + RumResourceMethod.PATCH -> ResourceEvent.Method.PATCH } } -internal fun String.toErrorMethod(internalLogger: InternalLogger): ErrorEvent.Method { - return try { - ErrorEvent.Method.valueOf(this.uppercase(Locale.US)) - } catch (e: IllegalArgumentException) { - internalLogger.log( - InternalLogger.Level.ERROR, - listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - { "Unable to convert [$this] to a valid http method" }, - e - ) - ErrorEvent.Method.GET +internal fun RumResourceMethod.toErrorMethod(): ErrorEvent.Method { + return when (this) { + RumResourceMethod.GET -> ErrorEvent.Method.GET + RumResourceMethod.POST -> ErrorEvent.Method.POST + RumResourceMethod.HEAD -> ErrorEvent.Method.HEAD + RumResourceMethod.PUT -> ErrorEvent.Method.PUT + RumResourceMethod.DELETE -> ErrorEvent.Method.DELETE + RumResourceMethod.PATCH -> ErrorEvent.Method.PATCH } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt index fb6ed56339..021732b939 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt @@ -10,6 +10,7 @@ import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumPerformanceMetric import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.event.ResourceTiming @@ -51,7 +52,7 @@ internal sealed class RumRawEvent { internal data class StartResource( val key: String, val url: String, - val method: String, + val method: RumResourceMethod, val attributes: Map, override val eventTime: Time = Time() ) : RumRawEvent() diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt index 930718e5ae..4574fa7cec 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt @@ -17,6 +17,7 @@ import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time @@ -34,7 +35,7 @@ internal class RumResourceScope( internal val parentScope: RumScope, internal val sdkCore: InternalSdkCore, internal val url: String, - internal val method: String, + internal val method: RumResourceMethod, internal val key: String, eventTime: Time, initialAttributes: Map, @@ -201,7 +202,7 @@ internal class RumResourceScope( type = kind.toSchemaType(), url = url, duration = duration, - method = method.toMethod(sdkCore.internalLogger), + method = method.toResourceMethod(), statusCode = statusCode, size = size, dns = finalTiming?.dns(), @@ -323,7 +324,7 @@ internal class RumResourceScope( isCrash = false, resource = ErrorEvent.Resource( url = url, - method = method.toErrorMethod(sdkCore.internalLogger), + method = method.toErrorMethod(), statusCode = statusCode ?: 0, provider = resolveErrorProvider() ), diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index f4da27f870..54db148f0b 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -20,6 +20,7 @@ import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumMonitor import com.datadog.android.rum.RumPerformanceMetric import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum._RumInternalProxy import com.datadog.android.rum.internal.CombinedRumSessionListener @@ -161,11 +162,46 @@ internal class DatadogRumMonitor( ) } + @Deprecated( + "This method is deprecated and will be removed in the future versions." + + " Use `startResource` method which takes `RumHttpMethod` as `method` parameter instead." + ) override fun startResource( key: String, method: String, url: String, attributes: Map + ) { + // enum value names may be changed if obfuscation is aggressive + val rumResourceMethod = when (method.uppercase(Locale.US)) { + "POST" -> RumResourceMethod.POST + "GET" -> RumResourceMethod.GET + "HEAD" -> RumResourceMethod.HEAD + "PUT" -> RumResourceMethod.PUT + "DELETE" -> RumResourceMethod.DELETE + "PATCH" -> RumResourceMethod.PATCH + else -> { + sdkCore.internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { + "Unsupported HTTP method %s reported, using GET instead".format( + Locale.US, + method + ) + } + ) + RumResourceMethod.GET + } + } + startResource(key, rumResourceMethod, url, attributes) + } + + override fun startResource( + key: String, + method: RumResourceMethod, + url: String, + attributes: Map ) { val eventTime = getEventTime(attributes) handleEvent( diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt index 2dddcf3406..85d4eda80a 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt @@ -11,6 +11,7 @@ import com.datadog.android.api.SdkCore import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.domain.event.ResourceTiming import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor import java.io.InputStream @@ -176,7 +177,7 @@ class RumResourceInputStream @JvmOverloads constructor( // endregion internal companion object { - internal const val METHOD: String = "GET" + internal val METHOD: RumResourceMethod = RumResourceMethod.GET internal const val ERROR_CLOSE = "Error closing input stream" internal const val ERROR_MARK = "Error marking input stream" diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt index 563644fc13..9a6b02b8e6 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt @@ -9,8 +9,10 @@ package com.datadog.android.rum.assertj import com.datadog.android.api.context.NetworkInfo import com.datadog.android.api.context.UserInfo import com.datadog.android.rum.RumErrorSource +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.scope.isConnected +import com.datadog.android.rum.internal.domain.scope.toErrorMethod import com.datadog.android.rum.internal.domain.scope.toSchemaSource import com.datadog.android.rum.model.ErrorEvent import org.assertj.core.api.AbstractObjectAssert @@ -76,7 +78,7 @@ internal class ErrorEventAssert(actual: ErrorEvent) : fun hasResource( expectedUrl: String, - expectedMethod: String, + expectedMethod: RumResourceMethod, expectedStatusCode: Long ): ErrorEventAssert { assertThat(actual.error.resource?.url) @@ -90,7 +92,7 @@ internal class ErrorEventAssert(actual: ErrorEvent) : "Expected event data to have error.resource.method $expectedMethod " + "but was ${actual.error.resource?.method}" ) - .isEqualTo(ErrorEvent.Method.valueOf(expectedMethod)) + .isEqualTo(expectedMethod.toErrorMethod()) assertThat(actual.error.resource?.statusCode) .overridingErrorMessage( "Expected event data to have error.resource.statusCode $expectedStatusCode " + diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt index 25ed3a20f9..d74394c4cd 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt @@ -9,16 +9,16 @@ package com.datadog.android.rum.assertj import com.datadog.android.api.context.NetworkInfo import com.datadog.android.api.context.UserInfo import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.event.ResourceTiming import com.datadog.android.rum.internal.domain.scope.isConnected -import com.datadog.android.rum.internal.domain.scope.toMethod +import com.datadog.android.rum.internal.domain.scope.toResourceMethod import com.datadog.android.rum.internal.domain.scope.toSchemaType import com.datadog.android.rum.model.ResourceEvent import org.assertj.core.api.AbstractObjectAssert import org.assertj.core.api.Assertions.assertThat import org.assertj.core.data.Offset -import org.mockito.kotlin.mock internal class ResourceEventAssert(actual: ResourceEvent) : AbstractObjectAssert( @@ -68,13 +68,13 @@ internal class ResourceEventAssert(actual: ResourceEvent) : return this } - fun hasMethod(expected: String): ResourceEventAssert { + fun hasMethod(expected: RumResourceMethod): ResourceEventAssert { assertThat(actual.resource.method) .overridingErrorMessage( "Expected event data to have resource.method $expected " + "but was ${actual.resource.method}" ) - .isEqualTo(expected.toMethod(mock())) + .isEqualTo(expected.toResourceMethod()) return this } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt index c088e4f89a..bfd2778f8e 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt @@ -16,6 +16,7 @@ import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.assertj.ActionEventAssert.Companion.assertThat import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.RumContext @@ -203,7 +204,7 @@ internal class RumActionScopeTest { @Test fun `𝕄 send Action after threshold 𝕎 handleEvent(StartResource+StopResource+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, @LongForgery(200, 600) statusCode: Long, @LongForgery(0, 1024) size: Long, @@ -270,7 +271,7 @@ internal class RumActionScopeTest { fun `𝕄 do nothing 𝕎 handleEvent(StartResource+StopResource+any) {unknown key}`( @StringForgery key: String, @StringForgery key2: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, @LongForgery(200, 600) statusCode: Long, @LongForgery(0, 1024) size: Long, @@ -296,7 +297,7 @@ internal class RumActionScopeTest { @Test fun `𝕄 send Action after threshold 𝕎 handleEvent(StartResource+StopResourceWithError+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, @LongForgery(200, 600) statusCode: Long, @StringForgery message: String, @@ -374,7 +375,7 @@ internal class RumActionScopeTest { @Test fun `𝕄 send Action after threshold 𝕎 handleEvent(StartResource+StopResourceWithStackTrace)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, @LongForgery(200, 600) statusCode: Long, @StringForgery message: String, @@ -457,7 +458,7 @@ internal class RumActionScopeTest { fun `𝕄 send Action 𝕎 handleEvent(StartResource+StopResourceWithError+any) {unknown key}`( @StringForgery key: String, @StringForgery key2: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, @LongForgery(200, 600) statusCode: Long, @StringForgery message: String, @@ -491,7 +492,7 @@ internal class RumActionScopeTest { fun `𝕄 send Action 𝕎 handleEvent(StartResource+StopResourceWithStackTrace+any) {unknown key}`( @StringForgery key: String, @StringForgery key2: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, @LongForgery(200, 600) statusCode: Long, @StringForgery message: String, @@ -528,7 +529,7 @@ internal class RumActionScopeTest { @Test fun `𝕄 send Action after threshold 𝕎 handleEvent(StartResource+any) missing resource key`( - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // Given @@ -2053,7 +2054,7 @@ internal class RumActionScopeTest { @Test fun `𝕄 doNothing 𝕎 handleEvent(StartResource+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // When @@ -2071,7 +2072,7 @@ internal class RumActionScopeTest { @Test fun `𝕄 send Action after timeout 𝕎 handleEvent(StartResource+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // When diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt index ac203b4072..0387769e72 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt @@ -16,6 +16,7 @@ import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.assertj.ActionEventAssert.Companion.assertThat import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time @@ -395,7 +396,7 @@ internal class RumContinuousActionScopeTest { @Test fun `𝕄 send Action after threshold 𝕎 handleEvent(StartResource+StopAction+StopResource+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, @LongForgery(200, 600) statusCode: Long, @LongForgery(0, 1024) size: Long, @@ -463,7 +464,7 @@ internal class RumContinuousActionScopeTest { @Test fun `𝕄 send Action 𝕎 handleEvent(StartResource+StopAction+StopResourceWithError+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, @LongForgery(200, 600) statusCode: Long, @StringForgery message: String, @@ -543,7 +544,7 @@ internal class RumContinuousActionScopeTest { @Test fun `𝕄 send Action 𝕎 handleEvent(StartResource+StopAction+StopResourceWithStackTrace+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, @LongForgery(200, 600) statusCode: Long, @StringForgery message: String, @@ -626,7 +627,7 @@ internal class RumContinuousActionScopeTest { @Test fun `𝕄 send Action 𝕎 handleEvent(StartResource+StopAction+any) missing resource key`( - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // Given @@ -1774,7 +1775,7 @@ internal class RumContinuousActionScopeTest { @Test fun `𝕄 do nothing 𝕎 handleEvent(StartResource+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // When @@ -1792,7 +1793,7 @@ internal class RumContinuousActionScopeTest { @Test fun `𝕄 do nothing 𝕎 handleEvent(StartResource+StopAction+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // When @@ -1814,7 +1815,7 @@ internal class RumContinuousActionScopeTest { @Test fun `𝕄 send Action after timeout 𝕎 handleEvent(StartResource+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // When @@ -1872,7 +1873,7 @@ internal class RumContinuousActionScopeTest { @Test fun `𝕄 send Action after timeout 𝕎 handleEvent(StartResource+StopAction+any)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // When diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt index 071fd295a0..9a941f6cba 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt @@ -11,6 +11,7 @@ import com.datadog.android.api.context.NetworkInfo import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent @@ -20,7 +21,6 @@ import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.StringForgery -import fr.xgouchet.elmyr.annotation.StringForgeryType import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -29,7 +29,6 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource -import org.mockito.kotlin.mock @Extensions( ExtendWith(ForgeExtension::class) @@ -38,55 +37,27 @@ import org.mockito.kotlin.mock internal class RumEventExtTest { @ParameterizedTest - @EnumSource(ResourceEvent.Method::class) - fun `𝕄 return method 𝕎 toMethod() {valid name}`( - method: ResourceEvent.Method - ) { - // Given - val name = method.name - - // When - val result = name.toMethod(internalLogger = mock()) - - // Then - assertThat(result).isEqualTo(method) - } - - @Test - fun `𝕄 return GET 𝕎 toMethod() {invalid name}`( - @StringForgery(type = StringForgeryType.NUMERICAL) name: String + @EnumSource(RumResourceMethod::class) + fun `𝕄 return method 𝕎 toMethod()`( + method: RumResourceMethod ) { // When - val result = name.toMethod(internalLogger = mock()) + val result = method.toResourceMethod() // Then - assertThat(result).isEqualTo(ResourceEvent.Method.GET) + assertThat(result.name).isEqualTo(method.name) } @ParameterizedTest - @EnumSource(ErrorEvent.Method::class) - fun `𝕄 return method 𝕎 toErrorMethod() {valid name}`( - method: ErrorEvent.Method - ) { - // Given - val name = method.name - - // When - val result = name.toErrorMethod(internalLogger = mock()) - - // Then - assertThat(result).isEqualTo(method) - } - - @Test - fun `𝕄 return GET 𝕎 toErrorMethod() {invalid name}`( - @StringForgery(type = StringForgeryType.NUMERICAL) name: String + @EnumSource(RumResourceMethod::class) + fun `𝕄 return method 𝕎 toErrorMethod()`( + method: RumResourceMethod ) { // When - val result = name.toErrorMethod(internalLogger = mock()) + val result = method.toErrorMethod() // Then - assertThat(result).isEqualTo(ErrorEvent.Method.GET) + assertThat(result.name).isEqualTo(method.name) } @ParameterizedTest diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt index 842b34c0e0..a55074222c 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt @@ -9,6 +9,7 @@ package com.datadog.android.rum.internal.domain.scope import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.domain.Time import com.datadog.tools.unit.forge.aThrowable import com.datadog.tools.unit.forge.exhaustiveAttributes @@ -60,7 +61,7 @@ internal fun Forge.startResourceEvent(): RumRawEvent.StartResource { return RumRawEvent.StartResource( key = anAlphabeticalString(), url = getForgery().toString(), - method = anElementFrom("POST", "GET", "PUT", "DELETE", "HEAD"), + method = aValueFrom(RumResourceMethod::class.java), attributes = exhaustiveAttributes() ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt index 8ef16365c2..1319420eb4 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt @@ -18,6 +18,7 @@ import com.datadog.android.core.internal.utils.loggableStackTrace import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod 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 @@ -104,7 +105,9 @@ internal class RumResourceScopeTest { @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") lateinit var fakeUrl: String lateinit var fakeKey: String - lateinit var fakeMethod: String + + @Forgery + lateinit var fakeMethod: RumResourceMethod lateinit var fakeAttributes: Map @Forgery @@ -164,7 +167,6 @@ internal class RumResourceScopeTest { forge.aLong(min = minLimit, max = maxLimit) fakeAttributes = forge.exhaustiveAttributes() fakeKey = forge.anAsciiString() - fakeMethod = forge.anElementFrom("PUT", "POST", "GET", "DELETE") mockEvent = mockEvent() fakeSampleRate = forge.aFloat(min = 0.0f, max = 100.0f) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt index 2e6f019d98..c46052be58 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt @@ -20,6 +20,7 @@ import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumPerformanceMetric +import com.datadog.android.rum.RumResourceMethod 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 @@ -3808,7 +3809,7 @@ internal class RumViewScopeTest { @Test fun `𝕄 create ResourceScope 𝕎 handleEvent(StartResource)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, forge: Forge ) { @@ -3844,7 +3845,7 @@ internal class RumViewScopeTest { @Test fun `𝕄 create ResourceScope with active actionId 𝕎 handleEvent(StartResource)`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, forge: Forge ) { @@ -3931,7 +3932,7 @@ internal class RumViewScopeTest { @Test fun `𝕄 wait for pending Resource 𝕎 handleEvent(StartResource) on active view`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // Given @@ -6470,7 +6471,7 @@ internal class RumViewScopeTest { RumRawEvent.StartResource( forge.anAlphabeticalString(), forge.anAlphabeticalString(), - forge.anAlphabeticalString(), + forge.aValueFrom(RumResourceMethod::class.java), emptyMap() ), RumRawEvent.ResourceSent( diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index d89483c54a..8f4e74ab1f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -19,6 +19,7 @@ import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumPerformanceMetric import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.RumFeature @@ -77,6 +78,7 @@ import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness +import java.util.Locale import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.Future @@ -287,10 +289,62 @@ internal class DatadogRumMonitorTest { } @Test - fun `M delegate event to rootScope W startResource()`( + fun `M delegate event to rootScope W startResource() { deprecated, known http method }`( + @StringForgery key: String, + @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String, + forge: Forge + ) { + val method = forge.anElementFrom( + "GeT", + "PoSt", + "pUt", + "HeAd", + "DeLeTe", + "pAtCh" + ) + @Suppress("DEPRECATION") + testedMonitor.startResource(key, method, url, fakeAttributes) + Thread.sleep(PROCESSING_DELAY) + + argumentCaptor { + verify(mockScope).handleEvent(capture(), same(mockWriter)) + + val event = firstValue as RumRawEvent.StartResource + assertThat(event.key).isEqualTo(key) + assertThat(event.method.name).isEqualTo(method.uppercase(Locale.US)) + assertThat(event.url).isEqualTo(url) + assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) + } + verifyNoMoreInteractions(mockScope, mockWriter) + } + + @Test + fun `M delegate event to rootScope W startResource() { deprecated, unknown http method }`( @StringForgery key: String, @StringForgery method: String, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String + ) { + @Suppress("DEPRECATION") + testedMonitor.startResource(key, method, url, fakeAttributes) + Thread.sleep(PROCESSING_DELAY) + + argumentCaptor { + verify(mockScope).handleEvent(capture(), same(mockWriter)) + + val event = firstValue as RumRawEvent.StartResource + assertThat(event.key).isEqualTo(key) + assertThat(event.method).isEqualTo(RumResourceMethod.GET) + assertThat(event.url).isEqualTo(url) + assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) + } + verifyNoMoreInteractions(mockScope, mockWriter) + } + + @Test + fun `M delegate event to rootScope W startResource()`( + @StringForgery key: String, + @Forgery method: RumResourceMethod, + @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { testedMonitor.startResource(key, method, url, fakeAttributes) Thread.sleep(PROCESSING_DELAY) @@ -698,7 +752,7 @@ internal class DatadogRumMonitorTest { @Test fun `M delegate event to rootScope with timestamp W startResource()`( @StringForgery key: String, - @StringForgery method: String, + @Forgery method: RumResourceMethod, @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { val attributes = fakeAttributes + (RumAttributes.INTERNAL_TIMESTAMP to fakeTimestamp) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayPrivacy.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayPrivacy.kt index 97623aa27b..5887aa4a96 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayPrivacy.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayPrivacy.kt @@ -11,6 +11,8 @@ import android.view.View import android.widget.Button import android.widget.CheckBox import android.widget.CheckedTextView +import android.widget.EditText +import android.widget.ImageButton import android.widget.ImageView import android.widget.NumberPicker import android.widget.RadioButton @@ -26,7 +28,8 @@ import com.datadog.android.sessionreplay.internal.recorder.mapper.BasePickerMapp import com.datadog.android.sessionreplay.internal.recorder.mapper.ButtonMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckBoxMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckedTextViewMapper -import com.datadog.android.sessionreplay.internal.recorder.mapper.ImageViewMapper +import com.datadog.android.sessionreplay.internal.recorder.mapper.EditTextViewMapper +import com.datadog.android.sessionreplay.internal.recorder.mapper.ImageButtonMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.MapperTypeWrapper import com.datadog.android.sessionreplay.internal.recorder.mapper.MaskCheckBoxMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.MaskCheckedTextViewMapper @@ -42,6 +45,8 @@ import com.datadog.android.sessionreplay.internal.recorder.mapper.SeekBarWirefra import com.datadog.android.sessionreplay.internal.recorder.mapper.SwitchCompatMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.TextViewMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.UnsupportedViewMapper +import com.datadog.android.sessionreplay.internal.recorder.mapper.ViewScreenshotWireframeMapper +import com.datadog.android.sessionreplay.internal.recorder.mapper.ViewWireframeMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.WireframeMapper import com.datadog.android.sessionreplay.utils.UniqueIdentifierGenerator import androidx.appcompat.widget.Toolbar as AppCompatToolbar @@ -81,14 +86,17 @@ enum class SessionReplayPrivacy { val imageWireframeHelper = ImageWireframeHelper(base64Serializer = base64Serializer) val uniqueIdentifierGenerator = UniqueIdentifierGenerator + val viewWireframeMapper = ViewWireframeMapper() val unsupportedViewMapper = UnsupportedViewMapper() - val imageViewMapper = ImageViewMapper( + val imageButtonMapper = ImageButtonMapper( base64Serializer = base64Serializer, imageWireframeHelper = imageWireframeHelper, uniqueIdentifierGenerator = uniqueIdentifierGenerator ) + val imageMapper: ViewScreenshotWireframeMapper val textMapper: TextViewMapper val buttonMapper: ButtonMapper + val editTextViewMapper: EditTextViewMapper val checkedTextViewMapper: CheckedTextViewMapper val checkBoxMapper: CheckBoxMapper val radioButtonMapper: RadioButtonMapper @@ -97,11 +105,13 @@ enum class SessionReplayPrivacy { val numberPickerMapper: BasePickerMapper? when (this) { ALLOW -> { + imageMapper = ViewScreenshotWireframeMapper(viewWireframeMapper) textMapper = TextViewMapper( imageWireframeHelper = imageWireframeHelper, uniqueIdentifierGenerator = uniqueIdentifierGenerator ) buttonMapper = ButtonMapper(textMapper) + editTextViewMapper = EditTextViewMapper(textMapper) checkedTextViewMapper = CheckedTextViewMapper(textMapper) checkBoxMapper = CheckBoxMapper(textMapper) radioButtonMapper = RadioButtonMapper(textMapper) @@ -110,11 +120,13 @@ enum class SessionReplayPrivacy { numberPickerMapper = getNumberPickerMapper() } MASK -> { + imageMapper = ViewScreenshotWireframeMapper(viewWireframeMapper) textMapper = MaskTextViewMapper( imageWireframeHelper = imageWireframeHelper, uniqueIdentifierGenerator = uniqueIdentifierGenerator ) buttonMapper = ButtonMapper(textMapper) + editTextViewMapper = EditTextViewMapper(textMapper) checkedTextViewMapper = MaskCheckedTextViewMapper(textMapper) checkBoxMapper = MaskCheckBoxMapper(textMapper) radioButtonMapper = MaskRadioButtonMapper(textMapper) @@ -123,11 +135,13 @@ enum class SessionReplayPrivacy { numberPickerMapper = getMaskNumberPickerMapper() } MASK_USER_INPUT -> { + imageMapper = ViewScreenshotWireframeMapper(viewWireframeMapper) textMapper = MaskInputTextViewMapper( imageWireframeHelper = imageWireframeHelper, uniqueIdentifierGenerator = uniqueIdentifierGenerator ) buttonMapper = ButtonMapper(textMapper) + editTextViewMapper = EditTextViewMapper(textMapper) checkedTextViewMapper = MaskCheckedTextViewMapper(textMapper) checkBoxMapper = MaskCheckBoxMapper(textMapper) radioButtonMapper = MaskRadioButtonMapper(textMapper) @@ -142,8 +156,10 @@ enum class SessionReplayPrivacy { MapperTypeWrapper(CheckBox::class.java, checkBoxMapper.toGenericMapper()), MapperTypeWrapper(CheckedTextView::class.java, checkedTextViewMapper.toGenericMapper()), MapperTypeWrapper(Button::class.java, buttonMapper.toGenericMapper()), + MapperTypeWrapper(ImageButton::class.java, imageButtonMapper.toGenericMapper()), + MapperTypeWrapper(EditText::class.java, editTextViewMapper.toGenericMapper()), MapperTypeWrapper(TextView::class.java, textMapper.toGenericMapper()), - MapperTypeWrapper(ImageView::class.java, imageViewMapper.toGenericMapper()), + MapperTypeWrapper(ImageView::class.java, imageMapper.toGenericMapper()), MapperTypeWrapper(AppCompatToolbar::class.java, unsupportedViewMapper.toGenericMapper()) ) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/EditTextViewMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/EditTextViewMapper.kt new file mode 100644 index 0000000000..1387402499 --- /dev/null +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/EditTextViewMapper.kt @@ -0,0 +1,86 @@ +/* + * 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.recorder.mapper + +import android.widget.EditText +import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback +import com.datadog.android.sessionreplay.internal.recorder.MappingContext +import com.datadog.android.sessionreplay.model.MobileSegment +import com.datadog.android.sessionreplay.utils.StringUtils +import com.datadog.android.sessionreplay.utils.UniqueIdentifierGenerator +import com.datadog.android.sessionreplay.utils.ViewUtils + +/** + * A [WireframeMapper] implementation to map a [EditText] component in case the + * [SessionReplayPrivacy.ALLOW] rule was used in the configuration. + * In this case the mapper will use the provided [textViewMapper] used for the current privacy + * level and will only mask the [EditText] for which the input type is considered sensible + * (password, email, address, postal address, numeric password) with the static mask: [***]. + */ +internal open class EditTextViewMapper( + internal val textViewMapper: TextViewMapper, + private val uniqueIdentifierGenerator: UniqueIdentifierGenerator = UniqueIdentifierGenerator, + viewUtils: ViewUtils = ViewUtils, + stringUtils: StringUtils = StringUtils +) : BaseWireframeMapper( + viewUtils = viewUtils, + stringUtils = stringUtils +) { + + override fun map( + view: EditText, + mappingContext: MappingContext, + asyncJobStatusCallback: AsyncJobStatusCallback + ): + List { + val mainWireframeList = textViewMapper.map(view, mappingContext, asyncJobStatusCallback) + resolveUnderlineWireframe(view, mappingContext.systemInformation.screenDensity) + ?.let { wireframe -> + return mainWireframeList + wireframe + } + return mainWireframeList + } + + private fun resolveUnderlineWireframe( + parent: EditText, + pixelsDensity: Float + ): MobileSegment.Wireframe? { + val identifier = uniqueIdentifierGenerator.resolveChildUniqueIdentifier( + parent, + UNDERLINE_KEY_NAME + ) ?: return null + val viewGlobalBounds = resolveViewGlobalBounds(parent, pixelsDensity) + val fieldUnderlineColor = resolveUnderlineColor(parent) + return MobileSegment.Wireframe.ShapeWireframe( + identifier, + viewGlobalBounds.x, + viewGlobalBounds.y + viewGlobalBounds.height - UNDERLINE_HEIGHT_IN_PIXELS, + viewGlobalBounds.width, + UNDERLINE_HEIGHT_IN_PIXELS, + shapeStyle = MobileSegment.ShapeStyle( + backgroundColor = fieldUnderlineColor, + opacity = parent.alpha + ) + ) + } + + private fun resolveUnderlineColor(view: EditText): String { + view.backgroundTintList?.let { + return colorAndAlphaAsStringHexa( + it.defaultColor, + OPAQUE_ALPHA_VALUE + ) + } + return colorAndAlphaAsStringHexa(view.currentTextColor, OPAQUE_ALPHA_VALUE) + } + + companion object { + internal const val UNDERLINE_HEIGHT_IN_PIXELS = 1L + internal const val UNDERLINE_KEY_NAME = "underline" + } +} diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageViewMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageButtonMapper.kt similarity index 95% rename from features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageViewMapper.kt rename to features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageButtonMapper.kt index 6105423e67..1ca0dec8a9 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageViewMapper.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageButtonMapper.kt @@ -6,7 +6,7 @@ package com.datadog.android.sessionreplay.internal.recorder.mapper -import android.widget.ImageView +import android.widget.ImageButton import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback import com.datadog.android.sessionreplay.internal.recorder.MappingContext import com.datadog.android.sessionreplay.internal.recorder.base64.Base64Serializer @@ -16,16 +16,16 @@ import com.datadog.android.sessionreplay.internal.recorder.densityNormalized import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.utils.UniqueIdentifierGenerator -internal class ImageViewMapper( +internal class ImageButtonMapper( private val base64Serializer: Base64Serializer, private val imageWireframeHelper: ImageWireframeHelper, uniqueIdentifierGenerator: UniqueIdentifierGenerator -) : BaseAsyncBackgroundWireframeMapper( +) : BaseAsyncBackgroundWireframeMapper( imageWireframeHelper = imageWireframeHelper, uniqueIdentifierGenerator = uniqueIdentifierGenerator ) { override fun map( - view: ImageView, + view: ImageButton, mappingContext: MappingContext, asyncJobStatusCallback: AsyncJobStatusCallback ): List { diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayPrivacyTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayPrivacyTest.kt index c8803ce2d4..6571daae18 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayPrivacyTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayPrivacyTest.kt @@ -11,6 +11,8 @@ import android.view.View import android.widget.Button import android.widget.CheckBox import android.widget.CheckedTextView +import android.widget.EditText +import android.widget.ImageButton import android.widget.ImageView import android.widget.NumberPicker import android.widget.RadioButton @@ -21,7 +23,8 @@ import androidx.appcompat.widget.SwitchCompat import com.datadog.android.sessionreplay.internal.recorder.mapper.ButtonMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckBoxMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckedTextViewMapper -import com.datadog.android.sessionreplay.internal.recorder.mapper.ImageViewMapper +import com.datadog.android.sessionreplay.internal.recorder.mapper.EditTextViewMapper +import com.datadog.android.sessionreplay.internal.recorder.mapper.ImageButtonMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.MapperTypeWrapper import com.datadog.android.sessionreplay.internal.recorder.mapper.MaskCheckBoxMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.MaskCheckedTextViewMapper @@ -37,6 +40,7 @@ import com.datadog.android.sessionreplay.internal.recorder.mapper.SeekBarWirefra import com.datadog.android.sessionreplay.internal.recorder.mapper.SwitchCompatMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.TextViewMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.UnsupportedViewMapper +import com.datadog.android.sessionreplay.internal.recorder.mapper.ViewScreenshotWireframeMapper import com.datadog.android.sessionreplay.internal.recorder.mapper.WireframeMapper import com.datadog.tools.unit.setStaticValue import org.assertj.core.api.Assertions.assertThat @@ -106,12 +110,16 @@ internal class SessionReplayPrivacyTest { // BASE private val mockButtonMapper: ButtonMapper = mock() + private val mockEditTextViewMapper: EditTextViewMapper = mock() + private val mockImageMapper: ViewScreenshotWireframeMapper = mock() private val mockUnsupportedViewMapper: UnsupportedViewMapper = mock() - private val mockImageViewMapper: ImageViewMapper = mock() + private val mockImageButtonViewMapper: ImageButtonMapper = mock() private val baseMappers = listOf( MapperTypeWrapper(Button::class.java, mockButtonMapper.toGenericMapper()), - MapperTypeWrapper(ImageView::class.java, mockImageViewMapper.toGenericMapper()), + MapperTypeWrapper(EditText::class.java, mockEditTextViewMapper.toGenericMapper()), + MapperTypeWrapper(ImageView::class.java, mockImageMapper.toGenericMapper()), + MapperTypeWrapper(ImageButton::class.java, mockImageButtonViewMapper.toGenericMapper()), MapperTypeWrapper(AppCompatToolbar::class.java, mockUnsupportedViewMapper.toGenericMapper()) ) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseEditTextViewMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseEditTextViewMapperTest.kt new file mode 100644 index 0000000000..bfed1faafc --- /dev/null +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseEditTextViewMapperTest.kt @@ -0,0 +1,167 @@ +/* + * 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.recorder.mapper + +import android.content.res.ColorStateList +import android.widget.EditText +import com.datadog.android.sessionreplay.internal.recorder.GlobalBounds +import com.datadog.android.sessionreplay.model.MobileSegment +import com.datadog.android.sessionreplay.utils.StringUtils +import com.datadog.android.sessionreplay.utils.UniqueIdentifierGenerator +import com.datadog.android.sessionreplay.utils.ViewUtils +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.LongForgery +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mock +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever + +internal abstract class BaseEditTextViewMapperTest : BaseWireframeMapperTest() { + + private lateinit var testedEditTextViewMapper: EditTextViewMapper + + @Mock + lateinit var mockuniqueIdentifierGenerator: UniqueIdentifierGenerator + + @Mock + lateinit var mockTextWireframeMapper: TextViewMapper + + @Forgery + lateinit var fakeTextWireframes: List + + @Mock + lateinit var mockEditText: EditText + + @Mock + lateinit var mockBackgroundTintList: ColorStateList + + @LongForgery + var fakeGeneratedIdentifier: Long = 0L + + @IntForgery + var fakeBackgroundTintColor: Int = 0 + + @IntForgery + var fakeTextColor: Int = 0 + + @Mock + lateinit var mockViewUtils: ViewUtils + + @Forgery + lateinit var fakeViewGlobalBounds: GlobalBounds + + @Mock + lateinit var mockStringUtils: StringUtils + + @BeforeEach + fun `set up`() { + whenever(mockEditText.currentTextColor).thenReturn(fakeTextColor) + whenever(mockBackgroundTintList.defaultColor).thenReturn(fakeBackgroundTintColor) + whenever( + mockuniqueIdentifierGenerator.resolveChildUniqueIdentifier( + mockEditText, + EditTextViewMapper.UNDERLINE_KEY_NAME + ) + ).thenReturn(fakeGeneratedIdentifier) + whenever(mockEditText.backgroundTintList).thenReturn(mockBackgroundTintList) + whenever(mockTextWireframeMapper.map(eq(mockEditText), eq(fakeMappingContext), any())) + .thenReturn(fakeTextWireframes) + whenever( + mockViewUtils.resolveViewGlobalBounds( + mockEditText, + fakeMappingContext.systemInformation.screenDensity + ) + ) + .thenReturn(fakeViewGlobalBounds) + testedEditTextViewMapper = initTestInstance() + } + + abstract fun initTestInstance(): EditTextViewMapper + + @Test + fun `M resolve the underline as ShapeWireframe W map()`(forge: Forge) { + // Given + val fakeExpectedUnderlineColor = forge.aStringMatching("#[0-9A-Fa-f]{8}") + whenever( + mockStringUtils.formatColorAndAlphaAsHexa( + fakeBackgroundTintColor, + OPAQUE_ALPHA_VALUE + ) + ) + .thenReturn(fakeExpectedUnderlineColor) + val expectedUnderlineShapeWireframe = MobileSegment.Wireframe.ShapeWireframe( + id = fakeGeneratedIdentifier, + x = fakeViewGlobalBounds.x, + y = fakeViewGlobalBounds.y + + fakeViewGlobalBounds.height - + EditTextViewMapper.UNDERLINE_HEIGHT_IN_PIXELS, + width = fakeViewGlobalBounds.width, + height = EditTextViewMapper.UNDERLINE_HEIGHT_IN_PIXELS, + shapeStyle = MobileSegment.ShapeStyle( + backgroundColor = fakeExpectedUnderlineColor, + opacity = mockEditText.alpha + ) + ) + + // When + assertThat(testedEditTextViewMapper.map(mockEditText, fakeMappingContext)) + .isEqualTo(fakeTextWireframes + expectedUnderlineShapeWireframe) + } + + @Test + fun `M resolve the underline color from textColor W map{backgroundTint is null}`( + forge: Forge + ) { + // Given + whenever(mockEditText.backgroundTintList).thenReturn(null) + val fakeExpectedUnderlineColor = forge.aStringMatching("#[0-9A-Fa-f]{8}") + whenever( + mockStringUtils.formatColorAndAlphaAsHexa( + fakeTextColor, + OPAQUE_ALPHA_VALUE + ) + ) + .thenReturn(fakeExpectedUnderlineColor) + val expectedUnderlineShapeWireframe = MobileSegment.Wireframe.ShapeWireframe( + id = fakeGeneratedIdentifier, + x = fakeViewGlobalBounds.x, + y = fakeViewGlobalBounds.y + + fakeViewGlobalBounds.height - + EditTextViewMapper.UNDERLINE_HEIGHT_IN_PIXELS, + width = fakeViewGlobalBounds.width, + height = EditTextViewMapper.UNDERLINE_HEIGHT_IN_PIXELS, + shapeStyle = MobileSegment.ShapeStyle( + backgroundColor = fakeExpectedUnderlineColor, + opacity = mockEditText.alpha + ) + ) + + // When + assertThat(testedEditTextViewMapper.map(mockEditText, fakeMappingContext)) + .isEqualTo(fakeTextWireframes + expectedUnderlineShapeWireframe) + } + + @Test + fun `M ignore the underline W map() { unique id could not be generated }`() { + // Given + whenever( + mockuniqueIdentifierGenerator.resolveChildUniqueIdentifier( + mockEditText, + EditTextViewMapper.UNDERLINE_KEY_NAME + ) + ).thenReturn(null) + + // Then + assertThat(testedEditTextViewMapper.map(mockEditText, fakeMappingContext)) + .isEqualTo(fakeTextWireframes) + } +} diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/EditTextViewMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/EditTextViewMapperTest.kt new file mode 100644 index 0000000000..923a84053d --- /dev/null +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/EditTextViewMapperTest.kt @@ -0,0 +1,36 @@ +/* + * 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.recorder.mapper + +import com.datadog.android.sessionreplay.forge.ForgeConfigurator +import com.datadog.tools.unit.extensions.ApiLevelExtension +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +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 EditTextViewMapperTest : BaseEditTextViewMapperTest() { + + override fun initTestInstance(): EditTextViewMapper { + return EditTextViewMapper( + mockTextWireframeMapper, + mockuniqueIdentifierGenerator, + mockViewUtils, + mockStringUtils + ) + } +} diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageViewMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageButtonMapperTest.kt similarity index 84% rename from features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageViewMapperTest.kt rename to features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageButtonMapperTest.kt index da0d1a56a3..1b0df9ef4b 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageViewMapperTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageButtonMapperTest.kt @@ -12,7 +12,7 @@ import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable.ConstantState import android.util.DisplayMetrics -import android.widget.ImageView +import android.widget.ImageButton import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback import com.datadog.android.sessionreplay.internal.recorder.GlobalBounds @@ -56,12 +56,12 @@ import org.mockito.quality.Strictness ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(ForgeConfigurator::class) -internal class ImageViewMapperTest { +internal class ImageButtonMapperTest { - private lateinit var testedMapper: ImageViewMapper + private lateinit var testedMapper: ImageButtonMapper @Mock - lateinit var mockImageView: ImageView + lateinit var mockImageButton: ImageButton @Mock lateinit var mockImageWireframeHelper: ImageWireframeHelper @@ -116,15 +116,15 @@ internal class ImageViewMapperTest { @BeforeEach fun setup(forge: Forge) { - whenever(mockImageView.background).thenReturn(null) + whenever(mockImageButton.background).thenReturn(null) whenever(mockUniqueIdentifierGenerator.resolveChildUniqueIdentifier(any(), any())) .thenReturn(fakeId) whenever(mockConstantState.newDrawable(any())).thenReturn(mockDrawable) whenever(mockDrawable.constantState).thenReturn(mockConstantState) - whenever(mockImageView.drawable).thenReturn(mockDrawable) - whenever(mockImageView.drawable.current).thenReturn(mockDrawable) + whenever(mockImageButton.drawable).thenReturn(mockDrawable) + whenever(mockImageButton.drawable.current).thenReturn(mockDrawable) whenever(mockDrawable.intrinsicWidth).thenReturn(forge.aPositiveInt()) whenever(mockDrawable.intrinsicHeight).thenReturn(forge.aPositiveInt()) @@ -135,10 +135,10 @@ internal class ImageViewMapperTest { whenever(mockMappingContext.systemInformation).thenReturn(mockSystemInformation) whenever(mockResources.displayMetrics).thenReturn(mockDisplayMetrics) - whenever(mockImageView.resources).thenReturn(mockResources) + whenever(mockImageButton.resources).thenReturn(mockResources) whenever(mockContext.applicationContext).thenReturn(mockContext) - whenever(mockImageView.context).thenReturn(mockContext) + whenever(mockImageButton.context).thenReturn(mockContext) whenever(mockBackground.current).thenReturn(mockBackground) whenever(mockViewUtils.resolveViewGlobalBounds(any(), any())).thenReturn(mockGlobalBounds) @@ -147,8 +147,8 @@ internal class ImageViewMapperTest { id = fakeId, x = mockGlobalBounds.x, y = mockGlobalBounds.y, - width = mockImageView.width.toLong(), - height = mockImageView.height.toLong(), + width = mockImageButton.width.toLong(), + height = mockImageButton.height.toLong(), shapeStyle = null, border = null, base64 = "", @@ -159,7 +159,7 @@ internal class ImageViewMapperTest { whenever(mockBase64Serializer.getDrawableScaledDimensions(any(), any(), any())) .thenReturn(DrawableDimensions(0, 0)) - testedMapper = ImageViewMapper( + testedMapper = ImageButtonMapper( base64Serializer = mockBase64Serializer, imageWireframeHelper = mockImageWireframeHelper, uniqueIdentifierGenerator = mockUniqueIdentifierGenerator @@ -169,11 +169,11 @@ internal class ImageViewMapperTest { @Test fun `M return foreground wireframe W map() { no background }`() { // Given - whenever(mockImageView.background).thenReturn(null) + whenever(mockImageButton.background).thenReturn(null) val fakeViewDrawable = mockDrawable.constantState?.newDrawable(mockResources) whenever( mockImageWireframeHelper.createImageWireframe( - eq(mockImageView), + eq(mockImageButton), any(), any(), any(), @@ -188,7 +188,7 @@ internal class ImageViewMapperTest { ).thenReturn(expectedWireframe) // When - val wireframes = testedMapper.map(mockImageView, mockMappingContext) + val wireframes = testedMapper.map(mockImageButton, mockMappingContext) // Then assertThat(wireframes.size).isEqualTo(1) @@ -204,8 +204,8 @@ internal class ImageViewMapperTest { id = id, x = mockGlobalBounds.x, y = mockGlobalBounds.y, - width = mockImageView.width.toLong(), - height = mockImageView.height.toLong(), + width = mockImageButton.width.toLong(), + height = mockImageButton.height.toLong(), shapeStyle = null, border = null, base64 = "", @@ -213,14 +213,14 @@ internal class ImageViewMapperTest { isEmpty = true ) - whenever(mockImageView.background).thenReturn(mockBackground) + whenever(mockImageButton.background).thenReturn(mockBackground) mockCreateImageWireframe( expectedBackgroundWireframe, expectedWireframe ) // When - val wireframes = testedMapper.map(mockImageView, mockMappingContext) + val wireframes = testedMapper.map(mockImageButton, mockMappingContext) // Then assertThat(wireframes.size).isEqualTo(2) @@ -231,7 +231,7 @@ internal class ImageViewMapperTest { @Test fun `M call async callback W map() { }`() { // Given - whenever(mockImageView.background).thenReturn(mockBackground) + whenever(mockImageButton.background).thenReturn(mockBackground) val argumentCaptor = argumentCaptor() whenever( @@ -251,7 +251,7 @@ internal class ImageViewMapperTest { ).thenReturn(expectedWireframe) // When - val wireframes = testedMapper.map(mockImageView, mockMappingContext, mockCallback) + val wireframes = testedMapper.map(mockImageButton, mockMappingContext, mockCallback) // Then assertThat(wireframes.size).isEqualTo(2) @@ -287,15 +287,15 @@ internal class ImageViewMapperTest { id = id, x = mockGlobalBounds.x, y = mockGlobalBounds.y, - width = mockImageView.width.toLong(), - height = mockImageView.height.toLong(), + width = mockImageButton.width.toLong(), + height = mockImageButton.height.toLong(), shapeStyle = null, border = null, base64 = "", mimeType = fakeMimeType, isEmpty = true ) - whenever(mockImageView.background).thenReturn(mockBackground) + whenever(mockImageButton.background).thenReturn(mockBackground) mockCreateImageWireframe( expectedBackgroundWireframe, @@ -303,7 +303,7 @@ internal class ImageViewMapperTest { ) // When - testedMapper.map(mockImageView, mockMappingContext) + testedMapper.map(mockImageButton, mockMappingContext) // Then val captor = argumentCaptor() @@ -328,7 +328,7 @@ internal class ImageViewMapperTest { @Test fun `M set index to 0 W map() { no background wireframe }`() { // Given - whenever(mockImageView.background).thenReturn(mockBackground) + whenever(mockImageButton.background).thenReturn(mockBackground) mockCreateImageWireframe( null, @@ -336,7 +336,7 @@ internal class ImageViewMapperTest { ) // When - testedMapper.map(mockImageView, mockMappingContext) + testedMapper.map(mockImageButton, mockMappingContext) // Then val captor = argumentCaptor() @@ -363,14 +363,14 @@ internal class ImageViewMapperTest { @LongForgery id: Long ) { // Given - whenever(mockImageView.background).thenReturn(mockBackground) + whenever(mockImageButton.background).thenReturn(mockBackground) val expectedBackgroundWireframe = MobileSegment.Wireframe.ImageWireframe( id = id, x = mockGlobalBounds.x, y = mockGlobalBounds.y, - width = mockImageView.width.toLong(), - height = mockImageView.height.toLong(), + width = mockImageButton.width.toLong(), + height = mockImageButton.height.toLong(), shapeStyle = null, border = null, base64 = "", @@ -384,7 +384,7 @@ internal class ImageViewMapperTest { ) // When - val wireframes = testedMapper.map(mockImageView, mockMappingContext) + val wireframes = testedMapper.map(mockImageButton, mockMappingContext) // Then assertThat(wireframes[0]::class.java).isEqualTo(MobileSegment.Wireframe.ImageWireframe::class.java) @@ -395,10 +395,10 @@ internal class ImageViewMapperTest { @Mock mockColorDrawable: ColorDrawable ) { // Given - whenever(mockImageView.background).thenReturn(mockColorDrawable) + whenever(mockImageButton.background).thenReturn(mockColorDrawable) // When - val wireframes = testedMapper.map(mockImageView, mockMappingContext) + val wireframes = testedMapper.map(mockImageButton, mockMappingContext) // Then assertThat(wireframes[0]::class.java).isEqualTo(MobileSegment.Wireframe.ShapeWireframe::class.java) @@ -409,7 +409,7 @@ internal class ImageViewMapperTest { @Mock mockColorDrawable: ColorDrawable ) { // Given - whenever(mockImageView.background).thenReturn(mockColorDrawable) + whenever(mockImageButton.background).thenReturn(mockColorDrawable) whenever(mockUniqueIdentifierGenerator.resolveChildUniqueIdentifier(any(), any())) .thenReturn(null) @@ -420,7 +420,7 @@ internal class ImageViewMapperTest { ) // When - val wireframes = testedMapper.map(mockImageView, mockMappingContext) + val wireframes = testedMapper.map(mockImageButton, mockMappingContext) // Then assertThat(wireframes.size).isEqualTo(1) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 26de3eaae6..a13c353b46 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ okHttp = "4.11.0" kronosNTP = "0.0.1-alpha11" # Android -androidToolsPlugin = "8.0.2" +androidToolsPlugin = "8.1.1" androidXAnnotations = "1.1.0" androidXAppCompat = "1.3.0" androidXCore = "1.3.1" diff --git a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/security/EncryptionTest.kt b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/security/EncryptionTest.kt index 50ce0e1b40..03c6ef09ec 100644 --- a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/security/EncryptionTest.kt +++ b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/security/EncryptionTest.kt @@ -21,6 +21,7 @@ import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumConfiguration import com.datadog.android.rum.RumMonitor import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import com.datadog.android.security.Encryption import com.datadog.android.sessionreplay.SessionReplay import com.datadog.android.sessionreplay.SessionReplayConfiguration @@ -170,7 +171,7 @@ internal class EncryptionTest { rumMonitor.startResource( resourceName, - "GET", + RumResourceMethod.GET, "https://${forge.anAlphaNumericalString()}.com" ) diff --git a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumMonitorE2ETests.kt b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumMonitorE2ETests.kt index a26aa348ea..52c2df2b38 100644 --- a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumMonitorE2ETests.kt +++ b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumMonitorE2ETests.kt @@ -639,7 +639,7 @@ class RumMonitorE2ETests { // region Resource /** - * apiMethodSignature: com.datadog.android.rum.RumMonitor#fun startResource(String, String, String, Map = emptyMap()) + * apiMethodSignature: com.datadog.android.rum.RumMonitor#fun startResource(String, HttpMethod, String, Map = emptyMap()) */ @Test fun rum_rummonitor_start_resource() { diff --git a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/utils/ForgeExtensions.kt b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/utils/ForgeExtensions.kt index 5c37e74a59..92b0a6e098 100644 --- a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/utils/ForgeExtensions.kt +++ b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/utils/ForgeExtensions.kt @@ -13,6 +13,7 @@ import com.datadog.android.nightly.rum.RUM_RESOURCE_URL_PREFIX import com.datadog.android.nightly.rum.RUM_VIEW_PREFIX import com.datadog.android.nightly.rum.RUM_VIEW_URL_PREFIX import com.datadog.android.nightly.rum.TAG_VALUE_PREFIX +import com.datadog.android.rum.RumResourceMethod import fr.xgouchet.elmyr.Forge fun Forge.exhaustiveAttributes(): Map { @@ -52,8 +53,8 @@ fun Forge.anErrorMessage(prefix: String = RUM_ERROR_MESSAGE_PREFIX): String { return prefix + this.aStringMatching("[a-zA-z](.+)") } -fun Forge.aResourceMethod(): String { - return this.anElementFrom(listOf("GET", "POST")) +fun Forge.aResourceMethod(): RumResourceMethod { + return this.aValueFrom(RumResourceMethod::class.java) } fun Forge.aTagValue(prefix: String = TAG_VALUE_PREFIX): String { diff --git a/instrumented/nightly-tests/src/main/kotlin/com/datadog/android/nightly/activities/UserInteractionCustomTargetActivity.kt b/instrumented/nightly-tests/src/main/kotlin/com/datadog/android/nightly/activities/UserInteractionCustomTargetActivity.kt index 418a47e295..679bcecf9f 100644 --- a/instrumented/nightly-tests/src/main/kotlin/com/datadog/android/nightly/activities/UserInteractionCustomTargetActivity.kt +++ b/instrumented/nightly-tests/src/main/kotlin/com/datadog/android/nightly/activities/UserInteractionCustomTargetActivity.kt @@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity import com.datadog.android.nightly.R import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumResourceKind +import com.datadog.android.rum.RumResourceMethod import java.net.HttpURLConnection internal class UserInteractionCustomTargetActivity : AppCompatActivity() { @@ -23,7 +24,7 @@ internal class UserInteractionCustomTargetActivity : AppCompatActivity() { findViewById