diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt index f08deeb4..5a70e7b8 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt @@ -135,6 +135,7 @@ class EmarsysTest : AnnotationSpec() { private lateinit var mockPush: PushApi private lateinit var mockPredict: PredictApi private lateinit var mockPredictRestricted: PredictRestrictedApi + private lateinit var mockLoggingPredictRestricted: PredictRestrictedApi private lateinit var mockMobileEngageApi: MobileEngageApi private lateinit var mockLoggingMobileEngageApi: MobileEngageApi private lateinit var mockLoggingPredict: PredictApi @@ -207,6 +208,7 @@ class EmarsysTest : AnnotationSpec() { mockPredict = mock() mockLoggingPredict = mock() mockPredictRestricted = mock() + mockLoggingPredictRestricted = mock() mockConfig = mock() mockConfigInternal = mock() mockMessageInbox = mock() @@ -269,7 +271,7 @@ class EmarsysTest : AnnotationSpec() { predict = mockPredict, loggingPredict = mockLoggingPredict, predictRestricted = mockPredictRestricted, - loggingPredictRestricted = mockPredictRestricted, + loggingPredictRestricted = mockLoggingPredictRestricted, config = mockConfig, configInternal = mockConfigInternal, eventService = mockEventServiceApi, @@ -654,7 +656,7 @@ class EmarsysTest : AnnotationSpec() { runBlockingOnCoreSdkThread() runBlockingOnCoreSdkThread { - verify(mockPredictRestricted).setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, null) + verify(mockPredictRestricted).setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, completionListener) verifyNoInteractions(mockMobileEngageApi) } } @@ -754,19 +756,19 @@ class EmarsysTest : AnnotationSpec() { } @Test - fun testSetContactWithCompletionListener_delegatesToInternals_whenBothFeaturesEnabled() { + fun testSetContactWithCompletionListener_delegatesToMobileEngageOnly_whenBothFeaturesEnabled() { setup(mobileEngageAndPredictConfig) setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, completionListener) runBlockingOnCoreSdkThread { - verify(mockPredictRestricted).setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, null) verify(mockMobileEngageApi).setContact( CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, completionListener ) } + verifyNoInteractions(mockPredictRestricted) } @Test @@ -786,20 +788,19 @@ class EmarsysTest : AnnotationSpec() { } @Test - fun testClearContactWithCompletionListener_delegatesToPredictInternal_whenPredictIsEnabled() { + fun testClearContactWithCompletionListener_delegatesToPredictInternal_whenOnlyPredictIsEnabled() { setup(predictConfig) clearContact(completionListener) - runBlockingOnCoreSdkThread() runBlockingOnCoreSdkThread { verifyNoInteractions(mockMobileEngageApi) - verify(mockPredictRestricted).clearContact() + verify(mockPredictRestricted).clearPredictOnlyContact(completionListener) } } @Test - fun testClearContactWithCompletionListener_delegatesToMobileApi_whenMobileEngageIsEnabled() { + fun testClearContactWithCompletionListener_delegatesToMobileApi_whenOnlyMobileEngageIsEnabled() { setup(mobileEngageConfig) clearContact(completionListener) @@ -810,25 +811,6 @@ class EmarsysTest : AnnotationSpec() { } } - @Test - fun testClearContactWithCompletionListener_doNotDelegatesToPredictInternal_whenPredictIsDisabled() { - setup(mobileEngageConfig) - - clearContact(completionListener) - runBlockingOnCoreSdkThread { - verifyNoInteractions(mockPredictRestricted) - } - } - - @Test - fun testClearContactWithCompletionListener_doNotDelegatesToMobileEngageApi_whenMobileEngageIsDisabled() { - setup(predictConfig) - - clearContact(completionListener) - - verifyNoInteractions(mockMobileEngageApi) - } - @Test fun testClearContactWithCompletionListener_delegatesToInternals_whenBothEnabled() { setup(mobileEngageAndPredictConfig) @@ -836,10 +818,9 @@ class EmarsysTest : AnnotationSpec() { clearContact(completionListener) runBlockingOnCoreSdkThread { + verify(mockMobileEngageApi).clearContact(completionListener) verify(mockPredictRestricted).clearContact() } - - verify(mockMobileEngageApi).clearContact(completionListener) } @Test @@ -851,6 +832,7 @@ class EmarsysTest : AnnotationSpec() { verifyNoInteractions(mockPredictRestricted) } verify(mockLoggingMobileEngageApi).clearContact(completionListener) + verify(mockLoggingPredictRestricted).clearContact() } @Test diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/di/FakeDependencyContainer.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/di/FakeDependencyContainer.kt index 4edbea94..5803f23f 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/di/FakeDependencyContainer.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/di/FakeDependencyContainer.kt @@ -76,6 +76,7 @@ import com.emarsys.predict.PredictApi import com.emarsys.predict.PredictInternal import com.emarsys.predict.PredictRestrictedApi import com.emarsys.predict.provider.PredictRequestModelBuilderProvider +import com.emarsys.predict.request.PredictMultiIdRequestModelFactory import com.emarsys.predict.request.PredictRequestContext import com.emarsys.push.PushApi import com.google.android.gms.location.FusedLocationProviderClient @@ -200,5 +201,6 @@ class FakeDependencyContainer( override val jsCommandFactoryProvider: JSCommandFactoryProvider = mock(), override val jsOnCloseListener: OnCloseListener = mock(), override val jsOnAppEventListener: OnAppEventListener = mock(), - override val remoteMessageMapperFactory: RemoteMessageMapperFactory = mock() + override val remoteMessageMapperFactory: RemoteMessageMapperFactory = mock(), + override val predictMultiIdRequestModelFactory: PredictMultiIdRequestModelFactory = mock(), ) : EmarsysComponent \ No newline at end of file diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/predict/PredictRestrictedTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/predict/PredictRestrictedTest.kt index 61b35aea..4e17e7dd 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/predict/PredictRestrictedTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/predict/PredictRestrictedTest.kt @@ -1,6 +1,7 @@ package com.emarsys.predict +import com.emarsys.core.api.result.CompletionListener import com.emarsys.di.FakeDependencyContainer import com.emarsys.di.emarsys import com.emarsys.di.setupEmarsysComponent @@ -52,4 +53,11 @@ class PredictRestrictedTest : AnnotationSpec() { predictRestricted.clearContact() Mockito.verify(mockPredictInternal).clearContact() } + + @Test + fun testPredict_clearPredictOnlyContact_delegatesTo_Predict_Internal() { + val mockCompletionListener = mock() + predictRestricted.clearPredictOnlyContact(mockCompletionListener) + Mockito.verify(mockPredictInternal).clearPredictOnlyContact(mockCompletionListener) + } } \ No newline at end of file diff --git a/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt b/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt index 6a954f36..28afd6e2 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt @@ -132,35 +132,79 @@ object Emarsys { contactFieldValue: String, completionListener: CompletionListener? = null ) { - if (FeatureRegistry.isFeatureEnabled(MOBILE_ENGAGE) - || (!FeatureRegistry.isFeatureEnabled(MOBILE_ENGAGE) - && !FeatureRegistry.isFeatureEnabled(PREDICT)) - ) { - EmarsysDependencyInjection.mobileEngageApi() - .proxyApi(mobileEngage().concurrentHandlerHolder) - .setContact(contactFieldId, contactFieldValue, completionListener) - } else if (FeatureRegistry.isFeatureEnabled(PREDICT)) { - EmarsysDependencyInjection.predictRestrictedApi() - .proxyApi(mobileEngage().concurrentHandlerHolder) - .setContact(contactFieldId, contactFieldValue, completionListener) - } + executeMobileEngageOrPredictOnlyLogic( + mobileEngageLogic = { + setMobileEngageContact( + contactFieldId, + contactFieldValue, + completionListener + ) + }, + predictOnlyLogic = { + setPredictOnlyContact( + contactFieldId, + contactFieldValue, + completionListener + ) + } + ) + } + + private fun setMobileEngageContact( + contactFieldId: Int, + contactFieldValue: String, + completionListener: CompletionListener? + ) { + EmarsysDependencyInjection.mobileEngageApi() + .proxyApi(mobileEngage().concurrentHandlerHolder) + .setContact(contactFieldId, contactFieldValue, completionListener) + } + + private fun setPredictOnlyContact( + contactFieldId: Int, + contactFieldValue: String, + completionListener: CompletionListener? + ) { + EmarsysDependencyInjection.predictRestrictedApi() + .proxyApi(mobileEngage().concurrentHandlerHolder) + .setContact(contactFieldId, contactFieldValue, completionListener) } @JvmStatic @JvmOverloads fun clearContact(completionListener: CompletionListener? = null) { + executeMobileEngageOrPredictOnlyLogic( + mobileEngageLogic = { clearMobileEngageContact(completionListener) }, + predictOnlyLogic = { clearPredictOnlyContact(completionListener) } + ) + } + + private fun clearMobileEngageContact(completionListener: CompletionListener?) { + EmarsysDependencyInjection.mobileEngageApi() + .proxyApi(mobileEngage().concurrentHandlerHolder) + .clearContact(completionListener) + EmarsysDependencyInjection.predictRestrictedApi() + .proxyApi(mobileEngage().concurrentHandlerHolder) + .clearContact() + } + + private fun clearPredictOnlyContact(completionListener: CompletionListener?) { + EmarsysDependencyInjection.predictRestrictedApi() + .proxyApi(mobileEngage().concurrentHandlerHolder) + .clearPredictOnlyContact(completionListener) + } + + private fun executeMobileEngageOrPredictOnlyLogic( + mobileEngageLogic: () -> Unit, + predictOnlyLogic: () -> Unit + ) { if (FeatureRegistry.isFeatureEnabled(MOBILE_ENGAGE) || (!FeatureRegistry.isFeatureEnabled(MOBILE_ENGAGE) && !FeatureRegistry.isFeatureEnabled(PREDICT)) ) { - EmarsysDependencyInjection.mobileEngageApi() - .proxyApi(mobileEngage().concurrentHandlerHolder) - .clearContact(completionListener) - } - if (FeatureRegistry.isFeatureEnabled(PREDICT)) { - EmarsysDependencyInjection.predictRestrictedApi() - .proxyApi(mobileEngage().concurrentHandlerHolder) - .clearContact() + mobileEngageLogic() + } else if (FeatureRegistry.isFeatureEnabled(PREDICT)) { + predictOnlyLogic() } } @@ -233,17 +277,31 @@ object Emarsys { } private fun refreshRemoteConfig(applicationCode: String?) { - if (!listOf("", "null", "nil", "0").contains(applicationCode?.lowercase()) && applicationCode != null) { + if (!listOf( + "", + "null", + "nil", + "0" + ).contains(applicationCode?.lowercase()) && applicationCode != null + ) { emarsys().configInternal.proxyApi(mobileEngage().concurrentHandlerHolder) .refreshRemoteConfig { it?.let { - val logEntry = StatusLog(Emarsys::class.java, "refreshRemoteConfig", mapOf("applicationCode" to applicationCode, "exception" to it.message)) + val logEntry = StatusLog( + Emarsys::class.java, + "refreshRemoteConfig", + mapOf("applicationCode" to applicationCode, "exception" to it.message) + ) Logger.log(logEntry) } } } else { Log.w("EmarsysSdk", "Invalid applicationCode: $applicationCode") - val logEntry = StatusLog(Emarsys::class.java, "refreshRemoteConfig", mapOf("applicationCode" to applicationCode)) + val logEntry = StatusLog( + Emarsys::class.java, + "refreshRemoteConfig", + mapOf("applicationCode" to applicationCode) + ) Logger.log(logEntry) } } diff --git a/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestricted.kt b/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestricted.kt index 7c27c302..cffea80a 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestricted.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestricted.kt @@ -11,6 +11,11 @@ class PredictRestricted(private val loggingInstance: Boolean = false) : PredictR .setContact(contactFieldId, contactFieldValue, completionListener) } + override fun clearPredictOnlyContact(completionListener: CompletionListener?) { + (if (loggingInstance) predict().loggingPredictInternal else predict().predictInternal) + .clearPredictOnlyContact(completionListener) + } + override fun clearContact() { (if (loggingInstance) predict().loggingPredictInternal else predict().predictInternal) .clearContact() diff --git a/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestrictedApi.kt b/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestrictedApi.kt index ead4703a..e5c74d4f 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestrictedApi.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestrictedApi.kt @@ -5,5 +5,8 @@ import com.emarsys.core.api.result.CompletionListener interface PredictRestrictedApi { fun setContact(contactFieldId: Int, contactFieldValue: String, completionListener: CompletionListener?) + + fun clearPredictOnlyContact(completionListener: CompletionListener?) + fun clearContact() } \ No newline at end of file diff --git a/predict/src/androidTest/java/com/emarsys/predict/DefaultPredictInternalTest.kt b/predict/src/androidTest/java/com/emarsys/predict/DefaultPredictInternalTest.kt index 5448c052..40128a80 100644 --- a/predict/src/androidTest/java/com/emarsys/predict/DefaultPredictInternalTest.kt +++ b/predict/src/androidTest/java/com/emarsys/predict/DefaultPredictInternalTest.kt @@ -205,15 +205,54 @@ class DefaultPredictInternalTest : AnnotationSpec() { } @Test - fun testClearContact_shouldRemove_contactIdFromKeyValueStore() { + fun testClearContact_shouldRemove_visitorIdFromKeyValueStore() { predictInternal.clearContact() - verify(mockKeyValueStore).remove("predict_contact_id") + verify(mockKeyValueStore).remove("predict_visitor_id") } @Test - fun testClearContact_shouldRemove_visitorIdFromKeyValueStore() { - predictInternal.clearContact() + fun testClearPredictOnlyContact_shouldCall_requestManager() { + whenever( + mockPredictMultiIdRequestModelFactory.createClearContactRequestModel() + ).thenReturn(mockRequestModel) + + predictInternal.clearPredictOnlyContact(mockCompletionListener) + + verify(mockRequestManager).submit(mockRequestModel, mockCompletionListener) + } + + @Test + fun testClearPredictOnlyContact_shouldCall_completionListener_whenExceptionIsThrown_andShouldNotRemoveVisitorId() { + val testException = IllegalArgumentException() + whenever( + mockPredictMultiIdRequestModelFactory.createClearContactRequestModel() + ).thenThrow(testException) + + predictInternal.clearPredictOnlyContact(mockCompletionListener) + + verify(mockCompletionListener).onCompleted(testException) + verifyNoInteractions(mockRequestManager) + verifyNoInteractions(mockKeyValueStore) + } + + @Test + fun testClearPredictOnlyContact_shouldNotCrash_withException_whenCompletionListenerIsNull_andShouldNotRemoveVisitorId() { + val testException = IllegalArgumentException() + whenever( + mockPredictMultiIdRequestModelFactory.createClearContactRequestModel() + ).thenThrow(testException) + + predictInternal.clearPredictOnlyContact(null) + + verifyNoInteractions(mockRequestManager) + verifyNoInteractions(mockKeyValueStore) + verifyNoInteractions(mockCompletionListener) + } + + @Test + fun testClearPredictOnlyContact_shouldRemove_visitorIdFromKeyValueStore() { + predictInternal.clearPredictOnlyContact(mockCompletionListener) verify(mockKeyValueStore).remove("predict_visitor_id") } diff --git a/predict/src/androidTest/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactoryTest.kt b/predict/src/androidTest/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactoryTest.kt index 9d9aa57c..69f31e03 100644 --- a/predict/src/androidTest/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactoryTest.kt +++ b/predict/src/androidTest/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactoryTest.kt @@ -120,4 +120,31 @@ class PredictMultiIdRequestModelFactoryTest { factory.createRefreshContactTokenRequestModel(REFRESH_TOKEN) } } + + @Test + fun testCreateClearContactRequestModel_whenMerchantId_isPresentOnContext() { + val expected = RequestModel( + "https://me-client.eservice.emarsys.net/v3/contact-token", + RequestMethod.DELETE, + null, + mapOf(), + TIMESTAMP, + Long.MAX_VALUE, + REQUEST_ID + ) + + val requestModel = + factory.createClearContactRequestModel() + + requestModel shouldBe expected + } + + @Test + fun testCreateClearContactRequestModel_whenMerchantId_isMissingFromContext_throws_IllegalArgumentException() { + testPredictRequestContext.merchantId = null + + shouldThrow { + factory.createClearContactRequestModel() + } + } } \ No newline at end of file diff --git a/predict/src/main/java/com/emarsys/predict/DefaultPredictInternal.kt b/predict/src/main/java/com/emarsys/predict/DefaultPredictInternal.kt index 6e45a4b7..7ba7fd4f 100644 --- a/predict/src/main/java/com/emarsys/predict/DefaultPredictInternal.kt +++ b/predict/src/main/java/com/emarsys/predict/DefaultPredictInternal.kt @@ -39,8 +39,6 @@ class DefaultPredictInternal( companion object { const val VISITOR_ID_KEY = "predict_visitor_id" const val XP_KEY = "xp" - const val CONTACT_FIELD_VALUE_KEY = "predict_contact_id" - const val CONTACT_FIELD_ID_KEY = "predict_contact_field_id" private const val TYPE_CART = "predict_cart" private const val TYPE_PURCHASE = "predict_purchase" private const val TYPE_ITEM_VIEW = "predict_item_view" @@ -70,9 +68,18 @@ class DefaultPredictInternal( } } + override fun clearPredictOnlyContact(completionListener: CompletionListener?) { + try { + val requestModel = predictMultiIdRequestModelFactory.createClearContactRequestModel() + requestManager.submit(requestModel, completionListener) + + keyValueStore.remove(VISITOR_ID_KEY) + } catch (e: Exception) { + completionListener?.onCompleted(e) + } + } + override fun clearContact() { - keyValueStore.remove(CONTACT_FIELD_VALUE_KEY) - keyValueStore.remove(CONTACT_FIELD_ID_KEY) keyValueStore.remove(VISITOR_ID_KEY) } diff --git a/predict/src/main/java/com/emarsys/predict/LoggingPredictInternal.kt b/predict/src/main/java/com/emarsys/predict/LoggingPredictInternal.kt index 4d622acf..fdd11715 100644 --- a/predict/src/main/java/com/emarsys/predict/LoggingPredictInternal.kt +++ b/predict/src/main/java/com/emarsys/predict/LoggingPredictInternal.kt @@ -26,6 +26,14 @@ class LoggingPredictInternal(private val klass: Class<*>) : PredictInternal { debug(MethodNotAllowed(klass, callerMethodName, parameters)) } + override fun clearPredictOnlyContact(completionListener: CompletionListener?) { + val parameters: Map = mapOf( + "completion_listener" to (completionListener != null) + ) + val callerMethodName = SystemUtils.getCallerMethodName() + debug(MethodNotAllowed(klass, callerMethodName, parameters)) + } + override fun clearContact() { val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, null)) diff --git a/predict/src/main/java/com/emarsys/predict/PredictInternal.kt b/predict/src/main/java/com/emarsys/predict/PredictInternal.kt index ee7aa0eb..4bb79cf4 100644 --- a/predict/src/main/java/com/emarsys/predict/PredictInternal.kt +++ b/predict/src/main/java/com/emarsys/predict/PredictInternal.kt @@ -10,6 +10,7 @@ import com.emarsys.predict.api.model.RecommendationFilter interface PredictInternal { fun setContact(contactFieldId: Int, contactFieldValue: String, completionListener: CompletionListener?) + fun clearPredictOnlyContact(completionListener: CompletionListener?) fun clearContact() fun trackCart(items: List): String fun trackPurchase(orderId: String, items: List): String diff --git a/predict/src/main/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactory.kt b/predict/src/main/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactory.kt index f48d5b1d..04f1648b 100644 --- a/predict/src/main/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactory.kt +++ b/predict/src/main/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactory.kt @@ -43,6 +43,16 @@ class PredictMultiIdRequestModelFactory( .build() } + fun createClearContactRequestModel(): RequestModel { + validateMerchantId() + return RequestModel.Builder(predictRequestContext.timestampProvider, predictRequestContext.uuidProvider) + .url( + "${clientServiceEndpointProvider.provideEndpointHost()}${Endpoint.CLIENT_MULTI_ID_BASE}/contact-token" + ) + .method(RequestMethod.DELETE) + .build() + } + private fun validateMerchantId() { if (predictRequestContext.merchantId.isNullOrBlank()) { throw IllegalArgumentException("Merchant Id must not be null!")