diff --git a/embrace-android-core/build.gradle.kts b/embrace-android-core/build.gradle.kts index 42b5da4d67..80a89bba67 100644 --- a/embrace-android-core/build.gradle.kts +++ b/embrace-android-core/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { compileOnly(libs.opentelemetry.semconv.incubating) implementation(libs.lifecycle.runtime) implementation(libs.lifecycle.process) + implementation(libs.okhttp) testImplementation(platform(libs.opentelemetry.bom)) testImplementation(libs.opentelemetry.api) diff --git a/embrace-android-core/config/detekt/baseline.xml b/embrace-android-core/config/detekt/baseline.xml index f7162bde92..5b94df8f86 100644 --- a/embrace-android-core/config/detekt/baseline.xml +++ b/embrace-android-core/config/detekt/baseline.xml @@ -2,6 +2,6 @@ - LongParameterList:DeliveryModuleSupplier.kt$( configModule: ConfigModule, initModule: InitModule, otelModule: OpenTelemetryModule, workerThreadModule: WorkerThreadModule, coreModule: CoreModule, storageModule: StorageModule, essentialServiceModule: EssentialServiceModule, payloadStorageServiceProvider: Provider<PayloadStorageService?>, cacheStorageServiceProvider: Provider<PayloadStorageService?>, requestExecutionServiceProvider: Provider<RequestExecutionService?>, deliveryServiceProvider: Provider<DeliveryService?>, ) + LongParameterList:DeliveryModuleSupplier.kt$( configModule: ConfigModule, initModule: InitModule, otelModule: OpenTelemetryModule, workerThreadModule: WorkerThreadModule, coreModule: CoreModule, storageModule: StorageModule, essentialServiceModule: EssentialServiceModule, androidServicesModule: AndroidServicesModule, payloadStorageServiceProvider: Provider<PayloadStorageService>?, cacheStorageServiceProvider: Provider<PayloadStorageService>?, requestExecutionServiceProvider: Provider<RequestExecutionService>?, deliveryServiceProvider: Provider<DeliveryService>?, ) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/DataCaptureOrchestrator.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/DataCaptureOrchestrator.kt index 880bb5001c..e3e71caceb 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/DataCaptureOrchestrator.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/DataCaptureOrchestrator.kt @@ -1,7 +1,6 @@ package io.embrace.android.embracesdk.internal.arch import io.embrace.android.embracesdk.internal.arch.datasource.DataSourceState -import io.embrace.android.embracesdk.internal.config.ConfigService import io.embrace.android.embracesdk.internal.logging.EmbLogger import io.embrace.android.embracesdk.internal.logging.InternalErrorType import io.embrace.android.embracesdk.internal.worker.BackgroundWorker @@ -12,17 +11,10 @@ import java.util.concurrent.CopyOnWriteArrayList * place to coordinate everything in one place. */ class DataCaptureOrchestrator( - configService: ConfigService, private val worker: BackgroundWorker, private val logger: EmbLogger, ) : EmbraceFeatureRegistry { - init { - configService.addListener { - onConfigChange() - } - } - private val dataSourceStates = CopyOnWriteArrayList>() var currentSessionType: SessionType? = null @@ -38,18 +30,6 @@ class DataCaptureOrchestrator( } } - private fun onConfigChange() { - dataSourceStates.forEach { state -> - try { - state.dispatchStateChange { - state.onConfigChange() - } - } catch (exc: Throwable) { - logger.trackInternalError(InternalErrorType.CFG_CHANGE_DATA_CAPTURE_FAIL, exc) - } - } - } - /** * Callback that is invoked when the session type changes. */ diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/datasource/DataSourceState.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/datasource/DataSourceState.kt index d278f42172..707de6f66b 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/datasource/DataSourceState.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/datasource/DataSourceState.kt @@ -67,13 +67,6 @@ class DataSourceState>( } } - /** - * Callback that is invoked when the config layer experiences a change. - */ - fun onConfigChange() { - updateDataSource() - } - private fun updateDataSource() { val enabled = currentSessionType != null && currentSessionType != disabledSessionType && configGate() diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/buildinfo/BuildInfoServiceImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/buildinfo/BuildInfoServiceImpl.kt index 0fdafdaf5c..7d145565b6 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/buildinfo/BuildInfoServiceImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/buildinfo/BuildInfoServiceImpl.kt @@ -2,60 +2,49 @@ package io.embrace.android.embracesdk.internal.buildinfo import android.content.res.Resources import io.embrace.android.embracesdk.internal.AndroidResourcesService -import io.embrace.android.embracesdk.internal.config.instrumented.ProjectConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig internal class BuildInfoServiceImpl( - resources: AndroidResourcesService, - packageName: String, + private val instrumentedConfig: InstrumentedConfig, + private val resources: AndroidResourcesService, + private val packageName: String, ) : BuildInfoService { + companion object { + private const val BUILD_INFO_RN_BUNDLE_ID: String = "emb_rn_bundle_id" + private const val RES_TYPE_STRING = "string" + } + private val info by lazy { - fromResources(resources, packageName) + BuildInfo( + instrumentedConfig.project.getBuildId(), + instrumentedConfig.project.getBuildType(), + instrumentedConfig.project.getBuildFlavor(), + getBuildResource(resources, packageName, BUILD_INFO_RN_BUNDLE_ID), + ) } override fun getBuildInfo(): BuildInfo = info - companion object { - private const val BUILD_INFO_RN_BUNDLE_ID: String = "emb_rn_bundle_id" - private const val RES_TYPE_STRING = "string" - - /** - * Loads the build information from resources provided by the config file packaged within the application by Gradle at - * build-time. - * - * @return the build information - */ - @JvmStatic - fun fromResources(resources: AndroidResourcesService, packageName: String): BuildInfo { - val buildInfo = BuildInfo( - ProjectConfig.getBuildId(), - ProjectConfig.getBuildType(), - ProjectConfig.getBuildFlavor(), - getBuildResource(resources, packageName, BUILD_INFO_RN_BUNDLE_ID), + /** + * Given a build property name and a build property type, retrieves the embrace build resource value. + */ + fun getBuildResource( + resources: AndroidResourcesService, + packageName: String, + buildProperty: String, + ): String? { + return try { + val resourceId = + resources.getIdentifier(buildProperty, RES_TYPE_STRING, packageName) + resources.getString(resourceId) + } catch (ex: NullPointerException) { + throw IllegalArgumentException( + "No resource found for $buildProperty property. Failed to create build info.", + ex ) - return buildInfo - } - - /** - * Given a build property name and a build property type, retrieves the embrace build resource value. - */ - fun getBuildResource( - resources: AndroidResourcesService, - packageName: String, - buildProperty: String, - ): String? { - return try { - val resourceId = - resources.getIdentifier(buildProperty, RES_TYPE_STRING, packageName) - resources.getString(resourceId) - } catch (ex: NullPointerException) { - throw IllegalArgumentException( - "No resource found for $buildProperty property. Failed to create build info.", - ex - ) - } catch (ex: Resources.NotFoundException) { - null - } + } catch (ex: Resources.NotFoundException) { + null } } } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiRequestMapper.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiRequestMapper.kt index 8f6b40db0b..bccd822c6c 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiRequestMapper.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiRequestMapper.kt @@ -13,7 +13,7 @@ internal class ApiRequestMapper( ) { private val apiUrlBuilders = Endpoint.values().associateWith { - urlBuilder.getEmbraceUrlWithSuffix(it.version, it.path) + urlBuilder.resolveUrl(it) } private fun Endpoint.asEmbraceUrl(): String = checkNotNull(apiUrlBuilders[this]) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiResponseCache.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiResponseCache.kt deleted file mode 100644 index 1a57e311b3..0000000000 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiResponseCache.kt +++ /dev/null @@ -1,95 +0,0 @@ -package io.embrace.android.embracesdk.internal.comms.api - -import android.net.http.HttpResponseCache -import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.serialization.PlatformSerializer -import io.embrace.android.embracesdk.internal.storage.StorageService -import java.io.Closeable -import java.io.IOException -import java.net.CacheResponse -import java.net.URI -import java.util.zip.GZIPInputStream - -/** - * Caches HTTP requests made via HttpUrlConnection using [HttpResponseCache]. This is - * currently only used to cache responses from the config endpoint, which contain etags in the - * response headers. - * - * This class therefore provides functions to retrieve the etag for any cached responses. This - * means the eTag can be set in the request header & we can avoid unnecessary work on the client - * & on the server. - */ -class ApiResponseCache( - private val serializer: PlatformSerializer, - private val storageService: StorageService, -) : Closeable { - - private companion object { - private const val MAX_CACHE_SIZE_BYTES: Long = 2 * 1024 * 1024 // 2 MiB - private const val ETAG_HEADER = "ETag" - } - - @Volatile - private var cache: HttpResponseCache? = null - private val lock = Object() - - private fun initializeIfNeeded() { - if (cache == null) { - synchronized(lock) { - if (cache == null) { - cache = runCatching { - HttpResponseCache.install( - storageService.getConfigCacheDir(), - MAX_CACHE_SIZE_BYTES - ) - }.getOrNull() - } - } - } - } - - override fun close() { - cache?.flush() - } - - fun retrieveCachedConfig(url: String, request: ApiRequest): CachedConfig { - val cachedResponse = retrieveCacheResponse(url, request) - val obj = cachedResponse?.runCatching { - GZIPInputStream(body).use { - serializer.fromJson(it, RemoteConfig::class.java) - } - }?.getOrNull() - val eTag = cachedResponse?.let { retrieveETag(cachedResponse) } - return CachedConfig(obj, eTag) - } - - /** - * Retrieves the cache response for the given request, if any exists. - */ - private fun retrieveCacheResponse(url: String, request: ApiRequest): CacheResponse? { - initializeIfNeeded() - val obj = cache ?: return null - - return try { - val uri = URI.create(url) - val requestMethod = request.httpMethod.toString() - val headerFields = request.getHeaders().mapValues { listOf(it.value) } - obj.get(uri, requestMethod, headerFields) - } catch (exc: IOException) { - null - } - } - - /** - * Searches the cache to see whether a request has a cached response, and if so returns its etag. - */ - private fun retrieveETag(cacheResponse: CacheResponse): String? { - runCatching { - val eTag = cacheResponse.headers[ETAG_HEADER] - if (!eTag.isNullOrEmpty()) { - return eTag[0] - } - } - return null - } -} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiService.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiService.kt index e0269036ba..b84009f5cf 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiService.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiService.kt @@ -1,13 +1,12 @@ package io.embrace.android.embracesdk.internal.comms.api import io.embrace.android.embracesdk.internal.capture.connectivity.NetworkConnectivityListener -import io.embrace.android.embracesdk.internal.config.RemoteConfigSource import io.embrace.android.embracesdk.internal.injection.SerializationAction import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.LogPayload import java.util.concurrent.Future -interface ApiService : RemoteConfigSource, NetworkConnectivityListener { +interface ApiService : NetworkConnectivityListener { /** * Sends a list of OTel Logs to the API. diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiUrlBuilder.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiUrlBuilder.kt index 3425794a53..85f7b47b90 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiUrlBuilder.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/ApiUrlBuilder.kt @@ -6,12 +6,22 @@ package io.embrace.android.embracesdk.internal.comms.api interface ApiUrlBuilder { /** - * Returns the url used to fetch the config. + * The App ID that will be used in API requests. */ - fun getConfigUrl(): String + val appId: String + + /** + * The Device ID that will be used in API requests. + */ + val deviceId: String + + /** + * Base URL for the data endpoint + */ + val baseDataUrl: String /** * Returns the url used to send data to Embrace. */ - fun getEmbraceUrlWithSuffix(apiVersion: String, suffix: String): String + fun resolveUrl(endpoint: Endpoint): String } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiService.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiService.kt index 1c7ba875f2..7ab5df10b7 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiService.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiService.kt @@ -1,25 +1,21 @@ package io.embrace.android.embracesdk.internal.comms.api -import io.embrace.android.embracesdk.core.BuildConfig import io.embrace.android.embracesdk.internal.Systrace import io.embrace.android.embracesdk.internal.TypeUtils import io.embrace.android.embracesdk.internal.comms.delivery.NetworkStatus import io.embrace.android.embracesdk.internal.comms.delivery.PendingApiCallsSender import io.embrace.android.embracesdk.internal.compression.ConditionalGzipOutputStream -import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.injection.SerializationAction import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.LogPayload import io.embrace.android.embracesdk.internal.serialization.PlatformSerializer import io.embrace.android.embracesdk.internal.worker.PriorityWorker -import io.embrace.android.embracesdk.network.http.HttpMethod import java.lang.reflect.ParameterizedType import java.util.concurrent.Future internal class EmbraceApiService( private val apiClient: ApiClient, private val serializer: PlatformSerializer, - private val cachedConfigProvider: (url: String, request: ApiRequest) -> CachedConfig, private val priorityWorker: PriorityWorker, private val pendingApiCallsSender: PendingApiCallsSender, lazyDeviceId: Lazy, @@ -32,11 +28,6 @@ internal class EmbraceApiService( ApiRequestMapper(urlBuilder, lazyDeviceId, appId) } } - private val configUrl by lazy { - Systrace.traceSynchronous("config-url-init") { - urlBuilder.getConfigUrl() - } - } private var lastNetworkStatus: NetworkStatus = NetworkStatus.UNKNOWN init { @@ -45,60 +36,6 @@ internal class EmbraceApiService( } } - @Throws(IllegalStateException::class) - @Suppress("UseCheckOrError") - override fun getConfig(): RemoteConfig? { - var request = prepareConfigRequest(configUrl) - val cachedResponse = cachedConfigProvider(configUrl, request) - if (cachedResponse.isValid()) { // only bother if we have a useful response. - request = request.copy(eTag = cachedResponse.eTag) - } - - return when (val response = apiClient.executeGet(request)) { - is ApiResponse.Success -> { - response.body?.let { - serializer.fromJson(it, RemoteConfig::class.java) - } - } - - is ApiResponse.NotModified -> { - cachedResponse.remoteConfig - } - - is ApiResponse.TooManyRequests -> { - // TODO: We should retry after the retryAfter time or 3 seconds and apply exponential backoff. - null - } - - is ApiResponse.Failure, ApiResponse.None, ApiResponse.NoPayload -> { - null - } - - is ApiResponse.Incomplete -> { - throw response.exception - } - - ApiResponse.PayloadTooLarge -> { - // Not expected to receive a 413 response for a GET request. - null - } - } - } - - override fun getCachedConfig(): CachedConfig { - val request = prepareConfigRequest(configUrl) - return cachedConfigProvider(configUrl, request) - } - - private fun prepareConfigRequest(url: String) = ApiRequest( - contentType = "application/json", - userAgent = "Embrace/a/" + BuildConfig.VERSION_NAME, - accept = "application/json", - url = ApiRequestUrl(url), - httpMethod = HttpMethod.GET, - acceptEncoding = "gzip", - ) - override fun onNetworkConnectivityStatusChanged(status: NetworkStatus) { lastNetworkStatus = status } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiUrlBuilder.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiUrlBuilder.kt index 1fded563a5..379e065281 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiUrlBuilder.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiUrlBuilder.kt @@ -1,29 +1,36 @@ package io.embrace.android.embracesdk.internal.comms.api import android.os.Build +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig internal class EmbraceApiUrlBuilder( - private val coreBaseUrl: String, - private val configBaseUrl: String, - private val appId: String, - private val lazyDeviceId: Lazy, - private val lazyAppVersionName: Lazy, + override val deviceId: String, + private val appVersionName: String, + instrumentedConfig: InstrumentedConfig, ) : ApiUrlBuilder { + companion object { - private const val CONFIG_API_VERSION = 2 + private const val CONFIG_DEFAULT: String = "config.emb-api.com" + private const val DATA_DEFAULT: String = "data.emb-api.com" } - private fun getConfigBaseUrl() = "$configBaseUrl/v$CONFIG_API_VERSION/${"config"}" - - private fun getOperatingSystemCode() = Build.VERSION.SDK_INT.toString() + ".0.0" - - override fun getConfigUrl(): String { - return "${getConfigBaseUrl()}?appId=$appId&osVersion=${getOperatingSystemCode()}" + - "&appVersion=${lazyAppVersionName.value}&deviceId=${lazyDeviceId.value}" - } + override val appId: String = checkNotNull(instrumentedConfig.project.getAppId()) + private val coreBaseUrl = instrumentedConfig.baseUrls.getData() ?: "https://a-$appId.$DATA_DEFAULT" + private val configBaseUrl = instrumentedConfig.baseUrls.getConfig() ?: "https://a-$appId.$CONFIG_DEFAULT" + private val operatingSystemCode = Build.VERSION.SDK_INT.toString() + ".0.0" + override val baseDataUrl: String = resolveUrl(Endpoint.SESSIONS).split(Endpoint.SESSIONS.path).first() - override fun getEmbraceUrlWithSuffix(apiVersion: String, suffix: String): String { - val fullSuffix = if (apiVersion == "v1") "log/$suffix" else suffix - return "$coreBaseUrl/$apiVersion/$fullSuffix" + override fun resolveUrl(endpoint: Endpoint): String { + val baseUrl = when (endpoint) { + Endpoint.CONFIG -> configBaseUrl + else -> coreBaseUrl + } + val queryParams = when (endpoint) { + Endpoint.CONFIG -> + "?appId=$appId&osVersion=$operatingSystemCode" + + "&appVersion=$appVersionName&deviceId=$deviceId" + else -> "" + } + return "$baseUrl/${endpoint.version}/${endpoint.path}$queryParams" } } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceUrl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceUrl.kt index 45ec98f1a9..db25dbe087 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceUrl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/EmbraceUrl.kt @@ -18,6 +18,7 @@ internal class EmbraceUrl(val url: URL) { return when (url.path.substringAfterLast("/")) { Endpoint.LOGS.path -> Endpoint.LOGS Endpoint.SESSIONS.path -> Endpoint.SESSIONS + Endpoint.CONFIG.path -> Endpoint.CONFIG else -> Endpoint.UNKNOWN } } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/delivery/PendingApiCallQueue.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/delivery/PendingApiCallQueue.kt index f68f776c7e..2e506c2876 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/delivery/PendingApiCallQueue.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/delivery/PendingApiCallQueue.kt @@ -88,7 +88,7 @@ class PendingApiCallQueue( return when (this) { Endpoint.LOGS -> 10 Endpoint.SESSIONS -> 100 - Endpoint.UNKNOWN -> 50 + else -> 50 } } } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/ConfigService.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/ConfigService.kt index 14b688cc09..0995e588bc 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/ConfigService.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/ConfigService.kt @@ -9,7 +9,6 @@ import io.embrace.android.embracesdk.internal.config.behavior.DataCaptureEventBe import io.embrace.android.embracesdk.internal.config.behavior.LogMessageBehavior import io.embrace.android.embracesdk.internal.config.behavior.NetworkBehavior import io.embrace.android.embracesdk.internal.config.behavior.NetworkSpanForwardingBehavior -import io.embrace.android.embracesdk.internal.config.behavior.SdkEndpointBehavior import io.embrace.android.embracesdk.internal.config.behavior.SdkModeBehavior import io.embrace.android.embracesdk.internal.config.behavior.SensitiveKeysBehavior import io.embrace.android.embracesdk.internal.config.behavior.SessionBehavior @@ -23,8 +22,6 @@ import io.embrace.android.embracesdk.internal.payload.AppFramework */ interface ConfigService { - var remoteConfigSource: RemoteConfigSource? - /** * How background activity functionality should behave. */ @@ -70,11 +67,6 @@ interface ConfigService { */ val sdkModeBehavior: SdkModeBehavior - /** - * Provides base endpoints the SDK should send data to - */ - val sdkEndpointBehavior: SdkEndpointBehavior - /** * Provides whether the SDK should enable certain 'behavior' of web vitals */ @@ -111,49 +103,4 @@ interface ConfigService { * to Embrace). */ fun isOnlyUsingOtelExporters(): Boolean - - /** - * Adds a listener for changes to the [RemoteConfig]. The listeners will be notified when the - * [ConfigService] refreshes its configuration. - * - * @param configListener the listener to add - */ - fun addListener(configListener: () -> Unit) - - /** - * Checks if the SDK is enabled. - * - * The SDK can be configured to disable a percentage of devices based on the normalization of - * their device ID between 1-100. This threshold is set in [RemoteConfig]. - * - * @return true if the sdk is enabled, false otherwise - */ - fun isSdkDisabled(): Boolean - - /** - * Checks if the capture of background activity is enabled. - * - * - * The background activity capture can be configured to enable a percentage of - * devices based on the normalization of their device ID between 1-100. - * - * @return true if background activity capture is enabled. - */ - fun isBackgroundActivityCaptureEnabled(): Boolean - - /** - * Returns true if the remote config has been fetched and is not expired. Generally speaking - * use of this function should be discouraged - but it can be useful to prevent running risky - * behavior that should only be switched on via remote config. - * - * Most callers will not need this function - try not to abuse it. - */ - fun hasValidRemoteConfig(): Boolean - - /** - * Checks if the capture of Application Exit Info is enabled. - * - * @return true if AEI capture is enabled. - */ - fun isAppExitInfoCaptureEnabled(): Boolean } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/ConfigServiceImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/ConfigServiceImpl.kt new file mode 100644 index 0000000000..24a82b3ccb --- /dev/null +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/ConfigServiceImpl.kt @@ -0,0 +1,73 @@ +package io.embrace.android.embracesdk.internal.config + +import io.embrace.android.embracesdk.internal.config.behavior.AnrBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.AppExitInfoBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.AutoDataCaptureBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.BackgroundActivityBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.BehaviorThresholdCheck +import io.embrace.android.embracesdk.internal.config.behavior.BreadcrumbBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.DataCaptureEventBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.LogMessageBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.NetworkBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.NetworkSpanForwardingBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.SdkModeBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.SensitiveKeysBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.SessionBehaviorImpl +import io.embrace.android.embracesdk.internal.config.behavior.WebViewVitalsBehaviorImpl +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.opentelemetry.OpenTelemetryConfiguration +import io.embrace.android.embracesdk.internal.payload.AppFramework +import io.embrace.android.embracesdk.internal.prefs.PreferencesService + +/** + * Loads configuration for the app from the Embrace API. + */ +internal class ConfigServiceImpl( + openTelemetryCfg: OpenTelemetryConfiguration, + preferencesService: PreferencesService, + suppliedFramework: AppFramework, + instrumentedConfig: InstrumentedConfig, + remoteConfig: RemoteConfig?, + thresholdCheck: BehaviorThresholdCheck = BehaviorThresholdCheck { preferencesService.deviceIdentifier }, +) : ConfigService { + + override val backgroundActivityBehavior = + BackgroundActivityBehaviorImpl(thresholdCheck, instrumentedConfig, remoteConfig) + override val autoDataCaptureBehavior = AutoDataCaptureBehaviorImpl(thresholdCheck, instrumentedConfig, remoteConfig) + override val breadcrumbBehavior = BreadcrumbBehaviorImpl(instrumentedConfig, remoteConfig) + override val sensitiveKeysBehavior = SensitiveKeysBehaviorImpl(instrumentedConfig) + override val logMessageBehavior = LogMessageBehaviorImpl(remoteConfig) + override val anrBehavior = AnrBehaviorImpl(thresholdCheck, instrumentedConfig, remoteConfig) + override val sessionBehavior = SessionBehaviorImpl(instrumentedConfig, remoteConfig) + override val networkBehavior = NetworkBehaviorImpl(instrumentedConfig, remoteConfig) + override val dataCaptureEventBehavior = DataCaptureEventBehaviorImpl(remoteConfig) + override val sdkModeBehavior = SdkModeBehaviorImpl(thresholdCheck, remoteConfig) + override val appExitInfoBehavior = AppExitInfoBehaviorImpl(thresholdCheck, instrumentedConfig, remoteConfig) + override val webViewVitalsBehavior = WebViewVitalsBehaviorImpl(thresholdCheck, remoteConfig) + override val networkSpanForwardingBehavior = + NetworkSpanForwardingBehaviorImpl(thresholdCheck, instrumentedConfig, remoteConfig) + + override val appId: String? = resolveAppId(instrumentedConfig.project.getAppId(), openTelemetryCfg) + + override fun isOnlyUsingOtelExporters(): Boolean = appId.isNullOrEmpty() + + /** + * Loads the build information from resources provided by the config file packaged within the application by Gradle at + * build-time. + * + * @return the local configuration + */ + fun resolveAppId(id: String?, openTelemetryCfg: OpenTelemetryConfiguration): String? { + require(!id.isNullOrEmpty() || openTelemetryCfg.hasConfiguredOtelExporters()) { + "No appId supplied in embrace-config.json. This is required if you want to " + + "send data to Embrace, unless you configure an OTel exporter and add" + + " embrace.disableMappingFileUpload=true to gradle.properties." + } + return id + } + + override val appFramework: AppFramework = instrumentedConfig.project.getAppFramework()?.let { + AppFramework.fromString(it) + } ?: suppliedFramework +} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/EmbraceConfigService.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/EmbraceConfigService.kt deleted file mode 100644 index e0e32ac9fe..0000000000 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/EmbraceConfigService.kt +++ /dev/null @@ -1,330 +0,0 @@ -package io.embrace.android.embracesdk.internal.config - -import io.embrace.android.embracesdk.internal.clock.Clock -import io.embrace.android.embracesdk.internal.config.behavior.AnrBehavior -import io.embrace.android.embracesdk.internal.config.behavior.AnrBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.AppExitInfoBehavior -import io.embrace.android.embracesdk.internal.config.behavior.AppExitInfoBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.AutoDataCaptureBehavior -import io.embrace.android.embracesdk.internal.config.behavior.AutoDataCaptureBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.BackgroundActivityBehavior -import io.embrace.android.embracesdk.internal.config.behavior.BackgroundActivityBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.BehaviorThresholdCheck -import io.embrace.android.embracesdk.internal.config.behavior.BreadcrumbBehavior -import io.embrace.android.embracesdk.internal.config.behavior.BreadcrumbBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.DataCaptureEventBehavior -import io.embrace.android.embracesdk.internal.config.behavior.DataCaptureEventBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.LogMessageBehavior -import io.embrace.android.embracesdk.internal.config.behavior.LogMessageBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.NetworkBehavior -import io.embrace.android.embracesdk.internal.config.behavior.NetworkBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.NetworkSpanForwardingBehavior -import io.embrace.android.embracesdk.internal.config.behavior.NetworkSpanForwardingBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.SdkEndpointBehavior -import io.embrace.android.embracesdk.internal.config.behavior.SdkEndpointBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.SdkModeBehavior -import io.embrace.android.embracesdk.internal.config.behavior.SdkModeBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.SensitiveKeysBehavior -import io.embrace.android.embracesdk.internal.config.behavior.SensitiveKeysBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.SessionBehavior -import io.embrace.android.embracesdk.internal.config.behavior.SessionBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.WebViewVitalsBehavior -import io.embrace.android.embracesdk.internal.config.behavior.WebViewVitalsBehaviorImpl -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig -import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.logging.EmbLogger -import io.embrace.android.embracesdk.internal.logging.InternalErrorType -import io.embrace.android.embracesdk.internal.opentelemetry.OpenTelemetryConfiguration -import io.embrace.android.embracesdk.internal.payload.AppFramework -import io.embrace.android.embracesdk.internal.prefs.PreferencesService -import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessStateListener -import io.embrace.android.embracesdk.internal.utils.Provider -import io.embrace.android.embracesdk.internal.utils.stream -import io.embrace.android.embracesdk.internal.worker.BackgroundWorker -import java.util.concurrent.CopyOnWriteArraySet -import kotlin.math.min - -/** - * Loads configuration for the app from the Embrace API. - */ -internal class EmbraceConfigService( - openTelemetryCfg: OpenTelemetryConfiguration, - private val preferencesService: PreferencesService, - private val clock: Clock, - private val logger: EmbLogger, - private val backgroundWorker: BackgroundWorker, - suppliedFramework: AppFramework, - private val foregroundAction: ConfigService.() -> Unit, - appIdFromConfig: String?, - val thresholdCheck: BehaviorThresholdCheck = - BehaviorThresholdCheck { preferencesService.deviceIdentifier }, -) : ConfigService, ProcessStateListener { - - /** - * The listeners subscribed to configuration changes. - */ - private val listeners: MutableSet<() -> Unit> = CopyOnWriteArraySet() - private val lock = Any() - override var remoteConfigSource: RemoteConfigSource? = null - set(value) { - field = value - loadConfigFromCache() - attemptConfigRefresh() - } - - @Volatile - private var configProp = RemoteConfig() - - @Volatile - var lastUpdated: Long = 0 - - @Volatile - private var lastRefreshConfigAttempt: Long = 0 - - @Volatile - private var configRetrySafeWindow = DEFAULT_RETRY_WAIT_TIME.toDouble() - - private val remoteSupplier: Provider = { getConfig() } - - override val backgroundActivityBehavior: BackgroundActivityBehavior = - BackgroundActivityBehaviorImpl( - thresholdCheck = thresholdCheck, - remoteSupplier = { getConfig().backgroundActivityConfig } - ) - - override val autoDataCaptureBehavior: AutoDataCaptureBehavior = - AutoDataCaptureBehaviorImpl( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier - ) - - override val breadcrumbBehavior: BreadcrumbBehavior = - BreadcrumbBehaviorImpl( - thresholdCheck, - remoteSupplier = remoteSupplier - ) - - override val sensitiveKeysBehavior: SensitiveKeysBehavior = SensitiveKeysBehaviorImpl() - - override val logMessageBehavior: LogMessageBehavior = - LogMessageBehaviorImpl( - thresholdCheck, - remoteSupplier = { getConfig().logConfig } - ) - - override val anrBehavior: AnrBehavior = - AnrBehaviorImpl( - thresholdCheck, - remoteSupplier = { getConfig().anrConfig } - ) - - override val sessionBehavior: SessionBehavior = SessionBehaviorImpl( - thresholdCheck, - remoteSupplier = { getConfig() } - ) - - override val networkBehavior: NetworkBehavior = - NetworkBehaviorImpl( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier - ) - - override val dataCaptureEventBehavior: DataCaptureEventBehavior = DataCaptureEventBehaviorImpl( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier - ) - - override val sdkModeBehavior: SdkModeBehavior = SdkModeBehaviorImpl( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier - ) - - override val sdkEndpointBehavior: SdkEndpointBehavior = SdkEndpointBehaviorImpl( - thresholdCheck = thresholdCheck - ) - - override val appExitInfoBehavior: AppExitInfoBehavior = AppExitInfoBehaviorImpl( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier - ) - - override val networkSpanForwardingBehavior: NetworkSpanForwardingBehavior = - NetworkSpanForwardingBehaviorImpl( - thresholdCheck = thresholdCheck, - remoteSupplier = { getConfig().networkSpanForwardingRemoteConfig } - ) - - override val webViewVitalsBehavior: WebViewVitalsBehavior = - WebViewVitalsBehaviorImpl( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier - ) - - override val appId: String? = resolveAppId(appIdFromConfig, openTelemetryCfg) - - override fun isOnlyUsingOtelExporters(): Boolean = appId.isNullOrEmpty() - - /** - * Loads the build information from resources provided by the config file packaged within the application by Gradle at - * build-time. - * - * @return the local configuration - */ - fun resolveAppId(id: String?, openTelemetryCfg: OpenTelemetryConfiguration): String? { - require(!id.isNullOrEmpty() || openTelemetryCfg.hasConfiguredOtelExporters()) { - "No appId supplied in embrace-config.json. This is required if you want to " + - "send data to Embrace, unless you configure an OTel exporter and add" + - " embrace.disableMappingFileUpload=true to gradle.properties." - } - return id - } - - /** - * Load Config from cache if present. - */ - fun loadConfigFromCache() { - val cachedConfig = remoteConfigSource?.getCachedConfig() - val obj = cachedConfig?.remoteConfig - - if (obj != null) { - val oldConfig = configProp - updateConfig(oldConfig, obj) - } - } - - private fun getConfig(): RemoteConfig { - attemptConfigRefresh() - return configProp - } - - private fun attemptConfigRefresh() { - if (configRequiresRefresh() && configRetryIsSafe()) { - synchronized(lock) { - if (configRequiresRefresh() && configRetryIsSafe()) { - lastRefreshConfigAttempt = clock.now() - // Attempt to asynchronously update the config if it is out of date - refreshConfig() - } - } - } - } - - private fun refreshConfig() { - val previousConfig = configProp - backgroundWorker.submit { - // Ensure that another thread didn't refresh it already in the meantime - if (configRequiresRefresh()) { - try { - lastRefreshConfigAttempt = clock.now() - val newConfig = remoteConfigSource?.getConfig() - if (newConfig != null) { - updateConfig(previousConfig, newConfig) - lastUpdated = clock.now() - } - configRetrySafeWindow = DEFAULT_RETRY_WAIT_TIME.toDouble() - } catch (ex: Exception) { - configRetrySafeWindow = - min( - MAX_ALLOWED_RETRY_WAIT_TIME.toDouble(), - configRetrySafeWindow * 2 - ) - } - } - } - } - - private fun updateConfig(previousConfig: RemoteConfig, newConfig: RemoteConfig) { - val b = newConfig != previousConfig - if (b) { - configProp = newConfig - persistConfig() - // Only notify listeners if the config has actually changed value - notifyListeners() - } - } - - private fun persistConfig() { - // TODO: future get rid of these prefs from PrefService entirely? - preferencesService.sdkDisabled = sdkModeBehavior.isSdkDisabled() - preferencesService.backgroundActivityEnabled = - backgroundActivityBehavior.isBackgroundActivityCaptureEnabled() - } - - // TODO: future extract these out to SdkBehavior interface - override fun isSdkDisabled(): Boolean { - return preferencesService.sdkDisabled - } - - override fun isBackgroundActivityCaptureEnabled(): Boolean { - return preferencesService.backgroundActivityEnabled - } - - override fun addListener(configListener: () -> Unit) { - listeners.add(configListener) - } - - override fun onForeground(coldStart: Boolean, timestamp: Long) { - // Refresh the config on resume if it has expired - getConfig() - foregroundAction() - } - - override val appFramework: AppFramework = InstrumentedConfig.project.getAppFramework()?.let { - AppFramework.fromString(it) - } ?: suppliedFramework - - /** - * Notifies the listeners that a new config was fetched from the server. - */ - private fun notifyListeners() { - stream(listeners) { listener -> - try { - listener() - } catch (ex: Exception) { - logger.trackInternalError(InternalErrorType.CONFIG_LISTENER_FAIL, ex) - } - } - } - - /** - * Checks if the time diff since the last fetch exceeds the - * [EmbraceConfigService.CONFIG_TTL] millis. - * - * @return if the config requires to be fetched from the remote server again or not. - */ - private fun configRequiresRefresh(): Boolean { - return clock.now() - lastUpdated > CONFIG_TTL - } - - /** - * Checks if the time diff since the last attempt is enough to try again. - * - * @return if the config can be fetched from the remote server again or not. - */ - private fun configRetryIsSafe(): Boolean { - return clock.now() > lastRefreshConfigAttempt + configRetrySafeWindow * 1000 - } - - override fun hasValidRemoteConfig(): Boolean = !configRequiresRefresh() - override fun isAppExitInfoCaptureEnabled(): Boolean { - return appExitInfoBehavior.isAeiCaptureEnabled() - } - - private companion object { - - /** - * Config lives for 1 hour before attempting to retrieve again. - */ - private const val CONFIG_TTL = 60 * 60 * 1000L - - /** - * Config refresh default retry period. - */ - private const val DEFAULT_RETRY_WAIT_TIME: Long = 20 // 20 seconds - - /** - * Config max allowed refresh retry period. - */ - private const val MAX_ALLOWED_RETRY_WAIT_TIME: Long = 300 // 5 minutes - } -} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/RemoteConfigSource.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/RemoteConfigSource.kt deleted file mode 100644 index 2fd1aadad5..0000000000 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/RemoteConfigSource.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.embrace.android.embracesdk.internal.config - -import io.embrace.android.embracesdk.internal.comms.api.CachedConfig -import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig - -interface RemoteConfigSource { - - /** - * Asynchronously gets the app's SDK configuration. - * - * These settings define app-specific settings, such as disabled log patterns, whether - * screenshots are enabled, as well as limits and thresholds. - * - * @return a future containing the configuration. - */ - fun getConfig(): RemoteConfig? - - fun getCachedConfig(): CachedConfig -} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AnrBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AnrBehavior.kt index 9e605a0492..4e52db4b4d 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AnrBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AnrBehavior.kt @@ -1,9 +1,11 @@ package io.embrace.android.embracesdk.internal.config.behavior +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig import io.embrace.android.embracesdk.internal.config.remote.AllowedNdkSampleMethod +import io.embrace.android.embracesdk.internal.config.remote.AnrRemoteConfig import io.embrace.android.embracesdk.internal.config.remote.Unwinder -interface AnrBehavior { +interface AnrBehavior : ConfigBehavior { /** * Percentage of users for which ANR stack trace capture is enabled. diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AnrBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AnrBehaviorImpl.kt index 02b2f5e5e7..fc956976c9 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AnrBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AnrBehaviorImpl.kt @@ -1,22 +1,18 @@ package io.embrace.android.embracesdk.internal.config.behavior -import io.embrace.android.embracesdk.internal.config.UnimplementedConfig -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.config.remote.AllowedNdkSampleMethod -import io.embrace.android.embracesdk.internal.config.remote.AnrRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.config.remote.Unwinder -import io.embrace.android.embracesdk.internal.utils.Provider /** * Provides the behavior that the ANR feature should follow. */ class AnrBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : AnrBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + private val thresholdCheck: BehaviorThresholdCheck, + local: InstrumentedConfig, + remote: RemoteConfig?, +) : AnrBehavior { private companion object { private const val DEFAULT_ANR_PCT_ENABLED = true @@ -33,6 +29,9 @@ class AnrBehaviorImpl( ) } + override val local = local.enabledFeatures + override val remote = remote?.anrConfig + override fun isAnrCaptureEnabled(): Boolean { return thresholdCheck.isBehaviorEnabled(remote?.pctEnabled) ?: DEFAULT_ANR_PCT_ENABLED @@ -64,7 +63,7 @@ class AnrBehaviorImpl( override fun isUnityAnrCaptureEnabled(): Boolean { return thresholdCheck.isBehaviorEnabled(remote?.pctNativeThreadAnrSamplingEnabled) - ?: InstrumentedConfig.enabledFeatures.isUnityAnrCaptureEnabled() + ?: local.isUnityAnrCaptureEnabled() } override fun isNativeThreadAnrSamplingOffsetEnabled(): Boolean = diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehavior.kt index 59891abe99..4dd2ba5f6a 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface AppExitInfoBehavior { +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.remote.AppExitInfoConfig + +interface AppExitInfoBehavior : ConfigBehavior { fun getTraceMaxLimit(): Int diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehaviorImpl.kt index 9c11f0bc9c..b9d5cbca3c 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehaviorImpl.kt @@ -1,21 +1,18 @@ package io.embrace.android.embracesdk.internal.config.behavior -import io.embrace.android.embracesdk.internal.config.UnimplementedConfig -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider /** * Provides the behavior that should be followed for select services that automatically * capture data. */ class AppExitInfoBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : AppExitInfoBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + private val thresholdCheck: BehaviorThresholdCheck, + local: InstrumentedConfig, + remote: RemoteConfig?, +) : AppExitInfoBehavior { + companion object { /** * Max size of bytes to allow capturing AppExitInfo ndk/anr traces @@ -24,14 +21,17 @@ class AppExitInfoBehaviorImpl( const val AEI_MAX_NUM_DEFAULT: Int = 0 // 0 means no limit } + override val local = local.enabledFeatures + override val remote = remote?.appExitInfoConfig + override fun getTraceMaxLimit(): Int = - remote?.appExitInfoConfig?.appExitInfoTracesLimit + remote?.appExitInfoTracesLimit ?: MAX_TRACE_SIZE_BYTES override fun isAeiCaptureEnabled(): Boolean { - return thresholdCheck.isBehaviorEnabled(remote?.appExitInfoConfig?.pctAeiCaptureEnabled) - ?: InstrumentedConfig.enabledFeatures.isAeiCaptureEnabled() + return thresholdCheck.isBehaviorEnabled(remote?.pctAeiCaptureEnabled) + ?: local.isAeiCaptureEnabled() } - override fun appExitInfoMaxNum(): Int = remote?.appExitInfoConfig?.aeiMaxNum ?: AEI_MAX_NUM_DEFAULT + override fun appExitInfoMaxNum(): Int = remote?.aeiMaxNum ?: AEI_MAX_NUM_DEFAULT } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehavior.kt index a80d09b792..81641b26ff 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface AutoDataCaptureBehavior { +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig + +interface AutoDataCaptureBehavior : ConfigBehavior { /** * Returns true if [io.embrace.android.embracesdk.MemoryService] should diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImpl.kt index e7e61eafa9..bd85048f23 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImpl.kt @@ -1,21 +1,17 @@ package io.embrace.android.embracesdk.internal.config.behavior -import io.embrace.android.embracesdk.internal.config.UnimplementedConfig -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider /** * Provides the behavior that should be followed for select services that automatically * capture data. */ class AutoDataCaptureBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : AutoDataCaptureBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + private val thresholdCheck: BehaviorThresholdCheck, + local: InstrumentedConfig, + override val remote: RemoteConfig?, +) : AutoDataCaptureBehavior { private companion object { const val THERMAL_STATUS_ENABLED_DEFAULT = true @@ -23,30 +19,30 @@ class AutoDataCaptureBehaviorImpl( const val USE_OKHTTP_DEFAULT = true } - private val cfg = InstrumentedConfig.enabledFeatures + override val local = local.enabledFeatures @Suppress("DEPRECATION") - override fun isMemoryWarningCaptureEnabled(): Boolean = cfg.isMemoryWarningCaptureEnabled() + override fun isMemoryWarningCaptureEnabled(): Boolean = local.isMemoryWarningCaptureEnabled() override fun isThermalStatusCaptureEnabled(): Boolean { return thresholdCheck.isBehaviorEnabled(remote?.dataConfig?.pctThermalStatusEnabled) ?: THERMAL_STATUS_ENABLED_DEFAULT } - override fun isPowerSaveModeCaptureEnabled(): Boolean = cfg.isPowerSaveModeCaptureEnabled() + override fun isPowerSaveModeCaptureEnabled(): Boolean = local.isPowerSaveModeCaptureEnabled() override fun isNetworkConnectivityCaptureEnabled(): Boolean = - cfg.isNetworkConnectivityCaptureEnabled() + local.isNetworkConnectivityCaptureEnabled() - override fun isAnrCaptureEnabled(): Boolean = cfg.isAnrCaptureEnabled() - override fun isJvmCrashCaptureEnabled(): Boolean = cfg.isJvmCrashCaptureEnabled() + override fun isAnrCaptureEnabled(): Boolean = local.isAnrCaptureEnabled() + override fun isJvmCrashCaptureEnabled(): Boolean = local.isJvmCrashCaptureEnabled() override fun isComposeClickCaptureEnabled(): Boolean = - remote?.killSwitchConfig?.jetpackCompose ?: cfg.isComposeClickCaptureEnabled() + remote?.killSwitchConfig?.jetpackCompose ?: local.isComposeClickCaptureEnabled() override fun is3rdPartySigHandlerDetectionEnabled(): Boolean = - remote?.killSwitchConfig?.sigHandlerDetection ?: cfg.is3rdPartySigHandlerDetectionEnabled() + remote?.killSwitchConfig?.sigHandlerDetection ?: local.is3rdPartySigHandlerDetectionEnabled() - override fun isNativeCrashCaptureEnabled(): Boolean = cfg.isNativeCrashCaptureEnabled() - override fun isDiskUsageCaptureEnabled(): Boolean = cfg.isDiskUsageCaptureEnabled() + override fun isNativeCrashCaptureEnabled(): Boolean = local.isNativeCrashCaptureEnabled() + override fun isDiskUsageCaptureEnabled(): Boolean = local.isDiskUsageCaptureEnabled() private val v2StorageImpl by lazy { thresholdCheck.isBehaviorEnabled(remote?.killSwitchConfig?.v2StoragePct) ?: V2_STORAGE_ENABLED_DEFAULT diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehavior.kt index 6eef92ec52..9593815f3b 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface BackgroundActivityBehavior { +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig + +interface BackgroundActivityBehavior : ConfigBehavior { /** * Whether the feature is enabled or not. diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehaviorImpl.kt index 14ab1ac00b..12e488b02e 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehaviorImpl.kt @@ -1,25 +1,25 @@ package io.embrace.android.embracesdk.internal.config.behavior -import io.embrace.android.embracesdk.internal.config.UnimplementedConfig -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig /** * Provides the behavior that the Background Activity feature should follow. */ class BackgroundActivityBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : BackgroundActivityBehavior, - MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier - ) { + private val thresholdCheck: BehaviorThresholdCheck, + local: InstrumentedConfig, + remote: RemoteConfig?, +) : BackgroundActivityBehavior { + + override val local: EnabledFeatureConfig = local.enabledFeatures + override val remote: BackgroundActivityRemoteConfig? = remote?.backgroundActivityConfig override fun isBackgroundActivityCaptureEnabled(): Boolean { return remote?.threshold?.let(thresholdCheck::isBehaviorEnabled) - ?: InstrumentedConfig.enabledFeatures.isBackgroundActivityCaptureEnabled() + ?: local.isBackgroundActivityCaptureEnabled() } override fun getManualBackgroundActivityLimit(): Int = 100 diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehavior.kt index 275313f5d6..2d28ea5845 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface BreadcrumbBehavior { +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig + +interface BreadcrumbBehavior : ConfigBehavior { fun getCustomBreadcrumbLimit(): Int fun getFragmentBreadcrumbLimit(): Int diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehaviorImpl.kt index 8a0e6fe5b4..0e9ae8217c 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehaviorImpl.kt @@ -1,21 +1,17 @@ package io.embrace.android.embracesdk.internal.config.behavior -import io.embrace.android.embracesdk.internal.config.UnimplementedConfig -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider /** * Provides the behavior that should be followed for select services that automatically * capture data. */ class BreadcrumbBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : BreadcrumbBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + local: InstrumentedConfig, + override val remote: RemoteConfig?, +) : BreadcrumbBehavior { private companion object { @@ -25,7 +21,7 @@ class BreadcrumbBehaviorImpl( const val DEFAULT_BREADCRUMB_LIMIT = 100 } - private val cfg = InstrumentedConfig.enabledFeatures + override val local: EnabledFeatureConfig = local.enabledFeatures override fun getCustomBreadcrumbLimit(): Int = remote?.uiConfig?.breadcrumbs ?: DEFAULT_BREADCRUMB_LIMIT @@ -38,16 +34,16 @@ class BreadcrumbBehaviorImpl( remote?.uiConfig?.webViews ?: DEFAULT_BREADCRUMB_LIMIT override fun isViewClickCoordinateCaptureEnabled(): Boolean = - cfg.isViewClickCoordinateCaptureEnabled() + local.isViewClickCoordinateCaptureEnabled() override fun isActivityBreadcrumbCaptureEnabled(): Boolean = - cfg.isActivityBreadcrumbCaptureEnabled() + local.isActivityBreadcrumbCaptureEnabled() override fun isWebViewBreadcrumbCaptureEnabled(): Boolean = - cfg.isWebViewBreadcrumbCaptureEnabled() + local.isWebViewBreadcrumbCaptureEnabled() override fun isWebViewBreadcrumbQueryParamCaptureEnabled(): Boolean = - cfg.isWebViewBreadcrumbQueryParamCaptureEnabled() + local.isWebViewBreadcrumbQueryParamCaptureEnabled() - override fun isFcmPiiDataCaptureEnabled(): Boolean = cfg.isFcmPiiDataCaptureEnabled() + override fun isFcmPiiDataCaptureEnabled(): Boolean = local.isFcmPiiDataCaptureEnabled() } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/ConfigBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/ConfigBehavior.kt new file mode 100644 index 0000000000..12272e1ebe --- /dev/null +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/ConfigBehavior.kt @@ -0,0 +1,17 @@ +package io.embrace.android.embracesdk.internal.config.behavior + +/** + * Determines the SDK's behavior at runtime. These values are immutable for the process lifetime. + */ +interface ConfigBehavior { + + /** + * The local config. + */ + val local: L + + /** + * The remote config. + */ + val remote: R? +} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehavior.kt index 1bd75beb70..0baa98ef53 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface DataCaptureEventBehavior { +import io.embrace.android.embracesdk.internal.config.UnimplementedConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig + +interface DataCaptureEventBehavior : ConfigBehavior { fun isInternalExceptionCaptureEnabled(): Boolean fun isEventEnabled(eventName: String): Boolean fun isLogMessageEnabled(logMessage: String): Boolean diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehaviorImpl.kt index 975db6ae24..9ec6135610 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehaviorImpl.kt @@ -3,20 +3,17 @@ package io.embrace.android.embracesdk.internal.config.behavior import io.embrace.android.embracesdk.internal.PatternCache import io.embrace.android.embracesdk.internal.config.UnimplementedConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider class DataCaptureEventBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider = { null }, -) : DataCaptureEventBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + override val remote: RemoteConfig?, +) : DataCaptureEventBehavior { private companion object { private const val DEFAULT_INTERNAL_EXCEPTION_CAPTURE = true } + override val local: UnimplementedConfig = null + private val patternCache = PatternCache() override fun isInternalExceptionCaptureEnabled(): Boolean = diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehavior.kt index 616fba870e..8c3286880d 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface LogMessageBehavior { +import io.embrace.android.embracesdk.internal.config.UnimplementedConfig +import io.embrace.android.embracesdk.internal.config.remote.LogRemoteConfig + +interface LogMessageBehavior : ConfigBehavior { fun getLogMessageMaximumAllowedLength(): Int fun getInfoLogLimit(): Int fun getWarnLogLimit(): Int diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehaviorImpl.kt index a6a35771a1..57865b27c2 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehaviorImpl.kt @@ -2,18 +2,14 @@ package io.embrace.android.embracesdk.internal.config.behavior import io.embrace.android.embracesdk.internal.config.UnimplementedConfig import io.embrace.android.embracesdk.internal.config.remote.LogRemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig /** * Provides the behavior that should be followed for remote log message functionality. */ class LogMessageBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : LogMessageBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + remote: RemoteConfig?, +) : LogMessageBehavior { private companion object { private const val DEFAULT_LOG_INFO_LIMIT = 100 @@ -21,6 +17,9 @@ class LogMessageBehaviorImpl( private const val DEFAULT_LOG_ERROR_LIMIT = 250 } + override val remote: LogRemoteConfig? = remote?.logConfig + override val local: UnimplementedConfig = null + override fun getLogMessageMaximumAllowedLength(): Int { return remote?.logMessageMaximumAllowedLength ?: LOG_MESSAGE_MAXIMUM_ALLOWED_LENGTH } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/MergedConfigBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/MergedConfigBehavior.kt deleted file mode 100644 index 82cec4268c..0000000000 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/MergedConfigBehavior.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.embrace.android.embracesdk.internal.config.behavior - -import io.embrace.android.embracesdk.internal.utils.Provider - -/** - * Merges multiple sources of config and tells the SDK how its functionality should behave. This - * means the caller doesn't need to worry about whether the remote config has been fetched or its - * precedence rules - it just gets told whether it should enable something or not. - * - * There are three sources of config: remote (from the config endpoint); local (from the - * embrace-config.json); and default (defined in subclasses of this type). - * - * Config is typically evaluated in the following precedence: Remote > Local > Default. Remote/local - * configs might not exist for every single field, as it doesn't always make sense for every value - * to be configurable by end-users. However, there should always be a default value. - */ -open class MergedConfigBehavior( - - /** - * Checks whether percent-based thresholds should be enabled or not. We should always return - * booleans about whether functionality is enabled - and should never expose percentages etc - * to the caller. - */ - protected val thresholdCheck: BehaviorThresholdCheck, - - /** - * Supplier for local config, from the embrace-config.json file. - */ - private val localSupplier: Provider = { null }, - - /** - * Supplier for remote config, from the config endpoint. - */ - private val remoteSupplier: Provider = { null }, -) { - - /** - * The local config. This property always returns the most up-to-date value, or null if - * no local config is available. - */ - protected val local: L? - get() = localSupplier() - - /** - * The remote config. This property always returns the most up-to-date value, or null if - * no remote config is available. - */ - protected val remote: R? - get() = remoteSupplier() -} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehavior.kt index 93f1619f48..cecd77f8ba 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehavior.kt @@ -1,8 +1,10 @@ package io.embrace.android.embracesdk.internal.config.behavior +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.config.remote.NetworkCaptureRuleRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -interface NetworkBehavior { +interface NetworkBehavior : ConfigBehavior { /** * Control whether request size for native Android requests is captured. diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehaviorImpl.kt index 079b64adc7..6ff586806b 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehaviorImpl.kt @@ -1,10 +1,8 @@ package io.embrace.android.embracesdk.internal.config.behavior -import io.embrace.android.embracesdk.internal.config.UnimplementedConfig -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.config.remote.NetworkCaptureRuleRemoteConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider import java.util.regex.Pattern import kotlin.math.min @@ -12,13 +10,10 @@ import kotlin.math.min * Provides the behavior that functionality relating to network call capture should follow. */ class NetworkBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, + override val local: InstrumentedConfig, + override val remote: RemoteConfig?, private val disabledUrlPatterns: List? = null, -) : NetworkBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { +) : NetworkBehavior { companion object { @@ -34,13 +29,13 @@ class NetworkBehaviorImpl( ) } - private val cfg = InstrumentedConfig.networkCapture + private val cfg = local.networkCapture override fun isRequestContentLengthCaptureEnabled(): Boolean = - InstrumentedConfig.enabledFeatures.isRequestContentLengthCaptureEnabled() + local.enabledFeatures.isRequestContentLengthCaptureEnabled() override fun isHttpUrlConnectionCaptureEnabled(): Boolean = - InstrumentedConfig.enabledFeatures.isHttpUrlConnectionCaptureEnabled() + local.enabledFeatures.isHttpUrlConnectionCaptureEnabled() override fun getLimitsByDomain(): Map { val limits = remote?.networkConfig?.domainLimits ?: cfg.getLimitsByDomain() diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehavior.kt index 9eca2f524d..cf27edb0af 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehavior.kt @@ -1,5 +1,8 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface NetworkSpanForwardingBehavior { +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.remote.NetworkSpanForwardingRemoteConfig + +interface NetworkSpanForwardingBehavior : ConfigBehavior { fun isNetworkSpanForwardingEnabled(): Boolean } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehaviorImpl.kt index a5a3cf8a7c..e9dce91e95 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehaviorImpl.kt @@ -1,17 +1,16 @@ package io.embrace.android.embracesdk.internal.config.behavior -import io.embrace.android.embracesdk.internal.config.UnimplementedConfig -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.config.remote.NetworkSpanForwardingRemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig class NetworkSpanForwardingBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : NetworkSpanForwardingBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + private val thresholdCheck: BehaviorThresholdCheck, + local: InstrumentedConfig, + remote: RemoteConfig?, +) : NetworkSpanForwardingBehavior { + companion object { /** * Header name for the W3C traceparent @@ -19,8 +18,11 @@ class NetworkSpanForwardingBehaviorImpl( const val TRACEPARENT_HEADER_NAME: String = "traceparent" } + override val local: EnabledFeatureConfig = local.enabledFeatures + override val remote: NetworkSpanForwardingRemoteConfig? = remote?.networkSpanForwardingRemoteConfig + override fun isNetworkSpanForwardingEnabled(): Boolean { return remote?.pctEnabled?.let { thresholdCheck.isBehaviorEnabled(it) } - ?: InstrumentedConfig.enabledFeatures.isNetworkSpanForwardingEnabled() + ?: local.isNetworkSpanForwardingEnabled() } } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkEndpointBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkEndpointBehavior.kt deleted file mode 100644 index cf00721f0b..0000000000 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkEndpointBehavior.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.embrace.android.embracesdk.internal.config.behavior - -interface SdkEndpointBehavior { - - /** - * Data base URL. - */ - fun getData(appId: String?): String - - /** - * Config base URL. - */ - fun getConfig(appId: String?): String -} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkEndpointBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkEndpointBehaviorImpl.kt deleted file mode 100644 index c908301adf..0000000000 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkEndpointBehaviorImpl.kt +++ /dev/null @@ -1,33 +0,0 @@ -package io.embrace.android.embracesdk.internal.config.behavior - -import io.embrace.android.embracesdk.internal.config.UnimplementedConfig -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig - -/** - * Provides the behavior that the Background Activity feature should follow. - */ -class SdkEndpointBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, -) : SdkEndpointBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck -) { - - companion object { - const val CONFIG_DEFAULT: String = "config.emb-api.com" - const val DATA_DEFAULT: String = "data.emb-api.com" - } - - override fun getData(appId: String?): String { - if (appId == null) { - return "" - } - return InstrumentedConfig.baseUrls.getData() ?: "https://a-$appId.$DATA_DEFAULT" - } - - override fun getConfig(appId: String?): String { - if (appId == null) { - return "" - } - return InstrumentedConfig.baseUrls.getConfig() ?: "https://a-$appId.$CONFIG_DEFAULT" - } -} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehavior.kt index e0dcd39c8c..402f546fef 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface SdkModeBehavior { +import io.embrace.android.embracesdk.internal.config.UnimplementedConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig + +interface SdkModeBehavior : ConfigBehavior { /** * Given a Config instance, computes if the SDK is enabled based on the threshold and the offset. diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehaviorImpl.kt index 7821b8a1b5..fa6edb95da 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehaviorImpl.kt @@ -2,7 +2,6 @@ package io.embrace.android.embracesdk.internal.config.behavior import io.embrace.android.embracesdk.internal.config.UnimplementedConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider import kotlin.math.max import kotlin.math.min @@ -10,12 +9,9 @@ import kotlin.math.min * Provides whether the SDK should enable certain 'behavior' modes, such as 'integration mode' */ class SdkModeBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : SdkModeBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + private val thresholdCheck: BehaviorThresholdCheck, + override val remote: RemoteConfig?, +) : SdkModeBehavior { private companion object { @@ -30,6 +26,8 @@ class SdkModeBehaviorImpl( private const val DEFAULT_OFFSET = 0 } + override val local: UnimplementedConfig = null + /** * The % of devices that should be enabled. */ diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehavior.kt index 17384d73e2..9aa968c074 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -fun interface SensitiveKeysBehavior { +import io.embrace.android.embracesdk.internal.config.UnimplementedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.RedactionConfig + +interface SensitiveKeysBehavior : ConfigBehavior { /** * Checks if the given key is sensitive. diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehaviorImpl.kt index 46e417b4f5..50240078f3 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehaviorImpl.kt @@ -1,16 +1,22 @@ package io.embrace.android.embracesdk.internal.config.behavior -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.UnimplementedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.RedactionConfig private const val SENSITIVE_KEY_MAX_LENGTH = 128 private const val SENSITIVE_KEYS_LIST_MAX_SIZE = 10000 const val REDACTED_LABEL: String = "" -class SensitiveKeysBehaviorImpl(denyList: List? = null) : SensitiveKeysBehavior { +class SensitiveKeysBehaviorImpl( + local: InstrumentedConfig, +) : SensitiveKeysBehavior { - private val denyList = - (denyList ?: InstrumentedConfig.redaction.getSensitiveKeysDenylist())?.take(SENSITIVE_KEYS_LIST_MAX_SIZE) + override val local: RedactionConfig = local.redaction + override val remote: UnimplementedConfig = null + + private val denyList = local.redaction.getSensitiveKeysDenylist()?.take(SENSITIVE_KEYS_LIST_MAX_SIZE) override fun isSensitiveKey(key: String): Boolean { return denyList?.any { it.take(SENSITIVE_KEY_MAX_LENGTH) == key } ?: false diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SessionBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SessionBehavior.kt index cdb2f81f62..3ef59e5479 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SessionBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SessionBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface SessionBehavior { +import io.embrace.android.embracesdk.internal.config.instrumented.schema.SessionConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig + +interface SessionBehavior : ConfigBehavior { /** * The whitelist of events (crashes, errors) that should send a full session payload even diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SessionBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SessionBehaviorImpl.kt index 6dd88269ea..b7c44824ea 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SessionBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/SessionBehaviorImpl.kt @@ -1,34 +1,32 @@ package io.embrace.android.embracesdk.internal.config.behavior -import io.embrace.android.embracesdk.internal.config.UnimplementedConfig -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.SessionConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.gating.SessionGatingKeys -import io.embrace.android.embracesdk.internal.utils.Provider import java.util.Locale /** * Provides the behavior that functionality relating to sessions should follow. */ class SessionBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : SessionBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + local: InstrumentedConfig, + override val remote: RemoteConfig?, +) : SessionBehavior { companion object { const val SESSION_PROPERTY_LIMIT: Int = 10 } + override val local: SessionConfig = local.session + override fun getFullSessionEvents(): Set { - val strings = remote?.sessionConfig?.fullSessionEvents ?: InstrumentedConfig.session.getFullSessionEvents() + val strings = remote?.sessionConfig?.fullSessionEvents ?: local.getFullSessionEvents() return strings.map { it.lowercase(Locale.US) }.toSet() } override fun getSessionComponents(): Set? = - (remote?.sessionConfig?.sessionComponents ?: InstrumentedConfig.session.getSessionComponents())?.toSet() + (remote?.sessionConfig?.sessionComponents ?: local.getSessionComponents())?.toSet() override fun isGatingFeatureEnabled(): Boolean = getSessionComponents() != null diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/WebViewVitalsBehavior.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/WebViewVitalsBehavior.kt index 7950e3b9e3..3222362af8 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/WebViewVitalsBehavior.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/WebViewVitalsBehavior.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.config.behavior -interface WebViewVitalsBehavior { +import io.embrace.android.embracesdk.internal.config.UnimplementedConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig + +interface WebViewVitalsBehavior : ConfigBehavior { fun getMaxWebViewVitals(): Int fun isWebViewVitalsEnabled(): Boolean } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/WebViewVitalsBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/WebViewVitalsBehaviorImpl.kt index 2131bf498e..d3db9519af 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/WebViewVitalsBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/WebViewVitalsBehaviorImpl.kt @@ -2,15 +2,11 @@ package io.embrace.android.embracesdk.internal.config.behavior import io.embrace.android.embracesdk.internal.config.UnimplementedConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider class WebViewVitalsBehaviorImpl( - thresholdCheck: BehaviorThresholdCheck, - remoteSupplier: Provider, -) : WebViewVitalsBehavior, MergedConfigBehavior( - thresholdCheck = thresholdCheck, - remoteSupplier = remoteSupplier -) { + private val thresholdCheck: BehaviorThresholdCheck, + override val remote: RemoteConfig?, +) : WebViewVitalsBehavior { private companion object { /** @@ -24,6 +20,8 @@ class WebViewVitalsBehaviorImpl( private const val DEFAULT_MAX_VITALS = 300 } + override val local: UnimplementedConfig = null + private fun getWebVitalsPct(): Float = remote?.webViewVitals?.pctEnabled ?: DEFAULT_WEB_VITALS_PCT override fun getMaxWebViewVitals(): Int = remote?.webViewVitals?.maxVitals ?: DEFAULT_MAX_VITALS diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/CombinedRemoteConfigSource.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/CombinedRemoteConfigSource.kt new file mode 100644 index 0000000000..ccd23dcb82 --- /dev/null +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/CombinedRemoteConfigSource.kt @@ -0,0 +1,46 @@ +package io.embrace.android.embracesdk.internal.config.source + +import io.embrace.android.embracesdk.internal.Systrace +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.config.store.RemoteConfigStore +import io.embrace.android.embracesdk.internal.worker.BackgroundWorker +import java.util.concurrent.TimeUnit + +class CombinedRemoteConfigSource( + private val store: RemoteConfigStore, + httpSource: Lazy, + private val worker: BackgroundWorker, + private val intervalMs: Long = 60 * 60 * 1000 +) { + + private val httpSource: RemoteConfigSource by httpSource + + // the remote config that is used for the lifetime of the process. + private val response by lazy { + Systrace.traceSynchronous("load-config-from-store") { + store.loadResponse() + } + } + + fun getConfig(): RemoteConfig? = response?.cfg + + fun scheduleConfigRequests() { + Systrace.traceSynchronous("set-initial-etag") { + response?.etag?.let(httpSource::setInitialEtag) + } + Systrace.traceSynchronous("schedule-http-request") { + worker.scheduleWithFixedDelay( + ::attemptConfigRequest, + 0, + intervalMs, + TimeUnit.MILLISECONDS + ) + } + } + + private fun attemptConfigRequest() { + httpSource.getConfig()?.let { + store.saveResponse(it) + } + } +} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/ConfigHttpResponse.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/ConfigHttpResponse.kt new file mode 100644 index 0000000000..a542c5774e --- /dev/null +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/ConfigHttpResponse.kt @@ -0,0 +1,8 @@ +package io.embrace.android.embracesdk.internal.config.source + +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig + +data class ConfigHttpResponse( + val cfg: RemoteConfig?, + val etag: String?, +) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/OkHttpRemoteConfigSource.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/OkHttpRemoteConfigSource.kt new file mode 100644 index 0000000000..763fb68b53 --- /dev/null +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/OkHttpRemoteConfigSource.kt @@ -0,0 +1,83 @@ +package io.embrace.android.embracesdk.internal.config.source + +import io.embrace.android.embracesdk.core.BuildConfig +import io.embrace.android.embracesdk.internal.comms.api.ApiRequest +import io.embrace.android.embracesdk.internal.comms.api.ApiRequestUrl +import io.embrace.android.embracesdk.internal.comms.api.ApiUrlBuilder +import io.embrace.android.embracesdk.internal.comms.api.Endpoint +import io.embrace.android.embracesdk.internal.comms.api.getHeaders +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.serialization.PlatformSerializer +import io.embrace.android.embracesdk.network.http.HttpMethod +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okio.GzipSource +import okio.buffer +import java.io.IOException + +internal class OkHttpRemoteConfigSource( + private val okhttpClient: OkHttpClient, + private val apiUrlBuilder: ApiUrlBuilder, + private val serializer: PlatformSerializer, +) : RemoteConfigSource { + + override fun getConfig(): ConfigHttpResponse? = try { + fetchConfigImpl() + } catch (exc: IOException) { + null + } + + override fun setInitialEtag(etag: String) { + this.etag = etag + } + + private var etag: String? = null + + private fun fetchConfigImpl(): ConfigHttpResponse? { + val request = prepareRequest() + val call = okhttpClient.newCall(request) + val response = call.execute() + return processResponse(response) + } + + private fun prepareRequest(): Request { + val url = apiUrlBuilder.resolveUrl(Endpoint.CONFIG) + val headers = prepareConfigRequest(url).getHeaders() + val builder = Request.Builder().url(url) + + etag?.let { + builder.header("If-None-Match", it) + } + headers.forEach { entry -> + builder.header(entry.key, entry.value) + } + val request = builder.build() + return request + } + + private fun processResponse(response: Response): ConfigHttpResponse? { + response.header("etag")?.let { + this.etag = it + } + if (!response.isSuccessful) { + return null + } + val cfg = response.body?.source()?.use { src -> + val gzipSource = GzipSource(src) + gzipSource.buffer().inputStream().use { + serializer.fromJson(it, RemoteConfig::class.java) + } + } + return ConfigHttpResponse(cfg, etag) + } + + private fun prepareConfigRequest(url: String) = ApiRequest( + userAgent = "Embrace/a/" + BuildConfig.VERSION_NAME, + url = ApiRequestUrl(url), + httpMethod = HttpMethod.GET, + acceptEncoding = "gzip", + appId = apiUrlBuilder.appId, + deviceId = apiUrlBuilder.deviceId, + ) +} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/RemoteConfigSource.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/RemoteConfigSource.kt new file mode 100644 index 0000000000..7655fff992 --- /dev/null +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/source/RemoteConfigSource.kt @@ -0,0 +1,14 @@ +package io.embrace.android.embracesdk.internal.config.source + +interface RemoteConfigSource { + + /** + * Gets the remotely delivered configuration that should apply to the app for the lifetime of this process, if any. + */ + fun getConfig(): ConfigHttpResponse? + + /** + * Sets the initial ETag to use for the first request, if any. + */ + fun setInitialEtag(etag: String) +} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/store/RemoteConfigStore.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/store/RemoteConfigStore.kt new file mode 100644 index 0000000000..ebefffa727 --- /dev/null +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/store/RemoteConfigStore.kt @@ -0,0 +1,19 @@ +package io.embrace.android.embracesdk.internal.config.store + +import io.embrace.android.embracesdk.internal.config.source.ConfigHttpResponse + +/** + * Interface for storing and loading the most recently received remote configuration. + */ +interface RemoteConfigStore { + + /** + * Loads the most recent remote configuration, if any. + */ + fun loadResponse(): ConfigHttpResponse? + + /** + * Saves a new remote configuration, overwriting whatever was stored before. + */ + fun saveResponse(response: ConfigHttpResponse) +} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/store/RemoteConfigStoreImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/store/RemoteConfigStoreImpl.kt new file mode 100644 index 0000000000..b72c82d1ca --- /dev/null +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/store/RemoteConfigStoreImpl.kt @@ -0,0 +1,63 @@ +package io.embrace.android.embracesdk.internal.config.store + +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.config.source.ConfigHttpResponse +import io.embrace.android.embracesdk.internal.serialization.PlatformSerializer +import java.io.File + +internal class RemoteConfigStoreImpl( + private val serializer: PlatformSerializer, + storageDir: File, +) : RemoteConfigStore { + + init { + storageDir.mkdirs() + } + + private val configFile = File(storageDir, "most_recent_response").apply { + createNewFile() + } + + private val etagFile = File(storageDir, "etag").apply { + createNewFile() + } + + override fun loadResponse(): ConfigHttpResponse? { + try { + val cfg = configFile.inputStream().buffered().use { + serializer.fromJson(it, RemoteConfig::class.java) + } + return ConfigHttpResponse( + cfg, + etagFile.readText().ifEmpty { + null + } + ) + } catch (exc: Exception) { + return null + } + } + + override fun saveResponse(response: ConfigHttpResponse) { + try { + configFile.outputStream().buffered().use { stream -> + serializer.toJson(response.cfg, RemoteConfig::class.java, stream) + } + response.etag?.let(etagFile::writeText) + } catch (exc: Exception) { + // paranoia: purge the cache + // to avoid the possibility of getting trapped with stale config + // where the SDK is disabled & persistence fails. In that scenario we prefer + // the default SDK behavior which will fetch the correct config eventually + purgeCache() + } + } + + private fun purgeCache() { + try { + configFile.delete() + etagFile.delete() + } catch (ignored: Exception) { + } + } +} diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleImpl.kt index 807aabf0a6..ca361b6d8c 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleImpl.kt @@ -5,23 +5,17 @@ package io.embrace.android.embracesdk.internal.injection import android.preference.PreferenceManager import io.embrace.android.embracesdk.internal.prefs.EmbracePreferencesService import io.embrace.android.embracesdk.internal.prefs.PreferencesService -import io.embrace.android.embracesdk.internal.worker.Worker internal class AndroidServicesModuleImpl( initModule: InitModule, coreModule: CoreModule, - workerThreadModule: WorkerThreadModule, ) : AndroidServicesModule { override val preferencesService: PreferencesService by singleton { - val lazyPrefs = lazy { + EmbracePreferencesService( PreferenceManager.getDefaultSharedPreferences( coreModule.context - ) - } - EmbracePreferencesService( - workerThreadModule.backgroundWorker(Worker.Background.IoRegWorker), - lazyPrefs, + ), initModule.clock, initModule.jsonSerializer ) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleSupplier.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleSupplier.kt index 2ff61d7093..183dcca485 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleSupplier.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleSupplier.kt @@ -6,11 +6,9 @@ package io.embrace.android.embracesdk.internal.injection typealias AndroidServicesModuleSupplier = ( initModule: InitModule, coreModule: CoreModule, - workerThreadModule: WorkerThreadModule, ) -> AndroidServicesModule fun createAndroidServicesModule( initModule: InitModule, coreModule: CoreModule, - workerThreadModule: WorkerThreadModule, -): AndroidServicesModule = AndroidServicesModuleImpl(initModule, coreModule, workerThreadModule) +): AndroidServicesModule = AndroidServicesModuleImpl(initModule, coreModule) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModule.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModule.kt index be6c8636fc..8e07e16e1c 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModule.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModule.kt @@ -1,7 +1,17 @@ package io.embrace.android.embracesdk.internal.injection +import io.embrace.android.embracesdk.internal.comms.api.ApiUrlBuilder import io.embrace.android.embracesdk.internal.config.ConfigService +import io.embrace.android.embracesdk.internal.config.source.CombinedRemoteConfigSource +import io.embrace.android.embracesdk.internal.config.source.RemoteConfigSource +import io.embrace.android.embracesdk.internal.config.store.RemoteConfigStore +import okhttp3.OkHttpClient interface ConfigModule { val configService: ConfigService + val combinedRemoteConfigSource: CombinedRemoteConfigSource? + val remoteConfigSource: RemoteConfigSource? + val remoteConfigStore: RemoteConfigStore + val urlBuilder: ApiUrlBuilder? + val okHttpClient: OkHttpClient } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModuleImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModuleImpl.kt index b756dead12..19846aa3a9 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModuleImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModuleImpl.kt @@ -1,36 +1,96 @@ package io.embrace.android.embracesdk.internal.injection import io.embrace.android.embracesdk.internal.Systrace +import io.embrace.android.embracesdk.internal.comms.api.ApiUrlBuilder +import io.embrace.android.embracesdk.internal.comms.api.EmbraceApiUrlBuilder import io.embrace.android.embracesdk.internal.config.ConfigService -import io.embrace.android.embracesdk.internal.config.EmbraceConfigService -import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.ConfigServiceImpl +import io.embrace.android.embracesdk.internal.config.source.CombinedRemoteConfigSource +import io.embrace.android.embracesdk.internal.config.source.OkHttpRemoteConfigSource +import io.embrace.android.embracesdk.internal.config.store.RemoteConfigStore +import io.embrace.android.embracesdk.internal.config.store.RemoteConfigStoreImpl import io.embrace.android.embracesdk.internal.payload.AppFramework import io.embrace.android.embracesdk.internal.worker.Worker +import okhttp3.OkHttpClient +import okhttp3.Protocol +import java.io.File +import java.util.concurrent.TimeUnit internal class ConfigModuleImpl( initModule: InitModule, + coreModule: CoreModule, openTelemetryModule: OpenTelemetryModule, workerThreadModule: WorkerThreadModule, androidServicesModule: AndroidServicesModule, framework: AppFramework, - private val configServiceProvider: (framework: AppFramework) -> ConfigService? = { null }, - private val foregroundAction: ConfigService.() -> Unit, - private val appIdFromConfig: String? = InstrumentedConfig.project.getAppId(), ) : ConfigModule { + companion object { + private const val DEFAULT_CONNECTION_TIMEOUT_SECONDS = 10L + private const val DEFAULT_READ_TIMEOUT_SECONDS = 60L + } + + override val okHttpClient by singleton { + Systrace.traceSynchronous("okhttp-client-init") { + OkHttpClient() + .newBuilder() + .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1)) + .connectTimeout(DEFAULT_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .readTimeout(DEFAULT_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .build() + } + } + + override val combinedRemoteConfigSource: CombinedRemoteConfigSource? by singleton { + if (initModule.onlyOtelExportEnabled()) return@singleton null + CombinedRemoteConfigSource( + store = remoteConfigStore, + httpSource = lazy { checkNotNull(remoteConfigSource) }, + worker = workerThreadModule.backgroundWorker(Worker.Background.IoRegWorker), + ) + } + override val configService: ConfigService by singleton { Systrace.traceSynchronous("config-service-init") { - configServiceProvider(framework) - ?: EmbraceConfigService( - openTelemetryCfg = openTelemetryModule.openTelemetryConfiguration, - preferencesService = androidServicesModule.preferencesService, - clock = initModule.clock, - logger = initModule.logger, - backgroundWorker = workerThreadModule.backgroundWorker(Worker.Background.IoRegWorker), - suppliedFramework = framework, - foregroundAction = foregroundAction, - appIdFromConfig = appIdFromConfig, - ) + ConfigServiceImpl( + openTelemetryCfg = openTelemetryModule.openTelemetryConfiguration, + preferencesService = androidServicesModule.preferencesService, + suppliedFramework = framework, + instrumentedConfig = initModule.instrumentedConfig, + remoteConfig = combinedRemoteConfigSource?.getConfig(), + ) + } + } + + override val remoteConfigSource by singleton { + if (initModule.onlyOtelExportEnabled()) return@singleton null + OkHttpRemoteConfigSource( + okhttpClient = okHttpClient, + apiUrlBuilder = urlBuilder ?: return@singleton null, + serializer = initModule.jsonSerializer, + ) + } + + override val remoteConfigStore: RemoteConfigStore by singleton { + RemoteConfigStoreImpl( + serializer = initModule.jsonSerializer, + storageDir = File(coreModule.context.filesDir, "embrace_remote_config"), + ) + } + + override val urlBuilder: ApiUrlBuilder? by singleton { + if (initModule.onlyOtelExportEnabled()) return@singleton null + Systrace.traceSynchronous("url-builder-init") { + EmbraceApiUrlBuilder( + deviceId = androidServicesModule.preferencesService.deviceIdentifier, + appVersionName = coreModule.packageVersionInfo.versionName, + instrumentedConfig = initModule.instrumentedConfig, + ) } } + + private fun InitModule.onlyOtelExportEnabled(): Boolean { + instrumentedConfig.project.getAppId() ?: return true + return false + } } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModuleSupplier.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModuleSupplier.kt index 01664e6265..3b4037730c 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModuleSupplier.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/ConfigModuleSupplier.kt @@ -1,6 +1,5 @@ package io.embrace.android.embracesdk.internal.injection -import io.embrace.android.embracesdk.internal.config.ConfigService import io.embrace.android.embracesdk.internal.payload.AppFramework /** @@ -8,28 +7,25 @@ import io.embrace.android.embracesdk.internal.payload.AppFramework */ typealias ConfigModuleSupplier = ( initModule: InitModule, + coreModule: CoreModule, openTelemetryModule: OpenTelemetryModule, workerThreadModule: WorkerThreadModule, androidServicesModule: AndroidServicesModule, framework: AppFramework, - configServiceProvider: (framework: AppFramework) -> ConfigService?, - foregroundAction: ConfigService.() -> Unit, ) -> ConfigModule fun createConfigModule( initModule: InitModule, + coreModule: CoreModule, openTelemetryModule: OpenTelemetryModule, workerThreadModule: WorkerThreadModule, androidServicesModule: AndroidServicesModule, framework: AppFramework, - configServiceProvider: (framework: AppFramework) -> ConfigService? = { null }, - foregroundAction: ConfigService.() -> Unit, ): ConfigModule = ConfigModuleImpl( initModule, + coreModule, openTelemetryModule, workerThreadModule, androidServicesModule, framework, - configServiceProvider, - foregroundAction ) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/CoreModuleImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/CoreModuleImpl.kt index be7a764304..5c1fc3e2af 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/CoreModuleImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/CoreModuleImpl.kt @@ -11,6 +11,7 @@ import io.embrace.android.embracesdk.internal.registry.ServiceRegistry class CoreModuleImpl( ctx: Context, + initModule: InitModule ) : CoreModule { override val context: Context by singleton { @@ -39,6 +40,6 @@ class CoreModuleImpl( } override val buildInfoService: BuildInfoService by lazy { - BuildInfoServiceImpl(resources, context.packageName) + BuildInfoServiceImpl(initModule.instrumentedConfig, resources, context.packageName) } } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/CoreModuleSupplier.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/CoreModuleSupplier.kt index 1ed6d12615..3a46c9c7c1 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/CoreModuleSupplier.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/CoreModuleSupplier.kt @@ -5,8 +5,12 @@ import android.content.Context /** * Function that returns an instance of [CoreModule]. Matches the signature of the constructor for [CoreModuleImpl] */ -typealias CoreModuleSupplier = (context: Context) -> CoreModule +typealias CoreModuleSupplier = ( + context: Context, + initModule: InitModule, +) -> CoreModule fun createCoreModule( context: Context, -): CoreModule = CoreModuleImpl(context) + initModule: InitModule +): CoreModule = CoreModuleImpl(context, initModule) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataSourceModuleImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataSourceModuleImpl.kt index d8c89a3b5f..45c0932c1e 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataSourceModuleImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataSourceModuleImpl.kt @@ -2,18 +2,15 @@ package io.embrace.android.embracesdk.internal.injection import io.embrace.android.embracesdk.internal.arch.DataCaptureOrchestrator import io.embrace.android.embracesdk.internal.arch.EmbraceFeatureRegistry -import io.embrace.android.embracesdk.internal.config.ConfigService import io.embrace.android.embracesdk.internal.worker.Worker internal class DataSourceModuleImpl( initModule: InitModule, - configService: ConfigService, workerThreadModule: WorkerThreadModule, ) : DataSourceModule { override val dataCaptureOrchestrator: DataCaptureOrchestrator by singleton { DataCaptureOrchestrator( - configService, workerThreadModule.backgroundWorker(Worker.Background.NonIoRegWorker), initModule.logger ) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataSourceModuleSupplier.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataSourceModuleSupplier.kt index 859a44cdda..d6b78b4154 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataSourceModuleSupplier.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataSourceModuleSupplier.kt @@ -1,22 +1,17 @@ package io.embrace.android.embracesdk.internal.injection -import io.embrace.android.embracesdk.internal.config.ConfigService - /** * Function that returns an instance of [DataSourceModule]. Matches the signature of the constructor for [DataSourceModuleImpl] */ typealias DataSourceModuleSupplier = ( initModule: InitModule, - configService: ConfigService, workerThreadModule: WorkerThreadModule, ) -> DataSourceModule fun createDataSourceModule( initModule: InitModule, - configService: ConfigService, workerThreadModule: WorkerThreadModule, ): DataSourceModule = DataSourceModuleImpl( initModule, - configService, workerThreadModule ) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DeliveryModuleImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DeliveryModuleImpl.kt index 98938667ad..e72a0691b1 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DeliveryModuleImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DeliveryModuleImpl.kt @@ -1,10 +1,13 @@ package io.embrace.android.embracesdk.internal.injection +import io.embrace.android.embracesdk.core.BuildConfig import io.embrace.android.embracesdk.internal.comms.delivery.DeliveryService import io.embrace.android.embracesdk.internal.comms.delivery.EmbraceDeliveryService import io.embrace.android.embracesdk.internal.delivery.StoredTelemetryMetadata import io.embrace.android.embracesdk.internal.delivery.caching.PayloadCachingService import io.embrace.android.embracesdk.internal.delivery.caching.PayloadCachingServiceImpl +import io.embrace.android.embracesdk.internal.delivery.execution.HttpUrlConnectionRequestExecutionService +import io.embrace.android.embracesdk.internal.delivery.execution.OkHttpRequestExecutionService import io.embrace.android.embracesdk.internal.delivery.execution.RequestExecutionService import io.embrace.android.embracesdk.internal.delivery.intake.IntakeService import io.embrace.android.embracesdk.internal.delivery.intake.IntakeServiceImpl @@ -28,21 +31,11 @@ internal class DeliveryModuleImpl( coreModule: CoreModule, storageModule: StorageModule, essentialServiceModule: EssentialServiceModule, - requestExecutionServiceProvider: Provider, - payloadStorageServiceProvider: Provider, - cacheStorageServiceProvider: Provider, - deliveryServiceProvider: () -> DeliveryService? = { - val apiService = essentialServiceModule.apiService - if (configModule.configService.isOnlyUsingOtelExporters() || apiService == null) { - null - } else { - EmbraceDeliveryService( - storageModule.deliveryCacheManager, - apiService, - initModule.jsonSerializer - ) - } - }, + androidServicesModule: AndroidServicesModule, + requestExecutionServiceProvider: Provider?, + payloadStorageServiceProvider: Provider?, + cacheStorageServiceProvider: Provider?, + deliveryServiceProvider: Provider? ) : DeliveryModule { private val processIdProvider = { otelModule.openTelemetryConfiguration.processIdentifier } @@ -64,7 +57,15 @@ internal class DeliveryModuleImpl( } override val deliveryService: DeliveryService? by singleton { - deliveryServiceProvider() + deliveryServiceProvider?.invoke() ?: if (configModule.configService.isOnlyUsingOtelExporters()) { + null + } else { + EmbraceDeliveryService( + storageModule.deliveryCacheManager, + essentialServiceModule.apiService ?: return@singleton null, + initModule.jsonSerializer + ) + } } private val dataPersistenceWorker: PriorityWorker by singleton { @@ -111,7 +112,7 @@ internal class DeliveryModuleImpl( } override val payloadStorageService: PayloadStorageService? by singleton { - payloadStorageServiceProvider() ?: if (configModule.configService.isOnlyUsingOtelExporters()) { + payloadStorageServiceProvider?.invoke() ?: if (configModule.configService.isOnlyUsingOtelExporters()) { null } else { PayloadStorageServiceImpl( @@ -125,7 +126,7 @@ internal class DeliveryModuleImpl( } override val cacheStorageService: PayloadStorageService? by singleton { - cacheStorageServiceProvider() ?: if (configModule.configService.isOnlyUsingOtelExporters()) { + cacheStorageServiceProvider?.invoke() ?: if (configModule.configService.isOnlyUsingOtelExporters()) { null } else { PayloadStorageServiceImpl( @@ -139,7 +140,31 @@ internal class DeliveryModuleImpl( } override val requestExecutionService: RequestExecutionService? by singleton { - requestExecutionServiceProvider() + requestExecutionServiceProvider?.invoke() ?: if (configModule.configService.isOnlyUsingOtelExporters()) { + null + } else { + val appId = configModule.configService.appId ?: return@singleton null + val coreBaseUrl = configModule.urlBuilder?.baseDataUrl ?: return@singleton null + val lazyDeviceId = lazy(androidServicesModule.preferencesService::deviceIdentifier) + if (configModule.configService.autoDataCaptureBehavior.shouldUseOkHttp()) { + OkHttpRequestExecutionService( + configModule.okHttpClient, + coreBaseUrl, + lazyDeviceId, + appId, + BuildConfig.VERSION_NAME, + initModule.logger, + ) + } else { + HttpUrlConnectionRequestExecutionService( + coreBaseUrl, + lazyDeviceId, + appId, + BuildConfig.VERSION_NAME, + initModule.logger, + ) + } + } } override val schedulingService: SchedulingService? by singleton { diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DeliveryModuleSupplier.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DeliveryModuleSupplier.kt index c8dff6de20..6b98dbef5d 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DeliveryModuleSupplier.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DeliveryModuleSupplier.kt @@ -16,10 +16,11 @@ typealias DeliveryModuleSupplier = ( coreModule: CoreModule, storageModule: StorageModule, essentialServiceModule: EssentialServiceModule, - payloadStorageServiceProvider: Provider, - cacheStorageServiceProvider: Provider, - requestExecutionServiceProvider: Provider, - deliveryServiceProvider: Provider, + androidServicesModule: AndroidServicesModule, + payloadStorageServiceProvider: Provider?, + cacheStorageServiceProvider: Provider?, + requestExecutionServiceProvider: Provider?, + deliveryServiceProvider: Provider?, ) -> DeliveryModule fun createDeliveryModule( @@ -30,10 +31,11 @@ fun createDeliveryModule( coreModule: CoreModule, storageModule: StorageModule, essentialServiceModule: EssentialServiceModule, - payloadStorageServiceProvider: Provider, - cacheStorageServiceProvider: Provider, - requestExecutionServiceProvider: Provider, - deliveryServiceProvider: Provider, + androidServicesModule: AndroidServicesModule, + payloadStorageServiceProvider: Provider?, + cacheStorageServiceProvider: Provider?, + requestExecutionServiceProvider: Provider?, + deliveryServiceProvider: Provider?, ): DeliveryModule = DeliveryModuleImpl( configModule, initModule, @@ -42,6 +44,7 @@ fun createDeliveryModule( coreModule, storageModule, essentialServiceModule, + androidServicesModule, requestExecutionServiceProvider, payloadStorageServiceProvider, cacheStorageServiceProvider, diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/EssentialServiceModule.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/EssentialServiceModule.kt index d742ac7bf0..39eaed4023 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/EssentialServiceModule.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/EssentialServiceModule.kt @@ -6,7 +6,6 @@ import io.embrace.android.embracesdk.internal.capture.session.SessionPropertiesS import io.embrace.android.embracesdk.internal.capture.user.UserService import io.embrace.android.embracesdk.internal.comms.api.ApiClient import io.embrace.android.embracesdk.internal.comms.api.ApiService -import io.embrace.android.embracesdk.internal.comms.api.ApiUrlBuilder import io.embrace.android.embracesdk.internal.comms.delivery.PendingApiCallsSender import io.embrace.android.embracesdk.internal.session.id.SessionIdTracker import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityTracker @@ -20,7 +19,6 @@ interface EssentialServiceModule { val processStateService: ProcessStateService val activityLifecycleTracker: ActivityTracker val userService: UserService - val urlBuilder: ApiUrlBuilder val apiClient: ApiClient val apiService: ApiService? val networkConnectivityService: NetworkConnectivityService diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/EssentialServiceModuleImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/EssentialServiceModuleImpl.kt index f3aa4aec4a..6f50cbc6b0 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/EssentialServiceModuleImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/EssentialServiceModuleImpl.kt @@ -13,11 +13,8 @@ import io.embrace.android.embracesdk.internal.capture.user.EmbraceUserService import io.embrace.android.embracesdk.internal.capture.user.UserService import io.embrace.android.embracesdk.internal.comms.api.ApiClient import io.embrace.android.embracesdk.internal.comms.api.ApiClientImpl -import io.embrace.android.embracesdk.internal.comms.api.ApiRequest import io.embrace.android.embracesdk.internal.comms.api.ApiService -import io.embrace.android.embracesdk.internal.comms.api.ApiUrlBuilder import io.embrace.android.embracesdk.internal.comms.api.EmbraceApiService -import io.embrace.android.embracesdk.internal.comms.api.EmbraceApiUrlBuilder import io.embrace.android.embracesdk.internal.comms.delivery.EmbracePendingApiCallsSender import io.embrace.android.embracesdk.internal.comms.delivery.PendingApiCallsSender import io.embrace.android.embracesdk.internal.session.id.SessionIdTracker @@ -56,25 +53,6 @@ class EssentialServiceModuleImpl( ActivityLifecycleTracker(coreModule.application, initModule.logger) } - override val urlBuilder: ApiUrlBuilder by singleton { - Systrace.traceSynchronous("url-builder-init") { - // We use SdkEndpointBehavior and localConfig directly to avoid a circular dependency - // but we want to access behaviors from ConfigService when possible. - val sdkEndpointBehavior = configService.sdkEndpointBehavior - val appId = checkNotNull(configService.appId) - val coreBaseUrl = sdkEndpointBehavior.getData(appId) - val configBaseUrl = sdkEndpointBehavior.getConfig(appId) - - EmbraceApiUrlBuilder( - coreBaseUrl = coreBaseUrl, - configBaseUrl = configBaseUrl, - appId = appId, - lazyDeviceId = lazyDeviceId, - lazyAppVersionName = lazy { coreModule.packageVersionInfo.versionName } - ) - } - } - override val userService: UserService by singleton { Systrace.traceSynchronous("user-service-init") { EmbraceUserService( @@ -111,16 +89,11 @@ class EssentialServiceModuleImpl( EmbraceApiService( apiClient = apiClient, serializer = initModule.jsonSerializer, - cachedConfigProvider = { url: String, request: ApiRequest -> - Systrace.traceSynchronous("provide-cache-config") { - storageModule.cache.retrieveCachedConfig(url, request) - } - }, priorityWorker = workerThreadModule.priorityWorker(Worker.Priority.NetworkRequestWorker), pendingApiCallsSender = pendingApiCallsSender, lazyDeviceId = lazyDeviceId, appId = appId, - urlBuilder = urlBuilder + urlBuilder = checkNotNull(configModule.urlBuilder) ) } } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/InitModule.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/InitModule.kt index 89da859bf4..d931355be7 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/InitModule.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/InitModule.kt @@ -1,6 +1,7 @@ package io.embrace.android.embracesdk.internal.injection import io.embrace.android.embracesdk.internal.SystemInfo +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.logging.EmbLogger import io.embrace.android.embracesdk.internal.serialization.PlatformSerializer import io.embrace.android.embracesdk.internal.telemetry.TelemetryService @@ -33,4 +34,6 @@ interface InitModule { * Returns the serializer used to serialize data to JSON */ val jsonSerializer: PlatformSerializer + + val instrumentedConfig: InstrumentedConfig } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/InitModuleImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/InitModuleImpl.kt index dfe01d1b8b..9502c0c6d7 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/InitModuleImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/InitModuleImpl.kt @@ -4,6 +4,8 @@ import io.embrace.android.embracesdk.internal.SystemInfo import io.embrace.android.embracesdk.internal.clock.Clock import io.embrace.android.embracesdk.internal.clock.NormalizedIntervalClock import io.embrace.android.embracesdk.internal.clock.SystemClock +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.logging.EmbLogger import io.embrace.android.embracesdk.internal.logging.EmbLoggerImpl import io.embrace.android.embracesdk.internal.serialization.DecoratedSerializer @@ -27,4 +29,6 @@ internal class InitModuleImpl( override val jsonSerializer: PlatformSerializer by singleton { DecoratedSerializer(EmbraceSerializer()) } + + override val instrumentedConfig: InstrumentedConfig = InstrumentedConfigImpl } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/LogModuleImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/LogModuleImpl.kt index 2e02aca1a4..21fb7d63c9 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/LogModuleImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/LogModuleImpl.kt @@ -30,6 +30,7 @@ internal class LogModuleImpl( androidServicesModule.preferencesService, { networkCaptureDataSource }, configModule.configService, + configModule.urlBuilder, initModule.jsonSerializer, initModule.logger ) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/StorageModule.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/StorageModule.kt index 6855f156ed..22232871e8 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/StorageModule.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/StorageModule.kt @@ -1,6 +1,5 @@ package io.embrace.android.embracesdk.internal.injection -import io.embrace.android.embracesdk.internal.comms.api.ApiResponseCache import io.embrace.android.embracesdk.internal.comms.delivery.CacheService import io.embrace.android.embracesdk.internal.comms.delivery.DeliveryCacheManager import io.embrace.android.embracesdk.internal.storage.StorageService @@ -10,7 +9,6 @@ import io.embrace.android.embracesdk.internal.storage.StorageService */ interface StorageModule { val storageService: StorageService - val cache: ApiResponseCache val cacheService: CacheService val deliveryCacheManager: DeliveryCacheManager } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/StorageModuleImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/StorageModuleImpl.kt index 724007130e..9c6a336db8 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/StorageModuleImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/StorageModuleImpl.kt @@ -1,6 +1,5 @@ package io.embrace.android.embracesdk.internal.injection -import io.embrace.android.embracesdk.internal.comms.api.ApiResponseCache import io.embrace.android.embracesdk.internal.comms.delivery.CacheService import io.embrace.android.embracesdk.internal.comms.delivery.DeliveryCacheManager import io.embrace.android.embracesdk.internal.comms.delivery.EmbraceCacheService @@ -25,13 +24,6 @@ internal class StorageModuleImpl( ) } - override val cache by singleton { - ApiResponseCache( - initModule.jsonSerializer, - storageService - ) - } - override val cacheService: CacheService by singleton { EmbraceCacheService( storageService, diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/network/logging/EmbraceNetworkCaptureService.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/network/logging/EmbraceNetworkCaptureService.kt index d52bafad29..bed4991c1b 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/network/logging/EmbraceNetworkCaptureService.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/network/logging/EmbraceNetworkCaptureService.kt @@ -1,5 +1,6 @@ package io.embrace.android.embracesdk.internal.network.logging +import io.embrace.android.embracesdk.internal.comms.api.ApiUrlBuilder import io.embrace.android.embracesdk.internal.config.ConfigService import io.embrace.android.embracesdk.internal.config.remote.NetworkCaptureRuleRemoteConfig import io.embrace.android.embracesdk.internal.logging.EmbLogger @@ -19,6 +20,7 @@ internal class EmbraceNetworkCaptureService( private val preferencesService: PreferencesService, private val networkCaptureDataSource: Provider, private val configService: ConfigService, + private val urlBuilder: ApiUrlBuilder?, private val serializer: PlatformSerializer, private val logger: EmbLogger, ) : NetworkCaptureService { @@ -41,9 +43,10 @@ internal class EmbraceNetworkCaptureService( } // Embrace data endpoint cannot be captured, even if there is a rule for that. - val appId = configService.appId - if (url.contentEquals(configService.sdkEndpointBehavior.getData(appId))) { - return emptySet() + urlBuilder?.baseDataUrl?.let { + if (url.startsWith(it)) { + return emptySet() + } } val applicableRules = networkCaptureRules.filter { rule -> diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/prefs/EmbracePreferencesService.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/prefs/EmbracePreferencesService.kt index 13b5d21c9c..280455100b 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/prefs/EmbracePreferencesService.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/prefs/EmbracePreferencesService.kt @@ -1,48 +1,16 @@ package io.embrace.android.embracesdk.internal.prefs import android.content.SharedPreferences -import io.embrace.android.embracesdk.internal.Systrace import io.embrace.android.embracesdk.internal.clock.Clock import io.embrace.android.embracesdk.internal.serialization.PlatformSerializer import io.embrace.android.embracesdk.internal.utils.Uuid.getEmbUuid -import io.embrace.android.embracesdk.internal.worker.BackgroundWorker -import java.util.concurrent.Callable -import java.util.concurrent.Future -import java.util.concurrent.TimeUnit internal class EmbracePreferencesService( - private val backgroundWorker: BackgroundWorker, - private val lazyPrefs: Lazy, + private val prefs: SharedPreferences, private val clock: Clock, private val serializer: PlatformSerializer, ) : PreferencesService { - // We get SharedPreferences on a background thread because it loads data from disk - // and can block. When client code needs to set/get a preference, getSharedPrefs() will - // block if necessary with Future.get(). Eagerly offloading buys us more time - // for SharedPreferences to load the File and reduces the likelihood of blocking - // when invoked by client code. - private val preferences: Future = Systrace.traceSynchronous("trigger-load-prefs") { - backgroundWorker.submit( - callable = Callable { - Systrace.traceSynchronous("load-prefs") { - lazyPrefs.value - } - } - ) - } - - // fallback from this very unlikely case by just loading on the main thread - private val prefs: SharedPreferences - get() = try { - Systrace.traceSynchronous("get-prefs") { - preferences.get(2, TimeUnit.SECONDS) - } - } catch (exc: Throwable) { - // fallback from this very unlikely case by just loading on the main thread - lazyPrefs.value - } - private fun SharedPreferences.getStringPreference(key: String): String? { return getString(key, null) } @@ -159,10 +127,6 @@ internal class EmbracePreferencesService( } set(value) = prefs.setStringPreference(DEVICE_IDENTIFIER_KEY, value) - override var sdkDisabled: Boolean - get() = prefs.getBooleanPreference(SDK_DISABLED_KEY, false) - set(value) = prefs.setBooleanPreference(SDK_DISABLED_KEY, value) - override var userPayer: Boolean get() = prefs.getBooleanPreference(USER_IS_PAYER_KEY, false) set(value) = prefs.setBooleanPreference(USER_IS_PAYER_KEY, value) @@ -191,10 +155,6 @@ internal class EmbracePreferencesService( get() = prefs.getLongPreference(SDK_CONFIG_FETCHED_TIMESTAMP) set(value) = prefs.setLongPreference(SDK_CONFIG_FETCHED_TIMESTAMP, value) - override var userMessageNeedsRetry: Boolean - get() = prefs.getBooleanPreference(LAST_USER_MESSAGE_FAILED_KEY, false) - set(value) = prefs.setBooleanPreference(LAST_USER_MESSAGE_FAILED_KEY, value) - override fun incrementAndGetSessionNumber(): Int { return incrementAndGetOrdinal(LAST_SESSION_NUMBER_KEY) } @@ -275,10 +235,6 @@ internal class EmbracePreferencesService( get() = prefs.getStringPreference(SCREEN_RESOLUTION_KEY) set(value) = prefs.setStringPreference(SCREEN_RESOLUTION_KEY, value) - override var backgroundActivityEnabled: Boolean - get() = prefs.getBooleanPreference(BACKGROUND_ACTIVITY_ENABLED_KEY, false) - set(value) = prefs.setBooleanPreference(BACKGROUND_ACTIVITY_ENABLED_KEY, value) - override var applicationExitInfoHistory: Set? get() = prefs.getStringSet(AEI_HASH_CODES, null) set(value) = prefs.setArrayPreference(AEI_HASH_CODES, value) @@ -309,8 +265,6 @@ internal class EmbracePreferencesService( } companion object { - internal const val SDK_STARTUP_IN_PROGRESS = "startup_entered" - internal const val SDK_STARTUP_COMPLETED = "startup_completed" private const val DEVICE_IDENTIFIER_KEY = "io.embrace.deviceid" private const val PREVIOUS_APP_VERSION_KEY = "io.embrace.lastappversion" private const val PREVIOUS_OS_VERSION_KEY = "io.embrace.lastosversion" @@ -320,7 +274,6 @@ internal class EmbracePreferencesService( private const val USER_USERNAME_KEY = "io.embrace.username" private const val USER_IS_PAYER_KEY = "io.embrace.userispayer" private const val USER_PERSONAS_KEY = "io.embrace.userpersonas" - private const val LAST_USER_MESSAGE_FAILED_KEY = "io.embrace.userupdatefailed" private const val LAST_SESSION_NUMBER_KEY = "io.embrace.sessionnumber" private const val LAST_BACKGROUND_ACTIVITY_NUMBER_KEY = "io.embrace.bgactivitynumber" private const val LAST_CRASH_NUMBER_KEY = "io.embrace.crashnumber" @@ -338,9 +291,7 @@ internal class EmbracePreferencesService( private const val EMBRACE_FLUTTER_SDK_VERSION_KEY = "io.embrace.flutter.sdk.version" private const val IS_JAILBROKEN_KEY = "io.embrace.is_jailbroken" private const val SCREEN_RESOLUTION_KEY = "io.embrace.screen.resolution" - private const val BACKGROUND_ACTIVITY_ENABLED_KEY = "io.embrace.bgactivitycapture" private const val NETWORK_CAPTURE_RULE_PREFIX_KEY = "io.embrace.networkcapturerule" - private const val SDK_DISABLED_KEY = "io.embrace.disabled" private const val SDK_CONFIG_FETCHED_TIMESTAMP = "io.embrace.sdkfetchedtimestamp" private const val AEI_HASH_CODES = "io.embrace.aeiHashCode" } diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/prefs/PreferencesService.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/prefs/PreferencesService.kt index f3a7e27e60..9f29f26de5 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/prefs/PreferencesService.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/prefs/PreferencesService.kt @@ -22,11 +22,6 @@ interface PreferencesService { */ var deviceIdentifier: String - /** - * If the sdk is disabled - */ - var sdkDisabled: Boolean - /** * If the user is payer */ @@ -62,11 +57,6 @@ interface PreferencesService { */ var lastConfigFetchDate: Long? - /** - * If the user message needs to retry send - */ - var userMessageNeedsRetry: Boolean - /** * Increments and returns the session number ordinal. This is an integer that increments * at the start of every session. This allows us to check the % of sessions that didn't get @@ -155,11 +145,6 @@ interface PreferencesService { */ var screenResolution: String? - /** - * If background activity capture is enabled - */ - var backgroundActivityEnabled: Boolean - /** * Set of hashcodes derived from ApplicationExitInfo objects */ diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/session/message/PayloadFactoryImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/session/message/PayloadFactoryImpl.kt index 5ae5f5d991..f627c491ce 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/session/message/PayloadFactoryImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/session/message/PayloadFactoryImpl.kt @@ -195,5 +195,5 @@ internal class PayloadFactoryImpl( ) } - private fun isBackgroundActivityEnabled(): Boolean = configService.isBackgroundActivityCaptureEnabled() + private fun isBackgroundActivityEnabled(): Boolean = configService.backgroundActivityBehavior.isBackgroundActivityCaptureEnabled() } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/DataCaptureOrchestratorTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/DataCaptureOrchestratorTest.kt index 0a37a1e48e..426d6b21cf 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/DataCaptureOrchestratorTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/DataCaptureOrchestratorTest.kt @@ -39,24 +39,11 @@ internal class DataCaptureOrchestratorTest { configService = FakeConfigService() executorService = BlockingScheduledExecutorService(blockingMode = false) orchestrator = DataCaptureOrchestrator( - configService, BackgroundWorker(executorService), EmbLoggerImpl(), ) } - @Test - fun `config changes are propagated`() { - orchestrator.add(syncDataSource) - assertEquals(0, dataSource.enableDataCaptureCount) - orchestrator.currentSessionType = SessionType.FOREGROUND - assertEquals(1, dataSource.enableDataCaptureCount) - - enabled = false - configService.updateListeners() - assertEquals(1, dataSource.disableDataCaptureCount) - } - @Test fun `session type change is propagated`() { orchestrator.add(syncDataSource) @@ -65,23 +52,6 @@ internal class DataCaptureOrchestratorTest { assertEquals(1, dataSource.enableDataCaptureCount) } - @Test - fun `async config change`() { - orchestrator.add(asyncDataSource) - executorService.blockingMode = true - - orchestrator.currentSessionType = SessionType.FOREGROUND - assertEquals(0, dataSource.enableDataCaptureCount) - executorService.runCurrentlyBlocked() - assertEquals(1, dataSource.enableDataCaptureCount) - - enabled = false - configService.updateListeners() - assertEquals(0, dataSource.disableDataCaptureCount) - executorService.runCurrentlyBlocked() - assertEquals(1, dataSource.disableDataCaptureCount) - } - @Test fun `async session change`() { orchestrator.add(asyncDataSource) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/DataSourceStateTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/DataSourceStateTest.kt index c9e41f82f2..0c54d86ee4 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/DataSourceStateTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/DataSourceStateTest.kt @@ -21,15 +21,9 @@ internal class DataSourceStateTest { configGate = { true } ) - // data source not retrievable if not session type is null + // data source not retrievable if the session type is null assertNull(state.dataSource) - // data capture is enabled by default. - state.onConfigChange() - state.currentSessionType = null - assertEquals(0, source.enableDataCaptureCount) - assertEquals(0, source.disableDataCaptureCount) - // data capture enabled for a session state.currentSessionType = SessionType.FOREGROUND assertSame(source, state.dataSource) @@ -64,7 +58,7 @@ internal class DataSourceStateTest { } @Test - fun `test config gate enabled by default`() { + fun `test config gate enabled`() { val source = FakeDataSource(RuntimeEnvironment.getApplication()) DataSourceState( factory = { source }, @@ -79,47 +73,18 @@ internal class DataSourceStateTest { } @Test - fun `test config gate affects data capture`() { + fun `test config gate disabled`() { val source = FakeDataSource(RuntimeEnvironment.getApplication()) - var enabled = false - val state = DataSourceState( + DataSourceState( factory = { source }, - configGate = { enabled }, + configGate = { false }, ).apply { currentSessionType = SessionType.FOREGROUND } - // data source not retrievable if disabled - assertNull(state.dataSource) - - // data capture is disabled by default. + // data capture is enabled by default. assertEquals(0, source.enableDataCaptureCount) assertEquals(0, source.disableDataCaptureCount) - - // enabling the config gate should enable data capture - enabled = true - state.onConfigChange() - assertEquals(1, source.enableDataCaptureCount) - assertEquals(0, source.disableDataCaptureCount) - - // another config change should not reregister listeners - state.onConfigChange() - assertEquals(1, source.enableDataCaptureCount) - assertEquals(0, source.disableDataCaptureCount) - - // deregistering works - enabled = false - state.onConfigChange() - assertEquals(1, source.enableDataCaptureCount) - assertEquals(1, source.disableDataCaptureCount) - - // functions can be called multiple times without issue - enabled = true - state.onConfigChange() - enabled = false - state.onConfigChange() - assertEquals(2, source.enableDataCaptureCount) - assertEquals(2, source.disableDataCaptureCount) } @Test diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/schema/TelemetryAttributesTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/schema/TelemetryAttributesTest.kt index 50beb6946b..7707dc192b 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/schema/TelemetryAttributesTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/arch/schema/TelemetryAttributesTest.kt @@ -105,14 +105,12 @@ internal class TelemetryAttributesTest { fun `log properties and session properties are not included in the attributes`() { val configService = FakeConfigService( sessionBehavior = createSessionBehavior( - remoteCfg = { - RemoteConfig( - sessionConfig = SessionRemoteConfig( - fullSessionEvents = setOf(), - sessionComponents = setOf() - ) + remoteCfg = RemoteConfig( + sessionConfig = SessionRemoteConfig( + fullSessionEvents = setOf(), + sessionComponents = setOf() ) - } + ) ) ) sessionPropertiesService.addProperty("perm", "permVal", true) @@ -133,14 +131,12 @@ internal class TelemetryAttributesTest { fun `log properties and session properties are included in the attributes`() { val configService = FakeConfigService( sessionBehavior = createSessionBehavior( - remoteCfg = { - RemoteConfig( - sessionConfig = SessionRemoteConfig( - fullSessionEvents = setOf(), - sessionComponents = setOf("s_props", "log_pr") - ) + remoteCfg = RemoteConfig( + sessionConfig = SessionRemoteConfig( + fullSessionEvents = setOf(), + sessionComponents = setOf("s_props", "log_pr") ) - } + ) ) ) sessionPropertiesService.addProperty("perm", "permVal", true) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/capture/session/EmbraceSessionPropertiesTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/capture/session/EmbraceSessionPropertiesTest.kt index a4a80a65e1..8aa13c942b 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/capture/session/EmbraceSessionPropertiesTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/capture/session/EmbraceSessionPropertiesTest.kt @@ -10,7 +10,6 @@ import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan import io.embrace.android.embracesdk.fakes.behavior.FakeSessionBehavior -import io.embrace.android.embracesdk.fakes.fakeBackgroundWorker import io.embrace.android.embracesdk.internal.prefs.EmbracePreferencesService import io.embrace.android.embracesdk.internal.prefs.PreferencesService import io.embrace.android.embracesdk.internal.serialization.EmbraceSerializer @@ -43,11 +42,10 @@ internal class EmbraceSessionPropertiesTest { @Before fun setUp() { - val worker = fakeBackgroundWorker() context = ApplicationProvider.getApplicationContext() - val prefs = lazy { PreferenceManager.getDefaultSharedPreferences(context) } + val prefs = PreferenceManager.getDefaultSharedPreferences(context) preferencesService = - EmbracePreferencesService(worker, prefs, fakeClock, EmbraceSerializer()) + EmbracePreferencesService(prefs, fakeClock, EmbraceSerializer()) configService = FakeConfigService( sessionBehavior = FakeSessionBehavior(MAX_SESSION_PROPERTIES_DEFAULT) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/capture/session/SessionPropertiesServiceImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/capture/session/SessionPropertiesServiceImplTest.kt index 22d3276dad..406a45f15d 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/capture/session/SessionPropertiesServiceImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/capture/session/SessionPropertiesServiceImplTest.kt @@ -3,6 +3,8 @@ package io.embrace.android.embracesdk.internal.capture.session import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan import io.embrace.android.embracesdk.fakes.FakePreferenceService +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeRedactionConfig import io.embrace.android.embracesdk.internal.config.behavior.REDACTED_LABEL import io.embrace.android.embracesdk.internal.config.behavior.SensitiveKeysBehaviorImpl import org.junit.Assert.assertEquals @@ -21,7 +23,9 @@ internal class SessionPropertiesServiceImplTest { fun setUp() { val fakeConfigService = FakeConfigService( - sensitiveKeysBehavior = SensitiveKeysBehaviorImpl(listOf("password")) + sensitiveKeysBehavior = SensitiveKeysBehaviorImpl( + FakeInstrumentedConfig(redaction = FakeRedactionConfig(sensitiveKeys = listOf("password"))), + ) ) fakeCurrentSessionSpan = FakeCurrentSessionSpan() service = SessionPropertiesServiceImpl( diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/ApiRequestMapperTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/ApiRequestMapperTest.kt index 27065ba416..de8105a82a 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/ApiRequestMapperTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/ApiRequestMapperTest.kt @@ -1,5 +1,7 @@ package io.embrace.android.embracesdk.internal.comms.api +import io.embrace.android.embracesdk.fakes.config.FakeBaseUrlConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.internal.payload.Attribute import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.Log @@ -19,11 +21,14 @@ internal class ApiRequestMapperTest { private val deviceId = lazy { "deviceId" } private val mapper = ApiRequestMapper( urlBuilder = EmbraceApiUrlBuilder( - BASE_URL, - CONFIG_URL, - "appId", - deviceId, - lazy { "appVersionName" } + deviceId.value, + "1.0", + FakeInstrumentedConfig( + baseUrls = FakeBaseUrlConfig( + configImpl = CONFIG_URL, + dataImpl = BASE_URL + ) + ) ), lazyDeviceId = deviceId, appId = "appId" diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/CachedConfigTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/CachedConfigTest.kt deleted file mode 100644 index e7d6810714..0000000000 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/CachedConfigTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.embrace.android.embracesdk.internal.comms.api - -import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test - -internal class CachedConfigTest { - - @Test - fun isValid() { - assertFalse(CachedConfig(null, null).isValid()) - assertFalse(CachedConfig(RemoteConfig(), null).isValid()) - assertFalse(CachedConfig(null, "ba09cc").isValid()) - assertTrue(CachedConfig(RemoteConfig(), "ba09cc").isValid()) - } -} diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiServiceTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiServiceTest.kt index db8ca53ac9..f293e5b210 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiServiceTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiServiceTest.kt @@ -1,16 +1,14 @@ package io.embrace.android.embracesdk.internal.comms.api -import io.embrace.android.embracesdk.ResourceReader import io.embrace.android.embracesdk.concurrency.BlockingScheduledExecutorService import io.embrace.android.embracesdk.fakes.FakeApiClient import io.embrace.android.embracesdk.fakes.FakeDeliveryCacheManager import io.embrace.android.embracesdk.fakes.FakePendingApiCallsSender +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.internal.TypeUtils -import io.embrace.android.embracesdk.internal.comms.api.ApiClient.Companion.NO_HTTP_RESPONSE import io.embrace.android.embracesdk.internal.comms.delivery.DeliveryCacheManager import io.embrace.android.embracesdk.internal.comms.delivery.NetworkStatus import io.embrace.android.embracesdk.internal.compression.ConditionalGzipOutputStream -import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.payload.Attribute import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.Log @@ -24,7 +22,6 @@ import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull -import org.junit.Assert.assertSame import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -42,23 +39,17 @@ internal class EmbraceApiServiceTest { private lateinit var fakeApiClient: FakeApiClient private lateinit var fakeCacheManager: DeliveryCacheManager private lateinit var testScheduledExecutor: BlockingScheduledExecutorService - private lateinit var cachedConfig: CachedConfig private lateinit var apiService: EmbraceApiService private lateinit var fakePendingApiCallsSender: FakePendingApiCallsSender @Before fun setUp() { apiUrlBuilder = EmbraceApiUrlBuilder( - coreBaseUrl = "https://a-$fakeAppId.data.emb-api.com", - configBaseUrl = "https://a-$fakeAppId.config.emb-api.com", - appId = fakeAppId, - lazyDeviceId = lazy { fakeDeviceId }, - lazyAppVersionName = lazy { fakeAppVersionName } + deviceId = fakeDeviceId, + appVersionName = fakeAppVersionName, + instrumentedConfig = FakeInstrumentedConfig() ) fakeApiClient = FakeApiClient() - cachedConfig = CachedConfig( - remoteConfig = RemoteConfig() - ) testScheduledExecutor = BlockingScheduledExecutorService(blockingMode = false) fakeCacheManager = FakeDeliveryCacheManager() fakePendingApiCallsSender = FakePendingApiCallsSender() @@ -72,78 +63,6 @@ internal class EmbraceApiServiceTest { } } - @Test - fun `test getConfig returns correct values in Response`() { - fakeApiClient.queueResponse( - ApiResponse.Success( - headers = emptyMap(), - body = defaultConfigResponseBody - ) - ) - - val remoteConfig = apiService.getConfig() - - // verify a few fields were serialized correctly. - checkNotNull(remoteConfig) - assertTrue(checkNotNull(remoteConfig.sessionConfig?.isEnabled)) - assertEquals(100, remoteConfig.threshold) - } - - @Test(expected = IllegalStateException::class) - fun `getConfig throws an exception when receiving ApiResponse_Incomplete`() { - val incompleteResponse: ApiResponse.Incomplete = ApiResponse.Incomplete( - IllegalStateException("Connection failed") - ) - fakeApiClient.queueResponse(incompleteResponse) - apiService.getConfig() - } - - @Test - fun `cached remote config returned when 304 received`() { - fakeApiClient.queueResponse( - ApiResponse.NotModified - ) - assertEquals(cachedConfig.remoteConfig, apiService.getConfig()) - } - - @Test - fun `getConfig did not complete returns a null config`() { - fakeApiClient.queueResponse( - ApiResponse.Failure( - code = NO_HTTP_RESPONSE, - headers = emptyMap() - ) - ) - assertNull(apiService.getConfig()) - } - - @Test - fun `getConfig results in unexpected response code returns a null config`() { - fakeApiClient.queueResponse( - ApiResponse.Failure( - code = 400, - headers = emptyMap() - ) - ) - assertNull(apiService.getConfig()) - } - - @Test - fun testGetConfigWithMatchingEtag() { - val cfg = RemoteConfig() - cachedConfig = CachedConfig(cfg, "my_etag") - fakeApiClient.queueResponse( - ApiResponse.NotModified - ) - val remoteConfig = apiService.getConfig() - assertSame(cfg, remoteConfig) - } - - @Test - fun `getCacheConfig returns what the provider provides`() { - assertEquals(apiService.getCachedConfig(), cachedConfig) - } - @Test fun `send v2 session`() { fakeApiClient.queueResponse(successfulPostResponse) @@ -216,23 +135,6 @@ internal class EmbraceApiServiceTest { assertArrayEquals(getGenericsExpectedPayloadSerialized(logsEnvelope, type), payload) } - @Test - fun `validate all API endpoint URLs`() { - Endpoint.values().forEach { - if (it.version == "v1") { - assertEquals( - "https://a-$fakeAppId.data.emb-api.com/v1/log/${it.path}", - apiUrlBuilder.getEmbraceUrlWithSuffix("v1", it.path) - ) - } else { - assertEquals( - "https://a-$fakeAppId.data.emb-api.com/v2/${it.path}", - apiUrlBuilder.getEmbraceUrlWithSuffix("v2", it.path) - ) - } - } - } - @Test fun `network request runnable is used`() { initApiService() @@ -430,7 +332,6 @@ internal class EmbraceApiServiceTest { apiService = EmbraceApiService( apiClient = fakeApiClient, serializer = serializer, - cachedConfigProvider = { _, _ -> cachedConfig }, priorityWorker = PriorityWorker(testScheduledExecutor), pendingApiCallsSender = fakePendingApiCallsSender, lazyDeviceId = lazy { fakeDeviceId }, @@ -441,11 +342,9 @@ internal class EmbraceApiServiceTest { } companion object { - private const val fakeAppId = "A1B2C" + private const val fakeAppId = "abcde" private const val fakeDeviceId = "ajflkadsflkadslkfjds" private const val fakeAppVersionName = "6.1.0" - private val defaultConfigResponseBody = - ResourceReader.readResourceAsText("remote_config_response.json") private val successfulPostResponse = ApiResponse.Success( headers = emptyMap(), body = "" diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiUrlBuilderTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiUrlBuilderTest.kt index 5c33f75f9e..138998099a 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiUrlBuilderTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/api/EmbraceApiUrlBuilderTest.kt @@ -1,13 +1,11 @@ package io.embrace.android.embracesdk.internal.comms.api -import io.embrace.android.embracesdk.fakes.createSdkEndpointBehavior -import io.mockk.unmockkAll -import org.junit.After +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -private const val APP_ID = "o0o0o" +private const val APP_ID = "abcde" private const val APP_VERSION_NAME = "1.0.0" private const val DEVICE_ID = "07D85B44E4E245F4A30E559BFC0D07FF" @@ -16,36 +14,27 @@ internal class EmbraceApiUrlBuilderTest { @Before fun setup() { - val baseUrlLocalConfig = createSdkEndpointBehavior() - apiUrlBuilder = EmbraceApiUrlBuilder( - coreBaseUrl = baseUrlLocalConfig.getData(APP_ID), - configBaseUrl = baseUrlLocalConfig.getConfig(APP_ID), - appId = APP_ID, - lazyDeviceId = lazy { DEVICE_ID }, - lazyAppVersionName = lazy { APP_VERSION_NAME }, + deviceId = DEVICE_ID, + appVersionName = APP_VERSION_NAME, + FakeInstrumentedConfig() ) } - @After - fun tearDown() { - unmockkAll() - } - @Test fun testUrls() { assertEquals( "https://a-$APP_ID.config.emb-api.com/v2/config?appId=$APP_ID&osVersion=0.0.0" + "&appVersion=$APP_VERSION_NAME&deviceId=$DEVICE_ID", - apiUrlBuilder.getConfigUrl() + apiUrlBuilder.resolveUrl(Endpoint.CONFIG) ) assertEquals( - "https://a-$APP_ID.data.emb-api.com/v1/log/suffix", - apiUrlBuilder.getEmbraceUrlWithSuffix("v1", "suffix") + "https://a-$APP_ID.data.emb-api.com/v2/logs", + apiUrlBuilder.resolveUrl(Endpoint.LOGS) ) assertEquals( - "https://a-$APP_ID.data.emb-api.com/v2/suffix", - apiUrlBuilder.getEmbraceUrlWithSuffix("v2", "suffix") + "https://a-$APP_ID.data.emb-api.com/v2/spans", + apiUrlBuilder.resolveUrl(Endpoint.SESSIONS) ) } } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/delivery/EmbracePendingApiCallsSenderTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/delivery/EmbracePendingApiCallsSenderTest.kt index b2c880ad0a..5d80158e77 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/delivery/EmbracePendingApiCallsSenderTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/delivery/EmbracePendingApiCallsSenderTest.kt @@ -2,6 +2,7 @@ package io.embrace.android.embracesdk.internal.comms.delivery import io.embrace.android.embracesdk.concurrency.BlockingScheduledExecutorService import io.embrace.android.embracesdk.fakes.FakeClock +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.internal.comms.api.ApiRequest import io.embrace.android.embracesdk.internal.comms.api.ApiRequestMapper import io.embrace.android.embracesdk.internal.comms.api.ApiRequestUrl @@ -251,11 +252,9 @@ internal class EmbracePendingApiCallsSenderTest { repeat(15) { val mapper = ApiRequestMapper( EmbraceApiUrlBuilder( - "https://data.emb-api.com/$it", - "https://config.emb-api.com", - "appId", - lazy { "deviceId" }, - lazy { "appVersionName" } + "deviceId", + "appVersionName", + FakeInstrumentedConfig() ), lazy { "deviceId" }, "appId" @@ -271,22 +270,20 @@ internal class EmbracePendingApiCallsSenderTest { // verify logs were added to the queue, and oldest added requests are dropped assertEquals( - "https://data.emb-api.com/5/v2/logs", + "https://a-abcde.data.emb-api.com/v2/logs", queue.pollNextPendingApiCall()?.apiRequest?.url?.url ) assertEquals( - "https://data.emb-api.com/6/v2/logs", + "https://a-abcde.data.emb-api.com/v2/logs", queue.pollNextPendingApiCall()?.apiRequest?.url?.url ) // now add some sessions for retry and verify they are returned first val mapper = ApiRequestMapper( EmbraceApiUrlBuilder( - "https://data.emb-api.com/session", - "https://config.emb-api.com", - "appId", - lazy { "deviceId" }, - lazy { "appVersionName" } + "deviceId", + "appVersionName", + FakeInstrumentedConfig() ), lazy { "deviceId" }, "appId" diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/delivery/PendingApiCallsTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/delivery/PendingApiCallsTest.kt index 86d2068eec..7332f8e719 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/delivery/PendingApiCallsTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/comms/delivery/PendingApiCallsTest.kt @@ -181,8 +181,8 @@ internal class PendingApiCallsTest { private fun Endpoint.getMaxPendingApiCalls(): Int { return when (this) { Endpoint.LOGS -> 10 - Endpoint.UNKNOWN -> 50 Endpoint.SESSIONS -> 100 + else -> 50 } } } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/ConfigServiceImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/ConfigServiceImplTest.kt new file mode 100644 index 0000000000..ec62bc11f9 --- /dev/null +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/ConfigServiceImplTest.kt @@ -0,0 +1,204 @@ +package io.embrace.android.embracesdk.internal.config + +import io.embrace.android.embracesdk.concurrency.BlockingScheduledExecutorService +import io.embrace.android.embracesdk.fakes.FakeClock +import io.embrace.android.embracesdk.fakes.FakeLogRecordExporter +import io.embrace.android.embracesdk.fakes.FakePreferenceService +import io.embrace.android.embracesdk.fakes.FakeProcessStateService +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeProjectConfig +import io.embrace.android.embracesdk.internal.SystemInfo +import io.embrace.android.embracesdk.internal.config.behavior.BehaviorThresholdCheck +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.logging.EmbLogger +import io.embrace.android.embracesdk.internal.logging.EmbLoggerImpl +import io.embrace.android.embracesdk.internal.logs.LogSinkImpl +import io.embrace.android.embracesdk.internal.opentelemetry.OpenTelemetryConfiguration +import io.embrace.android.embracesdk.internal.payload.AppFramework +import io.embrace.android.embracesdk.internal.prefs.PreferencesService +import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessStateService +import io.embrace.android.embracesdk.internal.spans.SpanSinkImpl +import io.embrace.android.embracesdk.internal.worker.BackgroundWorker +import io.mockk.clearAllMocks +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.junit.After +import org.junit.AfterClass +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test + +internal class ConfigServiceImplTest { + + private lateinit var fakePreferenceService: PreferencesService + private lateinit var service: ConfigServiceImpl + private lateinit var worker: BackgroundWorker + private lateinit var executor: BlockingScheduledExecutorService + private lateinit var thresholdCheck: BehaviorThresholdCheck + + companion object { + private lateinit var remoteConfig: RemoteConfig + private lateinit var processStateService: ProcessStateService + private lateinit var logger: EmbLogger + private lateinit var fakeClock: FakeClock + + /** + * Setup before all tests get executed. Create mocks here. + */ + @BeforeClass + @JvmStatic + fun setupBeforeAll() { + mockkStatic(RemoteConfig::class) + remoteConfig = RemoteConfig() + processStateService = FakeProcessStateService() + fakeClock = FakeClock() + logger = EmbLoggerImpl() + } + + /** + * Setup after all tests get executed. Un-mock all here. + */ + @AfterClass + @JvmStatic + fun tearDownAfterAll() { + unmockkAll() + } + } + + /** + * Setup before each test. + */ + @Before + fun setup() { + fakeClock.setCurrentTime(1000000000000) + fakePreferenceService = FakePreferenceService(deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D07FF") + executor = BlockingScheduledExecutorService(blockingMode = false) + worker = BackgroundWorker(executor) + service = createService() + assertFalse(service.isOnlyUsingOtelExporters()) + } + + /** + * Setup after each test. Clean mocks content. + */ + @After + fun tearDown() { + clearAllMocks() + } + + @Suppress("DEPRECATION") + @Test + fun `test legacy normalized DeviceId`() { + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D0700" + assertEquals(0.0, thresholdCheck.getNormalizedDeviceId().toDouble(), 0.01) + + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D07FF" + assertEquals(100.0, thresholdCheck.getNormalizedDeviceId().toDouble(), 0.01) + + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D0739" + assertEquals(22.35, thresholdCheck.getNormalizedDeviceId().toDouble(), 0.01) + + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D07D9" + assertEquals(85.09, thresholdCheck.getNormalizedDeviceId().toDouble(), 0.01) + } + + @Test + fun `test new normalized DeviceId`() { + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC000000" + assertEquals(0.0, thresholdCheck.getNormalizedLargeDeviceId().toDouble(), 0.01) + + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFCFFFFFF" + assertEquals(100.0, thresholdCheck.getNormalizedLargeDeviceId().toDouble(), 0.01) + + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D0739" + assertEquals(5.08, thresholdCheck.getNormalizedLargeDeviceId().toDouble(), 0.01) + + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFCED0739" + assertEquals(92.58, thresholdCheck.getNormalizedLargeDeviceId().toDouble(), 0.01) + } + + @Test + fun `test isBehaviourEnabled`() { + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC000000" + assertFalse(thresholdCheck.isBehaviorEnabled(0.0f)) + assertTrue(thresholdCheck.isBehaviorEnabled(0.1f)) + assertTrue(thresholdCheck.isBehaviorEnabled(100.0f)) + assertTrue(thresholdCheck.isBehaviorEnabled(99.9f)) + assertTrue(thresholdCheck.isBehaviorEnabled(34.9f)) + + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFCFFFFFF" + assertFalse(thresholdCheck.isBehaviorEnabled(99.9f)) + assertTrue(thresholdCheck.isBehaviorEnabled(100.0f)) + + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D0739" + assertFalse(thresholdCheck.isBehaviorEnabled(0.0f)) + assertFalse(thresholdCheck.isBehaviorEnabled(2.0f)) + assertFalse(thresholdCheck.isBehaviorEnabled(5.0f)) + assertFalse(thresholdCheck.isBehaviorEnabled(5.07f)) + assertTrue(thresholdCheck.isBehaviorEnabled(5.09f)) + assertTrue(thresholdCheck.isBehaviorEnabled(47.92f)) + assertTrue(thresholdCheck.isBehaviorEnabled(100.0f)) + } + + @Test + fun `test isBehaviourEnabled with bad input`() { + fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFCFFFFFF" + assertFalse(thresholdCheck.isBehaviorEnabled(1000f)) + assertFalse(thresholdCheck.isBehaviorEnabled(-1000f)) + } + + @Test + fun `test app framework`() { + assertEquals(AppFramework.NATIVE, service.appFramework) + } + + @Test(expected = IllegalArgumentException::class) + fun testEmptyAppId() { + createService(appId = null) + } + + @Test(expected = IllegalArgumentException::class) + fun testNullAppId() { + createService(appId = null) + } + + @Test + fun testNoAppIdRequiredWithExporters() { + val cfg = OpenTelemetryConfiguration( + SpanSinkImpl(), + LogSinkImpl(), + SystemInfo() + ) + cfg.addLogExporter(FakeLogRecordExporter()) + val service = createService(config = cfg, appId = null) + assertNotNull(service) + assertTrue(service.isOnlyUsingOtelExporters()) + } + + /** + * Create a new instance of the [ConfigServiceImpl] using the passed in [worker] to run + * tasks for its internal [BackgroundWorker] + */ + private fun createService( + config: OpenTelemetryConfiguration = OpenTelemetryConfiguration( + SpanSinkImpl(), + LogSinkImpl(), + SystemInfo() + ), + appId: String? = "AbCdE", + ): ConfigServiceImpl { + thresholdCheck = BehaviorThresholdCheck { fakePreferenceService.deviceIdentifier } + return ConfigServiceImpl( + openTelemetryCfg = config, + preferencesService = fakePreferenceService, + suppliedFramework = AppFramework.NATIVE, + instrumentedConfig = FakeInstrumentedConfig(project = FakeProjectConfig(appId = appId)), + remoteConfig = remoteConfig, + thresholdCheck = thresholdCheck + ) + } +} diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/EmbraceConfigServiceTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/EmbraceConfigServiceTest.kt deleted file mode 100644 index 2683920eb0..0000000000 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/EmbraceConfigServiceTest.kt +++ /dev/null @@ -1,346 +0,0 @@ -package io.embrace.android.embracesdk.internal.config - -import io.embrace.android.embracesdk.concurrency.BlockingScheduledExecutorService -import io.embrace.android.embracesdk.fakes.FakeClock -import io.embrace.android.embracesdk.fakes.FakeLogRecordExporter -import io.embrace.android.embracesdk.fakes.FakePreferenceService -import io.embrace.android.embracesdk.fakes.FakeProcessStateService -import io.embrace.android.embracesdk.fakes.fakeBackgroundWorker -import io.embrace.android.embracesdk.internal.SystemInfo -import io.embrace.android.embracesdk.internal.comms.api.ApiService -import io.embrace.android.embracesdk.internal.comms.api.CachedConfig -import io.embrace.android.embracesdk.internal.comms.delivery.CacheService -import io.embrace.android.embracesdk.internal.config.remote.AnrRemoteConfig -import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.logging.EmbLogger -import io.embrace.android.embracesdk.internal.logging.EmbLoggerImpl -import io.embrace.android.embracesdk.internal.logs.LogSinkImpl -import io.embrace.android.embracesdk.internal.opentelemetry.OpenTelemetryConfiguration -import io.embrace.android.embracesdk.internal.payload.AppFramework -import io.embrace.android.embracesdk.internal.prefs.PreferencesService -import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessStateService -import io.embrace.android.embracesdk.internal.spans.SpanSinkImpl -import io.embrace.android.embracesdk.internal.worker.BackgroundWorker -import io.mockk.clearAllMocks -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkAll -import org.junit.After -import org.junit.AfterClass -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.BeforeClass -import org.junit.Test - -internal class EmbraceConfigServiceTest { - - private lateinit var fakePreferenceService: PreferencesService - private lateinit var service: EmbraceConfigService - private lateinit var worker: BackgroundWorker - - companion object { - private lateinit var remoteConfig: RemoteConfig - private lateinit var mockApiService: ApiService - private lateinit var processStateService: ProcessStateService - private lateinit var mockCacheService: CacheService - private lateinit var logger: EmbLogger - private lateinit var fakeClock: FakeClock - private lateinit var mockConfigListener: () -> Unit - private lateinit var fakeCachedConfig: RemoteConfig - private var configListenerTriggered = false - - /** - * Setup before all tests get executed. Create mocks here. - */ - @BeforeClass - @JvmStatic - fun setupBeforeAll() { - mockkStatic(RemoteConfig::class) - remoteConfig = RemoteConfig() - mockApiService = mockk() - processStateService = FakeProcessStateService() - mockCacheService = mockk(relaxed = true) - fakeClock = FakeClock() - logger = EmbLoggerImpl() - configListenerTriggered = false - mockConfigListener = { configListenerTriggered = true } - fakeCachedConfig = RemoteConfig( // alter config to trigger listener - anrConfig = AnrRemoteConfig() - ) - } - - /** - * Setup after all tests get executed. Un-mock all here. - */ - @AfterClass - @JvmStatic - fun tearDownAfterAll() { - unmockkAll() - } - } - - /** - * Setup before each test. - */ - @Before - fun setup() { - fakeClock.setCurrentTime(1000000000000) - every { mockApiService.getConfig() } returns remoteConfig - fakePreferenceService = FakePreferenceService(deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D07FF") - every { - mockCacheService.loadObject("config.json", RemoteConfig::class.java) - } returns fakeCachedConfig - every { mockApiService.getCachedConfig() } returns CachedConfig(fakeCachedConfig, null) - worker = fakeBackgroundWorker() - service = createService(worker = worker, action = {}) - assertFalse(service.isOnlyUsingOtelExporters()) - } - - /** - * Setup after each test. Clean mocks content. - */ - @After - fun tearDown() { - clearAllMocks() - } - - @Suppress("DEPRECATION") - @Test - fun `test legacy normalized DeviceId`() { - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D0700" - assertEquals(0.0, service.thresholdCheck.getNormalizedDeviceId().toDouble(), 0.01) - - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D07FF" - assertEquals(100.0, service.thresholdCheck.getNormalizedDeviceId().toDouble(), 0.01) - - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D0739" - assertEquals(22.35, service.thresholdCheck.getNormalizedDeviceId().toDouble(), 0.01) - - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D07D9" - assertEquals(85.09, service.thresholdCheck.getNormalizedDeviceId().toDouble(), 0.01) - } - - @Test - fun `test new normalized DeviceId`() { - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC000000" - assertEquals(0.0, service.thresholdCheck.getNormalizedLargeDeviceId().toDouble(), 0.01) - - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFCFFFFFF" - assertEquals(100.0, service.thresholdCheck.getNormalizedLargeDeviceId().toDouble(), 0.01) - - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D0739" - assertEquals(5.08, service.thresholdCheck.getNormalizedLargeDeviceId().toDouble(), 0.01) - - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFCED0739" - assertEquals(92.58, service.thresholdCheck.getNormalizedLargeDeviceId().toDouble(), 0.01) - } - - @Test - fun `test isBehaviourEnabled`() { - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC000000" - assertFalse(service.thresholdCheck.isBehaviorEnabled(0.0f)) - assertTrue(service.thresholdCheck.isBehaviorEnabled(0.1f)) - assertTrue(service.thresholdCheck.isBehaviorEnabled(100.0f)) - assertTrue(service.thresholdCheck.isBehaviorEnabled(99.9f)) - assertTrue(service.thresholdCheck.isBehaviorEnabled(34.9f)) - - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFCFFFFFF" - assertFalse(service.thresholdCheck.isBehaviorEnabled(99.9f)) - assertTrue(service.thresholdCheck.isBehaviorEnabled(100.0f)) - - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFC0D0739" - assertFalse(service.thresholdCheck.isBehaviorEnabled(0.0f)) - assertFalse(service.thresholdCheck.isBehaviorEnabled(2.0f)) - assertFalse(service.thresholdCheck.isBehaviorEnabled(5.0f)) - assertFalse(service.thresholdCheck.isBehaviorEnabled(5.07f)) - assertTrue(service.thresholdCheck.isBehaviorEnabled(5.09f)) - assertTrue(service.thresholdCheck.isBehaviorEnabled(47.92f)) - assertTrue(service.thresholdCheck.isBehaviorEnabled(100.0f)) - } - - @Test - fun `test isBehaviourEnabled with bad input`() { - fakePreferenceService.deviceIdentifier = "07D85B44E4E245F4A30E559BFCFFFFFF" - assertFalse(service.thresholdCheck.isBehaviorEnabled(1000f)) - assertFalse(service.thresholdCheck.isBehaviorEnabled(-1000f)) - } - - @Test - fun `test config exists in cache and is loaded correctly`() { - assertTrue(service.anrBehavior.isAnrCaptureEnabled()) - - val obj = RemoteConfig(anrConfig = AnrRemoteConfig(pctEnabled = 0)) - every { mockApiService.getCachedConfig() } returns CachedConfig(obj, null) - service.loadConfigFromCache() - - // config was updated - assertFalse(service.anrBehavior.isAnrCaptureEnabled()) - } - - @Test - fun `test config does not exist in cache, so it's not loaded`() { - assertTrue(service.anrBehavior.isAnrCaptureEnabled()) - every { mockApiService.getCachedConfig() } returns CachedConfig(null, null) - service.loadConfigFromCache() - - // config was not updated - assertTrue(service.anrBehavior.isAnrCaptureEnabled()) - } - - @Test - fun `test service constructor reads cached config`() { - val obj = RemoteConfig(anrConfig = AnrRemoteConfig(pctEnabled = 0)) - every { mockApiService.getConfig() } returns null - every { mockApiService.getCachedConfig() } returns CachedConfig(obj, null) - service = createService(worker) - - // config was updated - assertFalse(service.anrBehavior.isAnrCaptureEnabled()) - } - - /** - * Test that calling getConfig() notifies the listener. - * As we are using a DirectExecutor this method will run synchronously and - * return the updated config. - * In a real situation, the async refresh would be triggered and the config returned would be the previous one. - */ - @Test - fun `test getConfig() notifies a listener`() { - // advance the clock so it's safe to retry config refresh - fakeClock.tick(1000000000000) - - // return a different object from default so listener triggers - val newConfig = RemoteConfig(anrConfig = AnrRemoteConfig(sampleIntervalMs = 200)) - every { mockApiService.getConfig() } returns newConfig - fakePreferenceService.sdkDisabled = false - service.addListener(mockConfigListener) - - // call an arbitrary function to trigger a config refresh - service.anrBehavior.isAnrCaptureEnabled() - assertTrue(configListenerTriggered) - } - - /** - * Test that calling getConfig() refreshes the config and notify the listener - * As we are using a DirectExecutor this method will run synchronously and - * return the updated config. - * In a real situation, the async refresh would be triggered and the config returned would be the previous one. - */ - @Test - fun `test onForeground() refreshes the config`() { - // advance the clock so it's safe to retry config refresh - fakeClock.tick(1000000000000) - val newConfig = RemoteConfig(anrConfig = AnrRemoteConfig()) - every { mockApiService.getConfig() } returns newConfig - fakePreferenceService.sdkDisabled = false - service.addListener(mockConfigListener) - - service.onForeground(true, 1100L) - - assertTrue(configListenerTriggered) - } - - @Test - fun `test onForeground() with sdk started and config sdkDisabled=true stops the SDK`() { - var stopped = false - service = createService(worker, action = { - stopped = true - }) - fakePreferenceService.sdkDisabled = true - service.onForeground(true, 1100L) - assertTrue(stopped) - } - - @Test - fun `test isSdkDisabled returns true`() { - fakePreferenceService.sdkDisabled = true - assertTrue(service.isSdkDisabled()) - } - - @Test - fun `test isSdkDisabled returns false`() { - fakePreferenceService.sdkDisabled = false - assertFalse(service.isSdkDisabled()) - } - - @Test - fun `Update listeners when cached config is loaded`() { - // Use ExecutorService that requires tasks to be explicitly run. This allows us to simulate the case - // when the loading from the cache doesn't run before the config is read. - - val executorService = BlockingScheduledExecutorService(blockingMode = true) - - every { mockApiService.getCachedConfig() } returns CachedConfig(null, null) - - // Create a new instance of the ConfigService where the value of the config is what it is when the config - // variable is initialized, before the cached version is loaded. - val configService = createService(BackgroundWorker(executorService)) - assertFalse(configService.hasValidRemoteConfig()) - - // call arbitrary function to trigger config refresh - configService.anrBehavior.isAnrCaptureEnabled() - - // Only run the task from the executor that loads the cached config to the ConfigService so the call to fetch - // a new config from the server isn't run - executorService.runCurrentlyBlocked() - assertTrue(configService.hasValidRemoteConfig()) - } - - @Test - fun `test app framework`() { - assertEquals(AppFramework.NATIVE, service.appFramework) - } - - @Test(expected = IllegalArgumentException::class) - fun testEmptyAppId() { - createService(worker = worker, appId = null) - } - - @Test(expected = IllegalArgumentException::class) - fun testNullAppId() { - createService(worker = worker, appId = null) - } - - @Test - fun testNoAppIdRequiredWithExporters() { - val cfg = OpenTelemetryConfiguration( - SpanSinkImpl(), - LogSinkImpl(), - SystemInfo() - ) - cfg.addLogExporter(FakeLogRecordExporter()) - val service = createService(worker = worker, config = cfg, appId = null) - assertNotNull(service) - assertTrue(service.isOnlyUsingOtelExporters()) - } - - /** - * Create a new instance of the [EmbraceConfigService] using the passed in [worker] to run - * tasks for its internal [BackgroundWorker] - */ - private fun createService( - worker: BackgroundWorker, - action: ConfigService.() -> Unit = {}, - config: OpenTelemetryConfiguration = OpenTelemetryConfiguration( - SpanSinkImpl(), - LogSinkImpl(), - SystemInfo() - ), - appId: String? = "AbCdE", - ): EmbraceConfigService = EmbraceConfigService( - openTelemetryCfg = config, - preferencesService = fakePreferenceService, - clock = fakeClock, - logger = logger, - backgroundWorker = worker, - suppliedFramework = AppFramework.NATIVE, - foregroundAction = action, - appIdFromConfig = appId - ).apply { - remoteConfigSource = mockApiService - } -} diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/OkHttpRemoteConfigSourceTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/OkHttpRemoteConfigSourceTest.kt new file mode 100644 index 0000000000..a3f2868d2f --- /dev/null +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/OkHttpRemoteConfigSourceTest.kt @@ -0,0 +1,181 @@ +package io.embrace.android.embracesdk.internal.config + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.embrace.android.embracesdk.fakes.TestPlatformSerializer +import io.embrace.android.embracesdk.fakes.config.FakeBaseUrlConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.internal.comms.api.EmbraceApiUrlBuilder +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.config.source.OkHttpRemoteConfigSource +import okhttp3.OkHttpClient +import okhttp3.Protocol +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import okio.Buffer +import okio.GzipSink +import okio.buffer +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.TimeUnit + +@RunWith(AndroidJUnit4::class) +class OkHttpRemoteConfigSourceTest { + + private lateinit var server: MockWebServer + private lateinit var client: OkHttpClient + private lateinit var urlBuilder: EmbraceApiUrlBuilder + private lateinit var source: OkHttpRemoteConfigSource + + private val remoteConfig = RemoteConfig( + backgroundActivityConfig = BackgroundActivityRemoteConfig(100f) + ) + private lateinit var configResponseBuffer: Buffer + + @Before + fun setUp() { + server = MockWebServer().apply { + protocols = listOf(Protocol.HTTP_1_1, Protocol.HTTP_2) + start() + } + val baseUrl = server.url("api").toString() + client = OkHttpClient.Builder().build() + urlBuilder = EmbraceApiUrlBuilder( + "deviceId", + "1.0.0", + FakeInstrumentedConfig( + baseUrls = FakeBaseUrlConfig( + configImpl = baseUrl, + ) + ) + ) + + // serialize the config response + configResponseBuffer = Buffer() + val gzipSink = GzipSink(configResponseBuffer).buffer() + TestPlatformSerializer().toJson( + remoteConfig, + RemoteConfig::class.java, + gzipSink.outputStream() + ) + source = OkHttpRemoteConfigSource(client, urlBuilder, TestPlatformSerializer()) + } + + @Test + fun `test config 2xx`() { + val (cfg, request) = executeRequest( + MockResponse().setResponseCode(200).setBody(configResponseBuffer) + ) + assertConfigRequestReceived(request) + assertConfigResponseDeserialized(cfg) + } + + @Test + fun `test config 4xx`() { + val (cfg, request) = executeRequest( + MockResponse().setResponseCode(400) + ) + assertConfigRequestReceived(request) + assertConfigResponseNotDeserialized(cfg) + } + + @Test + fun `test config 5xx`() { + val (cfg, request) = executeRequest( + MockResponse().setResponseCode(500) + ) + assertConfigRequestReceived(request) + assertConfigResponseNotDeserialized(cfg) + } + + @Test + fun `test no response from server`() { + client = client.newBuilder().callTimeout(1, TimeUnit.MILLISECONDS).build() + val (cfg, request) = executeRequest(null) + assertConfigRequestNotReceived(request) + assertConfigResponseNotDeserialized(cfg) + } + + @Test + fun `test invalid response from server`() { + val (cfg, request) = executeRequest( + MockResponse().setResponseCode(200).setBody("{") + ) + assertConfigRequestReceived(request) + assertConfigResponseNotDeserialized(cfg) + } + + @Test + fun `test etag header respected`() { + val etagValue = "attempt_1" + val (cfg, request) = executeRequest( + MockResponse().setResponseCode(200) + .setBody(configResponseBuffer) + .setHeader("etag", etagValue) + ) + assertConfigRequestReceived(request) + assertNull(request?.getHeader("If-None-Match")) + assertConfigResponseDeserialized(cfg) + + // second request with etag + val (secondCfg, secondRequest) = executeRequest( + MockResponse().setResponseCode(304) + .setHeader("etag", etagValue) + ) + assertConfigRequestReceived(secondRequest) + assertEquals(etagValue, secondRequest?.getHeader("If-None-Match")) + assertConfigResponseNotDeserialized(secondCfg) + } + + private fun executeRequest(response: MockResponse?): Pair { + if (response == null) { + return Pair(null, null) + } + server.enqueue(response) + val cfg = source.getConfig()?.cfg + val request = pollRequest() + CallData(request, cfg) + return Pair(cfg, request) + } + + private fun assertEmbraceHeadersAdded(request: RecordedRequest?) { + val headers = request?.headers?.toMap() ?: error("Request headers cannot be null") + assertEquals("application/json", headers["Accept"]) + assertEquals("application/json", headers["Content-Type"]) + assertEquals("gzip", headers["Accept-Encoding"]) + assertEquals("abcde", headers["X-EM-AID"]) + assertEquals("deviceId", headers["X-EM-DID"]) + } + + private fun assertConfigRequestNotReceived(request: RecordedRequest?) { + assertNull(request) + } + + private fun assertConfigRequestReceived(request: RecordedRequest?) { + checkNotNull(request) + assertEmbraceHeadersAdded(request) + val requestUrl = request.requestUrl?.toUrl() ?: error("Request URL cannot be null") + assertEquals("/api/v2/config", requestUrl.path) + assertEquals("appId=abcde&osVersion=21.0.0&appVersion=1.0.0&deviceId=deviceId", requestUrl.query) + } + + private fun assertConfigResponseDeserialized(cfg: RemoteConfig?) { + checkNotNull(cfg) + assertEquals(remoteConfig.backgroundActivityConfig, cfg.backgroundActivityConfig) + } + + private fun assertConfigResponseNotDeserialized(cfg: RemoteConfig?) { + assertNull(cfg) + } + + private data class CallData( + val request: RecordedRequest?, + val deserializedConfig: RemoteConfig?, + ) + + private fun pollRequest(): RecordedRequest? = server.takeRequest(1, TimeUnit.SECONDS) +} diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AnrBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AnrBehaviorImplTest.kt index 14d9a46d71..aa73c8d73f 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AnrBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AnrBehaviorImplTest.kt @@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.internal.config.behavior import io.embrace.android.embracesdk.fakes.createAnrBehavior import io.embrace.android.embracesdk.internal.config.remote.AllowedNdkSampleMethod import io.embrace.android.embracesdk.internal.config.remote.AnrRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.config.remote.Unwinder import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -57,7 +58,7 @@ internal class AnrBehaviorImplTest { @Test fun testRemoteAndLocal() { - with(createAnrBehavior(remoteCfg = { remote })) { + with(createAnrBehavior(remoteCfg = RemoteConfig(anrConfig = remote))) { assertEquals(200L, getSamplingIntervalMs()) assertFalse(isNativeThreadAnrSamplingOffsetEnabled()) assertFalse(isNativeThreadAnrSamplingAllowlistIgnored()) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehaviorImplTest.kt index 6bf0c3b282..a14cdd2810 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AppExitInfoBehaviorImplTest.kt @@ -23,7 +23,7 @@ internal class AppExitInfoBehaviorImplTest { @Test fun testLocalAndRemote() { - with(createAppExitInfoBehavior(remoteCfg = { remote })) { + with(createAppExitInfoBehavior(remoteCfg = remote)) { assertEquals(55209, getTraceMaxLimit()) assertTrue(isAeiCaptureEnabled()) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImplTest.kt index 314567bd9c..60159ac88a 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImplTest.kt @@ -40,7 +40,7 @@ internal class AutoDataCaptureBehaviorImplTest { @Test fun testLocalAndRemote() { - with(createAutoDataCaptureBehavior(remoteCfg = { remote })) { + with(createAutoDataCaptureBehavior(remoteCfg = remote)) { assertFalse(is3rdPartySigHandlerDetectionEnabled()) assertFalse(isComposeClickCaptureEnabled()) assertFalse(isThermalStatusCaptureEnabled()) @@ -56,7 +56,7 @@ internal class AutoDataCaptureBehaviorImplTest { } // Jetpack Compose disabled remotely - with(createAutoDataCaptureBehavior(remoteCfg = { remote })) { + with(createAutoDataCaptureBehavior(remoteCfg = remote)) { assertFalse(isComposeClickCaptureEnabled()) } val remoteComposeKillSwitchOff = RemoteConfig( @@ -69,7 +69,7 @@ internal class AutoDataCaptureBehaviorImplTest { // Jetpack Compose enabled remotely with( createAutoDataCaptureBehavior( - remoteCfg = { remoteComposeKillSwitchOff } + remoteCfg = remoteComposeKillSwitchOff ) ) { assertTrue(isComposeClickCaptureEnabled()) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehaviorImplTest.kt index 5e7e84fd16..2bb34475f7 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/BackgroundActivityBehaviorImplTest.kt @@ -2,6 +2,7 @@ package io.embrace.android.embracesdk.internal.config.behavior import io.embrace.android.embracesdk.fakes.createBackgroundActivityBehavior import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Test @@ -24,7 +25,7 @@ internal class BackgroundActivityBehaviorImplTest { @Test fun testRemoteAndLocal() { - with(createBackgroundActivityBehavior(remoteCfg = { remote })) { + with(createBackgroundActivityBehavior(remoteCfg = RemoteConfig(backgroundActivityConfig = remote))) { assertFalse(isBackgroundActivityCaptureEnabled()) assertEquals(100, getManualBackgroundActivityLimit()) assertEquals(5000L, getMinBackgroundActivityDuration()) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehaviorImplTest.kt index ea66b8b759..3393d857b2 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/BreadcrumbBehaviorImplTest.kt @@ -1,8 +1,8 @@ package io.embrace.android.embracesdk.internal.config.behavior +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.config.remote.UiRemoteConfig -import io.embrace.android.embracesdk.internal.utils.Uuid import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -19,11 +19,14 @@ internal class BreadcrumbBehaviorImplTest { ) ) - private val behaviorThresholdCheck = BehaviorThresholdCheck { Uuid.getEmbUuid() } - @Test fun testDefaults() { - with(BreadcrumbBehaviorImpl(thresholdCheck = behaviorThresholdCheck) { null }) { + with( + BreadcrumbBehaviorImpl( + InstrumentedConfigImpl, + null, + ) + ) { assertEquals(100, getCustomBreadcrumbLimit()) assertEquals(100, getTapBreadcrumbLimit()) assertEquals(100, getWebViewBreadcrumbLimit()) @@ -39,7 +42,10 @@ internal class BreadcrumbBehaviorImplTest { @Test fun testRemoteAndLocal() { with( - BreadcrumbBehaviorImpl(thresholdCheck = behaviorThresholdCheck) { remote } + BreadcrumbBehaviorImpl( + InstrumentedConfigImpl, + remote, + ) ) { assertEquals(99, getCustomBreadcrumbLimit()) assertEquals(98, getTapBreadcrumbLimit()) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehaviorImplTest.kt index 81e61c6824..18171aadee 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/DataCaptureEventBehaviorImplTest.kt @@ -26,7 +26,7 @@ internal class DataCaptureEventBehaviorImplTest { @Test fun testRemoteOnly() { - with(createDataCaptureEventBehavior(remoteCfg = { remote })) { + with(createDataCaptureEventBehavior(remoteCfg = remote)) { assertFalse(isInternalExceptionCaptureEnabled()) assertFalse(isEventEnabled("my_event")) assertTrue(isEventEnabled("other_event")) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehaviorImplTest.kt index a3b1f5967f..c73c0b553b 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/LogMessageBehaviorImplTest.kt @@ -2,16 +2,19 @@ package io.embrace.android.embracesdk.internal.config.behavior import io.embrace.android.embracesdk.fakes.createLogMessageBehavior import io.embrace.android.embracesdk.internal.config.remote.LogRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import org.junit.Assert.assertEquals import org.junit.Test internal class LogMessageBehaviorImplTest { - private val remote = LogRemoteConfig( - 256, - 200, - 300, - 400 + private val remote = RemoteConfig( + logConfig = LogRemoteConfig( + 256, + 200, + 300, + 400 + ) ) @Test @@ -26,7 +29,7 @@ internal class LogMessageBehaviorImplTest { @Test fun testRemoteAndLocal() { - with(createLogMessageBehavior(remoteCfg = { remote })) { + with(createLogMessageBehavior(remoteCfg = remote)) { assertEquals(256, getLogMessageMaximumAllowedLength()) assertEquals(200, getInfoLogLimit()) assertEquals(300, getWarnLogLimit()) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehaviorImplTest.kt index 0671dbc969..1d782f5781 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/NetworkBehaviorImplTest.kt @@ -32,7 +32,7 @@ internal class NetworkBehaviorImplTest { @Test fun testDefaults() { - with(createNetworkBehavior(remoteCfg = { null })) { + with(createNetworkBehavior(remoteCfg = null)) { assertFalse(isRequestContentLengthCaptureEnabled()) assertTrue(isHttpUrlConnectionCaptureEnabled()) assertEquals(1000, getRequestLimitPerDomain()) @@ -46,7 +46,7 @@ internal class NetworkBehaviorImplTest { @Test fun testRemoteOnly() { - with(createNetworkBehavior(remoteCfg = { remote })) { + with(createNetworkBehavior(remoteCfg = remote)) { assertEquals(409, getRequestLimitPerDomain()) assertEquals(mapOf("google.com" to 50), getLimitsByDomain()) assertTrue(isUrlEnabled("google.com")) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehaviorImplTest.kt index 5f63257132..19cc45a929 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/NetworkSpanForwardingBehaviorImplTest.kt @@ -2,6 +2,7 @@ package io.embrace.android.embracesdk.internal.config.behavior import io.embrace.android.embracesdk.fakes.createNetworkSpanForwardingBehavior import io.embrace.android.embracesdk.internal.config.remote.NetworkSpanForwardingRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test @@ -17,17 +18,19 @@ internal class NetworkSpanForwardingBehaviorImplTest { @Test fun testRemote() { - with(createNetworkSpanForwardingBehavior(remoteConfig = { remoteEnabled })) { + with(createNetworkSpanForwardingBehavior(remoteConfig = remoteEnabled)) { assertTrue(isNetworkSpanForwardingEnabled()) } - with(createNetworkSpanForwardingBehavior(remoteConfig = { remoteDisabled })) { + with(createNetworkSpanForwardingBehavior(remoteConfig = remoteDisabled)) { assertFalse(isNetworkSpanForwardingEnabled()) } } companion object { - private val remoteEnabled = NetworkSpanForwardingRemoteConfig(pctEnabled = 100.0f) - private val remoteDisabled = NetworkSpanForwardingRemoteConfig(pctEnabled = 0.0f) + private val remoteEnabled = + RemoteConfig(networkSpanForwardingRemoteConfig = NetworkSpanForwardingRemoteConfig(pctEnabled = 100.0f)) + private val remoteDisabled = + RemoteConfig(networkSpanForwardingRemoteConfig = NetworkSpanForwardingRemoteConfig(pctEnabled = 0.0f)) } } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SdkEndpointBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SdkEndpointBehaviorImplTest.kt deleted file mode 100644 index 2c042fbe42..0000000000 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SdkEndpointBehaviorImplTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.embrace.android.embracesdk.internal.config.behavior - -import io.embrace.android.embracesdk.fakes.createSdkEndpointBehavior -import org.junit.Assert.assertEquals -import org.junit.Test - -internal class SdkEndpointBehaviorImplTest { - - @Test - fun testDefaults() { - with(createSdkEndpointBehavior()) { - assertEquals("https://a-12345.config.emb-api.com", getConfig("12345")) - assertEquals("https://a-12345.data.emb-api.com", getData("12345")) - } - } -} diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehaviorImplTest.kt index d7bcb0334e..062185e885 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SdkModeBehaviorImplTest.kt @@ -33,28 +33,28 @@ internal class SdkModeBehaviorImplTest { // SDK disabled var behavior = createSdkModeBehavior( thresholdCheck = enabled, - remoteCfg = { RemoteConfig(threshold = 0) } + remoteCfg = RemoteConfig(threshold = 0) ) assertTrue(behavior.isSdkDisabled()) // SDK enabled behavior = createSdkModeBehavior( thresholdCheck = enabled, - remoteCfg = { RemoteConfig(threshold = 100) } + remoteCfg = RemoteConfig(threshold = 100) ) assertFalse(behavior.isSdkDisabled()) // SDK 30% enabled with default offset behavior = createSdkModeBehavior( thresholdCheck = halfEnabled, - remoteCfg = { RemoteConfig(threshold = 30) } + remoteCfg = RemoteConfig(threshold = 30) ) assertTrue(behavior.isSdkDisabled()) // SDK 30% enabled with non-default offset behavior = createSdkModeBehavior( thresholdCheck = halfEnabled, - remoteCfg = { RemoteConfig(threshold = 30, offset = 25) } + remoteCfg = RemoteConfig(threshold = 30, offset = 25) ) assertFalse(behavior.isSdkDisabled()) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehaviorImplTest.kt index a13a0aeca5..c09d239c93 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SensitiveKeysBehaviorImplTest.kt @@ -1,5 +1,8 @@ package io.embrace.android.embracesdk.internal.config.behavior +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeRedactionConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test @@ -9,7 +12,7 @@ internal class SensitiveKeysBehaviorImplTest { @Test fun `keys are not sensitive if they are not in the sensitive keys list`() { // given an empty sensitive list - val behavior = SensitiveKeysBehaviorImpl(emptyList()) + val behavior = SensitiveKeysBehaviorImpl(emptyList().toConfig()) // when checking if a key is sensitive val isSensitive = behavior.isSensitiveKey("password") @@ -21,7 +24,8 @@ internal class SensitiveKeysBehaviorImplTest { @Test fun `keys are not sensitive with a null sensitive keys list`() { // given a null sensitive list - val behavior = SensitiveKeysBehaviorImpl(null) + val behavior = + SensitiveKeysBehaviorImpl(FakeInstrumentedConfig(redaction = FakeRedactionConfig(sensitiveKeys = null))) // when checking if a key is sensitive val isSensitive = behavior.isSensitiveKey("password") @@ -33,7 +37,7 @@ internal class SensitiveKeysBehaviorImplTest { @Test fun `keys are sensitive when found in the sensitive keys list`() { // given a sensitive list with a key - val behavior = SensitiveKeysBehaviorImpl(listOf("password")) + val behavior = SensitiveKeysBehaviorImpl(listOf("password").toConfig()) // when checking if a key present in the list is sensitive val isSensitive = behavior.isSensitiveKey("password") @@ -45,7 +49,7 @@ internal class SensitiveKeysBehaviorImplTest { @Test fun `keys in the sensitive list are truncated to 128 characters`() { // given a sensitive list with a long key - val behavior = SensitiveKeysBehaviorImpl(listOf("a".repeat(200))) + val behavior = SensitiveKeysBehaviorImpl(listOf("a".repeat(200)).toConfig()) // when checking if a key present in the list is sensitive val sensitiveKey = behavior.isSensitiveKey("a".repeat(128)) @@ -60,7 +64,7 @@ internal class SensitiveKeysBehaviorImplTest { fun `sensitive list is truncated to 10000 keys`() { // given a sensitive list with more than 10000 keys val behavior = SensitiveKeysBehaviorImpl( - List(10000) { it.toString() } + "password" + (List(10000) { it.toString() } + "password").toConfig() ) // when checking a key present in the 10001st position @@ -74,10 +78,7 @@ internal class SensitiveKeysBehaviorImplTest { fun `sensitive list with multiple keys`() { // given a sensitive list with multiple keys val behavior = SensitiveKeysBehaviorImpl( - listOf( - "password", - "passkey" - ) + listOf("password", "passkey").toConfig(), ) // when checking if a key present in the list is sensitive @@ -90,4 +91,8 @@ internal class SensitiveKeysBehaviorImplTest { assertTrue(anotherSensitiveKey) assertFalse(notSensitiveKey) } + + private fun List.toConfig(): InstrumentedConfig { + return FakeInstrumentedConfig(redaction = FakeRedactionConfig(sensitiveKeys = this)) + } } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SessionBehaviorImplImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SessionBehaviorImplImplTest.kt index 94a15b97c7..05746fd16b 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SessionBehaviorImplImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/SessionBehaviorImplImplTest.kt @@ -33,7 +33,7 @@ internal class SessionBehaviorImplImplTest { @Test fun testRemoteAndLocal() { - with(createSessionBehavior(remoteCfg = { remote })) { + with(createSessionBehavior(remoteCfg = remote)) { assertTrue(isGatingFeatureEnabled()) assertTrue(isSessionControlEnabled()) assertEquals(setOf("test"), getSessionComponents()) @@ -45,9 +45,7 @@ internal class SessionBehaviorImplImplTest { @Test fun `test upper case full session events`() { val behavior = createSessionBehavior( - remoteCfg = { - buildGatingConfig(setOf("CRASHES", "ERRORS")) - } + remoteCfg = buildGatingConfig(setOf("CRASHES", "ERRORS")) ) assertEquals(setOf("crashes", "errors"), behavior.getFullSessionEvents()) } @@ -55,9 +53,7 @@ internal class SessionBehaviorImplImplTest { @Test fun `test lower case full session events`() { val behavior = createSessionBehavior( - remoteCfg = { - buildGatingConfig(setOf("crashes", "errors")) - } + remoteCfg = buildGatingConfig(setOf("crashes", "errors")) ) assertEquals(setOf("crashes", "errors"), behavior.getFullSessionEvents()) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/WebVitalsBehaviorTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/WebVitalsBehaviorTest.kt index cdc92c2b14..fe39ba9c4f 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/WebVitalsBehaviorTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/WebVitalsBehaviorTest.kt @@ -22,7 +22,7 @@ internal class WebVitalsBehaviorTest { @Test fun testRemote() { - with(createWebViewVitalsBehavior(remoteCfg = { remote })) { + with(createWebViewVitalsBehavior(remoteCfg = remote)) { assertEquals(100, getMaxWebViewVitals()) assertFalse(isWebViewVitalsEnabled()) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/source/CombinedRemoteConfigSourceTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/source/CombinedRemoteConfigSourceTest.kt new file mode 100644 index 0000000000..c3de00ac7d --- /dev/null +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/source/CombinedRemoteConfigSourceTest.kt @@ -0,0 +1,69 @@ +package io.embrace.android.embracesdk.internal.config.source + +import io.embrace.android.embracesdk.concurrency.BlockingScheduledExecutorService +import io.embrace.android.embracesdk.fakes.FakeRemoteConfigSource +import io.embrace.android.embracesdk.fakes.FakeRemoteConfigStore +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.worker.BackgroundWorker +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test + +class CombinedRemoteConfigSourceTest { + + private lateinit var source: CombinedRemoteConfigSource + private lateinit var remoteConfig: RemoteConfig + private lateinit var executorService: BlockingScheduledExecutorService + private lateinit var remoteConfigSource: FakeRemoteConfigSource + private lateinit var remoteConfigStore: FakeRemoteConfigStore + + @Before + fun setUp() { + remoteConfig = RemoteConfig(92) + executorService = BlockingScheduledExecutorService() + remoteConfigSource = FakeRemoteConfigSource(ConfigHttpResponse(remoteConfig, "another")) + remoteConfigStore = FakeRemoteConfigStore() + source = CombinedRemoteConfigSource( + remoteConfigStore, + lazy { remoteConfigSource }, + BackgroundWorker(executorService) + ) + } + + @Test + fun `test initial config null`() { + assertNull(source.getConfig()) + } + + @Test + fun `test initial config populated`() { + val cfg = RemoteConfig(100) + source = CombinedRemoteConfigSource( + FakeRemoteConfigStore(ConfigHttpResponse(cfg, null)), + lazy { remoteConfigSource }, + BackgroundWorker(executorService) + ) + assertEquals(cfg, source.getConfig()) + } + + @Test + fun `test requests scheduled`() { + assertEquals(0, remoteConfigSource.callCount) + source.scheduleConfigRequests() + executorService.runCurrentlyBlocked() + assertEquals(1, remoteConfigSource.callCount) + assertNull(remoteConfigSource.etag) + } + + @Test + fun `test persisted etag value populated`() { + remoteConfigStore.impl = ConfigHttpResponse(RemoteConfig(), "etag") + assertEquals(0, remoteConfigSource.callCount) + source.scheduleConfigRequests() + assertEquals("etag", remoteConfigSource.etag) + executorService.runCurrentlyBlocked() + assertEquals(1, remoteConfigSource.callCount) + assertEquals(1, remoteConfigStore.saveCount) + } +} diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/store/RemoteConfigStoreImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/store/RemoteConfigStoreImplTest.kt new file mode 100644 index 0000000000..6cd61be8df --- /dev/null +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/store/RemoteConfigStoreImplTest.kt @@ -0,0 +1,48 @@ +package io.embrace.android.embracesdk.internal.config.store + +import io.embrace.android.embracesdk.fakes.TestPlatformSerializer +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.config.source.ConfigHttpResponse +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import java.io.File +import java.nio.file.Files + +internal class RemoteConfigStoreImplTest { + + private lateinit var store: RemoteConfigStoreImpl + private lateinit var dir: File + + @Before + fun setUp() { + dir = Files.createTempDirectory("test").toFile() + store = RemoteConfigStoreImpl(TestPlatformSerializer(), dir) + } + + @Test + fun `test config store`() { + assertNull(store.loadResponse()) + + // store a config + val config = RemoteConfig(50) + store.saveResponse(ConfigHttpResponse(config, "etag")) + + // load the config + val loaded = checkNotNull(store.loadResponse()) + assertEquals(config, loaded.cfg) + assertEquals("etag", loaded.etag) + + val newConfig = RemoteConfig(100) + store.saveResponse(ConfigHttpResponse(newConfig, "another")) + + val newLoaded = checkNotNull(store.loadResponse()) + assertEquals(newConfig, newLoaded.cfg) + assertEquals("another", newLoaded.etag) + + store.saveResponse(ConfigHttpResponse(newConfig, "another")) + assertEquals(newConfig, newLoaded.cfg) + assertEquals("another", newLoaded.etag) + } +} diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/event/gating/EmbraceGatingServiceV2PayloadTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/event/gating/EmbraceGatingServiceV2PayloadTest.kt index cd04ae0a30..e9b75af7da 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/event/gating/EmbraceGatingServiceV2PayloadTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/event/gating/EmbraceGatingServiceV2PayloadTest.kt @@ -32,8 +32,8 @@ internal class EmbraceGatingServiceV2PayloadTest { @Before fun setUp() { - sessionBehavior = createSessionBehavior { cfg } - configService = FakeConfigService(sessionBehavior = createSessionBehavior { cfg }) + sessionBehavior = createSessionBehavior(cfg) + configService = FakeConfigService(sessionBehavior = createSessionBehavior(cfg)) logService = FakeLogService() gatingService = EmbraceGatingService(configService, logService) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleImplTest.kt index 65e40ff366..9024062d3d 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/AndroidServicesModuleImplTest.kt @@ -1,20 +1,22 @@ package io.embrace.android.embracesdk.internal.injection +import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.internal.prefs.EmbracePreferencesService import io.mockk.mockk import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) internal class AndroidServicesModuleImplTest { @Test fun testDefault() { val initModule = InitModuleImpl() - val coreModule = createCoreModule(mockk(relaxed = true)) + val coreModule = createCoreModule(mockk(relaxed = true), initModule) val module = AndroidServicesModuleImpl( initModule = initModule, - coreModule = coreModule, - workerThreadModule = WorkerThreadModuleImpl() + coreModule = coreModule ) assertTrue(module.preferencesService is EmbracePreferencesService) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/ConfigModuleImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/ConfigModuleImplTest.kt index 853704db39..241b342706 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/ConfigModuleImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/ConfigModuleImplTest.kt @@ -1,14 +1,13 @@ package io.embrace.android.embracesdk.internal.injection +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeOpenTelemetryModule import io.embrace.android.embracesdk.fakes.injection.FakeAndroidServicesModule import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fakes.injection.FakeWorkerThreadModule import io.embrace.android.embracesdk.internal.payload.AppFramework import org.junit.Assert.assertNotNull -import org.junit.Assert.assertSame import org.junit.Test import org.junit.runner.RunWith @@ -17,47 +16,16 @@ internal class ConfigModuleImplTest { @Test fun `test defaults`() { + val initModule = FakeInitModule() val module = ConfigModuleImpl( - initModule = FakeInitModule(), + initModule = initModule, + coreModule = createCoreModule(ApplicationProvider.getApplicationContext(), initModule), openTelemetryModule = FakeOpenTelemetryModule(), workerThreadModule = FakeWorkerThreadModule(), androidServicesModule = FakeAndroidServicesModule(), framework = AppFramework.NATIVE, - configServiceProvider = { null }, - foregroundAction = {}, - appIdFromConfig = "AbCeD", ) assertNotNull(module.configService) - } - - @Test - fun testConfigServiceProvider() { - val fakeConfigService = FakeConfigService() - val module = ConfigModuleImpl( - initModule = FakeInitModule(), - openTelemetryModule = FakeOpenTelemetryModule(), - workerThreadModule = FakeWorkerThreadModule(), - androidServicesModule = FakeAndroidServicesModule(), - framework = AppFramework.NATIVE, - configServiceProvider = { fakeConfigService }, - foregroundAction = {}, - appIdFromConfig = "AbCeD", - ) - assertSame(fakeConfigService, module.configService) - } - - @Test - fun `validate appId in appIdProvider is used`() { - val module = ConfigModuleImpl( - initModule = FakeInitModule(), - openTelemetryModule = FakeOpenTelemetryModule(), - workerThreadModule = FakeWorkerThreadModule(), - androidServicesModule = FakeAndroidServicesModule(), - framework = AppFramework.NATIVE, - configServiceProvider = { null }, - foregroundAction = {}, - appIdFromConfig = "AbCeD", - ) - assertSame("AbCeD", module.configService.appId) + assertNotNull(module.urlBuilder) } } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/CoreModuleImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/CoreModuleImplTest.kt index eff0148d5a..bae0e15a31 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/CoreModuleImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/CoreModuleImplTest.kt @@ -1,6 +1,7 @@ package io.embrace.android.embracesdk.internal.injection import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.internal.capture.metadata.AppEnvironment import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -12,10 +13,12 @@ import org.robolectric.RuntimeEnvironment @RunWith(AndroidJUnit4::class) internal class CoreModuleImplTest { + private val initModule = FakeInitModule() + @Test fun testApplicationObject() { val ctx = RuntimeEnvironment.getApplication().applicationContext - val module = CoreModuleImpl(ctx) + val module = CoreModuleImpl(ctx, initModule) assertSame(ctx, module.context) assertSame(ctx, module.application) assertNotNull(module.serviceRegistry) @@ -26,7 +29,7 @@ internal class CoreModuleImplTest { val application = RuntimeEnvironment.getApplication() val isDebug = AppEnvironment(application.applicationInfo).isDebug val ctx = application.applicationContext - val module = CoreModuleImpl(ctx) + val module = CoreModuleImpl(ctx, initModule) assertSame(application, module.application) assertEquals(isDebug, module.isDebug) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/DataSourceModuleImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/DataSourceModuleImplTest.kt index 93e168ba0e..0684ff6269 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/DataSourceModuleImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/DataSourceModuleImplTest.kt @@ -1,6 +1,5 @@ package io.embrace.android.embracesdk.internal.injection -import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fakes.injection.FakeWorkerThreadModule import io.embrace.android.embracesdk.internal.worker.Worker @@ -15,7 +14,6 @@ internal class DataSourceModuleImplTest { val fakeInitModule = FakeInitModule() val module = DataSourceModuleImpl( fakeInitModule, - FakeConfigService(), FakeWorkerThreadModule( fakeInitModule = fakeInitModule, testWorkerName = Worker.Background.NonIoRegWorker diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/DeliveryModuleImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/DeliveryModuleImplTest.kt index a759cf9f58..2f69cb6e37 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/DeliveryModuleImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/DeliveryModuleImplTest.kt @@ -8,6 +8,7 @@ import io.embrace.android.embracesdk.fakes.FakeDeliveryService import io.embrace.android.embracesdk.fakes.FakeOpenTelemetryModule import io.embrace.android.embracesdk.fakes.FakeRequestExecutionService import io.embrace.android.embracesdk.fakes.behavior.FakeAutoDataCaptureBehavior +import io.embrace.android.embracesdk.fakes.injection.FakeAndroidServicesModule import io.embrace.android.embracesdk.fakes.injection.FakeEssentialServiceModule import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fakes.injection.FakeStorageModule @@ -29,17 +30,19 @@ class DeliveryModuleImplTest { @Before fun setUp() { configService = FakeConfigService() + val initModule = FakeInitModule() module = DeliveryModuleImpl( FakeConfigModule(configService), - FakeInitModule(), + initModule, FakeOpenTelemetryModule(), FakeWorkerThreadModule(), - CoreModuleImpl(ApplicationProvider.getApplicationContext()), + CoreModuleImpl(ApplicationProvider.getApplicationContext(), initModule), FakeStorageModule(), FakeEssentialServiceModule(), + FakeAndroidServicesModule(), ::FakeRequestExecutionService, - { null }, - { null }, + null, + null, ::FakeDeliveryService ) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/PayloadSourceModuleImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/PayloadSourceModuleImplTest.kt index 8db3dabbff..d1fe753eda 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/PayloadSourceModuleImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/PayloadSourceModuleImplTest.kt @@ -29,7 +29,7 @@ internal class PayloadSourceModuleImplTest { val initModule = FakeInitModule() val module = PayloadSourceModuleImpl( initModule, - CoreModuleImpl(RuntimeEnvironment.getApplication()), + CoreModuleImpl(RuntimeEnvironment.getApplication(), initModule), FakeWorkerThreadModule(), FakeSystemServiceModule(), FakeAndroidServicesModule(), @@ -52,7 +52,7 @@ internal class PayloadSourceModuleImplTest { val initModule = FakeInitModule() val module = PayloadSourceModuleImpl( initModule, - CoreModuleImpl(RuntimeEnvironment.getApplication()), + CoreModuleImpl(RuntimeEnvironment.getApplication(), initModule), FakeWorkerThreadModule(), FakeSystemServiceModule(), FakeAndroidServicesModule(), diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/SystemServiceModuleImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/SystemServiceModuleImplTest.kt index 8d6920e2a9..61f797ebdc 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/SystemServiceModuleImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/injection/SystemServiceModuleImplTest.kt @@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.internal.injection import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.fakes.FakeVersionChecker +import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Before @@ -15,17 +16,18 @@ import org.robolectric.annotation.Config internal class SystemServiceModuleImplTest { private lateinit var coreModule: CoreModule + private val initModule = FakeInitModule() @Before fun setUp() { - coreModule = createCoreModule(RuntimeEnvironment.getApplication()) + coreModule = createCoreModule(RuntimeEnvironment.getApplication(), initModule) } @Config(sdk = [Build.VERSION_CODES.O]) @Test fun testVersionChecksNew() { val new = SystemServiceModuleImpl( - createCoreModule(RuntimeEnvironment.getApplication()), + createCoreModule(RuntimeEnvironment.getApplication(), initModule), FakeVersionChecker(true) ) assertNotNull(new.storageManager) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/logs/EmbraceLogServiceTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/logs/EmbraceLogServiceTest.kt index 234283f906..280867355a 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/logs/EmbraceLogServiceTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/logs/EmbraceLogServiceTest.kt @@ -7,6 +7,8 @@ import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeLogWriter import io.embrace.android.embracesdk.fakes.FakeSessionPropertiesService import io.embrace.android.embracesdk.fakes.behavior.FakeLogMessageBehavior +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeRedactionConfig import io.embrace.android.embracesdk.fakes.createSessionBehavior import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.config.behavior.REDACTED_LABEL @@ -37,7 +39,9 @@ internal class EmbraceLogServiceTest { @Before fun setUp() { fakeConfigService = FakeConfigService( - sensitiveKeysBehavior = SensitiveKeysBehaviorImpl(listOf("password")) + sensitiveKeysBehavior = SensitiveKeysBehaviorImpl( + FakeInstrumentedConfig(redaction = FakeRedactionConfig(sensitiveKeys = listOf("password"))), + ) ) fakeSessionPropertiesService = FakeSessionPropertiesService() fakeLogWriter = FakeLogWriter() @@ -105,14 +109,12 @@ internal class EmbraceLogServiceTest { // given a config that gates info and warning logs fakeConfigService = FakeConfigService( sessionBehavior = createSessionBehavior( - remoteCfg = { - RemoteConfig( - sessionConfig = SessionRemoteConfig( - isEnabled = true, - sessionComponents = emptySet() // empty set will gate everything - ) + remoteCfg = RemoteConfig( + sessionConfig = SessionRemoteConfig( + isEnabled = true, + sessionComponents = emptySet() // empty set will gate everything ) - } + ) ) ) logService = createEmbraceLogService() diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/network/logging/EmbraceNetworkCaptureServiceTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/network/logging/EmbraceNetworkCaptureServiceTest.kt index 27e25c0fa4..b304c6162f 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/network/logging/EmbraceNetworkCaptureServiceTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/network/logging/EmbraceNetworkCaptureServiceTest.kt @@ -4,7 +4,9 @@ import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeNetworkCaptureDataSource import io.embrace.android.embracesdk.fakes.FakePreferenceService import io.embrace.android.embracesdk.fakes.FakeSessionIdTracker +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.fakes.createNetworkBehavior +import io.embrace.android.embracesdk.internal.comms.api.EmbraceApiUrlBuilder import io.embrace.android.embracesdk.internal.config.remote.NetworkCaptureRuleRemoteConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.logging.EmbLoggerImpl @@ -35,7 +37,7 @@ internal class EmbraceNetworkCaptureServiceTest { fun setUp() { cfg = RemoteConfig() configService = FakeConfigService( - networkBehavior = createNetworkBehavior(remoteCfg = { cfg }) + networkBehavior = createNetworkBehavior(remoteCfg = cfg) ) preferenceService = FakePreferenceService() networkCaptureDataSource = FakeNetworkCaptureDataSource() @@ -65,10 +67,9 @@ internal class EmbraceNetworkCaptureServiceTest { @Test fun `test capture rule doesn't capture Embrace endpoints`() { - configService.appId = "o0o0o" - val rule = getDefaultRule(urlRegex = "https://a-o0o0o.data.emb-api.com") + val rule = getDefaultRule(urlRegex = "https://a-abcde.data.emb-api.com/v2") cfg = RemoteConfig(networkCaptureRules = setOf(rule)) - val result = getService().getNetworkCaptureRules("https://a-o0o0o.data.emb-api.com", "GET") + val result = getService().getNetworkCaptureRules("https://a-abcde.data.emb-api.com/v2/spans", "GET") assertEquals(0, result.size) } @@ -190,14 +191,24 @@ internal class EmbraceNetworkCaptureServiceTest { assertEquals(2, networkCaptureDataSource.loggedCalls.size) } - private fun getService() = EmbraceNetworkCaptureService( - sessionIdTracker, - preferenceService, - { networkCaptureDataSource }, - configService, - EmbraceSerializer(), - EmbLoggerImpl() - ) + private fun getService(): EmbraceNetworkCaptureService { + configService = FakeConfigService( + networkBehavior = createNetworkBehavior(remoteCfg = cfg) + ) + return EmbraceNetworkCaptureService( + sessionIdTracker, + preferenceService, + { networkCaptureDataSource }, + configService, + EmbraceApiUrlBuilder( + "deviceId", + "1.0.0", + FakeInstrumentedConfig() + ), + EmbraceSerializer(), + EmbLoggerImpl() + ) + } private fun getDefaultRule( id: String = "123", diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/prefs/EmbracePreferencesServiceTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/prefs/EmbracePreferencesServiceTest.kt index 20529e7d6f..0bdef72e48 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/prefs/EmbracePreferencesServiceTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/prefs/EmbracePreferencesServiceTest.kt @@ -9,7 +9,6 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.FakeSharedPreferences -import io.embrace.android.embracesdk.fakes.fakeBackgroundWorker import io.embrace.android.embracesdk.internal.serialization.EmbraceSerializer import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -27,7 +26,6 @@ internal class EmbracePreferencesServiceTest { private lateinit var service: EmbracePreferencesService private lateinit var fakeClock: FakeClock - private val executorService = fakeBackgroundWorker() private val context = ApplicationProvider.getApplicationContext() @Before @@ -35,8 +33,7 @@ internal class EmbracePreferencesServiceTest { prefs = PreferenceManager.getDefaultSharedPreferences(context) fakeClock = FakeClock() service = EmbracePreferencesService( - executorService, - lazy { prefs }, + prefs, fakeClock, EmbraceSerializer() ) @@ -84,13 +81,6 @@ internal class EmbracePreferencesServiceTest { assertNotNull(service.deviceIdentifier) } - @Test - fun `test sdk disabled is saved`() { - assertFalse(service.sdkDisabled) - service.sdkDisabled = true - assertTrue(service.sdkDisabled) - } - @Test fun `test user payer is saved`() { assertFalse(service.userPayer) @@ -156,13 +146,6 @@ internal class EmbracePreferencesServiceTest { assertEquals(1234L, service.lastConfigFetchDate) } - @Test - fun `test user message needs retry is saved`() { - assertFalse(service.userMessageNeedsRetry) - service.userMessageNeedsRetry = true - assertTrue(service.userMessageNeedsRetry) - } - @Test fun `test session number is saved`() { assertEquals(1, service.incrementAndGetSessionNumber()) @@ -204,8 +187,7 @@ internal class EmbracePreferencesServiceTest { @Test fun `test incrementAndGet returns -1 on an exception`() { service = EmbracePreferencesService( - executorService, - lazy { FakeSharedPreferences(throwExceptionOnGet = true) }, + FakeSharedPreferences(throwExceptionOnGet = true), fakeClock, EmbraceSerializer() ) @@ -317,15 +299,6 @@ internal class EmbracePreferencesServiceTest { assertEquals(version, service.embraceFlutterSdkVersion) } - @Test - fun `test background activity enabled is saved`() { - assertFalse(service.backgroundActivityEnabled) - - val expected = true - service.backgroundActivityEnabled = true - assertEquals(expected, service.backgroundActivityEnabled) - } - @Test fun `test is users first day`() { assertFalse(service.isUsersFirstDay()) diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/PayloadFactoryBaTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/PayloadFactoryBaTest.kt index ec6771cae4..504d405629 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/PayloadFactoryBaTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/PayloadFactoryBaTest.kt @@ -14,10 +14,13 @@ import io.embrace.android.embracesdk.fakes.FakePreferenceService import io.embrace.android.embracesdk.fakes.FakeProcessStateService import io.embrace.android.embracesdk.fakes.FakeSessionIdTracker import io.embrace.android.embracesdk.fakes.FakeUserService +import io.embrace.android.embracesdk.fakes.createBackgroundActivityBehavior import io.embrace.android.embracesdk.fakes.fakeSessionZygote import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.internal.capture.metadata.MetadataService import io.embrace.android.embracesdk.internal.capture.user.UserService +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.envelope.session.SessionEnvelopeSourceImpl import io.embrace.android.embracesdk.internal.envelope.session.SessionPayloadSourceImpl import io.embrace.android.embracesdk.internal.logging.EmbLoggerImpl @@ -60,7 +63,7 @@ internal class PayloadFactoryBaTest { activityService = FakeProcessStateService(isInBackground = true) deliveryService = FakeDeliveryService() ndkService = FakeNdkService() - preferencesService = FakePreferenceService(backgroundActivityEnabled = true) + preferencesService = FakePreferenceService() userService = FakeUserService() val initModule = FakeInitModule(clock = clock) spanRepository = initModule.openTelemetryModule.spanRepository @@ -68,9 +71,10 @@ internal class PayloadFactoryBaTest { currentSessionSpan = initModule.openTelemetryModule.currentSessionSpan spanService = initModule.openTelemetryModule.spanService configService = FakeConfigService( - backgroundActivityCaptureEnabled = true + backgroundActivityBehavior = createBackgroundActivityBehavior( + remoteCfg = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(threshold = 100f)) + ) ) - configService.updateListeners() blockingExecutorService = BlockingScheduledExecutorService(blockingMode = false) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/PayloadFactorySessionTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/PayloadFactorySessionTest.kt index 004c0d5ee1..a319b0dd5c 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/PayloadFactorySessionTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/PayloadFactorySessionTest.kt @@ -60,7 +60,6 @@ internal class PayloadFactorySessionTest { companion object { private val processStateService = FakeProcessStateService() - private val clock = FakeClock() @BeforeClass @JvmStatic @@ -86,13 +85,12 @@ internal class PayloadFactorySessionTest { sessionIdTracker = FakeSessionIdTracker() activityService = FakeProcessStateService(isInBackground = true) ndkService = FakeNdkService() - preferencesService = FakePreferenceService(backgroundActivityEnabled = true) + preferencesService = FakePreferenceService() userService = FakeUserService() val initModule = FakeInitModule(clock = clock) spanRepository = initModule.openTelemetryModule.spanRepository currentSessionSpan = initModule.openTelemetryModule.currentSessionSpan spanService = initModule.openTelemetryModule.spanService - configService.updateListeners() blockingExecutorService = BlockingScheduledExecutorService(blockingMode = false) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/SessionOrchestrationModuleImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/SessionOrchestrationModuleImplTest.kt index 67b66e3ea5..8e838904be 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/SessionOrchestrationModuleImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/SessionOrchestrationModuleImplTest.kt @@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.internal.session import io.embrace.android.embracesdk.fakes.FakeConfigModule import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeStartupService +import io.embrace.android.embracesdk.fakes.createBackgroundActivityBehavior import io.embrace.android.embracesdk.fakes.injection.FakeAndroidServicesModule import io.embrace.android.embracesdk.fakes.injection.FakeDeliveryModule import io.embrace.android.embracesdk.fakes.injection.FakeEssentialServiceModule @@ -10,11 +11,12 @@ import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fakes.injection.FakeLogModule import io.embrace.android.embracesdk.fakes.injection.FakePayloadSourceModule import io.embrace.android.embracesdk.fakes.injection.FakeWorkerThreadModule +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.injection.SessionOrchestrationModuleImpl import io.embrace.android.embracesdk.internal.injection.createDataSourceModule import io.embrace.android.embracesdk.internal.worker.Worker import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue import org.junit.Test internal class SessionOrchestrationModuleImplTest { @@ -30,7 +32,6 @@ internal class SessionOrchestrationModuleImplTest { fun testDefaultImplementations() { val dataSourceModule = createDataSourceModule( initModule, - configService, workerThreadModule ) val module = SessionOrchestrationModuleImpl( @@ -48,10 +49,6 @@ internal class SessionOrchestrationModuleImplTest { assertNotNull(module.payloadMessageCollator) assertNotNull(module.payloadFactory) assertNotNull(module.sessionOrchestrator) - assertTrue( - configService.listeners.single().javaClass.toString() - .contains("DataCaptureOrchestrator") - ) } @Test @@ -59,7 +56,6 @@ internal class SessionOrchestrationModuleImplTest { val configModule = createEnabledBehavior() val dataSourceModule = createDataSourceModule( initModule, - configService, workerThreadModule ) @@ -83,7 +79,9 @@ internal class SessionOrchestrationModuleImplTest { private fun createEnabledBehavior(): FakeConfigModule { return FakeConfigModule( configService = FakeConfigService( - backgroundActivityCaptureEnabled = true + backgroundActivityBehavior = createBackgroundActivityBehavior( + remoteCfg = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(threshold = 100f)) + ), ) ) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/message/PayloadFactoryImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/message/PayloadFactoryImplTest.kt index df0566353a..60569154e4 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/message/PayloadFactoryImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/message/PayloadFactoryImplTest.kt @@ -6,7 +6,10 @@ import io.embrace.android.embracesdk.fakes.FakeEnvelopeResourceSource import io.embrace.android.embracesdk.fakes.FakeGatingService import io.embrace.android.embracesdk.fakes.FakePreferenceService import io.embrace.android.embracesdk.fakes.FakeSessionPayloadSource +import io.embrace.android.embracesdk.fakes.createBackgroundActivityBehavior import io.embrace.android.embracesdk.fakes.injection.FakeInitModule +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.envelope.session.SessionEnvelopeSourceImpl import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessState import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessState.BACKGROUND @@ -54,7 +57,9 @@ internal class PayloadFactoryImplTest { @Test fun `verify expected payloads with ba enabled`() { - configService.backgroundActivityCaptureEnabled = true + configService.backgroundActivityBehavior = createBackgroundActivityBehavior( + remoteCfg = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(threshold = 100f)) + ) verifyPayloadWithState(state = FOREGROUND, zygoteCreated = true, startNewSession = true) verifyPayloadWithState(state = BACKGROUND, zygoteCreated = true, startNewSession = true) verifyPayloadWithManual() @@ -62,7 +67,9 @@ internal class PayloadFactoryImplTest { @Test fun `verify expected payloads with ba disabled`() { - configService.backgroundActivityCaptureEnabled = false + configService.backgroundActivityBehavior = createBackgroundActivityBehavior( + remoteCfg = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(threshold = 0f)) + ) verifyPayloadWithState(state = FOREGROUND, zygoteCreated = true, startNewSession = false) verifyPayloadWithState(state = BACKGROUND, zygoteCreated = false, startNewSession = false) verifyPayloadWithManual() diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/message/PayloadMessageCollatorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/message/PayloadMessageCollatorImplTest.kt index 62b2898fe7..be09ba24da 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/message/PayloadMessageCollatorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/message/PayloadMessageCollatorImplTest.kt @@ -37,7 +37,7 @@ internal class PayloadMessageCollatorImplTest { @Before fun setUp() { initModule = FakeInitModule() - coreModule = CoreModuleImpl(RuntimeEnvironment.getApplication()) + coreModule = CoreModuleImpl(RuntimeEnvironment.getApplication(), initModule) gatingService = FakeGatingService() val sessionEnvelopeSource = SessionEnvelopeSourceImpl( metadataSource = FakeEnvelopeMetadataSource(), diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/orchestrator/SessionOrchestratorTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/orchestrator/SessionOrchestratorTest.kt index 72c2f3c421..34c06072e7 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/orchestrator/SessionOrchestratorTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/session/orchestrator/SessionOrchestratorTest.kt @@ -17,11 +17,14 @@ import io.embrace.android.embracesdk.fakes.FakeStartupService import io.embrace.android.embracesdk.fakes.FakeUserService import io.embrace.android.embracesdk.fakes.FakeV2PayloadCollator import io.embrace.android.embracesdk.fakes.behavior.FakeSessionBehavior +import io.embrace.android.embracesdk.fakes.createBackgroundActivityBehavior import io.embrace.android.embracesdk.fakes.fakeBackgroundWorker import io.embrace.android.embracesdk.internal.arch.DataCaptureOrchestrator import io.embrace.android.embracesdk.internal.arch.datasource.DataSourceState import io.embrace.android.embracesdk.internal.capture.session.SessionPropertiesService import io.embrace.android.embracesdk.internal.clock.nanosToMillis +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.delivery.caching.PayloadCachingService import io.embrace.android.embracesdk.internal.delivery.caching.PayloadCachingServiceImpl import io.embrace.android.embracesdk.internal.logging.EmbLogger @@ -65,7 +68,11 @@ internal class SessionOrchestratorTest { fun setUp() { clock = FakeClock() logger = EmbLoggerImpl() - configService = FakeConfigService(backgroundActivityCaptureEnabled = true) + configService = FakeConfigService( + backgroundActivityBehavior = createBackgroundActivityBehavior( + remoteCfg = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(threshold = 100f)) + ) + ) } @Test @@ -254,7 +261,9 @@ internal class SessionOrchestratorTest { @Test fun `end with crash in background`() { configService = FakeConfigService( - backgroundActivityCaptureEnabled = true, + backgroundActivityBehavior = createBackgroundActivityBehavior( + remoteCfg = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(threshold = 100f)) + ) ) createOrchestrator(true) orchestrator.handleCrash("crashId") @@ -264,7 +273,9 @@ internal class SessionOrchestratorTest { @Test fun `end with crash in foreground`() { configService = FakeConfigService( - backgroundActivityCaptureEnabled = true, + backgroundActivityBehavior = createBackgroundActivityBehavior( + remoteCfg = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(threshold = 100f)) + ) ) createOrchestrator(false) orchestrator.handleCrash("crashId") @@ -369,7 +380,6 @@ internal class SessionOrchestratorTest { ) fakeDataSource = FakeDataSource(RuntimeEnvironment.getApplication()) dataCaptureOrchestrator = DataCaptureOrchestrator( - configService, fakeBackgroundWorker(), logger ).apply { diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanFactoryImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanFactoryImplTest.kt index 60a2f0f670..fab712cbb0 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanFactoryImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanFactoryImplTest.kt @@ -4,6 +4,8 @@ import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.FakePayloadCachingService import io.embrace.android.embracesdk.fakes.FakePersistableEmbraceSpan import io.embrace.android.embracesdk.fakes.FakeTracer +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeRedactionConfig import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.arch.schema.PrivateSpan @@ -39,7 +41,9 @@ internal class EmbraceSpanFactoryImplTest { tracer = tracer, openTelemetryClock = initModule.openTelemetryModule.openTelemetryClock, spanRepository = spanRepository, - sensitiveKeysBehavior = SensitiveKeysBehaviorImpl(listOf("password")), + sensitiveKeysBehavior = SensitiveKeysBehaviorImpl( + FakeInstrumentedConfig(redaction = FakeRedactionConfig(sensitiveKeys = listOf("password"))), + ), ) } diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanImplTest.kt index b61335b2dc..8eecb7c963 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanImplTest.kt @@ -2,6 +2,8 @@ package io.embrace.android.embracesdk.internal.spans import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.FakeOpenTelemetryClock +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeRedactionConfig import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fixtures.MAX_LENGTH_ATTRIBUTE_KEY import io.embrace.android.embracesdk.fixtures.MAX_LENGTH_ATTRIBUTE_KEY_FOR_INTERNAL_SPAN @@ -53,7 +55,9 @@ internal class EmbraceSpanImplTest { .setTracerProvider(SdkTracerProvider.builder().build()).build() .getTracer(EmbraceSpanImplTest::class.java.name) - private val sensitiveKeysBehavior = SensitiveKeysBehaviorImpl(listOf("password")) + private val sensitiveKeysBehavior = SensitiveKeysBehaviorImpl( + FakeInstrumentedConfig(redaction = FakeRedactionConfig(sensitiveKeys = listOf("password"))), + ) @Before fun setup() { diff --git a/embrace-android-core/src/test/resources/remote_config_response.json b/embrace-android-core/src/test/resources/remote_config_response.json deleted file mode 100644 index 38f601e5d4..0000000000 --- a/embrace-android-core/src/test/resources/remote_config_response.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "ls": 100, - "event_limits": {}, - "offset": 0, - "personas": [], - "session_control": { - "enable": true, - "async_end": false - }, - "threshold": 100, - "ui": { - "views": 100 - }, - "urlconnection_request_enabled": true -} \ No newline at end of file diff --git a/embrace-android-delivery/src/main/kotlin/io/embrace/android/embracesdk/internal/delivery/execution/OkHttpRequestExecutionService.kt b/embrace-android-delivery/src/main/kotlin/io/embrace/android/embracesdk/internal/delivery/execution/OkHttpRequestExecutionService.kt index 7372fb568f..019a7bc68b 100644 --- a/embrace-android-delivery/src/main/kotlin/io/embrace/android/embracesdk/internal/delivery/execution/OkHttpRequestExecutionService.kt +++ b/embrace-android-delivery/src/main/kotlin/io/embrace/android/embracesdk/internal/delivery/execution/OkHttpRequestExecutionService.kt @@ -9,7 +9,6 @@ import io.embrace.android.embracesdk.internal.logging.InternalErrorType import okhttp3.Headers.Companion.toHeaders import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient -import okhttp3.Protocol import okhttp3.Request import okhttp3.RequestBody import okio.BufferedSink @@ -17,25 +16,16 @@ import okio.buffer import okio.source import java.io.IOException import java.io.InputStream -import java.util.concurrent.TimeUnit class OkHttpRequestExecutionService( + private val okHttpClient: OkHttpClient, private val coreBaseUrl: String, private val lazyDeviceId: Lazy, private val appId: String, private val embraceVersionName: String, private val logger: EmbLogger, - connectionTimeoutSeconds: Long = DEFAULT_CONNECTION_TIMEOUT_SECONDS, - readTimeoutSeconds: Long = DEFAULT_READ_TIMEOUT_SECONDS, ) : RequestExecutionService { - private val okHttpClient = OkHttpClient() - .newBuilder() - .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1)) - .connectTimeout(connectionTimeoutSeconds, TimeUnit.SECONDS) - .readTimeout(readTimeoutSeconds, TimeUnit.SECONDS) - .build() - override fun attemptHttpRequest( payloadStream: () -> InputStream, envelopeType: SupportedEnvelopeType, @@ -79,7 +69,7 @@ class OkHttpRequestExecutionService( } private fun Endpoint.getApiRequestFromEndpoint(): ApiRequestV2 = ApiRequestV2( - url = "$coreBaseUrl/v2/${this.path}", + url = "$coreBaseUrl${this.path}", appId = appId, deviceId = lazyDeviceId.value, contentEncoding = "gzip", @@ -87,8 +77,6 @@ class OkHttpRequestExecutionService( ) private companion object { - const val DEFAULT_CONNECTION_TIMEOUT_SECONDS = 10L - const val DEFAULT_READ_TIMEOUT_SECONDS = 60L private val mediaType = "application/json".toMediaType() class ApiRequestBody( diff --git a/embrace-android-delivery/src/test/kotlin/io/embrace/android/embracesdk/internal/delivery/execution/OkHttpRequestExecutionServiceTest.kt b/embrace-android-delivery/src/test/kotlin/io/embrace/android/embracesdk/internal/delivery/execution/OkHttpRequestExecutionServiceTest.kt index d5eafd4109..afda048a10 100644 --- a/embrace-android-delivery/src/test/kotlin/io/embrace/android/embracesdk/internal/delivery/execution/OkHttpRequestExecutionServiceTest.kt +++ b/embrace-android-delivery/src/test/kotlin/io/embrace/android/embracesdk/internal/delivery/execution/OkHttpRequestExecutionServiceTest.kt @@ -4,6 +4,7 @@ import io.embrace.android.embracesdk.fakes.FakeEmbLogger import io.embrace.android.embracesdk.internal.delivery.PayloadType import io.embrace.android.embracesdk.internal.delivery.SupportedEnvelopeType import okhttp3.Headers.Companion.toHeaders +import okhttp3.OkHttpClient import okhttp3.Protocol import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer @@ -15,12 +16,14 @@ import org.junit.Before import org.junit.Test import java.net.SocketTimeoutException import java.net.UnknownHostException +import java.util.concurrent.TimeUnit class OkHttpRequestExecutionServiceTest { private lateinit var requestExecutionService: OkHttpRequestExecutionService private lateinit var server: MockWebServer private lateinit var testServerUrl: String private lateinit var logger: FakeEmbLogger + private lateinit var client: OkHttpClient private val testAppId = "test_app_id" private val testDeviceId = "test_device_id" @@ -48,15 +51,20 @@ class OkHttpRequestExecutionServiceTest { protocols = listOf(Protocol.HTTP_2, Protocol.HTTP_1_1) start() } - testServerUrl = server.url("").toString().removeSuffix("/") + testServerUrl = server.url("").toString() + client = OkHttpClient() + .newBuilder() + .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1)) + .connectTimeout(1, TimeUnit.SECONDS) + .readTimeout(1, TimeUnit.SECONDS) + .build() requestExecutionService = OkHttpRequestExecutionService( + okHttpClient = client, coreBaseUrl = testServerUrl, lazyDeviceId = lazy { testDeviceId }, appId = testAppId, embraceVersionName = testEmbraceVersionName, logger = logger, - connectionTimeoutSeconds = 1L, - readTimeoutSeconds = 1L, ) } @@ -69,7 +77,8 @@ class OkHttpRequestExecutionServiceTest { fun `return incomplete if the server does not exist`() { // given a request execution service with a non existent url requestExecutionService = OkHttpRequestExecutionService( - coreBaseUrl = "https://nonexistenturl:1565", + okHttpClient = client, + coreBaseUrl = "https://nonexistenturl:1565/", lazyDeviceId = lazy { testDeviceId }, appId = testAppId, embraceVersionName = testEmbraceVersionName, diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/anr/ndk/NativeThreadSamplerInstaller.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/anr/ndk/NativeThreadSamplerInstaller.kt index 0b5dbeb306..9b12592cb1 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/anr/ndk/NativeThreadSamplerInstaller.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/anr/ndk/NativeThreadSamplerInstaller.kt @@ -55,32 +55,12 @@ class NativeThreadSamplerInstaller( if (configService.anrBehavior.isUnityAnrCaptureEnabled()) { monitorCurrentThread(sampler, anrService) } - - // always install the handler. if config subsequently changes we take the decision - // to just ignore anr intervals, rather than attempting to uninstall the handler - configService.addListener { - onConfigChange(configService, sampler, anrService) - } } private fun isMonitoringCurrentThread(): Boolean { return isMonitoring.get() && Thread.currentThread().id == currentThread?.id } - private fun onConfigChange( - configService: ConfigService, - sampler: NativeThreadSamplerService, - anrService: AnrService, - ) { - targetHandler?.post( - Runnable { - if (configService.anrBehavior.isUnityAnrCaptureEnabled() && !isMonitoring.get()) { - monitorCurrentThread(sampler, anrService) - } - } - ) - } - private fun monitorCurrentThread(sampler: NativeThreadSamplerService, anrService: AnrService) { synchronized(this) { if (!isMonitoring.get()) { diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/FeatureModuleImpl.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/FeatureModuleImpl.kt index adc2bde58d..f208561ece 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/FeatureModuleImpl.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/FeatureModuleImpl.kt @@ -198,7 +198,7 @@ internal class FeatureModuleImpl( override val applicationExitInfoDataSource: DataSourceState by dataSourceState { DataSourceState( factory = { aeiService }, - configGate = { configService.isAppExitInfoCaptureEnabled() } + configGate = { configService.appExitInfoBehavior.isAeiCaptureEnabled() } ) } diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/detection/LivenessCheckSchedulerTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/detection/LivenessCheckSchedulerTest.kt index fef31f841c..67e399d010 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/detection/LivenessCheckSchedulerTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/detection/LivenessCheckSchedulerTest.kt @@ -7,6 +7,7 @@ import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.createAnrBehavior import io.embrace.android.embracesdk.internal.config.ConfigService import io.embrace.android.embracesdk.internal.config.remote.AnrRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.logging.EmbLogger import io.embrace.android.embracesdk.internal.logging.EmbLoggerImpl import io.embrace.android.embracesdk.internal.worker.BackgroundWorker @@ -40,7 +41,7 @@ internal class LivenessCheckSchedulerTest { anrMonitorThread = AtomicReference(Thread.currentThread()) cfg = AnrRemoteConfig() fakeClock = FakeClock(160982340900) - configService = FakeConfigService(anrBehavior = createAnrBehavior { cfg }) + configService = FakeConfigService(anrBehavior = createAnrBehavior(remoteCfg = RemoteConfig(anrConfig = cfg))) anrExecutorService = BlockingScheduledExecutorService(fakeClock) logger = EmbLoggerImpl() looper = mockk { @@ -146,21 +147,6 @@ internal class LivenessCheckSchedulerTest { assertEquals(160982340900, state.lastMonitorThreadResponseMs) } - @Test - fun testExecuteHealthCheckDifferentIntervalMs() { - // alter the intervalMs to trigger rescheduling - scheduler.startMonitoringThread() - anrExecutorService.runCurrentlyBlocked() - assertEquals(fakeClock.now(), state.lastMonitorThreadResponseMs) - cfg = cfg.copy(sampleIntervalMs = 10) - assertEquals(1, anrExecutorService.scheduledTasksCount()) - anrExecutorService.moveForwardAndRunBlocked(100) - anrExecutorService.runCurrentlyBlocked() - anrExecutorService.moveForwardAndRunBlocked(10) - assertEquals(fakeClock.now(), state.lastMonitorThreadResponseMs) - assertEquals(1, anrExecutorService.scheduledTasksCount()) - } - @Test fun `starting monitoring thread twice does not result in multiple recurring tasks`() { repeat(2) { diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/ndk/EmbraceNativeThreadSamplerServiceTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/ndk/EmbraceNativeThreadSamplerServiceTest.kt index 2773fd5355..f41d45fa4c 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/ndk/EmbraceNativeThreadSamplerServiceTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/ndk/EmbraceNativeThreadSamplerServiceTest.kt @@ -11,6 +11,7 @@ import io.embrace.android.embracesdk.internal.config.ConfigService import io.embrace.android.embracesdk.internal.config.behavior.AnrBehavior import io.embrace.android.embracesdk.internal.config.remote.AllowedNdkSampleMethod import io.embrace.android.embracesdk.internal.config.remote.AnrRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.config.remote.Unwinder import io.embrace.android.embracesdk.internal.payload.NativeThreadAnrInterval import io.embrace.android.embracesdk.internal.payload.NativeThreadAnrSample @@ -46,7 +47,12 @@ internal class EmbraceNativeThreadSamplerServiceTest { @Before fun setUp() { cfg = AnrRemoteConfig(pctNativeThreadAnrSamplingEnabled = 100f) - anrBehavior = createAnrBehavior { cfg } + setupSampler() + every { random.nextInt(any()) } returns 0 + } + + private fun setupSampler() { + anrBehavior = createAnrBehavior(remoteCfg = RemoteConfig(anrConfig = cfg)) configService = FakeConfigService(anrBehavior = anrBehavior) sharedObjectLoader = FakeSharedObjectLoader() delegate = mockk(relaxed = true) @@ -62,7 +68,6 @@ internal class EmbraceNativeThreadSamplerServiceTest { FakeDeviceArchitecture(), sharedObjectLoader ) - every { random.nextInt(any()) } returns 0 } @Test @@ -90,6 +95,7 @@ internal class EmbraceNativeThreadSamplerServiceTest { @Test fun testSessionEndDisabledSampling() { cfg = cfg.copy(pctNativeThreadAnrSamplingEnabled = 0f) + setupSampler() sampler.intervals = mutableListOf(obj) simulateUnityThreadSample() assertNull(sampler.getCapturedIntervals(false)) @@ -210,6 +216,7 @@ internal class EmbraceNativeThreadSamplerServiceTest { ), ignoreNativeThreadAnrSamplingAllowlist = false ) + setupSampler() val unityPlayer = StackTraceElement("com.unity3d.player.UnityPlayer", "pauseUnity", null, -1) @@ -252,6 +259,7 @@ internal class EmbraceNativeThreadSamplerServiceTest { cfg = cfg.copy( ignoreNativeThreadAnrSamplingAllowlist = false ) + setupSampler() assertEquals(-1, sampler.count) assertEquals(-1, sampler.factor) assertTrue(sampler.ignored) @@ -268,6 +276,7 @@ internal class EmbraceNativeThreadSamplerServiceTest { cfg = cfg.copy( pctNativeThreadAnrSamplingEnabled = 0f ) + setupSampler() sampler.onThreadBlocked(Thread.currentThread(), 0) verify(exactly = 0) { delegate.startSampling(any(), any()) } @@ -281,6 +290,7 @@ internal class EmbraceNativeThreadSamplerServiceTest { cfg = cfg.copy( ignoreNativeThreadAnrSamplingAllowlist = false ) + setupSampler() sampler.onThreadBlocked(Thread.currentThread(), 0) repeat(10) { @@ -294,6 +304,7 @@ internal class EmbraceNativeThreadSamplerServiceTest { cfg = cfg.copy( pctNativeThreadAnrSamplingEnabled = 0f ) + setupSampler() sampler.onThreadBlocked(Thread.currentThread(), 0) repeat(10) { diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/crumbs/BreadcrumbDataSourceTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/crumbs/BreadcrumbDataSourceTest.kt index 39dfae0f7a..c3e15a1de1 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/crumbs/BreadcrumbDataSourceTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/crumbs/BreadcrumbDataSourceTest.kt @@ -1,9 +1,9 @@ package io.embrace.android.embracesdk.internal.capture.crumbs import io.embrace.android.embracesdk.arch.assertIsType -import io.embrace.android.embracesdk.fakes.FakeBreadcrumbBehavior import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan import io.embrace.android.embracesdk.fakes.FakeEmbLogger +import io.embrace.android.embracesdk.fakes.behavior.FakeBreadcrumbBehavior import io.embrace.android.embracesdk.internal.arch.schema.EmbType import org.junit.Assert.assertEquals import org.junit.Before diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/crumbs/WebViewUrlDataSourceTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/crumbs/WebViewUrlDataSourceTest.kt index fcd18f2343..6d7a756b03 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/crumbs/WebViewUrlDataSourceTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/crumbs/WebViewUrlDataSourceTest.kt @@ -1,8 +1,8 @@ package io.embrace.android.embracesdk.internal.capture.crumbs -import io.embrace.android.embracesdk.fakes.FakeBreadcrumbBehavior import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan +import io.embrace.android.embracesdk.fakes.behavior.FakeBreadcrumbBehavior import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.config.ConfigService import io.embrace.android.embracesdk.internal.logging.EmbLoggerImpl diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/FeatureModuleImplTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/FeatureModuleImplTest.kt index 9dd0bb706e..2ec60b7d09 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/FeatureModuleImplTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/FeatureModuleImplTest.kt @@ -18,10 +18,11 @@ internal class FeatureModuleImplTest { @Test fun testFeatureModule() { val registry = FakeFeatureRegistry() + val initModule = FakeInitModule() val module = FeatureModuleImpl( featureRegistry = registry, - coreModule = createCoreModule(mockk(relaxed = true)), - initModule = FakeInitModule(), + coreModule = createCoreModule(mockk(relaxed = true), initModule), + initModule = initModule, otelModule = FakeOpenTelemetryModule(), workerThreadModule = FakeWorkerThreadModule(), systemServiceModule = FakeSystemServiceModule(), diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/NativeFeatureModuleImplTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/NativeFeatureModuleImplTest.kt index 2015cc7ac5..19dc14a2a4 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/NativeFeatureModuleImplTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/NativeFeatureModuleImplTest.kt @@ -20,9 +20,10 @@ internal class NativeFeatureModuleImplTest { @Test fun testDefaultImplementations() { + val initModule = FakeInitModule() val module = NativeFeatureModuleImpl( - FakeInitModule(), - CoreModuleImpl(RuntimeEnvironment.getApplication()), + initModule, + CoreModuleImpl(RuntimeEnvironment.getApplication(), initModule), FakeStorageModule(), FakeEssentialServiceModule(), FakeConfigModule(), diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/StorageModuleImplTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/StorageModuleImplTest.kt index 9d35f24f6e..dbb6bcd8c9 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/StorageModuleImplTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/StorageModuleImplTest.kt @@ -14,7 +14,7 @@ internal class StorageModuleImplTest { @Test fun testDefaultImplementations() { val initModule = FakeInitModule() - val coreModule = CoreModuleImpl(RuntimeEnvironment.getApplication()) + val coreModule = CoreModuleImpl(RuntimeEnvironment.getApplication(), initModule) val module = createStorageModuleSupplier( initModule = initModule, coreModule = coreModule, @@ -22,7 +22,6 @@ internal class StorageModuleImplTest { ) assertNotNull(module.storageService) - assertNotNull(module.cache) assertNotNull(module.cacheService) assertNotNull(module.deliveryCacheManager) assertNotNull(module.storageService) diff --git a/embrace-android-infra/src/main/kotlin/io/embrace/android/embracesdk/internal/logging/InternalErrorType.kt b/embrace-android-infra/src/main/kotlin/io/embrace/android/embracesdk/internal/logging/InternalErrorType.kt index 6a0cc58b19..e1326504ff 100644 --- a/embrace-android-infra/src/main/kotlin/io/embrace/android/embracesdk/internal/logging/InternalErrorType.kt +++ b/embrace-android-infra/src/main/kotlin/io/embrace/android/embracesdk/internal/logging/InternalErrorType.kt @@ -10,13 +10,11 @@ enum class InternalErrorType { ENABLE_DATA_CAPTURE, NETWORK_STATUS_CAPTURE_FAIL, SCREEN_RES_CAPTURE_FAIL, - CONFIG_LISTENER_FAIL, MEMORY_CLEAN_LISTENER_FAIL, FG_SESSION_CACHE_FAIL, ACTIVITY_LISTENER_FAIL, PROCESS_STATE_CALLBACK_FAIL, ANR_HEARTBEAT_CHECK_FAIL, - CFG_CHANGE_DATA_CAPTURE_FAIL, SESSION_CHANGE_DATA_CAPTURE_FAIL, DATA_SOURCE_DATA_CAPTURE_FAIL, DISK_STAT_CAPTURE_FAIL, diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/CachedConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/CachedConfig.kt deleted file mode 100644 index bb838e1c6f..0000000000 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/CachedConfig.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.embrace.android.embracesdk.internal.comms.api - -import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig - -class CachedConfig( - val remoteConfig: RemoteConfig? = null, - val eTag: String? = null, -) { - fun isValid(): Boolean = remoteConfig != null && eTag != null -} diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/Endpoint.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/Endpoint.kt index 30296e0ecc..be2e2c46b4 100644 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/Endpoint.kt +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/comms/api/Endpoint.kt @@ -9,5 +9,6 @@ enum class Endpoint( ) { LOGS("logs", "v2"), SESSIONS("spans", "v2"), - UNKNOWN("unknown", "v1") + CONFIG("config", "v2"), + UNKNOWN("unknown", "v1"), } diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/InstrumentedConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/InstrumentedConfig.kt deleted file mode 100644 index a0fbdcfa09..0000000000 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/InstrumentedConfig.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.embrace.android.embracesdk.internal.config.instrumented - -/** - * This class and its contents are instrumented by the swazzler to alter its return values - * based on what values have been set in the embrace-config.json. If no value has been set, - * the default value specified in the class will be used. - * - * It's important to: - * - * (1) always use functions, as this is somewhat easier to instrument than Kotlin properties - * (2) always keep the swazzler in sync when adding new config values or altering existing ones - */ -@Swazzled -object InstrumentedConfig { - val baseUrls: BaseUrlConfig = BaseUrlConfig - val enabledFeatures: EnabledFeatureConfig = EnabledFeatureConfig - val networkCapture: NetworkCaptureConfig = NetworkCaptureConfig - val project: ProjectConfig = ProjectConfig - val redaction: RedactionConfig = RedactionConfig - val session: SessionConfig = SessionConfig -} diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/InstrumentedConfigImpl.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/InstrumentedConfigImpl.kt new file mode 100644 index 0000000000..eed51253eb --- /dev/null +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/InstrumentedConfigImpl.kt @@ -0,0 +1,47 @@ +package io.embrace.android.embracesdk.internal.config.instrumented + +import io.embrace.android.embracesdk.internal.config.instrumented.schema.BaseUrlConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.NetworkCaptureConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.ProjectConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.RedactionConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.SessionConfig + +/** + * This class and its contents are instrumented by the swazzler to alter its return values + * based on what values have been set in the embrace-config.json. If no value has been set, + * the default value specified in the class will be used. + * + * It's important to: + * + * (1) always use functions, as this is somewhat easier to instrument than Kotlin properties + * (2) always keep the swazzler in sync when adding new config values or altering existing ones + */ +@Swazzled +object InstrumentedConfigImpl : InstrumentedConfig { + override val baseUrls: BaseUrlConfig = BaseUrlConfigImpl + override val enabledFeatures: EnabledFeatureConfig = EnabledFeatureConfigImpl + override val networkCapture: NetworkCaptureConfig = NetworkCaptureConfigImpl + override val project: ProjectConfig = ProjectConfigImpl + override val redaction: RedactionConfig = RedactionConfigImpl + override val session: SessionConfig = SessionConfigImpl +} + +@Swazzled +object BaseUrlConfigImpl : BaseUrlConfig + +@Swazzled +object EnabledFeatureConfigImpl : EnabledFeatureConfig + +@Swazzled +object NetworkCaptureConfigImpl : NetworkCaptureConfig + +@Swazzled +object ProjectConfigImpl : ProjectConfig + +@Swazzled +object RedactionConfigImpl : RedactionConfig + +@Swazzled +object SessionConfigImpl : SessionConfig diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/BaseUrlConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/BaseUrlConfig.kt similarity index 82% rename from embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/BaseUrlConfig.kt rename to embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/BaseUrlConfig.kt index 6385207e9b..187b8f54d4 100644 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/BaseUrlConfig.kt +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/BaseUrlConfig.kt @@ -1,11 +1,9 @@ -package io.embrace.android.embracesdk.internal.config.instrumented +package io.embrace.android.embracesdk.internal.config.instrumented.schema /** * Declares the base URLs the SDK should use in HTTP requests */ -@Suppress("FunctionOnlyReturningConstant") -@Swazzled -object BaseUrlConfig { +interface BaseUrlConfig { /** * Config base URL diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/EnabledFeatureConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/EnabledFeatureConfig.kt similarity index 97% rename from embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/EnabledFeatureConfig.kt rename to embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/EnabledFeatureConfig.kt index bb215b49c8..7ede1a4c4c 100644 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/EnabledFeatureConfig.kt +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/EnabledFeatureConfig.kt @@ -1,11 +1,9 @@ -package io.embrace.android.embracesdk.internal.config.instrumented +package io.embrace.android.embracesdk.internal.config.instrumented.schema /** * Declares what features are enabled/disabled across the entire SDK. */ -@Suppress("FunctionOnlyReturningConstant") -@Swazzled -object EnabledFeatureConfig { +interface EnabledFeatureConfig { /** * Gates Unity ANR capture. diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/InstrumentedConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/InstrumentedConfig.kt new file mode 100644 index 0000000000..911040a6b8 --- /dev/null +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/InstrumentedConfig.kt @@ -0,0 +1,14 @@ +package io.embrace.android.embracesdk.internal.config.instrumented.schema + +/** + * Defines the locally set configuration for the SDK. This is typically set from embrace-config.json + * and instrumented by swazzler, but can be overridden for test purposes. + */ +interface InstrumentedConfig { + val baseUrls: BaseUrlConfig + val enabledFeatures: EnabledFeatureConfig + val networkCapture: NetworkCaptureConfig + val project: ProjectConfig + val redaction: RedactionConfig + val session: SessionConfig +} diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/NetworkCaptureConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/NetworkCaptureConfig.kt similarity index 95% rename from embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/NetworkCaptureConfig.kt rename to embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/NetworkCaptureConfig.kt index 326ec2c86f..e51306d410 100644 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/NetworkCaptureConfig.kt +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/NetworkCaptureConfig.kt @@ -1,11 +1,10 @@ -package io.embrace.android.embracesdk.internal.config.instrumented +package io.embrace.android.embracesdk.internal.config.instrumented.schema /** * Declares how the SDK should capture network requests */ @Suppress("FunctionOnlyReturningConstant") -@Swazzled -object NetworkCaptureConfig { +interface NetworkCaptureConfig { /** * The network request capture limit per domain diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/ProjectConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/ProjectConfig.kt similarity index 91% rename from embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/ProjectConfig.kt rename to embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/ProjectConfig.kt index 51677bbb83..93a24d8592 100644 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/ProjectConfig.kt +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/ProjectConfig.kt @@ -1,11 +1,9 @@ -package io.embrace.android.embracesdk.internal.config.instrumented +package io.embrace.android.embracesdk.internal.config.instrumented.schema /** * Declares metadata about the app project */ -@Suppress("FunctionOnlyReturningConstant") -@Swazzled -object ProjectConfig { +interface ProjectConfig { /** * The project's appId diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/RedactionConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/RedactionConfig.kt similarity index 80% rename from embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/RedactionConfig.kt rename to embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/RedactionConfig.kt index 513ffe96f6..7de255e1af 100644 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/RedactionConfig.kt +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/RedactionConfig.kt @@ -1,11 +1,9 @@ -package io.embrace.android.embracesdk.internal.config.instrumented +package io.embrace.android.embracesdk.internal.config.instrumented.schema /** * Declares how the SDK should redact sensitive data */ -@Suppress("FunctionOnlyReturningConstant") -@Swazzled -object RedactionConfig { +interface RedactionConfig { /** * Provides a list of sensitive keys whose values should be redacted on capture. diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/SessionConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/SessionConfig.kt similarity index 79% rename from embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/SessionConfig.kt rename to embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/SessionConfig.kt index 7b2575a51b..8f69dabb2b 100644 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/SessionConfig.kt +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/instrumented/schema/SessionConfig.kt @@ -1,13 +1,9 @@ -package io.embrace.android.embracesdk.internal.config.instrumented - -import com.squareup.moshi.Json +package io.embrace.android.embracesdk.internal.config.instrumented.schema /** * Declares metadata about the app project */ -@Suppress("FunctionOnlyReturningConstant") -@Swazzled -object SessionConfig { +interface SessionConfig { /** * A whitelist of session components (i.e. Breadcrumbs, Session properties, etc) that should be @@ -16,7 +12,6 @@ object SessionConfig { * * sdk_config.session.components */ - @Json(name = "components") fun getSessionComponents(): List? = null /** @@ -25,6 +20,5 @@ object SessionConfig { * * sdk_config.session.send_full_for */ - @Json(name = "send_full_for") fun getFullSessionEvents(): List = emptyList() } diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/EmbraceInternalInterfaceTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/EmbraceInternalInterfaceTest.kt index c54c185289..aa95aa90a3 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/EmbraceInternalInterfaceTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/EmbraceInternalInterfaceTest.kt @@ -7,8 +7,6 @@ import io.embrace.android.embracesdk.LogType import io.embrace.android.embracesdk.assertions.findEventOfType import io.embrace.android.embracesdk.assertions.findSessionSpan import io.embrace.android.embracesdk.assertions.findSpansByName -import io.embrace.android.embracesdk.fakes.behavior.FakeSdkModeBehavior -import io.embrace.android.embracesdk.fakes.createNetworkBehavior import io.embrace.android.embracesdk.internal.EmbraceInternalApi import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.config.remote.NetworkCaptureRuleRemoteConfig @@ -113,8 +111,6 @@ internal class EmbraceInternalInterfaceTest { testRule.runTest( testCaseAction = { recordSession { - clock.tick() - configService.updateListeners() clock.tick() EmbraceInternalApi.getInstance().internalInterface.recordCompletedNetworkRequest( url = URL, @@ -207,23 +203,18 @@ internal class EmbraceInternalInterfaceTest { @Test fun `access check methods work as expected`() { testRule.runTest( - setupAction = { - overriddenConfigService.networkBehavior = - createNetworkBehavior(remoteCfg = { - RemoteConfig( - disabledUrlPatterns = setOf("dontlogmebro.pizza"), - networkCaptureRules = setOf( - NetworkCaptureRuleRemoteConfig( - id = "test", - duration = 10000, - method = "GET", - urlRegex = "capture.me", - expiresIn = 10000 - ) - ) - ) - }) - }, + persistedRemoteConfig = RemoteConfig( + disabledUrlPatterns = setOf("dontlogmebro.pizza"), + networkCaptureRules = setOf( + NetworkCaptureRuleRemoteConfig( + id = "test", + duration = 10000, + method = "GET", + urlRegex = "capture.me", + expiresIn = 10000 + ) + ) + ), testCaseAction = { recordSession { assertTrue( @@ -239,7 +230,7 @@ internal class EmbraceInternalInterfaceTest { ) ) assertFalse(EmbraceInternalApi.getInstance().internalInterface.shouldCaptureNetworkBody(URL, "GET")) - assertTrue(EmbraceInternalApi.getInstance().internalInterface.isNetworkSpanForwardingEnabled()) + assertFalse(EmbraceInternalApi.getInstance().internalInterface.isNetworkSpanForwardingEnabled()) } } ) @@ -314,10 +305,8 @@ internal class EmbraceInternalInterfaceTest { @Test fun `SDK will not start if feature flag has it being disabled`() { testRule.runTest( + persistedRemoteConfig = RemoteConfig(threshold = 0), expectSdkToStart = false, - setupAction = { - overriddenConfigService.sdkModeBehavior = FakeSdkModeBehavior(sdkDisabled = true) - }, testCaseAction = { assertFalse(embrace.isStarted) } diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/FlutterInternalInterfaceTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/FlutterInternalInterfaceTest.kt index b9ce27625d..0747068f44 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/FlutterInternalInterfaceTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/FlutterInternalInterfaceTest.kt @@ -1,11 +1,10 @@ -@file:Suppress("DEPRECATION") - package io.embrace.android.embracesdk.testcases import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.embrace.android.embracesdk.AppFramework.FLUTTER import io.embrace.android.embracesdk.LogExceptionType import io.embrace.android.embracesdk.fakes.FakeClock +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeProjectConfig import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fakes.injection.FakeWorkerThreadModule import io.embrace.android.embracesdk.internal.EmbraceInternalApi @@ -30,13 +29,17 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) internal class FlutterInternalInterfaceTest { + private val instrumentedConfig = FakeInstrumentedConfig(project = FakeProjectConfig( + appId = "abcde", + appFramework = "flutter" + )) + @Rule @JvmField val testRule: IntegrationTestRule = IntegrationTestRule { val clock = FakeClock(IntegrationTestRule.DEFAULT_SDK_START_TIME_MS) val fakeInitModule = FakeInitModule(clock = clock) EmbraceSetupInterface( - appFramework = FLUTTER, overriddenClock = clock, overriddenInitModule = fakeInitModule, overriddenWorkerThreadModule = FakeWorkerThreadModule( @@ -49,6 +52,7 @@ internal class FlutterInternalInterfaceTest { @Test fun `flutter without values should return defaults`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession() }, @@ -65,6 +69,7 @@ internal class FlutterInternalInterfaceTest { @Test fun `flutter methods work in current session`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().flutterInternalInterface.setDartVersion("28.9.1") @@ -84,6 +89,7 @@ internal class FlutterInternalInterfaceTest { @Test fun `flutter metadata already present from previous session`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().flutterInternalInterface.setDartVersion("28.9.1") @@ -104,6 +110,7 @@ internal class FlutterInternalInterfaceTest { @Test fun `setting null is ignored`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().flutterInternalInterface.setDartVersion("28.9.1") @@ -128,6 +135,7 @@ internal class FlutterInternalInterfaceTest { @Test fun `flutter values from current session override previous values`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().flutterInternalInterface.setDartVersion("28.9.1") @@ -158,6 +166,7 @@ internal class FlutterInternalInterfaceTest { val expectedLibrary = "library" testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().flutterInternalInterface.logHandledDartException( @@ -202,6 +211,7 @@ internal class FlutterInternalInterfaceTest { val expectedLibrary = "library" testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().flutterInternalInterface.logUnhandledDartException( diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt index ab0ee9df8c..59a3087d59 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt @@ -1,7 +1,6 @@ package io.embrace.android.embracesdk.testcases import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.embrace.android.embracesdk.fakes.createNetworkBehavior import io.embrace.android.embracesdk.internal.clock.millisToNanos import io.embrace.android.embracesdk.internal.config.remote.NetworkCaptureRuleRemoteConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig @@ -180,26 +179,20 @@ internal class NetworkRequestApiTest { @Test fun `disabled URLs not recorded`() { testRule.runTest( - setupAction = { - overriddenConfigService.networkBehavior = - createNetworkBehavior(remoteCfg = { - RemoteConfig( - disabledUrlPatterns = setOf("dontlogmebro.pizza"), - networkCaptureRules = setOf( - NetworkCaptureRuleRemoteConfig( - id = "test", - duration = 10000, - method = "GET", - urlRegex = "capture.me", - expiresIn = 10000 - ) - ) - ) - }) - }, + persistedRemoteConfig = RemoteConfig( + disabledUrlPatterns = setOf("dontlogmebro.pizza"), + networkCaptureRules = setOf( + NetworkCaptureRuleRemoteConfig( + id = "test", + duration = 10000, + method = "GET", + urlRegex = "capture.me", + expiresIn = 10000 + ) + ) + ), testCaseAction = { recordSession { - configService.updateListeners() clock.tick(5) embrace.recordNetworkRequest( EmbraceNetworkRequest.fromCompletedRequest( @@ -253,7 +246,6 @@ internal class NetworkRequestApiTest { testRule.runTest( testCaseAction = { recordSession { - configService.updateListeners() clock.tick(5) val request = EmbraceNetworkRequest.fromCompletedRequest( @@ -292,7 +284,6 @@ internal class NetworkRequestApiTest { testCaseAction = { recordSession { clock.tick(2L) - configService.updateListeners() clock.tick(5L) embrace.recordNetworkRequest(expectedRequest) } diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PublicApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PublicApiTest.kt index 440f82ff68..0470220368 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PublicApiTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PublicApiTest.kt @@ -2,12 +2,12 @@ package io.embrace.android.embracesdk.testcases import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.LastRunEndState -import io.embrace.android.embracesdk.fakes.behavior.FakeNetworkSpanForwardingBehavior -import io.embrace.android.embracesdk.internal.payload.AppFramework +import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.testframework.IntegrationTestRule -import io.embrace.android.embracesdk.testframework.actions.EmbraceSetupInterface import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -26,55 +26,26 @@ internal class PublicApiTest { val validPattern = Regex("^00-" + "[0-9a-fA-F]{32}" + "-" + "[0-9a-fA-F]{16}" + "-01$") } + private val instrumentedConfig = FakeInstrumentedConfig(enabledFeatures = FakeEnabledFeatureConfig(networkSpanForwarding = true)) + @Rule @JvmField - val testRule: IntegrationTestRule = IntegrationTestRule { - EmbraceSetupInterface().apply { - overriddenConfigService.networkSpanForwardingBehavior = - FakeNetworkSpanForwardingBehavior(true) - } - } - - @Test - fun `SDK can start`() { - testRule.runTest( - preSdkStartAction = { - assertFalse(embrace.isStarted) - }, - testCaseAction = { - assertEquals(AppFramework.NATIVE, configService.appFramework) - assertFalse(configService.isSdkDisabled()) - assertTrue(embrace.isStarted) - } - ) - } + val testRule: IntegrationTestRule = IntegrationTestRule() @Test fun `SDK start defaults to native app framework`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { - assertEquals(AppFramework.NATIVE, configService.appFramework) assertTrue(embrace.isStarted) } ) } - @Test - fun `SDK disabled via config cannot start`() { - testRule.runTest( - expectSdkToStart = false, - setupAction = { - overriddenConfigService.sdkDisabled = true - }, - testCaseAction = { - assertFalse(embrace.isStarted) - } - ) - } - @Test fun `getCurrentSessionId returns null when SDK is not started`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, startSdk = false, testCaseAction = { assertNull(embrace.currentSessionId) @@ -85,6 +56,7 @@ internal class PublicApiTest { @Test fun `getCurrentSessionId returns sessionId when SDK is started and foreground session is active`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { assertEquals( @@ -102,6 +74,8 @@ internal class PublicApiTest { var foregroundSessionId: String? = null var backgroundSessionId: String? = null testRule.runTest( + persistedRemoteConfig = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(100f)), + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { foregroundSessionId = embrace.currentSessionId @@ -118,6 +92,7 @@ internal class PublicApiTest { @Test fun `getLastRunEndState() behave as expected`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, preSdkStartAction = { assertEquals(LastRunEndState.INVALID, embrace.lastRunEndState) }, @@ -130,6 +105,7 @@ internal class PublicApiTest { @Test fun `ensure all generated W3C traceparent conforms to the expected format`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { repeat(100) { assertTrue(validPattern.matches(checkNotNull(embrace.generateW3cTraceparent()))) diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PushNotificationApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PushNotificationApiTest.kt index 6472060b28..65858c0fc4 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PushNotificationApiTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PushNotificationApiTest.kt @@ -3,7 +3,8 @@ package io.embrace.android.embracesdk.testcases import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.assertions.findEventOfType import io.embrace.android.embracesdk.assertions.findSessionSpan -import io.embrace.android.embracesdk.fakes.FakeBreadcrumbBehavior +import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.SessionPayload @@ -84,11 +85,9 @@ internal class PushNotificationApiTest { @Test fun `log push notification with pii`() { testRule.runTest( - setupAction = { - overriddenConfigService.breadcrumbBehavior = FakeBreadcrumbBehavior( - captureFcmPiiDataEnabled = true - ) - }, + instrumentedConfig = FakeInstrumentedConfig( + enabledFeatures = FakeEnabledFeatureConfig(fcmPiiCapture = true), + ), testCaseAction = { recordSession { embrace.logPushNotification("title", "body", "from", "id", 1, 2, true, true) diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/ReactNativeInternalInterfaceTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/ReactNativeInternalInterfaceTest.kt index f51582828e..e8af191bfd 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/ReactNativeInternalInterfaceTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/ReactNativeInternalInterfaceTest.kt @@ -1,12 +1,11 @@ -@file:Suppress("DEPRECATION") - package io.embrace.android.embracesdk.testcases import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.embrace.android.embracesdk.AppFramework.REACT_NATIVE import io.embrace.android.embracesdk.assertions.findSpanOfType import io.embrace.android.embracesdk.assertions.findSpanSnapshotOfType import io.embrace.android.embracesdk.assertions.findSpansByName +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeProjectConfig import io.embrace.android.embracesdk.internal.EmbraceInternalApi import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.clock.nanosToMillis @@ -26,15 +25,19 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) internal class ReactNativeInternalInterfaceTest { + private val instrumentedConfig = FakeInstrumentedConfig(project = FakeProjectConfig( + appId = "abcde", + appFramework = "react_native" + )) + @Rule @JvmField - val testRule: IntegrationTestRule = IntegrationTestRule { - EmbraceSetupInterface(appFramework = REACT_NATIVE) - } + val testRule: IntegrationTestRule = IntegrationTestRule() @Test fun `react native without values should return defaults`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession() }, @@ -51,6 +54,7 @@ internal class ReactNativeInternalInterfaceTest { @Test fun `react native methods work in current session`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().reactNativeInternalInterface.setReactNativeVersionNumber("28.9.1") @@ -72,6 +76,7 @@ internal class ReactNativeInternalInterfaceTest { @Test fun `react native metadata already present from previous session`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().reactNativeInternalInterface.setReactNativeVersionNumber("28.9.1") @@ -95,6 +100,7 @@ internal class ReactNativeInternalInterfaceTest { @Test fun `react native values from current session override previous values`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().reactNativeInternalInterface.setReactNativeVersionNumber("28.9.1") @@ -123,6 +129,7 @@ internal class ReactNativeInternalInterfaceTest { @Test fun `react native action`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().reactNativeInternalInterface.logRnAction( @@ -163,6 +170,7 @@ internal class ReactNativeInternalInterfaceTest { @Test fun `react native log RN view`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().reactNativeInternalInterface.logRnView("HomeScreen") @@ -197,6 +205,7 @@ internal class ReactNativeInternalInterfaceTest { @Test fun `react native log RN view same name`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().reactNativeInternalInterface.logRnView("HomeScreen") diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/SessionApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/SessionApiTest.kt index d98371c4b4..8f1b716641 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/SessionApiTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/SessionApiTest.kt @@ -3,6 +3,8 @@ package io.embrace.android.embracesdk.testcases import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.ResourceReader import io.embrace.android.embracesdk.fakes.behavior.FakeAutoDataCaptureBehavior +import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.internal.clock.millisToNanos import io.embrace.android.embracesdk.internal.clock.nanosToMillis import io.embrace.android.embracesdk.internal.opentelemetry.embFreeDiskBytes @@ -23,12 +25,7 @@ internal class SessionApiTest { @Rule @JvmField - val testRule: IntegrationTestRule = IntegrationTestRule { - EmbraceSetupInterface().apply { - overriddenConfigService.autoDataCaptureBehavior = - FakeAutoDataCaptureBehavior(diskUsageReportingEnabled = false) - } - } + val testRule: IntegrationTestRule = IntegrationTestRule() /** * Verifies that a session end message is sent. @@ -39,6 +36,7 @@ internal class SessionApiTest { var startTime: Long = -1 testRule.runTest( + instrumentedConfig = FakeInstrumentedConfig(enabledFeatures = FakeEnabledFeatureConfig(diskUsageCapture = false, bgActivityCapture = true)), testCaseAction = { startTime = clock.now() recordSession { diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt index 0a6fb25a0e..b55fbb5613 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt @@ -5,10 +5,14 @@ import io.embrace.android.embracesdk.arch.assertIsTypePerformance import io.embrace.android.embracesdk.assertions.assertEmbraceSpanData import io.embrace.android.embracesdk.concurrency.SingleThreadTestScheduledExecutor import io.embrace.android.embracesdk.fakes.FakeSpanExporter +import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.createBackgroundActivityBehavior import io.embrace.android.embracesdk.fixtures.TOO_LONG_ATTRIBUTE_KEY import io.embrace.android.embracesdk.fixtures.TOO_LONG_ATTRIBUTE_VALUE import io.embrace.android.embracesdk.internal.EmbraceInternalApi import io.embrace.android.embracesdk.internal.clock.millisToNanos +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig import io.embrace.android.embracesdk.internal.payload.ApplicationState import io.embrace.android.embracesdk.internal.payload.Attribute import io.embrace.android.embracesdk.internal.payload.Span @@ -56,6 +60,7 @@ internal class TracingApiTest { val spanExporter = FakeSpanExporter() testRule.runTest( + instrumentedConfig = FakeInstrumentedConfig(enabledFeatures = FakeEnabledFeatureConfig(bgActivityCapture = true)), preSdkStartAction = { testStartTimeMs = clock.now() clock.tick(100L) @@ -320,9 +325,6 @@ internal class TracingApiTest { @Test fun `can only create span if there is a valid session`() { testRule.runTest( - setupAction = { - overriddenConfigService.backgroundActivityCaptureEnabled = false - }, preSdkStartAction = { assertNull(embrace.startSpan("test")) }, diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/UnityInternalInterfaceTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/UnityInternalInterfaceTest.kt index 029b6d7612..874008e65f 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/UnityInternalInterfaceTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/UnityInternalInterfaceTest.kt @@ -1,13 +1,11 @@ -@file:Suppress("DEPRECATION") - package io.embrace.android.embracesdk.testcases import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.embrace.android.embracesdk.AppFramework.UNITY +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeProjectConfig import io.embrace.android.embracesdk.internal.EmbraceInternalApi import io.embrace.android.embracesdk.internal.payload.AppFramework import io.embrace.android.embracesdk.testframework.IntegrationTestRule -import io.embrace.android.embracesdk.testframework.actions.EmbraceSetupInterface import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Rule @@ -20,15 +18,19 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) internal class UnityInternalInterfaceTest { + private val instrumentedConfig = FakeInstrumentedConfig(project = FakeProjectConfig( + appId = "abcde", + appFramework = "unity" + )) + @Rule @JvmField - val testRule: IntegrationTestRule = IntegrationTestRule { - EmbraceSetupInterface(appFramework = UNITY) - } + val testRule: IntegrationTestRule = IntegrationTestRule() @Test fun `unity without values should return defaults`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession() }, @@ -45,6 +47,7 @@ internal class UnityInternalInterfaceTest { @Test fun `unity methods work in current session`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().unityInternalInterface.setUnityMetaData( @@ -68,6 +71,7 @@ internal class UnityInternalInterfaceTest { @Test fun `unity metadata already present from previous session`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().unityInternalInterface.setUnityMetaData( @@ -92,6 +96,7 @@ internal class UnityInternalInterfaceTest { @Test fun `unity values from current session override previous values`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { EmbraceInternalApi.getInstance().unityInternalInterface.setUnityMetaData( diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ActivityFeatureTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ActivityFeatureTest.kt index ffbda44ee0..16ee1053be 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ActivityFeatureTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ActivityFeatureTest.kt @@ -3,7 +3,6 @@ package io.embrace.android.embracesdk.testcases.features import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.assertions.findSpanOfType -import io.embrace.android.embracesdk.fakes.FakeBreadcrumbBehavior import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.clock.nanosToMillis import io.embrace.android.embracesdk.testframework.IntegrationTestRule @@ -27,11 +26,6 @@ internal class ActivityFeatureTest { var startTimeMs: Long = 0 testRule.runTest( - setupAction = { - overriddenConfigService.breadcrumbBehavior = FakeBreadcrumbBehavior( - automaticActivityCaptureEnabled = true - ) - }, testCaseAction = { recordSession { startTimeMs = clock.now() diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/BreadcrumbFeatureTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/BreadcrumbFeatureTest.kt index 92ac750c92..6eb6bcdca1 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/BreadcrumbFeatureTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/BreadcrumbFeatureTest.kt @@ -4,6 +4,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.assertions.findEventOfType import io.embrace.android.embracesdk.assertions.findSessionSpan import io.embrace.android.embracesdk.internal.arch.schema.EmbType +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.payload.ApplicationState import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.SessionPayload @@ -23,6 +25,7 @@ internal class BreadcrumbFeatureTest { @Test fun `custom breadcrumb feature`() { testRule.runTest( + persistedRemoteConfig = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(100f)), testCaseAction = { recordSession { embrace.addBreadcrumb("Hello, world!") diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/EmbraceLoggingFeatureTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/EmbraceLoggingFeatureTest.kt index 55b27ff546..70666ce0de 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/EmbraceLoggingFeatureTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/EmbraceLoggingFeatureTest.kt @@ -4,6 +4,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.LogExceptionType import io.embrace.android.embracesdk.Severity import io.embrace.android.embracesdk.fakes.FakeClock +import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fakes.injection.FakeWorkerThreadModule import io.embrace.android.embracesdk.internal.payload.Envelope @@ -21,6 +23,8 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) internal class EmbraceLoggingFeatureTest { + private val instrumentedConfig = FakeInstrumentedConfig(enabledFeatures = FakeEnabledFeatureConfig(bgActivityCapture = true)) + @Rule @JvmField val testRule: IntegrationTestRule = IntegrationTestRule { @@ -39,6 +43,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log info message sent in foreground`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { embrace.logInfo("test message") @@ -61,6 +66,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log warning message sent in background`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { embrace.logWarning("test message") flushLogs() @@ -80,6 +86,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log error message sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { embrace.logError("test message") flushLogs() @@ -99,6 +106,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log messages with different severities sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { Severity.values().forEach { severity -> val expectedMessage = "test message ${severity.name}" @@ -125,6 +133,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log messages with different severities and properties sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { Severity.values().forEach { severity -> val expectedMessage = "test message ${severity.name}" @@ -151,6 +160,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log exception message sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { embrace.logException(testException) flushLogs() @@ -175,6 +185,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log exception with different severities sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { embrace.logException(testException, Severity.INFO) flushLogs() @@ -199,6 +210,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log exception with different severities and properties sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { Severity.values().forEach { severity -> embrace.logException( @@ -232,6 +244,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log exception with different severities, properties, and custom message sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { Severity.values().forEach { severity -> val expectedMessage = "test message ${severity.name}" @@ -264,6 +277,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log custom stacktrace message sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { embrace.logCustomStacktrace(stacktrace) flushLogs() @@ -286,6 +300,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log custom stacktrace with different severities sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { Severity.values().forEach { severity -> embrace.logCustomStacktrace(stacktrace, severity) @@ -313,6 +328,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log custom stacktrace with different severities and properties sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { Severity.values().forEach { severity -> embrace.logCustomStacktrace(stacktrace, severity, customProperties) @@ -341,6 +357,7 @@ internal class EmbraceLoggingFeatureTest { @Test fun `log custom stacktrace with different severities, properties, and custom message sent`() { testRule.runTest( + instrumentedConfig = instrumentedConfig, testCaseAction = { Severity.values().forEach { severity -> val expectedMessage = "test message ${severity.name}" diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/JvmCrashFeatureTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/JvmCrashFeatureTest.kt index 8a7c369948..9c0d21806d 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/JvmCrashFeatureTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/JvmCrashFeatureTest.kt @@ -1,10 +1,13 @@ package io.embrace.android.embracesdk.testcases.features import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeProjectConfig import io.embrace.android.embracesdk.internal.EmbraceInternalApi +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.opentelemetry.embCrashId import io.embrace.android.embracesdk.internal.opentelemetry.embState -import io.embrace.android.embracesdk.internal.payload.AppFramework import io.embrace.android.embracesdk.internal.payload.ApplicationState import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.LegacyExceptionInfo @@ -59,6 +62,7 @@ internal class JvmCrashFeatureTest { @Test fun `app crash in the background generates a crash log`() { testRule.runTest( + persistedRemoteConfig = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(100f)), testCaseAction = { simulateJvmUncaughtException(testException) }, @@ -72,13 +76,15 @@ internal class JvmCrashFeatureTest { ) } - @Suppress("DEPRECATION") @Test fun `React Native crash generates an OTel Log and matches the crashId in the session`() { testRule.runTest( - setupAction = { - overriddenConfigService.appFramework = AppFramework.REACT_NATIVE - }, + instrumentedConfig = FakeInstrumentedConfig( + project = FakeProjectConfig( + appId = "abcde", + appFramework = "react_native" + ) + ), testCaseAction = { recordSession { EmbraceInternalApi.getInstance().reactNativeInternalInterface.logUnhandledJsException( diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/PruningFeatureTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/PruningFeatureTest.kt index 191648bd9e..1937446427 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/PruningFeatureTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/PruningFeatureTest.kt @@ -40,9 +40,6 @@ internal class PruningFeatureTest { @Test fun `stored payloads are pruned appropriately`() { testRule.runTest( - setupAction = { - overriddenConfigService.backgroundActivityCaptureEnabled = false - }, testCaseAction = { simulateNetworkChange(NetworkStatus.NOT_REACHABLE) repeat(STORAGE_LIMIT + OVERAGE) { k -> diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/RemoteConfigTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/RemoteConfigTest.kt new file mode 100644 index 0000000000..da0f5e808b --- /dev/null +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/RemoteConfigTest.kt @@ -0,0 +1,73 @@ +package io.embrace.android.embracesdk.testcases.features + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.embrace.android.embracesdk.assertions.returnIfConditionMet +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.testframework.IntegrationTestRule +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests how the SDK behaves when controlling by 'remote config'. This is a HTTP response from the + * Embrace server that can enable or disable individual features or the entire SDK. + */ +@RunWith(AndroidJUnit4::class) +internal class RemoteConfigTest { + + private val sdkDisabledConfig = RemoteConfig(0) + private val sdkEnabledConfig = RemoteConfig(100) + + @Rule + @JvmField + val testRule: IntegrationTestRule = IntegrationTestRule() + + @Test + fun `SDK can start`() { + testRule.runTest( + persistedRemoteConfig = sdkEnabledConfig, + preSdkStartAction = { + assertNoConfigPersisted() + assertFalse(embrace.isStarted) + }, + testCaseAction = { + assertTrue(embrace.isStarted) + }, + assertAction = { + assertConfigRequested(1) + val response = readPersistedConfigResponse() + assertEquals(100, response.cfg?.threshold) + assertEquals("server_etag_value", response.etag) + } + ) + } + + @Test + fun `SDK disabled via config cannot start`() { + testRule.runTest( + persistedRemoteConfig = sdkDisabledConfig, + serverResponseConfig = sdkEnabledConfig, + expectSdkToStart = false, + preSdkStartAction = { + assertNoConfigPersisted() + }, + testCaseAction = { + assertFalse(embrace.isStarted) + }, + assertAction = { + assertConfigRequested(1) + returnIfConditionMet( + dataProvider = ::readPersistedConfigResponse, + condition = { + val response = readPersistedConfigResponse() + response.cfg?.threshold == 100 && response.etag == "server_etag_value" + }, + desiredValueSupplier = {} + ) + } + ) + } +} diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ResurrectionFeatureTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ResurrectionFeatureTest.kt index a7a0e92ae0..d567b7911b 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ResurrectionFeatureTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ResurrectionFeatureTest.kt @@ -4,10 +4,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.assertions.getSessionId import io.embrace.android.embracesdk.fakes.FakeNativeCrashService import io.embrace.android.embracesdk.fakes.FakePayloadStorageService -import io.embrace.android.embracesdk.fakes.behavior.FakeAutoDataCaptureBehavior import io.embrace.android.embracesdk.fakes.fakeIncompleteSessionEnvelope import io.embrace.android.embracesdk.fixtures.fakeCachedSessionStoredTelemetryMetadata import io.embrace.android.embracesdk.internal.clock.nanosToMillis +import io.embrace.android.embracesdk.internal.config.remote.KillSwitchRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.opentelemetry.embCrashId import io.embrace.android.embracesdk.internal.payload.NativeCrashData import io.embrace.android.embracesdk.internal.payload.Span @@ -45,9 +46,6 @@ internal class ResurrectionFeatureTest { ) testRule.runTest( setupAction = { - overriddenConfigService.autoDataCaptureBehavior = FakeAutoDataCaptureBehavior( - v2StorageEnabled = true - ) cacheStorageService.addPayload(sessionMetadata, deadSessionEnvelope) cacheStorageServiceProvider = { cacheStorageService } val nativeCrashService = fakeNativeFeatureModule.nativeCrashService as FakeNativeCrashService @@ -84,11 +82,9 @@ internal class ResurrectionFeatureTest { @Test fun `resurrection attempt with v2 delivery layer off does not crash the SDK`() { testRule.runTest( + persistedRemoteConfig = RemoteConfig(killSwitchConfig = KillSwitchRemoteConfig(v2StoragePct = 0f)), setupAction = { useMockWebServer = false - overriddenConfigService.autoDataCaptureBehavior = FakeAutoDataCaptureBehavior( - v2StorageEnabled = false - ) }, testCaseAction = { recordSession() diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/SensitiveKeysRedactionFeatureTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/SensitiveKeysRedactionFeatureTest.kt index dcbef06937..ab8b057587 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/SensitiveKeysRedactionFeatureTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/SensitiveKeysRedactionFeatureTest.kt @@ -2,8 +2,9 @@ package io.embrace.android.embracesdk.testcases.features import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.assertions.findSpanByName +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.config.FakeRedactionConfig import io.embrace.android.embracesdk.internal.config.behavior.REDACTED_LABEL -import io.embrace.android.embracesdk.internal.config.behavior.SensitiveKeysBehaviorImpl import io.embrace.android.embracesdk.testframework.IntegrationTestRule import io.embrace.android.embracesdk.testframework.assertions.assertMatches import org.junit.Rule @@ -12,20 +13,19 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) internal class SensitiveKeysRedactionFeatureTest { + + private val instrumentedConfig = FakeInstrumentedConfig( + redaction = FakeRedactionConfig(sensitiveKeys = listOf("password")) + ) + @Rule @JvmField val testRule: IntegrationTestRule = IntegrationTestRule() - private val sensitiveKeysBehavior = SensitiveKeysBehaviorImpl( - listOf("password") - ) - @Test fun `custom span properties are redacted if they are sensitive`() { testRule.runTest( - setupAction = { - overriddenConfigService.sensitiveKeysBehavior = sensitiveKeysBehavior - }, + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { embrace.startSpan("test span")?.apply { @@ -49,9 +49,7 @@ internal class SensitiveKeysRedactionFeatureTest { @Test fun `custom span events are redacted if they are sensitive`() { testRule.runTest( - setupAction = { - overriddenConfigService.sensitiveKeysBehavior = sensitiveKeysBehavior - }, + instrumentedConfig = instrumentedConfig, testCaseAction = { recordSession { embrace.startSpan("test span")?.apply { @@ -78,4 +76,4 @@ internal class SensitiveKeysRedactionFeatureTest { } ) } -} \ No newline at end of file +} diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/SessionPropertiesTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/SessionPropertiesTest.kt index 3639692c08..ad3587b6e4 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/SessionPropertiesTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/SessionPropertiesTest.kt @@ -3,6 +3,10 @@ package io.embrace.android.embracesdk.testcases.features import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.Embrace import io.embrace.android.embracesdk.assertions.findSessionSpan +import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig +import io.embrace.android.embracesdk.fakes.createBackgroundActivityBehavior +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig import io.embrace.android.embracesdk.internal.payload.ApplicationState import io.embrace.android.embracesdk.internal.payload.Span import io.embrace.android.embracesdk.internal.spans.getSessionProperty @@ -25,6 +29,7 @@ internal class SessionPropertiesTest { @Test fun `session properties additions and removal works at all stages app state transition`() { testRule.runTest( + instrumentedConfig = FakeInstrumentedConfig(enabledFeatures = FakeEnabledFeatureConfig(bgActivityCapture = true)), setupAction = { setupPermanentProperties() }, @@ -67,7 +72,6 @@ internal class SessionPropertiesTest { fun `session properties work with background activity disabled`() { testRule.runTest( setupAction = { - overriddenConfigService.backgroundActivityCaptureEnabled = false setupPermanentProperties() }, testCaseAction = { @@ -114,7 +118,6 @@ internal class SessionPropertiesTest { fun `session properties are persisted in cached payloads when bg activities are disabled`() { testRule.runTest( setupAction = { - overriddenConfigService.backgroundActivityCaptureEnabled = false setupPermanentProperties() }, testCaseAction = { diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ThermalStateTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ThermalStateTest.kt index 0a91c43f2d..24ea35d84b 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ThermalStateTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/ThermalStateTest.kt @@ -5,7 +5,6 @@ import android.os.PowerManager import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.assertions.findSpanSnapshotOfType import io.embrace.android.embracesdk.assertions.findSpansOfType -import io.embrace.android.embracesdk.fakes.behavior.FakeAutoDataCaptureBehavior import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.clock.nanosToMillis import io.embrace.android.embracesdk.internal.spans.findAttributeValue @@ -26,16 +25,11 @@ internal class ThermalStateFeatureTest { @JvmField val testRule: IntegrationTestRule = IntegrationTestRule() - private val autoDataCaptureBehavior = FakeAutoDataCaptureBehavior(thermalStatusCaptureEnabled = true) - @Test fun `single thermal state change generates a snapshot`() { var startTimeMs = 0L testRule.runTest( - setupAction = { - overriddenConfigService.autoDataCaptureBehavior = autoDataCaptureBehavior - }, testCaseAction = { recordSession { startTimeMs = clock.now() @@ -64,9 +58,6 @@ internal class ThermalStateFeatureTest { var startTimeMs = 0L testRule.runTest( - setupAction = { - overriddenConfigService.autoDataCaptureBehavior = autoDataCaptureBehavior - }, testCaseAction = { recordSession { startTimeMs = clock.now() diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/V1DeliveryFeatureTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/V1DeliveryFeatureTest.kt index 42225d2031..90465f3265 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/V1DeliveryFeatureTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/V1DeliveryFeatureTest.kt @@ -7,7 +7,9 @@ import android.content.Context import android.os.Build import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.embrace.android.embracesdk.fakes.behavior.FakeAutoDataCaptureBehavior +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.KillSwitchRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.payload.ApplicationState import io.embrace.android.embracesdk.testframework.IntegrationTestRule import io.embrace.android.embracesdk.testframework.assertions.assertMatches @@ -30,15 +32,17 @@ internal class V1DeliveryFeatureTest { @JvmField val testRule: IntegrationTestRule = IntegrationTestRule() - private val behavior = FakeAutoDataCaptureBehavior(v2StorageEnabled = false) + private val remoteConfig = RemoteConfig( + killSwitchConfig = KillSwitchRemoteConfig(v2StoragePct = 0f), + backgroundActivityConfig = BackgroundActivityRemoteConfig(100f) + ) - @Suppress("DEPRECATION") @Test fun `v1 session delivery`() { testRule.runTest( + persistedRemoteConfig = remoteConfig, setupAction = { useMockWebServer = false - overriddenConfigService.autoDataCaptureBehavior = behavior }, testCaseAction = { recordSession { @@ -52,13 +56,12 @@ internal class V1DeliveryFeatureTest { ) } - @Suppress("DEPRECATION") @Test fun `v1 background activity delivery`() { testRule.runTest( + persistedRemoteConfig = remoteConfig, setupAction = { useMockWebServer = false - overriddenConfigService.autoDataCaptureBehavior = behavior }, testCaseAction = { embrace.setUserIdentifier("foo") @@ -74,9 +77,9 @@ internal class V1DeliveryFeatureTest { @Test fun `v1 crash delivery`() { testRule.runTest( + persistedRemoteConfig = remoteConfig, setupAction = { useMockWebServer = false - overriddenConfigService.autoDataCaptureBehavior = behavior }, testCaseAction = { recordSession { @@ -93,9 +96,9 @@ internal class V1DeliveryFeatureTest { @Test fun `v1 log delivery`() { testRule.runTest( + persistedRemoteConfig = remoteConfig, setupAction = { useMockWebServer = false - overriddenConfigService.autoDataCaptureBehavior = behavior setupFakeAeiData() }, testCaseAction = { diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/WebviewFeatureTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/WebviewFeatureTest.kt index 896696fd73..1a28dbf3d4 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/WebviewFeatureTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/WebviewFeatureTest.kt @@ -5,7 +5,6 @@ import com.squareup.moshi.Types import io.embrace.android.embracesdk.ResourceReader import io.embrace.android.embracesdk.assertions.findEventsOfType import io.embrace.android.embracesdk.assertions.findSessionSpan -import io.embrace.android.embracesdk.fakes.behavior.FakeWebViewVitalsBehavior import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.payload.WebVital import io.embrace.android.embracesdk.internal.payload.WebVitalType @@ -33,9 +32,6 @@ internal class WebviewFeatureTest { @Test fun `webview info feature`() { testRule.runTest( - setupAction = { - overriddenConfigService.webViewVitalsBehavior = FakeWebViewVitalsBehavior(50, true) - }, testCaseAction = { recordSession { embrace.trackWebViewPerformance("myWebView", expectedCompleteData) diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/BackgroundActivityDisabledTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/BackgroundActivityDisabledTest.kt index 208ed62c62..2d2e445343 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/BackgroundActivityDisabledTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/BackgroundActivityDisabledTest.kt @@ -56,9 +56,7 @@ internal class BackgroundActivityDisabledTest { overriddenClock = clock, overriddenInitModule = initModule, overriddenWorkerThreadModule = workerThreadModule, - ).apply { - overriddenConfigService.backgroundActivityCaptureEnabled = false - } + ) } @Test diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/BackgroundActivityTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/BackgroundActivityTest.kt index bcbd43259f..003f0d925f 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/BackgroundActivityTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/BackgroundActivityTest.kt @@ -5,6 +5,8 @@ import io.embrace.android.embracesdk.assertions.findSessionSpan import io.embrace.android.embracesdk.assertions.getSessionId import io.embrace.android.embracesdk.assertions.hasSpanSnapshotsOfType import io.embrace.android.embracesdk.internal.arch.schema.EmbType +import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.opentelemetry.embSessionNumber import io.embrace.android.embracesdk.internal.payload.ApplicationState import io.embrace.android.embracesdk.testframework.IntegrationTestRule @@ -29,6 +31,7 @@ internal class BackgroundActivityTest { @Test fun `bg activity messages are recorded`() { testRule.runTest( + persistedRemoteConfig = RemoteConfig(backgroundActivityConfig = BackgroundActivityRemoteConfig(100f)), testCaseAction = { recordSession() clock.tick(30000) diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/ManualSessionTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/ManualSessionTest.kt index fc7688de3f..2ab533129c 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/ManualSessionTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/ManualSessionTest.kt @@ -2,7 +2,8 @@ package io.embrace.android.embracesdk.testcases.session import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.assertions.findSessionSpan -import io.embrace.android.embracesdk.fakes.behavior.FakeSessionBehavior +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.SessionRemoteConfig import io.embrace.android.embracesdk.internal.opentelemetry.embSessionNumber import io.embrace.android.embracesdk.testframework.IntegrationTestRule import io.embrace.android.embracesdk.testframework.assertions.assertMatches @@ -47,10 +48,7 @@ internal class ManualSessionTest { @Test fun `calling endSession when session control enabled does not end sessions`() { testRule.runTest( - setupAction = { - overriddenConfigService.sessionBehavior = - FakeSessionBehavior(sessionControlEnabled = true) - }, + persistedRemoteConfig = RemoteConfig(sessionConfig = SessionRemoteConfig(isEnabled = true)), testCaseAction = { recordSession { clock.tick(10000) @@ -67,9 +65,7 @@ internal class ManualSessionTest { @Test fun `calling endSession when state session is below 5s has no effect`() { testRule.runTest( - setupAction = { - overriddenConfigService.sessionBehavior = FakeSessionBehavior(sessionControlEnabled = true) - }, + persistedRemoteConfig = RemoteConfig(sessionConfig = SessionRemoteConfig(isEnabled = true)), testCaseAction = { recordSession { clock.tick(1000) // not enough to trigger new session diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/OtelSessionGatingTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/OtelSessionGatingTest.kt index ccbf055999..759977048b 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/OtelSessionGatingTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/OtelSessionGatingTest.kt @@ -5,8 +5,6 @@ import io.embrace.android.embracesdk.assertions.findSessionSpan import io.embrace.android.embracesdk.assertions.hasEventOfType import io.embrace.android.embracesdk.assertions.hasSpanOfType import io.embrace.android.embracesdk.fakes.FakeAnrService -import io.embrace.android.embracesdk.fakes.FakeConfigService -import io.embrace.android.embracesdk.fakes.createSessionBehavior import io.embrace.android.embracesdk.fakes.fakeCompletedAnrInterval import io.embrace.android.embracesdk.fakes.fakeInProgressAnrInterval import io.embrace.android.embracesdk.internal.EmbraceInternalApi @@ -17,7 +15,6 @@ import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.SessionPayload import io.embrace.android.embracesdk.testframework.IntegrationTestRule import io.embrace.android.embracesdk.testframework.actions.EmbraceActionInterface -import io.embrace.android.embracesdk.testframework.actions.EmbraceSetupInterface import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Rule @@ -30,26 +27,14 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) internal class OtelSessionGatingTest { - private var gatingConfig = SessionRemoteConfig( - fullSessionEvents = setOf(), - sessionComponents = setOf() - ) - @Rule @JvmField - val testRule: IntegrationTestRule = IntegrationTestRule { - EmbraceSetupInterface( - overriddenConfigService = FakeConfigService( - sessionBehavior = createSessionBehavior(remoteCfg = { RemoteConfig(sessionConfig = gatingConfig) }) - ) - ) - } + val testRule: IntegrationTestRule = IntegrationTestRule() @Test fun `session sent in full without gating`() { - gatingConfig = SessionRemoteConfig() - testRule.runTest( + persistedRemoteConfig = RemoteConfig(sessionConfig = SessionRemoteConfig()), testCaseAction = { simulateSession() }, @@ -62,14 +47,16 @@ internal class OtelSessionGatingTest { @Test fun `session gated`() { - gatingConfig = SessionRemoteConfig( - sessionComponents = emptySet(), - fullSessionEvents = setOf( - "crashes", - "errors" - ) - ) testRule.runTest( + persistedRemoteConfig = RemoteConfig( + sessionConfig = SessionRemoteConfig( + sessionComponents = emptySet(), + fullSessionEvents = setOf( + "crashes", + "errors" + ) + ) + ), testCaseAction = { simulateSession() }, diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/SessionSpamTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/SessionSpamTest.kt index 80c7aa2b15..e94a3a8731 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/SessionSpamTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/SessionSpamTest.kt @@ -2,6 +2,8 @@ package io.embrace.android.embracesdk.testcases.session import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.assertions.getSessionId +import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.internal.payload.ApplicationState import io.embrace.android.embracesdk.testframework.IntegrationTestRule import org.junit.Assert.assertEquals @@ -21,6 +23,7 @@ internal class SessionSpamTest { @Test fun `session messages are recorded`() { testRule.runTest( + instrumentedConfig = FakeInstrumentedConfig(enabledFeatures = FakeEnabledFeatureConfig(bgActivityCapture = true)), testCaseAction = { repeat(SESSION_COUNT) { recordSession { diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/SessionSpanTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/SessionSpanTest.kt index 478f7642ee..785182b51d 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/SessionSpanTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/session/SessionSpanTest.kt @@ -1,6 +1,8 @@ package io.embrace.android.embracesdk.testcases.session import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.internal.payload.getSessionSpan import io.embrace.android.embracesdk.testframework.IntegrationTestRule import org.junit.Assert.assertEquals @@ -21,6 +23,7 @@ internal class SessionSpanTest { val ids = mutableListOf() testRule.runTest( + instrumentedConfig = FakeInstrumentedConfig(enabledFeatures = FakeEnabledFeatureConfig(bgActivityCapture = true)), testCaseAction = { recordSession { ids.add(embrace.currentSessionId) diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/IntegrationTestRule.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/IntegrationTestRule.kt index ae2b672b74..1640f41aab 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/IntegrationTestRule.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/IntegrationTestRule.kt @@ -1,14 +1,18 @@ package io.embrace.android.embracesdk.testframework import android.content.Context +import androidx.test.core.app.ApplicationProvider import io.embrace.android.embracesdk.EmbraceHooks import io.embrace.android.embracesdk.EmbraceImpl import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeDeliveryService -import io.embrace.android.embracesdk.fakes.behavior.FakeSdkEndpointBehavior +import io.embrace.android.embracesdk.fakes.TestPlatformSerializer +import io.embrace.android.embracesdk.fakes.config.FakeBaseUrlConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.fakes.injection.FakeCoreModule import io.embrace.android.embracesdk.fakes.injection.FakeDeliveryModule +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.injection.CoreModule import io.embrace.android.embracesdk.internal.injection.DeliveryModule import io.embrace.android.embracesdk.internal.injection.EssentialServiceModule @@ -23,6 +27,7 @@ import io.embrace.android.embracesdk.testframework.actions.EmbracePreSdkStartInt import io.embrace.android.embracesdk.testframework.actions.EmbraceSetupInterface import io.embrace.android.embracesdk.testframework.export.FilteredSpanExporter import io.embrace.android.embracesdk.testframework.server.FakeApiServer +import java.io.File import okhttp3.Protocol import okhttp3.mockwebserver.MockWebServer import org.junit.Assert.assertEquals @@ -93,6 +98,7 @@ internal class IntegrationTestRule( private lateinit var otelAssertion: EmbraceOtelExportAssertionInterface private lateinit var spanExporter: FilteredSpanExporter private lateinit var embraceImpl: EmbraceImpl + private lateinit var baseUrl: String lateinit var bootstrapper: ModuleInitBootstrapper @@ -101,8 +107,11 @@ internal class IntegrationTestRule( * assertions. This aims to enforce the better compartmentalisation & reuse of test code within * the integration test suite. */ - inline fun runTest( + fun runTest( startSdk: Boolean = true, + instrumentedConfig: FakeInstrumentedConfig = FakeInstrumentedConfig(), + persistedRemoteConfig: RemoteConfig = RemoteConfig(), + serverResponseConfig: RemoteConfig = persistedRemoteConfig, expectSdkToStart: Boolean = startSdk, setupAction: EmbraceSetupInterface.() -> Unit = {}, preSdkStartAction: EmbracePreSdkStartInterface.() -> Unit = {}, @@ -110,6 +119,26 @@ internal class IntegrationTestRule( assertAction: EmbracePayloadAssertionInterface.() -> Unit = {}, otelExportAssertion: EmbraceOtelExportAssertionInterface.() -> Unit = {}, ) { + setup = embraceSetupInterfaceSupplier() + var apiServer: FakeApiServer? = null + + if (setup.useMockWebServer) { + apiServer = FakeApiServer(serverResponseConfig) + val server: MockWebServer = MockWebServer().apply { + protocols = listOf(Protocol.HTTP_2, Protocol.HTTP_1_1) + dispatcher = apiServer + start() + } + baseUrl = server.url("api").toString() + } + + preSdkStart = EmbracePreSdkStartInterface(setup) + bootstrapper = setup.createBootstrapper(prepareConfig(instrumentedConfig)) + action = EmbraceActionInterface(setup, bootstrapper) + payloadAssertion = EmbracePayloadAssertionInterface(bootstrapper, apiServer) + spanExporter = FilteredSpanExporter() + otelAssertion = EmbraceOtelExportAssertionInterface(spanExporter) + setupAction(setup) with(setup) { embraceImpl = EmbraceImpl(bootstrapper) @@ -117,11 +146,16 @@ internal class IntegrationTestRule( preSdkStartAction(preSdkStart) embraceImpl.addSpanExporter(spanExporter) + // persist config here before the SDK starts up + persistConfig(persistedRemoteConfig) + if (startSdk) { - embraceImpl.start(overriddenCoreModule.context, appFramework) { - overriddenConfigService.apply { appFramework = it } - } - assertEquals(expectSdkToStart, bootstrapper.essentialServiceModule.processStateService.isInitialized()) + embraceImpl.start(overriddenCoreModule.context) + assertEquals( + "SDK did not start in integration test.", + expectSdkToStart, + embraceImpl.isStarted + ) } } testCaseAction(action) @@ -131,33 +165,34 @@ internal class IntegrationTestRule( } /** - * Setup the Embrace SDK so it's ready for testing. + * Writes a config response to the expected location on disk so the SDK can read it. */ - override fun before() { - setup = embraceSetupInterfaceSupplier.invoke() - var apiServer: FakeApiServer? = null + private fun persistConfig(persistedRemoteConfig: RemoteConfig) { + val ctx = ApplicationProvider.getApplicationContext() + val storageDir = File(ctx.filesDir, "embrace_remote_config").apply { + mkdirs() + } + File(storageDir, "etag").writeText("persisted_etag") + val responseFile = File(storageDir, "most_recent_response") - if (setup.useMockWebServer) { - apiServer = FakeApiServer() - val server: MockWebServer = MockWebServer().apply { - protocols = listOf(Protocol.HTTP_2, Protocol.HTTP_1_1) - dispatcher = apiServer - start() - } - val baseUrl = server.url("api").toString() + responseFile.outputStream().buffered().use { stream -> + TestPlatformSerializer().toJson(persistedRemoteConfig, RemoteConfig::class.java, stream) + } + } - setup.overriddenConfigService.sdkEndpointBehavior = FakeSdkEndpointBehavior( - baseUrl, - baseUrl + private fun prepareConfig(instrumentedConfig: FakeInstrumentedConfig) = + when { + setup.useMockWebServer -> instrumentedConfig.copy( + baseUrls = FakeBaseUrlConfig(configImpl = baseUrl, dataImpl = baseUrl) ) + else -> instrumentedConfig } - preSdkStart = EmbracePreSdkStartInterface(setup) - bootstrapper = setup.createBootstrapper() - action = EmbraceActionInterface(setup, bootstrapper) - payloadAssertion = EmbracePayloadAssertionInterface(bootstrapper, apiServer) - spanExporter = FilteredSpanExporter() - otelAssertion = EmbraceOtelExportAssertionInterface(spanExporter) + /** + * Setup the Embrace SDK so it's ready for testing. + */ + override fun before() { + } /** diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceActionInterface.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceActionInterface.kt index b5c768cdb8..58920fd1de 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceActionInterface.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceActionInterface.kt @@ -26,9 +26,6 @@ internal class EmbraceActionInterface( val clock: FakeClock get() = setup.overriddenClock - val configService: FakeConfigService - get() = setup.overriddenConfigService - /** * Starts & ends a session for the purposes of testing. An action can be supplied as a lambda * parameter: any code inside the lambda will be executed, so can be used to add breadcrumbs, diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbracePayloadAssertionInterface.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbracePayloadAssertionInterface.kt index f9be427ff3..0c546b7cc1 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbracePayloadAssertionInterface.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbracePayloadAssertionInterface.kt @@ -1,12 +1,15 @@ package io.embrace.android.embracesdk.testframework.actions +import android.content.Context +import androidx.test.core.app.ApplicationProvider import io.embrace.android.embracesdk.ResourceReader import io.embrace.android.embracesdk.assertions.findSessionSpan import io.embrace.android.embracesdk.assertions.getSessionId import io.embrace.android.embracesdk.assertions.returnIfConditionMet import io.embrace.android.embracesdk.fakes.FakeDeliveryService import io.embrace.android.embracesdk.fakes.FakeNativeCrashService -import io.embrace.android.embracesdk.fakes.FakeRequestExecutionService +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.config.source.ConfigHttpResponse import io.embrace.android.embracesdk.internal.injection.ModuleInitBootstrapper import io.embrace.android.embracesdk.internal.opentelemetry.embCleanExit import io.embrace.android.embracesdk.internal.opentelemetry.embState @@ -17,6 +20,7 @@ import io.embrace.android.embracesdk.internal.payload.SessionPayload import io.embrace.android.embracesdk.internal.spans.findAttributeValue import io.embrace.android.embracesdk.testframework.assertions.JsonComparator import io.embrace.android.embracesdk.testframework.server.FakeApiServer +import java.io.File import java.io.IOException import java.util.Locale import java.util.concurrent.TimeoutException @@ -32,8 +36,14 @@ internal class EmbracePayloadAssertionInterface( private val apiServer: FakeApiServer?, ) { + companion object { + private const val WAIT_TIME_MS = 10000 + private const val CONFIG_OUTPUT_DIR = "embrace_remote_config" + private const val CONFIG_RESPONSE_FILE = "most_recent_response" + private const val CONFIG_ETAG_FILE = "etag" + } + private val deliveryService by lazy { bootstrapper.deliveryModule.deliveryService as FakeDeliveryService } - private val requestExecutionService by lazy { bootstrapper.deliveryModule.requestExecutionService as FakeRequestExecutionService } private val serializer by lazy { bootstrapper.initModule.jsonSerializer } private val nativeCrashService by lazy { bootstrapper.nativeFeatureModule.nativeCrashService as FakeNativeCrashService @@ -55,17 +65,6 @@ internal class EmbracePayloadAssertionInterface( return getLogEnvelopes(1).single() } - /** - * Returns a list of logs that were completed by the SDK & sent to a mock web server. - */ - internal fun getLogEnvelopesFromMockServer( - expectedSize: Int, - ): List> { - return retrievePayload(expectedSize) { - checkNotNull(apiServer).getLogEnvelopes() - } - } - private fun retrieveLogEnvelopes( expectedSize: Int, ): List> { @@ -136,7 +135,7 @@ internal class EmbracePayloadAssertionInterface( internal fun getSessionEnvelopes( expectedSize: Int, state: ApplicationState = ApplicationState.FOREGROUND, - waitTimeMs: Int = 1000, + waitTimeMs: Int = WAIT_TIME_MS, ): List> { return retrieveSessionEnvelopes(expectedSize, state, waitTimeMs) } @@ -181,6 +180,63 @@ internal class EmbracePayloadAssertionInterface( return ApplicationState.valueOf(value) } + + /*** Config ***/ + + + internal fun assertConfigRequested(expectedRequests: Int) { + val supplier = { + checkNotNull(apiServer).getConfigRequests() + } + try { + retrievePayload(expectedRequests, supplier) + } catch (exc: TimeoutException) { + throw IllegalStateException( + "Expected $expectedRequests config requests, but got ${supplier().size}.", + exc + ) + } + } + + /** + * Asserts that config was persisted on disk and returns the persisted information. + */ + internal fun readPersistedConfigResponse(): ConfigHttpResponse { + val ctx = ApplicationProvider.getApplicationContext() + val storageDir = File(ctx.filesDir, "embrace_remote_config") + if (!storageDir.exists()) { + throw IllegalStateException("Config storage directory does not exist.") + } + val responseFile = File(storageDir, "most_recent_response") + if (!responseFile.exists()) { + throw IllegalStateException("Config response file does not exist.") + } + val etagFile = File(storageDir, "etag") + if (!etagFile.exists()) { + throw IllegalStateException("Config etag file does not exist.") + } + val remoteConfig = readRemoteConfigFile(responseFile) + return ConfigHttpResponse(remoteConfig, readEtagFile(etagFile)) + } + + private fun readRemoteConfigFile(file: File): RemoteConfig { + try { + return file.inputStream().buffered().use { + serializer.fromJson(it, RemoteConfig::class.java) + } + } catch (exc: Throwable) { + throw IllegalStateException("Failed to read remote config file.", exc) + } + } + + private fun readEtagFile(etagFile: File): String { + try { + return etagFile.readText() + } catch (exc: Throwable) { + throw IllegalStateException("Failed to read etag file for config.", exc) + } + } + /*** Native ***/ internal fun getSentNativeCrashes() = nativeCrashService.nativeCrashesSent.toList() @@ -247,14 +303,14 @@ internal class EmbracePayloadAssertionInterface( private inline fun retrievePayload( expectedSize: Int?, supplier: () -> List, - ): List = retrievePayload(expectedSize, 1000, supplier) + ): List = retrievePayload(expectedSize, WAIT_TIME_MS, supplier) /** * Retrieves a payload that was stored in the delivery service. */ private inline fun retrievePayload( expectedSize: Int?, - waitTimeMs: Int = 1000, + waitTimeMs: Int = WAIT_TIME_MS, supplier: () -> List, ): List { return when (expectedSize) { diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbracePreSdkStartInterface.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbracePreSdkStartInterface.kt index 2c02d206b7..a78a8f341e 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbracePreSdkStartInterface.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbracePreSdkStartInterface.kt @@ -1,7 +1,10 @@ package io.embrace.android.embracesdk.testframework.actions +import android.content.Context +import androidx.test.core.app.ApplicationProvider import io.embrace.android.embracesdk.Embrace import io.embrace.android.embracesdk.fakes.FakeClock +import java.io.File internal class EmbracePreSdkStartInterface( private val setup: EmbraceSetupInterface, @@ -10,4 +13,15 @@ internal class EmbracePreSdkStartInterface( val clock: FakeClock get() = setup.overriddenClock -} \ No newline at end of file + + /** + * Asserts that no config has been persisted on disk yet. + */ + internal fun assertNoConfigPersisted() { + val ctx = ApplicationProvider.getApplicationContext() + val storageDir = File(ctx.filesDir, "embrace_remote_config") + if (storageDir.exists()) { + throw IllegalStateException("Did not expect config storage directory to exist.") + } + } +} diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceSetupInterface.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceSetupInterface.kt index dc5b149aae..3283327aed 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceSetupInterface.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceSetupInterface.kt @@ -1,28 +1,24 @@ -@file:Suppress("DEPRECATION") - package io.embrace.android.embracesdk.testframework.actions import androidx.lifecycle.Lifecycle import androidx.lifecycle.testing.TestLifecycleOwner -import io.embrace.android.embracesdk.AppFramework import io.embrace.android.embracesdk.fakes.FakeClock -import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeDeliveryService import io.embrace.android.embracesdk.fakes.FakeEmbLogger import io.embrace.android.embracesdk.fakes.FakeNativeFeatureModule import io.embrace.android.embracesdk.fakes.FakeNetworkConnectivityService import io.embrace.android.embracesdk.fakes.FakeRequestExecutionService -import io.embrace.android.embracesdk.fakes.behavior.FakeAutoDataCaptureBehavior -import io.embrace.android.embracesdk.fakes.behavior.FakeNetworkSpanForwardingBehavior +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.fakes.injection.FakeAnrModule import io.embrace.android.embracesdk.fakes.injection.FakeCoreModule import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fakes.injection.FakeNativeCoreModule +import io.embrace.android.embracesdk.internal.comms.delivery.DeliveryService +import io.embrace.android.embracesdk.internal.delivery.execution.RequestExecutionService import io.embrace.android.embracesdk.internal.delivery.storage.PayloadStorageService import io.embrace.android.embracesdk.internal.injection.AndroidServicesModule import io.embrace.android.embracesdk.internal.injection.AnrModule import io.embrace.android.embracesdk.internal.injection.ModuleInitBootstrapper -import io.embrace.android.embracesdk.internal.injection.NativeCoreModule import io.embrace.android.embracesdk.internal.injection.OpenTelemetryModule import io.embrace.android.embracesdk.internal.injection.WorkerThreadModule import io.embrace.android.embracesdk.internal.injection.createAndroidServicesModule @@ -38,36 +34,32 @@ import io.embrace.android.embracesdk.testframework.IntegrationTestRule internal class EmbraceSetupInterface @JvmOverloads constructor( currentTimeMs: Long = IntegrationTestRule.DEFAULT_SDK_START_TIME_MS, var useMockWebServer: Boolean = true, - val appFramework: AppFramework = AppFramework.NATIVE, val overriddenClock: FakeClock = FakeClock(currentTime = currentTimeMs), val overriddenInitModule: FakeInitModule = FakeInitModule(clock = overriddenClock, logger = FakeEmbLogger()), val overriddenOpenTelemetryModule: OpenTelemetryModule = overriddenInitModule.openTelemetryModule, val overriddenCoreModule: FakeCoreModule = FakeCoreModule(), - val overriddenConfigService: FakeConfigService = FakeConfigService( - backgroundActivityCaptureEnabled = true, - networkSpanForwardingBehavior = FakeNetworkSpanForwardingBehavior(true), - autoDataCaptureBehavior = FakeAutoDataCaptureBehavior(thermalStatusCaptureEnabled = false) - ), val overriddenWorkerThreadModule: WorkerThreadModule = createWorkerThreadModule(), val overriddenAndroidServicesModule: AndroidServicesModule = createAndroidServicesModule( initModule = overriddenInitModule, - coreModule = overriddenCoreModule, - workerThreadModule = overriddenWorkerThreadModule + coreModule = overriddenCoreModule ), val fakeAnrModule: AnrModule = FakeAnrModule(), - val fakeNativeCoreModule: NativeCoreModule = FakeNativeCoreModule(), val fakeNativeFeatureModule: FakeNativeFeatureModule = FakeNativeFeatureModule(), - var cacheStorageServiceProvider: Provider = { null }, - var payloadStorageServiceProvider: Provider = { null }, + var cacheStorageServiceProvider: Provider? = null, + var payloadStorageServiceProvider: Provider? = null, val networkConnectivityService: FakeNetworkConnectivityService = FakeNetworkConnectivityService(), val lifecycleOwner: TestLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED), ) { - fun createBootstrapper(): ModuleInitBootstrapper = ModuleInitBootstrapper( - initModule = overriddenInitModule, + fun createBootstrapper( + instrumentedConfig: FakeInstrumentedConfig, + ): ModuleInitBootstrapper = ModuleInitBootstrapper( + initModule = overriddenInitModule.apply { + this.instrumentedConfig = instrumentedConfig + }, openTelemetryModule = overriddenInitModule.openTelemetryModule, - coreModuleSupplier = { _ -> overriddenCoreModule }, + coreModuleSupplier = { _, _ -> overriddenCoreModule }, workerThreadModuleSupplier = { overriddenWorkerThreadModule }, - androidServicesModuleSupplier = { _, _, _ -> overriddenAndroidServicesModule }, + androidServicesModuleSupplier = { _, _ -> overriddenAndroidServicesModule }, essentialServiceModuleSupplier = { initModule, configModule, openTelemetryModule, coreModule, workerThreadModule, systemServiceModule, androidServicesModule, storageModule, _, _ -> createEssentialServiceModule( initModule, @@ -81,7 +73,15 @@ internal class EmbraceSetupInterface @JvmOverloads constructor( { lifecycleOwner } ) { networkConnectivityService } }, - deliveryModuleSupplier = { configModule, otelModule, initModule, workerThreadModule, coreModule, storageModule, essentialServiceModule, _, _, requestExecutionServiceProvider, deliveryServiceProvider -> + deliveryModuleSupplier = { configModule, otelModule, initModule, workerThreadModule, coreModule, storageModule, essentialServiceModule, androidServicesModule, _, _, _, _ -> + val requestExecutionServiceProvider: Provider? = when { + useMockWebServer -> null + else -> ::FakeRequestExecutionService + } + val deliveryServiceProvider: Provider? = when { + useMockWebServer -> null + else -> ::FakeDeliveryService + } createDeliveryModule( configModule, otelModule, @@ -90,23 +90,15 @@ internal class EmbraceSetupInterface @JvmOverloads constructor( coreModule, storageModule, essentialServiceModule, + androidServicesModule, payloadStorageServiceProvider = payloadStorageServiceProvider, cacheStorageServiceProvider = cacheStorageServiceProvider, - requestExecutionServiceProvider = { - when { - useMockWebServer -> requestExecutionServiceProvider() - else -> FakeRequestExecutionService() - } - }, - deliveryServiceProvider = { - when { - useMockWebServer -> deliveryServiceProvider() - else -> FakeDeliveryService() - } - }) + requestExecutionServiceProvider = requestExecutionServiceProvider, + deliveryServiceProvider = deliveryServiceProvider + ) }, anrModuleSupplier = { _, _, _ -> fakeAnrModule }, - nativeCoreModuleSupplier = { fakeNativeCoreModule }, + nativeCoreModuleSupplier = { FakeNativeCoreModule() }, nativeFeatureModuleSupplier = { _, _, _, _, _, _, _, _, _ -> fakeNativeFeatureModule } ) } diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/server/FakeApiServer.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/server/FakeApiServer.kt index d4967a9cbe..dc055162d9 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/server/FakeApiServer.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/server/FakeApiServer.kt @@ -2,7 +2,7 @@ package io.embrace.android.embracesdk.testframework.server import io.embrace.android.embracesdk.fakes.TestPlatformSerializer import io.embrace.android.embracesdk.internal.TypeUtils -import io.embrace.android.embracesdk.internal.comms.api.Endpoint +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.LogPayload import io.embrace.android.embracesdk.internal.payload.SessionPayload @@ -12,17 +12,27 @@ import java.util.zip.GZIPInputStream import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest +import okio.Buffer +import okio.GzipSink +import okio.buffer import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull /** * Fake API server that is used to capture log/session requests made by the SDK in integration tests. */ -internal class FakeApiServer : Dispatcher() { +internal class FakeApiServer(private val remoteConfig: RemoteConfig) : Dispatcher() { + + private enum class Endpoint { + LOGS, + SESSIONS, + CONFIG + } private val serializer by threadLocal { TestPlatformSerializer() } private val sessionRequests = ConcurrentLinkedQueue>() private val logRequests = ConcurrentLinkedQueue>() + private val configRequests = ConcurrentLinkedQueue() /** * Returns a list of session envelopes in the order in which the server received them. @@ -34,20 +44,54 @@ internal class FakeApiServer : Dispatcher() { */ fun getLogEnvelopes(): List> = logRequests.toList() - @Suppress("UNCHECKED_CAST") + /** + * Returns a list of config requests in the order in which the server received them. + * The returned value is the query parameter. + */ + fun getConfigRequests(): List = configRequests.toList() + override fun dispatch(request: RecordedRequest): MockResponse { - val endpoint = findRequestEndpoint(request) + return when (val endpoint = request.asEndpoint()) { + Endpoint.LOGS, Endpoint.SESSIONS -> handleEnvelopeRequest(request, endpoint) + + // IMPORTANT NOTE: this response is not used until the SDK next starts! + Endpoint.CONFIG -> handleConfigRequest(request) + } + } + + @Suppress("UNCHECKED_CAST") + private fun handleEnvelopeRequest( + request: RecordedRequest, + endpoint: Endpoint, + ): MockResponse { val envelope = deserializeEnvelope(request, endpoint) validateHeaders(request.headers.toMultimap().mapValues { it.value.joinToString() }) when (endpoint) { Endpoint.SESSIONS -> sessionRequests.add(envelope as Envelope) Endpoint.LOGS -> logRequests.add(envelope as Envelope) - else -> error("Unsupported request type $endpoint") + else -> error("Unsupported endpoint $endpoint") } return MockResponse().setResponseCode(200) } + private fun handleConfigRequest(request: RecordedRequest): MockResponse { + configRequests.add(request.requestUrl?.toUrl()?.query) + + // serialize the config response + val configResponseBuffer = Buffer() + val gzipSink = GzipSink(configResponseBuffer).buffer() + serializer.toJson( + remoteConfig, + RemoteConfig::class.java, + gzipSink.outputStream() + ) + return MockResponse() + .setBody(configResponseBuffer) + .addHeader("etag", "server_etag_value") + .setResponseCode(200) + } + private fun validateHeaders(headers: Map) { with(headers) { assertEquals("application/json", get("accept")) @@ -58,11 +102,13 @@ internal class FakeApiServer : Dispatcher() { } } - private fun findRequestEndpoint(request: RecordedRequest): Endpoint { - return when (val path = request.path?.removePrefix("/api/v2/")) { - Endpoint.LOGS.path -> Endpoint.LOGS - Endpoint.SESSIONS.path -> Endpoint.SESSIONS - else -> error("Unsupported path $path") + private fun RecordedRequest.asEndpoint(): Endpoint { + val path = requestUrl?.toUrl()?.path + return when (val endpoint = path?.removePrefix("/api/v2/")) { + "logs" -> Endpoint.LOGS + "spans" -> Endpoint.SESSIONS + "config" -> Endpoint.CONFIG + else -> error("Unsupported path $endpoint") } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt index 64b1252f71..b903312570 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt @@ -118,34 +118,14 @@ internal class EmbraceImpl @JvmOverloads constructor( } @Suppress("DEPRECATION") - override fun start(context: Context) = start(context, io.embrace.android.embracesdk.AppFramework.NATIVE) { null } + override fun start(context: Context) = start(context, io.embrace.android.embracesdk.AppFramework.NATIVE) @Suppress("DEPRECATION") @Deprecated("Use {@link #start(Context)} instead.", ReplaceWith("start(context)")) - override fun start(context: Context, appFramework: io.embrace.android.embracesdk.AppFramework) = - start(context, appFramework) { null } - - /** - * Starts instrumentation of the Android application using the Embrace SDK. This should be - * called during creation of the application, as early as possible. - * - * See [Embrace Docs](https://embrace.io/docs/android/) for - * integration instructions. For compatibility with other networking SDKs such as Akamai, - * the Embrace SDK must be initialized after any other SDK. - * - * @param context an instance of context - * @param appFramework the AppFramework of the application - * @param configServiceProvider provider for the config service - */ - @Suppress("DEPRECATION") - fun start( - context: Context, - appFramework: io.embrace.android.embracesdk.AppFramework, - configServiceProvider: (framework: AppFramework) -> ConfigService? = { null }, - ) { + override fun start(context: Context, appFramework: io.embrace.android.embracesdk.AppFramework) { try { startSynchronous("sdk-start") - startImpl(context, appFramework, configServiceProvider) + startImpl(context, appFramework) endSynchronous() } catch (t: Throwable) { runCatching { @@ -157,8 +137,7 @@ internal class EmbraceImpl @JvmOverloads constructor( @Suppress("DEPRECATION", "CyclomaticComplexMethod", "ComplexMethod") private fun startImpl( context: Context, - framework: io.embrace.android.embracesdk.AppFramework, - configServiceProvider: (framework: AppFramework) -> ConfigService?, + framework: io.embrace.android.embracesdk.AppFramework ) { if (application != null) { return @@ -167,17 +146,18 @@ internal class EmbraceImpl @JvmOverloads constructor( val startTimeMs = sdkClock.now() val appFramework = fromFramework(framework) - bootstrapper.init(context, appFramework, startTimeMs, configServiceProvider) + if (!bootstrapper.init(context, appFramework, startTimeMs)) { + if (bootstrapper.configModule.configService.sdkModeBehavior.isSdkDisabled()) { + stop() + } + return + } startSynchronous("post-services-setup") val coreModule = bootstrapper.coreModule application = coreModule.application val configModule = bootstrapper.configModule - if (configModule.configService.isSdkDisabled()) { - stop() - return - } if (configModule.configService.autoDataCaptureBehavior.isComposeClickCaptureEnabled()) { registerComposeActivityListener(coreModule.application) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/injection/ModuleInitBootstrapper.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/injection/ModuleInitBootstrapper.kt index 7ec29a8f96..84ddae9a2c 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/injection/ModuleInitBootstrapper.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/injection/ModuleInitBootstrapper.kt @@ -1,14 +1,8 @@ package io.embrace.android.embracesdk.internal.injection import android.content.Context -import io.embrace.android.embracesdk.core.BuildConfig -import io.embrace.android.embracesdk.internal.EmbraceInternalApi import io.embrace.android.embracesdk.internal.Systrace import io.embrace.android.embracesdk.internal.capture.envelope.session.OtelPayloadMapperImpl -import io.embrace.android.embracesdk.internal.comms.delivery.EmbraceDeliveryService -import io.embrace.android.embracesdk.internal.config.ConfigService -import io.embrace.android.embracesdk.internal.delivery.execution.HttpUrlConnectionRequestExecutionService -import io.embrace.android.embracesdk.internal.delivery.execution.OkHttpRequestExecutionService import io.embrace.android.embracesdk.internal.logging.EmbLogger import io.embrace.android.embracesdk.internal.logging.EmbLoggerImpl import io.embrace.android.embracesdk.internal.logging.InternalErrorType @@ -117,7 +111,6 @@ internal class ModuleInitBootstrapper( context: Context, appFramework: AppFramework, sdkStartTimeMs: Long, - configServiceProvider: (framework: AppFramework) -> ConfigService? = { null }, versionChecker: VersionChecker = BuildVersionChecker, ): Boolean { try { @@ -128,9 +121,8 @@ internal class ModuleInitBootstrapper( synchronized(initialized) { val result = if (!isInitialized()) { - coreModule = init(CoreModule::class) { coreModuleSupplier(context) } + coreModule = init(CoreModule::class) { coreModuleSupplier(context, initModule) } - val serviceRegistry = coreModule.serviceRegistry workerThreadModule = init(WorkerThreadModule::class) { workerThreadModuleSupplier() } @@ -141,33 +133,45 @@ internal class ModuleInitBootstrapper( } } - systemServiceModule = init(SystemServiceModule::class) { - systemServiceModuleSupplier(coreModule, versionChecker) - } - androidServicesModule = init(AndroidServicesModule::class) { - androidServicesModuleSupplier(initModule, coreModule, workerThreadModule) + androidServicesModuleSupplier(initModule, coreModule) } configModule = init(ConfigModule::class) { configModuleSupplier( initModule, + coreModule, openTelemetryModule, workerThreadModule, androidServicesModule, - appFramework, - configServiceProvider, - ) { - if (isSdkDisabled()) { - EmbraceInternalApi.getInstance().internalInterface.stopSdk() + appFramework + ) + } + + Systrace.traceSynchronous("sdk-disable-check") { + // kick off config HTTP request first so the SDK can't get in a permanently disabled state + Systrace.traceSynchronous("load-config-response") { + configModule.combinedRemoteConfigSource?.scheduleConfigRequests() + } + + Systrace.traceSynchronous("behavior-check") { + if (configModule.configService.sdkModeBehavior.isSdkDisabled()) { + return false } } } + + val serviceRegistry = coreModule.serviceRegistry postInit(ConfigModule::class) { serviceRegistry.registerService(lazy { configModule.configService }) + serviceRegistry.registerService(lazy { configModule.remoteConfigSource }) openTelemetryModule.setupSensitiveKeysBehavior(configModule.configService.sensitiveKeysBehavior) } + systemServiceModule = init(SystemServiceModule::class) { + systemServiceModuleSupplier(coreModule, versionChecker) + } + storageModule = init(StorageModule::class) { storageModuleSupplier(initModule, coreModule, workerThreadModule) } @@ -187,10 +191,7 @@ internal class ModuleInitBootstrapper( ) } postInit(EssentialServiceModule::class) { - // Allow config service to start making HTTP requests with(essentialServiceModule) { - configModule.configService.remoteConfigSource = apiService - serviceRegistry.registerServices( lazy { essentialServiceModule.processStateService }, lazy { activityLifecycleTracker }, @@ -226,7 +227,6 @@ internal class ModuleInitBootstrapper( dataSourceModule = init(DataSourceModule::class) { dataSourceModuleSupplier( initModule, - configModule.configService, workerThreadModule ) } @@ -285,49 +285,15 @@ internal class ModuleInitBootstrapper( coreModule, storageModule, essentialServiceModule, - { null }, - { null }, - { - if (configModule.configService.isOnlyUsingOtelExporters()) { - null - } else { - val appId = checkNotNull(configModule.configService.appId) - val coreBaseUrl = configModule.configService.sdkEndpointBehavior.getData(appId) - val lazyDeviceId = lazy(androidServicesModule.preferencesService::deviceIdentifier) - if (configModule.configService.autoDataCaptureBehavior.shouldUseOkHttp()) { - OkHttpRequestExecutionService( - coreBaseUrl, - lazyDeviceId, - appId, - BuildConfig.VERSION_NAME, - logger, - ) - } else { - HttpUrlConnectionRequestExecutionService( - coreBaseUrl, - lazyDeviceId, - appId, - BuildConfig.VERSION_NAME, - logger, - ) - } - } - }, - { - val apiService = essentialServiceModule.apiService - if (configModule.configService.isOnlyUsingOtelExporters() || apiService == null) { - null - } else { - EmbraceDeliveryService( - storageModule.deliveryCacheManager, - apiService, - initModule.jsonSerializer - ) - } - } + androidServicesModule, + null, + null, + null, + null ) - }.apply { - payloadCachingService?.run { + } + postInit(DeliveryModule::class) { + deliveryModule.payloadCachingService?.run { openTelemetryModule.spanRepository.setSpanUpdateNotifier { reportBackgroundActivityStateChange() } @@ -493,7 +459,6 @@ internal class ModuleInitBootstrapper( if (isInitialized()) { coreModule.serviceRegistry.close() workerThreadModule.close() - essentialServiceModule.processStateService.close() initialized.set(false) } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeModuleInitBootstrapper.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeModuleInitBootstrapper.kt index 9df07d2ca5..c3dcc2105c 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeModuleInitBootstrapper.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeModuleInitBootstrapper.kt @@ -37,16 +37,16 @@ internal fun fakeModuleInitBootstrapper( fakeEmbLogger: FakeEmbLogger = FakeEmbLogger(), fakeInitModule: FakeInitModule = FakeInitModule(logger = fakeEmbLogger), fakeOpenTelemetryModule: FakeOpenTelemetryModule = FakeOpenTelemetryModule(), - coreModuleSupplier: CoreModuleSupplier = { _ -> FakeCoreModule() }, + coreModuleSupplier: CoreModuleSupplier = { _, _ -> FakeCoreModule() }, systemServiceModuleSupplier: SystemServiceModuleSupplier = { _, _ -> FakeSystemServiceModule() }, - androidServicesModuleSupplier: AndroidServicesModuleSupplier = { _, _, _ -> FakeAndroidServicesModule() }, + androidServicesModuleSupplier: AndroidServicesModuleSupplier = { _, _ -> FakeAndroidServicesModule() }, workerThreadModuleSupplier: WorkerThreadModuleSupplier = { FakeWorkerThreadModule() }, storageModuleSupplier: StorageModuleSupplier = { _, _, _ -> FakeStorageModule() }, essentialServiceModuleSupplier: EssentialServiceModuleSupplier = { _, _, _, _, _, _, _, _, _, _ -> FakeEssentialServiceModule() }, - configModuleSupplier: ConfigModuleSupplier = { _, _, _, _, _, _, _ -> FakeConfigModule() }, - dataSourceModuleSupplier: DataSourceModuleSupplier = { _, _, _ -> FakeDataSourceModule() }, + configModuleSupplier: ConfigModuleSupplier = { _, _, _, _, _, _ -> FakeConfigModule() }, + dataSourceModuleSupplier: DataSourceModuleSupplier = { _, _ -> FakeDataSourceModule() }, dataCaptureServiceModuleSupplier: DataCaptureServiceModuleSupplier = { _, _, _, _, _, _ -> FakeDataCaptureServiceModule() }, - deliveryModuleSupplier: DeliveryModuleSupplier = { _, _, _, _, _, _, _, _, _, _, _ -> FakeDeliveryModule() }, + deliveryModuleSupplier: DeliveryModuleSupplier = { _, _, _, _, _, _, _, _, _, _, _, _ -> FakeDeliveryModule() }, anrModuleSupplier: AnrModuleSupplier = { _, _, _ -> FakeAnrModule() }, logModuleSupplier: LogModuleSupplier = { _, _, _, _, _, _, _, _ -> FakeLogModule() }, nativeCoreModuleSupplier: NativeCoreModuleSupplier = { _ -> FakeNativeCoreModule() }, diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/EssentialServiceModuleImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/EssentialServiceModuleImplTest.kt index 54181c55e2..8563ce8ebe 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/EssentialServiceModuleImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/EssentialServiceModuleImplTest.kt @@ -40,7 +40,6 @@ internal class EssentialServiceModuleImplTest { ) assertNotNull(module.processStateService) - assertNotNull(module.urlBuilder) assertNotNull(module.apiClient) assertNotNull(module.apiService) assertNotNull(module.activityLifecycleTracker) diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/ModuleInitBootstrapperTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/ModuleInitBootstrapperTest.kt index 0523b3c308..77801b3b23 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/ModuleInitBootstrapperTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/injection/ModuleInitBootstrapperTest.kt @@ -32,8 +32,8 @@ internal class ModuleInitBootstrapperTest { logger = EmbLoggerImpl() coreModule = FakeCoreModule() moduleInitBootstrapper = ModuleInitBootstrapper( - configModuleSupplier = { _, _, _, _, _, _, _ -> FakeConfigModule(FakeConfigService()) }, - coreModuleSupplier = { _ -> coreModule }, + configModuleSupplier = { _, _, _, _, _, _ -> FakeConfigModule(FakeConfigService()) }, + coreModuleSupplier = { _, _ -> coreModule }, nativeFeatureModuleSupplier = { _, _, _, _, _, _, _, _, _ -> FakeNativeFeatureModule() }, logger = logger ) diff --git a/embrace-test-fakes/build.gradle.kts b/embrace-test-fakes/build.gradle.kts index 592b9536f3..a154a20657 100644 --- a/embrace-test-fakes/build.gradle.kts +++ b/embrace-test-fakes/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { compileOnly(libs.opentelemetry.semconv.incubating) implementation(libs.junit) + implementation(libs.okhttp) implementation(libs.robolectric) implementation(libs.lifecycle.runtime) } diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/Behavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/Behavior.kt index df6668df75..3f5eb3495b 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/Behavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/Behavior.kt @@ -17,8 +17,6 @@ import io.embrace.android.embracesdk.internal.config.behavior.NetworkBehavior import io.embrace.android.embracesdk.internal.config.behavior.NetworkBehaviorImpl import io.embrace.android.embracesdk.internal.config.behavior.NetworkSpanForwardingBehavior import io.embrace.android.embracesdk.internal.config.behavior.NetworkSpanForwardingBehaviorImpl -import io.embrace.android.embracesdk.internal.config.behavior.SdkEndpointBehavior -import io.embrace.android.embracesdk.internal.config.behavior.SdkEndpointBehaviorImpl import io.embrace.android.embracesdk.internal.config.behavior.SdkModeBehavior import io.embrace.android.embracesdk.internal.config.behavior.SdkModeBehaviorImpl import io.embrace.android.embracesdk.internal.config.behavior.SensitiveKeysBehaviorImpl @@ -26,12 +24,8 @@ import io.embrace.android.embracesdk.internal.config.behavior.SessionBehavior import io.embrace.android.embracesdk.internal.config.behavior.SessionBehaviorImpl import io.embrace.android.embracesdk.internal.config.behavior.WebViewVitalsBehavior import io.embrace.android.embracesdk.internal.config.behavior.WebViewVitalsBehaviorImpl -import io.embrace.android.embracesdk.internal.config.remote.AnrRemoteConfig -import io.embrace.android.embracesdk.internal.config.remote.BackgroundActivityRemoteConfig -import io.embrace.android.embracesdk.internal.config.remote.LogRemoteConfig -import io.embrace.android.embracesdk.internal.config.remote.NetworkSpanForwardingRemoteConfig +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig -import io.embrace.android.embracesdk.internal.utils.Provider import io.embrace.android.embracesdk.internal.utils.Uuid private val behaviorThresholdCheck = BehaviorThresholdCheck(Uuid::getEmbUuid) @@ -41,98 +35,91 @@ private val behaviorThresholdCheck = BehaviorThresholdCheck(Uuid::getEmbUuid) */ fun createAnrBehavior( thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, -): AnrBehavior = AnrBehaviorImpl(thresholdCheck, remoteCfg) + remoteCfg: RemoteConfig? = null, +): AnrBehavior = AnrBehaviorImpl(thresholdCheck, InstrumentedConfigImpl, remoteCfg) /** * A [SessionBehaviorImpl] that returns default values. */ fun createSessionBehavior( - thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, -): SessionBehavior = SessionBehaviorImpl(thresholdCheck, remoteCfg) + remoteCfg: RemoteConfig? = null, +): SessionBehavior = SessionBehaviorImpl(InstrumentedConfigImpl, remoteCfg) /** * A [NetworkBehaviorImpl] that returns default values. */ fun createNetworkBehavior( - thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, + remoteCfg: RemoteConfig? = null, disabledUrlPatterns: List? = null, -): NetworkBehavior = NetworkBehaviorImpl(thresholdCheck, remoteCfg, disabledUrlPatterns) +): NetworkBehavior = NetworkBehaviorImpl(InstrumentedConfigImpl, remoteCfg, disabledUrlPatterns) /** * A [BackgroundActivityBehaviorImpl] that returns default values. */ fun createBackgroundActivityBehavior( thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, -): BackgroundActivityBehavior = BackgroundActivityBehaviorImpl(thresholdCheck, remoteCfg) + remoteCfg: RemoteConfig? = null, +): BackgroundActivityBehavior = BackgroundActivityBehaviorImpl(thresholdCheck, InstrumentedConfigImpl, remoteCfg) /** * A [AutoDataCaptureBehaviorImpl] that returns default values. */ fun createAutoDataCaptureBehavior( thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, -): AutoDataCaptureBehavior = AutoDataCaptureBehaviorImpl(thresholdCheck, remoteCfg) + remoteCfg: RemoteConfig? = null, +): AutoDataCaptureBehavior = AutoDataCaptureBehaviorImpl(thresholdCheck, InstrumentedConfigImpl, remoteCfg) /** * A [LogMessageBehaviorImpl] that returns default values. */ fun createLogMessageBehavior( - thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, -): LogMessageBehavior = LogMessageBehaviorImpl(thresholdCheck, remoteCfg) + remoteCfg: RemoteConfig? = null, +): LogMessageBehavior = LogMessageBehaviorImpl(remoteCfg) /** * A [DataCaptureEventBehaviorImpl] that returns default values. */ fun createDataCaptureEventBehavior( - thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, -): DataCaptureEventBehavior = DataCaptureEventBehaviorImpl(thresholdCheck, remoteCfg) + remoteCfg: RemoteConfig? = null, +): DataCaptureEventBehavior = DataCaptureEventBehaviorImpl(remoteCfg) /** * A [SdkModeBehaviorImpl] that returns default values. */ fun createSdkModeBehavior( thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, + remoteCfg: RemoteConfig? = null, ): SdkModeBehavior = SdkModeBehaviorImpl(thresholdCheck, remoteCfg) -/** - * A [SdkModeBehaviorImpl] that returns default values. - */ -fun createSdkEndpointBehavior( - thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, -): SdkEndpointBehavior = SdkEndpointBehaviorImpl(thresholdCheck) - /** * A [AppExitInfoBehavior] that returns default values. */ fun createAppExitInfoBehavior( thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, -): AppExitInfoBehavior = AppExitInfoBehaviorImpl(thresholdCheck, remoteCfg) + remoteCfg: RemoteConfig? = null, +): AppExitInfoBehavior = AppExitInfoBehaviorImpl(thresholdCheck, InstrumentedConfigImpl, remoteCfg) /** * A [NetworkSpanForwardingBehaviorImpl] that returns default values. */ fun createNetworkSpanForwardingBehavior( thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteConfig: Provider = { null }, -): NetworkSpanForwardingBehavior = NetworkSpanForwardingBehaviorImpl(thresholdCheck, remoteConfig) + remoteConfig: RemoteConfig? = null, +): NetworkSpanForwardingBehavior = NetworkSpanForwardingBehaviorImpl( + thresholdCheck, + InstrumentedConfigImpl, + remoteConfig +) /** * A [WebViewVitalsBehaviorImpl] that returns default values. */ fun createWebViewVitalsBehavior( thresholdCheck: BehaviorThresholdCheck = behaviorThresholdCheck, - remoteCfg: Provider = { null }, + remoteCfg: RemoteConfig? = null, ): WebViewVitalsBehavior = WebViewVitalsBehaviorImpl(thresholdCheck, remoteCfg) /** * A [SensitiveKeysBehaviorImpl] that returns default values. */ -internal fun createSensitiveKeysBehavior() = SensitiveKeysBehaviorImpl() +internal fun createSensitiveKeysBehavior() = SensitiveKeysBehaviorImpl(InstrumentedConfigImpl) diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeApiService.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeApiService.kt index f6b8f60bc7..586eb34134 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeApiService.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeApiService.kt @@ -2,9 +2,7 @@ package io.embrace.android.embracesdk.fakes import io.embrace.android.embracesdk.internal.comms.api.ApiResponse import io.embrace.android.embracesdk.internal.comms.api.ApiService -import io.embrace.android.embracesdk.internal.comms.api.CachedConfig import io.embrace.android.embracesdk.internal.comms.delivery.NetworkStatus -import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.injection.SerializationAction import io.embrace.android.embracesdk.internal.payload.Envelope import io.embrace.android.embracesdk.internal.payload.LogPayload @@ -27,14 +25,6 @@ class FakeApiService : ApiService { val sessionRequests: MutableList> = mutableListOf() var futureGetCount: Int = 0 - override fun getConfig(): RemoteConfig? { - TODO("Not yet implemented") - } - - override fun getCachedConfig(): CachedConfig { - TODO("Not yet implemented") - } - override fun sendLogEnvelope(logEnvelope: Envelope) { sentLogPayloads.add(logEnvelope.data) } diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeApiUrlBuilder.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeApiUrlBuilder.kt index 9de47a61ce..75e2607e7e 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeApiUrlBuilder.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeApiUrlBuilder.kt @@ -1,13 +1,12 @@ package io.embrace.android.embracesdk.fakes import io.embrace.android.embracesdk.internal.comms.api.ApiUrlBuilder +import io.embrace.android.embracesdk.internal.comms.api.Endpoint -class FakeApiUrlBuilder : ApiUrlBuilder { - override fun getConfigUrl(): String { - return "" - } - - override fun getEmbraceUrlWithSuffix(apiVersion: String, suffix: String): String { - return "" - } +class FakeApiUrlBuilder( + override val appId: String = "", + override val deviceId: String = "", + override val baseDataUrl: String = "", +) : ApiUrlBuilder { + override fun resolveUrl(endpoint: Endpoint): String = "" } diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeConfigModule.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeConfigModule.kt index 39bc131064..6b5a0795b3 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeConfigModule.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeConfigModule.kt @@ -1,8 +1,12 @@ package io.embrace.android.embracesdk.fakes import io.embrace.android.embracesdk.fakes.behavior.FakeNetworkBehavior +import io.embrace.android.embracesdk.internal.comms.api.ApiUrlBuilder import io.embrace.android.embracesdk.internal.config.ConfigService +import io.embrace.android.embracesdk.internal.config.source.CombinedRemoteConfigSource +import io.embrace.android.embracesdk.internal.config.source.RemoteConfigSource import io.embrace.android.embracesdk.internal.injection.ConfigModule +import okhttp3.OkHttpClient class FakeConfigModule( override val configService: ConfigService = FakeConfigService( @@ -10,4 +14,10 @@ class FakeConfigModule( captureHttpUrlConnectionRequests = false ) ), + override val combinedRemoteConfigSource: CombinedRemoteConfigSource? = null, + + override val remoteConfigSource: RemoteConfigSource = FakeRemoteConfigSource(), + override val remoteConfigStore: FakeRemoteConfigStore = FakeRemoteConfigStore(), + override val urlBuilder: ApiUrlBuilder = FakeApiUrlBuilder(), + override val okHttpClient: OkHttpClient = OkHttpClient(), ) : ConfigModule diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeConfigService.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeConfigService.kt index 022dc87320..e39d48a8b9 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeConfigService.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeConfigService.kt @@ -1,7 +1,7 @@ package io.embrace.android.embracesdk.fakes +import io.embrace.android.embracesdk.fakes.behavior.FakeBreadcrumbBehavior import io.embrace.android.embracesdk.internal.config.ConfigService -import io.embrace.android.embracesdk.internal.config.RemoteConfigSource import io.embrace.android.embracesdk.internal.config.behavior.AnrBehavior import io.embrace.android.embracesdk.internal.config.behavior.AppExitInfoBehavior import io.embrace.android.embracesdk.internal.config.behavior.AutoDataCaptureBehavior @@ -11,7 +11,6 @@ import io.embrace.android.embracesdk.internal.config.behavior.DataCaptureEventBe import io.embrace.android.embracesdk.internal.config.behavior.LogMessageBehavior import io.embrace.android.embracesdk.internal.config.behavior.NetworkBehavior import io.embrace.android.embracesdk.internal.config.behavior.NetworkSpanForwardingBehavior -import io.embrace.android.embracesdk.internal.config.behavior.SdkEndpointBehavior import io.embrace.android.embracesdk.internal.config.behavior.SdkModeBehavior import io.embrace.android.embracesdk.internal.config.behavior.SensitiveKeysBehavior import io.embrace.android.embracesdk.internal.config.behavior.SessionBehavior @@ -19,17 +18,14 @@ import io.embrace.android.embracesdk.internal.config.behavior.WebViewVitalsBehav import io.embrace.android.embracesdk.internal.payload.AppFramework /** - * Fake [ConfigService] used for testing. Updates to registered listeners can be triggered by calling [updateListeners]. Note that the + * Fake [ConfigService] used for testing. Note that the * current config values of this object will be propagated, and you can trigger this fake update even if you have not changed the underlying * data. Beware of this difference in implementation compared to the real EmbraceConfigService */ class FakeConfigService( override var appFramework: AppFramework = AppFramework.NATIVE, override var appId: String = "abcde", - var sdkDisabled: Boolean = false, - var backgroundActivityCaptureEnabled: Boolean = false, var onlyUsingOtelExporters: Boolean = false, - private var hasValidRemoteConfig: Boolean = false, override var backgroundActivityBehavior: BackgroundActivityBehavior = createBackgroundActivityBehavior(), override var autoDataCaptureBehavior: AutoDataCaptureBehavior = createAutoDataCaptureBehavior(), override var breadcrumbBehavior: BreadcrumbBehavior = FakeBreadcrumbBehavior(), @@ -39,31 +35,10 @@ class FakeConfigService( override var networkBehavior: NetworkBehavior = createNetworkBehavior(), override var dataCaptureEventBehavior: DataCaptureEventBehavior = createDataCaptureEventBehavior(), override var sdkModeBehavior: SdkModeBehavior = createSdkModeBehavior(), - override var sdkEndpointBehavior: SdkEndpointBehavior = createSdkEndpointBehavior(), override var webViewVitalsBehavior: WebViewVitalsBehavior = createWebViewVitalsBehavior(), override var appExitInfoBehavior: AppExitInfoBehavior = createAppExitInfoBehavior(), override var networkSpanForwardingBehavior: NetworkSpanForwardingBehavior = createNetworkSpanForwardingBehavior(), override var sensitiveKeysBehavior: SensitiveKeysBehavior = createSensitiveKeysBehavior(), ) : ConfigService { - - override var remoteConfigSource: RemoteConfigSource? = null - - val listeners: MutableSet<() -> Unit> = mutableSetOf() - override fun addListener(configListener: () -> Unit) { - listeners.add(configListener) - } - - override fun isSdkDisabled(): Boolean = sdkDisabled || sdkModeBehavior.isSdkDisabled() - - override fun isBackgroundActivityCaptureEnabled(): Boolean = backgroundActivityCaptureEnabled - - override fun hasValidRemoteConfig(): Boolean = hasValidRemoteConfig - override fun isAppExitInfoCaptureEnabled(): Boolean = appExitInfoBehavior.isAeiCaptureEnabled() override fun isOnlyUsingOtelExporters(): Boolean = onlyUsingOtelExporters - - fun updateListeners() { - listeners.forEach { - it() - } - } } diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeDataSourceModule.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeDataSourceModule.kt index 140c571db8..4984a8b3bf 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeDataSourceModule.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeDataSourceModule.kt @@ -7,7 +7,6 @@ import io.embrace.android.embracesdk.internal.injection.DataSourceModule class FakeDataSourceModule : DataSourceModule { override val dataCaptureOrchestrator: DataCaptureOrchestrator = DataCaptureOrchestrator( - FakeConfigService(), fakeBackgroundWorker(), FakeEmbLogger() ) diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakePreferenceService.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakePreferenceService.kt index 4144d17a2c..27460d72c0 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakePreferenceService.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakePreferenceService.kt @@ -8,7 +8,6 @@ class FakePreferenceService( override var osVersion: String? = null, override var installDate: Long? = 0, override var deviceIdentifier: String = "", - override var sdkDisabled: Boolean = false, override var userPayer: Boolean = false, override var userIdentifier: String? = null, override var userEmailAddress: String? = null, @@ -16,13 +15,11 @@ class FakePreferenceService( override var username: String? = null, override var permanentSessionProperties: Map? = null, override var lastConfigFetchDate: Long? = null, - override var userMessageNeedsRetry: Boolean = false, override var reactNativeVersionNumber: String? = null, override var unityVersionNumber: String? = null, override var unityBuildIdNumber: String? = null, override var unitySdkVersionNumber: String? = null, override var screenResolution: String? = null, - override var backgroundActivityEnabled: Boolean = false, override var dartSdkVersion: String? = null, override var javaScriptBundleURL: String? = null, override var javaScriptBundleId: String? = null, diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeRemoteConfigSource.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeRemoteConfigSource.kt new file mode 100644 index 0000000000..ce31a67b61 --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeRemoteConfigSource.kt @@ -0,0 +1,21 @@ +package io.embrace.android.embracesdk.fakes + +import io.embrace.android.embracesdk.internal.config.source.ConfigHttpResponse +import io.embrace.android.embracesdk.internal.config.source.RemoteConfigSource + +class FakeRemoteConfigSource( + var cfg: ConfigHttpResponse? = null +) : RemoteConfigSource { + + var callCount: Int = 0 + var etag: String? = null + + override fun getConfig(): ConfigHttpResponse? { + callCount++ + return cfg + } + + override fun setInitialEtag(etag: String) { + this.etag = etag + } +} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeRemoteConfigStore.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeRemoteConfigStore.kt new file mode 100644 index 0000000000..9974210a4b --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeRemoteConfigStore.kt @@ -0,0 +1,18 @@ +package io.embrace.android.embracesdk.fakes + +import io.embrace.android.embracesdk.internal.config.source.ConfigHttpResponse +import io.embrace.android.embracesdk.internal.config.store.RemoteConfigStore + +class FakeRemoteConfigStore( + var impl: ConfigHttpResponse? = null, +) : RemoteConfigStore { + + var saveCount: Int = 0 + + override fun loadResponse(): ConfigHttpResponse? = impl + + override fun saveResponse(response: ConfigHttpResponse) { + saveCount++ + impl = response + } +} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAnrBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAnrBehavior.kt index fbe4c24c65..7d8ba4bcc4 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAnrBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAnrBehavior.kt @@ -1,7 +1,9 @@ package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.AnrBehavior +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig import io.embrace.android.embracesdk.internal.config.remote.AllowedNdkSampleMethod +import io.embrace.android.embracesdk.internal.config.remote.AnrRemoteConfig import io.embrace.android.embracesdk.internal.config.remote.Unwinder class FakeAnrBehavior( @@ -15,6 +17,11 @@ class FakeAnrBehavior( var nativeThreadAnrSamplingAllowlistImpl: List = emptyList(), ) : AnrBehavior { + override val local: EnabledFeatureConfig + get() = throw UnsupportedOperationException() + override val remote: AnrRemoteConfig + get() = throw UnsupportedOperationException() + override fun isAnrCaptureEnabled(): Boolean = anrCaptureEnabled override fun getSamplingIntervalMs(): Long = sampleIntervalMsImpl override fun getMaxStacktracesPerInterval(): Int = 80 diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAppExitInfoBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAppExitInfoBehavior.kt index 596804196a..bddcb7349f 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAppExitInfoBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAppExitInfoBehavior.kt @@ -1,6 +1,8 @@ package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.AppExitInfoBehavior +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.remote.AppExitInfoConfig class FakeAppExitInfoBehavior( private val traceMaxLimit: Int = 20000000, @@ -8,6 +10,11 @@ class FakeAppExitInfoBehavior( private val appExitInfoMaxNum: Int = 0, ) : AppExitInfoBehavior { + override val local: EnabledFeatureConfig + get() = throw UnsupportedOperationException() + override val remote: AppExitInfoConfig + get() = throw UnsupportedOperationException() + override fun getTraceMaxLimit(): Int = traceMaxLimit override fun isAeiCaptureEnabled(): Boolean = enabled override fun appExitInfoMaxNum(): Int = appExitInfoMaxNum diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAutoDataCaptureBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAutoDataCaptureBehavior.kt index be364a32f9..dce1e1830c 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAutoDataCaptureBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeAutoDataCaptureBehavior.kt @@ -1,6 +1,8 @@ package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.AutoDataCaptureBehavior +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig class FakeAutoDataCaptureBehavior( private val memoryServiceEnabled: Boolean = true, @@ -17,6 +19,11 @@ class FakeAutoDataCaptureBehavior( private val useOkhttp: Boolean = true, ) : AutoDataCaptureBehavior { + override val local: EnabledFeatureConfig + get() = throw UnsupportedOperationException() + override val remote: RemoteConfig + get() = throw UnsupportedOperationException() + override fun isMemoryWarningCaptureEnabled(): Boolean = memoryServiceEnabled override fun isThermalStatusCaptureEnabled(): Boolean = thermalStatusCaptureEnabled override fun isPowerSaveModeCaptureEnabled(): Boolean = powerSaveModeServiceEnabled diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeBreadcrumbBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeBreadcrumbBehavior.kt similarity index 76% rename from embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeBreadcrumbBehavior.kt rename to embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeBreadcrumbBehavior.kt index fac0ab8466..482c29f541 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeBreadcrumbBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeBreadcrumbBehavior.kt @@ -1,6 +1,8 @@ -package io.embrace.android.embracesdk.fakes +package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.BreadcrumbBehavior +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig class FakeBreadcrumbBehavior( var customBreadcrumbLimitImpl: Int = 100, @@ -13,6 +15,12 @@ class FakeBreadcrumbBehavior( var queryParamCaptureEnabled: Boolean = true, var captureFcmPiiDataEnabled: Boolean = false, ) : BreadcrumbBehavior { + + override val local: EnabledFeatureConfig + get() = throw UnsupportedOperationException() + override val remote: RemoteConfig + get() = throw UnsupportedOperationException() + override fun getCustomBreadcrumbLimit(): Int = customBreadcrumbLimitImpl override fun getFragmentBreadcrumbLimit(): Int = fragmentBreadcrumbLimitImpl override fun getTapBreadcrumbLimit(): Int = tapBreadcrumbLimitImpl diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeLogMessageBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeLogMessageBehavior.kt index 9cfb2abb8c..dbd494a802 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeLogMessageBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeLogMessageBehavior.kt @@ -1,6 +1,7 @@ package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.LogMessageBehavior +import io.embrace.android.embracesdk.internal.config.remote.LogRemoteConfig class FakeLogMessageBehavior( private val logMessageMaximumAllowedLength: Int = 128, @@ -9,6 +10,11 @@ class FakeLogMessageBehavior( private val errorLogLimit: Int = 100, ) : LogMessageBehavior { + override val local: Unit + get() = throw UnsupportedOperationException() + override val remote: LogRemoteConfig + get() = throw UnsupportedOperationException() + override fun getLogMessageMaximumAllowedLength(): Int = logMessageMaximumAllowedLength override fun getInfoLogLimit(): Int = infoLogLimit override fun getWarnLogLimit(): Int = warnLogLimit diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeNetworkBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeNetworkBehavior.kt index 72efa1a25a..c49fb014a1 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeNetworkBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeNetworkBehavior.kt @@ -1,13 +1,21 @@ package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.NetworkBehavior +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.config.remote.NetworkCaptureRuleRemoteConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig class FakeNetworkBehavior( private val captureLimit: Int = 1000, private val domains: Map = emptyMap(), private val captureHttpUrlConnectionRequests: Boolean = true, ) : NetworkBehavior { + + override val local: InstrumentedConfig + get() = throw UnsupportedOperationException() + override val remote: RemoteConfig + get() = throw UnsupportedOperationException() + override fun isRequestContentLengthCaptureEnabled(): Boolean = false override fun isHttpUrlConnectionCaptureEnabled(): Boolean = captureHttpUrlConnectionRequests override fun getLimitsByDomain(): Map = domains diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeNetworkSpanForwardingBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeNetworkSpanForwardingBehavior.kt index d9db1aec12..6648621f62 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeNetworkSpanForwardingBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeNetworkSpanForwardingBehavior.kt @@ -1,10 +1,17 @@ package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.NetworkSpanForwardingBehavior +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig +import io.embrace.android.embracesdk.internal.config.remote.NetworkSpanForwardingRemoteConfig class FakeNetworkSpanForwardingBehavior( private val networkSpanForwardingEnabled: Boolean = false, ) : NetworkSpanForwardingBehavior { + override val local: EnabledFeatureConfig + get() = throw UnsupportedOperationException() + override val remote: NetworkSpanForwardingRemoteConfig + get() = throw UnsupportedOperationException() + override fun isNetworkSpanForwardingEnabled(): Boolean = networkSpanForwardingEnabled } diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSdkEndpointBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSdkEndpointBehavior.kt deleted file mode 100644 index 5752a15f88..0000000000 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSdkEndpointBehavior.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.embrace.android.embracesdk.fakes.behavior - -import io.embrace.android.embracesdk.internal.config.behavior.SdkEndpointBehavior - -class FakeSdkEndpointBehavior( - private val dataEndpoint: String, - private val configEndpoint: String, -) : SdkEndpointBehavior { - override fun getData(appId: String?): String = dataEndpoint - override fun getConfig(appId: String?): String = configEndpoint -} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSdkModeBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSdkModeBehavior.kt index c0a281d6e3..6e202fb949 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSdkModeBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSdkModeBehavior.kt @@ -1,9 +1,16 @@ package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.SdkModeBehavior +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig class FakeSdkModeBehavior( var sdkDisabled: Boolean, ) : SdkModeBehavior { + + override val local: Unit + get() = throw UnsupportedOperationException() + override val remote: RemoteConfig + get() = throw UnsupportedOperationException() + override fun isSdkDisabled() = sdkDisabled } diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSessionBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSessionBehavior.kt index 4978a76cd1..bca5ca8d61 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSessionBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeSessionBehavior.kt @@ -1,12 +1,19 @@ package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.SessionBehavior +import io.embrace.android.embracesdk.internal.config.instrumented.schema.SessionConfig +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig class FakeSessionBehavior( private val maxSessionProperties: Int = 100, private val sessionControlEnabled: Boolean = false, ) : SessionBehavior { + override val local: SessionConfig + get() = throw UnsupportedOperationException() + override val remote: RemoteConfig + get() = throw UnsupportedOperationException() + override fun getFullSessionEvents(): Set = emptySet() override fun getSessionComponents(): Set? = null override fun isGatingFeatureEnabled(): Boolean = false diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeWebViewVitalsBehavior.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeWebViewVitalsBehavior.kt index f51fc6e36b..bec80ba97d 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeWebViewVitalsBehavior.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/behavior/FakeWebViewVitalsBehavior.kt @@ -1,12 +1,18 @@ package io.embrace.android.embracesdk.fakes.behavior import io.embrace.android.embracesdk.internal.config.behavior.WebViewVitalsBehavior +import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig class FakeWebViewVitalsBehavior( private val maxWebViewVitals: Int = 100, private val webViewVitalsEnabled: Boolean = true, ) : WebViewVitalsBehavior { + override val local: Unit + get() = throw UnsupportedOperationException() + override val remote: RemoteConfig + get() = throw UnsupportedOperationException() + override fun getMaxWebViewVitals(): Int = maxWebViewVitals override fun isWebViewVitalsEnabled(): Boolean = webViewVitalsEnabled } diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeBaseUrlConfig.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeBaseUrlConfig.kt new file mode 100644 index 0000000000..e7eb30647d --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeBaseUrlConfig.kt @@ -0,0 +1,13 @@ +package io.embrace.android.embracesdk.fakes.config + +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl +import io.embrace.android.embracesdk.internal.config.instrumented.schema.BaseUrlConfig + +class FakeBaseUrlConfig( + base: BaseUrlConfig = InstrumentedConfigImpl.baseUrls, + private val configImpl: String? = base.getConfig(), + private val dataImpl: String? = base.getData(), +) : BaseUrlConfig { + override fun getConfig(): String? = configImpl + override fun getData(): String? = dataImpl +} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeEnabledFeatureConfig.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeEnabledFeatureConfig.kt new file mode 100644 index 0000000000..e6e93d59c8 --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeEnabledFeatureConfig.kt @@ -0,0 +1,53 @@ +package io.embrace.android.embracesdk.fakes.config + +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl +import io.embrace.android.embracesdk.internal.config.instrumented.schema.EnabledFeatureConfig + +@Suppress("DEPRECATION") +class FakeEnabledFeatureConfig( + base: EnabledFeatureConfig = InstrumentedConfigImpl.enabledFeatures, + private val unityAnrCapture: Boolean = base.isUnityAnrCaptureEnabled(), + private val activityBreadcrumbCapture: Boolean = base.isActivityBreadcrumbCaptureEnabled(), + private val composeClickCapture: Boolean = base.isComposeClickCaptureEnabled(), + private val viewClickCoordCapture: Boolean = base.isViewClickCoordinateCaptureEnabled(), + private val memoryWarningCapture: Boolean = base.isMemoryWarningCaptureEnabled(), + private val powerSaveCapture: Boolean = base.isPowerSaveModeCaptureEnabled(), + private val networkConnectivityCapture: Boolean = base.isNetworkConnectivityCaptureEnabled(), + private val anrCapture: Boolean = base.isAnrCaptureEnabled(), + private val diskUsageCapture: Boolean = base.isDiskUsageCaptureEnabled(), + private val jvmCrashCapture: Boolean = base.isJvmCrashCaptureEnabled(), + private val nativeCrashCapture: Boolean = base.isNativeCrashCaptureEnabled(), + private val aeiCapture: Boolean = base.isAeiCaptureEnabled(), + private val sigHandlerDetection: Boolean = base.is3rdPartySigHandlerDetectionEnabled(), + private val bgActivityCapture: Boolean = base.isBackgroundActivityCaptureEnabled(), + private val webviewBreadcrumbCapture: Boolean = base.isWebViewBreadcrumbCaptureEnabled(), + private val webviewQueryCapture: Boolean = base.isWebViewBreadcrumbQueryParamCaptureEnabled(), + private val fcmPiiCapture: Boolean = base.isFcmPiiDataCaptureEnabled(), + private val requestContentLengthCapture: Boolean = base.isRequestContentLengthCaptureEnabled(), + private val httpUrlConnectionCapture: Boolean = base.isHttpUrlConnectionCaptureEnabled(), + private val networkSpanForwarding: Boolean = base.isNetworkSpanForwardingEnabled(), +) : EnabledFeatureConfig { + + override fun isUnityAnrCaptureEnabled(): Boolean = unityAnrCapture + override fun isActivityBreadcrumbCaptureEnabled(): Boolean = activityBreadcrumbCapture + override fun isComposeClickCaptureEnabled(): Boolean = composeClickCapture + override fun isViewClickCoordinateCaptureEnabled(): Boolean = viewClickCoordCapture + + @Deprecated("Will be removed in a future release.") + override fun isMemoryWarningCaptureEnabled(): Boolean = memoryWarningCapture + override fun isPowerSaveModeCaptureEnabled(): Boolean = powerSaveCapture + override fun isNetworkConnectivityCaptureEnabled(): Boolean = networkConnectivityCapture + override fun isAnrCaptureEnabled(): Boolean = anrCapture + override fun isDiskUsageCaptureEnabled(): Boolean = diskUsageCapture + override fun isJvmCrashCaptureEnabled(): Boolean = jvmCrashCapture + override fun isNativeCrashCaptureEnabled(): Boolean = nativeCrashCapture + override fun isAeiCaptureEnabled(): Boolean = aeiCapture + override fun is3rdPartySigHandlerDetectionEnabled(): Boolean = sigHandlerDetection + override fun isBackgroundActivityCaptureEnabled(): Boolean = bgActivityCapture + override fun isWebViewBreadcrumbCaptureEnabled(): Boolean = webviewBreadcrumbCapture + override fun isWebViewBreadcrumbQueryParamCaptureEnabled(): Boolean = webviewQueryCapture + override fun isFcmPiiDataCaptureEnabled(): Boolean = fcmPiiCapture + override fun isRequestContentLengthCaptureEnabled(): Boolean = requestContentLengthCapture + override fun isHttpUrlConnectionCaptureEnabled(): Boolean = httpUrlConnectionCapture + override fun isNetworkSpanForwardingEnabled(): Boolean = networkSpanForwarding +} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeInstrumentedConfig.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeInstrumentedConfig.kt new file mode 100644 index 0000000000..d2b39df33a --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeInstrumentedConfig.kt @@ -0,0 +1,21 @@ +package io.embrace.android.embracesdk.fakes.config + +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.NetworkCaptureConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.ProjectConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.RedactionConfig +import io.embrace.android.embracesdk.internal.config.instrumented.schema.SessionConfig + +/** + * A fake [InstrumentedConfig] implementation that defaults to the real implementation unless an override is supplied. + */ +data class FakeInstrumentedConfig( + private val base: InstrumentedConfig = InstrumentedConfigImpl, + override val baseUrls: FakeBaseUrlConfig = FakeBaseUrlConfig(base.baseUrls), + override val enabledFeatures: FakeEnabledFeatureConfig = FakeEnabledFeatureConfig(base.enabledFeatures), + override val networkCapture: NetworkCaptureConfig = FakeNetworkCaptureConfig(base.networkCapture), + override val project: ProjectConfig = FakeProjectConfig(base.project, appId = "abcde"), + override val redaction: RedactionConfig = FakeRedactionConfig(base.redaction), + override val session: SessionConfig = FakeSessionConfig(base.session), +) : InstrumentedConfig diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeNetworkCaptureConfig.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeNetworkCaptureConfig.kt new file mode 100644 index 0000000000..10564764fb --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeNetworkCaptureConfig.kt @@ -0,0 +1,17 @@ +package io.embrace.android.embracesdk.fakes.config + +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl +import io.embrace.android.embracesdk.internal.config.instrumented.schema.NetworkCaptureConfig + +class FakeNetworkCaptureConfig( + base: NetworkCaptureConfig = InstrumentedConfigImpl.networkCapture, + private val requestLimit: Int = base.getRequestLimitPerDomain(), + private val limits: Map = base.getLimitsByDomain(), + private val ignoredRequestPatterns: List = base.getIgnoredRequestPatternList(), + private val publicKey: String? = base.getNetworkBodyCapturePublicKey(), +) : NetworkCaptureConfig { + override fun getRequestLimitPerDomain(): Int = requestLimit + override fun getLimitsByDomain(): Map = limits + override fun getIgnoredRequestPatternList(): List = ignoredRequestPatterns + override fun getNetworkBodyCapturePublicKey(): String? = publicKey +} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeProjectConfig.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeProjectConfig.kt new file mode 100644 index 0000000000..8bf65d93fd --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeProjectConfig.kt @@ -0,0 +1,19 @@ +package io.embrace.android.embracesdk.fakes.config + +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl +import io.embrace.android.embracesdk.internal.config.instrumented.schema.ProjectConfig + +class FakeProjectConfig( + base: ProjectConfig = InstrumentedConfigImpl.project, + private val appId: String? = base.getAppId(), + private val appFramework: String? = base.getAppFramework(), + private val buildId: String? = base.getBuildId(), + private val buildType: String? = base.getBuildType(), + private val buildFlavor: String? = base.getBuildFlavor(), +) : ProjectConfig { + override fun getAppId(): String? = appId + override fun getAppFramework(): String? = appFramework + override fun getBuildId(): String? = buildId + override fun getBuildType(): String? = buildType + override fun getBuildFlavor(): String? = buildFlavor +} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeRedactionConfig.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeRedactionConfig.kt new file mode 100644 index 0000000000..0ec308725e --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeRedactionConfig.kt @@ -0,0 +1,11 @@ +package io.embrace.android.embracesdk.fakes.config + +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl +import io.embrace.android.embracesdk.internal.config.instrumented.schema.RedactionConfig + +class FakeRedactionConfig( + base: RedactionConfig = InstrumentedConfigImpl.redaction, + private val sensitiveKeys: List? = base.getSensitiveKeysDenylist(), +) : RedactionConfig { + override fun getSensitiveKeysDenylist(): List? = sensitiveKeys +} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeSessionConfig.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeSessionConfig.kt new file mode 100644 index 0000000000..c902b7fdbe --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/config/FakeSessionConfig.kt @@ -0,0 +1,13 @@ +package io.embrace.android.embracesdk.fakes.config + +import io.embrace.android.embracesdk.internal.config.instrumented.InstrumentedConfigImpl +import io.embrace.android.embracesdk.internal.config.instrumented.schema.SessionConfig + +class FakeSessionConfig( + base: SessionConfig = InstrumentedConfigImpl.session, + private val sessionComponentsImpl: List? = base.getSessionComponents(), + private val fullSessionEventsImpl: List = base.getFullSessionEvents(), +) : SessionConfig { + override fun getSessionComponents(): List? = sessionComponentsImpl + override fun getFullSessionEvents(): List = fullSessionEventsImpl +} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeEssentialServiceModule.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeEssentialServiceModule.kt index a93e288bf7..d9aa61f38d 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeEssentialServiceModule.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeEssentialServiceModule.kt @@ -3,7 +3,6 @@ package io.embrace.android.embracesdk.fakes.injection import io.embrace.android.embracesdk.fakes.FakeActivityTracker import io.embrace.android.embracesdk.fakes.FakeApiClient import io.embrace.android.embracesdk.fakes.FakeApiService -import io.embrace.android.embracesdk.fakes.FakeApiUrlBuilder import io.embrace.android.embracesdk.fakes.FakeLogWriter import io.embrace.android.embracesdk.fakes.FakeNetworkConnectivityService import io.embrace.android.embracesdk.fakes.FakePendingApiCallsSender @@ -16,7 +15,6 @@ import io.embrace.android.embracesdk.internal.capture.connectivity.NetworkConnec import io.embrace.android.embracesdk.internal.capture.user.UserService import io.embrace.android.embracesdk.internal.comms.api.ApiClient import io.embrace.android.embracesdk.internal.comms.api.ApiService -import io.embrace.android.embracesdk.internal.comms.api.ApiUrlBuilder import io.embrace.android.embracesdk.internal.comms.delivery.PendingApiCallsSender import io.embrace.android.embracesdk.internal.injection.EssentialServiceModule import io.embrace.android.embracesdk.internal.session.id.SessionIdTracker @@ -32,7 +30,6 @@ class FakeEssentialServiceModule( override val apiService: ApiService = FakeApiService(), override val networkConnectivityService: NetworkConnectivityService = FakeNetworkConnectivityService(), override val pendingApiCallsSender: PendingApiCallsSender = FakePendingApiCallsSender(), - override val urlBuilder: ApiUrlBuilder = FakeApiUrlBuilder(), override val logWriter: LogWriter = FakeLogWriter(), override val sessionPropertiesService: FakeSessionPropertiesService = FakeSessionPropertiesService(), ) : EssentialServiceModule diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeInitModule.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeInitModule.kt index a0f142a21c..cc7ea9c2ec 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeInitModule.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeInitModule.kt @@ -2,8 +2,10 @@ package io.embrace.android.embracesdk.fakes.injection import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.FakeEmbLogger +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.internal.SystemInfo import io.embrace.android.embracesdk.internal.clock.Clock +import io.embrace.android.embracesdk.internal.config.instrumented.schema.InstrumentedConfig import io.embrace.android.embracesdk.internal.injection.InitModule import io.embrace.android.embracesdk.internal.injection.OpenTelemetryModule import io.embrace.android.embracesdk.internal.injection.createInitModule @@ -23,6 +25,7 @@ class FakeInitModule( logger = logger, systemInfo = systemInfo ), + override var instrumentedConfig: InstrumentedConfig = FakeInstrumentedConfig() ) : InitModule by initModule { val openTelemetryModule: OpenTelemetryModule by lazy { createOpenTelemetryModule(initModule) } diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeStorageModule.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeStorageModule.kt index b2434af92b..1880c133b8 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeStorageModule.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/injection/FakeStorageModule.kt @@ -3,7 +3,6 @@ package io.embrace.android.embracesdk.fakes.injection import io.embrace.android.embracesdk.fakes.FakeCacheService import io.embrace.android.embracesdk.fakes.FakeDeliveryCacheManager import io.embrace.android.embracesdk.fakes.FakeStorageService -import io.embrace.android.embracesdk.internal.comms.api.ApiResponseCache import io.embrace.android.embracesdk.internal.comms.delivery.CacheService import io.embrace.android.embracesdk.internal.comms.delivery.DeliveryCacheManager import io.embrace.android.embracesdk.internal.injection.StorageModule @@ -13,8 +12,4 @@ class FakeStorageModule( override val cacheService: CacheService = FakeCacheService(), override val deliveryCacheManager: DeliveryCacheManager = FakeDeliveryCacheManager(), override val storageService: StorageService = FakeStorageService(), -) : StorageModule { - - override val cache: ApiResponseCache - get() = throw UnsupportedOperationException() -} +) : StorageModule