From ff7a60ec844336ff919edbbbf211d7b652562b25 Mon Sep 17 00:00:00 2001 From: Marton Matusek Date: Mon, 22 Apr 2024 13:36:20 +0200 Subject: [PATCH] feat(multi-id): implement predict only setContact SUITEDEV-35599 Co-authored-by: Andras Sarro Co-authored-by: davidSchuppa <32750715+davidSchuppa@users.noreply.github.com> --- .../java/com/emarsys/EmarsysTest.kt | 4 +- .../emarsys/predict/PredictRestrictedTest.kt | 4 +- .../src/main/java/com/emarsys/Emarsys.kt | 5 +- .../com/emarsys/di/DefaultEmarsysComponent.kt | 10 ++- .../com/emarsys/predict/PredictRestricted.kt | 5 +- .../emarsys/predict/PredictRestrictedApi.kt | 4 +- .../emarsys/mapper/MerchantIdHeaderMapper.kt | 3 +- gradle/libs.versions.toml | 2 +- .../util/RequestModelHelperTest.kt | 20 +++++ .../mobileengage/util/RequestModelHelper.kt | 9 +- .../predict/DefaultPredictInternalTest.kt | 58 +++++++++++- .../PredictMultiIdRequestModelFactoryTest.kt | 90 +++++++++++++++++++ .../emarsys/predict/DefaultPredictInternal.kt | 22 ++++- .../emarsys/predict/LoggingPredictInternal.kt | 48 ++++++---- .../com/emarsys/predict/PredictInternal.kt | 3 +- .../emarsys/predict/endpoint/Endpoint.java | 6 -- .../com/emarsys/predict/endpoint/Endpoint.kt | 9 ++ .../PredictMultiIdRequestModelFactory.kt | 36 ++++++++ 18 files changed, 290 insertions(+), 48 deletions(-) create mode 100644 predict/src/androidTest/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactoryTest.kt delete mode 100644 predict/src/main/java/com/emarsys/predict/endpoint/Endpoint.java create mode 100644 predict/src/main/java/com/emarsys/predict/endpoint/Endpoint.kt create mode 100644 predict/src/main/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactory.kt diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt index 9c1d26860..f08deeb4f 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt @@ -654,7 +654,7 @@ class EmarsysTest : AnnotationSpec() { runBlockingOnCoreSdkThread() runBlockingOnCoreSdkThread { - verify(mockPredictRestricted).setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE) + verify(mockPredictRestricted).setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, null) verifyNoInteractions(mockMobileEngageApi) } } @@ -760,7 +760,7 @@ class EmarsysTest : AnnotationSpec() { setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, completionListener) runBlockingOnCoreSdkThread { - verify(mockPredictRestricted).setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE) + verify(mockPredictRestricted).setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, null) verify(mockMobileEngageApi).setContact( CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, 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 db42a4aeb..61b35aeae 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/predict/PredictRestrictedTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/predict/PredictRestrictedTest.kt @@ -43,8 +43,8 @@ class PredictRestrictedTest : AnnotationSpec() { @Test fun testPredict_setContact_delegatesTo_Predict_Internal() { - predictRestricted.setContact(CONTACT_FIELD_ID, "contactId") - Mockito.verify(mockPredictInternal).setContact(CONTACT_FIELD_ID, "contactId") + predictRestricted.setContact(CONTACT_FIELD_ID, "contactId", null) + Mockito.verify(mockPredictInternal).setContact(CONTACT_FIELD_ID, "contactId", null) } @Test diff --git a/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt b/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt index fc0ea64be..6a954f36e 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt @@ -139,11 +139,10 @@ object Emarsys { EmarsysDependencyInjection.mobileEngageApi() .proxyApi(mobileEngage().concurrentHandlerHolder) .setContact(contactFieldId, contactFieldValue, completionListener) - } - if (FeatureRegistry.isFeatureEnabled(PREDICT)) { + } else if (FeatureRegistry.isFeatureEnabled(PREDICT)) { EmarsysDependencyInjection.predictRestrictedApi() .proxyApi(mobileEngage().concurrentHandlerHolder) - .setContact(contactFieldId, contactFieldValue) + .setContact(contactFieldId, contactFieldValue, completionListener) } } diff --git a/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysComponent.kt b/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysComponent.kt index 3c86296b8..5b46a1e6a 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysComponent.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysComponent.kt @@ -177,8 +177,10 @@ import com.emarsys.predict.PredictInternal import com.emarsys.predict.PredictResponseMapper import com.emarsys.predict.PredictRestricted import com.emarsys.predict.PredictRestrictedApi +import com.emarsys.predict.endpoint.Endpoint.PREDICT_BASE_URL import com.emarsys.predict.provider.PredictRequestModelBuilderProvider import com.emarsys.predict.request.PredictHeaderFactory +import com.emarsys.predict.request.PredictMultiIdRequestModelFactory import com.emarsys.predict.request.PredictRequestContext import com.emarsys.predict.response.VisitorIdResponseHandler import com.emarsys.predict.response.XPResponseHandler @@ -935,7 +937,6 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { ) } - override val coreSQLiteDatabase: CoreSQLiteDatabase by lazy { coreDbHelper.writableCoreDatabase } @@ -983,6 +984,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { DefaultPredictInternal( predictRequestContext, requestManager, + predictMultiIdRequestModelFactory, concurrentHandlerHolder, predictRequestModelBuilderProvider, PredictResponseMapper() @@ -1009,7 +1011,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val predictServiceProvider: ServiceEndpointProvider by lazy { ServiceEndpointProvider( predictServiceStorage, - com.emarsys.predict.endpoint.Endpoint.PREDICT_BASE_URL + PREDICT_BASE_URL ) } @@ -1017,6 +1019,10 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { StringStorage(PredictStorageKey.PREDICT_SERVICE_URL, sharedPreferences) } + private val predictMultiIdRequestModelFactory: PredictMultiIdRequestModelFactory by lazy { + PredictMultiIdRequestModelFactory(predictRequestContext, clientServiceEndpointProvider) + } + private fun createRequestModelRepository( coreDbHelper: CoreDbHelper, inAppEventHandler: InAppEventHandlerInternal 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 af0a4cb80..7c27c302b 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestricted.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestricted.kt @@ -1,13 +1,14 @@ package com.emarsys.predict import com.emarsys.core.Mockable +import com.emarsys.core.api.result.CompletionListener import com.emarsys.predict.di.predict @Mockable class PredictRestricted(private val loggingInstance: Boolean = false) : PredictRestrictedApi { - override fun setContact(contactFieldId: Int, contactFieldValue: String) { + override fun setContact(contactFieldId: Int, contactFieldValue: String, completionListener: CompletionListener?) { (if (loggingInstance) predict().loggingPredictInternal else predict().predictInternal) - .setContact(contactFieldId, contactFieldValue) + .setContact(contactFieldId, contactFieldValue, completionListener) } override fun 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 df408436c..ead4703ac 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestrictedApi.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestrictedApi.kt @@ -1,7 +1,9 @@ package com.emarsys.predict +import com.emarsys.core.api.result.CompletionListener + interface PredictRestrictedApi { - fun setContact(contactFieldId: Int, contactFieldValue: String) + fun setContact(contactFieldId: Int, contactFieldValue: String, completionListener: CompletionListener?) fun clearContact() } \ No newline at end of file diff --git a/emarsys/src/main/java/com/emarsys/mapper/MerchantIdHeaderMapper.kt b/emarsys/src/main/java/com/emarsys/mapper/MerchantIdHeaderMapper.kt index cdcf5101a..cfdbceff3 100644 --- a/emarsys/src/main/java/com/emarsys/mapper/MerchantIdHeaderMapper.kt +++ b/emarsys/src/main/java/com/emarsys/mapper/MerchantIdHeaderMapper.kt @@ -26,7 +26,8 @@ class MerchantIdHeaderMapper( override fun shouldMapRequestModel(requestModel: RequestModel): Boolean { return (requestModelHelper.isMobileEngageSetContactRequest(requestModel) - || requestModelHelper.isRefreshContactTokenRequest(requestModel)) + || requestModelHelper.isRefreshContactTokenRequest(requestModel) + || requestModelHelper.isPredictMultiIdContactRequest(requestModel)) && !predictRequestContext.merchantId.isNullOrBlank() } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3ea4190be..63176e5ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ androidx-navigation-compose = "2.7.6" compose-compiler = "1.5.6" compose-plugin = "1.6.0-dev1419" android-compileSdk = "34" -android-minSdk = "26" +android-minSdk = "24" android-targetSdk = "34" androidx-activity = "1.8.2" androidx-activityCompose = "1.8.2" diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/util/RequestModelHelperTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/util/RequestModelHelperTest.kt index bd562e368..cf328bf49 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/util/RequestModelHelperTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/util/RequestModelHelperTest.kt @@ -160,6 +160,26 @@ class RequestModelHelperTest : AnnotationSpec() { result shouldBe false } + @Test + fun testIsPredictMultiIdSetContactRequest_true_whenItIsPredictMultiIdSetContactRequest() { + val mockRequestModel = mock(RequestModel::class.java).apply { + whenever(url).thenReturn(URL("$CLIENT_HOST/contact-token")) + } + val result = requestModelHelper.isPredictMultiIdContactRequest(mockRequestModel) + + result shouldBe true + } + + @Test + fun testIsPredictMultiIdSetContactRequest_false_whenItIsNotPredictMultiIdSetContactRequest() { + val mockRequestModel = mock(RequestModel::class.java).apply { + whenever(url).thenReturn(URL("$CLIENT_BASE/contact-token")) + } + val result = requestModelHelper.isPredictMultiIdContactRequest(mockRequestModel) + + result shouldBe false + } + @Test fun testIsMobileEngageClientRequest_false_whenIsNotClientEndpoint() { diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/util/RequestModelHelper.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/util/RequestModelHelper.kt index e2a3c9d15..0be02eee3 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/util/RequestModelHelper.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/util/RequestModelHelper.kt @@ -46,7 +46,14 @@ class RequestModelHelper( val clientServiceUrl = clientServiceEndpointProvider.provideEndpointHost() val url = requestModel.url.toString() - return url.startsWithOneOf(clientServiceUrl) && url.endsWith("/contact-token") + return url.startsWithOneOf(clientServiceUrl) && url.endsWith("/client/contact-token") + } + + fun isPredictMultiIdContactRequest(requestModel: RequestModel): Boolean { + val clientServiceUrl = clientServiceEndpointProvider.provideEndpointHost() + + val url = requestModel.url.toString() + return url.startsWith(clientServiceUrl) && !url.contains("apps") && url.endsWith("/contact-token") } fun isMobileEngageSetContactRequest(requestModel: RequestModel): Boolean { diff --git a/predict/src/androidTest/java/com/emarsys/predict/DefaultPredictInternalTest.kt b/predict/src/androidTest/java/com/emarsys/predict/DefaultPredictInternalTest.kt index b380b37f3..5448c052e 100644 --- a/predict/src/androidTest/java/com/emarsys/predict/DefaultPredictInternalTest.kt +++ b/predict/src/androidTest/java/com/emarsys/predict/DefaultPredictInternalTest.kt @@ -28,6 +28,7 @@ import com.emarsys.predict.fake.FakeRestClient import com.emarsys.predict.fake.FakeResultListener import com.emarsys.predict.model.LastTrackedItemContainer import com.emarsys.predict.provider.PredictRequestModelBuilderProvider +import com.emarsys.predict.request.PredictMultiIdRequestModelFactory import com.emarsys.predict.request.PredictRequestContext import com.emarsys.predict.request.PredictRequestModelBuilder import com.emarsys.testUtil.AnnotationSpec @@ -46,6 +47,7 @@ import org.mockito.kotlin.doReturnConsecutively import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions import java.util.concurrent.CountDownLatch class DefaultPredictInternalTest : AnnotationSpec() { @@ -61,12 +63,14 @@ class DefaultPredictInternalTest : AnnotationSpec() { const val TYPE = "INCLUDE_OR_EXCLUDE" val EXPECTATIONS = listOf() const val CONTACT_FIELD_ID = 999 + const val CONTACT_FIELD_VALUE = "testContactFieldValue" } private lateinit var mockKeyValueStore: KeyValueStore private lateinit var predictInternal: PredictInternal private lateinit var mockRequestManager: RequestManager + private lateinit var mockPredictMultiIdRequestModelFactory: PredictMultiIdRequestModelFactory private lateinit var mockTimestampProvider: TimestampProvider private lateinit var mockUuidProvider: UUIDProvider private lateinit var mockRequestContext: PredictRequestContext @@ -82,6 +86,7 @@ class DefaultPredictInternalTest : AnnotationSpec() { private lateinit var latch: CountDownLatch private lateinit var mockResultListener: ResultListener>> private lateinit var mockLastTrackedItemContainer: LastTrackedItemContainer + private lateinit var mockCompletionListener: CompletionListener @Before @Suppress("UNCHECKED_CAST") @@ -94,6 +99,7 @@ class DefaultPredictInternalTest : AnnotationSpec() { mockResponseModel = mock() mockKeyValueStore = mock() mockRequestManager = mock() + mockPredictMultiIdRequestModelFactory = mock() concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockPredictResponseMapper = mock() mockLogic = mock() @@ -135,9 +141,12 @@ class DefaultPredictInternalTest : AnnotationSpec() { mockLastTrackedItemContainer = mock() + mockCompletionListener = mock() + predictInternal = DefaultPredictInternal( mockRequestContext, mockRequestManager, + mockPredictMultiIdRequestModelFactory, concurrentHandlerHolder, mockRequestModelBuilderProvider, mockPredictResponseMapper @@ -151,12 +160,48 @@ class DefaultPredictInternalTest : AnnotationSpec() { } @Test - fun testSetContact_shouldPersistsWithKeyValueStore() { - val contactId = "contactId" + fun testSetContact_shouldCall_requestManager() { + whenever( + mockPredictMultiIdRequestModelFactory.createSetContactRequestModel( + CONTACT_FIELD_ID, + CONTACT_FIELD_VALUE + ) + ).thenReturn(mockRequestModel) + + predictInternal.setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, mockCompletionListener) + + verify(mockRequestManager).submit(mockRequestModel, mockCompletionListener) + } + + @Test + fun testSetContact_shouldCall_completionListener_whenExceptionIsThrown() { + val testException = IllegalArgumentException() + whenever( + mockPredictMultiIdRequestModelFactory.createSetContactRequestModel( + CONTACT_FIELD_ID, + CONTACT_FIELD_VALUE + ) + ).thenThrow(testException) + + predictInternal.setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, mockCompletionListener) + + verify(mockCompletionListener).onCompleted(testException) + verifyNoInteractions(mockRequestManager) + } + + @Test + fun testSetContact_shouldNotCrash_withException_whenCompletionListenerIsNull() { + val testException = IllegalArgumentException() + whenever( + mockPredictMultiIdRequestModelFactory.createSetContactRequestModel( + CONTACT_FIELD_ID, + CONTACT_FIELD_VALUE + ) + ).thenThrow(testException) - predictInternal.setContact(CONTACT_FIELD_ID, contactId) + predictInternal.setContact(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE, null) - verify(mockKeyValueStore).putString("predict_contact_id", contactId) + verifyNoInteractions(mockRequestManager) } @Test @@ -484,6 +529,7 @@ class DefaultPredictInternalTest : AnnotationSpec() { FakeRestClient.Mode.SUCCESS ) ), + mockPredictMultiIdRequestModelFactory, concurrentHandlerHolder, mockRequestModelBuilderProvider, mockPredictResponseMapper @@ -521,6 +567,7 @@ class DefaultPredictInternalTest : AnnotationSpec() { FakeRestClient.Mode.ERROR_RESPONSE_MODEL ) ), + mockPredictMultiIdRequestModelFactory, concurrentHandlerHolder, mockRequestModelBuilderProvider, mockPredictResponseMapper @@ -547,6 +594,7 @@ class DefaultPredictInternalTest : AnnotationSpec() { FakeRestClient.Mode.ERROR_RESPONSE_MODEL ) ), + mockPredictMultiIdRequestModelFactory, concurrentHandlerHolder, mockRequestModelBuilderProvider, mockPredictResponseMapper @@ -574,6 +622,7 @@ class DefaultPredictInternalTest : AnnotationSpec() { predictInternal = DefaultPredictInternal( mockRequestContext, requestManagerWithRestClient(FakeRestClient(mockException)), + mockPredictMultiIdRequestModelFactory, concurrentHandlerHolder, mockRequestModelBuilderProvider, mockPredictResponseMapper @@ -597,6 +646,7 @@ class DefaultPredictInternalTest : AnnotationSpec() { predictInternal = DefaultPredictInternal( mockRequestContext, requestManagerWithRestClient(FakeRestClient(mockException)), + mockPredictMultiIdRequestModelFactory, concurrentHandlerHolder, mockRequestModelBuilderProvider, mockPredictResponseMapper diff --git a/predict/src/androidTest/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactoryTest.kt b/predict/src/androidTest/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactoryTest.kt new file mode 100644 index 000000000..b59bb3afe --- /dev/null +++ b/predict/src/androidTest/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactoryTest.kt @@ -0,0 +1,90 @@ +package com.emarsys.predict.request + +import com.emarsys.core.endpoint.ServiceEndpointProvider +import com.emarsys.core.provider.timestamp.TimestampProvider +import com.emarsys.core.provider.uuid.UUIDProvider +import com.emarsys.core.request.model.RequestMethod +import com.emarsys.core.request.model.RequestModel +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock + + +class PredictMultiIdRequestModelFactoryTest { + + private companion object { + const val TIMESTAMP = 123456789L + const val REQUEST_ID = "request_id" + const val CLIENT_HOST = "https://me-client.eservice.emarsys.net" + const val CONTACT_FIELD_ID = 3 + const val CONTACT_FIELD_VALUE = "contactFieldValue" + } + + private lateinit var mockTimestampProvider: TimestampProvider + private lateinit var mockUUIDProvider: UUIDProvider + private lateinit var testPredictRequestContext: PredictRequestContext + private lateinit var mockClientServiceProvider: ServiceEndpointProvider + + private lateinit var factory: PredictMultiIdRequestModelFactory + + @Before + fun setup() { + mockTimestampProvider = mock { + on { provideTimestamp() } doReturn TIMESTAMP + } + + mockUUIDProvider = mock { + on { provideId() } doReturn REQUEST_ID + } + + testPredictRequestContext = PredictRequestContext( + "merchant_id", + mock(), + mockTimestampProvider, + mockUUIDProvider, + mock() + ) + mockClientServiceProvider = mock { + on { provideEndpointHost() } doReturn CLIENT_HOST + } + + factory = + PredictMultiIdRequestModelFactory(testPredictRequestContext, mockClientServiceProvider) + } + + @Test + fun testCreateSetContactRequestModel_whenMerchantId_isPresentOnContext() { + val expected = RequestModel( + "https://me-client.eservice.emarsys.net/v3/contact-token", + RequestMethod.POST, + mapOf( + "contactFieldId" to CONTACT_FIELD_ID, + "contactFieldValue" to CONTACT_FIELD_VALUE + ), + mapOf(), + TIMESTAMP, + Long.MAX_VALUE, + REQUEST_ID + ) + + val requestModel = + factory.createSetContactRequestModel(CONTACT_FIELD_ID, CONTACT_FIELD_VALUE) + + requestModel shouldBe expected + } + + @Test + fun testCreateSetContactRequestModel_whenMerchantId_isMissingFromContext_throws_IllegalArgumentException() { + testPredictRequestContext.merchantId = null + + shouldThrow { + factory.createSetContactRequestModel( + CONTACT_FIELD_ID, + CONTACT_FIELD_VALUE + ) + } + } +} \ 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 ac3eb72a9..6e45a4b7a 100644 --- a/predict/src/main/java/com/emarsys/predict/DefaultPredictInternal.kt +++ b/predict/src/main/java/com/emarsys/predict/DefaultPredictInternal.kt @@ -2,6 +2,7 @@ package com.emarsys.predict import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.api.ResponseErrorException +import com.emarsys.core.api.result.CompletionListener import com.emarsys.core.api.result.ResultListener import com.emarsys.core.api.result.Try import com.emarsys.core.api.result.Try.Companion.failure @@ -21,12 +22,14 @@ import com.emarsys.predict.api.model.Product import com.emarsys.predict.api.model.RecommendationFilter import com.emarsys.predict.model.LastTrackedItemContainer import com.emarsys.predict.provider.PredictRequestModelBuilderProvider +import com.emarsys.predict.request.PredictMultiIdRequestModelFactory import com.emarsys.predict.request.PredictRequestContext import com.emarsys.predict.util.CartItemUtils class DefaultPredictInternal( requestContext: PredictRequestContext, private val requestManager: RequestManager, + private val predictMultiIdRequestModelFactory: PredictMultiIdRequestModelFactory, private val concurrentHandlerHolder: ConcurrentHandlerHolder, private val requestModelBuilderProvider: PredictRequestModelBuilderProvider, private val responseMapper: PredictResponseMapper, @@ -50,10 +53,21 @@ class DefaultPredictInternal( private val timestampProvider: TimestampProvider = requestContext.timestampProvider private val keyValueStore: KeyValueStore = requestContext.keyValueStore - - override fun setContact(contactFieldId: Int, contactFieldValue: String) { - keyValueStore.putString(CONTACT_FIELD_VALUE_KEY, contactFieldValue) - keyValueStore.putInt(CONTACT_FIELD_ID_KEY, contactFieldId) + override fun setContact( + contactFieldId: Int, + contactFieldValue: String, + completionListener: CompletionListener? + ) { + try { + val requestModel = + predictMultiIdRequestModelFactory.createSetContactRequestModel( + contactFieldId, + contactFieldValue + ) + requestManager.submit(requestModel, completionListener) + } catch (e: Exception) { + completionListener?.onCompleted(e) + } } override fun clearContact() { diff --git a/predict/src/main/java/com/emarsys/predict/LoggingPredictInternal.kt b/predict/src/main/java/com/emarsys/predict/LoggingPredictInternal.kt index 54dab4c02..4d622acf6 100644 --- a/predict/src/main/java/com/emarsys/predict/LoggingPredictInternal.kt +++ b/predict/src/main/java/com/emarsys/predict/LoggingPredictInternal.kt @@ -1,5 +1,6 @@ package com.emarsys.predict +import com.emarsys.core.api.result.CompletionListener import com.emarsys.core.api.result.ResultListener import com.emarsys.core.api.result.Try import com.emarsys.core.util.SystemUtils @@ -11,10 +12,15 @@ import com.emarsys.predict.api.model.Product import com.emarsys.predict.api.model.RecommendationFilter class LoggingPredictInternal(private val klass: Class<*>) : PredictInternal { - override fun setContact(contactFieldId: Int, contactFieldValue: String) { + override fun setContact( + contactFieldId: Int, + contactFieldValue: String, + completionListener: CompletionListener? + ) { val parameters: Map = mapOf( - "contact_field_value" to contactFieldValue, - "contact_field_id" to contactFieldId + "contact_field_value" to contactFieldValue, + "contact_field_id" to contactFieldId, + "completion_listener" to (completionListener != null) ) val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, parameters)) @@ -27,7 +33,7 @@ class LoggingPredictInternal(private val klass: Class<*>) : PredictInternal { override fun trackCart(items: List): String { val parameters: Map = mapOf( - "items" to items.toString() + "items" to items.toString() ) val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, parameters)) @@ -36,8 +42,8 @@ class LoggingPredictInternal(private val klass: Class<*>) : PredictInternal { override fun trackPurchase(orderId: String, items: List): String { val parameters: Map = mapOf( - "order_id" to orderId, - "items" to items.toString() + "order_id" to orderId, + "items" to items.toString() ) val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, parameters)) @@ -46,7 +52,7 @@ class LoggingPredictInternal(private val klass: Class<*>) : PredictInternal { override fun trackItemView(itemId: String): String { val parameters: Map = mapOf( - "item_id" to itemId + "item_id" to itemId ) val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, parameters)) @@ -55,7 +61,7 @@ class LoggingPredictInternal(private val klass: Class<*>) : PredictInternal { override fun trackCategoryView(categoryPath: String): String { val parameters: Map = mapOf( - "category_path" to categoryPath + "category_path" to categoryPath ) val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, parameters)) @@ -64,7 +70,7 @@ class LoggingPredictInternal(private val klass: Class<*>) : PredictInternal { override fun trackSearchTerm(searchTerm: String): String { val parameters: Map = mapOf( - "search_term" to searchTerm + "search_term" to searchTerm ) val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, parameters)) @@ -73,20 +79,26 @@ class LoggingPredictInternal(private val klass: Class<*>) : PredictInternal { override fun trackTag(tag: String, attributes: Map?) { val parameters: Map = mapOf( - "tag" to tag, - "attributes" to attributes.toString() + "tag" to tag, + "attributes" to attributes.toString() ) val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, parameters)) } - override fun recommendProducts(recommendationLogic: Logic, limit: Int?, recommendationFilters: List?, availabilityZone: String?, resultListener: ResultListener>>) { + override fun recommendProducts( + recommendationLogic: Logic, + limit: Int?, + recommendationFilters: List?, + availabilityZone: String?, + resultListener: ResultListener>> + ) { val parameters: Map = mapOf( - "recommendation_logic" to recommendationLogic.toString(), - "result_listener" to true, - "limit" to limit, - "recommendation_filter" to recommendationFilters?.toTypedArray()?.contentToString(), - "availabilityZone" to availabilityZone + "recommendation_logic" to recommendationLogic.toString(), + "result_listener" to true, + "limit" to limit, + "recommendation_filter" to recommendationFilters?.toTypedArray()?.contentToString(), + "availabilityZone" to availabilityZone ) val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, parameters)) @@ -94,7 +106,7 @@ class LoggingPredictInternal(private val klass: Class<*>) : PredictInternal { override fun trackRecommendationClick(product: Product): String { val parameters: Map = mapOf( - "product" to product.toString() + "product" to product.toString() ) val callerMethodName = SystemUtils.getCallerMethodName() debug(MethodNotAllowed(klass, callerMethodName, parameters)) diff --git a/predict/src/main/java/com/emarsys/predict/PredictInternal.kt b/predict/src/main/java/com/emarsys/predict/PredictInternal.kt index 6e1ae797f..ee7aa0eb9 100644 --- a/predict/src/main/java/com/emarsys/predict/PredictInternal.kt +++ b/predict/src/main/java/com/emarsys/predict/PredictInternal.kt @@ -1,5 +1,6 @@ package com.emarsys.predict +import com.emarsys.core.api.result.CompletionListener import com.emarsys.core.api.result.ResultListener import com.emarsys.core.api.result.Try import com.emarsys.predict.api.model.CartItem @@ -8,7 +9,7 @@ import com.emarsys.predict.api.model.Product import com.emarsys.predict.api.model.RecommendationFilter interface PredictInternal { - fun setContact(contactFieldId: Int, contactFieldValue: String) + fun setContact(contactFieldId: Int, contactFieldValue: String, 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/endpoint/Endpoint.java b/predict/src/main/java/com/emarsys/predict/endpoint/Endpoint.java deleted file mode 100644 index c9595ca19..000000000 --- a/predict/src/main/java/com/emarsys/predict/endpoint/Endpoint.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.emarsys.predict.endpoint; - -public class Endpoint { - public static final String PREDICT_BASE_URL = "https://recommender.scarabresearch.com/merchants"; - -} diff --git a/predict/src/main/java/com/emarsys/predict/endpoint/Endpoint.kt b/predict/src/main/java/com/emarsys/predict/endpoint/Endpoint.kt new file mode 100644 index 000000000..19a0d7671 --- /dev/null +++ b/predict/src/main/java/com/emarsys/predict/endpoint/Endpoint.kt @@ -0,0 +1,9 @@ +package com.emarsys.predict.endpoint + +object Endpoint { + + const val PREDICT_BASE_URL = "https://recommender.scarabresearch.com/merchants" + + const val CLIENT_MULTI_ID_BASE = "/v3" + +} diff --git a/predict/src/main/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactory.kt b/predict/src/main/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactory.kt new file mode 100644 index 000000000..f02700a5d --- /dev/null +++ b/predict/src/main/java/com/emarsys/predict/request/PredictMultiIdRequestModelFactory.kt @@ -0,0 +1,36 @@ +package com.emarsys.predict.request + +import com.emarsys.core.Mockable +import com.emarsys.core.endpoint.ServiceEndpointProvider +import com.emarsys.core.request.model.RequestMethod +import com.emarsys.core.request.model.RequestModel +import com.emarsys.predict.endpoint.Endpoint + +@Mockable +class PredictMultiIdRequestModelFactory( + private val requestContext: PredictRequestContext, + private val clientServiceEndpointProvider: ServiceEndpointProvider, +) { + + fun createSetContactRequestModel(contactFieldId: Int, contactFieldValue: String): RequestModel { + validateMerchantId() + return RequestModel.Builder(requestContext.timestampProvider, requestContext.uuidProvider) + .url( + "${clientServiceEndpointProvider.provideEndpointHost()}${Endpoint.CLIENT_MULTI_ID_BASE}/contact-token" + ) + .method(RequestMethod.POST) + .payload( + mapOf( + "contactFieldId" to contactFieldId, + "contactFieldValue" to contactFieldValue + ) + ) + .build() + } + + private fun validateMerchantId() { + if (requestContext.merchantId.isNullOrBlank()) { + throw IllegalArgumentException("Merchant Id must not be null!") + } + } +} \ No newline at end of file