From 2baad0da3ae6b452be60f649c85c7ebc2a9e388b Mon Sep 17 00:00:00 2001 From: Andras Sarro Date: Thu, 6 Jan 2022 13:55:08 +0100 Subject: [PATCH 01/23] feat(request-task): migrate to coroutines SUITEDEV-29296 Co-authored-by: davidSchuppa <32750715+davidSchuppa@users.noreply.github.com> Co-authored-by: LasOri <24588073+LasOri@users.noreply.github.com> Co-authored-by: megamegax --- .../emarsys/core/fake/FakeRequestTask.java | 39 ++--- .../com/emarsys/core/fake/FakeRestClient.java | 15 +- .../core/request/RequestManagerOfflineTest.kt | 13 -- .../core/request/RequestManagerTest.kt | 4 +- .../emarsys/core/request/RequestTaskTest.kt | 139 +----------------- .../emarsys/core/request/RestClientTest.kt | 22 +++ .../emarsys/core/worker/DefaultWorkerTest.kt | 133 ++++++++++++----- .../com/emarsys/core/request/RequestTask.kt | 17 +-- .../emarsys/core/worker/DefaultWorker.java | 3 +- .../MobileEngageRefreshTokenInternalTest.kt | 68 +++++---- 10 files changed, 198 insertions(+), 255 deletions(-) diff --git a/core/src/androidTest/java/com/emarsys/core/fake/FakeRequestTask.java b/core/src/androidTest/java/com/emarsys/core/fake/FakeRequestTask.java index 97d4a357a..13b9e482c 100644 --- a/core/src/androidTest/java/com/emarsys/core/fake/FakeRequestTask.java +++ b/core/src/androidTest/java/com/emarsys/core/fake/FakeRequestTask.java @@ -1,59 +1,42 @@ package com.emarsys.core.fake; -import com.emarsys.core.CoreCompletionHandler; -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; +import static org.mockito.Mockito.mock; +import com.emarsys.core.api.result.Try; import com.emarsys.core.connection.ConnectionProvider; import com.emarsys.core.provider.timestamp.TimestampProvider; import com.emarsys.core.request.RequestTask; import com.emarsys.core.request.model.RequestModel; -import com.emarsys.core.response.ResponseHandlersProcessor; import com.emarsys.core.response.ResponseModel; - import java.util.HashMap; import java.util.List; -import static org.mockito.Mockito.mock; - public class FakeRequestTask extends RequestTask { final Object fakeResult; final RequestModel requestModel; - final CoreCompletionHandler handler; @SuppressWarnings("unchecked") - public FakeRequestTask(RequestModel requestModel, CoreCompletionHandler handler, Object fakeResult) { + public FakeRequestTask(RequestModel requestModel, Object fakeResult) { super(requestModel, - handler, mock(ConnectionProvider.class), - mock(TimestampProvider.class), - mock(ResponseHandlersProcessor.class), - mock(List.class), - new CoreSdkHandlerProvider().provideHandler()); + mock(TimestampProvider.class) + ); this.fakeResult = fakeResult; this.requestModel = requestModel; - this.handler = handler; } - public FakeRequestTask(RequestModel requestModel, CoreCompletionHandler handler) { - this(requestModel, handler, Integer.MIN_VALUE); - } + public Try execute() { + Try result = null; - @Override - public Void doInBackground(Void... params) { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } - return null; - } - @Override - protected void onPostExecute(Void result) { if (fakeResult instanceof Exception) { - handler.onError(requestModel.getId(), (Exception) fakeResult); - + result = Try.failure((Exception) fakeResult); } else if (fakeResult instanceof Integer) { int statusCode = (Integer) fakeResult; ResponseModel responseModel = new ResponseModel.Builder() @@ -64,11 +47,11 @@ protected void onPostExecute(Void result) { .build(); if (200 <= statusCode && statusCode < 400) { - handler.onSuccess(requestModel.getId(), responseModel); - + result = Try.success(responseModel); } else { - handler.onError(requestModel.getId(), responseModel); + result = new Try(responseModel, new Exception("Error")); } } + return result; } } diff --git a/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java b/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java index aae061d95..f8ca9a9dc 100644 --- a/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java +++ b/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java @@ -4,6 +4,7 @@ import android.os.Looper; import com.emarsys.core.CoreCompletionHandler; +import com.emarsys.core.api.result.Try; import com.emarsys.core.concurrency.CoreSdkHandlerProvider; import com.emarsys.core.connection.ConnectionProvider; import com.emarsys.core.provider.timestamp.TimestampProvider; @@ -11,6 +12,7 @@ import com.emarsys.core.request.RestClient; import com.emarsys.core.request.model.RequestModel; import com.emarsys.core.response.ResponseHandlersProcessor; +import com.emarsys.core.response.ResponseModel; import java.util.ArrayList; import java.util.Arrays; @@ -36,14 +38,21 @@ public FakeRestClient(Object... fakeResults) { @Override public void execute(RequestModel model, CoreCompletionHandler completionHandler) { - create(model, completionHandler).execute(); + Try result = create(model).execute(); + if (result.getErrorCause() != null && result.getResult() != null) { + completionHandler.onError(model.getId(), result.getResult()); + } else if (result.getErrorCause() != null) { + completionHandler.onError(model.getId(), (Exception) result.getErrorCause()); + } else { + completionHandler.onSuccess(model.getId(), result.getResult()); + } } - private RequestTask create(RequestModel model, CoreCompletionHandler completionHandler) { + private RequestTask create(RequestModel model) { if (fakeResults.isEmpty()) { throw new IllegalStateException("No more predefined fake responses!"); } else { - return new FakeRequestTask(model, completionHandler, fakeResults.remove(0)); + return new FakeRequestTask(model, fakeResults.remove(0)); } } } diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt index ae40bc7fa..f57f52787 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt @@ -245,19 +245,6 @@ class RequestManagerOfflineTest { coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider(requestRepository, uiHandler, coreSdkHandler) worker = DefaultWorker(requestRepository, watchDog, uiHandler, completionHandler, fakeRestClient, coreCompletionHandlerMiddlewareProvider) - manager = RequestManager( - coreSdkHandler, - requestRepository, - shardRepository, - worker, - fakeRestClient, - mock() as Registry, - completionHandler, - mockProxyProvider, - mock(), - mock() - ) - coreSdkHandler.post { requestModels.forEach(requestRepository::add) worker.run() diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt index 949f07bfb..2831e462b 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt @@ -76,7 +76,7 @@ class RequestManagerTest { @Before fun init() { deleteCoreDatabase() - val requestModelMappers: MutableList?> = ArrayList() + val requestModelMappers: MutableList> = mutableListOf() mockRequestModelMapper = mock() requestModelMappers.add(mockRequestModelMapper) whenever(mockRequestModelMapper.map(any())).thenAnswer { invocation -> @@ -101,7 +101,7 @@ class RequestManagerTest { ConnectionProvider(), mock(), mock(), - requestModelMappers, + requestModelMappers.toList(), Handler(Looper.getMainLooper()), CoreSdkHandlerProvider().provideHandler() ) diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestTaskTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestTaskTest.kt index 4f3e7e13f..c91119910 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestTaskTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestTaskTest.kt @@ -1,82 +1,44 @@ package com.emarsys.core.request -import com.emarsys.core.CoreCompletionHandler -import com.emarsys.core.Mapper -import com.emarsys.core.concurrency.CoreSdkHandlerProvider import com.emarsys.core.connection.ConnectionProvider -import com.emarsys.core.handler.CoreSdkHandler import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.request.model.RequestModel -import com.emarsys.core.response.ResponseHandlersProcessor -import com.emarsys.core.response.ResponseModel -import com.emarsys.testUtil.ReflectionTestUtils import com.emarsys.testUtil.TimeoutUtils -import io.kotlintest.shouldBe import org.junit.Assert import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule -import org.mockito.kotlin.* -import java.io.IOException +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever import java.net.URL -import java.util.* -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit import javax.net.ssl.HttpsURLConnection class RequestTaskTest { - private lateinit var mockRequestModel: RequestModel - private lateinit var mockCoreCompletionHandler: CoreCompletionHandler private lateinit var connectionProvider: ConnectionProvider private lateinit var mockTimestampProvider: TimestampProvider - private lateinit var mockResponseHandlersProcessor: ResponseHandlersProcessor - private lateinit var requestModelMappers: MutableList> - private lateinit var coreSdkHandler: CoreSdkHandler - private lateinit var fakeCoreCompletionHandler: CoreCompletionHandler @Rule @JvmField val timeout: TestRule = TimeoutUtils.timeoutRule - companion object { private const val WRONG_URL = "https://localhost/missing" - private const val URL = "https://emarsys.com" private const val TIMESTAMP_1: Long = 600 private const val TIMESTAMP_2: Long = 1600 } - @Before fun setUp() { - mockRequestModel = mock() - mockCoreCompletionHandler = mock() connectionProvider = ConnectionProvider() mockTimestampProvider = mock() - mockResponseHandlersProcessor = mock() - requestModelMappers = ArrayList() - coreSdkHandler = CoreSdkHandlerProvider().provideHandler() - fakeCoreCompletionHandler = - object : CoreCompletionHandler { - override fun onSuccess(id: String, responseModel: ResponseModel) { - Thread.currentThread().name.startsWith("CoreSDKHandlerThread") shouldBe true - } - - override fun onError(id: String, responseModel: ResponseModel) { - Thread.currentThread().name.startsWith("CoreSDKHandlerThread") shouldBe true - } - - override fun onError(id: String, cause: Exception) { - Thread.currentThread().name.startsWith("CoreSDKHandlerThread") shouldBe true - } - } - whenever(mockTimestampProvider.provideTimestamp()).thenReturn(TIMESTAMP_1, TIMESTAMP_2) } @Test - fun testDoInBackground_shouldBeResilientToRuntimeExceptions() { + fun testExecute_shouldBeResilientToRuntimeExceptions() { val requestModel: RequestModel = mock { on { url } doReturn URL(WRONG_URL) } @@ -97,97 +59,6 @@ class RequestTaskTest { } } - @Test - fun testDoInBackground_mappersHaveBeenCalled() { - connectionProvider = mock() - val requestModel: RequestModel = mock { - on { url } doReturn URL(URL) - } - val connection: HttpsURLConnection = mock() - val expectedRequestModel1: RequestModel = mock() - val expectedRequestModel2: RequestModel = mock() - val mapper1: Mapper = mock { - on { map(requestModel) } doReturn expectedRequestModel1 - } - val mapper2: Mapper = mock { - on { map(expectedRequestModel1) } doReturn expectedRequestModel2 - } - requestModelMappers.add(mapper1) - requestModelMappers.add(mapper2) - whenever(connectionProvider.provideConnection(expectedRequestModel2)).thenReturn(connection) - - val requestTask = createRequestTask(requestModel) - - requestTask.doInBackground() - verify(mapper1).map(requestModel) - verify(mapper2).map(expectedRequestModel1) - verify(connectionProvider).provideConnection(expectedRequestModel2) - } - - @Test - fun testOnPostExecute_shouldRunOnCoreSdkThread_whenSuccess() { - val latch = CountDownLatch(1) - val mockResponseModel: ResponseModel = mock { - on { statusCode } doReturn 200 - } - - val requestTask = createRequestTask() - - ReflectionTestUtils.setInstanceField(requestTask, "responseModel", mockResponseModel) - ReflectionTestUtils.invokeInstanceMethod( - requestTask, - "onPostExecute", - Pair(Void::class.java, null) - ) - coreSdkHandler.post { - latch.countDown() - } - - latch.await(2, TimeUnit.SECONDS) - } - - @Test - fun testOnPostExecute_shouldRunOnCoreSdkThread_whenError() { - val latch = CountDownLatch(1) - val mockResponseModel: ResponseModel = mock() - - val requestTask = createRequestTask() - - ReflectionTestUtils.setInstanceField(requestTask, "responseModel", mockResponseModel) - ReflectionTestUtils.invokeInstanceMethod( - requestTask, - "onPostExecute", - Pair(Void::class.java, null) - ) - coreSdkHandler.post { - latch.countDown() - } - - latch.await(2, TimeUnit.SECONDS) - } - - @Test - fun testOnPostExecute_shouldRunOnCoreSdkThread_whenException() { - val latch = CountDownLatch(1) - val mockResponseModel: ResponseModel = mock() - val mockException: Exception = mock() - - val requestTask = createRequestTask() - - ReflectionTestUtils.setInstanceField(requestTask, "responseModel", mockResponseModel) - ReflectionTestUtils.setInstanceField(requestTask, "exception", mockException) - ReflectionTestUtils.invokeInstanceMethod( - requestTask, - "onPostExecute", - Pair(Void::class.java, null) - ) - coreSdkHandler.post { - latch.countDown() - } - - latch.await(2, TimeUnit.SECONDS) - } - private fun createRequestTask(requestModel: RequestModel = mock()): RequestTask { return RequestTask( requestModel, diff --git a/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt index 3f21944f8..8d54b5da9 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt @@ -26,7 +26,9 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.verify import java.net.UnknownHostException import java.util.concurrent.CountDownLatch @@ -136,4 +138,24 @@ class RestClientTest { handler.asRequestResult() shouldBe RequestResult.failure(model.id, UnknownHostException::class.java) } + @Test + fun testExecute_mappersHaveBeenCalled() { + connectionProvider = mock() + val requestModel: RequestModel = mock () + val expectedRequestModel1: RequestModel = mock() + val expectedRequestModel2: RequestModel = mock() + val mockRequestModelMapper1: Mapper = mock { + on { map(requestModel) } doReturn expectedRequestModel1 + } + val mockRequestModelMapper2: Mapper = mock { + on { map(expectedRequestModel1) } doReturn expectedRequestModel2 + } + + requestModelMappers = listOf(mockRequestModelMapper1, mockRequestModelMapper2) + client = RestClient(connectionProvider, mockTimestampProvider, mockResponseHandlersProcessor, requestModelMappers, uiHandler, coreSdkHandler) + + client.execute(requestModel, mock()) + verify(mockRequestModelMapper1).map(requestModel) + verify(mockRequestModelMapper2).map(expectedRequestModel1) + } } \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt b/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt index 0dd3b9705..cdc56bc10 100644 --- a/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt @@ -60,55 +60,100 @@ class DefaultWorkerTest { restClient = mock() uiHandler = Handler(Looper.getMainLooper()) mockProxyProvider = mock() - worker = DefaultWorker(requestRepository, watchDogMock, uiHandler, mockCoreCompletionHandler, restClient, mockProxyProvider) + worker = DefaultWorker( + requestRepository, + watchDogMock, + uiHandler, + mockCoreCompletionHandler, + restClient, + mockProxyProvider + ) whenever(mockProxyProvider.provideProxy(any(), any())).thenReturn(mock()) now = System.currentTimeMillis() expiredModel1 = RequestModel( - URL, - RequestMethod.GET, - HashMap(), - HashMap(), - now - 500, 300, - "id1") + URL, + RequestMethod.GET, + HashMap(), + HashMap(), + now - 500, 300, + "id1" + ) expiredModel2 = RequestModel( - URL, - RequestMethod.GET, - HashMap(), - HashMap(), - now - 400, 150, - "id2") + URL, + RequestMethod.GET, + HashMap(), + HashMap(), + now - 400, 150, + "id2" + ) notExpiredModel = RequestModel( - URL, - RequestMethod.GET, - HashMap(), - HashMap(), - now, 60000, - "id2") + URL, + RequestMethod.GET, + HashMap(), + HashMap(), + now, 60000, + "id2" + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_queueShouldNotBeNull() { - DefaultWorker(null, mock(), uiHandler, mockCoreCompletionHandler, restClient, mockProxyProvider) + DefaultWorker( + null, + mock(), + uiHandler, + mockCoreCompletionHandler, + restClient, + mockProxyProvider + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_watchDogShouldNotBeNull() { - DefaultWorker(requestRepository, null, uiHandler, mockCoreCompletionHandler, restClient, mockProxyProvider) + DefaultWorker( + requestRepository, + null, + uiHandler, + mockCoreCompletionHandler, + restClient, + mockProxyProvider + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_uiHandlerShouldNotBeNull() { - DefaultWorker(requestRepository, mock(), null, mockCoreCompletionHandler, restClient, mockProxyProvider) + DefaultWorker( + requestRepository, + mock(), + null, + mockCoreCompletionHandler, + restClient, + mockProxyProvider + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_restClientShouldNotBeNull() { - DefaultWorker(requestRepository, mock(), uiHandler, mockCoreCompletionHandler, null, mockProxyProvider) + DefaultWorker( + requestRepository, + mock(), + uiHandler, + mockCoreCompletionHandler, + null, + mockProxyProvider + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_proxyProvider_mustNotBeNull() { - DefaultWorker(requestRepository, mock(), uiHandler, mockCoreCompletionHandler, restClient, null) + DefaultWorker( + requestRepository, + mock(), + uiHandler, + mockCoreCompletionHandler, + restClient, + null + ) } @Test @@ -124,14 +169,28 @@ class DefaultWorkerTest { @Test fun testConstructor_setRepositorySuccessfully() { - worker = DefaultWorker(requestRepository, mock(), uiHandler, mockCoreCompletionHandler, restClient, mockProxyProvider) + worker = DefaultWorker( + requestRepository, + mock(), + uiHandler, + mockCoreCompletionHandler, + restClient, + mockProxyProvider + ) Assert.assertEquals(requestRepository, worker.requestRepository) } @Test fun testConstructor_setWatchDogSuccessfully() { val watchDog: ConnectionWatchDog = mock() - worker = DefaultWorker(requestRepository, watchDog, uiHandler, mockCoreCompletionHandler, restClient, mockProxyProvider) + worker = DefaultWorker( + requestRepository, + watchDog, + uiHandler, + mockCoreCompletionHandler, + restClient, + mockProxyProvider + ) Assert.assertEquals(watchDog, worker.connectionWatchDog) } @@ -177,20 +236,22 @@ class DefaultWorkerTest { fun testRun_queueIsNotEmptyThenSendRequestIsCalled() { worker = spy(worker) val expectedModel = createRequestModel(RequestMethod.GET) - val captor = ArgumentCaptor.forClass(RequestModel::class.java) - whenever(requestRepository.query(any())).thenReturn(listOf(expectedModel)) - whenever(requestRepository.isEmpty()).thenReturn(false) - worker.run() - verify(worker.restClient).execute(captor.capture(), any()) - val returnedModel = captor.value - Assert.assertEquals(expectedModel, returnedModel) + argumentCaptor { + whenever(requestRepository.query(any())).thenReturn(listOf(expectedModel)) + whenever(requestRepository.isEmpty()).thenReturn(false) + worker.run() + verify(worker.restClient).execute(capture(), any()) + val returnedModel = firstValue + Assert.assertEquals(expectedModel, returnedModel) + } + } @Test fun testRun_expiration_shouldPopExpiredRequestModels() { worker = spy(worker) whenever(requestRepository.query(any())) - .thenReturn(listOf(expiredModel1), listOf(expiredModel2), listOf(notExpiredModel)) + .thenReturn(listOf(expiredModel1), listOf(expiredModel2), listOf(notExpiredModel)) whenever(requestRepository.isEmpty()).thenReturn(false, false, false, false, true) worker.run() verify(requestRepository, times(3)).query(any()) @@ -205,7 +266,7 @@ class DefaultWorkerTest { val latch = CountDownLatch(2) worker.coreCompletionHandler = spy(FakeCompletionHandler(latch)) whenever(requestRepository.query(any())) - .thenReturn(listOf(expiredModel1), listOf(expiredModel2), listOf(notExpiredModel)) + .thenReturn(listOf(expiredModel1), listOf(expiredModel2), listOf(notExpiredModel)) whenever(requestRepository.isEmpty()).thenReturn(false, false, false, false, true) worker.run() latch.await() @@ -220,7 +281,7 @@ class DefaultWorkerTest { fun testRun_expiration_whenOnlyExpiredModelsWereInQueue() { worker = spy(worker) whenever(worker.requestRepository.query(any())) - .thenReturn(listOf(expiredModel1), listOf(expiredModel2)) + .thenReturn(listOf(expiredModel1), listOf(expiredModel2)) whenever(worker.requestRepository.isEmpty()).thenReturn(false, false, false, true) worker.run() verify(worker.requestRepository, times(2)).query(any()) diff --git a/core/src/main/java/com/emarsys/core/request/RequestTask.kt b/core/src/main/java/com/emarsys/core/request/RequestTask.kt index 980c640c4..874607bf8 100644 --- a/core/src/main/java/com/emarsys/core/request/RequestTask.kt +++ b/core/src/main/java/com/emarsys/core/request/RequestTask.kt @@ -22,14 +22,12 @@ import java.io.InputStreamReader import java.nio.charset.StandardCharsets import javax.net.ssl.HttpsURLConnection -open class RequestTask( - private val requestModel: RequestModel, - private val coreCompletionHandler: CoreCompletionHandler, - private val connectionProvider: ConnectionProvider, - private val timestampProvider: TimestampProvider, - private val responseHandlersProcessor: ResponseHandlersProcessor, - private val requestModelMappers: List>, - private val coreSdkHandler: CoreSdkHandler) : AsyncTask() { +@Mockable +class RequestTask( + private val requestModel: RequestModel, + private val connectionProvider: ConnectionProvider, + private val timestampProvider: TimestampProvider +) { companion object { private const val TIMEOUT = 30000 @@ -47,7 +45,8 @@ open class RequestTask( initializeConnection(connection, updatedRequestModel) connection.connectTimeout = 20000 connection.connect() - sendBody(connection, updatedRequestModel) + + sendBody(connection, requestModel) responseModel = readResponse(connection) debug(RequestLog(responseModel!!, dbEnd, updatedRequestModel)) diff --git a/core/src/main/java/com/emarsys/core/worker/DefaultWorker.java b/core/src/main/java/com/emarsys/core/worker/DefaultWorker.java index 2a9a1bf8f..7436fe682 100644 --- a/core/src/main/java/com/emarsys/core/worker/DefaultWorker.java +++ b/core/src/main/java/com/emarsys/core/worker/DefaultWorker.java @@ -1,7 +1,6 @@ package com.emarsys.core.worker; import android.os.Handler; - import com.emarsys.core.CoreCompletionHandler; import com.emarsys.core.connection.ConnectionChangeListener; import com.emarsys.core.connection.ConnectionState; @@ -30,7 +29,7 @@ public class DefaultWorker implements ConnectionChangeListener, Worker { private boolean locked; CoreCompletionHandler coreCompletionHandler; RestClient restClient; - private Handler uiHandler; + private final Handler uiHandler; public DefaultWorker(Repository requestRepository, ConnectionWatchDog connectionWatchDog, Handler uiHandler, CoreCompletionHandler coreCompletionHandler, RestClient restClient, CompletionHandlerProxyProvider proxyProvider) { Assert.notNull(requestRepository, "RequestRepository must not be null!"); diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/MobileEngageRefreshTokenInternalTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/MobileEngageRefreshTokenInternalTest.kt index cef98104a..55410f4ae 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/MobileEngageRefreshTokenInternalTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/MobileEngageRefreshTokenInternalTest.kt @@ -1,6 +1,5 @@ package com.emarsys.mobileengage -import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.api.ResponseErrorException import com.emarsys.core.api.result.CompletionListener import com.emarsys.core.request.RestClient @@ -10,14 +9,11 @@ import com.emarsys.mobileengage.fake.FakeRestClient import com.emarsys.mobileengage.request.MobileEngageRequestModelFactory import com.emarsys.mobileengage.responsehandler.MobileEngageTokenResponseHandler import com.emarsys.testUtil.TimeoutUtils -import com.emarsys.testUtil.mockito.whenever import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito.* +import org.mockito.kotlin.* import java.util.concurrent.CountDownLatch class MobileEngageRefreshTokenInternalTest { @@ -30,6 +26,7 @@ class MobileEngageRefreshTokenInternalTest { private lateinit var mockRequestModel: RequestModel private lateinit var mockRequestModelFactory: MobileEngageRequestModelFactory private lateinit var mockResponseHandler: MobileEngageTokenResponseHandler + private lateinit var mockCompletionListener: CompletionListener @Rule @JvmField @@ -37,16 +34,21 @@ class MobileEngageRefreshTokenInternalTest { @Before fun setUp() { - mockRestClient = mock(RestClient::class.java) - mockResponseHandler = mock(MobileEngageTokenResponseHandler::class.java) - mockRequestModel = mock(RequestModel::class.java).apply { - whenever(id).thenReturn(REQUEST_ID) + mockRestClient = mock() + mockResponseHandler = mock() + mockCompletionListener = mock() + mockRequestModel = mock { + on { id } doReturn REQUEST_ID } - mockRequestModelFactory = mock(MobileEngageRequestModelFactory::class.java).apply { - whenever(createRefreshContactTokenRequest()).thenReturn(mockRequestModel) + mockRequestModelFactory = mock { + on { createRefreshContactTokenRequest() } doReturn (mockRequestModel) } - refreshTokenInternal = MobileEngageRefreshTokenInternal(mockResponseHandler, mockRestClient, mockRequestModelFactory) + refreshTokenInternal = MobileEngageRefreshTokenInternal( + mockResponseHandler, + mockRestClient, + mockRequestModelFactory + ) } @Test(expected = IllegalArgumentException::class) @@ -66,22 +68,24 @@ class MobileEngageRefreshTokenInternalTest { @Test fun testRefreshContactToken_shouldCallSubmitNow() { - val mockCompletionListener = mock(CompletionListener::class.java) refreshTokenInternal.refreshContactToken(mockCompletionListener) - verify(mockRestClient).execute(eq(mockRequestModel), any(CoreCompletionHandler::class.java)) + verify(mockRestClient).execute(eq(mockRequestModel), any()) } @Test fun testRefreshContactToken_shouldProcessResponseHandler_andCallCompletionListener_whenSuccess() { - val mockResponseModel = mock(ResponseModel::class.java).apply { - whenever(requestModel).thenReturn(mockRequestModel) + val mockResponseModel: ResponseModel = mock { + on { requestModel } doReturn (mockRequestModel) } val fakeRestClient = FakeRestClient(mockResponseModel, FakeRestClient.Mode.SUCCESS) - val mockCompletionListener = mock(CompletionListener::class.java) val latch = CountDownLatch(1) - refreshTokenInternal = MobileEngageRefreshTokenInternal(mockResponseHandler, fakeRestClient, mockRequestModelFactory) + refreshTokenInternal = MobileEngageRefreshTokenInternal( + mockResponseHandler, + fakeRestClient, + mockRequestModelFactory + ) refreshTokenInternal.refreshContactToken(mockCompletionListener) refreshTokenInternal.refreshContactToken { @@ -93,20 +97,24 @@ class MobileEngageRefreshTokenInternalTest { inOrder.verify(mockResponseHandler).processResponse(mockResponseModel) inOrder.verify(mockCompletionListener).onCompleted(null) - } @Test fun testRefreshContactToken_shouldCallCompletionListener_whenFailure() { - val mockResponseModel = mock(ResponseModel::class.java).apply { - whenever(requestModel).thenReturn(mockRequestModel) + val mockResponseModel: ResponseModel = mock { + on { requestModel } doReturn (mockRequestModel) } - val fakeRestClient = FakeRestClient(mockResponseModel, FakeRestClient.Mode.ERROR_RESPONSE_MODEL) - val mockCompletionListener = mock(CompletionListener::class.java) + val fakeRestClient = + FakeRestClient(mockResponseModel, FakeRestClient.Mode.ERROR_RESPONSE_MODEL) + val mockCompletionListener: CompletionListener = mock() val latch = CountDownLatch(1) - refreshTokenInternal = MobileEngageRefreshTokenInternal(mockResponseHandler, fakeRestClient, mockRequestModelFactory) + refreshTokenInternal = MobileEngageRefreshTokenInternal( + mockResponseHandler, + fakeRestClient, + mockRequestModelFactory + ) refreshTokenInternal.refreshContactToken(mockCompletionListener) refreshTokenInternal.refreshContactToken { @@ -114,15 +122,19 @@ class MobileEngageRefreshTokenInternalTest { } latch.await() - verify(mockCompletionListener).onCompleted(any(ResponseErrorException::class.java)) + verify(mockCompletionListener).onCompleted(any()) } @Test fun testRefreshContactToken_shouldCallCompletionListener_whenException() { val fakeRestClient = FakeRestClient(Exception()) - val mockCompletionListener = mock(CompletionListener::class.java) + val mockCompletionListener: CompletionListener = mock() val latch = CountDownLatch(1) - refreshTokenInternal = MobileEngageRefreshTokenInternal(mockResponseHandler, fakeRestClient, mockRequestModelFactory) + refreshTokenInternal = MobileEngageRefreshTokenInternal( + mockResponseHandler, + fakeRestClient, + mockRequestModelFactory + ) refreshTokenInternal.refreshContactToken(mockCompletionListener) refreshTokenInternal.refreshContactToken { @@ -130,6 +142,6 @@ class MobileEngageRefreshTokenInternalTest { } latch.await() - verify(mockCompletionListener).onCompleted(any(Exception::class.java)) + verify(mockCompletionListener).onCompleted(any()) } } \ No newline at end of file From 59b1bf29f2be595922c600f3560302c30d92a4dc Mon Sep 17 00:00:00 2001 From: Andras Sarro Date: Mon, 10 Jan 2022 14:48:34 +0100 Subject: [PATCH 02/23] feat(request-task): migrate to coroutines SUITEDEV-29296 Co-authored-by: davidSchuppa <32750715+davidSchuppa@users.noreply.github.com> Co-authored-by: LasOri <24588073+LasOri@users.noreply.github.com> Co-authored-by: megamegax --- .../emarsys/core/request/RequestTaskTest.kt | 12 +-- .../emarsys/core/request/RestClientTest.kt | 30 ------- .../com/emarsys/core/request/RequestTask.kt | 66 +++++----------- .../com/emarsys/core/request/RestClient.java | 73 ----------------- .../com/emarsys/core/request/RestClient.kt | 78 +++++++++++++++++++ 5 files changed, 101 insertions(+), 158 deletions(-) delete mode 100644 core/src/main/java/com/emarsys/core/request/RestClient.java create mode 100644 core/src/main/java/com/emarsys/core/request/RestClient.kt diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestTaskTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestTaskTest.kt index c91119910..49bd88261 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestTaskTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestTaskTest.kt @@ -53,7 +53,7 @@ class RequestTaskTest { val requestTask = createRequestTask(requestModel) try { - requestTask.doInBackground() + requestTask.execute() } catch (e: Exception) { Assert.fail("Request Task should handle exception: " + e.message) } @@ -61,13 +61,9 @@ class RequestTaskTest { private fun createRequestTask(requestModel: RequestModel = mock()): RequestTask { return RequestTask( - requestModel, - fakeCoreCompletionHandler, - connectionProvider, - mockTimestampProvider, - mockResponseHandlersProcessor, - requestModelMappers, - coreSdkHandler + requestModel, + connectionProvider, + mockTimestampProvider ) } } \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt index 8d54b5da9..c0d2cf6b9 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt @@ -71,36 +71,6 @@ class RestClientTest { latch = CountDownLatch(1) } - @Test(expected = IllegalArgumentException::class) - fun testConstructor_connectionProvider_mustNotBeNull() { - RestClient(null, mockTimestampProvider, mockResponseHandlersProcessor, requestModelMappers, uiHandler, coreSdkHandler) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_timestampProvider_mustNotBeNull() { - RestClient(connectionProvider, null, mockResponseHandlersProcessor, requestModelMappers, uiHandler, coreSdkHandler) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_responseHandlersRunner_mustNotBeNull() { - RestClient(connectionProvider, mockTimestampProvider, null, requestModelMappers, uiHandler, coreSdkHandler) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_requestModelMapper_mustNotBeNull() { - RestClient(connectionProvider, mockTimestampProvider, mockResponseHandlersProcessor, null, uiHandler, coreSdkHandler) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_uiHandler_mustNotBeNull() { - RestClient(connectionProvider, mockTimestampProvider, mockResponseHandlersProcessor, requestModelMappers, null, coreSdkHandler) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_coreSdkHandler_mustNotBeNull() { - RestClient(connectionProvider, mockTimestampProvider, mockResponseHandlersProcessor, requestModelMappers, uiHandler, null) - } - @Test fun testSendRequest_requestDoneSuccessfully() { val handler = FakeCompletionHandler(latch) diff --git a/core/src/main/java/com/emarsys/core/request/RequestTask.kt b/core/src/main/java/com/emarsys/core/request/RequestTask.kt index 874607bf8..9f0261b1a 100644 --- a/core/src/main/java/com/emarsys/core/request/RequestTask.kt +++ b/core/src/main/java/com/emarsys/core/request/RequestTask.kt @@ -1,14 +1,11 @@ package com.emarsys.core.request -import android.os.AsyncTask -import com.emarsys.core.CoreCompletionHandler -import com.emarsys.core.Mapper +import com.emarsys.core.Mockable +import com.emarsys.core.api.result.Try import com.emarsys.core.connection.ConnectionProvider -import com.emarsys.core.handler.CoreSdkHandler import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.request.model.RequestMethod import com.emarsys.core.request.model.RequestModel -import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.response.ResponseModel import com.emarsys.core.util.JsonUtils.fromMap import com.emarsys.core.util.filterNotNull @@ -33,46 +30,29 @@ class RequestTask( private const val TIMEOUT = 30000 } - private var responseModel: ResponseModel? = null - private var exception: Exception? = null - - public override fun doInBackground(vararg params: Void?): Void? { + fun execute(): Try { + var responseModel: ResponseModel? = null + var exception: Exception? = null val dbEnd = timestampProvider.provideTimestamp() var connection: HttpsURLConnection? = null try { - val updatedRequestModel = mapRequestModel(requestModel) - connection = connectionProvider.provideConnection(updatedRequestModel) - initializeConnection(connection, updatedRequestModel) + connection = connectionProvider.provideConnection(requestModel) + initializeConnection(connection, requestModel) connection.connectTimeout = 20000 connection.connect() sendBody(connection, requestModel) responseModel = readResponse(connection) - debug(RequestLog(responseModel!!, dbEnd, updatedRequestModel)) - info(RequestLog(responseModel!!, dbEnd), strict = true) + debug(RequestLog(responseModel, dbEnd, requestModel)) + info(RequestLog(responseModel, dbEnd), strict = true) } catch (e: Exception) { exception = e } finally { connection?.disconnect() } - return null - } - - override fun onPostExecute(result: Void?) { - coreSdkHandler.post { - if (exception != null) { - coreCompletionHandler.onError(requestModel.id, exception!!) - } else if (responseModel != null) { - responseHandlersProcessor.process(responseModel) - if (isStatusCodeOK(responseModel!!.statusCode)) { - coreCompletionHandler.onSuccess(requestModel.id, responseModel!!) - } else { - coreCompletionHandler.onError(requestModel.id, responseModel!!) - } - } - } + return Try(responseModel, exception) } private fun initializeConnection(connection: HttpsURLConnection, model: RequestModel) { @@ -92,7 +72,8 @@ class RequestTask( private fun sendBody(connection: HttpsURLConnection, model: RequestModel) { if (model.payload != null) { - val payload = fromMap(model.payload!!.filterNotNull()).toString().toByteArray(StandardCharsets.UTF_8) + val payload = fromMap(model.payload!!.filterNotNull()).toString() + .toByteArray(StandardCharsets.UTF_8) val writer = BufferedOutputStream(connection.outputStream) writer.write(payload) writer.close() @@ -105,18 +86,17 @@ class RequestTask( val headers = connection.headerFields val body = readBody(connection) return ResponseModel.Builder(timestampProvider) - .statusCode(statusCode) - .message(message) - .headers(headers) - .body(body) - .requestModel(requestModel) - .build() + .statusCode(statusCode) + .message(message) + .headers(headers) + .body(body) + .requestModel(requestModel) + .build() } private fun readBody(connection: HttpsURLConnection?): String { val responseCode = connection!!.responseCode - val inputStream: InputStream - inputStream = if (isStatusCodeOK(responseCode)) { + val inputStream: InputStream = if (isStatusCodeOK(responseCode)) { connection.inputStream } else { connection.errorStream @@ -134,12 +114,4 @@ class RequestTask( private fun isStatusCodeOK(responseCode: Int): Boolean { return responseCode in 200..299 } - - private fun mapRequestModel(requestModel: RequestModel): RequestModel { - var updatedRequestModel = requestModel - for (mapper in requestModelMappers) { - updatedRequestModel = mapper.map(updatedRequestModel) - } - return updatedRequestModel - } } \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/request/RestClient.java b/core/src/main/java/com/emarsys/core/request/RestClient.java deleted file mode 100644 index ff2335798..000000000 --- a/core/src/main/java/com/emarsys/core/request/RestClient.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.emarsys.core.request; - -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Looper; - -import com.emarsys.core.CoreCompletionHandler; -import com.emarsys.core.Mapper; -import com.emarsys.core.connection.ConnectionProvider; -import com.emarsys.core.handler.CoreSdkHandler; -import com.emarsys.core.provider.timestamp.TimestampProvider; -import com.emarsys.core.request.model.RequestModel; -import com.emarsys.core.response.ResponseHandlersProcessor; -import com.emarsys.core.util.Assert; - -import java.util.List; - -public class RestClient { - - private ConnectionProvider connectionProvider; - private TimestampProvider timestampProvider; - private ResponseHandlersProcessor responseHandlersProcessor; - private List> requestModelMappers; - private Handler uiHandler; - private CoreSdkHandler coreSdkHandler; - - public RestClient( - ConnectionProvider connectionProvider, - TimestampProvider timestampProvider, - ResponseHandlersProcessor responseHandlersProcessor, - List> requestModelMappers, - Handler uiHandler, - CoreSdkHandler coreSdkHandler) { - Assert.notNull(connectionProvider, "ConnectionProvider must not be null!"); - Assert.notNull(timestampProvider, "TimestampProvider must not be null!"); - Assert.notNull(responseHandlersProcessor, "ResponseHandlersProcessor must not be null!"); - Assert.notNull(requestModelMappers, "RequestModelMappers must not be null!"); - Assert.notNull(uiHandler, "UiHandler must not be null!"); - Assert.notNull(coreSdkHandler, "CoreSdkHandler must not be null!"); - - this.connectionProvider = connectionProvider; - this.timestampProvider = timestampProvider; - this.responseHandlersProcessor = responseHandlersProcessor; - this.requestModelMappers = requestModelMappers; - this.uiHandler = uiHandler; - this.coreSdkHandler = coreSdkHandler; - } - - public void execute(RequestModel model, CoreCompletionHandler completionHandler) { - Assert.notNull(model, "Model must not be null!"); - Assert.notNull(completionHandler, "CoreCompletionHandler must not be null!"); - - final RequestTask task = new RequestTask( - model, - completionHandler, - connectionProvider, - timestampProvider, - responseHandlersProcessor, - requestModelMappers, - coreSdkHandler); - - if (Looper.myLooper() == uiHandler.getLooper()) { - task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - } else { - uiHandler.post(new Runnable() { - @Override - public void run() { - task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - } - }); - } - } -} diff --git a/core/src/main/java/com/emarsys/core/request/RestClient.kt b/core/src/main/java/com/emarsys/core/request/RestClient.kt new file mode 100644 index 000000000..240feb30d --- /dev/null +++ b/core/src/main/java/com/emarsys/core/request/RestClient.kt @@ -0,0 +1,78 @@ +package com.emarsys.core.request + +import android.os.Handler +import com.emarsys.core.CoreCompletionHandler +import com.emarsys.core.Mapper +import com.emarsys.core.api.result.Try +import com.emarsys.core.connection.ConnectionProvider +import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.provider.timestamp.TimestampProvider +import com.emarsys.core.request.model.RequestModel +import com.emarsys.core.response.ResponseHandlersProcessor +import com.emarsys.core.response.ResponseModel +import kotlinx.coroutines.* +import kotlinx.coroutines.android.asCoroutineDispatcher + +open class RestClient( + private val connectionProvider: ConnectionProvider, + private val timestampProvider: TimestampProvider, + private val responseHandlersProcessor: ResponseHandlersProcessor, + private val requestModelMappers: List>, + private val uiHandler: Handler, + coreSdkHandler: CoreSdkHandler +) { + private val coreSdkHandlerDispatcher = coreSdkHandler.handler.asCoroutineDispatcher() + + private val sdkScope: CoroutineScope = CoroutineScope(Job() + coreSdkHandlerDispatcher) + private val defaultScope = CoroutineScope(Job() + Dispatchers.Default) + + open fun execute(model: RequestModel, completionHandler: CoreCompletionHandler) { + val updatedRequestModel = mapRequestModel(model) + val task = RequestTask( + updatedRequestModel, + connectionProvider, + timestampProvider + ) + + defaultScope.launch { + val responseModel = async(context = sdkScope.coroutineContext) { + task.execute() + } + + sdkScope.launch { + onPostExecute(model.id, responseModel.await(), completionHandler) + } + } + } + + private fun onPostExecute( + requestId: String, + result: Try, + completionHandler: CoreCompletionHandler + ) { + + if (result.errorCause != null) { + completionHandler.onError(requestId, result.errorCause as Exception) + } else { + val responseModel = result.result!! + responseHandlersProcessor.process(result.result) + if (isStatusCodeOK(responseModel.statusCode)) { + completionHandler.onSuccess(requestId, responseModel) + } else { + completionHandler.onError(requestId, responseModel) + } + } + } + + private fun mapRequestModel(requestModel: RequestModel): RequestModel { + var updatedRequestModel = requestModel + for (mapper in requestModelMappers) { + updatedRequestModel = mapper.map(updatedRequestModel) + } + return updatedRequestModel + } + + private fun isStatusCodeOK(responseCode: Int): Boolean { + return responseCode in 200..299 + } +} \ No newline at end of file From 3555393eb4aba2ab4b8b61483cd3c263594af77a Mon Sep 17 00:00:00 2001 From: LasOri Date: Tue, 11 Jan 2022 12:05:36 +0100 Subject: [PATCH 03/23] fix(config): Modify changeAppCode SUITEDEV-29873 Co-authored-by: davidSchuppa <32750715+davidSchuppa@users.noreply.github.com> Co-authored-by: LordAndras <49073629+LordAndras@users.noreply.github.com> Co-authored-by: megamegax --- .../config/DefaultConfigInternalTest.kt | 157 +++++++++++------- .../emarsys/config/DefaultConfigInternal.kt | 130 +++++++-------- 2 files changed, 156 insertions(+), 131 deletions(-) diff --git a/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt b/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt index f0e395bde..ec81c2897 100644 --- a/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt +++ b/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt @@ -14,6 +14,7 @@ import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.device.DeviceInfo import com.emarsys.core.feature.FeatureRegistry +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient import com.emarsys.core.request.factory.CompletionHandlerProxyProvider @@ -29,14 +30,16 @@ import com.emarsys.mobileengage.MobileEngageInternal import com.emarsys.mobileengage.MobileEngageRequestContext import com.emarsys.mobileengage.client.ClientServiceInternal import com.emarsys.mobileengage.push.PushInternal -import com.emarsys.mobileengage.push.PushTokenProvider import com.emarsys.predict.PredictInternal import com.emarsys.predict.request.PredictRequestContext import com.emarsys.testUtil.ExtensionTestUtils.tryCast import com.emarsys.testUtil.FeatureTestUtils -import com.emarsys.testUtil.ReflectionTestUtils import com.emarsys.testUtil.TimeoutUtils +import com.emarsys.testUtil.mockito.ThreadSpy import io.kotlintest.shouldBe +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import org.junit.After import org.junit.Before import org.junit.Rule @@ -67,7 +70,6 @@ class DefaultConfigInternalTest { private lateinit var mockPredictRequestContext: PredictRequestContext private lateinit var mockMobileEngageInternal: MobileEngageInternal private lateinit var mockPushInternal: PushInternal - private lateinit var mockPushTokenProvider: PushTokenProvider private lateinit var mockPredictInternal: PredictInternal private lateinit var mockDeviceInfo: DeviceInfo private lateinit var latch: CountDownLatch @@ -85,6 +87,7 @@ class DefaultConfigInternalTest { private lateinit var mockCrypto: Crypto private lateinit var mockClientServiceInternal: ClientServiceInternal private lateinit var mockCompletionListener: CompletionListener + private lateinit var mockConcurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -99,10 +102,6 @@ class DefaultConfigInternalTest { mockCompletionListener = mock() - mockPushTokenProvider = mock { - on { providePushToken() } doReturn PUSH_TOKEN - } - mockPredictRequestContext = mock() { on { merchantId } doReturn MERCHANT_ID } @@ -159,10 +158,17 @@ class DefaultConfigInternalTest { } } + val scope = CoroutineScope(Job() + Dispatchers.Default) + val mainScope = CoroutineScope(Job() + Dispatchers.Main) + + mockConcurrentHandlerHolder = mock { + on { coreScope } doReturn scope + on { uiScope } doReturn mainScope + } + configInternal = spy(DefaultConfigInternal(mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, mockRequestManager, @@ -175,7 +181,8 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal)) + mockClientServiceInternal, + mockConcurrentHandlerHolder)) } @After @@ -205,36 +212,30 @@ class DefaultConfigInternalTest { } @Test - fun testChangeApplicationCode_shouldSavePushTokenToInternal() { - + fun testChangeApplicationCode_shouldCallClearContact() { val latch = CountDownLatch(1) + whenever(mockMobileEngageRequestContext.hasContactIdentification()).thenReturn(true) + whenever(mockMobileEngageRequestContext.applicationCode).thenReturn(APPLICATION_CODE) configInternal.changeApplicationCode(OTHER_APPLICATION_CODE) { latch.countDown() } - latch.await() - val result = ReflectionTestUtils.getInstanceField(configInternal, "originalPushToken") - - result shouldBe PUSH_TOKEN - } - - @Test - fun testChangeApplicationCode_shouldCallClearContact() { - whenever(mockMobileEngageRequestContext.hasContactIdentification()).thenReturn(true) - configInternal.changeApplicationCode(OTHER_APPLICATION_CODE) { } - verify(mockMobileEngageInternal).clearContact(any()) } @Test fun testChangeApplicationCode_shouldChangeApplicationCodeAfterClearContact() { + whenever(mockPushInternal.pushToken).thenReturn(PUSH_TOKEN) + whenever(mockMobileEngageRequestContext.hasContactIdentification()).thenReturn(true) + val latch = CountDownLatch(1) configInternal.changeApplicationCode(OTHER_APPLICATION_CODE) { latch.countDown() } latch.await() + val inOrder = inOrder(mockMobileEngageInternal, mockPushInternal, mockMobileEngageRequestContext) inOrder.verify(mockPushInternal).clearPushToken(any()) inOrder.verify(mockMobileEngageInternal).clearContact(any()) @@ -244,7 +245,12 @@ class DefaultConfigInternalTest { @Test fun testChangeApplicationCode_shouldInterruptFlow_andDisableFeature_whenErrorHappenedDuringClearContact() { + whenever(mockPushInternal.pushToken).thenReturn(PUSH_TOKEN) + whenever(mockMobileEngageRequestContext.hasContactIdentification()).thenReturn(true) + FeatureRegistry.enableFeature(InnerFeature.MOBILE_ENGAGE) + FeatureRegistry.enableFeature(InnerFeature.EVENT_SERVICE_V4) + val mockMobileEngageInternal: MobileEngageInternal = mock() whenever(mockMobileEngageInternal.clearContact(any())).thenAnswer { invocation -> (invocation.getArgument(0) as CompletionListener).onCompleted(Throwable()) @@ -253,7 +259,6 @@ class DefaultConfigInternalTest { configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, mockRequestManager, @@ -266,13 +271,16 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal) + mockClientServiceInternal, + mockConcurrentHandlerHolder) val latch = CountDownLatch(1) val completionListener = CompletionListener { latch.countDown() } configInternal.changeApplicationCode(OTHER_APPLICATION_CODE, completionListener) latch.await() + + verify(mockPushInternal).pushToken verify(mockMobileEngageRequestContext).applicationCode verify(mockMobileEngageRequestContext).hasContactIdentification() verify(mockPushInternal).clearPushToken(any()) @@ -280,6 +288,7 @@ class DefaultConfigInternalTest { verifyNoMoreInteractions(mockMobileEngageInternal) verifyNoMoreInteractions(mockPushInternal) FeatureRegistry.isFeatureEnabled(InnerFeature.MOBILE_ENGAGE) shouldBe false + FeatureRegistry.isFeatureEnabled(InnerFeature.EVENT_SERVICE_V4) shouldBe false verify(mockMobileEngageRequestContext).applicationCode = null verifyNoMoreInteractions(mockMobileEngageRequestContext) } @@ -295,10 +304,11 @@ class DefaultConfigInternalTest { on { contactFieldId } doReturn CONTACT_FIELD_ID } + whenever(mockPushInternal.pushToken).thenReturn(PUSH_TOKEN) + configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, mockRequestManager, @@ -311,7 +321,8 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal) + mockClientServiceInternal, + mockConcurrentHandlerHolder) configInternal.changeApplicationCode(OTHER_APPLICATION_CODE) { latch.countDown() @@ -329,12 +340,13 @@ class DefaultConfigInternalTest { @Test fun testChangeApplicationCode_shouldInterruptFlow_andDisableFeature_whenErrorHappenedDuringSetPushToken() { FeatureRegistry.enableFeature(InnerFeature.MOBILE_ENGAGE) + FeatureRegistry.enableFeature(InnerFeature.EVENT_SERVICE_V4) val mockPushInternal: PushInternal = mock { on { setPushToken(any(), any()) } doAnswer { invocation -> - (invocation.getArgument(1) as CompletionListener).onCompleted(Throwable()) + (invocation.getArgument(1) as CompletionListener).onCompleted(Throwable("testErrorMessage")) } on { clearPushToken(any()) @@ -342,10 +354,11 @@ class DefaultConfigInternalTest { (invocation.getArgument(0) as CompletionListener).onCompleted(null) } } + whenever(mockPushInternal.pushToken).thenReturn(PUSH_TOKEN) + configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, mockRequestManager, @@ -358,22 +371,26 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal) + mockClientServiceInternal, + mockConcurrentHandlerHolder) configInternal.changeApplicationCode(OTHER_APPLICATION_CODE) { latch.countDown() } latch.await() - verify(mockMobileEngageInternal).clearContact(any()) verify(mockPushInternal).setPushToken(eq(PUSH_TOKEN), any()) verifyNoMoreInteractions(mockMobileEngageInternal) FeatureRegistry.isFeatureEnabled(InnerFeature.MOBILE_ENGAGE) shouldBe false + FeatureRegistry.isFeatureEnabled(InnerFeature.EVENT_SERVICE_V4) shouldBe false verify(mockMobileEngageRequestContext).applicationCode = null } @Test fun testChangeApplicationCode_shouldWorkWithoutCompletionListener() { + whenever(mockPushInternal.pushToken).thenReturn(PUSH_TOKEN) + whenever(mockMobileEngageRequestContext.hasContactIdentification()).thenReturn(true) + configInternal.changeApplicationCode(OTHER_APPLICATION_CODE, null) val inOrder = inOrder(mockMobileEngageInternal, mockPushInternal, mockMobileEngageRequestContext) @@ -410,6 +427,7 @@ class DefaultConfigInternalTest { @Test fun testChangeApplicationCode_whenClearPushToken_returnsWithError_callHandleError() { + whenever(mockPushInternal.pushToken).thenReturn(PUSH_TOKEN) whenever(mockPushInternal.clearPushToken(any())).thenAnswer { invocation -> (invocation.getArgument(0) as CompletionListener).onCompleted(Throwable()) } @@ -422,12 +440,12 @@ class DefaultConfigInternalTest { verifyNoInteractions(mockMobileEngageInternal) FeatureRegistry.isFeatureEnabled(InnerFeature.MOBILE_ENGAGE) shouldBe false + FeatureRegistry.isFeatureEnabled(InnerFeature.EVENT_SERVICE_V4) shouldBe false verify(mockMobileEngageRequestContext).applicationCode = null } @Test fun testChangeApplicationCode_whenClearPushToken_butPushTokenHasNotBeenSetPreviously_callOnSuccess() { - whenever(mockPushTokenProvider.providePushToken()).thenReturn(null) whenever(mockPushInternal.clearPushToken(any())).thenAnswer { invocation -> (invocation.getArgument(0) as CompletionListener).onCompleted(Throwable()) } @@ -445,40 +463,50 @@ class DefaultConfigInternalTest { @Test fun testChangeApplicationCode_shouldOnlyLogout_whenApplicationCodeIsNull() { + whenever(mockPushInternal.pushToken).thenReturn("testPushToken") + whenever(mockMobileEngageRequestContext.hasContactIdentification()).thenReturn(true) + whenever(mockMobileEngageRequestContext.applicationCode).thenReturn(APPLICATION_CODE) + val latch = CountDownLatch(1) val completionListener = CompletionListener { latch.countDown() } + configInternal.changeApplicationCode(null, completionListener) + latch.await() + + verify(mockPushInternal).pushToken verify(mockMobileEngageRequestContext).applicationCode verify(mockMobileEngageRequestContext).hasContactIdentification() + verify(mockMobileEngageRequestContext).applicationCode = null verify(mockPushInternal).clearPushToken(any()) verify(mockMobileEngageInternal).clearContact(any()) verifyNoMoreInteractions(mockMobileEngageRequestContext) - verifyNoMoreInteractions(mockPushInternal) verifyNoMoreInteractions(mockMobileEngageInternal) FeatureRegistry.isFeatureEnabled(InnerFeature.MOBILE_ENGAGE) shouldBe false } @Test fun testChangeApplicationCode_shouldNotSendPushToken_whenPushTokenIsNull() { - whenever(mockPushTokenProvider.providePushToken()).thenReturn(null) + whenever(mockMobileEngageRequestContext.hasContactIdentification()).thenReturn(true) val latch = CountDownLatch(1) configInternal.changeApplicationCode(OTHER_APPLICATION_CODE) { latch.countDown() } latch.await() + val inOrder = inOrder(mockMobileEngageInternal, mockPushInternal, mockMobileEngageRequestContext, mockClientServiceInternal) + inOrder.verify(mockPushInternal).pushToken inOrder.verify(mockMobileEngageInternal).clearContact(any()) inOrder.verify(mockMobileEngageRequestContext).applicationCode = OTHER_APPLICATION_CODE inOrder.verify(mockClientServiceInternal).trackDeviceInfo(any()) - verifyNoInteractions(mockPushInternal) + verifyNoMoreInteractions(mockPushInternal) } @Test - fun testChangeApplicationCode_shouldClearContactAfter_ifContactWasNotSet() { + fun testChangeApplicationCode_shouldSetAnonymContact_whenThereWasNoContactIdentification() { val latch = CountDownLatch(1) whenever(mockMobileEngageRequestContext.hasContactIdentification()).doReturn(false) configInternal.changeApplicationCode(OTHER_APPLICATION_CODE) { @@ -486,7 +514,6 @@ class DefaultConfigInternalTest { } latch.await() val inOrder = inOrder(mockMobileEngageInternal, mockMobileEngageRequestContext, mockClientServiceInternal) - inOrder.verify(mockMobileEngageInternal).clearContact(any()) inOrder.verify(mockMobileEngageRequestContext).applicationCode = OTHER_APPLICATION_CODE inOrder.verify(mockClientServiceInternal).trackDeviceInfo(any()) inOrder.verify(mockMobileEngageInternal).clearContact(any()) @@ -610,7 +637,6 @@ class DefaultConfigInternalTest { val configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, requestManagerWithRestClient(FakeRestClient(mockResponseModel, FakeRestClient.Mode.SUCCESS)), @@ -623,7 +649,8 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal) + mockClientServiceInternal, + mockConcurrentHandlerHolder) configInternal.fetchRemoteConfig(resultListener) @@ -639,7 +666,6 @@ class DefaultConfigInternalTest { mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, requestManagerWithRestClient(FakeRestClient(mockResponseModel, FakeRestClient.Mode.ERROR_RESPONSE_MODEL)), @@ -652,7 +678,8 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal) + mockClientServiceInternal, + mockConcurrentHandlerHolder) val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) (configInternal as DefaultConfigInternal).fetchRemoteConfig(resultListener) @@ -670,7 +697,6 @@ class DefaultConfigInternalTest { mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, requestManagerWithRestClient(FakeRestClient(mockException)), @@ -683,7 +709,8 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal) + mockClientServiceInternal, + mockConcurrentHandlerHolder) val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) @@ -703,7 +730,6 @@ class DefaultConfigInternalTest { val configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, requestManagerWithRestClient(FakeRestClient(mockResponseModel, FakeRestClient.Mode.SUCCESS)), @@ -716,7 +742,8 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal) + mockClientServiceInternal, + mockConcurrentHandlerHolder) configInternal.fetchRemoteConfigSignature(resultListener) @@ -732,7 +759,6 @@ class DefaultConfigInternalTest { mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, requestManagerWithRestClient(FakeRestClient(mockResponseModel, FakeRestClient.Mode.ERROR_RESPONSE_MODEL)), @@ -745,7 +771,8 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal) + mockClientServiceInternal, + mockConcurrentHandlerHolder) val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) (configInternal as DefaultConfigInternal).fetchRemoteConfigSignature(resultListener) @@ -763,7 +790,6 @@ class DefaultConfigInternalTest { mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, - mockPushTokenProvider, mockPredictRequestContext, mockDeviceInfo, requestManagerWithRestClient(FakeRestClient(mockException)), @@ -776,7 +802,8 @@ class DefaultConfigInternalTest { mockMessageInboxServiceStorage, mockLogLevelStorage, mockCrypto, - mockClientServiceInternal) + mockClientServiceInternal, + mockConcurrentHandlerHolder) val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) @@ -971,6 +998,20 @@ class DefaultConfigInternalTest { verify(configInternal as DefaultConfigInternal).resetRemoteConfig() } + @Test + fun testChangeApplicationCode_shouldCallCompletionListener_onMainThread() { + val latch = CountDownLatch(1) + val threadSpy = ThreadSpy() + + configInternal.changeApplicationCode(null) { + threadSpy.call() + latch.countDown() + } + + latch.await() + threadSpy.verifyCalledOnMainThread() + } + @Suppress("UNCHECKED_CAST") fun requestManagerWithRestClient(restClient: RestClient): RequestManager { val mockScopeDelegatorCompletionHandlerProvider: ScopeDelegatorCompletionHandlerProvider = mock { @@ -991,16 +1032,16 @@ class DefaultConfigInternalTest { } return RequestManager( - mock(), - mock() as Repository, - mock() as Repository, - mock(), - restClient, - mock() as Registry, - mock(), - mockProvider, - mockScopeDelegatorCompletionHandlerProvider, - mock() + mock(), + mock() as Repository, + mock() as Repository, + mock(), + restClient, + mock() as Registry, + mock(), + mockProvider, + mockScopeDelegatorCompletionHandlerProvider, + mock() ) } } \ No newline at end of file diff --git a/emarsys/src/main/java/com/emarsys/config/DefaultConfigInternal.kt b/emarsys/src/main/java/com/emarsys/config/DefaultConfigInternal.kt index 81e53fc78..71e604d4f 100644 --- a/emarsys/src/main/java/com/emarsys/config/DefaultConfigInternal.kt +++ b/emarsys/src/main/java/com/emarsys/config/DefaultConfigInternal.kt @@ -13,6 +13,7 @@ import com.emarsys.core.api.result.Try import com.emarsys.core.crypto.Crypto import com.emarsys.core.device.DeviceInfo import com.emarsys.core.feature.FeatureRegistry +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.request.RequestManager import com.emarsys.core.response.ResponseModel import com.emarsys.core.storage.Storage @@ -20,14 +21,15 @@ import com.emarsys.mobileengage.MobileEngageInternal import com.emarsys.mobileengage.MobileEngageRequestContext import com.emarsys.mobileengage.client.ClientServiceInternal import com.emarsys.mobileengage.push.PushInternal -import com.emarsys.mobileengage.push.PushTokenProvider import com.emarsys.predict.request.PredictRequestContext +import kotlinx.coroutines.launch +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine @Mockable class DefaultConfigInternal(private val mobileEngageRequestContext: MobileEngageRequestContext, private val mobileEngageInternal: MobileEngageInternal, private val pushInternal: PushInternal, - private val pushTokenProvider: PushTokenProvider, private val predictRequestContext: PredictRequestContext, private val deviceInfo: DeviceInfo, private val requestManager: RequestManager, @@ -40,7 +42,8 @@ class DefaultConfigInternal(private val mobileEngageRequestContext: MobileEngage private val messageInboxServiceStorage: Storage, private val logLevelStorage: Storage, private val crypto: Crypto, - private val clientServiceInternal: ClientServiceInternal) : ConfigInternal { + private val clientServiceInternal: ClientServiceInternal, + private val concurrentHandlerHolder: ConcurrentHandlerHolder) : ConfigInternal { override val applicationCode: String? get() = mobileEngageRequestContext.applicationCode @@ -66,101 +69,82 @@ class DefaultConfigInternal(private val mobileEngageRequestContext: MobileEngage override val sdkVersion: String get() = deviceInfo.sdkVersion - private var originalPushToken: String? = null - - private var hasContactIdentification: Boolean = false override fun changeApplicationCode(applicationCode: String?, completionListener: CompletionListener?) { - originalPushToken = pushTokenProvider.providePushToken() - hasContactIdentification = mobileEngageRequestContext.hasContactIdentification() - - if (mobileEngageRequestContext.applicationCode == null) { - handleApplicationCodeChange(applicationCode, completionListener) - } else { - clearUpPushTokenAndContact(completionListener) { - handleApplicationCodeChange(applicationCode, completionListener) + val pushToken: String? = pushInternal.pushToken + val hasContactIdentification = mobileEngageRequestContext.hasContactIdentification() + var throwable: Throwable? = null + concurrentHandlerHolder.coreScope?.launch { //TODO: remove question mark + if (pushToken != null) { + throwable = clearPushToken() } - } - } - - private fun clearUpPushTokenAndContact(completionListener: CompletionListener?, onSuccess: () -> Unit) { - clearPushToken(completionListener) { - clearContact(completionListener) { - onSuccess() + if ((throwable == null) && (mobileEngageRequestContext.applicationCode != null) && hasContactIdentification) { + throwable = clearContact() + } + if (throwable == null) { + handleAppCodeChange(applicationCode) + if (applicationCode != null) { + throwable = sendDeviceInfo() + if (pushToken != null) { + throwable = sendPushToken(pushToken) + } + if (throwable == null && !hasContactIdentification) { + throwable = clearContact() + } + } + } + if (throwable != null) { + handleAppCodeChange(null) + } + concurrentHandlerHolder.uiScope?.launch { //TODO: remove question mark + completionListener?.onCompleted(throwable) } } } - private fun clearContactIfWasNotIdentified(completionListener: CompletionListener?, onSuccess: () -> Unit) { - if (hasContactIdentification) { - onSuccess() - } else { - clearContact(completionListener, onSuccess) - } + private fun handleAppCodeChange(applicationCode: String?) { + mobileEngageRequestContext.applicationCode = applicationCode + if (applicationCode != null) { + FeatureRegistry.enableFeature(InnerFeature.MOBILE_ENGAGE) + FeatureRegistry.enableFeature(InnerFeature.EVENT_SERVICE_V4) + } else { + FeatureRegistry.disableFeature(InnerFeature.MOBILE_ENGAGE) + FeatureRegistry.disableFeature(InnerFeature.EVENT_SERVICE_V4) + } } - private fun clearContact(completionListener: CompletionListener?, onSuccess: () -> Unit) { - mobileEngageInternal.clearContact { throwable -> - if (throwable == null) { - onSuccess() - } else { - handleError(throwable, completionListener) + private suspend fun clearPushToken(): Throwable? { + return suspendCoroutine { continuation -> + pushInternal.clearPushToken { + continuation.resume(it) } } } - private fun clearPushToken(completionListener: CompletionListener?, onSuccess: () -> Unit) { - if (originalPushToken != null) { - pushInternal.clearPushToken { throwable -> - if (throwable == null) { - onSuccess() - } else { - handleError(throwable, completionListener) - } + private suspend fun clearContact(): Throwable? { + return suspendCoroutine { continuation -> + mobileEngageInternal.clearContact { + continuation.resume(it) } - } else { - onSuccess() } } - private fun handleApplicationCodeChange(applicationCode: String?, completionListener: CompletionListener?) { - if (applicationCode != null) { - FeatureRegistry.enableFeature(InnerFeature.MOBILE_ENGAGE) - FeatureRegistry.enableFeature(InnerFeature.EVENT_SERVICE_V4) - mobileEngageRequestContext.applicationCode = applicationCode - collectClientState(completionListener) { - clearContactIfWasNotIdentified(completionListener) { - completionListener?.onCompleted(null) - } + private suspend fun sendPushToken(pushToken: String): Throwable? { + return suspendCoroutine { continuation -> + pushInternal.setPushToken(pushToken) { + continuation.resume(it) } - } else { - FeatureRegistry.disableFeature(InnerFeature.MOBILE_ENGAGE) - completionListener?.onCompleted(null) } } - private fun collectClientState(completionListener: CompletionListener?, onSuccess: () -> Unit) { - clientServiceInternal.trackDeviceInfo { - if (originalPushToken != null) { - pushInternal.setPushToken(originalPushToken) { - if (it == null) { - onSuccess() - } else { - handleError(it, completionListener) - } - } - } else { - onSuccess() + private suspend fun sendDeviceInfo(): Throwable? { + return suspendCoroutine { continuation -> + clientServiceInternal.trackDeviceInfo { + continuation.resume(it) } } } - private fun handleError(throwable: Throwable?, completionListener: CompletionListener?) { - FeatureRegistry.disableFeature(InnerFeature.MOBILE_ENGAGE) - mobileEngageRequestContext.applicationCode = null - completionListener?.onCompleted(throwable) - } - override fun changeMerchantId(merchantId: String?) { predictRequestContext.merchantId = merchantId if (merchantId == null) { From 8522f1c68e06702c340c506bb9f8ccdc852e5363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hunyady=20Mih=C3=A1ly?= Date: Tue, 11 Jan 2022 12:08:02 +0100 Subject: [PATCH 04/23] chore(github): use dev branch from pipelines SUITEDEV-29967 Co-authored-by: davidSchuppa <32750715+davidSchuppa@users.noreply.github.com> Co-authored-by: LasOri <24588073+LasOri@users.noreply.github.com> Co-authored-by: LordAndras <49073629+LordAndras@users.noreply.github.com> --- .github/workflows/blackduck.yml | 3 ++- .github/workflows/nightly_e2e_workflow.yml | 1 + .github/workflows/nightly_workflow.yml | 1 + .github/workflows/on_push_workflow.yml | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/blackduck.yml b/.github/workflows/blackduck.yml index e18e80c4c..e5951b283 100644 --- a/.github/workflows/blackduck.yml +++ b/.github/workflows/blackduck.yml @@ -4,7 +4,7 @@ on: repository_dispatch: types: [ security-scan ] branches: - - master + - dev workflow_dispatch: env: @@ -21,6 +21,7 @@ jobs: steps: - uses: actions/checkout@v2.3.1 with: + ref: dev submodules: true fetch-depth: 0 - run: git fetch --all || echo "==> Accept any result" diff --git a/.github/workflows/nightly_e2e_workflow.yml b/.github/workflows/nightly_e2e_workflow.yml index 28a26dc4a..e08819883 100644 --- a/.github/workflows/nightly_e2e_workflow.yml +++ b/.github/workflows/nightly_e2e_workflow.yml @@ -25,6 +25,7 @@ jobs: steps: - uses: actions/checkout@v2.3.1 with: + ref: dev submodules: true fetch-depth: 0 diff --git a/.github/workflows/nightly_workflow.yml b/.github/workflows/nightly_workflow.yml index 5cf44099b..35833b8bb 100644 --- a/.github/workflows/nightly_workflow.yml +++ b/.github/workflows/nightly_workflow.yml @@ -25,6 +25,7 @@ jobs: steps: - uses: actions/checkout@v2.3.1 with: + ref: dev submodules: true fetch-depth: 0 # 0 indicates all history - run: git fetch --all || echo "==> Accept any result" diff --git a/.github/workflows/on_push_workflow.yml b/.github/workflows/on_push_workflow.yml index f72f01c42..0686ff76b 100644 --- a/.github/workflows/on_push_workflow.yml +++ b/.github/workflows/on_push_workflow.yml @@ -3,7 +3,7 @@ name: Last push build on: workflow_dispatch: push: - branches: [ master ] + branches: [ dev ] env: RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD }} @@ -26,6 +26,7 @@ jobs: steps: - uses: actions/checkout@v2.3.1 with: + ref: dev submodules: true fetch-depth: 0 # 0 indicates all history - run: git fetch --all || echo "==> Accept any result" From 363515fabe0f4efacbbcc9e416bafdfff5b2ca5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hunyady=20Mih=C3=A1ly?= Date: Tue, 18 Jan 2022 11:06:41 +0100 Subject: [PATCH 05/23] feat(coroutines): create and use ConcurrentHandlerHolder SUITEDEV-29941 Co-authored-by: davidSchuppa <32750715+davidSchuppa@users.noreply.github.com> Co-authored-by: LasOri <24588073+LasOri@users.noreply.github.com> Co-authored-by: LordAndras <49073629+LordAndras@users.noreply.github.com> --- core/build.gradle | 2 + .../ActivityLifecycleActionRegistryTest.kt | 19 +- .../activity/ActivityLifecycleWatchdogTest.kt | 2 - .../com/emarsys/core/api/AsyncProxyTest.kt | 39 +- .../emarsys/core/api/LogExceptionProxyTest.kt | 20 +- .../core/app/AppLifecycleObserverTest.kt | 18 +- .../concurrency/CoreHandlerProviderTest.kt | 26 +- .../core/connection/ConnectionWatchDogTest.kt | 22 +- .../ConnectivityChangeReceiverTest.kt | 27 +- ...ty_getConnectionState_ParameterizedTest.kt | 29 +- .../AbstractSqliteRepositoryTest.kt | 55 +- .../specification/EverythingTest.kt | 38 +- .../specification/FilterByRequestIdsTest.kt | 45 +- .../QueryLatestRequestModelTest.kt | 28 +- .../core/di/FakeCoreDependencyContainer.kt | 65 +- .../core/fake/FakeConnectionWatchDog.java | 7 +- .../com/emarsys/core/fake/FakeRestClient.java | 8 +- .../hardwareid/HardwareIdProviderTest.kt | 70 ++- .../core/request/RequestManagerDennaTest.java | 246 -------- .../core/request/RequestManagerDennaTest.kt | 240 ++++++++ .../core/request/RequestManagerOfflineTest.kt | 99 +-- .../core/request/RequestManagerTest.kt | 122 ++-- .../emarsys/core/request/RestClientTest.kt | 24 +- ...CompletionHandlerMiddlewareProviderTest.kt | 28 +- .../model/RequestModelRepositoryTest.java | 36 +- .../specification/FilterByUrlPatternTest.kt | 57 +- .../core/shard/ShardModelRepositoryTest.kt | 13 +- .../specification/FilterByShardIdsTest.kt | 57 +- .../specification/FilterByShardTypeTest.kt | 30 +- .../emarsys/core/util/FileDownloaderTest.kt | 14 +- .../util/batch/BatchingShardTriggerTest.kt | 27 +- .../com/emarsys/core/util/log/LoggerTest.kt | 62 +- .../CoreCompletionHandlerMiddlewareTest.kt | 231 ++++--- .../emarsys/core/worker/DefaultWorkerTest.kt | 98 +-- .../ActivityLifecycleActionRegistry.kt | 8 +- .../java/com/emarsys/core/api/ApiProxy.kt | 6 +- .../java/com/emarsys/core/api/AsyncProxy.kt | 25 +- .../emarsys/core/app/AppLifecycleObserver.kt | 10 +- .../ConcurrentHandlerHolderFactory.kt | 18 + .../concurrency/CoreSdkHandlerProvider.kt | 13 - .../core/connection/ConnectionWatchDog.kt | 13 +- .../connection/ConnectivityChangeReceiver.kt | 6 +- .../repository/AbstractSqliteRepository.kt | 68 ++- .../core/database/repository/Repository.kt | 6 +- .../emarsys/core/device/HardwareRepository.kt | 17 +- .../java/com/emarsys/core/di/CoreComponent.kt | 15 +- .../core/handler/ConcurrentHandlerHolder.kt | 18 + .../emarsys/core/handler/CoreSdkHandler.kt | 13 - .../com/emarsys/core/handler/SdkHandler.kt | 16 + .../provider/hardwareid/HardwareIdProvider.kt | 27 +- .../emarsys/core/request/RequestManager.kt | 31 +- .../com/emarsys/core/request/RestClient.kt | 14 +- ...CoreCompletionHandlerMiddlewareProvider.kt | 21 +- .../request/model/RequestModelRepository.java | 26 +- .../core/shard/ShardModelRepository.kt | 15 +- .../core/util/batch/BatchingShardTrigger.kt | 22 +- .../java/com/emarsys/core/util/log/Logger.kt | 33 +- .../CoreCompletionHandlerMiddleware.java | 136 ----- .../worker/CoreCompletionHandlerMiddleware.kt | 108 ++++ .../emarsys/core/worker/DefaultWorker.java | 121 ---- .../com/emarsys/core/worker/DefaultWorker.kt | 103 ++++ .../java/com/emarsys/EmarsysE2ETests.kt | 2 +- .../java/com/emarsys/testUtil/E2ETestUtils.kt | 4 +- .../fake/FakeFirebaseDependencyContainer.kt | 188 +++--- .../EmarsysFirebaseMessagingServiceTest.kt | 34 +- .../EmarsysFirebaseMessagingService.kt | 2 +- .../EmarsysFirebaseMessagingServiceUtils.kt | 19 +- .../fake/FakeHuaweiDependencyContainer.kt | 188 +++--- .../EmarsysHuaweiMessagingServiceTest.kt | 64 +- .../service/EmarsysHuaweiMessagingService.kt | 2 +- .../EmarsysHuaweiMessagingServiceUtils.kt | 19 +- .../java/com/emarsys/EmarsysTest.kt | 2 +- .../androidTest/java/com/emarsys/InAppTest.kt | 2 +- .../InappNotificationIntegrationTest.kt | 4 - .../emarsys/MobileEngageIntegrationTest.kt | 2 +- ...ngageRefreshContactTokenIntegrationTest.kt | 2 +- .../com/emarsys/PredictIntegrationTest.kt | 6 +- .../java/com/emarsys/PredictTest.kt | 2 +- .../emarsys/RemoteConfigIntegrationTest.kt | 9 +- .../com/emarsys/di/FakeDependencyContainer.kt | 249 ++++---- .../java/com/emarsys/fake/FakeRestClient.java | 6 +- .../emarsys/inapp/ui/InlineInAppViewTest.kt | 14 +- .../emarsys/predict/PredictRestrictedTest.kt | 2 +- .../emarsys/testUtil/IntegrationTestUtils.kt | 4 +- .../src/main/java/com/emarsys/Emarsys.kt | 16 +- .../com/emarsys/di/DefaultEmarsysComponent.kt | 115 ++-- .../emarsys/di/DefaultEmarsysDependencies.kt | 2 +- .../com/emarsys/inapp/ui/InlineInAppView.kt | 4 +- .../config/DefaultConfigInternalTest.kt | 4 +- .../fake/FakeEmarsysDependencyContainer.kt | 12 +- .../java/com/emarsys/fake/FakeRestClient.java | 10 +- .../emarsys/config/DefaultConfigInternal.kt | 4 +- .../emarsys/config/FetchRemoteConfigAction.kt | 2 +- .../FakeMobileEngageDependencyContainer.kt | 12 +- .../mobileengage/fake/FakeRequestManager.kt | 4 +- .../mobileengage/fake/FakeRestClient.java | 15 +- .../geofence/DefaultGeofenceInternalTest.kt | 10 +- .../iam/OverlayInAppPresenterTest.kt | 30 +- .../iam/SaveDisplayedIamActionTest.java | 87 --- .../iam/SaveDisplayedIamActionTest.kt | 79 +++ .../action/SendDisplayedIamActionTest.java | 24 +- .../iam/jsbridge/JSCommandFactoryTest.kt | 92 ++- .../ButtonClickedRepositoryTest.kt | 9 +- .../DisplayedIamRepositoryTest.java | 77 --- .../DisplayedIamRepositoryTest.kt | 70 +++ .../RequestRepositoryProxyTest.kt | 568 ++++++++++-------- .../specification/FilterByCampaignIdTest.kt | 134 +++-- .../inbox/DefaultMessageInboxInternalTest.kt | 31 +- .../PreloadedInappHandlerCommandTest.kt | 39 +- .../InAppCleanUpResponseHandlerTest.kt | 38 +- .../InAppCleanUpResponseHandlerV4Test.kt | 75 ++- .../OnEventActionResponseHandlerTest.kt | 105 ++-- .../service/MessagingServiceUtilsTest.kt | 2 +- .../service/NotificationActionUtilsTest.kt | 2 +- .../service/RemoteMessageMapperTest.kt | 2 +- .../mobileengage/util/AsyncSDKUtils.kt | 4 +- .../mobileengage/deeplink/DeepLinkAction.kt | 3 +- .../device/DeviceInfoStartAction.kt | 4 +- .../geofence/DefaultGeofenceInternal.kt | 6 +- .../geofence/FetchGeofencesAction.kt | 2 +- .../geofence/GeofenceBroadcastReceiver.kt | 10 +- .../mobileengage/iam/AppStartAction.kt | 7 +- .../mobileengage/iam/OverlayInAppPresenter.kt | 50 +- .../dialog/action/SaveDisplayedIamAction.java | 36 -- .../dialog/action/SaveDisplayedIamAction.kt | 24 + .../dialog/action/SendDisplayedIamAction.java | 10 +- .../iam/jsbridge/JSCommandFactory.kt | 40 +- .../buttonclicked/ButtonClickedRepository.kt | 13 +- .../displayediam/DisplayedIamRepository.java | 5 +- .../RequestRepositoryProxy.java | 31 +- .../inbox/DefaultMessageInboxInternal.kt | 30 +- .../InAppCleanUpResponseHandler.kt | 14 +- .../InAppCleanUpResponseHandlerV4.kt | 15 +- .../OnEventActionResponseHandler.kt | 28 +- .../service/NotificationActionUtils.kt | 2 +- .../emarsys/predict/fake/FakeRestClient.java | 10 +- 136 files changed, 3124 insertions(+), 2533 deletions(-) delete mode 100644 core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.java create mode 100644 core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.kt create mode 100644 core/src/main/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactory.kt delete mode 100644 core/src/main/java/com/emarsys/core/concurrency/CoreSdkHandlerProvider.kt create mode 100644 core/src/main/java/com/emarsys/core/handler/ConcurrentHandlerHolder.kt delete mode 100644 core/src/main/java/com/emarsys/core/handler/CoreSdkHandler.kt create mode 100644 core/src/main/java/com/emarsys/core/handler/SdkHandler.kt delete mode 100644 core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.java create mode 100644 core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.kt delete mode 100644 core/src/main/java/com/emarsys/core/worker/DefaultWorker.java create mode 100644 core/src/main/java/com/emarsys/core/worker/DefaultWorker.kt delete mode 100644 mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.java create mode 100644 mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.kt delete mode 100644 mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.java create mode 100644 mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.kt delete mode 100644 mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SaveDisplayedIamAction.java create mode 100644 mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SaveDisplayedIamAction.kt diff --git a/core/build.gradle b/core/build.gradle index 79b38745e..ab097e036 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,6 +1,8 @@ dependencies { implementation project(':core-api') implementation Libs.google_tink + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' + androidTestImplementation project(':testUtils') } \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleActionRegistryTest.kt b/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleActionRegistryTest.kt index 9b8330026..55ef76534 100644 --- a/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleActionRegistryTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleActionRegistryTest.kt @@ -1,10 +1,12 @@ package com.emarsys.core.activity import android.app.Activity +import android.os.Handler +import android.os.Looper import com.emarsys.core.activity.ActivityLifecycleAction.ActivityLifecycle.CREATE import com.emarsys.core.activity.ActivityLifecycleAction.ActivityLifecycle.RESUME -import com.emarsys.core.concurrency.CoreSdkHandlerProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import io.kotlintest.shouldBe import org.junit.Before @@ -15,16 +17,18 @@ import java.util.concurrent.CountDownLatch class ActivityLifecycleActionRegistryTest { private lateinit var activityLifecycleActionRegistry: ActivityLifecycleActionRegistry - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var mockCurrentActivityProvider: CurrentActivityProvider private lateinit var mockAction1: ActivityLifecycleAction private lateinit var mockAction2: ActivityLifecycleAction private lateinit var mockAction3: ActivityLifecycleAction private lateinit var mockActions: MutableList private lateinit var mockActivity: Activity + private lateinit var uiHandler: Handler @Before fun setup() { + uiHandler = Handler(Looper.getMainLooper()) mockActivity = mock() mockAction1 = mock { on { triggeringLifecycle } doReturn RESUME @@ -39,10 +43,10 @@ class ActivityLifecycleActionRegistryTest { on { get() } doReturn mockActivity } mockActions = mutableListOf(mockAction1, mockAction2, mockAction3) - coreSdkHandler = CoreSdkHandlerProvider().provideHandler() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() activityLifecycleActionRegistry = ActivityLifecycleActionRegistry( - coreSdkHandler, mockCurrentActivityProvider, mockActions + concurrentHandlerHolder, mockCurrentActivityProvider, mockActions ) } @@ -54,7 +58,8 @@ class ActivityLifecycleActionRegistryTest { @Test fun testConstructor_createEmptyArraysByDefault() { - val emptyActivityLifecycleActionRegistry = ActivityLifecycleActionRegistry(coreSdkHandler, mockCurrentActivityProvider) + val emptyActivityLifecycleActionRegistry = + ActivityLifecycleActionRegistry(concurrentHandlerHolder, mockCurrentActivityProvider) emptyActivityLifecycleActionRegistry.triggerOnActivityActions.size shouldBe 0 emptyActivityLifecycleActionRegistry.lifecycleActions.size shouldBe 0 } @@ -247,7 +252,7 @@ class ActivityLifecycleActionRegistryTest { private fun waitForCoreSDKThread() { val latch = CountDownLatch(1) - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { latch.countDown() } latch.await() diff --git a/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleWatchdogTest.kt b/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleWatchdogTest.kt index 17095b87a..0195a0a17 100644 --- a/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleWatchdogTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleWatchdogTest.kt @@ -1,8 +1,6 @@ package com.emarsys.core.activity import android.app.Activity -import com.emarsys.core.concurrency.CoreSdkHandlerProvider -import com.emarsys.core.handler.CoreSdkHandler import com.emarsys.testUtil.TimeoutUtils import org.junit.Before import org.junit.Rule diff --git a/core/src/androidTest/java/com/emarsys/core/api/AsyncProxyTest.kt b/core/src/androidTest/java/com/emarsys/core/api/AsyncProxyTest.kt index cf950a3af..5eadcba9a 100644 --- a/core/src/androidTest/java/com/emarsys/core/api/AsyncProxyTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/api/AsyncProxyTest.kt @@ -1,8 +1,10 @@ package com.emarsys.core.api +import android.os.Handler import android.os.HandlerThread -import com.emarsys.core.concurrency.CoreHandler -import com.emarsys.core.handler.CoreSdkHandler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.testUtil.TimeoutUtils import com.emarsys.testUtil.mockito.ThreadSpy import io.kotlintest.shouldBe @@ -16,7 +18,8 @@ import java.util.concurrent.CountDownLatch class AsyncProxyTest { - private lateinit var handler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + private lateinit var uiHandler: Handler @Rule @JvmField @@ -26,14 +29,15 @@ class AsyncProxyTest { fun setUp() { val handlerThread = HandlerThread("CoreSDKHandlerThread-" + UUID.randomUUID().toString()) handlerThread.start() - handler = CoreSdkHandler(CoreHandler(handlerThread)) + uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() } @Test fun testInvoke_shouldInvokeMethod() { val expected: CharSequence = "test" - val result = expected.proxyWithHandler(handler) + val result = expected.proxyWithHandler(concurrentHandlerHolder) result.toString() shouldBe "test" } @@ -48,7 +52,7 @@ class AsyncProxyTest { latch.countDown() } - callback.proxyWithHandler(handler).run() + callback.proxyWithHandler(concurrentHandlerHolder).run() latch.await() threadSpy.verifyCalledOnCoreSdkThread() @@ -63,7 +67,7 @@ class AsyncProxyTest { "test" } - val result = callback.proxyWithHandler(handler).call() + val result = callback.proxyWithHandler(concurrentHandlerHolder).call() result shouldBe "test" threadSpy.verifyCalledOnCoreSdkThread() @@ -72,9 +76,12 @@ class AsyncProxyTest { @Test fun testInvoke_shouldHandlePrimitives_boolean() { val latch = CountDownLatch(1) - val proxiedTestClass = (TestClassWithPrimitives() as Proxyable).proxyWithHandler(handler, timeout = 1) + val proxiedTestClass = (TestClassWithPrimitives() as Proxyable).proxyWithHandler( + concurrentHandlerHolder, + timeout = 1 + ) var error: Exception? = null - handler.post { + concurrentHandlerHolder.coreHandler.post { try { val result = proxiedTestClass.testBoolean() result shouldBe false @@ -91,9 +98,12 @@ class AsyncProxyTest { @Test fun testInvoke_shouldHandlePrimitives_double() { val latch = CountDownLatch(1) - val proxiedTestClass = (TestClassWithPrimitives() as Proxyable).proxyWithHandler(handler, timeout = 1) + val proxiedTestClass = (TestClassWithPrimitives() as Proxyable).proxyWithHandler( + concurrentHandlerHolder, + timeout = 1 + ) var error: Exception? = null - handler.post { + concurrentHandlerHolder.coreHandler.post { try { val result = proxiedTestClass.testDouble() result shouldBe 0.0 @@ -110,9 +120,12 @@ class AsyncProxyTest { @Test fun testInvoke_shouldHandlePrimitives_char() { val latch = CountDownLatch(1) - val proxiedTestClass = (TestClassWithPrimitives() as Proxyable).proxyWithHandler(handler, timeout = 1) + val proxiedTestClass = (TestClassWithPrimitives() as Proxyable).proxyWithHandler( + concurrentHandlerHolder, + timeout = 1 + ) var error: Exception? = null - handler.post { + concurrentHandlerHolder.coreHandler.post { try { val result = proxiedTestClass.testChar() result shouldBe Char(0) diff --git a/core/src/androidTest/java/com/emarsys/core/api/LogExceptionProxyTest.kt b/core/src/androidTest/java/com/emarsys/core/api/LogExceptionProxyTest.kt index ee2f3eabf..8ab799932 100644 --- a/core/src/androidTest/java/com/emarsys/core/api/LogExceptionProxyTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/api/LogExceptionProxyTest.kt @@ -1,10 +1,12 @@ package com.emarsys.core.api -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.di.FakeCoreDependencyContainer import com.emarsys.core.di.setupCoreComponent import com.emarsys.core.di.tearDownCoreComponent -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.util.log.LogLevel import com.emarsys.core.util.log.Logger import com.emarsys.core.util.log.entry.CrashLog @@ -16,13 +18,13 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.mockito.kotlin.* -import java.lang.RuntimeException import java.lang.reflect.InvocationTargetException import java.util.concurrent.CountDownLatch class LogExceptionProxyTest { private lateinit var mockLogger: Logger - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + private lateinit var uiHandler: Handler @Rule @JvmField @@ -30,11 +32,15 @@ class LogExceptionProxyTest { @Before fun setUp() { - coreSdkHandler = CoreSdkHandlerProvider().provideHandler() + uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() mockLogger = mock() val dependencyContainer = - FakeCoreDependencyContainer(coreSdkHandler = coreSdkHandler, logger = mockLogger) + FakeCoreDependencyContainer( + concurrentHandlerHolder = concurrentHandlerHolder, + logger = mockLogger + ) setupCoreComponent(dependencyContainer) } @@ -62,7 +68,7 @@ class LogExceptionProxyTest { callback.proxyWithLogExceptions().run() val latch = CountDownLatch(1) - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { latch.countDown() } latch.await() diff --git a/core/src/androidTest/java/com/emarsys/core/app/AppLifecycleObserverTest.kt b/core/src/androidTest/java/com/emarsys/core/app/AppLifecycleObserverTest.kt index 5f651ff28..fec76da1e 100644 --- a/core/src/androidTest/java/com/emarsys/core/app/AppLifecycleObserverTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/app/AppLifecycleObserverTest.kt @@ -1,13 +1,12 @@ package com.emarsys.core.app import android.os.Handler -import android.os.HandlerThread import android.os.Looper import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry -import com.emarsys.core.concurrency.CoreHandler -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.session.Session import com.emarsys.testUtil.TimeoutUtils import com.emarsys.testUtil.mockito.whenever @@ -18,13 +17,12 @@ import org.junit.rules.TestRule import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify -import java.util.* import java.util.concurrent.CountDownLatch class AppLifecycleObserverTest { private lateinit var mockSession: Session private lateinit var appLifecycleObserver: AppLifecycleObserver - private lateinit var coreHandler: CoreSdkHandler + private lateinit var coreHandlerHolder: ConcurrentHandlerHolder private lateinit var mockLifecycleOwner: LifecycleOwner private lateinit var uiHandler: Handler @@ -37,10 +35,8 @@ class AppLifecycleObserverTest { uiHandler = Handler(Looper.getMainLooper()) mockSession = mock() mockLifecycleOwner = mock() - val handlerThread = HandlerThread("CoreSDKHandlerThread-" + UUID.randomUUID().toString()) - handlerThread.start() - coreHandler = CoreSdkHandler(CoreHandler(handlerThread)) - appLifecycleObserver = AppLifecycleObserver(mockSession, coreHandler) + coreHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + appLifecycleObserver = AppLifecycleObserver(mockSession, coreHandlerHolder) } @Test @@ -54,7 +50,7 @@ class AppLifecycleObserverTest { } appLifecycleObserver.onStart(mockLifecycleOwner) - coreHandler.post { + coreHandlerHolder.coreHandler.post { latch.countDown() } @@ -75,7 +71,7 @@ class AppLifecycleObserverTest { } appLifecycleObserver.onStop(mockLifecycleOwner) - coreHandler.post { + coreHandlerHolder.coreHandler.post { latch.countDown() } diff --git a/core/src/androidTest/java/com/emarsys/core/concurrency/CoreHandlerProviderTest.kt b/core/src/androidTest/java/com/emarsys/core/concurrency/CoreHandlerProviderTest.kt index 5dc76e9d5..e453ae9b6 100644 --- a/core/src/androidTest/java/com/emarsys/core/concurrency/CoreHandlerProviderTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/concurrency/CoreHandlerProviderTest.kt @@ -1,23 +1,31 @@ package com.emarsys.core.concurrency +import android.os.Handler +import android.os.Looper +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.testUtil.TimeoutUtils.timeoutRule -import com.emarsys.core.handler.CoreSdkHandler import io.kotlintest.shouldBe import io.kotlintest.shouldNotBe -import org.junit.* +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test import org.junit.rules.TestRule class CoreHandlerProviderTest { - private lateinit var provider: CoreSdkHandlerProvider - private lateinit var provided: CoreSdkHandler + private lateinit var holderFactory: ConcurrentHandlerHolderFactory + private lateinit var provided: ConcurrentHandlerHolder + private lateinit var uiHandler: Handler @Rule @JvmField var timeout: TestRule = timeoutRule + @Before fun setUp() { - provider = CoreSdkHandlerProvider() - provided = provider.provideHandler() + uiHandler = Handler(Looper.getMainLooper()) + holderFactory = ConcurrentHandlerHolderFactory(uiHandler) + provided = holderFactory.create() } @After @@ -31,12 +39,12 @@ class CoreHandlerProviderTest { } @Test - fun testProvideHandler_shouldReturnCoreSdkHandler() { - provided.javaClass shouldBe CoreSdkHandler::class.java + fun testProvideHandler_shouldReturnConcurrentHandlerHolder() { + provided.javaClass shouldBe ConcurrentHandlerHolder::class.java } @Test - fun testProvideHandler_shouldReturnCoreSdkHandlerWithCorrectName() { + fun testProvideHandler_shouldReturnConcurrentHandlerHolderWithCorrectName() { val expectedNamePrefix = "CoreSDKHandlerThread" val actualName = provided.looper.thread.name actualName.startsWith(expectedNamePrefix) shouldBe true diff --git a/core/src/androidTest/java/com/emarsys/core/connection/ConnectionWatchDogTest.kt b/core/src/androidTest/java/com/emarsys/core/connection/ConnectionWatchDogTest.kt index 468531e09..d49da87e1 100644 --- a/core/src/androidTest/java/com/emarsys/core/connection/ConnectionWatchDogTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/connection/ConnectionWatchDogTest.kt @@ -4,9 +4,11 @@ import android.content.Context import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.os.Build +import android.os.Handler +import android.os.Looper import androidx.test.filters.SdkSuppress -import com.emarsys.core.concurrency.CoreSdkHandlerProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.testUtil.ConnectionTestUtils.getContextMock_withAppContext_withConnectivityManager import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext import com.emarsys.testUtil.ReflectionTestUtils @@ -21,7 +23,8 @@ import org.junit.rules.TestRule @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) class ConnectionWatchDogTest { private lateinit var context: Context - private lateinit var mockHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + private lateinit var uiHandler: Handler @Rule @JvmField @@ -30,12 +33,13 @@ class ConnectionWatchDogTest { @Before fun setup() { context = getTargetContext().applicationContext - mockHandler = CoreSdkHandlerProvider().provideHandler() + uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() } @Test fun testConstructor_connectivityManagerShouldBeSet() { - val watchDog = ConnectionWatchDog(context, mockHandler) + val watchDog = ConnectionWatchDog(context, concurrentHandlerHolder) val manager: ConnectivityManager = ReflectionTestUtils.getInstanceField(watchDog, "connectivityManager")!! Assert.assertNotNull(manager) @@ -47,7 +51,7 @@ class ConnectionWatchDogTest { true, NetworkCapabilities.TRANSPORT_VPN ) - val connectionWatchDog = ConnectionWatchDog(contextMock, mockHandler) + val connectionWatchDog = ConnectionWatchDog(contextMock, concurrentHandlerHolder) val connectionChangeListener: ConnectionChangeListener = mockk() connectionWatchDog.registerReceiver(connectionChangeListener) @@ -67,7 +71,7 @@ class ConnectionWatchDogTest { true, NetworkCapabilities.TRANSPORT_VPN ) - val connectionWatchDog = ConnectionWatchDog(contextMock, mockHandler) + val connectionWatchDog = ConnectionWatchDog(contextMock, concurrentHandlerHolder) val connectionChangeListener: ConnectionChangeListener = mockk() every { connectionChangeListener.onConnectionChanged(any(), any()) } just Runs val manager = @@ -89,14 +93,14 @@ class ConnectionWatchDogTest { true, NetworkCapabilities.TRANSPORT_WIFI ) - val watchDog = ConnectionWatchDog(contextMock, mockHandler) + val watchDog = ConnectionWatchDog(contextMock, concurrentHandlerHolder) Assert.assertTrue(watchDog.isConnected) } @Test fun testIsConnected_Offline() { val contextMock = getContextMock_withAppContext_withConnectivityManager(false, -1) - val watchDog = ConnectionWatchDog(contextMock, mockHandler) + val watchDog = ConnectionWatchDog(contextMock, concurrentHandlerHolder) Assert.assertFalse(watchDog.isConnected) } } \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/connection/ConnectivityChangeReceiverTest.kt b/core/src/androidTest/java/com/emarsys/core/connection/ConnectivityChangeReceiverTest.kt index acc175b34..420ef3374 100644 --- a/core/src/androidTest/java/com/emarsys/core/connection/ConnectivityChangeReceiverTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/connection/ConnectivityChangeReceiverTest.kt @@ -1,16 +1,15 @@ package com.emarsys.core.connection import android.content.Context -import com.emarsys.testUtil.TimeoutUtils.timeoutRule -import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext - -import androidx.test.filters.SdkSuppress -import com.emarsys.core.fake.FakeConnectionChangeListener import android.os.Build import android.os.Handler +import android.os.Looper +import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry -import com.emarsys.core.concurrency.CoreSdkHandlerProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.fake.FakeConnectionChangeListener +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.testUtil.TimeoutUtils.timeoutRule import io.kotlintest.shouldBe import org.junit.After import org.junit.Before @@ -24,23 +23,25 @@ class ConnectivityChangeReceiverTest { private lateinit var receiver: ConnectivityChangeReceiver private lateinit var mockListener: ConnectionChangeListener private lateinit var context: Context + private lateinit var uiHandler: Handler @Rule @JvmField var timeout: TestRule = timeoutRule - lateinit var sdkHandler: CoreSdkHandler + lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Before fun setup() { + uiHandler = Handler(Looper.getMainLooper()) context = InstrumentationRegistry.getInstrumentation().targetContext - sdkHandler = CoreSdkHandlerProvider().provideHandler() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() mockListener = mock() } @After fun tearDown() { - sdkHandler.looper.quit() + concurrentHandlerHolder.looper.quit() } @Test @@ -48,11 +49,11 @@ class ConnectivityChangeReceiverTest { fun testOnReceive_listenerShouldCall_onCoreSDKThread() { val latch = CountDownLatch(1) val fakeListener = FakeConnectionChangeListener(latch) - val expectedName = sdkHandler.looper.thread.name + val expectedName = concurrentHandlerHolder.looper.thread.name receiver = ConnectivityChangeReceiver( fakeListener, - ConnectionWatchDog(context, sdkHandler), - sdkHandler + ConnectionWatchDog(context, concurrentHandlerHolder), + concurrentHandlerHolder ) receiver.onReceive(context, mock()) latch.await() diff --git a/core/src/androidTest/java/com/emarsys/core/connection/Connectivity_getConnectionState_ParameterizedTest.kt b/core/src/androidTest/java/com/emarsys/core/connection/Connectivity_getConnectionState_ParameterizedTest.kt index 9d32f1919..7113e2440 100644 --- a/core/src/androidTest/java/com/emarsys/core/connection/Connectivity_getConnectionState_ParameterizedTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/connection/Connectivity_getConnectionState_ParameterizedTest.kt @@ -1,22 +1,21 @@ package com.emarsys.core.connection -import com.emarsys.testUtil.TimeoutUtils.timeoutRule -import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext -import com.emarsys.testUtil.ReflectionTestUtils.setInstanceField -import com.emarsys.testUtil.ConnectionTestUtils.getConnectivityManagerMock -import androidx.test.filters.SdkSuppress - import android.net.NetworkCapabilities import android.os.Build -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import android.os.Handler +import android.os.Looper +import androidx.test.filters.SdkSuppress +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.testUtil.ConnectionTestUtils.getConnectivityManagerMock +import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext +import com.emarsys.testUtil.ReflectionTestUtils.setInstanceField +import com.emarsys.testUtil.TimeoutUtils.timeoutRule import io.kotlintest.data.forall import io.kotlintest.shouldBe import io.kotlintest.tables.row import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule -import org.junit.runners.Parameterized -import java.util.* @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) class Connectivity_getConnectionState_ParameterizedTest { @@ -37,8 +36,16 @@ class Connectivity_getConnectionState_ParameterizedTest { row(true, NetworkCapabilities.TRANSPORT_ETHERNET, ConnectionState.CONNECTED), row(true, NetworkCapabilities.TRANSPORT_VPN, ConnectionState.CONNECTED) ) { isConnected, connectionType, expectedConnectionState -> - val connectionWatchDog = ConnectionWatchDog(getTargetContext(), CoreSdkHandlerProvider().provideHandler()) - setInstanceField(connectionWatchDog, "connectivityManager", getConnectivityManagerMock(isConnected, connectionType)) + val connectionWatchDog = ConnectionWatchDog( + getTargetContext(), ConcurrentHandlerHolderFactory( + Handler(Looper.getMainLooper()) + ).create() + ) + setInstanceField( + connectionWatchDog, + "connectivityManager", + getConnectivityManagerMock(isConnected, connectionType) + ) connectionWatchDog.connectionState shouldBe expectedConnectionState } diff --git a/core/src/androidTest/java/com/emarsys/core/database/repository/AbstractSqliteRepositoryTest.kt b/core/src/androidTest/java/com/emarsys/core/database/repository/AbstractSqliteRepositoryTest.kt index 754d60dbb..746f88a6c 100644 --- a/core/src/androidTest/java/com/emarsys/core/database/repository/AbstractSqliteRepositoryTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/database/repository/AbstractSqliteRepositoryTest.kt @@ -2,6 +2,9 @@ package com.emarsys.core.database.repository import android.content.ContentValues import android.database.Cursor +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.CoreSQLiteDatabase import com.emarsys.core.database.DatabaseContract import com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_HEADERS @@ -14,6 +17,7 @@ import com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_URL import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.helper.DbHelper import com.emarsys.core.device.FilterByHardwareId +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.model.RequestModel @@ -23,6 +27,7 @@ import com.emarsys.testUtil.InstrumentationRegistry import com.emarsys.testUtil.TimeoutUtils import com.emarsys.testUtil.mockito.anyNotNull import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -49,6 +54,7 @@ class AbstractSqliteRepositoryTest { private lateinit var dbHelperMock: DbHelper private lateinit var dbMock: CoreSQLiteDatabase private lateinit var dummySpecification: SqlSpecification + private lateinit var testConcurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -58,6 +64,8 @@ class AbstractSqliteRepositoryTest { @Suppress("UNCHECKED_CAST") fun init() { DatabaseTestUtils.deleteCoreDatabase() + testConcurrentHandlerHolder = + ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() dummySpecification = sqlSpecification( DISTINCT, @@ -77,10 +85,10 @@ class AbstractSqliteRepositoryTest { on { writableCoreDatabase } doReturn dbMock } - repository = (mock(defaultAnswer = Mockito.CALLS_REAL_METHODS) as AbstractSqliteRepository).apply { tableName = TABLE_NAME dbHelper = dbHelperMock + concurrentHandlerHolder = testConcurrentHandlerHolder } } @@ -93,7 +101,9 @@ class AbstractSqliteRepositoryTest { val input = any() - repository.add(input) + runBlocking { + repository.add(input) + } verify(repository).contentValuesFromItem(input) verify(dbMock).beginTransaction() @@ -104,21 +114,23 @@ class AbstractSqliteRepositoryTest { @Test fun testUpdate_shouldUpdateTheDb() { - val contentValues = ContentValues().apply { - put("key", "value") + runBlocking { + val contentValues = ContentValues().apply { + put("key", "value") + } + whenever(repository.contentValuesFromItem(anyNotNull())).thenReturn(contentValues) + whenever(dbMock.update(any(), any(), any(), any())).thenReturn(1) + val input = anyNotNull() + + val result = repository.update(input, eq(FilterByHardwareId("id"))) + + verify(repository).contentValuesFromItem(input) + verify(dbMock).beginTransaction() + verify(dbMock).update(TABLE_NAME, contentValues, "hardware_id=?", arrayOf("id")) + verify(dbMock).setTransactionSuccessful() + verify(dbMock).endTransaction() + result shouldBe 1 } - whenever(repository.contentValuesFromItem(anyNotNull())).thenReturn(contentValues) - whenever(dbMock.update(any(), any(), any(), any())).thenReturn(1) - val input = anyNotNull() - - val result = repository.update(input, eq(FilterByHardwareId("id"))) - - verify(repository).contentValuesFromItem(input) - verify(dbMock).beginTransaction() - verify(dbMock).update(TABLE_NAME, contentValues, "hardware_id=?", arrayOf("id")) - verify(dbMock).setTransactionSuccessful() - verify(dbMock).endTransaction() - result shouldBe 1 } @Test @@ -184,10 +196,17 @@ class AbstractSqliteRepositoryTest { @Test fun testRemove_shouldDeleteSpecifiedRow() { - repository.remove(dummySpecification) + + runBlocking { + repository.remove(dummySpecification) + } verify(dbMock).beginTransaction() - verify(dbMock).delete(TABLE_NAME, dummySpecification.selection, dummySpecification.selectionArgs) + verify(dbMock).delete( + TABLE_NAME, + dummySpecification.selection, + dummySpecification.selectionArgs + ) verify(dbMock).setTransactionSuccessful() verify(dbMock).endTransaction() } diff --git a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/EverythingTest.kt b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/EverythingTest.kt index 601872241..e3b983688 100644 --- a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/EverythingTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/EverythingTest.kt @@ -1,6 +1,10 @@ package com.emarsys.core.database.repository.specification +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.shard.ShardModel import com.emarsys.core.shard.ShardModelRepository import com.emarsys.testUtil.DatabaseTestUtils @@ -9,6 +13,7 @@ import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.matchers.beEmpty import io.kotlintest.should import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -16,6 +21,9 @@ import org.junit.rules.TestRule class EverythingTest { + private lateinit var uiHandler: Handler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + @Rule @JvmField val timeout: TestRule = TimeoutUtils.timeoutRule @@ -23,6 +31,8 @@ class EverythingTest { @Before fun init() { DatabaseTestUtils.deleteCoreDatabase() + uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() } @Test @@ -43,14 +53,18 @@ class EverythingTest { fun testQueryAll() { val context = InstrumentationRegistry.getTargetContext().applicationContext val coreDbHelper = CoreDbHelper(context, mutableMapOf()) - val repository = ShardModelRepository(coreDbHelper) + val repository = ShardModelRepository(coreDbHelper, concurrentHandlerHolder) val expectedList = mutableListOf( - ShardModel("a1", "button_click", mapOf(), 0, 0), - ShardModel("a2", "button_click", mapOf(), 0, 0), - ShardModel("a2", "button_click", mapOf("key" to 22, "key2" to "value"), 0, 0), - ShardModel("a4", "not_button_click", mapOf("key" to 11, "key2" to "asdasd"), 0, 0) + ShardModel("a1", "button_click", mapOf(), 0, 0), + ShardModel("a2", "button_click", mapOf(), 0, 0), + ShardModel("a2", "button_click", mapOf("key" to 22, "key2" to "value"), 0, 0), + ShardModel("a4", "not_button_click", mapOf("key" to 11, "key2" to "asdasd"), 0, 0) ) - expectedList.forEach(repository::add) + runBlocking { + expectedList.forEach { + repository.add(it) + } + } val resultList = repository.query(Everything()) @@ -61,15 +75,19 @@ class EverythingTest { fun testDeleteAll() { val context = InstrumentationRegistry.getTargetContext().applicationContext val coreDbHelper = CoreDbHelper(context, mutableMapOf()) - val repository = ShardModelRepository(coreDbHelper) - mutableListOf( + val repository = ShardModelRepository(coreDbHelper, concurrentHandlerHolder) + runBlocking { + mutableListOf( ShardModel("a1", "button_click", mapOf(), 0, 0), ShardModel("a2", "button_click", mapOf(), 0, 0), ShardModel("a2", "button_click", mapOf("key" to 22, "key2" to "value"), 0, 0), ShardModel("a4", "not_button_click", mapOf("key" to 11, "key2" to "asdasd"), 0, 0) - ).forEach(repository::add) + ).forEach { + repository.add(it) + } - repository.remove(Everything()) + repository.remove(Everything()) + } val resultList = repository.query(Everything()) resultList should beEmpty() diff --git a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/FilterByRequestIdsTest.kt b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/FilterByRequestIdsTest.kt index 81d38ab13..186588954 100644 --- a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/FilterByRequestIdsTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/FilterByRequestIdsTest.kt @@ -1,5 +1,8 @@ package com.emarsys.core.database.repository.specification +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider @@ -10,6 +13,7 @@ import com.emarsys.testUtil.DatabaseTestUtils import com.emarsys.testUtil.InstrumentationRegistry import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -34,25 +38,37 @@ class FilterByRequestIdsTest { val timestampProvider = TimestampProvider() val uuidProvider = UUIDProvider() - requestModel1 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/1").build() - requestModel2 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/2").build() - requestModel3 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/3").build() - requestModel4 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/4").build() + requestModel1 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/1") + .build() + requestModel2 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/2") + .build() + requestModel3 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/3") + .build() + requestModel4 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/4") + .build() val coreDbHelper = CoreDbHelper(InstrumentationRegistry.getTargetContext(), HashMap()) - repository = RequestModelRepository(coreDbHelper) - - repository.add(requestModel1) - repository.add(requestModel2) - repository.add(requestModel3) - repository.add(requestModel4) + val uiHandler = Handler(Looper.getMainLooper()) + val concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + repository = RequestModelRepository(coreDbHelper, concurrentHandlerHolder) + runBlocking { + repository.add(requestModel1) + repository.add(requestModel2) + repository.add(requestModel3) + repository.add(requestModel4) + } } @Test fun testDelete_withRequestModel() { val expected = arrayOf(requestModel1, requestModel3, requestModel4) - - repository.remove(FilterByRequestIds(arrayOf(requestModel2.id))) + runBlocking { + repository.remove(FilterByRequestIds(arrayOf(requestModel2.id))) + } val actual = repository.query(Everything()) @@ -64,8 +80,9 @@ class FilterByRequestIdsTest { val expected = arrayOf(requestModel2) val originalRequestIds = arrayOf(requestModel1.id, requestModel3.id, requestModel4.id) - - repository.remove(FilterByRequestIds(originalRequestIds)) + runBlocking { + repository.remove(FilterByRequestIds(originalRequestIds)) + } val actual = repository.query(Everything()) diff --git a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/QueryLatestRequestModelTest.kt b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/QueryLatestRequestModelTest.kt index 0545d8fb3..aab6e3ecc 100644 --- a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/QueryLatestRequestModelTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/QueryLatestRequestModelTest.kt @@ -1,5 +1,8 @@ package com.emarsys.core.database.repository.specification +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider @@ -10,6 +13,7 @@ import com.emarsys.testUtil.DatabaseTestUtils import com.emarsys.testUtil.InstrumentationRegistry import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -48,18 +52,28 @@ class QueryLatestRequestModelTest { specification = QueryLatestRequestModel() val timestampProvider = TimestampProvider() val uuidProvider = UUIDProvider() + val uiHandler = Handler(Looper.getMainLooper()) + val concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() val context = InstrumentationRegistry.getTargetContext().applicationContext val coreDbHelper = CoreDbHelper(context, mutableMapOf()) - val repository = RequestModelRepository(coreDbHelper) + val repository = RequestModelRepository(coreDbHelper, concurrentHandlerHolder) - val expectedRequestModel = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/1").build() - val requestModel1 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/2").build() - val requestModel2 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/3").build() + val expectedRequestModel = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/1") + .build() + val requestModel1 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/2") + .build() + val requestModel2 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/3") + .build() - repository.add(expectedRequestModel) - repository.add(requestModel1) - repository.add(requestModel2) + runBlocking { + repository.add(expectedRequestModel) + repository.add(requestModel1) + repository.add(requestModel2) + } val resultList = repository.query(specification) diff --git a/core/src/androidTest/java/com/emarsys/core/di/FakeCoreDependencyContainer.kt b/core/src/androidTest/java/com/emarsys/core/di/FakeCoreDependencyContainer.kt index 5be47ae71..bb4737baa 100644 --- a/core/src/androidTest/java/com/emarsys/core/di/FakeCoreDependencyContainer.kt +++ b/core/src/androidTest/java/com/emarsys/core/di/FakeCoreDependencyContainer.kt @@ -2,10 +2,12 @@ package com.emarsys.core.di import android.content.SharedPreferences import android.os.Handler +import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog import com.emarsys.core.activity.CurrentActivityWatchdog +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.crypto.Crypto import com.emarsys.core.database.CoreSQLiteDatabase @@ -13,12 +15,13 @@ import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.device.DeviceInfo -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.hardwareid.HardwareIdProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient +import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.shard.ShardModel import com.emarsys.core.storage.KeyValueStore @@ -32,32 +35,36 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.android.asCoroutineDispatcher import org.mockito.kotlin.mock -class FakeCoreDependencyContainer(override val coreSdkHandler: CoreSdkHandler = mock(), - override val uiHandler: Handler = mock(), - override val activityLifecycleWatchdog: ActivityLifecycleWatchdog = mock(), - override val currentActivityWatchdog: CurrentActivityWatchdog = mock(), - override val coreSQLiteDatabase: CoreSQLiteDatabase = mock(), - override val deviceInfo: DeviceInfo = mock(), - override val shardRepository: Repository = mock(), - override val timestampProvider: TimestampProvider = mock(), - override val uuidProvider: UUIDProvider = mock(), - override val logShardTrigger: Runnable = mock(), - override val logger: Logger = mock(), - override val restClient: RestClient = mock(), - override val fileDownloader: FileDownloader = mock(), - override val keyValueStore: KeyValueStore = mock(), - override val sharedPreferences: SharedPreferences = mock(), - override val hardwareIdProvider: HardwareIdProvider = mock(), - override val coreDbHelper: CoreDbHelper = mock(), - override val hardwareIdStorage: Storage = mock(), - override val crypto: Crypto = mock(), - override val requestManager: RequestManager = mock(), - override val worker: Worker = mock(), - override val requestModelRepository: Repository = mock(), - override val connectionWatchdog: ConnectionWatchDog = mock(), - override val coreCompletionHandler: CoreCompletionHandler = mock(), - override val logLevelStorage: Storage = mock(), - override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), - override val coreSdkScope: CoroutineScope = CoroutineScope(Job() + coreSdkHandler.handler.asCoroutineDispatcher()), - override val uiScope: CoroutineScope = CoroutineScope(Job() + Dispatchers.Main) +class FakeCoreDependencyContainer( + override val uiHandler: Handler = Handler(Looper.getMainLooper()), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( + uiHandler + ).create(), + override val activityLifecycleWatchdog: ActivityLifecycleWatchdog = mock(), + override val currentActivityWatchdog: CurrentActivityWatchdog = mock(), + override val coreSQLiteDatabase: CoreSQLiteDatabase = mock(), + override val deviceInfo: DeviceInfo = mock(), + override val shardRepository: Repository = mock(), + override val timestampProvider: TimestampProvider = mock(), + override val uuidProvider: UUIDProvider = mock(), + override val logShardTrigger: Runnable = mock(), + override val logger: Logger = mock(), + override val restClient: RestClient = mock(), + override val fileDownloader: FileDownloader = mock(), + override val keyValueStore: KeyValueStore = mock(), + override val sharedPreferences: SharedPreferences = mock(), + override val hardwareIdProvider: HardwareIdProvider = mock(), + override val coreDbHelper: CoreDbHelper = mock(), + override val hardwareIdStorage: Storage = mock(), + override val crypto: Crypto = mock(), + override val requestManager: RequestManager = mock(), + override val worker: Worker = mock(), + override val requestModelRepository: Repository = mock(), + override val connectionWatchdog: ConnectionWatchDog = mock(), + override val coreCompletionHandler: CoreCompletionHandler = mock(), + override val logLevelStorage: Storage = mock(), + override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), + override val coreSdkScope: CoroutineScope = CoroutineScope(Job() + concurrentHandlerHolder.coreHandler.handler.asCoroutineDispatcher()), + override val uiScope: CoroutineScope = CoroutineScope(Job() + Dispatchers.Main), + override val runnableFactory: RunnableFactory = mock() ) : CoreComponent \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/fake/FakeConnectionWatchDog.java b/core/src/androidTest/java/com/emarsys/core/fake/FakeConnectionWatchDog.java index 0c7608dc9..5b4056d25 100644 --- a/core/src/androidTest/java/com/emarsys/core/fake/FakeConnectionWatchDog.java +++ b/core/src/androidTest/java/com/emarsys/core/fake/FakeConnectionWatchDog.java @@ -1,6 +1,9 @@ package com.emarsys.core.fake; -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; +import android.os.Handler; +import android.os.Looper; + +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; import com.emarsys.core.connection.ConnectionChangeListener; import com.emarsys.core.connection.ConnectionWatchDog; import com.emarsys.testUtil.InstrumentationRegistry; @@ -17,7 +20,7 @@ public class FakeConnectionWatchDog extends ConnectionWatchDog { public CountDownLatch latch; public FakeConnectionWatchDog(CountDownLatch latch, Boolean... isConnectedReplies) { - super(InstrumentationRegistry.getTargetContext(), new CoreSdkHandlerProvider().provideHandler()); + super(InstrumentationRegistry.getTargetContext(), new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); this.latch = latch; this.isConnectedReplies = new ArrayList<>(Arrays.asList(isConnectedReplies)); } diff --git a/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java b/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java index f8ca9a9dc..1f6ba530d 100644 --- a/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java +++ b/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java @@ -1,11 +1,13 @@ package com.emarsys.core.fake; +import static org.mockito.Mockito.mock; + import android.os.Handler; import android.os.Looper; import com.emarsys.core.CoreCompletionHandler; import com.emarsys.core.api.result.Try; -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; import com.emarsys.core.connection.ConnectionProvider; import com.emarsys.core.provider.timestamp.TimestampProvider; import com.emarsys.core.request.RequestTask; @@ -18,8 +20,6 @@ import java.util.Arrays; import java.util.List; -import static org.mockito.Mockito.mock; - public class FakeRestClient extends RestClient { private final List fakeResults; @@ -27,7 +27,7 @@ public class FakeRestClient extends RestClient { @SuppressWarnings("unchecked") public FakeRestClient(Object... fakeResults) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); + new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); for (Object o : fakeResults) { if (!(o instanceof Integer || o instanceof Exception)) { throw new IllegalArgumentException("FakeResults list can only contain Integers and Exceptions!"); diff --git a/core/src/androidTest/java/com/emarsys/core/provider/hardwareid/HardwareIdProviderTest.kt b/core/src/androidTest/java/com/emarsys/core/provider/hardwareid/HardwareIdProviderTest.kt index e7bbfed38..b7b8ae1e3 100644 --- a/core/src/androidTest/java/com/emarsys/core/provider/hardwareid/HardwareIdProviderTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/provider/hardwareid/HardwareIdProviderTest.kt @@ -10,6 +10,7 @@ import com.emarsys.core.storage.Storage import com.emarsys.testUtil.TimeoutUtils import com.emarsys.testUtil.mockito.whenever import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -17,7 +18,7 @@ import org.junit.rules.TestRule import org.mockito.kotlin.* -class HardwareIdProviderTest{ +class HardwareIdProviderTest { companion object { private const val HARDWARE_ID = "hw_value" @@ -27,8 +28,14 @@ class HardwareIdProviderTest{ internal const val IV = "testIv" private val HARDWARE = HardwareIdentification(HARDWARE_ID) private val SHARED_HARDWARE = HardwareIdentification(SHARED_HARDWARE_ID) - private val ENCRYPTED_HARDWARE = HardwareIdentification(HARDWARE_ID, "encrypted_hardware_id", "testSalt", "testIv") - private val ENCRYPTED_SHARED_HARDWARE = HardwareIdentification(SHARED_HARDWARE_ID, "encrypted_shared_hardware_id", "testSalt", "testIv") + private val ENCRYPTED_HARDWARE = + HardwareIdentification(HARDWARE_ID, "encrypted_hardware_id", "testSalt", "testIv") + private val ENCRYPTED_SHARED_HARDWARE = HardwareIdentification( + SHARED_HARDWARE_ID, + "encrypted_shared_hardware_id", + "testSalt", + "testIv" + ) } private lateinit var mockHardwareIdentificationCrypto: HardwareIdentificationCrypto @@ -43,7 +50,7 @@ class HardwareIdProviderTest{ val timeout: TestRule = TimeoutUtils.timeoutRule @Before - fun setUp() { + fun setUp() { mockStorage = mock() mockUUIDProvider = mock { on { provideId() } doReturn HARDWARE_ID @@ -57,7 +64,13 @@ class HardwareIdProviderTest{ } mockHardwareIdContentResolver = mock() - hardwareIdProvider = HardwareIdProvider(mockUUIDProvider, mockRepository, mockStorage, mockHardwareIdContentResolver, mockHardwareIdentificationCrypto) + hardwareIdProvider = HardwareIdProvider( + mockUUIDProvider, + mockRepository, + mockStorage, + mockHardwareIdContentResolver, + mockHardwareIdentificationCrypto + ) } @Test @@ -74,25 +87,35 @@ class HardwareIdProviderTest{ whenever(mockStorage.get()).thenReturn(HARDWARE_ID) whenever(mockHardwareIdentificationCrypto.encrypt(HARDWARE)).thenReturn(HARDWARE) - val result = hardwareIdProvider.provideHardwareId() - - verify(mockRepository).query(any()) - verify(mockHardwareIdentificationCrypto).encrypt(HARDWARE) - verify(mockStorage).get() - verify(mockRepository).add(HARDWARE) - result shouldBe HARDWARE_ID + runBlocking { + val result = hardwareIdProvider.provideHardwareId() + verify(mockRepository).query(any()) + verify(mockHardwareIdentificationCrypto).encrypt(HARDWARE) + verify(mockStorage).get() + verify(mockRepository).add(HARDWARE) + result shouldBe HARDWARE_ID + } } @Test fun testProvideHardwareId_shouldGetHardwareId_fromContentResolver_andStoreInRepository_ifNotInRepositoryNorStorage() { whenever(mockHardwareIdContentResolver.resolveHardwareId()).thenReturn(SHARED_HARDWARE_ID) - val result = hardwareIdProvider.provideHardwareId() - verify(mockRepository).add(HardwareIdentification(SHARED_HARDWARE_ID, ENCRYPTED_HARDWARE_ID, SALT, IV)) - verify(mockHardwareIdContentResolver).resolveHardwareId() - verifyNoInteractions(mockUUIDProvider) - - result shouldBe SHARED_HARDWARE_ID + runBlocking { + val result = hardwareIdProvider.provideHardwareId() + verify(mockRepository).add( + HardwareIdentification( + SHARED_HARDWARE_ID, + ENCRYPTED_HARDWARE_ID, + SALT, + IV + ) + ) + verify(mockHardwareIdContentResolver).resolveHardwareId() + verifyNoInteractions(mockUUIDProvider) + + result shouldBe SHARED_HARDWARE_ID + } } @Test @@ -100,11 +123,12 @@ class HardwareIdProviderTest{ whenever(mockHardwareIdContentResolver.resolveHardwareId()).thenReturn(null) whenever(mockHardwareIdentificationCrypto.encrypt(HARDWARE)).thenReturn(HARDWARE) whenever(mockUUIDProvider.provideId()).thenReturn(HARDWARE_ID) + runBlocking { + val result = hardwareIdProvider.provideHardwareId() + verify(mockRepository).add(HARDWARE) + verify(mockUUIDProvider).provideId() - val result = hardwareIdProvider.provideHardwareId() - verify(mockRepository).add(HARDWARE) - verify(mockUUIDProvider).provideId() - - result shouldBe HARDWARE_ID + result shouldBe HARDWARE_ID + } } } \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.java b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.java deleted file mode 100644 index 443eee872..000000000 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.java +++ /dev/null @@ -1,246 +0,0 @@ -package com.emarsys.core.request; - -import static com.emarsys.testUtil.TestUrls.DENNA_ECHO; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; - -import com.emarsys.core.Mapper; -import com.emarsys.core.Registry; -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; -import com.emarsys.core.connection.ConnectionProvider; -import com.emarsys.core.connection.ConnectionWatchDog; -import com.emarsys.core.database.helper.CoreDbHelper; -import com.emarsys.core.database.repository.Repository; -import com.emarsys.core.database.repository.SqlSpecification; -import com.emarsys.core.database.trigger.TriggerKey; -import com.emarsys.core.fake.FakeCompletionHandler; -import com.emarsys.core.handler.CoreSdkHandler; -import com.emarsys.core.provider.timestamp.TimestampProvider; -import com.emarsys.core.provider.uuid.UUIDProvider; -import com.emarsys.core.request.factory.CompletionHandlerProxyProvider; -import com.emarsys.core.request.factory.CoreCompletionHandlerMiddlewareProvider; -import com.emarsys.core.request.factory.ScopeDelegatorCompletionHandlerProvider; -import com.emarsys.core.request.model.RequestMethod; -import com.emarsys.core.request.model.RequestModel; -import com.emarsys.core.request.model.RequestModelRepository; -import com.emarsys.core.response.ResponseHandlersProcessor; -import com.emarsys.core.shard.ShardModel; -import com.emarsys.core.shard.ShardModelRepository; -import com.emarsys.core.worker.DefaultWorker; -import com.emarsys.core.worker.Worker; -import com.emarsys.testUtil.ConnectionTestUtils; -import com.emarsys.testUtil.DatabaseTestUtils; -import com.emarsys.testUtil.InstrumentationRegistry; -import com.emarsys.testUtil.TimeoutUtils; - -import org.json.JSONObject; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; - -import kotlinx.coroutines.CoroutineScope; - -public class RequestManagerDennaTest { - - private RequestManager manager; - private Map headers; - private RequestModel model; - private CountDownLatch latch; - private FakeCompletionHandler fakeCompletionHandler; - private CoreSdkHandlerProvider provider; - private CoreSdkHandler coreSdkHandler; - private Handler uiHandler; - private Worker worker; - private TimestampProvider timestampProvider; - private UUIDProvider uuidProvider; - private CoreCompletionHandlerMiddlewareProvider coreCompletionHandlerMiddlewareProvider; - - @Rule - public TestRule timeout = TimeoutUtils.getTimeoutRule(); - private Mapper mockRequestModelMapper; - - @Before - @SuppressWarnings("unchecked") - public void init() { - DatabaseTestUtils.deleteCoreDatabase(); - - List> requestModelMappers = new ArrayList<>(); - mockRequestModelMapper = mock(Mapper.class); - - when(mockRequestModelMapper.map(any(RequestModel.class))).thenAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) { - Object[] args = invocation.getArguments(); - return args[0]; - } - }); - - requestModelMappers.add(mockRequestModelMapper); - - Context context = InstrumentationRegistry.getTargetContext(); - ConnectionTestUtils.checkConnection(context); - - provider = new CoreSdkHandlerProvider(); - coreSdkHandler = provider.provideHandler(); - uiHandler = new Handler(Looper.getMainLooper()); - - ConnectionWatchDog connectionWatchDog = new ConnectionWatchDog(context, coreSdkHandler); - CoreDbHelper coreDbHelper = new CoreDbHelper(context, new HashMap>()); - Repository requestRepository = new RequestModelRepository(coreDbHelper); - Repository shardRepository = new ShardModelRepository(coreDbHelper); - latch = new CountDownLatch(1); - fakeCompletionHandler = new FakeCompletionHandler(latch); - RestClient restClient = new RestClient(new ConnectionProvider(), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), - requestModelMappers, new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); - coreCompletionHandlerMiddlewareProvider = new CoreCompletionHandlerMiddlewareProvider(requestRepository, uiHandler, coreSdkHandler); - worker = new DefaultWorker(requestRepository, connectionWatchDog, uiHandler, fakeCompletionHandler, restClient, coreCompletionHandlerMiddlewareProvider); - timestampProvider = new TimestampProvider(); - uuidProvider = new UUIDProvider(); - manager = new RequestManager( - coreSdkHandler, - requestRepository, - shardRepository, - worker, - restClient, - mock(Registry.class), - fakeCompletionHandler, - mock(CompletionHandlerProxyProvider.class), - mock(ScopeDelegatorCompletionHandlerProvider.class), - mock(CoroutineScope.class)); - headers = new HashMap<>(); - headers.put("Accept", "application/json"); - headers.put("Content", "application/x-www-form-urlencoded"); - headers.put("Header1", "value1"); - headers.put("Header2", "value2"); - } - - @After - public void tearDown() { - coreSdkHandler.getLooper().quit(); - } - - @Test - public void testGet() throws Exception { - model = new RequestModel.Builder(timestampProvider, uuidProvider).url(DENNA_ECHO).method(RequestMethod.GET).headers(headers).build(); - manager.submit(model, null); - latch.await(); - - assertEquals(null, fakeCompletionHandler.getException()); - assertEquals(0, fakeCompletionHandler.getOnErrorCount()); - assertEquals(1, fakeCompletionHandler.getOnSuccessCount()); - assertEquals(200, fakeCompletionHandler.getSuccessResponseModel().getStatusCode()); - - JSONObject responseJson = new JSONObject(fakeCompletionHandler.getSuccessResponseModel().getBody()); - JSONObject headers = (JSONObject) responseJson.get("headers"); - - assertEquals("value1", headers.get("Header1")); - assertEquals("value2", headers.get("Header2")); - assertEquals("application/json", headers.get("Accept")); - assertEquals("application/x-www-form-urlencoded", headers.get("Content")); - assertEquals("GET", responseJson.get("method")); - assertFalse(responseJson.has("body")); - } - - @Test - public void testPost() throws Exception { - HashMap deepPayload = new HashMap<>(); - deepPayload.put("deep1", "deepValue1"); - deepPayload.put("deep2", "deepValue2"); - - HashMap payload = new HashMap<>(); - payload.put("key1", "val1"); - payload.put("key2", "val2"); - payload.put("key3", "val3"); - payload.put("key4", 4); - payload.put("deepKey", deepPayload); - - model = new RequestModel.Builder(timestampProvider, uuidProvider).url(DENNA_ECHO).method(RequestMethod.POST).headers(headers).payload(payload).build(); - manager.submit(model, null); - latch.await(); - - assertEquals(null, fakeCompletionHandler.getException()); - assertEquals(0, fakeCompletionHandler.getOnErrorCount()); - assertEquals(1, fakeCompletionHandler.getOnSuccessCount()); - assertEquals(200, fakeCompletionHandler.getSuccessResponseModel().getStatusCode()); - - JSONObject responseJson = new JSONObject(fakeCompletionHandler.getSuccessResponseModel().getBody()); - JSONObject headers = responseJson.getJSONObject("headers"); - JSONObject body = responseJson.getJSONObject("body"); - - assertEquals("value1", headers.get("Header1")); - assertEquals("value2", headers.get("Header2")); - assertEquals("application/json", headers.get("Accept")); - assertEquals("application/x-www-form-urlencoded", headers.get("Content")); - assertEquals("POST", responseJson.get("method")); - assertEquals("val1", body.get("key1")); - assertEquals("val2", body.get("key2")); - assertEquals("val3", body.get("key3")); - assertEquals(4, body.get("key4")); - - JSONObject soDeepJson = body.getJSONObject("deepKey"); - assertEquals("deepValue1", soDeepJson.getString("deep1")); - assertEquals("deepValue2", soDeepJson.getString("deep2")); - } - - @Test - public void testPut() throws Exception { - model = new RequestModel.Builder(timestampProvider, uuidProvider).url(DENNA_ECHO).method(RequestMethod.PUT).headers(headers).build(); - manager.submit(model, null); - latch.await(); - - assertEquals(null, fakeCompletionHandler.getException()); - assertEquals(0, fakeCompletionHandler.getOnErrorCount()); - assertEquals(1, fakeCompletionHandler.getOnSuccessCount()); - assertEquals(200, fakeCompletionHandler.getSuccessResponseModel().getStatusCode()); - - JSONObject responseJson = new JSONObject(fakeCompletionHandler.getSuccessResponseModel().getBody()); - JSONObject headers = responseJson.getJSONObject("headers"); - - assertEquals("value1", headers.get("Header1")); - assertEquals("value2", headers.get("Header2")); - assertEquals("application/json", headers.get("Accept")); - assertEquals("application/x-www-form-urlencoded", headers.get("Content")); - assertEquals("PUT", responseJson.get("method")); - assertFalse(responseJson.has("body")); - } - - @Test - public void testDelete() throws Exception { - model = new RequestModel.Builder(timestampProvider, uuidProvider).url(DENNA_ECHO).method(RequestMethod.DELETE).headers(headers).build(); - manager.submit(model, null); - latch.await(); - - assertEquals(null, fakeCompletionHandler.getException()); - assertEquals(0, fakeCompletionHandler.getOnErrorCount()); - assertEquals(1, fakeCompletionHandler.getOnSuccessCount()); - assertEquals(200, fakeCompletionHandler.getSuccessResponseModel().getStatusCode()); - - JSONObject responseJson = new JSONObject(fakeCompletionHandler.getSuccessResponseModel().getBody()); - JSONObject headers = responseJson.getJSONObject("headers"); - - assertEquals("value1", headers.get("Header1")); - assertEquals("value2", headers.get("Header2")); - assertEquals("application/json", headers.get("Accept")); - assertEquals("application/x-www-form-urlencoded", headers.get("Content")); - assertEquals("DELETE", responseJson.get("method")); - assertFalse(responseJson.has("body")); - } - -} \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.kt new file mode 100644 index 000000000..39011447f --- /dev/null +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.kt @@ -0,0 +1,240 @@ +package com.emarsys.core.request + +import android.os.Handler +import androidx.test.platform.app.InstrumentationRegistry +import com.emarsys.core.Mapper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.connection.ConnectionProvider +import com.emarsys.core.connection.ConnectionWatchDog +import com.emarsys.core.database.helper.CoreDbHelper +import com.emarsys.core.database.repository.Repository +import com.emarsys.core.database.repository.SqlSpecification +import com.emarsys.core.fake.FakeCompletionHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.core.provider.timestamp.TimestampProvider +import com.emarsys.core.provider.uuid.UUIDProvider +import com.emarsys.core.request.factory.CoreCompletionHandlerMiddlewareProvider +import com.emarsys.core.request.factory.DefaultRunnableFactory +import com.emarsys.core.request.factory.RunnableFactory +import com.emarsys.core.request.model.RequestMethod +import com.emarsys.core.request.model.RequestModel +import com.emarsys.core.request.model.RequestModelRepository +import com.emarsys.core.shard.ShardModel +import com.emarsys.core.shard.ShardModelRepository +import com.emarsys.core.worker.DefaultWorker +import com.emarsys.core.worker.Worker +import com.emarsys.testUtil.ConnectionTestUtils.checkConnection +import com.emarsys.testUtil.DatabaseTestUtils.deleteCoreDatabase +import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext +import com.emarsys.testUtil.TestUrls.DENNA_ECHO +import com.emarsys.testUtil.TimeoutUtils.timeoutRule +import org.json.JSONObject +import org.junit.* +import org.junit.rules.TestRule +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.mock +import java.util.* +import java.util.concurrent.CountDownLatch + +class RequestManagerDennaTest { + private lateinit var manager: RequestManager + private lateinit var headers: MutableMap + private lateinit var model: RequestModel + private lateinit var latch: CountDownLatch + private lateinit var fakeCompletionHandler: FakeCompletionHandler + private lateinit var concurrentHandlerHolderFactory: ConcurrentHandlerHolderFactory + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + private lateinit var uiHandler: Handler + private lateinit var worker: Worker + private lateinit var timestampProvider: TimestampProvider + private lateinit var uuidProvider: UUIDProvider + private lateinit var coreCompletionHandlerMiddlewareProvider: CoreCompletionHandlerMiddlewareProvider + private lateinit var runnableFactory: RunnableFactory + + @Rule + @JvmField + var timeout: TestRule = timeoutRule + + private lateinit var mockRequestModelMapper: Mapper + + @Before + fun init() { + deleteCoreDatabase() + val requestModelMappers: MutableList> = mutableListOf() + mockRequestModelMapper = mock { + on { map(any()) } doAnswer { invocation -> + val args = invocation.arguments + args[0] as RequestModel + } + } + requestModelMappers.add(mockRequestModelMapper) + val context = getTargetContext() + checkConnection(context) + runnableFactory = DefaultRunnableFactory() + uiHandler = Handler(InstrumentationRegistry.getInstrumentation().targetContext.mainLooper) + concurrentHandlerHolderFactory = ConcurrentHandlerHolderFactory(uiHandler) + concurrentHandlerHolder = concurrentHandlerHolderFactory.create() + val connectionWatchDog = ConnectionWatchDog(context, concurrentHandlerHolder) + val coreDbHelper = CoreDbHelper(context, mutableMapOf()) + val requestRepository: Repository = + RequestModelRepository(coreDbHelper, concurrentHandlerHolder) + val shardRepository: Repository = + ShardModelRepository(coreDbHelper, concurrentHandlerHolder) + latch = CountDownLatch(1) + fakeCompletionHandler = FakeCompletionHandler(latch) + val restClient = RestClient( + ConnectionProvider(), + mock(), + mock(), + requestModelMappers.toList(), + concurrentHandlerHolder + ) + coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider( + requestRepository, + uiHandler, + concurrentHandlerHolder, + runnableFactory + ) + worker = DefaultWorker( + requestRepository, + connectionWatchDog, + uiHandler, + fakeCompletionHandler, + restClient, + coreCompletionHandlerMiddlewareProvider + ) + timestampProvider = TimestampProvider() + uuidProvider = UUIDProvider() + manager = RequestManager( + concurrentHandlerHolder, + requestRepository, + shardRepository, + worker as DefaultWorker, + restClient, + mock(), + fakeCompletionHandler, + mock(), + mock(), + mock() + ) + headers = HashMap() + headers["Accept"] = "application/json" + headers["Content"] = "application/x-www-form-urlencoded" + headers["Header1"] = "value1" + headers["Header2"] = "value2" + } + + @After + fun tearDown() { + concurrentHandlerHolder.looper.quit() + } + + @Test + fun testGet() { + model = RequestModel.Builder(timestampProvider, uuidProvider).url(DENNA_ECHO) + .method(RequestMethod.GET).headers( + headers + ).build() + + manager.submit(model, null) + latch.await() + + Assert.assertEquals(null, fakeCompletionHandler.exception) + Assert.assertEquals(0, fakeCompletionHandler.onErrorCount.toLong()) + Assert.assertEquals(1, fakeCompletionHandler.onSuccessCount.toLong()) + Assert.assertEquals(200, fakeCompletionHandler.successResponseModel.statusCode.toLong()) + val responseJson = JSONObject(fakeCompletionHandler.successResponseModel.body!!) + val headers = responseJson["headers"] as JSONObject + Assert.assertEquals("value1", headers["Header1"]) + Assert.assertEquals("value2", headers["Header2"]) + Assert.assertEquals("application/json", headers["Accept"]) + Assert.assertEquals("application/x-www-form-urlencoded", headers["Content"]) + Assert.assertEquals("GET", responseJson["method"]) + Assert.assertFalse(responseJson.has("body")) + } + + @Test + fun testPost() { + val deepPayload = HashMap() + deepPayload["deep1"] = "deepValue1" + deepPayload["deep2"] = "deepValue2" + val payload = HashMap() + payload["key1"] = "val1" + payload["key2"] = "val2" + payload["key3"] = "val3" + payload["key4"] = 4 + payload["deepKey"] = deepPayload + model = RequestModel.Builder(timestampProvider, uuidProvider).url(DENNA_ECHO) + .method(RequestMethod.POST).headers( + headers + ).payload(payload).build() + + manager.submit(model, null) + latch.await() + + Assert.assertEquals(null, fakeCompletionHandler.exception) + Assert.assertEquals(0, fakeCompletionHandler.onErrorCount.toLong()) + Assert.assertEquals(1, fakeCompletionHandler.onSuccessCount.toLong()) + Assert.assertEquals(200, fakeCompletionHandler.successResponseModel.statusCode.toLong()) + val responseJson = JSONObject(fakeCompletionHandler.successResponseModel.body!!) + val headers = responseJson.getJSONObject("headers") + val body = responseJson.getJSONObject("body") + Assert.assertEquals("value1", headers["Header1"]) + Assert.assertEquals("value2", headers["Header2"]) + Assert.assertEquals("application/json", headers["Accept"]) + Assert.assertEquals("application/x-www-form-urlencoded", headers["Content"]) + Assert.assertEquals("POST", responseJson["method"]) + Assert.assertEquals("val1", body["key1"]) + Assert.assertEquals("val2", body["key2"]) + Assert.assertEquals("val3", body["key3"]) + Assert.assertEquals(4, body["key4"]) + val soDeepJson = body.getJSONObject("deepKey") + Assert.assertEquals("deepValue1", soDeepJson.getString("deep1")) + Assert.assertEquals("deepValue2", soDeepJson.getString("deep2")) + } + + @Test + fun testPut() { + model = RequestModel.Builder(timestampProvider, uuidProvider).url(DENNA_ECHO) + .method(RequestMethod.PUT).headers( + headers + ).build() + manager.submit(model, null) + latch.await() + Assert.assertEquals(null, fakeCompletionHandler.exception) + Assert.assertEquals(0, fakeCompletionHandler.onErrorCount.toLong()) + Assert.assertEquals(1, fakeCompletionHandler.onSuccessCount.toLong()) + Assert.assertEquals(200, fakeCompletionHandler.successResponseModel.statusCode.toLong()) + val responseJson = JSONObject(fakeCompletionHandler.successResponseModel!!.body!!) + val headers = responseJson.getJSONObject("headers") + Assert.assertEquals("value1", headers["Header1"]) + Assert.assertEquals("value2", headers["Header2"]) + Assert.assertEquals("application/json", headers["Accept"]) + Assert.assertEquals("application/x-www-form-urlencoded", headers["Content"]) + Assert.assertEquals("PUT", responseJson["method"]) + Assert.assertFalse(responseJson.has("body")) + } + + @Test + fun testDelete() { + model = RequestModel.Builder(timestampProvider, uuidProvider).url(DENNA_ECHO) + .method(RequestMethod.DELETE).headers( + headers + ).build() + manager.submit(model, null) + latch.await() + Assert.assertEquals(null, fakeCompletionHandler.exception) + Assert.assertEquals(0, fakeCompletionHandler.onErrorCount.toLong()) + Assert.assertEquals(1, fakeCompletionHandler.onSuccessCount.toLong()) + Assert.assertEquals(200, fakeCompletionHandler.successResponseModel.statusCode.toLong()) + val responseJson = JSONObject(fakeCompletionHandler.successResponseModel!!.body!!) + val headers = responseJson.getJSONObject("headers") + Assert.assertEquals("value1", headers["Header1"]) + Assert.assertEquals("value2", headers["Header2"]) + Assert.assertEquals("application/json", headers["Accept"]) + Assert.assertEquals("application/x-www-form-urlencoded", headers["Content"]) + Assert.assertEquals("DELETE", responseJson["method"]) + Assert.assertFalse(responseJson.has("body")) + } +} \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt index f57f52787..e86600096 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt @@ -2,9 +2,7 @@ package com.emarsys.core.request import android.os.Handler import android.os.Looper -import com.emarsys.core.Registry -import com.emarsys.core.api.result.CompletionListener -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionState import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.Repository @@ -13,11 +11,13 @@ import com.emarsys.core.database.repository.specification.Everything import com.emarsys.core.fake.FakeCompletionHandler import com.emarsys.core.fake.FakeConnectionWatchDog import com.emarsys.core.fake.FakeRestClient -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.factory.CompletionHandlerProxyProvider import com.emarsys.core.request.factory.CoreCompletionHandlerMiddlewareProvider +import com.emarsys.core.request.factory.DefaultRunnableFactory +import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestMethod import com.emarsys.core.request.model.RequestModel import com.emarsys.core.request.model.RequestModelRepository @@ -29,9 +29,11 @@ import com.emarsys.core.worker.Worker import com.emarsys.testUtil.DatabaseTestUtils import com.emarsys.testUtil.InstrumentationRegistry import com.emarsys.testUtil.TimeoutUtils +import io.kotlintest.be import io.kotlintest.matchers.beEmpty import io.kotlintest.should import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before import org.junit.Rule @@ -62,25 +64,26 @@ class RequestManagerOfflineTest { private lateinit var shardRepository: Repository private lateinit var completionLatch: CountDownLatch private lateinit var completionHandler: FakeCompletionHandler - private lateinit var manager: RequestManager private lateinit var fakeRestClient: RestClient - private lateinit var provider: CoreSdkHandlerProvider - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var holderFactory: ConcurrentHandlerHolderFactory + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var uiHandler: Handler private lateinit var worker: Worker private lateinit var coreCompletionHandlerMiddlewareProvider: CoreCompletionHandlerMiddlewareProvider private lateinit var mockProxyProvider: CompletionHandlerProxyProvider + private lateinit var runnableFactory: RunnableFactory @Before fun setup() { DatabaseTestUtils.deleteCoreDatabase() uiHandler = Handler(Looper.getMainLooper()) + runnableFactory = DefaultRunnableFactory() } @After fun tearDown() { - coreSdkHandler.looper.quit() + concurrentHandlerHolder.looper.quit() DatabaseTestUtils.deleteCoreDatabase() } @@ -97,7 +100,12 @@ class RequestManagerOfflineTest { requestRepository.isEmpty() shouldBe false completionHandler.latch = CountDownLatch(1) - uiHandler.post { watchDog.connectionChangeListener.onConnectionChanged(ConnectionState.CONNECTED, true) } + uiHandler.post { + watchDog.connectionChangeListener.onConnectionChanged( + ConnectionState.CONNECTED, + true + ) + } completionHandler.latch.await() @@ -123,7 +131,8 @@ class RequestManagerOfflineTest { fun test_alwaysOnline_withExpiredRequests() { connectionStates = arrayOf(true) requestResults = arrayOf(200, 200, 200) - requestModels = arrayOf(normal(1), expired(1), normal(2), expired(2), expired(3), expired(4), normal(3)) + requestModels = + arrayOf(normal(1), expired(1), normal(2), expired(2), expired(3), expired(4), normal(3)) watchDogCountDown = 3 completionHandlerCountDown = 7 @@ -206,47 +215,63 @@ class RequestManagerOfflineTest { @Test fun test_exception_stopsQueue() { connectionStates = arrayOf(true) - requestResults = arrayOf(200, 300, 200, IOException()) + requestResults = arrayOf(200, 300, IOException(), 200) val lastNormal = normal(4) requestModels = arrayOf(normal(1), normal(2), normal(3), lastNormal) - watchDogCountDown = 4 - completionHandlerCountDown = 3 + watchDogCountDown = 3 + completionHandlerCountDown = 2 prepareTestCaseAndWait() - completionHandler.onSuccessCount shouldBe 3 - completionHandler.onErrorCount shouldBe 0 + completionHandler.onSuccessCount shouldBe 2 + completionHandler.onErrorCount shouldBe 1 requestRepository.isEmpty() shouldBe false + runBlocking { + requestRepository.remove(FilterByRequestIds(arrayOf(lastNormal.id))) + } - requestRepository.remove(FilterByRequestIds(arrayOf(lastNormal.id))) - - requestRepository.query(Everything()) should beEmpty() + requestRepository.query(Everything()).size should be(1) } @Suppress("UNCHECKED_CAST") private fun prepareTestCaseAndWait() { watchDogLatch = CountDownLatch(watchDogCountDown) watchDog = FakeConnectionWatchDog(watchDogLatch, *connectionStates) - val coreDbHelper = CoreDbHelper(InstrumentationRegistry.getTargetContext(), HashMap()) - requestRepository = RequestModelRepository(coreDbHelper) - shardRepository = ShardModelRepository(coreDbHelper) + val uiHandler: Handler = Handler(Looper.getMainLooper()) + holderFactory = ConcurrentHandlerHolderFactory(uiHandler) + val concurrentHandlerHolder = holderFactory.create() + requestRepository = RequestModelRepository(coreDbHelper, concurrentHandlerHolder) + shardRepository = ShardModelRepository(coreDbHelper, concurrentHandlerHolder) completionLatch = CountDownLatch(completionHandlerCountDown) completionHandler = FakeCompletionHandler(completionLatch) fakeRestClient = FakeRestClient(*requestResults) - - provider = CoreSdkHandlerProvider() - coreSdkHandler = provider.provideHandler() + concurrentHandlerHolder = holderFactory.create() mockProxyProvider = mock() - coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider(requestRepository, uiHandler, coreSdkHandler) - worker = DefaultWorker(requestRepository, watchDog, uiHandler, completionHandler, fakeRestClient, coreCompletionHandlerMiddlewareProvider) + coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider( + requestRepository, + uiHandler, + concurrentHandlerHolder, + runnableFactory + ) + worker = DefaultWorker( + requestRepository, + watchDog, + uiHandler, + completionHandler, + fakeRestClient, + coreCompletionHandlerMiddlewareProvider + ) + + concurrentHandlerHolder.sdkScope.launch { + requestModels.forEach { + requestRepository.add(it) + } - coreSdkHandler.post { - requestModels.forEach(requestRepository::add) worker.run() } @@ -257,17 +282,19 @@ class RequestManagerOfflineTest { private fun normal(orderId: Int): RequestModel { val timestampProvider = TimestampProvider() val uuidProvider = UUIDProvider() - return RequestModel.Builder(timestampProvider, uuidProvider).url(URL + "normal/" + orderId).method(RequestMethod.GET).ttl(60000).build() + return RequestModel.Builder(timestampProvider, uuidProvider).url(URL + "normal/" + orderId) + .method(RequestMethod.GET).ttl(60000).build() } private fun expired(orderId: Int): RequestModel { return RequestModel( - URL + "expired/" + orderId, - RequestMethod.GET, - HashMap(), - HashMap(), - System.currentTimeMillis() - 5000, - 100, - UUIDProvider().provideId()) + URL + "expired/" + orderId, + RequestMethod.GET, + HashMap(), + HashMap(), + System.currentTimeMillis() - 5000, + 100, + UUIDProvider().provideId() + ) } } diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt index 2831e462b..93039e69e 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt @@ -6,19 +6,16 @@ import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.Mapper import com.emarsys.core.Registry import com.emarsys.core.api.result.CompletionListener -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionProvider import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.fake.FakeCompletionHandler -import com.emarsys.core.fake.FakeRunnableFactory -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider -import com.emarsys.core.request.factory.CompletionHandlerProxyProvider -import com.emarsys.core.request.factory.CoreCompletionHandlerMiddlewareProvider -import com.emarsys.core.request.factory.ScopeDelegatorCompletionHandlerProvider +import com.emarsys.core.request.factory.* import com.emarsys.core.request.model.RequestMethod import com.emarsys.core.request.model.RequestModel import com.emarsys.core.shard.ShardModel @@ -32,7 +29,11 @@ import com.emarsys.testUtil.RetryUtils.retryRule import com.emarsys.testUtil.TestUrls.DENNA_ECHO import com.emarsys.testUtil.TestUrls.customResponse import com.emarsys.testUtil.TimeoutUtils.timeoutRule +import com.emarsys.testUtil.mockito.ThreadSpy import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.junit.* import org.junit.rules.TestRule import org.mockito.kotlin.* @@ -49,8 +50,8 @@ class RequestManagerTest { private lateinit var completionHandlerLatch: CountDownLatch private lateinit var runnableFactoryLatch: CountDownLatch private lateinit var mockConnectionWatchDog: ConnectionWatchDog - private lateinit var coreSdkHandlerProvider: CoreSdkHandlerProvider - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolderFactory: ConcurrentHandlerHolderFactory + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var uiHandler: Handler private lateinit var mockRequestRepository: Repository private lateinit var mockShardRepository: Repository @@ -64,6 +65,9 @@ class RequestManagerTest { private lateinit var mockCompletionHandlerProxyProvider: CompletionHandlerProxyProvider private lateinit var mockScopeDelegatorCompletionHandlerProvider: ScopeDelegatorCompletionHandlerProvider private lateinit var mockScope: CoroutineScope + private lateinit var runnableFactory: RunnableFactory + private lateinit var callbackRegistryThreadSpy: ThreadSpy> + private lateinit var shardRepositoryThreadSpy: ThreadSpy> @Rule @JvmField @@ -74,7 +78,7 @@ class RequestManagerTest { var retry: TestRule = retryRule @Before - fun init() { + fun setUp() { deleteCoreDatabase() val requestModelMappers: MutableList> = mutableListOf() mockRequestModelMapper = mock() @@ -85,15 +89,26 @@ class RequestManagerTest { } val context = getTargetContext() checkConnection(context) - coreSdkHandlerProvider = CoreSdkHandlerProvider() - coreSdkHandler = coreSdkHandlerProvider.provideHandler() + runnableFactory = DefaultRunnableFactory() uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolderFactory = ConcurrentHandlerHolderFactory(uiHandler) + concurrentHandlerHolder = concurrentHandlerHolderFactory.create() mockConnectionWatchDog = mock() mockRequestRepository = mock { on { isEmpty() } doReturn true } + callbackRegistryThreadSpy = ThreadSpy() + shardRepositoryThreadSpy = ThreadSpy() + mockShardRepository = mock() - mockCallbackRegistry = mock() + + runBlocking { + whenever(mockShardRepository.add(any())).thenAnswer(shardRepositoryThreadSpy) + } + mockCallbackRegistry = mock { + on { register(any(), isNull()) }.doAnswer(callbackRegistryThreadSpy) + } + completionHandlerLatch = CountDownLatch(1) fakeCompletionHandler = FakeCompletionHandler(completionHandlerLatch) mockDefaultHandler = mock() @@ -102,14 +117,14 @@ class RequestManagerTest { mock(), mock(), requestModelMappers.toList(), - Handler(Looper.getMainLooper()), - CoreSdkHandlerProvider().provideHandler() + concurrentHandlerHolder ) mockRestClient = mock() coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider( mockRequestRepository, uiHandler, - coreSdkHandler + concurrentHandlerHolder, + runnableFactory ) worker = DefaultWorker( mockRequestRepository, @@ -127,7 +142,7 @@ class RequestManagerTest { } mockScope = mock() manager = RequestManager( - coreSdkHandler, + concurrentHandlerHolder, mockRequestRepository, mockShardRepository, worker, @@ -141,7 +156,6 @@ class RequestManagerTest { timestampProvider = TimestampProvider() uuidProvider = UUIDProvider() runnableFactoryLatch = CountDownLatch(1) - manager.runnableFactory = FakeRunnableFactory(runnableFactoryLatch) val headers: MutableMap = HashMap() headers["accept"] = "application/json" headers["content"] = "application/x-www-form-urlencoded" @@ -160,32 +174,40 @@ class RequestManagerTest { @After fun tearDown() { - coreSdkHandler.looper.quit() + concurrentHandlerHolder.looper.quit() } @Test fun testSubmit_shouldAddRequestModelToQueue() { - manager.submit(requestModel, null) - runnableFactoryLatch.await() - verify(mockRequestRepository).add(requestModel) + manager.submit( + requestModel, + null + ) + runBlocking { + verify(mockRequestRepository, timeout(100)).add(requestModel) + } } @Test fun testSubmit_withRequestModel_shouldInvokeRunOnTheWorker() { val worker = mock() ReflectionTestUtils.setInstanceField(manager, "worker", worker) + manager.submit(requestModel, null) - runnableFactoryLatch.await() - verify(worker).run() + + verify(worker, timeout(100)).run() + } @Test fun testSubmit_withRequestModel_executesRunnableOn_CoreSDKHandlerThread() { - val fakeRunnableFactory = FakeRunnableFactory(runnableFactoryLatch, true) - manager.runnableFactory = fakeRunnableFactory - manager.submit(requestModel, null) - runnableFactoryLatch.await() - Assert.assertEquals(1, fakeRunnableFactory.executionCount.toLong()) + runBlocking { + withContext(Dispatchers.IO) { + manager.submit(requestModel, null) + } + } + + callbackRegistryThreadSpy.verifyCalledOnCoreSdkThread() } @Test @@ -205,23 +227,25 @@ class RequestManagerTest { @Test fun testSubmit_withRequestModel_shouldRegisterCallbackToRegistry() { val completionListener = mock() - + manager.submit(requestModel, completionListener) - runnableFactoryLatch.await() - verify(mockCallbackRegistry).register(requestModel, completionListener) + verify(mockCallbackRegistry, timeout(100)).register(requestModel, completionListener) + } @Test fun testSubmit_withRequestModel_shouldRegister_null_ToRegistryAsWell() { manager.submit(requestModel, null) - runnableFactoryLatch.await() - verify(mockCallbackRegistry).register(requestModel, null) + + verify(mockCallbackRegistry, timeout(100)).register(requestModel, null) } @Test fun testSubmitNow_withoutCompletionHandler_shouldCallProxyProviderForCompletionHandler() { - whenever(mockScopeDelegatorCompletionHandlerProvider.provide(any(), any())).doReturn(mockDefaultHandler) + whenever(mockScopeDelegatorCompletionHandlerProvider.provide(any(), any())).doReturn( + mockDefaultHandler + ) manager.submitNow(requestModel) verify(mockScopeDelegatorCompletionHandlerProvider).provide(mockDefaultHandler, mockScope) verify(mockCompletionHandlerProxyProvider, times(2)) @@ -232,7 +256,10 @@ class RequestManagerTest { @Test fun testSubmitNow_shouldCallProxyProviderForCompletionHandler() { manager.submitNow(requestModel, fakeCompletionHandler) - verify(mockScopeDelegatorCompletionHandlerProvider).provide(fakeCompletionHandler, mockScope) + verify(mockScopeDelegatorCompletionHandlerProvider).provide( + fakeCompletionHandler, + mockScope + ) verify(mockCompletionHandlerProxyProvider).provideProxy(null, fakeCompletionHandler) verify(mockRestClient).execute(requestModel, mockDefaultHandler) } @@ -241,7 +268,10 @@ class RequestManagerTest { fun testSubmitNow_shouldCallProxyProviderForCompletionHandler_withScope() { val mockOtherScope: CoroutineScope = mock() manager.submitNow(requestModel, fakeCompletionHandler, mockOtherScope) - verify(mockScopeDelegatorCompletionHandlerProvider).provide(fakeCompletionHandler, mockOtherScope) + verify(mockScopeDelegatorCompletionHandlerProvider).provide( + fakeCompletionHandler, + mockOtherScope + ) verify(mockCompletionHandlerProxyProvider).provideProxy(null, fakeCompletionHandler) verify(mockRestClient).execute(requestModel, mockDefaultHandler) } @@ -255,6 +285,7 @@ class RequestManagerTest { @Test fun testSubmitNow_shouldCallRestClient_withDefaultHandler() { manager.submitNow(requestModel) + verify(mockRestClient).execute(requestModel, mockDefaultHandler) } @@ -266,7 +297,8 @@ class RequestManagerTest { whenever(mockConnectionWatchDog.isConnected).thenReturn(true, false) whenever(mockRequestRepository.isEmpty()).thenReturn(false, false, true) whenever( - mockRequestRepository.query(any())).thenReturn(listOf(requestModel), emptyList()) + mockRequestRepository.query(any()) + ).thenReturn(listOf(requestModel), emptyList()) manager.submit(requestModel, null) completionHandlerLatch.await() Assert.assertEquals(requestModel.id, fakeCompletionHandler.errorId) @@ -296,16 +328,18 @@ class RequestManagerTest { @Test fun testSubmit_shouldAddShardModelToDatabase() { manager.submit(shardModel) - runnableFactoryLatch.await() - verify(mockShardRepository).add(shardModel) + runBlocking { + verify(mockShardRepository, timeout(100)).add(shardModel) + } } @Test fun testSubmit_withShardModel_executesRunnableOn_CoreSDKHandlerThread() { - val fakeRunnableFactory = FakeRunnableFactory(runnableFactoryLatch, true) - manager.runnableFactory = fakeRunnableFactory - manager.submit(shardModel) - runnableFactoryLatch.await() - Assert.assertEquals(1, fakeRunnableFactory.executionCount.toLong()) + runBlocking { + withContext(Dispatchers.IO) { + manager.submit(shardModel) + } + } + shardRepositoryThreadSpy.verifyCalledOnCoreSdkThread() } } \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt index c0d2cf6b9..b479a7ae6 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt @@ -3,10 +3,10 @@ package com.emarsys.core.request import android.os.Handler import android.os.Looper import com.emarsys.core.Mapper -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionProvider import com.emarsys.core.fake.FakeCompletionHandler -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.model.RequestMethod @@ -43,7 +43,7 @@ class RestClientTest { private lateinit var mockRequestModelMapper: Mapper private lateinit var requestModelMappers: List> private lateinit var uiHandler: Handler - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -59,7 +59,7 @@ class RestClientTest { mockResponseHandlersProcessor = mock() mockRequestModelMapper = mock() as Mapper uiHandler = Handler(Looper.getMainLooper()) - coreSdkHandler = CoreSdkHandlerProvider().provideHandler() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() whenever(mockRequestModelMapper.map(any())).thenAnswer { invocation -> val args = invocation.arguments @@ -67,7 +67,13 @@ class RestClientTest { } requestModelMappers = listOf(mockRequestModelMapper) - client = RestClient(connectionProvider, mockTimestampProvider, mockResponseHandlersProcessor, requestModelMappers, uiHandler, coreSdkHandler) + client = RestClient( + connectionProvider, + mockTimestampProvider, + mockResponseHandlersProcessor, + requestModelMappers, + concurrentHandlerHolder + ) latch = CountDownLatch(1) } @@ -122,7 +128,13 @@ class RestClientTest { } requestModelMappers = listOf(mockRequestModelMapper1, mockRequestModelMapper2) - client = RestClient(connectionProvider, mockTimestampProvider, mockResponseHandlersProcessor, requestModelMappers, uiHandler, coreSdkHandler) + client = RestClient( + connectionProvider, + mockTimestampProvider, + mockResponseHandlersProcessor, + requestModelMappers, + concurrentHandlerHolder + ) client.execute(requestModel, mock()) verify(mockRequestModelMapper1).map(requestModel) diff --git a/core/src/androidTest/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProviderTest.kt b/core/src/androidTest/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProviderTest.kt index 780813e53..1bbb207de 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProviderTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProviderTest.kt @@ -3,9 +3,10 @@ package com.emarsys.core.request.factory import android.os.Handler import android.os.Looper import com.emarsys.core.CoreCompletionHandler +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseModel import com.emarsys.core.worker.CoreCompletionHandlerMiddleware @@ -31,21 +32,20 @@ class CoreCompletionHandlerMiddlewareProviderTest { val timeout: TestRule = TimeoutUtils.timeoutRule private lateinit var mockRequestRepository: Repository - private lateinit var mockUiHandler: Handler - private lateinit var mockCoreSdkHandler: CoreSdkHandler + private lateinit var uiHandler: Handler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var mockCoreCompletionHandler: CoreCompletionHandler private lateinit var mockWorker: Worker private lateinit var mockRequestModel: RequestModel private lateinit var mockResponseModel: ResponseModel - private lateinit var coreCompletionHandlerMiddlewareProvider: CoreCompletionHandlerMiddlewareProvider private lateinit var latch: CountDownLatch + private lateinit var runnableFactory: RunnableFactory @Before @Suppress("UNCHECKED_CAST") fun setUp() { latch = CountDownLatch(1) - mockRequestModel = mock { whenever(it.id).thenReturn("requestId") } @@ -53,19 +53,29 @@ class CoreCompletionHandlerMiddlewareProviderTest { whenever(it.requestModel).thenReturn(mockRequestModel) } mockRequestRepository = (mock()) - mockUiHandler = Handler(Looper.getMainLooper()) - mockCoreSdkHandler = CoreSdkHandler(Handler(Looper.getMainLooper())) + runnableFactory = DefaultRunnableFactory() + uiHandler = Handler(Looper.getMainLooper()) + + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() mockCoreCompletionHandler = mock { whenever(it.onSuccess(any(), any())).thenAnswer { latch.countDown() } } mockWorker = mock() - coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider(mockRequestRepository, mockUiHandler, mockCoreSdkHandler) + coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider( + mockRequestRepository, + uiHandler, + concurrentHandlerHolder, + runnableFactory + ) } @Test fun testCreateCompletionHandler_shouldReturnWithMiddleware_whenWorkerIsPresent() { - val result = coreCompletionHandlerMiddlewareProvider.provideProxy(mockWorker, mockCoreCompletionHandler) + val result = coreCompletionHandlerMiddlewareProvider.provideProxy( + mockWorker, + mockCoreCompletionHandler + ) result should beInstanceOf(CoreCompletionHandlerMiddleware::class) diff --git a/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.java b/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.java index c485f3ca6..03efedc9a 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.java +++ b/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.java @@ -1,14 +1,31 @@ package com.emarsys.core.request.model; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_HEADERS; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_METHOD; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_PAYLOAD; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_REQUEST_ID; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TIMESTAMP; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TTL; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_URL; +import static com.emarsys.core.util.serialization.SerializationUtils.serializableToBlob; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.os.Handler; +import android.os.Looper; +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; import com.emarsys.core.database.CoreSQLiteDatabase; import com.emarsys.core.database.DatabaseContract; import com.emarsys.core.database.helper.CoreDbHelper; import com.emarsys.core.database.repository.specification.Everything; import com.emarsys.core.database.trigger.TriggerKey; +import com.emarsys.core.handler.ConcurrentHandlerHolder; import com.emarsys.testUtil.DatabaseTestUtils; import com.emarsys.testUtil.InstrumentationRegistry; import com.emarsys.testUtil.TimeoutUtils; @@ -23,19 +40,6 @@ import java.util.HashMap; import java.util.List; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_HEADERS; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_METHOD; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_PAYLOAD; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_REQUEST_ID; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TIMESTAMP; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TTL; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_URL; -import static com.emarsys.core.util.serialization.SerializationUtils.serializableToBlob; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - public class RequestModelRepositoryTest { @Rule @@ -55,8 +59,10 @@ public class RequestModelRepositoryTest { private RequestModel request; private RequestModelRepository repository; private Context context; + private ConcurrentHandlerHolder concurrentHandlerHolder; private HashMap headers; private HashMap payload; + private Handler uiHandler; @Before public void init() { @@ -64,7 +70,9 @@ public void init() { context = InstrumentationRegistry.getTargetContext(); CoreDbHelper coreDbHelper = new CoreDbHelper(context, new HashMap>()); - repository = new RequestModelRepository(coreDbHelper); + uiHandler = new Handler(Looper.getMainLooper()); + concurrentHandlerHolder = new ConcurrentHandlerHolderFactory(uiHandler).create(); + repository = new RequestModelRepository(coreDbHelper, concurrentHandlerHolder); payload = new HashMap<>(); diff --git a/core/src/androidTest/java/com/emarsys/core/request/model/specification/FilterByUrlPatternTest.kt b/core/src/androidTest/java/com/emarsys/core/request/model/specification/FilterByUrlPatternTest.kt index d6ea4706c..8d760cfc3 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/model/specification/FilterByUrlPatternTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/model/specification/FilterByUrlPatternTest.kt @@ -1,7 +1,11 @@ package com.emarsys.core.request.model.specification +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.specification.Everything +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.model.RequestModel @@ -11,6 +15,7 @@ import com.emarsys.testUtil.InstrumentationRegistry import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.matchers.collections.shouldContainAll import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -38,7 +43,10 @@ class FilterByUrlPatternTest { val context = InstrumentationRegistry.getTargetContext().applicationContext val coreDbHelper = CoreDbHelper(context, mutableMapOf()) - repository = RequestModelRepository(coreDbHelper) + val uiHandler = Handler(Looper.getMainLooper()) + val concurrentHandlerHolder: ConcurrentHandlerHolder = + ConcurrentHandlerHolderFactory(uiHandler).create() + repository = RequestModelRepository(coreDbHelper, concurrentHandlerHolder) } @Test @@ -64,14 +72,21 @@ class FilterByUrlPatternTest { fun testQueryUsingFilterByUrlPattern() { specification = FilterByUrlPattern("https://emarsys.com/%") - val expectedRequestModel = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/1").build() - val requestModel1 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://google.com/2").build() - val expectedRequestModel2 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/3").build() - - repository.add(expectedRequestModel) - repository.add(requestModel1) - repository.add(expectedRequestModel2) - + val expectedRequestModel = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/1") + .build() + val requestModel1 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://google.com/2") + .build() + val expectedRequestModel2 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/3") + .build() + + runBlocking { + repository.add(expectedRequestModel) + repository.add(requestModel1) + repository.add(expectedRequestModel2) + } val resultList = repository.query(specification) resultList.size shouldBe 2 @@ -82,15 +97,21 @@ class FilterByUrlPatternTest { fun testDeleteUsingFilterByUrlPattern() { specification = FilterByUrlPattern("https://emarsys.com/%") - val requestModel1 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/1").build() - val expectedRequestModel = RequestModel.Builder(timestampProvider, uuidProvider).url("https://google.com/2").build() - val requestModel2 = RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/3").build() - - repository.add(requestModel1) - repository.add(expectedRequestModel) - repository.add(requestModel2) - repository.remove(specification) - + val requestModel1 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/1") + .build() + val expectedRequestModel = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://google.com/2") + .build() + val requestModel2 = + RequestModel.Builder(timestampProvider, uuidProvider).url("https://emarsys.com/3") + .build() + runBlocking { + repository.add(requestModel1) + repository.add(expectedRequestModel) + repository.add(requestModel2) + repository.remove(specification) + } val resultList = repository.query(Everything()) resultList.size shouldBe 1 diff --git a/core/src/androidTest/java/com/emarsys/core/shard/ShardModelRepositoryTest.kt b/core/src/androidTest/java/com/emarsys/core/shard/ShardModelRepositoryTest.kt index 3f0d22154..7e07fed71 100644 --- a/core/src/androidTest/java/com/emarsys/core/shard/ShardModelRepositoryTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/shard/ShardModelRepositoryTest.kt @@ -2,12 +2,16 @@ package com.emarsys.core.shard import android.content.Context import android.database.Cursor +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.DatabaseContract.SHARD_COLUMN_DATA import com.emarsys.core.database.DatabaseContract.SHARD_COLUMN_ID import com.emarsys.core.database.DatabaseContract.SHARD_COLUMN_TIMESTAMP import com.emarsys.core.database.DatabaseContract.SHARD_COLUMN_TTL import com.emarsys.core.database.DatabaseContract.SHARD_COLUMN_TYPE import com.emarsys.core.database.helper.CoreDbHelper +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.util.serialization.SerializationUtils.serializableToBlob import com.emarsys.testUtil.DatabaseTestUtils import com.emarsys.testUtil.InstrumentationRegistry @@ -27,6 +31,7 @@ class ShardModelRepositoryTest { private lateinit var repository: ShardModelRepository private lateinit var payload: Map private lateinit var context: Context + private lateinit var uiHandler: Handler @Rule @JvmField @@ -42,10 +47,12 @@ class ShardModelRepositoryTest { @Before fun init() { DatabaseTestUtils.deleteCoreDatabase() - + uiHandler = Handler(Looper.getMainLooper()) context = InstrumentationRegistry.getTargetContext() - - repository = ShardModelRepository(CoreDbHelper(context, mutableMapOf())) + val concurrentHandlerHolder: ConcurrentHandlerHolder = + ConcurrentHandlerHolderFactory(uiHandler).create() + repository = + ShardModelRepository(CoreDbHelper(context, mutableMapOf()), concurrentHandlerHolder) payload = mutableMapOf().apply { this["payload1"] = "payload_value1" diff --git a/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardIdsTest.kt b/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardIdsTest.kt index 52ad1ef57..6bd9e9572 100644 --- a/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardIdsTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardIdsTest.kt @@ -1,14 +1,19 @@ package com.emarsys.core.shard.specification import android.content.Context +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.specification.Everything +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.shard.ShardModel import com.emarsys.core.shard.ShardModelRepository import com.emarsys.testUtil.DatabaseTestUtils import com.emarsys.testUtil.InstrumentationRegistry import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -19,6 +24,7 @@ class FilterByShardIdsTest { private lateinit var context: Context private lateinit var originalShardList: List private lateinit var shardModelRepository: ShardModelRepository + private lateinit var concurrentHadlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -28,31 +34,38 @@ class FilterByShardIdsTest { fun init() { DatabaseTestUtils.deleteCoreDatabase() context = InstrumentationRegistry.getTargetContext().applicationContext + concurrentHadlerHolder = + ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() originalShardList = listOf( - ShardModel("id1", "type1", mapOf(), 0, 0), - ShardModel("id2", "type2", mapOf(), 1, 10), - ShardModel("id3", "type3", mapOf("key1" to "value1", "key2" to 333), 2, 20), - ShardModel("id4", "type4", mapOf(), 3, 30) + ShardModel("id1", "type1", mapOf(), 0, 0), + ShardModel("id2", "type2", mapOf(), 1, 10), + ShardModel("id3", "type3", mapOf("key1" to "value1", "key2" to 333), 2, 20), + ShardModel("id4", "type4", mapOf(), 3, 30) ) val coreDbHelper = CoreDbHelper(context, mutableMapOf()) - shardModelRepository = ShardModelRepository(coreDbHelper) + shardModelRepository = ShardModelRepository(coreDbHelper, concurrentHadlerHolder) } @Test(expected = IllegalArgumentException::class) fun testDeleteRow_withInvalidArgument() { - shardModelRepository.remove(FilterByShardIds(null)) + runBlocking { + shardModelRepository.remove(FilterByShardIds(null)) + } } @Test fun testDeleteRows() { val deletionShardList = originalShardList.subList(1, 3) val expectedShardList = originalShardList - deletionShardList - originalShardList.forEach(shardModelRepository::add) - - shardModelRepository.remove(FilterByShardIds(deletionShardList)) + runBlocking { + originalShardList.forEach { + shardModelRepository.add(it) + } + shardModelRepository.remove(FilterByShardIds(deletionShardList)) + } val resultShardList = shardModelRepository.query(Everything()) resultShardList shouldBe expectedShardList @@ -60,9 +73,12 @@ class FilterByShardIdsTest { @Test fun testDeleteRows_deletesNothingWhenThereIsNoMatch() { - originalShardList.forEach(shardModelRepository::add) - shardModelRepository.remove(FilterByShardIds(listOf())) - + runBlocking { + originalShardList.forEach { + shardModelRepository.add(it) + } + shardModelRepository.remove(FilterByShardIds(listOf())) + } val resultShardList = shardModelRepository.query(Everything()) resultShardList shouldBe originalShardList @@ -72,9 +88,20 @@ class FilterByShardIdsTest { fun testQueryRows() { val deletionShardList = originalShardList.subList(2, 4) val expectedShardList = originalShardList - deletionShardList - originalShardList.forEach(shardModelRepository::add) - - val result = shardModelRepository.query(FilterByShardIds(listOf(originalShardList[0], originalShardList[1]))) + runBlocking { + originalShardList.forEach { + shardModelRepository.add(it) + } + } + + val result = shardModelRepository.query( + FilterByShardIds( + listOf( + originalShardList[0], + originalShardList[1] + ) + ) + ) result shouldBe expectedShardList } diff --git a/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardTypeTest.kt b/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardTypeTest.kt index e4494efad..96bea6139 100644 --- a/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardTypeTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardTypeTest.kt @@ -1,13 +1,18 @@ package com.emarsys.core.shard.specification +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.specification.Everything +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.shard.ShardModel import com.emarsys.core.shard.ShardModelRepository import com.emarsys.testUtil.DatabaseTestUtils import com.emarsys.testUtil.InstrumentationRegistry import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -26,20 +31,28 @@ class FilterByShardTypeTest { private lateinit var specification: FilterByShardType private lateinit var shardList: MutableList private lateinit var repository: ShardModelRepository + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + @Before fun setUp() { DatabaseTestUtils.deleteCoreDatabase() specification = FilterByShardType(TYPE) val context = InstrumentationRegistry.getTargetContext().applicationContext val coreDbHelper = CoreDbHelper(context, mutableMapOf()) - repository = ShardModelRepository(coreDbHelper) + concurrentHandlerHolder = + ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() + repository = ShardModelRepository(coreDbHelper, concurrentHandlerHolder) shardList = mutableListOf( - ShardModel("a1", "button_click", mapOf(), 0, 0), - ShardModel("a2", "button_click", mapOf(), 0, 0), - ShardModel("a2", "button_click", mapOf("key" to 22, "key2" to "value"), 0, 0), - ShardModel("a4", "not_button_click", mapOf("key" to 11, "key2" to "asdasd"), 0, 0) + ShardModel("a1", "button_click", mapOf(), 0, 0), + ShardModel("a2", "button_click", mapOf(), 0, 0), + ShardModel("a2", "button_click", mapOf("key" to 22, "key2" to "value"), 0, 0), + ShardModel("a4", "not_button_click", mapOf("key" to 11, "key2" to "asdasd"), 0, 0) ) - shardList.forEach(repository::add) + runBlocking { + shardList.forEach { + repository.add(it) + } + } } @Test(expected = IllegalArgumentException::class) @@ -73,8 +86,9 @@ class FilterByShardTypeTest { @Test fun testDeleteUsingFilterByShardType() { val expectedList = shardList.filterNot { x -> (x.type == "button_click") } - - repository.remove(FilterByShardType("button_click")) + runBlocking { + repository.remove(FilterByShardType("button_click")) + } val resultList = repository.query(Everything()) diff --git a/core/src/androidTest/java/com/emarsys/core/util/FileDownloaderTest.kt b/core/src/androidTest/java/com/emarsys/core/util/FileDownloaderTest.kt index c71718ea0..40aec238b 100644 --- a/core/src/androidTest/java/com/emarsys/core/util/FileDownloaderTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/util/FileDownloaderTest.kt @@ -1,9 +1,10 @@ package com.emarsys.core.util import android.content.Context -import com.emarsys.core.concurrency.CoreSdkHandlerProvider -import com.emarsys.core.di.core -import com.emarsys.core.handler.CoreSdkHandler +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.testUtil.FileTestUtils import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext import com.emarsys.testUtil.RetryUtils.retryRule @@ -11,7 +12,6 @@ import com.emarsys.testUtil.TestUrls.LARGE_IMAGE import com.emarsys.testUtil.TestUrls.customResponse import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.shouldBe -import kotlinx.coroutines.android.asCoroutineDispatcher import org.junit.Assert.* import org.junit.Before import org.junit.Rule @@ -68,10 +68,12 @@ class FileDownloaderTest { @Test fun testDownload_downloadedAndRemoteFileShouldBeTheSame() { + val uiHandler = Handler(Looper.getMainLooper()) val latch = CountDownLatch(1) - val coreSdkHandler: CoreSdkHandler = CoreSdkHandlerProvider().provideHandler() + val concurrentHandlerHolder: ConcurrentHandlerHolder = + ConcurrentHandlerHolderFactory(uiHandler).create() - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { val path: String = LARGE_IMAGE val filePath = fileDownloader.download(path, 3)!! diff --git a/core/src/androidTest/java/com/emarsys/core/util/batch/BatchingShardTriggerTest.kt b/core/src/androidTest/java/com/emarsys/core/util/batch/BatchingShardTriggerTest.kt index 4c550278c..8fc59d8aa 100644 --- a/core/src/androidTest/java/com/emarsys/core/util/batch/BatchingShardTriggerTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/util/batch/BatchingShardTriggerTest.kt @@ -13,6 +13,7 @@ import com.emarsys.core.util.batch.BatchingShardTrigger.RequestStrategy.TRANSIEN import com.emarsys.core.util.predicate.Predicate import com.emarsys.testUtil.TimeoutUtils import com.emarsys.testUtil.mockito.whenever +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -84,16 +85,17 @@ class BatchingShardTriggerTest { val (requestModel1, requestModel2, requestModel3) = requests persistentTrigger().run() - - inOrder(mockRequestManager, mockRepository).run { - this.verify(mockRepository).query(mockQuerySpecification) - this.verify(mockRequestManager, Mockito.timeout(50)).submit(requestModel1, null) - this.verify(mockRepository).remove(FilterByShardIds(listOf(shard1))) - this.verify(mockRequestManager, Mockito.timeout(50)).submit(requestModel2, null) - this.verify(mockRepository).remove(FilterByShardIds(listOf(shard2))) - this.verify(mockRequestManager, Mockito.timeout(50)).submit(requestModel3, null) - this.verify(mockRepository).remove(FilterByShardIds(listOf(shard3))) - this.verifyNoMoreInteractions() + runBlocking { + inOrder(mockRequestManager, mockRepository).run { + this.verify(mockRepository).query(mockQuerySpecification) + this.verify(mockRequestManager, Mockito.timeout(50)).submit(requestModel1, null) + this.verify(mockRepository).remove(FilterByShardIds(listOf(shard1))) + this.verify(mockRequestManager, Mockito.timeout(50)).submit(requestModel2, null) + this.verify(mockRepository).remove(FilterByShardIds(listOf(shard2))) + this.verify(mockRequestManager, Mockito.timeout(50)).submit(requestModel3, null) + this.verify(mockRepository).remove(FilterByShardIds(listOf(shard3))) + this.verifyNoMoreInteractions() + } } } @@ -153,7 +155,8 @@ class BatchingShardTriggerTest { private fun anyTrigger() = persistentTrigger() - private fun trigger(requestStrategy: BatchingShardTrigger.RequestStrategy) = BatchingShardTrigger( + private fun trigger(requestStrategy: BatchingShardTrigger.RequestStrategy) = + BatchingShardTrigger( mockRepository, mockPredicate, mockQuerySpecification, @@ -162,6 +165,6 @@ class BatchingShardTriggerTest { mockRequestManager, requestStrategy, mockConnectionWatchDog - ) + ) } \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/util/log/LoggerTest.kt b/core/src/androidTest/java/com/emarsys/core/util/log/LoggerTest.kt index 913287508..825c0af89 100644 --- a/core/src/androidTest/java/com/emarsys/core/util/log/LoggerTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/util/log/LoggerTest.kt @@ -1,10 +1,12 @@ package com.emarsys.core.util.log -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.di.* -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.shard.ShardModel @@ -13,6 +15,7 @@ import com.emarsys.core.util.log.entry.LogEntry import com.emarsys.testUtil.TimeoutUtils import com.emarsys.testUtil.mockito.ThreadSpy import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before import org.junit.Rule @@ -35,7 +38,7 @@ class LoggerTest { @JvmField val timeout: TestRule = TimeoutUtils.timeoutRule - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var shardRepositoryMock: Repository private lateinit var timestampProviderMock: TimestampProvider private lateinit var uuidProviderMock: UUIDProvider @@ -47,7 +50,8 @@ class LoggerTest { @Before @Suppress("UNCHECKED_CAST") fun init() { - coreSdkHandler = CoreSdkHandlerProvider().provideHandler() + val uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() shardRepositoryMock = mock() timestampProviderMock = mock().apply { whenever(provideTimestamp()).thenReturn(TIMESTAMP) @@ -58,18 +62,18 @@ class LoggerTest { mockLogLevelStorage = mock() loggerInstance = Logger( - coreSdkHandler, - shardRepositoryMock, - timestampProviderMock, - uuidProviderMock, - mockLogLevelStorage, - false, - mock() + concurrentHandlerHolder, + shardRepositoryMock, + timestampProviderMock, + uuidProviderMock, + mockLogLevelStorage, + false, + mock() ) loggerMock = mock() dependencyContainer = FakeCoreDependencyContainer( - coreSdkHandler = coreSdkHandler, + concurrentHandlerHolder = concurrentHandlerHolder, shardRepository = shardRepositoryMock, timestampProvider = timestampProviderMock, uuidProvider = uuidProviderMock, @@ -81,7 +85,7 @@ class LoggerTest { @After fun tearDown() { if (CoreComponent.isSetup()) { - core().coreSdkHandler.looper.quitSafely() + core().concurrentHandlerHolder.looper.quitSafely() tearDownCoreComponent() } } @@ -100,7 +104,9 @@ class LoggerTest { val captor = ArgumentCaptor.forClass(ShardModel::class.java) - verify(shardRepositoryMock, timeout(100)).add(capture(captor)) + runBlocking { + verify(shardRepositoryMock, timeout(100)).add(capture(captor)) + } captor.value shouldBe ShardModel( UUID, @@ -127,9 +133,9 @@ class LoggerTest { ) val captor = ArgumentCaptor.forClass(ShardModel::class.java) - - verify(shardRepositoryMock, timeout(100)).add(capture(captor)) - + runBlocking { + verify(shardRepositoryMock, timeout(100)).add(capture(captor)) + } captor.value shouldBe ShardModel( UUID, "any_log", @@ -145,9 +151,9 @@ class LoggerTest { @Test fun testPersistLog_addsLog_toShardRepository_viaCoreSdkHandler() { val threadSpy = ThreadSpy() - - org.mockito.Mockito.doAnswer(threadSpy).`when`(shardRepositoryMock).add(any()) - + runBlocking { + org.mockito.Mockito.doAnswer(threadSpy).`when`(shardRepositoryMock).add(any()) + } loggerInstance.persistLog(LogLevel.ERROR, logEntryMock(), "testThreadName", null) threadSpy.verifyCalledOnCoreSdkThread() @@ -200,8 +206,9 @@ class LoggerTest { "testThreadName" ) { latch.countDown() } latch.await() - - verify(shardRepositoryMock, timeout(100).times(0)).add(any()) + runBlocking { + verify(shardRepositoryMock, timeout(100).times(0)).add(any()) + } } @Test @@ -216,8 +223,9 @@ class LoggerTest { "testThreadName" ) { latch.countDown() } latch.await() - - verify(shardRepositoryMock, times(0)).add(any()) + runBlocking { + verify(shardRepositoryMock, times(0)).add(any()) + } } @Test @@ -233,7 +241,9 @@ class LoggerTest { "testThreadName" ) { latch.countDown() } latch.await() - verify(shardRepositoryMock, times(1)).add(any()) + runBlocking { + verify(shardRepositoryMock, times(1)).add(any()) + } } private fun logEntryMock(testTopic: String = "", testData: Map = mapOf()) = @@ -244,7 +254,7 @@ class LoggerTest { private fun waitForTask() { val latch = CountDownLatch(1) - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { latch.countDown() } latch.await() diff --git a/core/src/androidTest/java/com/emarsys/core/worker/CoreCompletionHandlerMiddlewareTest.kt b/core/src/androidTest/java/com/emarsys/core/worker/CoreCompletionHandlerMiddlewareTest.kt index ed633293e..e6090d214 100644 --- a/core/src/androidTest/java/com/emarsys/core/worker/CoreCompletionHandlerMiddlewareTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/worker/CoreCompletionHandlerMiddlewareTest.kt @@ -4,19 +4,21 @@ import android.os.Handler import android.os.Looper import android.os.Message import com.emarsys.core.CoreCompletionHandler -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.core.handler.SdkHandler import com.emarsys.core.request.model.CompositeRequestModel import com.emarsys.core.request.model.RequestMethod import com.emarsys.core.request.model.RequestModel import com.emarsys.core.request.model.specification.FilterByRequestIds import com.emarsys.core.response.ResponseModel +import com.emarsys.testUtil.ReflectionTestUtils import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.matchers.numerics.shouldBeLessThanOrEqual import io.kotlintest.shouldBe -import org.junit.Assert +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -33,7 +35,8 @@ class CoreCompletionHandlerMiddlewareTest { private lateinit var middleware: CoreCompletionHandlerMiddleware private lateinit var captor: ArgumentCaptor private lateinit var uiHandler: Handler - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + private lateinit var spyCoreHandler: SdkHandler @Rule @JvmField @@ -46,41 +49,23 @@ class CoreCompletionHandlerMiddlewareTest { coreCompletionHandler = mock() requestRepository = mock() uiHandler = Handler(Looper.getMainLooper()) - coreSdkHandler = CoreSdkHandlerProvider().provideHandler() - middleware = CoreCompletionHandlerMiddleware(worker, requestRepository, uiHandler, coreSdkHandler, coreCompletionHandler) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + spyCoreHandler = spy(concurrentHandlerHolder.coreHandler) + ReflectionTestUtils.setInstanceField( + concurrentHandlerHolder, + "coreHandler", + spyCoreHandler + ) + middleware = CoreCompletionHandlerMiddleware( + worker, + requestRepository, + uiHandler, + concurrentHandlerHolder, + coreCompletionHandler + ) captor = ArgumentCaptor.forClass(Message::class.java) } - @Test - fun testConstructor_handlerShouldNotBeNull() { - Assert.assertNotNull(middleware.coreSDKHandler) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_workerShouldNotBeNull() { - CoreCompletionHandlerMiddleware(null, requestRepository, uiHandler, coreSdkHandler, coreCompletionHandler) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_queueShouldNotBeNull() { - CoreCompletionHandlerMiddleware(worker, null, uiHandler, coreSdkHandler, coreCompletionHandler) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_coreCompletionHandlerShouldNotBeNull() { - CoreCompletionHandlerMiddleware(worker, requestRepository, uiHandler, coreSdkHandler, null) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_uiHandlerShouldNotBeNull() { - CoreCompletionHandlerMiddleware(worker, requestRepository, null, coreSdkHandler, coreCompletionHandler) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_coreHandlerShouldNotBeNull() { - CoreCompletionHandlerMiddleware(worker, requestRepository, uiHandler, null, coreCompletionHandler) - } - @Test fun testOnSuccess() { val captor = ArgumentCaptor.forClass(FilterByRequestIds::class.java) @@ -88,18 +73,19 @@ class CoreCompletionHandlerMiddlewareTest { middleware.onSuccess(expectedId, expectedModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) + runBlocking { + verify(worker).unlock() + verify(worker).run() + verifyNoMoreInteractions(worker) + verify(requestRepository).remove(capture(captor)) + val filter = captor.value - verify(worker).unlock() - verify(worker).run() - verifyNoMoreInteractions(worker) - verify(requestRepository).remove(capture(captor)) - val filter = captor.value - - filter.selectionArgs[0] shouldBe expectedModel.requestModel.id + filter.selectionArgs[0] shouldBe expectedModel.requestModel.id - verify(coreCompletionHandler).onSuccess(expectedId, expectedModel) + verify(coreCompletionHandler).onSuccess(expectedId, expectedModel) + } } @Test @@ -110,28 +96,23 @@ class CoreCompletionHandlerMiddlewareTest { middleware.onSuccess("0", responseModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) argumentCaptor().apply { - verify(requestRepository, times(41)).remove(capture()) + runBlocking { + verify(requestRepository, times(41)).remove(capture()) + } } } @Test fun testOnSuccess_callsHandlerPost() { - val handler: CoreSdkHandler = mock() - - middleware.coreSDKHandler = handler + middleware.concurrentHandlerHolder = concurrentHandlerHolder middleware.onSuccess(expectedId, createResponseModel(200)) - argumentCaptor { - verify(handler).post(capture()) - val runnable = this.firstValue - runnable.run() - } - - verify(worker).run() + verify(spyCoreHandler).post(any()) + verify(worker, timeout(50)).run() } @Test @@ -141,11 +122,14 @@ class CoreCompletionHandlerMiddlewareTest { middleware.onSuccess("0", responseModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) argumentCaptor().apply { - verify(middleware.coreCompletionHandler, times(2040)).onSuccess(capture(), eq(responseModel)) + verify(middleware.coreCompletionHandler, times(2040))!!.onSuccess( + capture(), + eq(responseModel) + ) allValues shouldBe ids.toList() } } @@ -156,12 +140,14 @@ class CoreCompletionHandlerMiddlewareTest { middleware.onError(expectedId, expectedModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) argumentCaptor().apply { - verify(requestRepository).remove(capture()) - firstValue.selectionArgs shouldBe arrayOf(expectedModel.requestModel.id) + runBlocking { + verify(requestRepository).remove(capture()) + firstValue.selectionArgs shouldBe arrayOf(expectedModel.requestModel.id) + } } verify(coreCompletionHandler).onError(expectedId, expectedModel) @@ -172,19 +158,12 @@ class CoreCompletionHandlerMiddlewareTest { @Test fun testOnError_4xx_callsHandlerPost() { - val handler: CoreSdkHandler = mock() - - middleware.coreSDKHandler = handler + middleware.concurrentHandlerHolder = concurrentHandlerHolder middleware.onError(expectedId, createResponseModel(401)) - argumentCaptor { - verify(handler).post(capture()) - val runnable = this.firstValue - runnable.run() - } - - verify(worker).run() + verify(spyCoreHandler).post(any()) + verify(worker, timeout(50)).run() } @Test @@ -193,7 +172,7 @@ class CoreCompletionHandlerMiddlewareTest { middleware.onError(expectedId, expectedModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) verify(worker).unlock() @@ -208,7 +187,7 @@ class CoreCompletionHandlerMiddlewareTest { middleware.onError(expectedId, expectedModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) verify(worker).unlock() @@ -223,7 +202,7 @@ class CoreCompletionHandlerMiddlewareTest { middleware.onError(expectedId, expectedModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) verify(worker).unlock() @@ -236,20 +215,23 @@ class CoreCompletionHandlerMiddlewareTest { fun testOnError_4xx_withCompositeModel() { val ids = Array(2040) { i -> "id$i" } val responseModel = ResponseModel.Builder() - .statusCode(400) - .message("Bad Request") - .headers(HashMap()) - .body("{'key': 'value'}") - .requestModel(createRequestModel(ids)) - .build() + .statusCode(400) + .message("Bad Request") + .headers(HashMap()) + .body("{'key': 'value'}") + .requestModel(createRequestModel(ids)) + .build() middleware.onError("0", responseModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) argumentCaptor().apply { - verify(middleware.coreCompletionHandler, times(2040)).onError(capture(), eq(responseModel)) + verify(middleware.coreCompletionHandler, times(2040))!!.onError( + capture(), + eq(responseModel) + ) allValues shouldBe ids.toList() } } @@ -259,20 +241,22 @@ class CoreCompletionHandlerMiddlewareTest { val ids = Array(2000) { i -> "id$i" } val responseModel = ResponseModel.Builder() - .statusCode(400) - .message("Bad Request") - .headers(HashMap()) - .body("{'key': 'value'}") - .requestModel(createRequestModel(ids)) - .build() + .statusCode(400) + .message("Bad Request") + .headers(HashMap()) + .body("{'key': 'value'}") + .requestModel(createRequestModel(ids)) + .build() middleware.onError("0", responseModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) argumentCaptor().apply { - verify(requestRepository, times(40)).remove(capture()) - allValues.size shouldBeLessThanOrEqual 500 + runBlocking { + verify(requestRepository, times(40)).remove(capture()) + allValues.size shouldBeLessThanOrEqual 500 + } } } @@ -282,21 +266,23 @@ class CoreCompletionHandlerMiddlewareTest { val ids = Array(2040) { i -> "id$i" } val responseModel = ResponseModel.Builder() - .statusCode(400) - .message("Bad Request") - .headers(HashMap()) - .body("{'key': 'value'}") - .requestModel(createRequestModel(ids)) - .build() + .statusCode(400) + .message("Bad Request") + .headers(HashMap()) + .body("{'key': 'value'}") + .requestModel(createRequestModel(ids)) + .build() middleware.onError("0", responseModel) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) argumentCaptor().apply { - verify(requestRepository, times(41)).remove(capture()) - allValues.size shouldBeLessThanOrEqual 500 + runBlocking { + verify(requestRepository, times(41)).remove(capture()) + allValues.size shouldBeLessThanOrEqual 500 + } } } @@ -306,7 +292,7 @@ class CoreCompletionHandlerMiddlewareTest { middleware.onError(expectedId, expectedException) - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) verify(worker).unlock() @@ -318,38 +304,39 @@ class CoreCompletionHandlerMiddlewareTest { private fun createRequestModel(ids: Array): RequestModel { return CompositeRequestModel( - "0", - "https://emarsys.com", - RequestMethod.POST, - null, - HashMap(), - 100, - 900000, - ids) + "0", + "https://emarsys.com", + RequestMethod.POST, + null, + HashMap(), + 100, + 900000, + ids + ) } private fun createResponseModel(requestModel: RequestModel): ResponseModel { return ResponseModel.Builder() - .statusCode(200) - .message("OK") - .headers(HashMap()) - .body("{'key': 'value'}") - .requestModel(requestModel) - .build() + .statusCode(200) + .message("OK") + .headers(HashMap()) + .body("{'key': 'value'}") + .requestModel(requestModel) + .build() } private fun createResponseModel(statusCode: Int): ResponseModel { val requestModel = mock() whenever(requestModel.id).thenReturn(expectedId) return ResponseModel.Builder() - .statusCode(statusCode) - .body("body") - .message("message") - .requestModel(requestModel) - .build() + .statusCode(statusCode) + .body("body") + .message("message") + .requestModel(requestModel) + .build() } - private fun waitForEventLoopToFinish(handler: CoreSdkHandler) { + private fun waitForEventLoopToFinish(handler: SdkHandler) { val latch = CountDownLatch(1) handler.post { latch.countDown() } latch.await() diff --git a/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt b/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt index cdc56bc10..b02f1d15c 100644 --- a/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt @@ -1,10 +1,6 @@ package com.emarsys.core.worker import android.os.Handler -import com.emarsys.testUtil.TimeoutUtils.timeoutRule -import com.emarsys.testUtil.DatabaseTestUtils.deleteCoreDatabase -import com.emarsys.core.testUtil.RequestModelTestUtils.createRequestModel - import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.connection.ConnectionState @@ -16,16 +12,16 @@ import com.emarsys.core.request.RestClient import com.emarsys.core.request.factory.CompletionHandlerProxyProvider import com.emarsys.core.request.model.RequestMethod import com.emarsys.core.request.model.RequestModel -import com.emarsys.core.request.model.specification.QueryLatestRequestModel -import com.emarsys.core.response.ResponseModel +import com.emarsys.core.testUtil.RequestModelTestUtils.createRequestModel +import com.emarsys.testUtil.DatabaseTestUtils.deleteCoreDatabase +import com.emarsys.testUtil.TimeoutUtils.timeoutRule +import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule -import org.mockito.ArgumentCaptor import org.mockito.kotlin.* -import java.lang.IllegalArgumentException import java.util.* import java.util.concurrent.CountDownLatch @@ -96,66 +92,6 @@ class DefaultWorkerTest { ) } - @Test(expected = IllegalArgumentException::class) - fun testConstructor_queueShouldNotBeNull() { - DefaultWorker( - null, - mock(), - uiHandler, - mockCoreCompletionHandler, - restClient, - mockProxyProvider - ) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_watchDogShouldNotBeNull() { - DefaultWorker( - requestRepository, - null, - uiHandler, - mockCoreCompletionHandler, - restClient, - mockProxyProvider - ) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_uiHandlerShouldNotBeNull() { - DefaultWorker( - requestRepository, - mock(), - null, - mockCoreCompletionHandler, - restClient, - mockProxyProvider - ) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_restClientShouldNotBeNull() { - DefaultWorker( - requestRepository, - mock(), - uiHandler, - mockCoreCompletionHandler, - null, - mockProxyProvider - ) - } - - @Test(expected = IllegalArgumentException::class) - fun testConstructor_proxyProvider_mustNotBeNull() { - DefaultWorker( - requestRepository, - mock(), - uiHandler, - mockCoreCompletionHandler, - restClient, - null - ) - } - @Test fun testRun_shouldLockWorker() { worker = spy(worker) @@ -253,11 +189,13 @@ class DefaultWorkerTest { whenever(requestRepository.query(any())) .thenReturn(listOf(expiredModel1), listOf(expiredModel2), listOf(notExpiredModel)) whenever(requestRepository.isEmpty()).thenReturn(false, false, false, false, true) - worker.run() - verify(requestRepository, times(3)).query(any()) - verify(requestRepository, times(2)).remove(any()) - verify(worker.restClient).execute(eq(notExpiredModel), any()) - Assert.assertTrue(worker.isLocked) + runBlocking { + worker.run() + verify(requestRepository, times(3)).query(any()) + verify(requestRepository, times(2)).remove(any()) + verify(worker.restClient).execute(eq(notExpiredModel), any()) + Assert.assertTrue(worker.isLocked) + } } @Test @@ -283,11 +221,13 @@ class DefaultWorkerTest { whenever(worker.requestRepository.query(any())) .thenReturn(listOf(expiredModel1), listOf(expiredModel2)) whenever(worker.requestRepository.isEmpty()).thenReturn(false, false, false, true) - worker.run() - verify(worker.requestRepository, times(2)).query(any()) - verify(worker.requestRepository, times(2)).remove(any()) - verifyNoInteractions(worker.restClient) - Assert.assertTrue(worker.requestRepository.isEmpty()) - Assert.assertFalse(worker.isLocked) + runBlocking { + worker.run() + verify(worker.requestRepository, times(2)).query(any()) + verify(worker.requestRepository, times(2)).remove(any()) + verifyNoInteractions(worker.restClient) + Assert.assertTrue(worker.requestRepository.isEmpty()) + Assert.assertFalse(worker.isLocked) + } } } \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/activity/ActivityLifecycleActionRegistry.kt b/core/src/main/java/com/emarsys/core/activity/ActivityLifecycleActionRegistry.kt index bcc392812..356d942a8 100644 --- a/core/src/main/java/com/emarsys/core/activity/ActivityLifecycleActionRegistry.kt +++ b/core/src/main/java/com/emarsys/core/activity/ActivityLifecycleActionRegistry.kt @@ -2,19 +2,19 @@ package com.emarsys.core.activity import android.app.Activity import com.emarsys.core.Mockable -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider @Mockable class ActivityLifecycleActionRegistry( - val coreSdkHandler: CoreSdkHandler, + val concurrentHandlerHolder: ConcurrentHandlerHolder, val currentActivityProvider: CurrentActivityProvider, val lifecycleActions: MutableList = mutableListOf() ) { val triggerOnActivityActions: MutableList = mutableListOf() fun execute(activity: Activity?, lifecycles: List) { - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { (lifecycleActions + triggerOnActivityActions) .filter { lifecycles.contains(it.triggeringLifecycle) @@ -33,7 +33,7 @@ class ActivityLifecycleActionRegistry( } fun addTriggerOnActivityAction(activityLifecycleAction: ActivityLifecycleAction) { - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { val currentActivity = currentActivityProvider.get() triggerOnActivityActions.add(activityLifecycleAction) if (currentActivity != null) { diff --git a/core/src/main/java/com/emarsys/core/api/ApiProxy.kt b/core/src/main/java/com/emarsys/core/api/ApiProxy.kt index ed3967600..a0528f56d 100644 --- a/core/src/main/java/com/emarsys/core/api/ApiProxy.kt +++ b/core/src/main/java/com/emarsys/core/api/ApiProxy.kt @@ -1,8 +1,8 @@ package com.emarsys.core.api -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder -inline fun T.proxyApi(handler: CoreSdkHandler): T { - return this.proxyWithLogExceptions().proxyWithHandler(handler) +inline fun T.proxyApi(handlerHolder: ConcurrentHandlerHolder): T { + return this.proxyWithLogExceptions().proxyWithHandler(handlerHolder) } \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/api/AsyncProxy.kt b/core/src/main/java/com/emarsys/core/api/AsyncProxy.kt index dcb0fe8d4..8083de3ea 100644 --- a/core/src/main/java/com/emarsys/core/api/AsyncProxy.kt +++ b/core/src/main/java/com/emarsys/core/api/AsyncProxy.kt @@ -1,6 +1,6 @@ package com.emarsys.core.api -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Proxy @@ -8,15 +8,22 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -inline fun T.proxyWithHandler(handler: CoreSdkHandler, timeout: Long = 10): T { - return Proxy.newProxyInstance(javaClass.classLoader, - javaClass.interfaces, - AsyncProxy(this, handler, timeout)) as T +inline fun T.proxyWithHandler( + handlerHolder: ConcurrentHandlerHolder, + timeout: Long = 10 +): T { + return Proxy.newProxyInstance( + javaClass.classLoader, + javaClass.interfaces, + AsyncProxy(this, handlerHolder, timeout) + ) as T } -class AsyncProxy(private val apiObject: T, - private val handler: CoreSdkHandler, - private val timeout: Long) : InvocationHandler { +class AsyncProxy( + private val apiObject: T, + private val handlerHolder: ConcurrentHandlerHolder, + private val timeout: Long +) : InvocationHandler { override fun invoke(proxy: Any, method: Method, args: Array?): Any? { EmarsysIdlingResources.increment() @@ -30,7 +37,7 @@ class AsyncProxy(private val apiObject: T, } } val latch = CountDownLatch(1) - handler.post { + handlerHolder.coreHandler.post { result = if (args != null) { method.invoke(apiObject, *args) } else { diff --git a/core/src/main/java/com/emarsys/core/app/AppLifecycleObserver.kt b/core/src/main/java/com/emarsys/core/app/AppLifecycleObserver.kt index 97239ce36..e244fa5e1 100644 --- a/core/src/main/java/com/emarsys/core/app/AppLifecycleObserver.kt +++ b/core/src/main/java/com/emarsys/core/app/AppLifecycleObserver.kt @@ -3,20 +3,20 @@ package com.emarsys.core.app import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import com.emarsys.core.Mockable -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.session.Session import com.emarsys.core.util.log.Logger import com.emarsys.core.util.log.entry.CrashLog @Mockable class AppLifecycleObserver( - private val session: Session, - private val coreSdkHandler: CoreSdkHandler + private val session: Session, + private val concurrentHandlerHolder: ConcurrentHandlerHolder ) : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { super.onStart(owner) - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { session.startSession { if (it != null) { Logger.error(CrashLog(it)) @@ -27,7 +27,7 @@ class AppLifecycleObserver( override fun onStop(owner: LifecycleOwner) { super.onStop(owner) - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { session.endSession { if (it != null) { Logger.error(CrashLog(it)) diff --git a/core/src/main/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactory.kt b/core/src/main/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactory.kt new file mode 100644 index 000000000..bdb48886e --- /dev/null +++ b/core/src/main/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactory.kt @@ -0,0 +1,18 @@ +package com.emarsys.core.concurrency + +import android.os.Handler +import android.os.HandlerThread +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.core.handler.SdkHandler +import java.util.* + +class ConcurrentHandlerHolderFactory(private val uiHandler: Handler) { + fun create(): ConcurrentHandlerHolder { + val handlerThread = HandlerThread("CoreSDKHandlerThread-" + UUID.randomUUID().toString()) + handlerThread.start() + return ConcurrentHandlerHolder( + SdkHandler(CoreHandler(handlerThread)), + SdkHandler(uiHandler) + ) + } +} \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/concurrency/CoreSdkHandlerProvider.kt b/core/src/main/java/com/emarsys/core/concurrency/CoreSdkHandlerProvider.kt deleted file mode 100644 index 30b52e468..000000000 --- a/core/src/main/java/com/emarsys/core/concurrency/CoreSdkHandlerProvider.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.emarsys.core.concurrency - -import android.os.HandlerThread -import com.emarsys.core.handler.CoreSdkHandler -import java.util.* - -class CoreSdkHandlerProvider { - fun provideHandler(): CoreSdkHandler { - val handlerThread = HandlerThread("CoreSDKHandlerThread-" + UUID.randomUUID().toString()) - handlerThread.start() - return CoreSdkHandler(CoreHandler(handlerThread)) - } -} \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/connection/ConnectionWatchDog.kt b/core/src/main/java/com/emarsys/core/connection/ConnectionWatchDog.kt index 1b7dab105..2dc6d10e0 100644 --- a/core/src/main/java/com/emarsys/core/connection/ConnectionWatchDog.kt +++ b/core/src/main/java/com/emarsys/core/connection/ConnectionWatchDog.kt @@ -2,20 +2,19 @@ package com.emarsys.core.connection import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent import android.content.IntentFilter import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest import com.emarsys.core.Mockable -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.util.AndroidVersionUtils @Mockable class ConnectionWatchDog( inputContext: Context, - private val coreSdkHandler: CoreSdkHandler + private val concurrentHandlerHolder: ConcurrentHandlerHolder ) : ConnectivityManager.NetworkCallback() { private companion object { var receiver: BroadcastReceiver? = null @@ -73,12 +72,16 @@ class ConnectionWatchDog( connectivityManager.registerNetworkCallback( networkRequest, this, - coreSdkHandler.handler + concurrentHandlerHolder.coreHandler.handler ) } else { if (receiver != null) { receiver = - ConnectivityChangeReceiver(connectionChangeListener, this, coreSdkHandler) + ConnectivityChangeReceiver( + connectionChangeListener, + this, + concurrentHandlerHolder + ) context.registerReceiver(receiver, intentFilter) } } diff --git a/core/src/main/java/com/emarsys/core/connection/ConnectivityChangeReceiver.kt b/core/src/main/java/com/emarsys/core/connection/ConnectivityChangeReceiver.kt index 20a54f5cc..6d12082a5 100644 --- a/core/src/main/java/com/emarsys/core/connection/ConnectivityChangeReceiver.kt +++ b/core/src/main/java/com/emarsys/core/connection/ConnectivityChangeReceiver.kt @@ -3,16 +3,16 @@ package com.emarsys.core.connection import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder class ConnectivityChangeReceiver( private val connectionChangeListener: ConnectionChangeListener, private val connectionWatchDog: ConnectionWatchDog, - private val coreSdkHandler: CoreSdkHandler + private val concurrentHandlerHolder: ConcurrentHandlerHolder ) : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { val connectionState: ConnectionState = connectionWatchDog.connectionState val isConnected: Boolean = connectionWatchDog.isConnected connectionChangeListener.onConnectionChanged(connectionState, isConnected) diff --git a/core/src/main/java/com/emarsys/core/database/repository/AbstractSqliteRepository.kt b/core/src/main/java/com/emarsys/core/database/repository/AbstractSqliteRepository.kt index e0d7602cb..98e41e3f8 100644 --- a/core/src/main/java/com/emarsys/core/database/repository/AbstractSqliteRepository.kt +++ b/core/src/main/java/com/emarsys/core/database/repository/AbstractSqliteRepository.kt @@ -4,6 +4,8 @@ import android.content.ContentValues import android.database.Cursor import com.emarsys.core.database.CoreSQLiteDatabase import com.emarsys.core.database.helper.DbHelper +import com.emarsys.core.handler.ConcurrentHandlerHolder + private inline fun CoreSQLiteDatabase.inTransaction(statement: CoreSQLiteDatabase.() -> T): T { this.beginTransaction() @@ -17,11 +19,16 @@ private inline fun CoreSQLiteDatabase.inTransaction(statement: CoreSQLiteDat return result } -abstract class AbstractSqliteRepository(var tableName: String, var dbHelper: DbHelper) : Repository { +abstract class AbstractSqliteRepository( + var tableName: String, + var dbHelper: DbHelper, + var concurrentHandlerHolder: ConcurrentHandlerHolder +) : Repository { + abstract fun contentValuesFromItem(item: T): ContentValues abstract fun itemFromCursor(cursor: Cursor): T - override fun add(item: T) { + override suspend fun add(item: T) { val contentValues = contentValuesFromItem(item) val database = dbHelper.writableCoreDatabase database.inTransaction { @@ -29,41 +36,60 @@ abstract class AbstractSqliteRepository(var tableName: String, var dbHelper: } } - override fun update(item: T, specification: SqlSpecification): Int { + override suspend fun update(item: T, specification: SqlSpecification): Int { val values = contentValuesFromItem(item) val database = dbHelper.writableCoreDatabase - return database.inTransaction { - update(tableName, values, specification.selection, specification.selectionArgs) + var result = 0 + + database.inTransaction { + result = update( + tableName, + values, + specification.selection, + specification.selectionArgs + ) + } + + return result } override fun query(specification: SqlSpecification): List { val database = dbHelper.readableCoreDatabase - database.query(specification.isDistinct, - tableName, - specification.columns, - specification.selection, - specification.selectionArgs, - specification.groupBy, - specification.having, - specification.orderBy, - specification.limit).use { cursor -> return mapCursorToResultList(cursor) } + + database.query( + specification.isDistinct, + tableName, + specification.columns, + specification.selection, + specification.selectionArgs, + specification.groupBy, + specification.having, + specification.orderBy, + specification.limit + ).use { cursor -> return mapCursorToResultList(cursor) } + + } - override fun remove(specification: SqlSpecification) { + override suspend fun remove(specification: SqlSpecification) { val database = dbHelper.writableCoreDatabase - database.inTransaction { + + return database.inTransaction { delete( - tableName, - specification.selection, - specification.selectionArgs) + tableName, + specification.selection, + specification.selectionArgs + ) } } override fun isEmpty(): Boolean { val database = dbHelper.readableCoreDatabase - database.rawQuery("SELECT COUNT(*) FROM $tableName;", - null).use { cursor -> + database.rawQuery( + "SELECT COUNT(*) FROM $tableName;", + null + ).use { cursor -> cursor.moveToFirst() val count = cursor.getInt(cursor.getColumnIndexOrThrow("COUNT(*)")) return count == 0 diff --git a/core/src/main/java/com/emarsys/core/database/repository/Repository.kt b/core/src/main/java/com/emarsys/core/database/repository/Repository.kt index 92882c29a..4b0ba9e44 100644 --- a/core/src/main/java/com/emarsys/core/database/repository/Repository.kt +++ b/core/src/main/java/com/emarsys/core/database/repository/Repository.kt @@ -1,9 +1,9 @@ package com.emarsys.core.database.repository interface Repository { - fun add(item: T) - fun update(item: T, specification: SqlSpecification): Int - fun remove(specification: S) + suspend fun add(item: T) + suspend fun update(item: T, specification: SqlSpecification): Int + suspend fun remove(specification: S) fun query(specification: S): List fun isEmpty(): Boolean } \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/device/HardwareRepository.kt b/core/src/main/java/com/emarsys/core/device/HardwareRepository.kt index cce976af8..b086b20a8 100644 --- a/core/src/main/java/com/emarsys/core/device/HardwareRepository.kt +++ b/core/src/main/java/com/emarsys/core/device/HardwareRepository.kt @@ -5,13 +5,24 @@ import android.database.Cursor import com.emarsys.core.database.DatabaseContract import com.emarsys.core.database.helper.DbHelper import com.emarsys.core.database.repository.AbstractSqliteRepository +import com.emarsys.core.handler.ConcurrentHandlerHolder -class HardwareRepository(dbHelper: DbHelper) : AbstractSqliteRepository(DatabaseContract.HARDWARE_IDENTIFICATION_TABLE_NAME, dbHelper) { +class HardwareRepository(dbHelper: DbHelper, concurrentHandlerHolder: ConcurrentHandlerHolder) : + AbstractSqliteRepository( + DatabaseContract.HARDWARE_IDENTIFICATION_TABLE_NAME, dbHelper, + concurrentHandlerHolder + ) { override fun contentValuesFromItem(item: HardwareIdentification?): ContentValues { val contentValues = ContentValues() - contentValues.put(DatabaseContract.HARDWARE_IDENTIFICATION_COLUMN_NAME_HARDWARE_ID, item?.hardwareId) - contentValues.put(DatabaseContract.HARDWARE_IDENTIFICATION_COLUMN_NAME_ENCRYPTED_HARDWARE_ID, item?.encryptedHardwareId) + contentValues.put( + DatabaseContract.HARDWARE_IDENTIFICATION_COLUMN_NAME_HARDWARE_ID, + item?.hardwareId + ) + contentValues.put( + DatabaseContract.HARDWARE_IDENTIFICATION_COLUMN_NAME_ENCRYPTED_HARDWARE_ID, + item?.encryptedHardwareId + ) contentValues.put(DatabaseContract.HARDWARE_IDENTIFICATION_COLUMN_NAME_SALT, item?.salt) contentValues.put(DatabaseContract.HARDWARE_IDENTIFICATION_COLUMN_NAME_IV, item?.iv) return contentValues diff --git a/core/src/main/java/com/emarsys/core/di/CoreComponent.kt b/core/src/main/java/com/emarsys/core/di/CoreComponent.kt index e62e8dc7f..6066dcfb2 100644 --- a/core/src/main/java/com/emarsys/core/di/CoreComponent.kt +++ b/core/src/main/java/com/emarsys/core/di/CoreComponent.kt @@ -13,7 +13,7 @@ import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.device.DeviceInfo -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.hardwareid.HardwareIdProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider @@ -26,10 +26,9 @@ import com.emarsys.core.storage.Storage import com.emarsys.core.util.FileDownloader import com.emarsys.core.util.log.Logger import com.emarsys.core.worker.Worker -import kotlinx.coroutines.CoroutineScope fun core() = CoreComponent.instance - ?: throw IllegalStateException("DependencyContainer has to be setup first!") + ?: throw IllegalStateException("DependencyContainer has to be setup first!") fun setupCoreComponent(coreComponent: CoreComponent) { CoreComponent.instance = coreComponent @@ -40,7 +39,7 @@ fun tearDownCoreComponent() { } fun isCoreComponentSetup() = - CoreComponent.instance != null + CoreComponent.instance != null interface CoreComponent { @@ -50,7 +49,7 @@ interface CoreComponent { fun isSetup() = instance != null } - val coreSdkHandler: CoreSdkHandler + val concurrentHandlerHolder: ConcurrentHandlerHolder val uiHandler: Handler @@ -58,7 +57,7 @@ interface CoreComponent { val currentActivityWatchdog: CurrentActivityWatchdog - val activityLifecycleActionRegistry : ActivityLifecycleActionRegistry + val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry val coreSQLiteDatabase: CoreSQLiteDatabase @@ -101,8 +100,4 @@ interface CoreComponent { val connectionWatchdog: ConnectionWatchDog val coreCompletionHandler: CoreCompletionHandler - - val coreSdkScope: CoroutineScope - - val uiScope: CoroutineScope } \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/handler/ConcurrentHandlerHolder.kt b/core/src/main/java/com/emarsys/core/handler/ConcurrentHandlerHolder.kt new file mode 100644 index 000000000..d78bade0e --- /dev/null +++ b/core/src/main/java/com/emarsys/core/handler/ConcurrentHandlerHolder.kt @@ -0,0 +1,18 @@ +package com.emarsys.core.handler; + +import com.emarsys.core.Mockable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.android.asCoroutineDispatcher + +@Mockable +class ConcurrentHandlerHolder(final val coreHandler: SdkHandler, final val uiHandler: SdkHandler) { + fun post(runnable: Runnable) { + coreHandler.post(runnable) + } + + val sdkScope = CoroutineScope(Job() + this.coreHandler.handler.asCoroutineDispatcher()) + val uiScope = CoroutineScope(Job() + Dispatchers.Main) + val looper = coreHandler.handler.looper +} \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/handler/CoreSdkHandler.kt b/core/src/main/java/com/emarsys/core/handler/CoreSdkHandler.kt deleted file mode 100644 index 0b40d7b5e..000000000 --- a/core/src/main/java/com/emarsys/core/handler/CoreSdkHandler.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.emarsys.core.handler; - -import android.os.Handler -import com.emarsys.core.Mockable - -@Mockable -class CoreSdkHandler(final val handler: Handler) { - fun post(runnable: Runnable) { - handler.post(runnable) - } - - val looper = handler.looper -} \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/handler/SdkHandler.kt b/core/src/main/java/com/emarsys/core/handler/SdkHandler.kt new file mode 100644 index 000000000..595794848 --- /dev/null +++ b/core/src/main/java/com/emarsys/core/handler/SdkHandler.kt @@ -0,0 +1,16 @@ +package com.emarsys.core.handler + +import android.os.Handler +import com.emarsys.core.Mockable + +@Mockable +class SdkHandler(val handler: Handler) { + fun post(runnable: Runnable): Boolean { + return handler.post(runnable) + } + + fun postDelayed(runnable: Runnable, delay: Long) { + handler.postDelayed(runnable, delay) + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/provider/hardwareid/HardwareIdProvider.kt b/core/src/main/java/com/emarsys/core/provider/hardwareid/HardwareIdProvider.kt index 0631e946e..c9de7f185 100644 --- a/core/src/main/java/com/emarsys/core/provider/hardwareid/HardwareIdProvider.kt +++ b/core/src/main/java/com/emarsys/core/provider/hardwareid/HardwareIdProvider.kt @@ -10,20 +10,25 @@ import com.emarsys.core.device.FilterByHardwareId import com.emarsys.core.device.HardwareIdentification import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.storage.Storage +import kotlinx.coroutines.runBlocking @Mockable -class HardwareIdProvider(private val uuidProvider: UUIDProvider, - private val repository: Repository, - private val hwIdStorage: Storage, - private val hardwareIdContentResolver: HardwareIdContentResolver, - private val hardwareIdentificationCrypto: HardwareIdentificationCrypto) { +class HardwareIdProvider( + private val uuidProvider: UUIDProvider, + private val repository: Repository, + private val hwIdStorage: Storage, + private val hardwareIdContentResolver: HardwareIdContentResolver, + private val hardwareIdentificationCrypto: HardwareIdentificationCrypto +) { fun provideHardwareId(): String { val hardware = repository.query(Everything()).firstOrNull() return if (hardware != null) { if (hardware.encryptedHardwareId == null) { hardwareIdentificationCrypto.encrypt(hardware).also { - repository.update(it, FilterByHardwareId(it.hardwareId)) + runBlocking { + repository.update(it, FilterByHardwareId(it.hardwareId)) + } } hardware.hardwareId } else { @@ -31,15 +36,19 @@ class HardwareIdProvider(private val uuidProvider: UUIDProvider, } } else { getHardwareIdentification().also { - repository.add(it) + runBlocking { + repository.add( + it + ) + } }.hardwareId } } private fun getHardwareIdentification(): HardwareIdentification { return (hwIdStorage.get() - ?: hardwareIdContentResolver.resolveHardwareId() - ?: generateNewHardwareId() + ?: hardwareIdContentResolver.resolveHardwareId() + ?: generateNewHardwareId() ).asEncryptedHardwareIdentification() } diff --git a/core/src/main/java/com/emarsys/core/request/RequestManager.kt b/core/src/main/java/com/emarsys/core/request/RequestManager.kt index 2f56ce717..a48ac5368 100644 --- a/core/src/main/java/com/emarsys/core/request/RequestManager.kt +++ b/core/src/main/java/com/emarsys/core/request/RequestManager.kt @@ -6,19 +6,18 @@ import com.emarsys.core.Registry import com.emarsys.core.api.result.CompletionListener import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.request.factory.CompletionHandlerProxyProvider -import com.emarsys.core.request.factory.DefaultRunnableFactory -import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.factory.ScopeDelegatorCompletionHandlerProvider import com.emarsys.core.request.model.RequestModel import com.emarsys.core.shard.ShardModel import com.emarsys.core.worker.Worker import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @Mockable class RequestManager( - private val coreSDKHandler: CoreSdkHandler, + private val concurrentHandlerHolder: ConcurrentHandlerHolder, private val requestRepository: Repository, private val shardRepository: Repository, private val worker: Worker, @@ -27,20 +26,21 @@ class RequestManager( private val defaultCoreCompletionHandler: CoreCompletionHandler, private val completionHandlerProxyProvider: CompletionHandlerProxyProvider, private val scopeDelegatorCompletionHandlerProvider: ScopeDelegatorCompletionHandlerProvider, - private val coreSdkScope: CoroutineScope ) { - var runnableFactory : RunnableFactory = DefaultRunnableFactory() - fun submit(model: RequestModel, callback: CompletionListener?) { - coreSDKHandler.post(runnableFactory.runnableFrom { - requestRepository.add(model) + concurrentHandlerHolder.sdkScope.launch { + requestRepository.add( + model + ) callbackRegistry.register(model, callback) worker.run() - }) + } } fun submit(model: ShardModel) { - coreSDKHandler.post(runnableFactory.runnableFrom { shardRepository.add(model) }) + concurrentHandlerHolder.sdkScope.launch { + shardRepository.add(model) + } } fun submitNow(requestModel: RequestModel) { @@ -49,8 +49,13 @@ class RequestManager( submitNow(requestModel, handler) } - fun submitNow(requestModel: RequestModel, completionHandler: CoreCompletionHandler, scope: CoroutineScope = coreSdkScope) { - val scopedHandler = scopeDelegatorCompletionHandlerProvider.provide(completionHandler, scope) + fun submitNow( + requestModel: RequestModel, + completionHandler: CoreCompletionHandler, + scope: CoroutineScope = concurrentHandlerHolder.sdkScope + ) { + val scopedHandler = + scopeDelegatorCompletionHandlerProvider.provide(completionHandler, scope) val handler = completionHandlerProxyProvider.provideProxy(null, scopedHandler) restClient.execute(requestModel, handler) } diff --git a/core/src/main/java/com/emarsys/core/request/RestClient.kt b/core/src/main/java/com/emarsys/core/request/RestClient.kt index 240feb30d..215a7792c 100644 --- a/core/src/main/java/com/emarsys/core/request/RestClient.kt +++ b/core/src/main/java/com/emarsys/core/request/RestClient.kt @@ -1,29 +1,23 @@ package com.emarsys.core.request -import android.os.Handler import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.Mapper import com.emarsys.core.api.result.Try import com.emarsys.core.connection.ConnectionProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.response.ResponseModel import kotlinx.coroutines.* -import kotlinx.coroutines.android.asCoroutineDispatcher open class RestClient( private val connectionProvider: ConnectionProvider, private val timestampProvider: TimestampProvider, private val responseHandlersProcessor: ResponseHandlersProcessor, private val requestModelMappers: List>, - private val uiHandler: Handler, - coreSdkHandler: CoreSdkHandler + private val concurrentHandlerHolder: ConcurrentHandlerHolder ) { - private val coreSdkHandlerDispatcher = coreSdkHandler.handler.asCoroutineDispatcher() - - private val sdkScope: CoroutineScope = CoroutineScope(Job() + coreSdkHandlerDispatcher) private val defaultScope = CoroutineScope(Job() + Dispatchers.Default) open fun execute(model: RequestModel, completionHandler: CoreCompletionHandler) { @@ -35,11 +29,11 @@ open class RestClient( ) defaultScope.launch { - val responseModel = async(context = sdkScope.coroutineContext) { + val responseModel = async(context = concurrentHandlerHolder.sdkScope.coroutineContext) { task.execute() } - sdkScope.launch { + concurrentHandlerHolder.sdkScope.launch { onPostExecute(model.id, responseModel.await(), completionHandler) } } diff --git a/core/src/main/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProvider.kt b/core/src/main/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProvider.kt index 3b4997334..565cd4844 100644 --- a/core/src/main/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProvider.kt +++ b/core/src/main/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProvider.kt @@ -1,23 +1,30 @@ package com.emarsys.core.request.factory -import android.os.Handler import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.Mockable import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.request.model.RequestModel import com.emarsys.core.worker.CoreCompletionHandlerMiddleware import com.emarsys.core.worker.Worker @Mockable class CoreCompletionHandlerMiddlewareProvider( - private val requestRepository: Repository, - private val uiHandler: Handler, - private val coreSdkHandler: CoreSdkHandler) : CompletionHandlerProxyProvider { + private val requestRepository: Repository, + private val concurrentHandlerHolder: ConcurrentHandlerHolder, +) : CompletionHandlerProxyProvider { - override fun provideProxy(worker: Worker?, completionHandler: CoreCompletionHandler?): CoreCompletionHandler { - return CoreCompletionHandlerMiddleware(worker, requestRepository, uiHandler, coreSdkHandler, completionHandler) + override fun provideProxy( + worker: Worker?, + completionHandler: CoreCompletionHandler? + ): CoreCompletionHandler { + return CoreCompletionHandlerMiddleware( + worker, + requestRepository, + concurrentHandlerHolder, + completionHandler + ) } } \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/request/model/RequestModelRepository.java b/core/src/main/java/com/emarsys/core/request/model/RequestModelRepository.java index 1428c2854..23062dd1e 100644 --- a/core/src/main/java/com/emarsys/core/request/model/RequestModelRepository.java +++ b/core/src/main/java/com/emarsys/core/request/model/RequestModelRepository.java @@ -1,30 +1,32 @@ package com.emarsys.core.request.model; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_HEADERS; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_METHOD; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_PAYLOAD; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_REQUEST_ID; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TIMESTAMP; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TTL; +import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_URL; +import static com.emarsys.core.util.serialization.SerializationUtils.blobToSerializable; +import static com.emarsys.core.util.serialization.SerializationUtils.serializableToBlob; + import android.content.ContentValues; import android.database.Cursor; import com.emarsys.core.database.DatabaseContract; import com.emarsys.core.database.helper.DbHelper; import com.emarsys.core.database.repository.AbstractSqliteRepository; +import com.emarsys.core.handler.ConcurrentHandlerHolder; import com.emarsys.core.util.serialization.SerializationException; import java.util.HashMap; import java.util.Map; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_HEADERS; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_METHOD; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_PAYLOAD; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_REQUEST_ID; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TIMESTAMP; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TTL; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_URL; -import static com.emarsys.core.util.serialization.SerializationUtils.blobToSerializable; -import static com.emarsys.core.util.serialization.SerializationUtils.serializableToBlob; - public class RequestModelRepository extends AbstractSqliteRepository { - public RequestModelRepository(DbHelper coreDbHelper) { - super(DatabaseContract.REQUEST_TABLE_NAME, coreDbHelper); + public RequestModelRepository(DbHelper coreDbHelper, ConcurrentHandlerHolder concurrentHandlerHolder) { + super(DatabaseContract.REQUEST_TABLE_NAME, coreDbHelper, concurrentHandlerHolder); + } @Override diff --git a/core/src/main/java/com/emarsys/core/shard/ShardModelRepository.kt b/core/src/main/java/com/emarsys/core/shard/ShardModelRepository.kt index dbbdd21b0..96188f7da 100644 --- a/core/src/main/java/com/emarsys/core/shard/ShardModelRepository.kt +++ b/core/src/main/java/com/emarsys/core/shard/ShardModelRepository.kt @@ -6,18 +6,29 @@ import com.emarsys.core.Mockable import com.emarsys.core.database.DatabaseContract import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.AbstractSqliteRepository +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.util.serialization.SerializationException import com.emarsys.core.util.serialization.SerializationUtils import com.emarsys.core.util.tryCastOrException import java.util.* @Mockable -class ShardModelRepository(coreDbHelper: CoreDbHelper) : AbstractSqliteRepository(DatabaseContract.SHARD_TABLE_NAME, coreDbHelper) { +class ShardModelRepository( + coreDbHelper: CoreDbHelper, + concurrentHandlerHolder: ConcurrentHandlerHolder +) : AbstractSqliteRepository( + DatabaseContract.SHARD_TABLE_NAME, + coreDbHelper, + concurrentHandlerHolder +) { override fun contentValuesFromItem(item: ShardModel): ContentValues { val contentValues = ContentValues() contentValues.put(DatabaseContract.SHARD_COLUMN_ID, item.id) contentValues.put(DatabaseContract.SHARD_COLUMN_TYPE, item.type) - contentValues.put(DatabaseContract.SHARD_COLUMN_DATA, SerializationUtils.serializableToBlob(item.data)) + contentValues.put( + DatabaseContract.SHARD_COLUMN_DATA, + SerializationUtils.serializableToBlob(item.data) + ) contentValues.put(DatabaseContract.SHARD_COLUMN_TIMESTAMP, item.timestamp) contentValues.put(DatabaseContract.SHARD_COLUMN_TTL, item.ttl) return contentValues diff --git a/core/src/main/java/com/emarsys/core/util/batch/BatchingShardTrigger.kt b/core/src/main/java/com/emarsys/core/util/batch/BatchingShardTrigger.kt index 457d49076..8266df954 100644 --- a/core/src/main/java/com/emarsys/core/util/batch/BatchingShardTrigger.kt +++ b/core/src/main/java/com/emarsys/core/util/batch/BatchingShardTrigger.kt @@ -9,16 +9,18 @@ import com.emarsys.core.request.model.RequestModel import com.emarsys.core.shard.ShardModel import com.emarsys.core.shard.specification.FilterByShardIds import com.emarsys.core.util.predicate.Predicate +import kotlinx.coroutines.runBlocking class BatchingShardTrigger( - private val repository: Repository, - private val predicate: Predicate>, - private val querySpecification: SqlSpecification, - private val chunker: Mapper, List>>, - private val merger: Mapper, RequestModel>, - private val requestManager: RequestManager, - private val requestStrategy: RequestStrategy, - private val connectionWatchDog: ConnectionWatchDog) : Runnable { + private val repository: Repository, + private val predicate: Predicate>, + private val querySpecification: SqlSpecification, + private val chunker: Mapper, List>>, + private val merger: Mapper, RequestModel>, + private val requestManager: RequestManager, + private val requestStrategy: RequestStrategy, + private val connectionWatchDog: ConnectionWatchDog +) : Runnable { enum class RequestStrategy { PERSISTENT, TRANSIENT @@ -31,7 +33,9 @@ class BatchingShardTrigger( val chunks = chunker.map(shards) chunks.forEach { submit(merger.map(it)) - repository.remove(FilterByShardIds(it)) + runBlocking { + repository.remove(FilterByShardIds(it)) + } } } } diff --git a/core/src/main/java/com/emarsys/core/util/log/Logger.kt b/core/src/main/java/com/emarsys/core/util/log/Logger.kt index 124623d64..fe710a4db 100644 --- a/core/src/main/java/com/emarsys/core/util/log/Logger.kt +++ b/core/src/main/java/com/emarsys/core/util/log/Logger.kt @@ -9,7 +9,7 @@ import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.di.CoreComponent import com.emarsys.core.di.core import com.emarsys.core.endpoint.Endpoint.LOG_URL -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.provider.wrapper.WrapperInfoContainer @@ -17,16 +17,17 @@ import com.emarsys.core.shard.ShardModel import com.emarsys.core.storage.Storage import com.emarsys.core.util.log.LogLevel.* import com.emarsys.core.util.log.entry.* +import kotlinx.coroutines.runBlocking @Mockable class Logger( - private val coreSdkHandler: CoreSdkHandler, - private val shardRepository: Repository, - private val timestampProvider: TimestampProvider, - private val uuidProvider: UUIDProvider, - private val logLevelStorage: Storage, - private val verboseConsoleLoggingEnabled: Boolean, - private val context: Context + private val concurrentHandlerHolder: ConcurrentHandlerHolder, + private val shardRepository: Repository, + private val timestampProvider: TimestampProvider, + private val uuidProvider: UUIDProvider, + private val logLevelStorage: Storage, + private val verboseConsoleLoggingEnabled: Boolean, + private val context: Context ) { companion object { @@ -80,7 +81,7 @@ class Logger( fun handleLog(logLevel: LogLevel, logEntry: LogEntry, onCompleted: (() -> Unit)? = null) { val currentThreadName = Thread.currentThread().name - core().coreSdkHandler.post { + core().concurrentHandlerHolder.coreHandler.post { val isDebugMode: Boolean = 0 != context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE if ((verboseConsoleLoggingEnabled || logEntry is MethodNotAllowed) && isDebugMode) { @@ -120,12 +121,20 @@ class Logger( if (isAppStartLog(logEntry) || (isNotLogLog(logEntry) && shouldLogBasedOnRemoteConfig(logLevel)) ) { - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { val shard = ShardModel.Builder(timestampProvider, uuidProvider) .type(logEntry.topic) - .payloadEntries(logEntry.toData(logLevel, currentThreadName, WrapperInfoContainer.wrapperInfo)) + .payloadEntries( + logEntry.toData( + logLevel, + currentThreadName, + WrapperInfoContainer.wrapperInfo + ) + ) .build() - shardRepository.add(shard) + runBlocking { + shardRepository.add(shard) + } onCompleted?.invoke() } } else { diff --git a/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.java b/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.java deleted file mode 100644 index e102ddeff..000000000 --- a/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.emarsys.core.worker; - -import android.os.Handler; - -import com.emarsys.core.CoreCompletionHandler; -import com.emarsys.core.database.repository.Repository; -import com.emarsys.core.database.repository.SqlSpecification; -import com.emarsys.core.handler.CoreSdkHandler; -import com.emarsys.core.request.factory.DefaultRunnableFactory; -import com.emarsys.core.request.factory.RunnableFactory; -import com.emarsys.core.request.model.RequestModel; -import com.emarsys.core.request.model.RequestModelKt; -import com.emarsys.core.request.model.specification.FilterByRequestIds; -import com.emarsys.core.response.ResponseModel; -import com.emarsys.core.util.Assert; -import com.emarsys.core.util.RequestModelUtils; - -import java.util.Arrays; - -public class CoreCompletionHandlerMiddleware implements CoreCompletionHandler { - CoreCompletionHandler coreCompletionHandler; - Repository requestRepository; - Worker worker; - CoreSdkHandler coreSDKHandler; - Handler uiHandler; - RunnableFactory runnableFactory; - - public CoreCompletionHandlerMiddleware( - Worker worker, - Repository requestRepository, - Handler uiHandler, - CoreSdkHandler coreSDKHandler, - CoreCompletionHandler coreCompletionHandler) { - Assert.notNull(requestRepository, "RequestRepository must not be null!"); - Assert.notNull(worker, "Worker must not be null!"); - Assert.notNull(coreCompletionHandler, "CoreCompletionHandler must not be null!"); - Assert.notNull(uiHandler, "uiHandler must not be null!"); - Assert.notNull(coreSDKHandler, "coreSDKHandler must not be null!"); - this.coreCompletionHandler = coreCompletionHandler; - this.requestRepository = requestRepository; - this.worker = worker; - this.coreSDKHandler = coreSDKHandler; - this.runnableFactory = new DefaultRunnableFactory(); - this.uiHandler = uiHandler; - } - - @Override - public void onSuccess(final String id, final ResponseModel responseModel) { - coreSDKHandler.post(runnableFactory.runnableFrom(new Runnable() { - @Override - public void run() { - removeRequestModel(responseModel); - - worker.unlock(); - worker.run(); - - handleSuccess(responseModel); - } - })); - } - - @Override - public void onError(final String id, final ResponseModel responseModel) { - coreSDKHandler.post(runnableFactory.runnableFrom(new Runnable() { - @Override - public void run() { - if (isNonRetriableError(responseModel.getStatusCode())) { - removeRequestModel(responseModel); - - handleError(responseModel); - worker.unlock(); - worker.run(); - } else { - worker.unlock(); - } - } - })); - } - - private void removeRequestModel(ResponseModel responseModel) { - String[] ids = RequestModelKt.collectRequestIds(responseModel.getRequestModel()); - - int maximumRequestCountInTransaction = 50; - int noOfIterations = ids.length % maximumRequestCountInTransaction == 0 ? ids.length / maximumRequestCountInTransaction : ids.length / maximumRequestCountInTransaction + 1; - for (int i = 0; i < noOfIterations; i++) { - int noOfElements = Math.min(ids.length, (i + 1) * maximumRequestCountInTransaction); - requestRepository.remove(new FilterByRequestIds(Arrays.copyOfRange(ids, i * maximumRequestCountInTransaction, noOfElements))); - } - } - - @Override - public void onError(final String id, final Exception cause) { - coreSDKHandler.post(runnableFactory.runnableFrom(new Runnable() { - @Override - public void run() { - worker.unlock(); - uiHandler.post(runnableFactory.runnableFrom(new Runnable() { - @Override - public void run() { - coreCompletionHandler.onError(id, cause); - } - })); - } - })); - } - - private boolean isNonRetriableError(int statusCode) { - if (statusCode == 408 || statusCode == 429) { - return false; - } else { - return 400 <= statusCode && statusCode < 500; - } - } - - private void handleSuccess(final ResponseModel responseModel) { - for (final String id : RequestModelUtils.extractIdsFromCompositeRequestModel(responseModel.getRequestModel())) { - uiHandler.post(runnableFactory.runnableFrom(new Runnable() { - @Override - public void run() { - coreCompletionHandler.onSuccess(id, responseModel); - } - })); - } - } - - private void handleError(final ResponseModel responseModel) { - for (final String id : RequestModelUtils.extractIdsFromCompositeRequestModel(responseModel.getRequestModel())) { - uiHandler.post(runnableFactory.runnableFrom(new Runnable() { - @Override - public void run() { - coreCompletionHandler.onError(id, responseModel); - } - })); - } - } -} diff --git a/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.kt b/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.kt new file mode 100644 index 000000000..4a613ed31 --- /dev/null +++ b/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.kt @@ -0,0 +1,108 @@ +package com.emarsys.core.worker + +import com.emarsys.core.CoreCompletionHandler +import com.emarsys.core.Mockable +import com.emarsys.core.database.repository.Repository +import com.emarsys.core.database.repository.SqlSpecification +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.core.request.model.RequestModel +import com.emarsys.core.request.model.collectRequestIds +import com.emarsys.core.request.model.specification.FilterByRequestIds +import com.emarsys.core.response.ResponseModel +import com.emarsys.core.util.RequestModelUtils +import kotlinx.coroutines.runBlocking +import java.util.* + +@Mockable +class CoreCompletionHandlerMiddleware( + private var worker: Worker?, + private val requestRepository: Repository, + var concurrentHandlerHolder: ConcurrentHandlerHolder, + val coreCompletionHandler: CoreCompletionHandler? +) : CoreCompletionHandler { + + override fun onSuccess(id: String, responseModel: ResponseModel) { + concurrentHandlerHolder.coreHandler.post { + removeRequestModel(responseModel) + worker?.unlock() + worker?.run() + handleSuccess(responseModel) + } + } + + override fun onError(id: String, responseModel: ResponseModel) { + concurrentHandlerHolder.coreHandler.post { + if (isNonRetriableError(responseModel.statusCode)) { + removeRequestModel(responseModel) + handleError(responseModel) + worker?.unlock() + worker?.run() + } else { + worker?.unlock() + } + } + } + + private fun removeRequestModel(responseModel: ResponseModel) { + val ids = responseModel.requestModel.collectRequestIds() + val maximumRequestCountInTransaction = 50 + val noOfIterations = + if (ids.size % maximumRequestCountInTransaction == 0) ids.size / maximumRequestCountInTransaction else ids.size / maximumRequestCountInTransaction + 1 + for (i in 0 until noOfIterations) { + val noOfElements = Math.min(ids.size, (i + 1) * maximumRequestCountInTransaction) + runBlocking { + requestRepository.remove( + FilterByRequestIds( + Arrays.copyOfRange( + ids, + i * maximumRequestCountInTransaction, + noOfElements + ) + ) + ) + } + } + } + + override fun onError(id: String, cause: Exception) { + concurrentHandlerHolder.coreHandler.post { + worker?.unlock() + concurrentHandlerHolder.uiHandler.post { + coreCompletionHandler?.onError( + id, + cause + ) + } + } + } + + private fun isNonRetriableError(statusCode: Int): Boolean { + return if (statusCode == 408 || statusCode == 429) { + false + } else { + 400 <= statusCode && statusCode < 500 + } + } + + private fun handleSuccess(responseModel: ResponseModel) { + for (id in RequestModelUtils.extractIdsFromCompositeRequestModel(responseModel.requestModel)) { + concurrentHandlerHolder.uiHandler.post { + coreCompletionHandler?.onSuccess( + id, + responseModel + ) + } + } + } + + private fun handleError(responseModel: ResponseModel) { + for (id in RequestModelUtils.extractIdsFromCompositeRequestModel(responseModel.requestModel)) { + concurrentHandlerHolder.uiHandler.post { + coreCompletionHandler?.onError( + id, + responseModel + ) + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/worker/DefaultWorker.java b/core/src/main/java/com/emarsys/core/worker/DefaultWorker.java deleted file mode 100644 index 7436fe682..000000000 --- a/core/src/main/java/com/emarsys/core/worker/DefaultWorker.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.emarsys.core.worker; - -import android.os.Handler; -import com.emarsys.core.CoreCompletionHandler; -import com.emarsys.core.connection.ConnectionChangeListener; -import com.emarsys.core.connection.ConnectionState; -import com.emarsys.core.connection.ConnectionWatchDog; -import com.emarsys.core.database.repository.Repository; -import com.emarsys.core.database.repository.SqlSpecification; -import com.emarsys.core.database.repository.specification.Everything; -import com.emarsys.core.request.RequestExpiredException; -import com.emarsys.core.request.RestClient; -import com.emarsys.core.request.factory.CompletionHandlerProxyProvider; -import com.emarsys.core.request.model.RequestModel; -import com.emarsys.core.request.model.specification.FilterByRequestIds; -import com.emarsys.core.request.model.specification.QueryLatestRequestModel; -import com.emarsys.core.util.Assert; -import com.emarsys.core.util.log.Logger; -import com.emarsys.core.util.log.entry.OfflineQueueSize; - -import java.util.List; - - -public class DefaultWorker implements ConnectionChangeListener, Worker { - - private final CompletionHandlerProxyProvider proxyProvider; - Repository requestRepository; - ConnectionWatchDog connectionWatchDog; - private boolean locked; - CoreCompletionHandler coreCompletionHandler; - RestClient restClient; - private final Handler uiHandler; - - public DefaultWorker(Repository requestRepository, ConnectionWatchDog connectionWatchDog, Handler uiHandler, CoreCompletionHandler coreCompletionHandler, RestClient restClient, CompletionHandlerProxyProvider proxyProvider) { - Assert.notNull(requestRepository, "RequestRepository must not be null!"); - Assert.notNull(connectionWatchDog, "ConnectionWatchDog must not be null!"); - Assert.notNull(uiHandler, "UiHandler must not be null!"); - Assert.notNull(coreCompletionHandler, "CoreCompletionHandler must not be null!"); - Assert.notNull(restClient, "RestClient must not be null!"); - Assert.notNull(proxyProvider, "ProxyProvider must not be null!"); - - this.coreCompletionHandler = coreCompletionHandler; - this.requestRepository = requestRepository; - this.connectionWatchDog = connectionWatchDog; - this.connectionWatchDog.registerReceiver(this); - this.uiHandler = uiHandler; - this.restClient = restClient; - this.proxyProvider = proxyProvider; - } - - @Override - public void lock() { - locked = true; - } - - @Override - public void unlock() { - locked = false; - } - - @Override - public boolean isLocked() { - return locked; - } - - @Override - public void run() { - if (!isLocked() && connectionWatchDog.isConnected() && !requestRepository.isEmpty()) { - lock(); - RequestModel model = findFirstNonExpiredModel(); - if (model != null) { - restClient.execute( - model, - proxyProvider.provideProxy(this, coreCompletionHandler)); - } else { - unlock(); - } - } - } - - @Override - public void onConnectionChanged(ConnectionState connectionState, boolean isConnected) { - if (isConnected) { - Logger.debug(new OfflineQueueSize(requestRepository.query(new Everything()).size()), false); - run(); - } - } - - private RequestModel findFirstNonExpiredModel() { - while (!requestRepository.isEmpty()) { - List result = requestRepository.query(new QueryLatestRequestModel()); - if (!result.isEmpty()) { - RequestModel model = result.get(0); - if (isExpired(model)) { - handleExpiration(model); - } else { - return model; - } - } else { - break; - } - } - return null; - } - - private boolean isExpired(RequestModel model) { - long now = System.currentTimeMillis(); - return now - model.getTimestamp() > model.getTtl(); - } - - private void handleExpiration(final RequestModel expiredModel) { - String[] ids = {expiredModel.getId()}; - requestRepository.remove(new FilterByRequestIds(ids)); - uiHandler.post(new Runnable() { - @Override - public void run() { - coreCompletionHandler.onError(expiredModel.getId(), new RequestExpiredException("Request expired", expiredModel.getUrl().getPath())); - } - }); - } -} \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/worker/DefaultWorker.kt b/core/src/main/java/com/emarsys/core/worker/DefaultWorker.kt new file mode 100644 index 000000000..b6fd5cf5e --- /dev/null +++ b/core/src/main/java/com/emarsys/core/worker/DefaultWorker.kt @@ -0,0 +1,103 @@ +package com.emarsys.core.worker + +import android.os.Handler +import com.emarsys.core.CoreCompletionHandler +import com.emarsys.core.Mockable +import com.emarsys.core.connection.ConnectionChangeListener +import com.emarsys.core.connection.ConnectionState +import com.emarsys.core.connection.ConnectionWatchDog +import com.emarsys.core.database.repository.Repository +import com.emarsys.core.database.repository.SqlSpecification +import com.emarsys.core.database.repository.specification.Everything +import com.emarsys.core.request.RequestExpiredException +import com.emarsys.core.request.RestClient +import com.emarsys.core.request.factory.CompletionHandlerProxyProvider +import com.emarsys.core.request.model.RequestModel +import com.emarsys.core.request.model.specification.FilterByRequestIds +import com.emarsys.core.request.model.specification.QueryLatestRequestModel +import com.emarsys.core.util.log.Logger.Companion.debug +import com.emarsys.core.util.log.entry.OfflineQueueSize +import kotlinx.coroutines.runBlocking + +@Mockable +class DefaultWorker( + var requestRepository: Repository, + var connectionWatchDog: ConnectionWatchDog, + val uiHandler: Handler, + var coreCompletionHandler: CoreCompletionHandler, + var restClient: RestClient, + val proxyProvider: CompletionHandlerProxyProvider +) : ConnectionChangeListener, Worker { + + final override var isLocked = false + private set + + init { + this.connectionWatchDog.registerReceiver(this) + } + + override fun lock() { + isLocked = true + } + + override fun unlock() { + isLocked = false + } + + override fun run() { + if (!isLocked && connectionWatchDog.isConnected && !requestRepository.isEmpty()) { + lock() + val model = findFirstNonExpiredModel() + if (model != null) { + restClient.execute( + model, + proxyProvider.provideProxy(this, coreCompletionHandler) + ) + } else { + unlock() + } + } + } + + override fun onConnectionChanged(connectionState: ConnectionState?, isConnected: Boolean) { + if (isConnected) { + debug(OfflineQueueSize(requestRepository.query(Everything()).size), false) + run() + } + } + + private fun findFirstNonExpiredModel(): RequestModel? { + while (!requestRepository.isEmpty()) { + val result = requestRepository.query(QueryLatestRequestModel()) + if (!result.isEmpty()) { + val model = result[0] + if (isExpired(model)) { + handleExpiration(model) + } else { + return model + } + } else { + break + } + } + return null + } + + private fun isExpired(model: RequestModel): Boolean { + val now = System.currentTimeMillis() + return now - model.timestamp > model.ttl + } + + private fun handleExpiration(expiredModel: RequestModel) { + val ids = arrayOf(expiredModel.id) + runBlocking { + requestRepository.remove(FilterByRequestIds(ids)) + } + uiHandler.post { + coreCompletionHandler.onError( + expiredModel.id, + RequestExpiredException("Request expired", expiredModel.url.path) + ) + } + } +} \ No newline at end of file diff --git a/emarsys-e2e-test/src/androidTest/java/com/emarsys/EmarsysE2ETests.kt b/emarsys-e2e-test/src/androidTest/java/com/emarsys/EmarsysE2ETests.kt index fcec421e3..ef15c6f3a 100644 --- a/emarsys-e2e-test/src/androidTest/java/com/emarsys/EmarsysE2ETests.kt +++ b/emarsys-e2e-test/src/androidTest/java/com/emarsys/EmarsysE2ETests.kt @@ -188,7 +188,7 @@ class EmarsysE2ETests { val latch = CountDownLatch(1) - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { fusedLocationProviderClient.lastLocation.addOnSuccessListener { currentLocation -> val testAction = JSONObject( mapOf( diff --git a/emarsys-e2e-test/src/androidTest/java/com/emarsys/testUtil/E2ETestUtils.kt b/emarsys-e2e-test/src/androidTest/java/com/emarsys/testUtil/E2ETestUtils.kt index ca7b4a181..f016e956c 100644 --- a/emarsys-e2e-test/src/androidTest/java/com/emarsys/testUtil/E2ETestUtils.kt +++ b/emarsys-e2e-test/src/androidTest/java/com/emarsys/testUtil/E2ETestUtils.kt @@ -10,7 +10,7 @@ object E2ETestUtils { fun tearDownEmarsys(application: Application? = null) { FeatureTestUtils.resetFeatures() - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { if (application != null) { application.unregisterActivityLifecycleCallbacks(emarsys().activityLifecycleWatchdog) application.unregisterActivityLifecycleCallbacks(emarsys().currentActivityWatchdog) @@ -31,7 +31,7 @@ object E2ETestUtils { emarsys().logLevelStorage.remove() emarsys().predictServiceStorage.remove() } - emarsys().coreSdkHandler.looper.quitSafely() + emarsys().concurrentHandlerHolder.looper.quitSafely() tearDownEmarsysComponent() } diff --git a/emarsys-firebase/src/androidTest/java/com/emarsys/fake/FakeFirebaseDependencyContainer.kt b/emarsys-firebase/src/androidTest/java/com/emarsys/fake/FakeFirebaseDependencyContainer.kt index 1ca1aed0b..d2cd8fb6e 100644 --- a/emarsys-firebase/src/androidTest/java/com/emarsys/fake/FakeFirebaseDependencyContainer.kt +++ b/emarsys-firebase/src/androidTest/java/com/emarsys/fake/FakeFirebaseDependencyContainer.kt @@ -9,7 +9,7 @@ import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog import com.emarsys.core.activity.CurrentActivityWatchdog import com.emarsys.core.app.AppLifecycleObserver -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.crypto.Crypto import com.emarsys.core.database.CoreSQLiteDatabase @@ -18,13 +18,14 @@ import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.device.DeviceInfo import com.emarsys.core.endpoint.ServiceEndpointProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import com.emarsys.core.provider.hardwareid.HardwareIdProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient +import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.shard.ShardModel @@ -68,94 +69,97 @@ import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock class FakeFirebaseDependencyContainer( - override val coreSdkHandler: CoreSdkHandler = CoreSdkHandlerProvider().provideHandler(), - override val uiHandler: Handler = Handler(Looper.getMainLooper()), - override val mobileEngageInternal: MobileEngageInternal = mock(), - override val loggingMobileEngageInternal: MobileEngageInternal = mock(), - override val clientServiceInternal: ClientServiceInternal = mock(), - override val loggingClientServiceInternal: ClientServiceInternal = mock(), - override val messageInboxInternal: MessageInboxInternal = mock(), - override val loggingMessageInboxInternal: MessageInboxInternal = mock(), - override val inAppInternal: InAppInternal = mock(), - override val loggingInAppInternal: InAppInternal = mock(), - override val deepLinkInternal: DeepLinkInternal = mock(), - override val loggingDeepLinkInternal: DeepLinkInternal = mock(), - override val pushInternal: PushInternal = mock(), - override val loggingPushInternal: PushInternal = mock(), - override val eventServiceInternal: EventServiceInternal = mock(), - override val loggingEventServiceInternal: EventServiceInternal = mock(), - override val refreshTokenInternal: RefreshTokenInternal = mock(), - override val inAppEventHandlerInternal: InAppEventHandlerInternal = mock(), - override val requestContext: MobileEngageRequestContext = mock(), - override val overlayInAppPresenter: OverlayInAppPresenter = mock(), - override val deviceInfoPayloadStorage: Storage = mock(), - override val contactFieldValueStorage: Storage = mock(), - override val contactTokenStorage: Storage = mock(), - override val clientStateStorage: Storage = mock(), - override val pushTokenStorage: Storage = mock(), - override val refreshTokenStorage: Storage = mock(), - override val clientServiceStorage: Storage = mock(), - override val eventServiceStorage: Storage = mock(), - override val deepLinkServiceStorage: Storage = mock(), - override val messageInboxServiceStorage: Storage = mock(), - override val deviceEventStateStorage: Storage = mock(), - override val responseHandlersProcessor: ResponseHandlersProcessor = mock(), - override val pushTokenProvider: PushTokenProvider = mock(), - override val clientServiceEndpointProvider: ServiceEndpointProvider = mock(), - override val eventServiceEndpointProvider: ServiceEndpointProvider = mock(), - override val deepLinkServiceProvider: ServiceEndpointProvider = mock(), - override val messageInboxServiceProvider: ServiceEndpointProvider = mock(), - override val notificationInformationListenerProvider: NotificationInformationListenerProvider = mock(), - override val silentNotificationInformationListenerProvider: SilentNotificationInformationListenerProvider = mock(), - override val notificationActionCommandFactory: ActionCommandFactory = mock(), - override val silentMessageActionCommandFactory: ActionCommandFactory = mock(), - override val notificationCacheableEventHandler: CacheableEventHandler = mock(), - override val silentMessageCacheableEventHandler: CacheableEventHandler = mock(), - override val onEventActionCacheableEventHandler: CacheableEventHandler = mock(), - override val geofenceCacheableEventHandler: CacheableEventHandler = mock(), - override val currentActivityProvider: CurrentActivityProvider = mock(), - override val geofenceInternal: GeofenceInternal = mock(), - override val loggingGeofenceInternal: GeofenceInternal = mock(), - override val buttonClickedRepository: Repository = mock(), - override val displayedIamRepository: Repository = mock(), - override val contactTokenResponseHandler: MobileEngageTokenResponseHandler = mock(), - override val webViewProvider: WebViewProvider = mock(), - override val inlineInAppWebViewFactory: InlineInAppWebViewFactory = mock(), - override val iamJsBridgeFactory: IamJsBridgeFactory = mock(), - override val remoteMessageMapper: RemoteMessageMapper = mock(), - override val appLifecycleObserver: AppLifecycleObserver = mock(), - override val requestModelHelper: RequestModelHelper = mock(), - override val sessionIdHolder: SessionIdHolder = mock(), - override val coreCompletionHandlerRefreshTokenProxyProvider: CoreCompletionHandlerRefreshTokenProxyProvider = mock(), - override val mobileEngageRequestModelFactory: MobileEngageRequestModelFactory = mock(), - override val mobileEngageSession: MobileEngageSession = mock(), - override val activityLifecycleWatchdog: ActivityLifecycleWatchdog = mock(), - override val currentActivityWatchdog: CurrentActivityWatchdog = mock(), - override val coreSQLiteDatabase: CoreSQLiteDatabase = mock(), - override val deviceInfo: DeviceInfo = mock(), - override val shardRepository: Repository = mock(), - override val timestampProvider: TimestampProvider = mock(), - override val uuidProvider: UUIDProvider = mock(), - override val logShardTrigger: Runnable = mock(), - override val logger: Logger = mock(), - override val restClient: RestClient = mock(), - override val fileDownloader: FileDownloader = mock(), - override val keyValueStore: KeyValueStore = mock(), - override val sharedPreferences: SharedPreferences = mock(), - override val hardwareIdProvider: HardwareIdProvider = mock(), - override val coreDbHelper: CoreDbHelper = mock(), - override val hardwareIdStorage: Storage = mock(), - override val logLevelStorage: Storage = mock(), - override val crypto: Crypto = mock(), - override val requestManager: RequestManager = mock(), - override val worker: Worker = mock(), - override val requestModelRepository: Repository = mock(), - override val connectionWatchdog: ConnectionWatchDog = mock(), - override val coreCompletionHandler: CoreCompletionHandler = mock(), - override val geofenceInitialEnterTriggerEnabledStorage: Storage = mock(), - override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), - override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), - override val notificationOpenedActivityClass: Class<*> = Activity::class.java, - override val coreSdkScope: CoroutineScope = mock(), - override val uiScope: CoroutineScope = mock() + override val uiHandler: Handler = Handler(Looper.getMainLooper()), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( + uiHandler + ).create(), + override val mobileEngageInternal: MobileEngageInternal = mock(), + override val loggingMobileEngageInternal: MobileEngageInternal = mock(), + override val clientServiceInternal: ClientServiceInternal = mock(), + override val loggingClientServiceInternal: ClientServiceInternal = mock(), + override val messageInboxInternal: MessageInboxInternal = mock(), + override val loggingMessageInboxInternal: MessageInboxInternal = mock(), + override val inAppInternal: InAppInternal = mock(), + override val loggingInAppInternal: InAppInternal = mock(), + override val deepLinkInternal: DeepLinkInternal = mock(), + override val loggingDeepLinkInternal: DeepLinkInternal = mock(), + override val pushInternal: PushInternal = mock(), + override val loggingPushInternal: PushInternal = mock(), + override val eventServiceInternal: EventServiceInternal = mock(), + override val loggingEventServiceInternal: EventServiceInternal = mock(), + override val refreshTokenInternal: RefreshTokenInternal = mock(), + override val inAppEventHandlerInternal: InAppEventHandlerInternal = mock(), + override val requestContext: MobileEngageRequestContext = mock(), + override val overlayInAppPresenter: OverlayInAppPresenter = mock(), + override val deviceInfoPayloadStorage: Storage = mock(), + override val contactFieldValueStorage: Storage = mock(), + override val contactTokenStorage: Storage = mock(), + override val clientStateStorage: Storage = mock(), + override val pushTokenStorage: Storage = mock(), + override val refreshTokenStorage: Storage = mock(), + override val clientServiceStorage: Storage = mock(), + override val eventServiceStorage: Storage = mock(), + override val deepLinkServiceStorage: Storage = mock(), + override val messageInboxServiceStorage: Storage = mock(), + override val deviceEventStateStorage: Storage = mock(), + override val responseHandlersProcessor: ResponseHandlersProcessor = mock(), + override val pushTokenProvider: PushTokenProvider = mock(), + override val clientServiceEndpointProvider: ServiceEndpointProvider = mock(), + override val eventServiceEndpointProvider: ServiceEndpointProvider = mock(), + override val deepLinkServiceProvider: ServiceEndpointProvider = mock(), + override val messageInboxServiceProvider: ServiceEndpointProvider = mock(), + override val notificationInformationListenerProvider: NotificationInformationListenerProvider = mock(), + override val silentNotificationInformationListenerProvider: SilentNotificationInformationListenerProvider = mock(), + override val notificationActionCommandFactory: ActionCommandFactory = mock(), + override val silentMessageActionCommandFactory: ActionCommandFactory = mock(), + override val notificationCacheableEventHandler: CacheableEventHandler = mock(), + override val silentMessageCacheableEventHandler: CacheableEventHandler = mock(), + override val onEventActionCacheableEventHandler: CacheableEventHandler = mock(), + override val geofenceCacheableEventHandler: CacheableEventHandler = mock(), + override val currentActivityProvider: CurrentActivityProvider = mock(), + override val geofenceInternal: GeofenceInternal = mock(), + override val loggingGeofenceInternal: GeofenceInternal = mock(), + override val buttonClickedRepository: Repository = mock(), + override val displayedIamRepository: Repository = mock(), + override val contactTokenResponseHandler: MobileEngageTokenResponseHandler = mock(), + override val webViewProvider: WebViewProvider = mock(), + override val inlineInAppWebViewFactory: InlineInAppWebViewFactory = mock(), + override val iamJsBridgeFactory: IamJsBridgeFactory = mock(), + override val remoteMessageMapper: RemoteMessageMapper = mock(), + override val appLifecycleObserver: AppLifecycleObserver = mock(), + override val requestModelHelper: RequestModelHelper = mock(), + override val sessionIdHolder: SessionIdHolder = mock(), + override val coreCompletionHandlerRefreshTokenProxyProvider: CoreCompletionHandlerRefreshTokenProxyProvider = mock(), + override val mobileEngageRequestModelFactory: MobileEngageRequestModelFactory = mock(), + override val mobileEngageSession: MobileEngageSession = mock(), + override val activityLifecycleWatchdog: ActivityLifecycleWatchdog = mock(), + override val currentActivityWatchdog: CurrentActivityWatchdog = mock(), + override val coreSQLiteDatabase: CoreSQLiteDatabase = mock(), + override val deviceInfo: DeviceInfo = mock(), + override val shardRepository: Repository = mock(), + override val timestampProvider: TimestampProvider = mock(), + override val uuidProvider: UUIDProvider = mock(), + override val logShardTrigger: Runnable = mock(), + override val logger: Logger = mock(), + override val restClient: RestClient = mock(), + override val fileDownloader: FileDownloader = mock(), + override val keyValueStore: KeyValueStore = mock(), + override val sharedPreferences: SharedPreferences = mock(), + override val hardwareIdProvider: HardwareIdProvider = mock(), + override val coreDbHelper: CoreDbHelper = mock(), + override val hardwareIdStorage: Storage = mock(), + override val logLevelStorage: Storage = mock(), + override val crypto: Crypto = mock(), + override val requestManager: RequestManager = mock(), + override val worker: Worker = mock(), + override val requestModelRepository: Repository = mock(), + override val connectionWatchdog: ConnectionWatchDog = mock(), + override val coreCompletionHandler: CoreCompletionHandler = mock(), + override val geofenceInitialEnterTriggerEnabledStorage: Storage = mock(), + override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), + override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), + override val notificationOpenedActivityClass: Class<*> = Activity::class.java, + override val coreSdkScope: CoroutineScope = mock(), + override val uiScope: CoroutineScope = mock(), + override val runnableFactory: RunnableFactory = mock() ) : MobileEngageComponent \ No newline at end of file diff --git a/emarsys-firebase/src/androidTest/java/com/emarsys/service/EmarsysFirebaseMessagingServiceTest.kt b/emarsys-firebase/src/androidTest/java/com/emarsys/service/EmarsysFirebaseMessagingServiceTest.kt index 8984d62c3..4e9aac63b 100644 --- a/emarsys-firebase/src/androidTest/java/com/emarsys/service/EmarsysFirebaseMessagingServiceTest.kt +++ b/emarsys-firebase/src/androidTest/java/com/emarsys/service/EmarsysFirebaseMessagingServiceTest.kt @@ -1,14 +1,19 @@ package com.emarsys.service import android.app.Application +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.device.DeviceInfo -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.core.handler.SdkHandler import com.emarsys.fake.FakeFirebaseDependencyContainer import com.emarsys.mobileengage.di.setupMobileEngageComponent import com.emarsys.mobileengage.di.tearDownMobileEngageComponent import com.emarsys.mobileengage.push.PushInternal import com.emarsys.testUtil.FeatureTestUtils import com.emarsys.testUtil.InstrumentationRegistry +import com.emarsys.testUtil.ReflectionTestUtils import com.emarsys.testUtil.TimeoutUtils import org.junit.After import org.junit.Before @@ -16,8 +21,6 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.mockito.kotlin.* -import org.mockito.stubbing.Answer -import java.util.concurrent.CountDownLatch class EmarsysFirebaseMessagingServiceTest { @@ -31,19 +34,22 @@ class EmarsysFirebaseMessagingServiceTest { private lateinit var mockPushInternal: PushInternal private lateinit var fakeDependencyContainer: FakeFirebaseDependencyContainer - private lateinit var mockCoreSdkHandler: CoreSdkHandler - val latch = CountDownLatch(1) + private lateinit var concurrentHandlerHolderFactory: ConcurrentHandlerHolderFactory + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + private lateinit var spyCoreHandler: SdkHandler @Before fun setUp() { mockPushInternal = mock() - - mockCoreSdkHandler = mock { - on { post(any()) } doAnswer Answer { invocation -> - invocation.getArgument(0).run() - null - } - } + concurrentHandlerHolderFactory = + ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())) + concurrentHandlerHolder = concurrentHandlerHolderFactory.create() + spyCoreHandler = spy(concurrentHandlerHolder.coreHandler) + ReflectionTestUtils.setInstanceField( + concurrentHandlerHolder, + "coreHandler", + spyCoreHandler + ) } @After @@ -67,7 +73,7 @@ class EmarsysFirebaseMessagingServiceTest { EmarsysFirebaseMessagingService().onNewToken("testToken") - verify(mockCoreSdkHandler, timeout(1000).times(1)).post(any()) + verify(spyCoreHandler, timeout(1000).times(1)).post(any()) } @Test @@ -97,7 +103,7 @@ class EmarsysFirebaseMessagingServiceTest { ) fakeDependencyContainer = FakeFirebaseDependencyContainer( - coreSdkHandler = mockCoreSdkHandler, + concurrentHandlerHolder = concurrentHandlerHolder, deviceInfo = deviceInfo, pushInternal = mockPushInternal ) diff --git a/emarsys-firebase/src/main/java/com/emarsys/service/EmarsysFirebaseMessagingService.kt b/emarsys-firebase/src/main/java/com/emarsys/service/EmarsysFirebaseMessagingService.kt index 4b3dc4539..2bb63fe2b 100644 --- a/emarsys-firebase/src/main/java/com/emarsys/service/EmarsysFirebaseMessagingService.kt +++ b/emarsys-firebase/src/main/java/com/emarsys/service/EmarsysFirebaseMessagingService.kt @@ -7,7 +7,7 @@ import com.google.firebase.messaging.RemoteMessage class EmarsysFirebaseMessagingService : FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) - mobileEngage().coreSdkHandler.post { + mobileEngage().concurrentHandlerHolder.coreHandler.post { if (mobileEngage().deviceInfo.isAutomaticPushSendingEnabled) { mobileEngage().pushInternal.setPushToken(token, null) } diff --git a/emarsys-firebase/src/main/java/com/emarsys/service/EmarsysFirebaseMessagingServiceUtils.kt b/emarsys-firebase/src/main/java/com/emarsys/service/EmarsysFirebaseMessagingServiceUtils.kt index 04869f0f0..7ff7ab024 100644 --- a/emarsys-firebase/src/main/java/com/emarsys/service/EmarsysFirebaseMessagingServiceUtils.kt +++ b/emarsys-firebase/src/main/java/com/emarsys/service/EmarsysFirebaseMessagingServiceUtils.kt @@ -1,7 +1,7 @@ package com.emarsys.service import android.content.Context -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.mobileengage.di.mobileEngage import com.emarsys.mobileengage.service.MessagingServiceUtils import com.emarsys.mobileengage.service.MessagingServiceUtils.handleMessage @@ -12,16 +12,17 @@ object EmarsysFirebaseMessagingServiceUtils { @JvmStatic fun handleMessage(context: Context, remoteMessage: RemoteMessage): Boolean { - val handler: CoreSdkHandler = mobileEngage().coreSdkHandler + val handlerHolder: ConcurrentHandlerHolder = mobileEngage().concurrentHandlerHolder - handler.post { + handlerHolder.coreHandler.post { handleMessage( - context, - remoteMessage.data, - mobileEngage().deviceInfo, - mobileEngage().fileDownloader, - mobileEngage().silentMessageActionCommandFactory, - mobileEngage().remoteMessageMapper) + context, + remoteMessage.data, + mobileEngage().deviceInfo, + mobileEngage().fileDownloader, + mobileEngage().silentMessageActionCommandFactory, + mobileEngage().remoteMessageMapper + ) } return isMobileEngageMessage(remoteMessage.data) diff --git a/emarsys-huawei/src/androidTest/java/com/emarsys/fake/FakeHuaweiDependencyContainer.kt b/emarsys-huawei/src/androidTest/java/com/emarsys/fake/FakeHuaweiDependencyContainer.kt index fc7400672..15241fbb4 100644 --- a/emarsys-huawei/src/androidTest/java/com/emarsys/fake/FakeHuaweiDependencyContainer.kt +++ b/emarsys-huawei/src/androidTest/java/com/emarsys/fake/FakeHuaweiDependencyContainer.kt @@ -9,7 +9,7 @@ import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog import com.emarsys.core.activity.CurrentActivityWatchdog import com.emarsys.core.app.AppLifecycleObserver -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.crypto.Crypto import com.emarsys.core.database.CoreSQLiteDatabase @@ -18,13 +18,14 @@ import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.device.DeviceInfo import com.emarsys.core.endpoint.ServiceEndpointProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import com.emarsys.core.provider.hardwareid.HardwareIdProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient +import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.shard.ShardModel @@ -68,94 +69,97 @@ import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock class FakeHuaweiDependencyContainer( - override val coreSdkHandler: CoreSdkHandler = CoreSdkHandlerProvider().provideHandler(), - override val uiHandler: Handler = Handler(Looper.getMainLooper()), - override val mobileEngageInternal: MobileEngageInternal = mock(), - override val loggingMobileEngageInternal: MobileEngageInternal = mock(), - override val clientServiceInternal: ClientServiceInternal = mock(), - override val loggingClientServiceInternal: ClientServiceInternal = mock(), - override val messageInboxInternal: MessageInboxInternal = mock(), - override val loggingMessageInboxInternal: MessageInboxInternal = mock(), - override val inAppInternal: InAppInternal = mock(), - override val loggingInAppInternal: InAppInternal = mock(), - override val deepLinkInternal: DeepLinkInternal = mock(), - override val loggingDeepLinkInternal: DeepLinkInternal = mock(), - override val pushInternal: PushInternal = mock(), - override val loggingPushInternal: PushInternal = mock(), - override val eventServiceInternal: EventServiceInternal = mock(), - override val loggingEventServiceInternal: EventServiceInternal = mock(), - override val refreshTokenInternal: RefreshTokenInternal = mock(), - override val inAppEventHandlerInternal: InAppEventHandlerInternal = mock(), - override val requestContext: MobileEngageRequestContext = mock(), - override val overlayInAppPresenter: OverlayInAppPresenter = mock(), - override val deviceInfoPayloadStorage: Storage = mock(), - override val contactFieldValueStorage: Storage = mock(), - override val contactTokenStorage: Storage = mock(), - override val clientStateStorage: Storage = mock(), - override val pushTokenStorage: Storage = mock(), - override val refreshTokenStorage: Storage = mock(), - override val clientServiceStorage: Storage = mock(), - override val eventServiceStorage: Storage = mock(), - override val deepLinkServiceStorage: Storage = mock(), - override val messageInboxServiceStorage: Storage = mock(), - override val deviceEventStateStorage: Storage = mock(), - override val responseHandlersProcessor: ResponseHandlersProcessor = mock(), - override val pushTokenProvider: PushTokenProvider = mock(), - override val clientServiceEndpointProvider: ServiceEndpointProvider = mock(), - override val eventServiceEndpointProvider: ServiceEndpointProvider = mock(), - override val deepLinkServiceProvider: ServiceEndpointProvider = mock(), - override val messageInboxServiceProvider: ServiceEndpointProvider = mock(), - override val notificationInformationListenerProvider: NotificationInformationListenerProvider = mock(), - override val silentNotificationInformationListenerProvider: SilentNotificationInformationListenerProvider = mock(), - override val notificationActionCommandFactory: ActionCommandFactory = mock(), - override val silentMessageActionCommandFactory: ActionCommandFactory = mock(), - override val notificationCacheableEventHandler: CacheableEventHandler = mock(), - override val silentMessageCacheableEventHandler: CacheableEventHandler = mock(), - override val onEventActionCacheableEventHandler: CacheableEventHandler = mock(), - override val geofenceCacheableEventHandler: CacheableEventHandler = mock(), - override val currentActivityProvider: CurrentActivityProvider = mock(), - override val geofenceInternal: GeofenceInternal = mock(), - override val loggingGeofenceInternal: GeofenceInternal = mock(), - override val buttonClickedRepository: Repository = mock(), - override val displayedIamRepository: Repository = mock(), - override val contactTokenResponseHandler: MobileEngageTokenResponseHandler = mock(), - override val webViewProvider: WebViewProvider = mock(), - override val inlineInAppWebViewFactory: InlineInAppWebViewFactory = mock(), - override val iamJsBridgeFactory: IamJsBridgeFactory = mock(), - override val remoteMessageMapper: RemoteMessageMapper = mock(), - override val appLifecycleObserver: AppLifecycleObserver = mock(), - override val requestModelHelper: RequestModelHelper = mock(), - override val sessionIdHolder: SessionIdHolder = mock(), - override val coreCompletionHandlerRefreshTokenProxyProvider: CoreCompletionHandlerRefreshTokenProxyProvider = mock(), - override val mobileEngageRequestModelFactory: MobileEngageRequestModelFactory = mock(), - override val mobileEngageSession: MobileEngageSession = mock(), - override val activityLifecycleWatchdog: ActivityLifecycleWatchdog = mock(), - override val currentActivityWatchdog: CurrentActivityWatchdog = mock(), - override val coreSQLiteDatabase: CoreSQLiteDatabase = mock(), - override val deviceInfo: DeviceInfo = mock(), - override val shardRepository: Repository = mock(), - override val timestampProvider: TimestampProvider = mock(), - override val uuidProvider: UUIDProvider = mock(), - override val logShardTrigger: Runnable = mock(), - override val logger: Logger = mock(), - override val restClient: RestClient = mock(), - override val fileDownloader: FileDownloader = mock(), - override val keyValueStore: KeyValueStore = mock(), - override val sharedPreferences: SharedPreferences = mock(), - override val hardwareIdProvider: HardwareIdProvider = mock(), - override val coreDbHelper: CoreDbHelper = mock(), - override val hardwareIdStorage: Storage = mock(), - override val logLevelStorage: Storage = mock(), - override val crypto: Crypto = mock(), - override val requestManager: RequestManager = mock(), - override val worker: Worker = mock(), - override val requestModelRepository: Repository = mock(), - override val connectionWatchdog: ConnectionWatchDog = mock(), - override val coreCompletionHandler: CoreCompletionHandler = mock(), - override val geofenceInitialEnterTriggerEnabledStorage: Storage = mock(), - override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), - override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), - override val notificationOpenedActivityClass: Class<*> = Activity::class.java, - override val coreSdkScope: CoroutineScope = mock(), - override val uiScope: CoroutineScope = mock() + override val uiHandler: Handler = Handler(Looper.getMainLooper()), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( + uiHandler + ).create(), + override val mobileEngageInternal: MobileEngageInternal = mock(), + override val loggingMobileEngageInternal: MobileEngageInternal = mock(), + override val clientServiceInternal: ClientServiceInternal = mock(), + override val loggingClientServiceInternal: ClientServiceInternal = mock(), + override val messageInboxInternal: MessageInboxInternal = mock(), + override val loggingMessageInboxInternal: MessageInboxInternal = mock(), + override val inAppInternal: InAppInternal = mock(), + override val loggingInAppInternal: InAppInternal = mock(), + override val deepLinkInternal: DeepLinkInternal = mock(), + override val loggingDeepLinkInternal: DeepLinkInternal = mock(), + override val pushInternal: PushInternal = mock(), + override val loggingPushInternal: PushInternal = mock(), + override val eventServiceInternal: EventServiceInternal = mock(), + override val loggingEventServiceInternal: EventServiceInternal = mock(), + override val refreshTokenInternal: RefreshTokenInternal = mock(), + override val inAppEventHandlerInternal: InAppEventHandlerInternal = mock(), + override val requestContext: MobileEngageRequestContext = mock(), + override val overlayInAppPresenter: OverlayInAppPresenter = mock(), + override val deviceInfoPayloadStorage: Storage = mock(), + override val contactFieldValueStorage: Storage = mock(), + override val contactTokenStorage: Storage = mock(), + override val clientStateStorage: Storage = mock(), + override val pushTokenStorage: Storage = mock(), + override val refreshTokenStorage: Storage = mock(), + override val clientServiceStorage: Storage = mock(), + override val eventServiceStorage: Storage = mock(), + override val deepLinkServiceStorage: Storage = mock(), + override val messageInboxServiceStorage: Storage = mock(), + override val deviceEventStateStorage: Storage = mock(), + override val responseHandlersProcessor: ResponseHandlersProcessor = mock(), + override val pushTokenProvider: PushTokenProvider = mock(), + override val clientServiceEndpointProvider: ServiceEndpointProvider = mock(), + override val eventServiceEndpointProvider: ServiceEndpointProvider = mock(), + override val deepLinkServiceProvider: ServiceEndpointProvider = mock(), + override val messageInboxServiceProvider: ServiceEndpointProvider = mock(), + override val notificationInformationListenerProvider: NotificationInformationListenerProvider = mock(), + override val silentNotificationInformationListenerProvider: SilentNotificationInformationListenerProvider = mock(), + override val notificationActionCommandFactory: ActionCommandFactory = mock(), + override val silentMessageActionCommandFactory: ActionCommandFactory = mock(), + override val notificationCacheableEventHandler: CacheableEventHandler = mock(), + override val silentMessageCacheableEventHandler: CacheableEventHandler = mock(), + override val onEventActionCacheableEventHandler: CacheableEventHandler = mock(), + override val geofenceCacheableEventHandler: CacheableEventHandler = mock(), + override val currentActivityProvider: CurrentActivityProvider = mock(), + override val geofenceInternal: GeofenceInternal = mock(), + override val loggingGeofenceInternal: GeofenceInternal = mock(), + override val buttonClickedRepository: Repository = mock(), + override val displayedIamRepository: Repository = mock(), + override val contactTokenResponseHandler: MobileEngageTokenResponseHandler = mock(), + override val webViewProvider: WebViewProvider = mock(), + override val inlineInAppWebViewFactory: InlineInAppWebViewFactory = mock(), + override val iamJsBridgeFactory: IamJsBridgeFactory = mock(), + override val remoteMessageMapper: RemoteMessageMapper = mock(), + override val appLifecycleObserver: AppLifecycleObserver = mock(), + override val requestModelHelper: RequestModelHelper = mock(), + override val sessionIdHolder: SessionIdHolder = mock(), + override val coreCompletionHandlerRefreshTokenProxyProvider: CoreCompletionHandlerRefreshTokenProxyProvider = mock(), + override val mobileEngageRequestModelFactory: MobileEngageRequestModelFactory = mock(), + override val mobileEngageSession: MobileEngageSession = mock(), + override val activityLifecycleWatchdog: ActivityLifecycleWatchdog = mock(), + override val currentActivityWatchdog: CurrentActivityWatchdog = mock(), + override val coreSQLiteDatabase: CoreSQLiteDatabase = mock(), + override val deviceInfo: DeviceInfo = mock(), + override val shardRepository: Repository = mock(), + override val timestampProvider: TimestampProvider = mock(), + override val uuidProvider: UUIDProvider = mock(), + override val logShardTrigger: Runnable = mock(), + override val logger: Logger = mock(), + override val restClient: RestClient = mock(), + override val fileDownloader: FileDownloader = mock(), + override val keyValueStore: KeyValueStore = mock(), + override val sharedPreferences: SharedPreferences = mock(), + override val hardwareIdProvider: HardwareIdProvider = mock(), + override val coreDbHelper: CoreDbHelper = mock(), + override val hardwareIdStorage: Storage = mock(), + override val logLevelStorage: Storage = mock(), + override val crypto: Crypto = mock(), + override val requestManager: RequestManager = mock(), + override val worker: Worker = mock(), + override val requestModelRepository: Repository = mock(), + override val connectionWatchdog: ConnectionWatchDog = mock(), + override val coreCompletionHandler: CoreCompletionHandler = mock(), + override val geofenceInitialEnterTriggerEnabledStorage: Storage = mock(), + override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), + override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), + override val notificationOpenedActivityClass: Class<*> = Activity::class.java, + override val coreSdkScope: CoroutineScope = mock(), + override val uiScope: CoroutineScope = mock(), + override val runnableFactory: RunnableFactory = mock() ) : MobileEngageComponent \ No newline at end of file diff --git a/emarsys-huawei/src/androidTest/java/com/emarsys/service/EmarsysHuaweiMessagingServiceTest.kt b/emarsys-huawei/src/androidTest/java/com/emarsys/service/EmarsysHuaweiMessagingServiceTest.kt index 1e960d075..dc0aa049c 100644 --- a/emarsys-huawei/src/androidTest/java/com/emarsys/service/EmarsysHuaweiMessagingServiceTest.kt +++ b/emarsys-huawei/src/androidTest/java/com/emarsys/service/EmarsysHuaweiMessagingServiceTest.kt @@ -1,15 +1,19 @@ package com.emarsys.service import android.app.Application +import android.os.Handler import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.device.DeviceInfo -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.core.handler.SdkHandler import com.emarsys.fake.FakeHuaweiDependencyContainer import com.emarsys.mobileengage.di.setupMobileEngageComponent import com.emarsys.mobileengage.di.tearDownMobileEngageComponent import com.emarsys.mobileengage.push.PushInternal import com.emarsys.testUtil.FeatureTestUtils import com.emarsys.testUtil.InstrumentationRegistry +import com.emarsys.testUtil.ReflectionTestUtils import com.emarsys.testUtil.TimeoutUtils import org.junit.After import org.junit.Before @@ -17,8 +21,6 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.mockito.kotlin.* -import org.mockito.stubbing.Answer -import java.util.concurrent.CountDownLatch class EmarsysHuaweiMessagingServiceTest { @@ -31,21 +33,23 @@ class EmarsysHuaweiMessagingServiceTest { private lateinit var mockPushInternal: PushInternal private lateinit var fakeDependencyContainer: FakeHuaweiDependencyContainer - private lateinit var mockCoreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + private lateinit var concurrentHandlerHolderFactory: ConcurrentHandlerHolderFactory private lateinit var emarsysHuaweiMessagingService: EmarsysHuaweiMessagingService - val latch = CountDownLatch(1) - + private lateinit var spyCoreHandler: SdkHandler @Before fun setUp() { mockPushInternal = mock() - - mockCoreSdkHandler = mock { - on { post(any()) } doAnswer Answer { invocation -> - invocation.getArgument(0).run() - null - } - } + concurrentHandlerHolderFactory = + ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())) + concurrentHandlerHolder = concurrentHandlerHolderFactory.create() + spyCoreHandler = spy(concurrentHandlerHolder.coreHandler) + ReflectionTestUtils.setInstanceField( + concurrentHandlerHolder, + "coreHandler", + spyCoreHandler + ) Looper.prepare() emarsysHuaweiMessagingService = EmarsysHuaweiMessagingService() } @@ -70,7 +74,7 @@ class EmarsysHuaweiMessagingServiceTest { setupEmarsys(true) emarsysHuaweiMessagingService.onNewToken("testToken") - verify(mockCoreSdkHandler, timeout(1000).times(1)).post(any()) + verify(spyCoreHandler, timeout(1000).times(1)).post(any()) } @Test @@ -84,25 +88,25 @@ class EmarsysHuaweiMessagingServiceTest { private fun setupEmarsys(isAutomaticPushSending: Boolean) { val deviceInfo = DeviceInfo( - application, - mock { - on { provideHardwareId() } doReturn "hardwareId" - }, - mock { - on { provideSdkVersion() } doReturn "version" - }, - mock { - on { provideLanguage(any()) } doReturn "language" - }, - mock(), - isAutomaticPushSending, - false + application, + mock { + on { provideHardwareId() } doReturn "hardwareId" + }, + mock { + on { provideSdkVersion() } doReturn "version" + }, + mock { + on { provideLanguage(any()) } doReturn "language" + }, + mock(), + isAutomaticPushSending, + false ) fakeDependencyContainer = FakeHuaweiDependencyContainer( - coreSdkHandler = mockCoreSdkHandler, - deviceInfo = deviceInfo, - pushInternal = mockPushInternal + concurrentHandlerHolder = concurrentHandlerHolder, + deviceInfo = deviceInfo, + pushInternal = mockPushInternal ) setupMobileEngageComponent(fakeDependencyContainer) diff --git a/emarsys-huawei/src/main/java/com/emarsys/service/EmarsysHuaweiMessagingService.kt b/emarsys-huawei/src/main/java/com/emarsys/service/EmarsysHuaweiMessagingService.kt index 92b26e3a8..2341ecebf 100644 --- a/emarsys-huawei/src/main/java/com/emarsys/service/EmarsysHuaweiMessagingService.kt +++ b/emarsys-huawei/src/main/java/com/emarsys/service/EmarsysHuaweiMessagingService.kt @@ -7,7 +7,7 @@ import com.huawei.hms.push.RemoteMessage class EmarsysHuaweiMessagingService : HmsMessageService() { override fun onNewToken(token: String) { super.onNewToken(token) - mobileEngage().coreSdkHandler.post { + mobileEngage().concurrentHandlerHolder.coreHandler.post { if (mobileEngage().deviceInfo.isAutomaticPushSendingEnabled) { mobileEngage().pushInternal.setPushToken(token, null) } diff --git a/emarsys-huawei/src/main/java/com/emarsys/service/EmarsysHuaweiMessagingServiceUtils.kt b/emarsys-huawei/src/main/java/com/emarsys/service/EmarsysHuaweiMessagingServiceUtils.kt index a42a1c445..20215dae1 100644 --- a/emarsys-huawei/src/main/java/com/emarsys/service/EmarsysHuaweiMessagingServiceUtils.kt +++ b/emarsys-huawei/src/main/java/com/emarsys/service/EmarsysHuaweiMessagingServiceUtils.kt @@ -1,23 +1,24 @@ package com.emarsys.service import android.content.Context -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.mobileengage.di.mobileEngage import com.emarsys.mobileengage.service.MessagingServiceUtils import com.huawei.hms.push.RemoteMessage object EmarsysHuaweiMessagingServiceUtils { fun handleMessage(context: Context, remoteMessage: RemoteMessage): Boolean { - val handler: CoreSdkHandler = mobileEngage().coreSdkHandler + val handlerHolder: ConcurrentHandlerHolder = mobileEngage().concurrentHandlerHolder - handler.post { + handlerHolder.coreHandler.post { MessagingServiceUtils.handleMessage( - context, - remoteMessage.dataOfMap, - mobileEngage().deviceInfo, - mobileEngage().fileDownloader, - mobileEngage().silentMessageActionCommandFactory, - mobileEngage().remoteMessageMapper) + context, + remoteMessage.dataOfMap, + mobileEngage().deviceInfo, + mobileEngage().fileDownloader, + mobileEngage().silentMessageActionCommandFactory, + mobileEngage().remoteMessageMapper + ) } return isMobileEngageMessage(remoteMessage.dataOfMap) diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt index b6cfab7f8..04f16f3f6 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/EmarsysTest.kt @@ -924,7 +924,7 @@ class EmarsysTest { private fun runBlockingOnCoreSdkThread(callback: (() -> Unit)? = null) { val latch = CountDownLatch(1) var exception: Exception? = null - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { try { callback?.invoke() } catch (e: Exception) { diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/InAppTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/InAppTest.kt index 53ae296d0..f6e5b59be 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/InAppTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/InAppTest.kt @@ -48,7 +48,7 @@ class InAppTest { mobileEngage().activityLifecycleWatchdog) application.unregisterActivityLifecycleCallbacks(emarsys().currentActivityWatchdog) try { - val looper: Looper = emarsys().coreSdkHandler.looper + val looper: Looper = emarsys().concurrentHandlerHolder.looper looper.quitSafely() tearDownEmarsysComponent() } catch (e: Exception) { diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/InappNotificationIntegrationTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/InappNotificationIntegrationTest.kt index eb6b6378b..6fb9ae149 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/InappNotificationIntegrationTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/InappNotificationIntegrationTest.kt @@ -9,8 +9,6 @@ import com.emarsys.config.EmarsysConfig import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog import com.emarsys.core.app.AppLifecycleObserver -import com.emarsys.core.concurrency.CoreSdkHandlerProvider -import com.emarsys.core.handler.CoreSdkHandler import com.emarsys.core.util.FileDownloader import com.emarsys.di.DefaultEmarsysComponent import com.emarsys.di.DefaultEmarsysDependencies @@ -20,7 +18,6 @@ import com.emarsys.mobileengage.service.IntentUtils import com.emarsys.testUtil.* import com.emarsys.testUtil.fake.FakeActivity import com.emarsys.testUtil.rules.DuplicatedThreadRule -import kotlinx.coroutines.android.asCoroutineDispatcher import org.junit.After import org.junit.Before import org.junit.Rule @@ -38,7 +35,6 @@ class InappNotificationIntegrationTest { private lateinit var completionListenerLatch: CountDownLatch private lateinit var baseConfig: EmarsysConfig private lateinit var mockInappPresenterOverlay: OverlayInAppPresenter - private var coreSdkHandler: CoreSdkHandler = CoreSdkHandlerProvider().provideHandler() private val application: Application get() = InstrumentationRegistry.getTargetContext().applicationContext as Application diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/MobileEngageIntegrationTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/MobileEngageIntegrationTest.kt index cccb70eca..075097b1e 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/MobileEngageIntegrationTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/MobileEngageIntegrationTest.kt @@ -244,7 +244,7 @@ class MobileEngageIntegrationTest { @Test fun testConfig_changeApplicationCode_nilToSomething() { val setupLatch = CountDownLatch(1) - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { setupLatch.countDown() } setupLatch.await() diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/MobileEngageRefreshContactTokenIntegrationTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/MobileEngageRefreshContactTokenIntegrationTest.kt index f79fada07..eb7119276 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/MobileEngageRefreshContactTokenIntegrationTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/MobileEngageRefreshContactTokenIntegrationTest.kt @@ -100,7 +100,7 @@ class MobileEngageRefreshContactTokenIntegrationTest { Emarsys.setup(baseConfig) - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { contactTokenStorage = emarsys().contactTokenStorage contactTokenStorage.remove() emarsys().pushTokenStorage.remove() diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/PredictIntegrationTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/PredictIntegrationTest.kt index 18fd917a2..41eeac992 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/PredictIntegrationTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/PredictIntegrationTest.kt @@ -139,7 +139,7 @@ class PredictIntegrationTest { get() = deviceInfo }) - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { emarsys().clientStateStorage.remove() emarsys().contactFieldValueStorage.remove() emarsys().contactTokenStorage.remove() @@ -148,7 +148,7 @@ class PredictIntegrationTest { Emarsys.setup(baseConfig) - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { emarsys().clientServiceStorage.remove() emarsys().eventServiceStorage.remove() emarsys().deepLinkServiceStorage.remove() @@ -464,7 +464,7 @@ class PredictIntegrationTest { @Test fun testMultipleInvocationsWithSetContact() { - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { clientStateStorage = emarsys().clientStateStorage clientStateStorage.set("predict-integration-test") } diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/PredictTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/PredictTest.kt index c83c3e6b2..1ac72ec9b 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/PredictTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/PredictTest.kt @@ -65,7 +65,7 @@ class PredictTest { @After fun tearDown() { try { - val handler = emarsys().coreSdkHandler + val handler = emarsys().concurrentHandlerHolder val looper: Looper = handler.looper looper.quit() tearDownEmarsysComponent() diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/RemoteConfigIntegrationTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/RemoteConfigIntegrationTest.kt index 651595f41..1a14399aa 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/RemoteConfigIntegrationTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/RemoteConfigIntegrationTest.kt @@ -59,14 +59,15 @@ class RemoteConfigIntegrationTest { @Test fun testRemoteConfig() { - val coreSdkHandler = emarsys().coreSdkHandler - coreSdkHandler.post { - coreSdkHandler.post { + val coreSdkHandler = emarsys().concurrentHandlerHolder + coreSdkHandler.coreHandler.post { + coreSdkHandler.coreHandler.post { emarsys().configInternal.refreshRemoteConfig { latch.countDown() } } } latch.await() - val clientServiceEndpointHost = emarsys().clientServiceEndpointProvider.provideEndpointHost() + val clientServiceEndpointHost = + emarsys().clientServiceEndpointProvider.provideEndpointHost() val eventServiceEndpointHost = emarsys().eventServiceEndpointProvider.provideEndpointHost() clientServiceEndpointHost shouldBe "https://me-client-staging.eservice.emarsys.com" eventServiceEndpointHost shouldBe "https://mobile-events-staging.eservice.emarsys.com" 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 8f5170069..6992c8b9b 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/di/FakeDependencyContainer.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/di/FakeDependencyContainer.kt @@ -12,7 +12,7 @@ import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog import com.emarsys.core.activity.CurrentActivityWatchdog import com.emarsys.core.app.AppLifecycleObserver -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.crypto.Crypto import com.emarsys.core.database.CoreSQLiteDatabase @@ -21,7 +21,7 @@ import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.device.DeviceInfo import com.emarsys.core.endpoint.ServiceEndpointProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import com.emarsys.core.provider.hardwareid.HardwareIdProvider import com.emarsys.core.provider.timestamp.TimestampProvider @@ -79,130 +79,129 @@ import com.emarsys.predict.provider.PredictRequestModelBuilderProvider import com.emarsys.predict.request.PredictRequestContext import com.emarsys.push.PushApi import com.google.android.gms.location.FusedLocationProviderClient -import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock class FakeDependencyContainer( - override val coreSdkHandler: CoreSdkHandler = CoreSdkHandlerProvider().provideHandler(), - override val uiHandler: Handler = Handler(Looper.getMainLooper()), - override val messageInbox: MessageInboxApi = mock(), - override val loggingMessageInbox: MessageInboxApi = mock(), - override val deepLink: DeepLinkApi = mock(), - override val loggingDeepLink: DeepLinkApi = mock(), - override val inApp: InAppApi = mock(), - override val loggingInApp: InAppApi = mock(), - override val onEventAction: OnEventActionApi = mock(), - override val loggingOnEventAction: OnEventActionApi = mock(), - override val push: PushApi = mock(), - override val loggingPush: PushApi = mock(), - override val predict: PredictApi = mock(), - override val loggingPredict: PredictApi = mock(), - override val predictRestricted: PredictRestrictedApi = mock(), - override val loggingPredictRestricted: PredictRestrictedApi = mock(), - override val config: ConfigApi = mock(), - override val geofence: GeofenceApi = mock(), - override val loggingGeofence: GeofenceApi = mock(), - override val mobileEngage: MobileEngageApi = mock(), - override val loggingMobileEngage: MobileEngageApi = mock(), - override val configInternal: ConfigInternal = mock(), - override val clientService: ClientServiceApi = mock(), - override val loggingClientService: ClientServiceApi = mock(), - override val eventService: EventServiceApi = mock(), - override val loggingEventService: EventServiceApi = mock(), - override val mobileEngageInternal: MobileEngageInternal = mock(), - override val loggingMobileEngageInternal: MobileEngageInternal = mock(), - override val clientServiceInternal: ClientServiceInternal = mock(), - override val loggingClientServiceInternal: ClientServiceInternal = mock(), - override val messageInboxInternal: MessageInboxInternal = mock(), - override val loggingMessageInboxInternal: MessageInboxInternal = mock(), - override val inAppInternal: InAppInternal = mock(), - override val loggingInAppInternal: InAppInternal = mock(), - override val deepLinkInternal: DeepLinkInternal = mock(), - override val loggingDeepLinkInternal: DeepLinkInternal = mock(), - override val pushInternal: PushInternal = mock(), - override val loggingPushInternal: PushInternal = mock(), - override val eventServiceInternal: EventServiceInternal = mock(), - override val loggingEventServiceInternal: EventServiceInternal = mock(), - override val refreshTokenInternal: RefreshTokenInternal = mock(), - override val inAppEventHandlerInternal: InAppEventHandlerInternal = mock(), - override val requestContext: MobileEngageRequestContext = mock(), - override val overlayInAppPresenter: OverlayInAppPresenter = mock(), - override val deviceInfoPayloadStorage: Storage = mock(), - override val contactFieldValueStorage: Storage = mock(), - override val contactTokenStorage: Storage = mock(), - override val clientStateStorage: Storage = mock(), - override val pushTokenStorage: Storage = mock(), - override val refreshTokenStorage: Storage = mock(), - override val clientServiceStorage: Storage = mock(), - override val eventServiceStorage: Storage = mock(), - override val deepLinkServiceStorage: Storage = mock(), - override val messageInboxServiceStorage: Storage = mock(), - override val deviceEventStateStorage: Storage = mock(), - override val responseHandlersProcessor: ResponseHandlersProcessor = mock(), - override val pushTokenProvider: PushTokenProvider = mock(), - override val clientServiceEndpointProvider: ServiceEndpointProvider = mock(), - override val eventServiceEndpointProvider: ServiceEndpointProvider = mock(), - override val deepLinkServiceProvider: ServiceEndpointProvider = mock(), - override val messageInboxServiceProvider: ServiceEndpointProvider = mock(), - override val notificationInformationListenerProvider: NotificationInformationListenerProvider = mock(), - override val silentNotificationInformationListenerProvider: SilentNotificationInformationListenerProvider = mock(), - override val notificationActionCommandFactory: ActionCommandFactory = mock(), - override val silentMessageActionCommandFactory: ActionCommandFactory = mock(), - override val notificationCacheableEventHandler: CacheableEventHandler = mock(), - override val silentMessageCacheableEventHandler: CacheableEventHandler = mock(), - override val onEventActionCacheableEventHandler: CacheableEventHandler = mock(), - override val geofenceCacheableEventHandler: CacheableEventHandler = mock(), - override val currentActivityProvider: CurrentActivityProvider = mock(), - override val geofenceInternal: GeofenceInternal = mock(), - override val loggingGeofenceInternal: GeofenceInternal = mock(), - override val buttonClickedRepository: Repository = mock(), - override val displayedIamRepository: Repository = mock(), - override val contactTokenResponseHandler: MobileEngageTokenResponseHandler = mock(), - override val webViewProvider: WebViewProvider = mock(), - override val inlineInAppWebViewFactory: InlineInAppWebViewFactory = mock(), - override val iamJsBridgeFactory: IamJsBridgeFactory = mock(), - override val remoteMessageMapper: RemoteMessageMapper = mock(), - override val appLifecycleObserver: AppLifecycleObserver = mock(), - override val requestModelHelper: RequestModelHelper = mock(), - override val sessionIdHolder: SessionIdHolder = mock(), - override val coreCompletionHandlerRefreshTokenProxyProvider: CoreCompletionHandlerRefreshTokenProxyProvider = mock(), - override val mobileEngageRequestModelFactory: MobileEngageRequestModelFactory = mock(), - override val mobileEngageSession: MobileEngageSession = mock(), - override val activityLifecycleWatchdog: ActivityLifecycleWatchdog = mock(), - override val currentActivityWatchdog: CurrentActivityWatchdog = mock(), - override val coreSQLiteDatabase: CoreSQLiteDatabase = mock(), - override val deviceInfo: DeviceInfo = mock(), - override val shardRepository: Repository = mock(), - override val timestampProvider: TimestampProvider = mock(), - override val uuidProvider: UUIDProvider = mock(), - override val logShardTrigger: Runnable = mock(), - override val logger: Logger = mock(), - override val restClient: RestClient = mock(), - override val fileDownloader: FileDownloader = mock(), - override val keyValueStore: KeyValueStore = mock(), - override val sharedPreferences: SharedPreferences = mock(), - override val hardwareIdProvider: HardwareIdProvider = mock(), - override val coreDbHelper: CoreDbHelper = mock(), - override val hardwareIdStorage: Storage = mock(), - override val logLevelStorage: Storage = mock(), - override val crypto: Crypto = mock(), - override val requestManager: RequestManager = mock(), - override val worker: Worker = mock(), - override val requestModelRepository: Repository = mock(), - override val connectionWatchdog: ConnectionWatchDog = mock(), - override val coreCompletionHandler: CoreCompletionHandler = mock(), - override val predictInternal: PredictInternal = mock(), - override val loggingPredictInternal: PredictInternal = mock(), - override val predictShardTrigger: Runnable = mock(), - override val predictServiceProvider: ServiceEndpointProvider = mock(), - override val predictServiceStorage: Storage = mock(), - override val predictRequestContext: PredictRequestContext = mock(), - override val predictRequestModelBuilderProvider: PredictRequestModelBuilderProvider = mock(), - override val geofenceInitialEnterTriggerEnabledStorage: Storage = mock(), - override val isGooglePlayServiceAvailable: Boolean = true, - override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), - override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), - override val notificationOpenedActivityClass: Class<*> = Activity::class.java, - override val coreSdkScope: CoroutineScope = mock(), - override val uiScope: CoroutineScope = mock() + override val uiHandler: Handler = Handler(Looper.getMainLooper()), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( + uiHandler + ).create(), + override val messageInbox: MessageInboxApi = mock(), + override val loggingMessageInbox: MessageInboxApi = mock(), + override val deepLink: DeepLinkApi = mock(), + override val loggingDeepLink: DeepLinkApi = mock(), + override val inApp: InAppApi = mock(), + override val loggingInApp: InAppApi = mock(), + override val onEventAction: OnEventActionApi = mock(), + override val loggingOnEventAction: OnEventActionApi = mock(), + override val push: PushApi = mock(), + override val loggingPush: PushApi = mock(), + override val predict: PredictApi = mock(), + override val loggingPredict: PredictApi = mock(), + override val predictRestricted: PredictRestrictedApi = mock(), + override val loggingPredictRestricted: PredictRestrictedApi = mock(), + override val config: ConfigApi = mock(), + override val geofence: GeofenceApi = mock(), + override val loggingGeofence: GeofenceApi = mock(), + override val mobileEngage: MobileEngageApi = mock(), + override val loggingMobileEngage: MobileEngageApi = mock(), + override val configInternal: ConfigInternal = mock(), + override val clientService: ClientServiceApi = mock(), + override val loggingClientService: ClientServiceApi = mock(), + override val eventService: EventServiceApi = mock(), + override val loggingEventService: EventServiceApi = mock(), + override val mobileEngageInternal: MobileEngageInternal = mock(), + override val loggingMobileEngageInternal: MobileEngageInternal = mock(), + override val clientServiceInternal: ClientServiceInternal = mock(), + override val loggingClientServiceInternal: ClientServiceInternal = mock(), + override val messageInboxInternal: MessageInboxInternal = mock(), + override val loggingMessageInboxInternal: MessageInboxInternal = mock(), + override val inAppInternal: InAppInternal = mock(), + override val loggingInAppInternal: InAppInternal = mock(), + override val deepLinkInternal: DeepLinkInternal = mock(), + override val loggingDeepLinkInternal: DeepLinkInternal = mock(), + override val pushInternal: PushInternal = mock(), + override val loggingPushInternal: PushInternal = mock(), + override val eventServiceInternal: EventServiceInternal = mock(), + override val loggingEventServiceInternal: EventServiceInternal = mock(), + override val refreshTokenInternal: RefreshTokenInternal = mock(), + override val inAppEventHandlerInternal: InAppEventHandlerInternal = mock(), + override val requestContext: MobileEngageRequestContext = mock(), + override val overlayInAppPresenter: OverlayInAppPresenter = mock(), + override val deviceInfoPayloadStorage: Storage = mock(), + override val contactFieldValueStorage: Storage = mock(), + override val contactTokenStorage: Storage = mock(), + override val clientStateStorage: Storage = mock(), + override val pushTokenStorage: Storage = mock(), + override val refreshTokenStorage: Storage = mock(), + override val clientServiceStorage: Storage = mock(), + override val eventServiceStorage: Storage = mock(), + override val deepLinkServiceStorage: Storage = mock(), + override val messageInboxServiceStorage: Storage = mock(), + override val deviceEventStateStorage: Storage = mock(), + override val responseHandlersProcessor: ResponseHandlersProcessor = mock(), + override val pushTokenProvider: PushTokenProvider = mock(), + override val clientServiceEndpointProvider: ServiceEndpointProvider = mock(), + override val eventServiceEndpointProvider: ServiceEndpointProvider = mock(), + override val deepLinkServiceProvider: ServiceEndpointProvider = mock(), + override val messageInboxServiceProvider: ServiceEndpointProvider = mock(), + override val notificationInformationListenerProvider: NotificationInformationListenerProvider = mock(), + override val silentNotificationInformationListenerProvider: SilentNotificationInformationListenerProvider = mock(), + override val notificationActionCommandFactory: ActionCommandFactory = mock(), + override val silentMessageActionCommandFactory: ActionCommandFactory = mock(), + override val notificationCacheableEventHandler: CacheableEventHandler = mock(), + override val silentMessageCacheableEventHandler: CacheableEventHandler = mock(), + override val onEventActionCacheableEventHandler: CacheableEventHandler = mock(), + override val geofenceCacheableEventHandler: CacheableEventHandler = mock(), + override val currentActivityProvider: CurrentActivityProvider = mock(), + override val geofenceInternal: GeofenceInternal = mock(), + override val loggingGeofenceInternal: GeofenceInternal = mock(), + override val buttonClickedRepository: Repository = mock(), + override val displayedIamRepository: Repository = mock(), + override val contactTokenResponseHandler: MobileEngageTokenResponseHandler = mock(), + override val webViewProvider: WebViewProvider = mock(), + override val inlineInAppWebViewFactory: InlineInAppWebViewFactory = mock(), + override val iamJsBridgeFactory: IamJsBridgeFactory = mock(), + override val remoteMessageMapper: RemoteMessageMapper = mock(), + override val appLifecycleObserver: AppLifecycleObserver = mock(), + override val requestModelHelper: RequestModelHelper = mock(), + override val sessionIdHolder: SessionIdHolder = mock(), + override val coreCompletionHandlerRefreshTokenProxyProvider: CoreCompletionHandlerRefreshTokenProxyProvider = mock(), + override val mobileEngageRequestModelFactory: MobileEngageRequestModelFactory = mock(), + override val mobileEngageSession: MobileEngageSession = mock(), + override val activityLifecycleWatchdog: ActivityLifecycleWatchdog = mock(), + override val currentActivityWatchdog: CurrentActivityWatchdog = mock(), + override val coreSQLiteDatabase: CoreSQLiteDatabase = mock(), + override val deviceInfo: DeviceInfo = mock(), + override val shardRepository: Repository = mock(), + override val timestampProvider: TimestampProvider = mock(), + override val uuidProvider: UUIDProvider = mock(), + override val logShardTrigger: Runnable = mock(), + override val logger: Logger = mock(), + override val restClient: RestClient = mock(), + override val fileDownloader: FileDownloader = mock(), + override val keyValueStore: KeyValueStore = mock(), + override val sharedPreferences: SharedPreferences = mock(), + override val hardwareIdProvider: HardwareIdProvider = mock(), + override val coreDbHelper: CoreDbHelper = mock(), + override val hardwareIdStorage: Storage = mock(), + override val logLevelStorage: Storage = mock(), + override val crypto: Crypto = mock(), + override val requestManager: RequestManager = mock(), + override val worker: Worker = mock(), + override val requestModelRepository: Repository = mock(), + override val connectionWatchdog: ConnectionWatchDog = mock(), + override val coreCompletionHandler: CoreCompletionHandler = mock(), + override val predictInternal: PredictInternal = mock(), + override val loggingPredictInternal: PredictInternal = mock(), + override val predictShardTrigger: Runnable = mock(), + override val predictServiceProvider: ServiceEndpointProvider = mock(), + override val predictServiceStorage: Storage = mock(), + override val predictRequestContext: PredictRequestContext = mock(), + override val predictRequestModelBuilderProvider: PredictRequestModelBuilderProvider = mock(), + override val geofenceInitialEnterTriggerEnabledStorage: Storage = mock(), + override val isGooglePlayServiceAvailable: Boolean = true, + override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), + override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), + override val notificationOpenedActivityClass: Class<*> = Activity::class.java ) : EmarsysComponent \ No newline at end of file diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/fake/FakeRestClient.java b/emarsys-sdk/src/androidTest/java/com/emarsys/fake/FakeRestClient.java index 3b59a7259..401436b97 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/fake/FakeRestClient.java +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/fake/FakeRestClient.java @@ -6,7 +6,7 @@ import android.os.Looper; import com.emarsys.core.CoreCompletionHandler; -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; import com.emarsys.core.connection.ConnectionProvider; import com.emarsys.core.provider.timestamp.TimestampProvider; import com.emarsys.core.request.RestClient; @@ -34,7 +34,7 @@ public FakeRestClient(ResponseModel returnValue, Mode mode) { @SuppressWarnings("unchecked") public FakeRestClient(List responses, Mode mode) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); + new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); this.responses = new ArrayList<>(responses); this.mode = mode; } @@ -51,7 +51,7 @@ public FakeRestClient(Exception exception) { @SuppressWarnings("unchecked") public FakeRestClient(List exceptions) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); + new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); this.exceptions = new ArrayList<>(exceptions); this.mode = Mode.ERROR_EXCEPTION; } diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/inapp/ui/InlineInAppViewTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/inapp/ui/InlineInAppViewTest.kt index 907f0739e..1720736d5 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/inapp/ui/InlineInAppViewTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/inapp/ui/InlineInAppViewTest.kt @@ -1,12 +1,14 @@ package com.emarsys.inapp.ui import android.content.Context +import android.os.Handler +import android.os.Looper import android.view.ViewGroup import android.webkit.WebView import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.api.ResponseErrorException import com.emarsys.core.api.result.CompletionListener -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.request.RequestManager @@ -59,6 +61,7 @@ class InlineInAppViewTest { private lateinit var mockButtonClickedRepository: Repository private lateinit var mockInAppInternal: InAppInternal private lateinit var mockScopeDelegatorCompletionHandlerProvider: ScopeDelegatorCompletionHandlerProvider + private lateinit var uiHandler: Handler @Rule @JvmField @@ -67,6 +70,7 @@ class InlineInAppViewTest { @Before fun setUp() { var onMessageLoadedListener: MessageLoadedListener? = null + uiHandler = Handler(Looper.getMainLooper()) context = InstrumentationRegistry.getTargetContext() webView = mock { on { layoutParams } doReturn ViewGroup.LayoutParams(10, 10) @@ -115,14 +119,14 @@ class InlineInAppViewTest { mockRequestManager = spy( RequestManager( - CoreSdkHandlerProvider().provideHandler(), + ConcurrentHandlerHolderFactory(uiHandler).create(), mock(), mock(), mock(), FakeRestClient( mockResponseModel, FakeRestClient.Mode.SUCCESS, - CoreSdkHandlerProvider().provideHandler().handler + ConcurrentHandlerHolderFactory(uiHandler).create().coreHandler.handler ), mock(), mock(), @@ -293,7 +297,7 @@ class InlineInAppViewTest { whenever(mockResponseModel.statusCode).thenReturn(expectedStatusCode) mockRequestManager = spy( RequestManager( - CoreSdkHandlerProvider().provideHandler(), + ConcurrentHandlerHolderFactory(uiHandler).create(), mock(), mock(), mock(), @@ -342,7 +346,7 @@ class InlineInAppViewTest { val expectedException = Exception("Error happened") mockRequestManager = spy( RequestManager( - CoreSdkHandlerProvider().provideHandler(), + ConcurrentHandlerHolderFactory(uiHandler).create(), mock(), mock(), mock(), 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 630b01949..33be7b1fd 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/predict/PredictRestrictedTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/predict/PredictRestrictedTest.kt @@ -39,7 +39,7 @@ class PredictRestrictedTest { @After fun tearDown() { try { - emarsys().coreSdkHandler.looper.quit() + emarsys().concurrentHandlerHolder.looper.quit() tearDownEmarsysComponent() } catch (e: Exception) { e.printStackTrace() diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/testUtil/IntegrationTestUtils.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/testUtil/IntegrationTestUtils.kt index c847e145f..eb5fddd28 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/testUtil/IntegrationTestUtils.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/testUtil/IntegrationTestUtils.kt @@ -37,7 +37,7 @@ object IntegrationTestUtils { @Synchronized fun tearDownEmarsys(application: Application? = null) { var latch = CountDownLatch(1) - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { if (application != null) { application.unregisterActivityLifecycleCallbacks(emarsys().activityLifecycleWatchdog) application.unregisterActivityLifecycleCallbacks(emarsys().currentActivityWatchdog) @@ -57,7 +57,7 @@ object IntegrationTestUtils { emarsys().predictServiceStorage.remove() latch.countDown() } - emarsys().coreSdkHandler.looper.quitSafely() + emarsys().concurrentHandlerHolder.looper.quitSafely() latch.await() latch = CountDownLatch(1) diff --git a/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt b/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt index 0c4e2ad60..a6d369516 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt @@ -86,7 +86,7 @@ object Emarsys { } } - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { registerWatchDogs(emarsysConfig) registerDatabaseTriggers() @@ -115,7 +115,7 @@ object Emarsys { && !FeatureRegistry.isFeatureEnabled(PREDICT) ) { EmarsysDependencyInjection.mobileEngageApi() - .proxyApi(mobileEngage().coreSdkHandler) + .proxyApi(mobileEngage().concurrentHandlerHolder) .setAuthenticatedContact(contactFieldId, openIdToken, completionListener) } @@ -134,12 +134,12 @@ object Emarsys { && !FeatureRegistry.isFeatureEnabled(PREDICT) ) { EmarsysDependencyInjection.mobileEngageApi() - .proxyApi(mobileEngage().coreSdkHandler) + .proxyApi(mobileEngage().concurrentHandlerHolder) .setContact(contactFieldId, contactFieldValue, completionListener) } if (FeatureRegistry.isFeatureEnabled(PREDICT)) { EmarsysDependencyInjection.predictRestrictedApi() - .proxyApi(mobileEngage().coreSdkHandler) + .proxyApi(mobileEngage().concurrentHandlerHolder) .setContact(contactFieldId, contactFieldValue) } } @@ -152,12 +152,12 @@ object Emarsys { && !FeatureRegistry.isFeatureEnabled(PREDICT) ) { EmarsysDependencyInjection.mobileEngageApi() - .proxyApi(mobileEngage().coreSdkHandler) + .proxyApi(mobileEngage().concurrentHandlerHolder) .clearContact(completionListener) } if (FeatureRegistry.isFeatureEnabled(PREDICT)) { EmarsysDependencyInjection.predictRestrictedApi() - .proxyApi(mobileEngage().coreSdkHandler) + .proxyApi(mobileEngage().concurrentHandlerHolder) .clearContact() } } @@ -170,7 +170,7 @@ object Emarsys { completionListener: CompletionListener? = null ) { EmarsysDependencyInjection.deepLinkApi() - .proxyApi(mobileEngage().coreSdkHandler) + .proxyApi(mobileEngage().concurrentHandlerHolder) .trackDeepLinkOpen(activity, intent, completionListener) } @@ -182,7 +182,7 @@ object Emarsys { completionListener: CompletionListener? = null ) { EmarsysDependencyInjection.eventServiceApi() - .proxyApi(mobileEngage().coreSdkHandler) + .proxyApi(mobileEngage().concurrentHandlerHolder) .trackCustomEventAsync(eventName, eventAttributes, 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 ca883538b..934c04365 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysComponent.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysComponent.kt @@ -20,7 +20,7 @@ import com.emarsys.core.activity.CurrentActivityWatchdog import com.emarsys.core.api.notification.NotificationSettings import com.emarsys.core.api.proxyApi import com.emarsys.core.app.AppLifecycleObserver -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionProvider import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.contentresolver.hardwareid.HardwareIdContentResolver @@ -34,7 +34,7 @@ import com.emarsys.core.device.DeviceInfo import com.emarsys.core.device.HardwareRepository import com.emarsys.core.device.LanguageProvider import com.emarsys.core.endpoint.ServiceEndpointProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.notification.NotificationManagerHelper import com.emarsys.core.notification.NotificationManagerProxy import com.emarsys.core.permission.PermissionChecker @@ -85,7 +85,10 @@ import com.emarsys.mobileengage.deeplink.DefaultDeepLinkInternal import com.emarsys.mobileengage.deeplink.LoggingDeepLinkInternal import com.emarsys.mobileengage.device.DeviceInfoStartAction import com.emarsys.mobileengage.endpoint.Endpoint -import com.emarsys.mobileengage.event.* +import com.emarsys.mobileengage.event.CacheableEventHandler +import com.emarsys.mobileengage.event.DefaultEventServiceInternal +import com.emarsys.mobileengage.event.EventServiceInternal +import com.emarsys.mobileengage.event.LoggingEventServiceInternal import com.emarsys.mobileengage.geofence.* import com.emarsys.mobileengage.iam.* import com.emarsys.mobileengage.iam.dialog.IamDialogProvider @@ -129,10 +132,6 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailabilityLight import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.GeofencingClient -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.android.asCoroutineDispatcher import org.json.JSONObject import java.security.KeyFactory import java.security.PublicKey @@ -156,75 +155,74 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val notificationOpenedActivityClass: Class<*> get() = com.emarsys.NotificationOpenedActivity::class.java - final override val coreSdkHandler: CoreSdkHandler = CoreSdkHandlerProvider().provideHandler() - - private val coreSdkHandlerDispatcher = coreSdkHandler.handler.asCoroutineDispatcher() - - override val coreSdkScope: CoroutineScope = CoroutineScope(Job() + coreSdkHandlerDispatcher) - - override val uiScope: CoroutineScope = CoroutineScope(Job() + Dispatchers.Main) - override val uiHandler: Handler = Handler(config.application.mainLooper) - override val deepLink: DeepLinkApi = (DeepLink() as DeepLinkApi).proxyApi(coreSdkHandler) + final override val concurrentHandlerHolder: ConcurrentHandlerHolder = + ConcurrentHandlerHolderFactory(uiHandler).create() + + override val deepLink: DeepLinkApi = + (DeepLink() as DeepLinkApi).proxyApi(concurrentHandlerHolder) override val loggingDeepLink: DeepLinkApi = - (DeepLink(true) as DeepLinkApi).proxyApi(coreSdkHandler) + (DeepLink(true) as DeepLinkApi).proxyApi(concurrentHandlerHolder) override val messageInbox: MessageInboxApi = - (MessageInbox() as MessageInboxApi).proxyApi(coreSdkHandler) + (MessageInbox() as MessageInboxApi).proxyApi(concurrentHandlerHolder) override val loggingMessageInbox: MessageInboxApi = - (MessageInbox(true) as MessageInboxApi).proxyApi(coreSdkHandler) + (MessageInbox(true) as MessageInboxApi).proxyApi(concurrentHandlerHolder) - override val inApp: InAppApi = (InApp() as InAppApi).proxyApi(coreSdkHandler) + override val inApp: InAppApi = (InApp() as InAppApi).proxyApi(concurrentHandlerHolder) - override val loggingInApp: InAppApi = (InApp(true) as InAppApi).proxyApi(coreSdkHandler) + override val loggingInApp: InAppApi = + (InApp(true) as InAppApi).proxyApi(concurrentHandlerHolder) override val onEventAction: OnEventActionApi = - (OnEventAction() as OnEventActionApi).proxyApi(coreSdkHandler) + (OnEventAction() as OnEventActionApi).proxyApi(concurrentHandlerHolder) override val loggingOnEventAction: OnEventActionApi = - (OnEventAction() as OnEventActionApi).proxyApi(coreSdkHandler) + (OnEventAction() as OnEventActionApi).proxyApi(concurrentHandlerHolder) - override val push: PushApi = (Push() as PushApi).proxyApi(coreSdkHandler) + override val push: PushApi = (Push() as PushApi).proxyApi(concurrentHandlerHolder) - override val loggingPush: PushApi = (Push(true) as PushApi).proxyApi(coreSdkHandler) + override val loggingPush: PushApi = (Push(true) as PushApi).proxyApi(concurrentHandlerHolder) - override val predict: PredictApi = (Predict() as PredictApi).proxyApi(coreSdkHandler) + override val predict: PredictApi = (Predict() as PredictApi).proxyApi(concurrentHandlerHolder) - override val loggingPredict: PredictApi = (Predict(true) as PredictApi).proxyApi(coreSdkHandler) + override val loggingPredict: PredictApi = + (Predict(true) as PredictApi).proxyApi(concurrentHandlerHolder) - override val config: ConfigApi = (Config() as ConfigApi).proxyApi(coreSdkHandler) + override val config: ConfigApi = (Config() as ConfigApi).proxyApi(concurrentHandlerHolder) - override val geofence: GeofenceApi = (Geofence() as GeofenceApi).proxyApi(coreSdkHandler) + override val geofence: GeofenceApi = + (Geofence() as GeofenceApi).proxyApi(concurrentHandlerHolder) override val loggingGeofence: GeofenceApi = - (Geofence(true) as GeofenceApi).proxyApi(coreSdkHandler) + (Geofence(true) as GeofenceApi).proxyApi(concurrentHandlerHolder) override val mobileEngage: MobileEngageApi = - (MobileEngage() as MobileEngageApi).proxyApi(coreSdkHandler) + (MobileEngage() as MobileEngageApi).proxyApi(concurrentHandlerHolder) override val loggingMobileEngage: MobileEngageApi = - (MobileEngage(true) as MobileEngageApi).proxyApi(coreSdkHandler) + (MobileEngage(true) as MobileEngageApi).proxyApi(concurrentHandlerHolder) override val predictRestricted: PredictRestrictedApi = - (PredictRestricted() as PredictRestrictedApi).proxyApi(coreSdkHandler) + (PredictRestricted() as PredictRestrictedApi).proxyApi(concurrentHandlerHolder) override val loggingPredictRestricted: PredictRestrictedApi = - (PredictRestricted(true) as PredictRestrictedApi).proxyApi(coreSdkHandler) + (PredictRestricted(true) as PredictRestrictedApi).proxyApi(concurrentHandlerHolder) override val clientService: ClientServiceApi = - (ClientService() as ClientServiceApi).proxyApi(coreSdkHandler) + (ClientService() as ClientServiceApi).proxyApi(concurrentHandlerHolder) override val loggingClientService: ClientServiceApi = - (ClientService(true) as ClientServiceApi).proxyApi(coreSdkHandler) + (ClientService(true) as ClientServiceApi).proxyApi(concurrentHandlerHolder) override val eventService: EventServiceApi = - (EventService() as EventServiceApi).proxyApi(coreSdkHandler) + (EventService() as EventServiceApi).proxyApi(concurrentHandlerHolder) override val loggingEventService: EventServiceApi = - (EventService(true) as EventServiceApi).proxyApi(coreSdkHandler) + (EventService(true) as EventServiceApi).proxyApi(concurrentHandlerHolder) override val responseHandlersProcessor: ResponseHandlersProcessor by lazy { ResponseHandlersProcessor(mutableListOf()) @@ -232,7 +230,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val overlayInAppPresenter: OverlayInAppPresenter by lazy { OverlayInAppPresenter( - coreSdkHandler, + concurrentHandlerHolder, uiHandler, IamStaticWebViewProvider(config.application, uiHandler), inAppInternal, @@ -253,7 +251,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { FetchRemoteConfigAction(configInternal) { logInitialSetup(config) }, AppStartAction(eventServiceInternal, contactTokenStorage) ) - ActivityLifecycleActionRegistry(coreSdkHandler, currentActivityProvider, actions) + ActivityLifecycleActionRegistry(concurrentHandlerHolder, currentActivityProvider, actions) } override val activityLifecycleWatchdog: ActivityLifecycleWatchdog by lazy { @@ -305,7 +303,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { displayedIamRepository, eventServiceInternal, timestampProvider, - coreSdkHandler + concurrentHandlerHolder ) ) responseHandlers.add( @@ -323,8 +321,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { timestampProvider, responseHandlersProcessor, createRequestModelMappers(), - uiHandler, - coreSdkHandler + concurrentHandlerHolder ) } @@ -369,7 +366,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { } override val hardwareIdProvider: HardwareIdProvider by lazy { - val hardwareRepository = HardwareRepository(coreDbHelper) + val hardwareRepository = HardwareRepository(coreDbHelper, concurrentHandlerHolder) val hardwareIdentificationCrypto = HardwareIdentificationCrypto(config.sharedSecret, crypto) val hardwareIdContentResolver = HardwareIdContentResolver( config.application, @@ -442,15 +439,15 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { } override val shardRepository: Repository by lazy { - ShardModelRepository(coreDbHelper) + ShardModelRepository(coreDbHelper, concurrentHandlerHolder) } override val buttonClickedRepository: Repository by lazy { - ButtonClickedRepository(coreDbHelper) + ButtonClickedRepository(coreDbHelper, concurrentHandlerHolder) } override val displayedIamRepository: Repository by lazy { - DisplayedIamRepository(coreDbHelper) + DisplayedIamRepository(coreDbHelper, concurrentHandlerHolder) } override val requestModelRepository: Repository by lazy { @@ -458,7 +455,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { } override val connectionWatchdog: ConnectionWatchDog by lazy { - ConnectionWatchDog(config.application, coreSdkHandler) + ConnectionWatchDog(config.application, concurrentHandlerHolder) } override val coreCompletionHandler: DefaultCoreCompletionHandler by lazy { @@ -516,8 +513,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val coreCompletionHandlerRefreshTokenProxyProvider: CoreCompletionHandlerRefreshTokenProxyProvider by lazy { val coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider( requestModelRepository, - uiHandler, - coreSdkHandler + concurrentHandlerHolder ) CoreCompletionHandlerRefreshTokenProxyProvider( coreCompletionHandlerMiddlewareProvider, @@ -543,7 +539,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val requestManager: RequestManager by lazy { RequestManager( - coreSdkHandler, + concurrentHandlerHolder, requestModelRepository, shardRepository, worker, @@ -551,8 +547,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { coreCompletionHandler, coreCompletionHandler, coreCompletionHandlerRefreshTokenProxyProvider, - ScopeDelegatorCompletionHandlerProvider(), - coreSdkScope + ScopeDelegatorCompletionHandlerProvider() ) } @@ -624,7 +619,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val messageInboxInternal: MessageInboxInternal by lazy { DefaultMessageInboxInternal( - uiScope, + concurrentHandlerHolder, requestManager, mobileEngageRequestModelFactory, MessageInboxResponseMapper() @@ -753,7 +748,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { geofenceCacheableEventHandler, BooleanStorage(MobileEngageStorageKey.GEOFENCE_ENABLED, sharedPreferences), GeofencePendingIntentProvider(config.application), - coreSdkHandler, + concurrentHandlerHolder, uiHandler, geofenceInitialEnterTriggerEnabledStorage ) @@ -780,7 +775,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { } override val appLifecycleObserver: AppLifecycleObserver by lazy { - AppLifecycleObserver(mobileEngageSession, coreSdkHandler) + AppLifecycleObserver(mobileEngageSession, concurrentHandlerHolder) } override val keyValueStore: KeyValueStore by lazy { @@ -804,7 +799,6 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { requestContext, mobileEngageInternal, pushInternal, - pushTokenProvider, predictRequestContext, deviceInfo, requestManager, @@ -817,7 +811,8 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { messageInboxServiceStorage, logLevelStorage, crypto, - clientServiceInternal + clientServiceInternal, + concurrentHandlerHolder ) } @@ -847,7 +842,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val logger: Logger by lazy { Logger( - coreSdkHandler, + concurrentHandlerHolder, shardRepository, timestampProvider, uuidProvider, @@ -907,7 +902,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { coreDbHelper: CoreDbHelper, inAppEventHandler: InAppEventHandlerInternal ): Repository { - val requestModelRepository = RequestModelRepository(coreDbHelper) + val requestModelRepository = RequestModelRepository(coreDbHelper, concurrentHandlerHolder) return RequestRepositoryProxy( requestModelRepository, displayedIamRepository, diff --git a/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysDependencies.kt b/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysDependencies.kt index da18e0deb..74d79b0df 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysDependencies.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysDependencies.kt @@ -11,7 +11,7 @@ open class DefaultEmarsysDependencies(config: EmarsysConfig, init { setupEmarsysComponent(component) - emarsys().coreSdkHandler.post { + emarsys().concurrentHandlerHolder.coreHandler.post { component.initializeResponseHandlers(config) } } diff --git a/emarsys-sdk/src/main/java/com/emarsys/inapp/ui/InlineInAppView.kt b/emarsys-sdk/src/main/java/com/emarsys/inapp/ui/InlineInAppView.kt index ec172c5b2..c96a68382 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/inapp/ui/InlineInAppView.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/inapp/ui/InlineInAppView.kt @@ -71,7 +71,7 @@ class InlineInAppView : LinearLayout { } fun loadInApp(viewId: String) { - mobileEngage().coreSdkHandler.post { + mobileEngage().concurrentHandlerHolder.coreHandler.post { this.viewId = viewId if (webView == null) { onCompletionListener?.onCompleted(IllegalArgumentException("WebView can not be created, please try again later!")) @@ -159,7 +159,7 @@ class InlineInAppView : LinearLayout { val jsCommandFactory = JSCommandFactory( mobileEngage().currentActivityProvider, mobileEngage().uiHandler, - mobileEngage().coreSdkHandler, + mobileEngage().concurrentHandlerHolder, inAppInternal, buttonClickedRepository, onCloseListener, diff --git a/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt b/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt index ec81c2897..40fff8fc3 100644 --- a/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt +++ b/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt @@ -162,8 +162,8 @@ class DefaultConfigInternalTest { val mainScope = CoroutineScope(Job() + Dispatchers.Main) mockConcurrentHandlerHolder = mock { - on { coreScope } doReturn scope - on { uiScope } doReturn mainScope + on { it.sdkScope } doReturn scope + on { it.uiScope } doReturn mainScope } configInternal = spy(DefaultConfigInternal(mockMobileEngageRequestContext, diff --git a/emarsys/src/androidTest/java/com/emarsys/fake/FakeEmarsysDependencyContainer.kt b/emarsys/src/androidTest/java/com/emarsys/fake/FakeEmarsysDependencyContainer.kt index 4700c86c6..a3652699d 100644 --- a/emarsys/src/androidTest/java/com/emarsys/fake/FakeEmarsysDependencyContainer.kt +++ b/emarsys/src/androidTest/java/com/emarsys/fake/FakeEmarsysDependencyContainer.kt @@ -10,7 +10,7 @@ import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog import com.emarsys.core.activity.CurrentActivityWatchdog import com.emarsys.core.app.AppLifecycleObserver -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.crypto.Crypto import com.emarsys.core.database.CoreSQLiteDatabase @@ -19,13 +19,14 @@ import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.device.DeviceInfo import com.emarsys.core.endpoint.ServiceEndpointProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import com.emarsys.core.provider.hardwareid.HardwareIdProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient +import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.shard.ShardModel @@ -69,8 +70,10 @@ import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock class FakeEmarsysDependencyContainer( - override val coreSdkHandler: CoreSdkHandler = CoreSdkHandlerProvider().provideHandler(), override val uiHandler: Handler = Handler(Looper.getMainLooper()), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( + uiHandler + ).create(), override val mobileEngageInternal: MobileEngageInternal = mock(), override val loggingMobileEngageInternal: MobileEngageInternal = mock(), override val clientServiceInternal: ClientServiceInternal = mock(), @@ -158,5 +161,6 @@ class FakeEmarsysDependencyContainer( override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), override val notificationOpenedActivityClass: Class<*> = Activity::class.java, override val coreSdkScope: CoroutineScope = mock(), - override val uiScope: CoroutineScope = mock() + override val uiScope: CoroutineScope = mock(), + override val runnableFactory: RunnableFactory = mock() ) : MobileEngageComponent \ No newline at end of file diff --git a/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.java b/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.java index a67d0080f..195eca3ea 100644 --- a/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.java +++ b/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.java @@ -1,10 +1,12 @@ package com.emarsys.fake; +import static org.mockito.Mockito.mock; + import android.os.Handler; import android.os.Looper; import com.emarsys.core.CoreCompletionHandler; -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; import com.emarsys.core.connection.ConnectionProvider; import com.emarsys.core.provider.timestamp.TimestampProvider; import com.emarsys.core.request.RestClient; @@ -16,8 +18,6 @@ import java.util.Collections; import java.util.List; -import static org.mockito.Mockito.mock; - public class FakeRestClient extends RestClient { private Mode mode; @@ -33,7 +33,7 @@ public FakeRestClient(ResponseModel returnValue, Mode mode) { @SuppressWarnings("unchecked") public FakeRestClient(List responses, Mode mode) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), - mock(List.class), new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); + mock(List.class), new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); this.responses = new ArrayList<>(responses); this.mode = mode; } @@ -45,7 +45,7 @@ public FakeRestClient(Exception exception) { @SuppressWarnings("unchecked") public FakeRestClient(List exceptions) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), - mock(List.class), new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); + mock(List.class), new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); this.exceptions = new ArrayList<>(exceptions); this.mode = Mode.ERROR_EXCEPTION; } diff --git a/emarsys/src/main/java/com/emarsys/config/DefaultConfigInternal.kt b/emarsys/src/main/java/com/emarsys/config/DefaultConfigInternal.kt index 71e604d4f..4f3258882 100644 --- a/emarsys/src/main/java/com/emarsys/config/DefaultConfigInternal.kt +++ b/emarsys/src/main/java/com/emarsys/config/DefaultConfigInternal.kt @@ -74,7 +74,7 @@ class DefaultConfigInternal(private val mobileEngageRequestContext: MobileEngage val pushToken: String? = pushInternal.pushToken val hasContactIdentification = mobileEngageRequestContext.hasContactIdentification() var throwable: Throwable? = null - concurrentHandlerHolder.coreScope?.launch { //TODO: remove question mark + concurrentHandlerHolder.sdkScope.launch { if (pushToken != null) { throwable = clearPushToken() } @@ -96,7 +96,7 @@ class DefaultConfigInternal(private val mobileEngageRequestContext: MobileEngage if (throwable != null) { handleAppCodeChange(null) } - concurrentHandlerHolder.uiScope?.launch { //TODO: remove question mark + concurrentHandlerHolder.uiScope.launch { completionListener?.onCompleted(throwable) } } diff --git a/emarsys/src/main/java/com/emarsys/config/FetchRemoteConfigAction.kt b/emarsys/src/main/java/com/emarsys/config/FetchRemoteConfigAction.kt index 03ca35dd6..560204f8c 100644 --- a/emarsys/src/main/java/com/emarsys/config/FetchRemoteConfigAction.kt +++ b/emarsys/src/main/java/com/emarsys/config/FetchRemoteConfigAction.kt @@ -15,7 +15,7 @@ class FetchRemoteConfigAction(private val configInternal: ConfigInternal, ) : ActivityLifecycleAction { override fun execute(activity: Activity?) { - configInternal.proxyApi(mobileEngage().coreSdkHandler) + configInternal.proxyApi(mobileEngage().concurrentHandlerHolder) .refreshRemoteConfig(completionListener) } } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeMobileEngageDependencyContainer.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeMobileEngageDependencyContainer.kt index b6e8a9292..66a343c26 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeMobileEngageDependencyContainer.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeMobileEngageDependencyContainer.kt @@ -9,7 +9,7 @@ import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog import com.emarsys.core.activity.CurrentActivityWatchdog import com.emarsys.core.app.AppLifecycleObserver -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.crypto.Crypto import com.emarsys.core.database.CoreSQLiteDatabase @@ -18,13 +18,14 @@ import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.device.DeviceInfo import com.emarsys.core.endpoint.ServiceEndpointProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import com.emarsys.core.provider.hardwareid.HardwareIdProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient +import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.shard.ShardModel @@ -68,8 +69,10 @@ import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock class FakeMobileEngageDependencyContainer( - override val coreSdkHandler: CoreSdkHandler = CoreSdkHandlerProvider().provideHandler(), override val uiHandler: Handler = Handler(Looper.getMainLooper()), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( + uiHandler + ).create(), override val mobileEngageInternal: MobileEngageInternal = mock(), override val loggingMobileEngageInternal: MobileEngageInternal = mock(), override val clientServiceInternal: ClientServiceInternal = mock(), @@ -157,5 +160,6 @@ class FakeMobileEngageDependencyContainer( override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), override val notificationOpenedActivityClass: Class<*> = Activity::class.java, override val coreSdkScope: CoroutineScope = mock(), - override val uiScope: CoroutineScope = mock() + override val uiScope: CoroutineScope = mock(), + override val runnableFactory: RunnableFactory = mock() ) : MobileEngageComponent \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRequestManager.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRequestManager.kt index 66f481cb3..023aba432 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRequestManager.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRequestManager.kt @@ -6,9 +6,9 @@ import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.Mockable import com.emarsys.core.Registry import com.emarsys.core.api.result.CompletionListener +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler import com.emarsys.core.request.RequestManager import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseModel @@ -18,7 +18,7 @@ import org.mockito.kotlin.mock @Mockable class FakeRequestManager(private val responseType: ResponseType, private val response: ResponseModel) : RequestManager( - CoreSdkHandler(Handler(Looper.getMainLooper())), + ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create(), mock() as Repository, mock() as Repository, mock(), diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRestClient.java b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRestClient.java index f093672a1..f6fb8fd70 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRestClient.java +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRestClient.java @@ -6,8 +6,9 @@ import android.os.Looper; import com.emarsys.core.CoreCompletionHandler; -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; import com.emarsys.core.connection.ConnectionProvider; +import com.emarsys.core.handler.SdkHandler; import com.emarsys.core.provider.timestamp.TimestampProvider; import com.emarsys.core.request.RestClient; import com.emarsys.core.request.model.RequestModel; @@ -23,7 +24,7 @@ public class FakeRestClient extends RestClient { private Mode mode; private List responses; private List exceptions; - private Handler handler; + private SdkHandler handler; public enum Mode {SUCCESS, ERROR_RESPONSE_MODEL, ERROR_EXCEPTION} @@ -34,7 +35,7 @@ public FakeRestClient(ResponseModel returnValue, Mode mode) { @SuppressWarnings("unchecked") public FakeRestClient(List responses, Mode mode) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); + new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); this.responses = new ArrayList<>(responses); this.mode = mode; } @@ -43,7 +44,7 @@ public FakeRestClient(Exception exception) { this(Collections.singletonList(exception)); } - public FakeRestClient(Exception exception, Handler handler) { + public FakeRestClient(Exception exception, SdkHandler handler) { this(Collections.singletonList(exception)); this.handler = handler; } @@ -51,19 +52,19 @@ public FakeRestClient(Exception exception, Handler handler) { @SuppressWarnings("unchecked") public FakeRestClient(List exceptions) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); + new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); this.exceptions = new ArrayList<>(exceptions); this.mode = Mode.ERROR_EXCEPTION; } - public FakeRestClient(ResponseModel returnValue, Mode mode, Handler handler){ + public FakeRestClient(ResponseModel returnValue, Mode mode, SdkHandler handler) { this(returnValue, mode); this.handler = handler; } @Override public void execute(final RequestModel model, final CoreCompletionHandler completionHandler) { - handler = handler == null ? new Handler(Looper.getMainLooper()) : handler; + handler = handler == null ? new SdkHandler(new Handler(Looper.getMainLooper())) : handler; handler.postDelayed(() -> { if (mode == Mode.SUCCESS) { completionHandler.onSuccess(model.getId(), getCurrentItem(responses)); diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternalTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternalTest.kt index f430db247..64de525e2 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternalTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternalTest.kt @@ -11,7 +11,7 @@ import android.os.Build import android.os.Handler import android.os.Looper import com.emarsys.core.api.MissingPermissionException -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.permission.PermissionChecker import com.emarsys.core.request.RequestManager import com.emarsys.core.response.ResponseModel @@ -91,12 +91,12 @@ class DefaultGeofenceInternalTest { private lateinit var mockEnabledStorage: Storage private lateinit var mockInitialEnterTriggerEnabledStorage: Storage private lateinit var mockPendingIntentProvider: GeofencePendingIntentProvider - private lateinit var mockHandler: CoreSdkHandler + private lateinit var mockHandlerHolder: ConcurrentHandlerHolder private lateinit var uiHandler: Handler @Before fun setUp() { - mockHandler = mock() + mockHandlerHolder = mock() mockInitialEnterTriggerEnabledStorage = mock { on { get() } doReturn false } @@ -146,7 +146,7 @@ class DefaultGeofenceInternalTest { mockCacheableEventHandler, mockEnabledStorage, mockPendingIntentProvider, - mockHandler, + mockHandlerHolder, uiHandler, mockInitialEnterTriggerEnabledStorage ) @@ -164,7 +164,7 @@ class DefaultGeofenceInternalTest { mockCacheableEventHandler, mockEnabledStorage, mockPendingIntentProvider, - mockHandler, + mockHandlerHolder, uiHandler, mockInitialEnterTriggerEnabledStorage ) diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/OverlayInAppPresenterTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/OverlayInAppPresenterTest.kt index bdedc7d1e..1be73d363 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/OverlayInAppPresenterTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/OverlayInAppPresenterTest.kt @@ -9,7 +9,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager import androidx.test.rule.ActivityTestRule -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.provider.activity.CurrentActivityProvider @@ -83,20 +83,22 @@ class OverlayInAppPresenterTest { on { createJsBridge(anyOrNull(), anyOrNull()) } doReturn mockJsBridge } - val coreSdkHandler = CoreSdkHandlerProvider().provideHandler() uiHandler = Handler(Looper.getMainLooper()) - - - overlayPresenter = OverlayInAppPresenter(coreSdkHandler, - uiHandler, - iamStaticWebViewProvider, - mockInAppInternal, - mockIamDialogProvider, - mockButtonClickedRepository, - mockDisplayedIamRepository, - mockTimestampProvider, - mockActivityProvider, - mockIamJsBridgeFactory) + val concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + + + overlayPresenter = OverlayInAppPresenter( + concurrentHandlerHolder, + uiHandler, + iamStaticWebViewProvider, + mockInAppInternal, + mockIamDialogProvider, + mockButtonClickedRepository, + mockDisplayedIamRepository, + mockTimestampProvider, + mockActivityProvider, + mockIamJsBridgeFactory + ) } @Test diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.java b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.java deleted file mode 100644 index 0c490e7d9..000000000 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.emarsys.mobileengage.iam; - -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; -import com.emarsys.core.database.repository.Repository; -import com.emarsys.core.database.repository.SqlSpecification; -import com.emarsys.core.handler.CoreSdkHandler; -import com.emarsys.core.provider.timestamp.TimestampProvider; -import com.emarsys.mobileengage.iam.dialog.action.SaveDisplayedIamAction; -import com.emarsys.mobileengage.iam.model.displayediam.DisplayedIam; -import com.emarsys.testUtil.TimeoutUtils; -import com.emarsys.testUtil.mockito.ThreadSpy; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; - -import static org.mockito.Mockito.*; - -public class SaveDisplayedIamActionTest { - - private static final String CAMPAIGN_ID = "123"; - private static final String SID = "testSid"; - private static final String URL = "https://www.emarsys.com"; - private static final long TIMESTAMP = 123; - private static final DisplayedIam IAM = new DisplayedIam(CAMPAIGN_ID, TIMESTAMP); - - private SaveDisplayedIamAction action; - private Repository repository; - private ThreadSpy threadSpy; - private CoreSdkHandler handler; - private TimestampProvider timestampProvider; - - @Rule - public TestRule timeout = TimeoutUtils.getTimeoutRule(); - - @Before - @SuppressWarnings("unchecked") - public void init() { - threadSpy = new ThreadSpy(); - repository = mock(Repository.class); - handler = new CoreSdkHandlerProvider().provideHandler(); - timestampProvider = mock(TimestampProvider.class); - when(timestampProvider.provideTimestamp()).thenReturn(TIMESTAMP); - - doAnswer(threadSpy).when(repository).add(IAM); - action = new SaveDisplayedIamAction(handler, repository, timestampProvider); - } - - @After - public void tearDown() { - handler.getLooper().quit(); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructor_handlerMustNotBeNull() { - new SaveDisplayedIamAction(null, repository, timestampProvider); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructor_repositoryMustNotBeNull() { - new SaveDisplayedIamAction(handler, null, timestampProvider); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructor_timestampProviderMustNotBeNull() { - new SaveDisplayedIamAction(handler, repository, null); - } - - @Test(expected = IllegalArgumentException.class) - public void testExecute_campaignIdMustNotBeNull() { - action.execute(null, SID, URL); - } - - @Test - public void testExecute_callsRepository() { - action.execute(CAMPAIGN_ID, SID, URL); - verify(repository, timeout(1000)).add(IAM); - } - - @Test - public void testExecute_callsRepository_onCoreSdkThread() throws InterruptedException { - action.execute(CAMPAIGN_ID, SID, URL); - threadSpy.verifyCalledOnCoreSdkThread(); - } - -} \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.kt new file mode 100644 index 000000000..0b307fa81 --- /dev/null +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.kt @@ -0,0 +1,79 @@ +package com.emarsys.mobileengage.iam + +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.database.repository.Repository +import com.emarsys.core.database.repository.SqlSpecification +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.core.provider.timestamp.TimestampProvider +import com.emarsys.mobileengage.iam.dialog.action.SaveDisplayedIamAction +import com.emarsys.mobileengage.iam.model.displayediam.DisplayedIam +import com.emarsys.testUtil.TimeoutUtils.timeoutRule +import com.emarsys.testUtil.mockito.ThreadSpy +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.timeout +import org.mockito.kotlin.verify + +class SaveDisplayedIamActionTest { + companion object { + private const val CAMPAIGN_ID = "123" + private const val SID = "testSid" + private const val URL = "https://www.emarsys.com" + private const val TIMESTAMP: Long = 123 + private val IAM = DisplayedIam(CAMPAIGN_ID, TIMESTAMP) + } + + private lateinit var action: SaveDisplayedIamAction + private lateinit var repository: Repository + private lateinit var threadSpy: ThreadSpy<*> + private lateinit var handler: ConcurrentHandlerHolder + private lateinit var timestampProvider: TimestampProvider + + @Rule + @JvmField + var timeout: TestRule = timeoutRule + + @Before + fun init() { + runBlocking { + threadSpy = ThreadSpy() + repository = mock() + handler = + ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() + timestampProvider = mock { + on { provideTimestamp() } doReturn TIMESTAMP + } + + org.mockito.Mockito.doAnswer(threadSpy).`when`(repository)?.add(IAM) + action = SaveDisplayedIamAction(handler, repository, timestampProvider) + } + } + + @After + fun tearDown() { + handler.looper.quit() + } + + @Test + fun testExecute_callsRepository() { + action.execute(CAMPAIGN_ID, SID, URL) + runBlocking { + verify(repository, timeout(1000)).add(IAM) + } + } + + @Test + @Throws(InterruptedException::class) + fun testExecute_callsRepository_onCoreSdkThread() { + action.execute(CAMPAIGN_ID, SID, URL) + threadSpy.verifyCalledOnCoreSdkThread() + } +} \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamActionTest.java b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamActionTest.java index 836811ada..0a70455bd 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamActionTest.java +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamActionTest.java @@ -1,11 +1,21 @@ package com.emarsys.mobileengage.iam.dialog.action; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.os.Handler; +import android.os.Looper; + import com.emarsys.core.api.result.CompletionListener; -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; -import com.emarsys.core.handler.CoreSdkHandler; +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; +import com.emarsys.core.handler.ConcurrentHandlerHolder; import com.emarsys.mobileengage.iam.InAppInternal; import com.emarsys.testUtil.TimeoutUtils; import com.emarsys.testUtil.mockito.ThreadSpy; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -16,10 +26,6 @@ import java.util.HashMap; import java.util.Map; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.*; - public class SendDisplayedIamActionTest { private static final String CAMPAIGN_ID = "123445"; @@ -28,7 +34,7 @@ public class SendDisplayedIamActionTest { private SendDisplayedIamAction action; - private CoreSdkHandler handler; + private ConcurrentHandlerHolder handler; private InAppInternal inAppInternal; @Rule @@ -36,7 +42,7 @@ public class SendDisplayedIamActionTest { @Before public void init() { - handler = new CoreSdkHandlerProvider().provideHandler(); + handler = new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create(); inAppInternal = mock(InAppInternal.class); action = new SendDisplayedIamAction(handler, inAppInternal); } @@ -57,7 +63,7 @@ public void testConstructor_handler_mustNotBeNull() { @Test(expected = IllegalArgumentException.class) public void testConstructor_inAppInternal_mustNotBeNull() { new SendDisplayedIamAction( - mock(CoreSdkHandler.class), + mock(ConcurrentHandlerHolder.class), null ); } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactoryTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactoryTest.kt index 67a41b4c8..b682e7118 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactoryTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactoryTest.kt @@ -4,10 +4,10 @@ import android.app.Activity import android.os.Handler import android.os.Looper import androidx.test.rule.ActivityTestRule -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.mobileengage.iam.InAppInternal @@ -17,6 +17,7 @@ import com.emarsys.testUtil.fake.FakeActivity import com.emarsys.testUtil.mockito.ThreadSpy import io.kotlintest.shouldBe import io.kotlintest.shouldThrow +import kotlinx.coroutines.runBlocking import org.json.JSONObject import org.junit.Before import org.junit.Rule @@ -38,7 +39,7 @@ class JSCommandFactoryTest { private lateinit var jsCommandFactory: JSCommandFactory private lateinit var mockCurrentActivityProvider: CurrentActivityProvider private lateinit var uiHandler: Handler - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var mockInAppInternal: InAppInternal private lateinit var mockButtonClickedRepository: Repository private lateinit var mockOnCloseListener: OnCloseListener @@ -57,7 +58,7 @@ class JSCommandFactoryTest { on { get() } doReturn mockActivity } uiHandler = Handler(Looper.getMainLooper()) - coreSdkHandler = CoreSdkHandlerProvider().provideHandler() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() mockInAppInternal = mock() mockButtonClickedRepository = mock() mockOnCloseListener = mock() @@ -65,7 +66,16 @@ class JSCommandFactoryTest { mockTimestampProvider = mock { on { provideTimestamp() } doReturn TIMESTAMP } - jsCommandFactory = JSCommandFactory(mockCurrentActivityProvider, uiHandler, coreSdkHandler, mockInAppInternal, mockButtonClickedRepository, mockOnCloseListener, mockOnAppEventListener, mockTimestampProvider) + jsCommandFactory = JSCommandFactory( + mockCurrentActivityProvider, + uiHandler, + concurrentHandlerHolder, + mockInAppInternal, + mockButtonClickedRepository, + mockOnCloseListener, + mockOnAppEventListener, + mockTimestampProvider + ) } @Test @@ -85,7 +95,8 @@ class JSCommandFactoryTest { val expectedJson = JSONObject(mapOf("testKey" to "testValue")) whenever(mockOnAppEventListener.invoke(anyOrNull(), any())).doAnswer(threadSpy) - jsCommandFactory.create(JSCommandFactory.CommandType.ON_APP_EVENT).invoke("test", expectedJson) + jsCommandFactory.create(JSCommandFactory.CommandType.ON_APP_EVENT) + .invoke("test", expectedJson) threadSpy.verifyCalledOnMainThread() verify(mockOnAppEventListener).invoke("test", expectedJson) @@ -96,20 +107,30 @@ class JSCommandFactoryTest { val inAppMessage = InAppMessage(CAMPAIGN_ID, SID, URL) val latch1 = CountDownLatch(1) val latch2 = CountDownLatch(1) - coreSdkHandler.post { latch1.await() } - jsCommandFactory.create(JSCommandFactory.CommandType.ON_BUTTON_CLICKED, inAppMessage).invoke(PROPERTY, JSONObject(mapOf("key" to "value"))) + concurrentHandlerHolder.coreHandler.post { latch1.await() } + jsCommandFactory.create(JSCommandFactory.CommandType.ON_BUTTON_CLICKED, inAppMessage) + .invoke(PROPERTY, JSONObject(mapOf("key" to "value"))) verifyNoInteractions(mockButtonClickedRepository) verifyNoInteractions(mockInAppInternal) latch1.countDown() - coreSdkHandler.post { latch2.countDown() } + concurrentHandlerHolder.coreHandler.post { latch2.countDown() } latch2.await() - verify(mockButtonClickedRepository).add(ButtonClicked(inAppMessage.campaignId, PROPERTY, TIMESTAMP)) + runBlocking { + verify(mockButtonClickedRepository).add( + ButtonClicked( + inAppMessage.campaignId, + PROPERTY, + TIMESTAMP + ) + ) + } val expectedEventName = "inapp:click" val attributes = mapOf( - "campaignId" to CAMPAIGN_ID, - "buttonId" to PROPERTY, - "sid" to SID, - "url" to URL) + "campaignId" to CAMPAIGN_ID, + "buttonId" to PROPERTY, + "sid" to SID, + "url" to URL + ) verify(mockInAppInternal).trackInternalCustomEvent(expectedEventName, attributes, null) } @@ -118,15 +139,25 @@ class JSCommandFactoryTest { fun testCreate_shouldTriggerInAPpInternalWithAttributes_whenSIDANDURL_areNulls() { val inAppMessage = InAppMessage(CAMPAIGN_ID, null, null) val latch = CountDownLatch(1) - jsCommandFactory.create(JSCommandFactory.CommandType.ON_BUTTON_CLICKED, inAppMessage).invoke(PROPERTY, JSONObject()) + jsCommandFactory.create(JSCommandFactory.CommandType.ON_BUTTON_CLICKED, inAppMessage) + .invoke(PROPERTY, JSONObject()) - coreSdkHandler.post { latch.countDown() } + concurrentHandlerHolder.coreHandler.post { latch.countDown() } latch.await() - verify(mockButtonClickedRepository).add(ButtonClicked(inAppMessage.campaignId, PROPERTY, TIMESTAMP)) + runBlocking { + verify(mockButtonClickedRepository).add( + ButtonClicked( + inAppMessage.campaignId, + PROPERTY, + TIMESTAMP + ) + ) + } val expectedEventName = "inapp:click" val attributes = mapOf( - "campaignId" to CAMPAIGN_ID, - "buttonId" to PROPERTY) + "campaignId" to CAMPAIGN_ID, + "buttonId" to PROPERTY + ) verify(mockInAppInternal).trackInternalCustomEvent(expectedEventName, attributes, null) } @@ -139,7 +170,7 @@ class JSCommandFactoryTest { whenever(mockActivity.startActivity(any())).doAnswer(threadSpy) jsCommandFactory.create(JSCommandFactory.CommandType.ON_OPEN_EXTERNAL_URL) - .invoke(property, JSONObject()) + .invoke(property, JSONObject()) verify(mockCurrentActivityProvider).get() @@ -155,7 +186,8 @@ class JSCommandFactoryTest { throw Exception() } - val openExternalUrlListener = jsCommandFactory.create(JSCommandFactory.CommandType.ON_OPEN_EXTERNAL_URL) + val openExternalUrlListener = + jsCommandFactory.create(JSCommandFactory.CommandType.ON_OPEN_EXTERNAL_URL) val exception = shouldThrow { openExternalUrlListener.invoke(property, JSONObject()) @@ -168,7 +200,8 @@ class JSCommandFactoryTest { val property = TEST_URL whenever(mockCurrentActivityProvider.get()) doReturn null - val openExternalUrlListener = jsCommandFactory.create(JSCommandFactory.CommandType.ON_OPEN_EXTERNAL_URL) + val openExternalUrlListener = + jsCommandFactory.create(JSCommandFactory.CommandType.ON_OPEN_EXTERNAL_URL) val exception = shouldThrow { openExternalUrlListener.invoke(property, JSONObject()) @@ -181,16 +214,23 @@ class JSCommandFactoryTest { val latch1 = CountDownLatch(1) val latch2 = CountDownLatch(1) - coreSdkHandler.post { latch1.await() } + concurrentHandlerHolder.coreHandler.post { latch1.await() } val meEventListener = jsCommandFactory.create(JSCommandFactory.CommandType.ON_ME_EVENT) val property = "testProperty" - meEventListener.invoke(property, JSONObject(mapOf("key" to "value", "payload" to mapOf("payloadKey" to "payloadValue")))) + meEventListener.invoke( + property, + JSONObject(mapOf("key" to "value", "payload" to mapOf("payloadKey" to "payloadValue"))) + ) verifyNoInteractions(mockInAppInternal) latch1.countDown() - coreSdkHandler.post { latch2.countDown() } + concurrentHandlerHolder.coreHandler.post { latch2.countDown() } latch2.await() - verify(mockInAppInternal).trackCustomEventAsync(property, mapOf("payloadKey" to "payloadValue"), null) + verify(mockInAppInternal).trackCustomEventAsync( + property, + mapOf("payloadKey" to "payloadValue"), + null + ) } } \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepositoryTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepositoryTest.kt index 47c3de5de..f26bda95e 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepositoryTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepositoryTest.kt @@ -2,9 +2,13 @@ package com.emarsys.mobileengage.iam.model.buttonclicked import android.content.ContentValues import android.database.Cursor +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.DatabaseContract import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.helper.DbHelper +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.testUtil.DatabaseTestUtils.deleteCoreDatabase import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext import com.emarsys.testUtil.TimeoutUtils @@ -25,6 +29,7 @@ class ButtonClickedRepositoryTest { private lateinit var repository: ButtonClickedRepository private lateinit var buttonClicked1: ButtonClicked + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -35,7 +40,9 @@ class ButtonClickedRepositoryTest { deleteCoreDatabase() val context = getTargetContext() val dbHelper: DbHelper = CoreDbHelper(context, HashMap()) - repository = ButtonClickedRepository(dbHelper) + val uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + repository = ButtonClickedRepository(dbHelper, concurrentHandlerHolder) buttonClicked1 = ButtonClicked("campaign1", "button1", Date().time) } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.java b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.java deleted file mode 100644 index d497cecf7..000000000 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.emarsys.mobileengage.iam.model.displayediam; - -import static com.emarsys.core.database.DatabaseContract.DISPLAYED_IAM_COLUMN_NAME_CAMPAIGN_ID; -import static com.emarsys.core.database.DatabaseContract.DISPLAYED_IAM_COLUMN_NAME_TIMESTAMP; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; - -import com.emarsys.core.database.helper.CoreDbHelper; -import com.emarsys.core.database.helper.DbHelper; -import com.emarsys.core.database.trigger.TriggerKey; -import com.emarsys.testUtil.DatabaseTestUtils; -import com.emarsys.testUtil.InstrumentationRegistry; -import com.emarsys.testUtil.TimeoutUtils; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; - -import java.util.Date; -import java.util.HashMap; -import java.util.List; - -public class DisplayedIamRepositoryTest { - - static { - mock(Cursor.class); - } - - private DisplayedIamRepository iamRepository; - private DisplayedIam displayedIam1; - - @Rule - public TestRule timeout = TimeoutUtils.getTimeoutRule(); - - @Before - public void init() { - DatabaseTestUtils.deleteCoreDatabase(); - - Context context = InstrumentationRegistry.getTargetContext(); - DbHelper dbHelper = new CoreDbHelper(context, new HashMap>()); - iamRepository = new DisplayedIamRepository(dbHelper); - displayedIam1 = new DisplayedIam("campaign1", new Date().getTime()); - } - - @Test - public void testContentValuesFromItem() { - ContentValues expected = new ContentValues(); - expected.put(DISPLAYED_IAM_COLUMN_NAME_CAMPAIGN_ID, displayedIam1.getCampaignId()); - expected.put(DISPLAYED_IAM_COLUMN_NAME_TIMESTAMP, displayedIam1.getTimestamp()); - - ContentValues result = iamRepository.contentValuesFromItem(displayedIam1); - - Assert.assertEquals(expected, result); - } - - @Test - public void testItemFromCursor() { - Cursor cursor = mock(Cursor.class); - - when(cursor.getColumnIndexOrThrow(DISPLAYED_IAM_COLUMN_NAME_CAMPAIGN_ID)).thenReturn(0); - when(cursor.getString(0)).thenReturn(displayedIam1.getCampaignId()); - when(cursor.getColumnIndexOrThrow(DISPLAYED_IAM_COLUMN_NAME_TIMESTAMP)).thenReturn(1); - when(cursor.getLong(1)).thenReturn(displayedIam1.getTimestamp()); - - DisplayedIam result = iamRepository.itemFromCursor(cursor); - DisplayedIam expected = displayedIam1; - - Assert.assertEquals(expected, result); - } - -} \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.kt new file mode 100644 index 000000000..edf250e33 --- /dev/null +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.kt @@ -0,0 +1,70 @@ +package com.emarsys.mobileengage.iam.model.displayediam + +import android.content.ContentValues +import android.database.Cursor +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.database.DatabaseContract.DISPLAYED_IAM_COLUMN_NAME_CAMPAIGN_ID +import com.emarsys.core.database.DatabaseContract.DISPLAYED_IAM_COLUMN_NAME_TIMESTAMP +import com.emarsys.core.database.helper.CoreDbHelper +import com.emarsys.core.database.helper.DbHelper +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.testUtil.DatabaseTestUtils.deleteCoreDatabase +import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext +import com.emarsys.testUtil.TimeoutUtils.timeoutRule +import io.kotlintest.shouldBe +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.util.* + +class DisplayedIamRepositoryTest { + + private lateinit var iamRepository: DisplayedIamRepository + private lateinit var displayedIam1: DisplayedIam + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder + + @Rule + @JvmField + var timeout: TestRule = timeoutRule + + @Before + fun setUp() { + deleteCoreDatabase() + val context = getTargetContext() + val dbHelper: DbHelper = CoreDbHelper(context, mutableMapOf()) + concurrentHandlerHolder = + ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() + iamRepository = DisplayedIamRepository(dbHelper, concurrentHandlerHolder) + displayedIam1 = DisplayedIam("campaign1", Date().time) + } + + @Test + fun testContentValuesFromItem() { + val expected = ContentValues() + expected.put(DISPLAYED_IAM_COLUMN_NAME_CAMPAIGN_ID, displayedIam1.campaignId) + expected.put(DISPLAYED_IAM_COLUMN_NAME_TIMESTAMP, displayedIam1.timestamp) + val result = iamRepository.contentValuesFromItem(displayedIam1) + + result shouldBe expected + } + + @Test + fun testItemFromCursor() { + val mockCursor: Cursor = mock() + whenever(mockCursor.getColumnIndexOrThrow(DISPLAYED_IAM_COLUMN_NAME_CAMPAIGN_ID)) + .thenReturn(0) + whenever(mockCursor.getString(0)).thenReturn(displayedIam1.campaignId) + whenever(mockCursor.getColumnIndexOrThrow(DISPLAYED_IAM_COLUMN_NAME_TIMESTAMP)) + .thenReturn(1) + whenever(mockCursor.getLong(1)).thenReturn(displayedIam1.timestamp) + val result = iamRepository.itemFromCursor(mockCursor) + val expected = displayedIam1 + Assert.assertEquals(expected, result) + } +} \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxyTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxyTest.kt index 512ad2c0c..801036325 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxyTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxyTest.kt @@ -1,6 +1,9 @@ package com.emarsys.mobileengage.iam.model.requestRepositoryProxy +import android.os.Handler +import android.os.Looper import com.emarsys.common.feature.InnerFeature +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.helper.DbHelper import com.emarsys.core.database.repository.Repository @@ -8,6 +11,7 @@ import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.database.repository.specification.Everything import com.emarsys.core.endpoint.ServiceEndpointProvider import com.emarsys.core.feature.FeatureRegistry +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.model.CompositeRequestModel @@ -29,6 +33,7 @@ import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext import com.emarsys.testUtil.RandomTestUtils.randomString import com.emarsys.testUtil.TimeoutUtils.timeoutRule import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.* import org.junit.rules.TestRule import org.mockito.kotlin.* @@ -47,6 +52,7 @@ class RequestRepositoryProxyTest { private lateinit var uuidProvider: UUIDProvider private lateinit var mockEventServiceProvider: ServiceEndpointProvider private lateinit var mockRequestModelHelper: RequestModelHelper + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -56,15 +62,17 @@ class RequestRepositoryProxyTest { fun setUp() { deleteCoreDatabase() val context = getTargetContext() + val uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() mockRequestContext = mock() mockRequestModelRepository = mock() mockDisplayedIamRepository = mock() mockButtonClickedRepository = mock() whenever(mockRequestContext.applicationCode).thenReturn(APPLICATION_CODE) val dbHelper: DbHelper = CoreDbHelper(context, HashMap()) - requestModelRepository = RequestModelRepository(dbHelper) - displayedIamRepository = DisplayedIamRepository(dbHelper) - buttonClickedRepository = ButtonClickedRepository(dbHelper) + requestModelRepository = RequestModelRepository(dbHelper, concurrentHandlerHolder) + displayedIamRepository = DisplayedIamRepository(dbHelper, concurrentHandlerHolder) + buttonClickedRepository = ButtonClickedRepository(dbHelper, concurrentHandlerHolder) timestampProvider = mock() whenever(timestampProvider.provideTimestamp()).thenReturn(TIMESTAMP) uuidProvider = mock() @@ -78,14 +86,15 @@ class RequestRepositoryProxyTest { on { isCustomEvent(any()) } doReturn true } compositeRepository = RequestRepositoryProxy( - mockRequestModelRepository, - mockDisplayedIamRepository, - mockButtonClickedRepository, - timestampProvider, - uuidProvider, - inAppEventHandlerInternal, - mockEventServiceProvider, - mockRequestModelHelper) + mockRequestModelRepository, + mockDisplayedIamRepository, + mockButtonClickedRepository, + timestampProvider, + uuidProvider, + inAppEventHandlerInternal, + mockEventServiceProvider, + mockRequestModelHelper + ) } @After @@ -96,121 +105,143 @@ class RequestRepositoryProxyTest { @Test(expected = IllegalArgumentException::class) fun testConstructor_requestRepository_mustNotBeNull() { - RequestRepositoryProxy(null, - mockDisplayedIamRepository, - mockButtonClickedRepository, - timestampProvider, - uuidProvider, - inAppEventHandlerInternal, - mockEventServiceProvider, - mockRequestModelHelper) + RequestRepositoryProxy( + null, + mockDisplayedIamRepository, + mockButtonClickedRepository, + timestampProvider, + uuidProvider, + inAppEventHandlerInternal, + mockEventServiceProvider, + mockRequestModelHelper + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_displayedIamRepository_mustNotBeNull() { - RequestRepositoryProxy(mockRequestModelRepository, - null, - mockButtonClickedRepository, - timestampProvider, - uuidProvider, - inAppEventHandlerInternal, - mockEventServiceProvider, - mockRequestModelHelper) + RequestRepositoryProxy( + mockRequestModelRepository, + null, + mockButtonClickedRepository, + timestampProvider, + uuidProvider, + inAppEventHandlerInternal, + mockEventServiceProvider, + mockRequestModelHelper + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_buttonClickedRepository_mustNotBeNull() { - RequestRepositoryProxy(mockRequestModelRepository, - mockDisplayedIamRepository, - null, - timestampProvider, - uuidProvider, - inAppEventHandlerInternal, - mockEventServiceProvider, - mockRequestModelHelper) + RequestRepositoryProxy( + mockRequestModelRepository, + mockDisplayedIamRepository, + null, + timestampProvider, + uuidProvider, + inAppEventHandlerInternal, + mockEventServiceProvider, + mockRequestModelHelper + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_timestampProvider_mustNotBeNull() { - RequestRepositoryProxy(mockRequestModelRepository, - mockDisplayedIamRepository, - buttonClickedRepository, - null, - uuidProvider, - inAppEventHandlerInternal, - mockEventServiceProvider, - mockRequestModelHelper) + RequestRepositoryProxy( + mockRequestModelRepository, + mockDisplayedIamRepository, + buttonClickedRepository, + null, + uuidProvider, + inAppEventHandlerInternal, + mockEventServiceProvider, + mockRequestModelHelper + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_inAppInternal_mustNotBeNull() { - RequestRepositoryProxy(mockRequestModelRepository, - mockDisplayedIamRepository, - buttonClickedRepository, - timestampProvider, - uuidProvider, - null, - mockEventServiceProvider, - mockRequestModelHelper) + RequestRepositoryProxy( + mockRequestModelRepository, + mockDisplayedIamRepository, + buttonClickedRepository, + timestampProvider, + uuidProvider, + null, + mockEventServiceProvider, + mockRequestModelHelper + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_uuidProvider_mustNotBeNull() { - RequestRepositoryProxy(mockRequestModelRepository, - mockDisplayedIamRepository, - buttonClickedRepository, - timestampProvider, - null, - inAppEventHandlerInternal, - mockEventServiceProvider, - mockRequestModelHelper) + RequestRepositoryProxy( + mockRequestModelRepository, + mockDisplayedIamRepository, + buttonClickedRepository, + timestampProvider, + null, + inAppEventHandlerInternal, + mockEventServiceProvider, + mockRequestModelHelper + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_eventServiceProvider_mustNotBeNull() { - RequestRepositoryProxy(mockRequestModelRepository, - mockDisplayedIamRepository, - buttonClickedRepository, - timestampProvider, - uuidProvider, - inAppEventHandlerInternal, - null, - mockRequestModelHelper) + RequestRepositoryProxy( + mockRequestModelRepository, + mockDisplayedIamRepository, + buttonClickedRepository, + timestampProvider, + uuidProvider, + inAppEventHandlerInternal, + null, + mockRequestModelHelper + ) } @Test(expected = IllegalArgumentException::class) fun testConstructor_requestModelHelper_mustNotBeNull() { - RequestRepositoryProxy(mockRequestModelRepository, - mockDisplayedIamRepository, - buttonClickedRepository, - timestampProvider, - uuidProvider, - inAppEventHandlerInternal, - mockEventServiceProvider, - null) + RequestRepositoryProxy( + mockRequestModelRepository, + mockDisplayedIamRepository, + buttonClickedRepository, + timestampProvider, + uuidProvider, + inAppEventHandlerInternal, + mockEventServiceProvider, + null + ) } @Test fun testAdd_shouldDelegate_toRequestModelRepository() { val requestModel: RequestModel = mock() - compositeRepository.add(requestModel) - verify(mockRequestModelRepository).add(requestModel) + runBlocking { + compositeRepository.add(requestModel) + verify(mockRequestModelRepository).add(requestModel) + } } @Test fun testAdd_shouldNotStoreCompositeRequestModels() { val requestModel: CompositeRequestModel = mock() - compositeRepository.add(requestModel) - verifyNoInteractions(mockRequestModelRepository) + runBlocking { + compositeRepository.add(requestModel) + verifyNoInteractions(mockRequestModelRepository) + } } @Test fun testRemove_shouldDelegate_toRequestModelRepository() { val spec: SqlSpecification = mock() - compositeRepository.remove(spec) - verify(mockRequestModelRepository).remove(spec) + runBlocking { + compositeRepository.remove(spec) + verify(mockRequestModelRepository).remove(spec) + } } @Test @@ -232,204 +263,225 @@ class RequestRepositoryProxyTest { whenever(mockRequestModelHelper.isCustomEvent(any())).thenReturn(false) compositeRepository = compositeRepositoryWithRealRepositories() val firstRequestModel = requestModel() - requestModelRepository.add(firstRequestModel) - requestModelRepository.add(requestModel()) - requestModelRepository.add(requestModel()) + runBlocking { + requestModelRepository.add(firstRequestModel) + requestModelRepository.add(requestModel()) + requestModelRepository.add(requestModel()) + } val expected = listOf(firstRequestModel) Assert.assertEquals(expected, compositeRepository.query(QueryLatestRequestModel())) } @Test fun testQuery_resultShouldContainCompositeRequestModel_whenResultContainsCustomEvent() { - compositeRepository = compositeRepositoryWithRealRepositories() - val request1 = requestModel() - val request2 = requestModel() - val request3 = requestModel() - val customEvent1 = customEvent(900, "event1") - val attributes = HashMap() - attributes["key1"] = "value1" - attributes["key2"] = "value2" - val customEvent2 = customEvent(1000, "event2", attributes) - val customEvent3 = customEvent(1200, "event3") - whenever(mockRequestModelHelper.isCustomEvent(customEvent1)).thenReturn(true) - whenever(mockRequestModelHelper.isCustomEvent(customEvent2)).thenReturn(true) - whenever(mockRequestModelHelper.isCustomEvent(customEvent3)).thenReturn(true) - whenever(mockRequestModelHelper.isCustomEvent(request1)).thenReturn(false) - whenever(mockRequestModelHelper.isCustomEvent(request2)).thenReturn(false) - whenever(mockRequestModelHelper.isCustomEvent(request3)).thenReturn(false) - requestModelRepository.add(request1) - requestModelRepository.add(request2) - requestModelRepository.add(customEvent1) - requestModelRepository.add(customEvent2) - requestModelRepository.add(request3) - requestModelRepository.add(customEvent3) - val event1: MutableMap = HashMap() - event1["type"] = "custom" - event1["name"] = "event1" - event1["timestamp"] = TimestampUtils.formatTimestampWithUTC(900) - val event2: MutableMap = HashMap() - event2["type"] = "custom" - event2["name"] = "event2" - event2["timestamp"] = TimestampUtils.formatTimestampWithUTC(1000) - event2["attributes"] = object : HashMap() { - init { - put("key1", "value1") - put("key2", "value2") + runBlocking { + compositeRepository = compositeRepositoryWithRealRepositories() + val request1 = requestModel() + val request2 = requestModel() + val request3 = requestModel() + val customEvent1 = customEvent(900, "event1") + val attributes = HashMap() + attributes["key1"] = "value1" + attributes["key2"] = "value2" + val customEvent2 = customEvent(1000, "event2", attributes) + val customEvent3 = customEvent(1200, "event3") + whenever(mockRequestModelHelper.isCustomEvent(customEvent1)).thenReturn(true) + whenever(mockRequestModelHelper.isCustomEvent(customEvent2)).thenReturn(true) + whenever(mockRequestModelHelper.isCustomEvent(customEvent3)).thenReturn(true) + whenever(mockRequestModelHelper.isCustomEvent(request1)).thenReturn(false) + whenever(mockRequestModelHelper.isCustomEvent(request2)).thenReturn(false) + whenever(mockRequestModelHelper.isCustomEvent(request3)).thenReturn(false) + requestModelRepository.add(request1) + requestModelRepository.add(request2) + requestModelRepository.add(customEvent1) + requestModelRepository.add(customEvent2) + requestModelRepository.add(request3) + requestModelRepository.add(customEvent3) + val event1: MutableMap = HashMap() + event1["type"] = "custom" + event1["name"] = "event1" + event1["timestamp"] = TimestampUtils.formatTimestampWithUTC(900) + val event2: MutableMap = HashMap() + event2["type"] = "custom" + event2["name"] = "event2" + event2["timestamp"] = TimestampUtils.formatTimestampWithUTC(1000) + event2["attributes"] = object : HashMap() { + init { + put("key1", "value1") + put("key2", "value2") + } } - } - val event3: MutableMap = HashMap() - event3["type"] = "custom" - event3["name"] = "event3" - event3["timestamp"] = TimestampUtils.formatTimestampWithUTC(1200) - val payload: Map = createCompositeRequestModelPayload( + val event3: MutableMap = HashMap() + event3["type"] = "custom" + event3["name"] = "event3" + event3["timestamp"] = TimestampUtils.formatTimestampWithUTC(1200) + val payload: Map = createCompositeRequestModelPayload( listOf>(event1, event2, event3), emptyList(), emptyList(), - false) - val expectedComposite: RequestModel = CompositeRequestModel( + false + ) + val expectedComposite: RequestModel = CompositeRequestModel( REQUEST_ID, "https://mobile-events.eservice.emarsys.net/v3/apps/$APPLICATION_CODE/client/events", RequestMethod.POST, payload, customEvent1.headers, - TIMESTAMP, Long.MAX_VALUE, arrayOf(customEvent1.id, customEvent2.id, customEvent3.id)) - val expected = listOf( + TIMESTAMP, + Long.MAX_VALUE, + arrayOf(customEvent1.id, customEvent2.id, customEvent3.id) + ) + val expected = listOf( request1, request2, expectedComposite, - request3) - Assert.assertEquals(expected, compositeRepository.query(Everything())) + request3 + ) + Assert.assertEquals(expected, compositeRepository.query(Everything())) + } } @Test fun testQuery_resultShouldContainClicksAndDisplays_withSingleCustomEvent() { - compositeRepository = compositeRepositoryWithRealRepositories() - val attributes = HashMap() - attributes["key1"] = "value1" - attributes["key2"] = "value2" - val customEvent1 = customEvent(1000, "event1", attributes) - requestModelRepository.add(customEvent1) - val buttonClicked1 = ButtonClicked("campaign1", "button1", 200) - val buttonClicked2 = ButtonClicked("campaign1", "button2", 300) - val buttonClicked3 = ButtonClicked("campaign2", "button1", 2000) - buttonClickedRepository.add(buttonClicked1) - buttonClickedRepository.add(buttonClicked2) - buttonClickedRepository.add(buttonClicked3) - val displayedIam1 = DisplayedIam("campaign1", 100) - val displayedIam2 = DisplayedIam("campaign2", 1500) - val displayedIam3 = DisplayedIam("campaign3", 30000) - displayedIamRepository.add(displayedIam1) - displayedIamRepository.add(displayedIam2) - displayedIamRepository.add(displayedIam3) - val eventAttributes = HashMap() - eventAttributes["key1"] = "value1" - eventAttributes["key2"] = "value2" - val event1: MutableMap = HashMap() - event1["type"] = "custom" - event1["name"] = "event1" - event1["timestamp"] = TimestampUtils.formatTimestampWithUTC(1000) - event1["attributes"] = eventAttributes - val payload: Map = createCompositeRequestModelPayload(listOf>(event1), + runBlocking { + compositeRepository = compositeRepositoryWithRealRepositories() + val attributes = HashMap() + attributes["key1"] = "value1" + attributes["key2"] = "value2" + val customEvent1 = customEvent(1000, "event1", attributes) + requestModelRepository.add(customEvent1) + val buttonClicked1 = ButtonClicked("campaign1", "button1", 200) + val buttonClicked2 = ButtonClicked("campaign1", "button2", 300) + val buttonClicked3 = ButtonClicked("campaign2", "button1", 2000) + buttonClickedRepository.add(buttonClicked1) + buttonClickedRepository.add(buttonClicked2) + buttonClickedRepository.add(buttonClicked3) + val displayedIam1 = DisplayedIam("campaign1", 100) + val displayedIam2 = DisplayedIam("campaign2", 1500) + val displayedIam3 = DisplayedIam("campaign3", 30000) + displayedIamRepository.add(displayedIam1) + displayedIamRepository.add(displayedIam2) + displayedIamRepository.add(displayedIam3) + val eventAttributes = HashMap() + eventAttributes["key1"] = "value1" + eventAttributes["key2"] = "value2" + val event1: MutableMap = HashMap() + event1["type"] = "custom" + event1["name"] = "event1" + event1["timestamp"] = TimestampUtils.formatTimestampWithUTC(1000) + event1["attributes"] = eventAttributes + val payload: Map = createCompositeRequestModelPayload( + listOf>(event1), listOf( - DisplayedIam("campaign1", 100), - DisplayedIam("campaign2", 1500), - DisplayedIam("campaign3", 30000) + DisplayedIam("campaign1", 100), + DisplayedIam("campaign2", 1500), + DisplayedIam("campaign3", 30000) ), listOf( - ButtonClicked("campaign1", "button1", 200), - ButtonClicked("campaign1", "button2", 300), - ButtonClicked("campaign2", "button1", 2000) + ButtonClicked("campaign1", "button1", 200), + ButtonClicked("campaign1", "button2", 300), + ButtonClicked("campaign2", "button1", 2000) ), - false) - val expectedComposite: RequestModel = CompositeRequestModel( + false + ) + val expectedComposite: RequestModel = CompositeRequestModel( REQUEST_ID, "https://mobile-events.eservice.emarsys.net/v3/apps/" + APPLICATION_CODE + "/client/events", RequestMethod.POST, payload, customEvent1.headers, - TIMESTAMP, Long.MAX_VALUE, arrayOf(customEvent1.id)) - val expected = listOf(expectedComposite) - Assert.assertEquals(expected, compositeRepository.query(Everything())) + TIMESTAMP, Long.MAX_VALUE, arrayOf(customEvent1.id) + ) + val expected = listOf(expectedComposite) + Assert.assertEquals(expected, compositeRepository.query(Everything())) + } } @Test fun testQuery_resultShouldContainClicksAndDisplays_withMultipleRequests() { - compositeRepository = compositeRepositoryWithRealRepositories() - val request1 = requestModel() - val request2 = requestModel() - val request3 = requestModel() - val customEvent1 = customEvent(900, "event1") - val attributes = HashMap() - attributes["key1"] = "value1" - attributes["key2"] = "value2" - val customEvent2 = customEvent(1000, "event2", attributes) - val customEvent3 = customEvent(1200, "event3") - whenever(mockRequestModelHelper.isCustomEvent(customEvent1)).thenReturn(true) - whenever(mockRequestModelHelper.isCustomEvent(customEvent2)).thenReturn(true) - whenever(mockRequestModelHelper.isCustomEvent(customEvent3)).thenReturn(true) - whenever(mockRequestModelHelper.isCustomEvent(request1)).thenReturn(false) - whenever(mockRequestModelHelper.isCustomEvent(request2)).thenReturn(false) - whenever(mockRequestModelHelper.isCustomEvent(request3)).thenReturn(false) - requestModelRepository.add(request1) - requestModelRepository.add(request2) - requestModelRepository.add(customEvent1) - requestModelRepository.add(customEvent2) - requestModelRepository.add(request3) - requestModelRepository.add(customEvent3) - val buttonClicked1 = ButtonClicked("campaign1", "button1", 200) - val buttonClicked2 = ButtonClicked("campaign1", "button2", 300) - val buttonClicked3 = ButtonClicked("campaign2", "button1", 2000) - buttonClickedRepository.add(buttonClicked1) - buttonClickedRepository.add(buttonClicked2) - buttonClickedRepository.add(buttonClicked3) - val displayedIam1 = DisplayedIam("campaign1", 100) - val displayedIam2 = DisplayedIam("campaign2", 1500) - val displayedIam3 = DisplayedIam("campaign3", 30000) - displayedIamRepository.add(displayedIam1) - displayedIamRepository.add(displayedIam2) - displayedIamRepository.add(displayedIam3) - val event1: MutableMap = HashMap() - event1["type"] = "custom" - event1["name"] = "event1" - event1["timestamp"] = TimestampUtils.formatTimestampWithUTC(900) - val event2: MutableMap = HashMap() - event2["type"] = "custom" - event2["name"] = "event2" - event2["timestamp"] = TimestampUtils.formatTimestampWithUTC(1000) - event2["attributes"] = object : HashMap() { - init { - put("key1", "value1") - put("key2", "value2") + runBlocking { + compositeRepository = compositeRepositoryWithRealRepositories() + val request1 = requestModel() + val request2 = requestModel() + val request3 = requestModel() + val customEvent1 = customEvent(900, "event1") + val attributes = HashMap() + attributes["key1"] = "value1" + attributes["key2"] = "value2" + val customEvent2 = customEvent(1000, "event2", attributes) + val customEvent3 = customEvent(1200, "event3") + whenever(mockRequestModelHelper.isCustomEvent(customEvent1)).thenReturn(true) + whenever(mockRequestModelHelper.isCustomEvent(customEvent2)).thenReturn(true) + whenever(mockRequestModelHelper.isCustomEvent(customEvent3)).thenReturn(true) + whenever(mockRequestModelHelper.isCustomEvent(request1)).thenReturn(false) + whenever(mockRequestModelHelper.isCustomEvent(request2)).thenReturn(false) + whenever(mockRequestModelHelper.isCustomEvent(request3)).thenReturn(false) + requestModelRepository.add(request1) + requestModelRepository.add(request2) + requestModelRepository.add(customEvent1) + requestModelRepository.add(customEvent2) + requestModelRepository.add(request3) + requestModelRepository.add(customEvent3) + val buttonClicked1 = ButtonClicked("campaign1", "button1", 200) + val buttonClicked2 = ButtonClicked("campaign1", "button2", 300) + val buttonClicked3 = ButtonClicked("campaign2", "button1", 2000) + buttonClickedRepository.add(buttonClicked1) + buttonClickedRepository.add(buttonClicked2) + buttonClickedRepository.add(buttonClicked3) + val displayedIam1 = DisplayedIam("campaign1", 100) + val displayedIam2 = DisplayedIam("campaign2", 1500) + val displayedIam3 = DisplayedIam("campaign3", 30000) + displayedIamRepository.add(displayedIam1) + displayedIamRepository.add(displayedIam2) + displayedIamRepository.add(displayedIam3) + val event1: MutableMap = HashMap() + event1["type"] = "custom" + event1["name"] = "event1" + event1["timestamp"] = TimestampUtils.formatTimestampWithUTC(900) + val event2: MutableMap = HashMap() + event2["type"] = "custom" + event2["name"] = "event2" + event2["timestamp"] = TimestampUtils.formatTimestampWithUTC(1000) + event2["attributes"] = object : HashMap() { + init { + put("key1", "value1") + put("key2", "value2") + } } - } - val event3: MutableMap = HashMap() - event3["type"] = "custom" - event3["name"] = "event3" - event3["timestamp"] = TimestampUtils.formatTimestampWithUTC(1200) - val payload: Map = createCompositeRequestModelPayload( + val event3: MutableMap = HashMap() + event3["type"] = "custom" + event3["name"] = "event3" + event3["timestamp"] = TimestampUtils.formatTimestampWithUTC(1200) + val payload: Map = createCompositeRequestModelPayload( listOf>(event1, event2, event3), listOf( - DisplayedIam("campaign1", 100), - DisplayedIam("campaign2", 1500), - DisplayedIam("campaign3", 30000) + DisplayedIam("campaign1", 100), + DisplayedIam("campaign2", 1500), + DisplayedIam("campaign3", 30000) ), listOf( - ButtonClicked("campaign1", "button1", 200), - ButtonClicked("campaign1", "button2", 300), - ButtonClicked("campaign2", "button1", 2000) + ButtonClicked("campaign1", "button1", 200), + ButtonClicked("campaign1", "button2", 300), + ButtonClicked("campaign2", "button1", 2000) ), - false) - val expectedComposite: RequestModel = CompositeRequestModel( + false + ) + val expectedComposite: RequestModel = CompositeRequestModel( REQUEST_ID, "https://mobile-events.eservice.emarsys.net/v3/apps/" + APPLICATION_CODE + "/client/events", RequestMethod.POST, payload, customEvent1.headers, - TIMESTAMP, Long.MAX_VALUE, arrayOf(customEvent1.id, customEvent2.id, customEvent3.id)) - val expected = listOf( + TIMESTAMP, + Long.MAX_VALUE, + arrayOf(customEvent1.id, customEvent2.id, customEvent3.id) + ) + val expected = listOf( request1, request2, expectedComposite, - request3) - Assert.assertEquals(expected, compositeRepository.query(Everything())) + request3 + ) + Assert.assertEquals(expected, compositeRepository.query(Everything())) + } } @Test @@ -437,7 +489,9 @@ class RequestRepositoryProxyTest { whenever(inAppEventHandlerInternal.isPaused).thenReturn(true) compositeRepository = compositeRepositoryWithRealRepositories() val customEvent1 = customEvent(900, "event1") - requestModelRepository.add(customEvent1) + runBlocking { + requestModelRepository.add(customEvent1) + } val result = compositeRepository.query(Everything()) val payload = result[0].payload payload?.get("dnd") shouldBe true @@ -448,7 +502,9 @@ class RequestRepositoryProxyTest { whenever(inAppEventHandlerInternal.isPaused).thenReturn(false) compositeRepository = compositeRepositoryWithRealRepositories() val customEvent1 = customEvent(900, "event1") - requestModelRepository.add(customEvent1) + runBlocking { + requestModelRepository.add(customEvent1) + } val result = compositeRepository.query(Everything()) val payload = result[0].payload payload?.get("dnd") shouldBe null @@ -456,18 +512,22 @@ class RequestRepositoryProxyTest { private fun compositeRepositoryWithRealRepositories(): RequestRepositoryProxy { return RequestRepositoryProxy( - requestModelRepository, - displayedIamRepository, - buttonClickedRepository, - timestampProvider, - uuidProvider, - inAppEventHandlerInternal, - mockEventServiceProvider, - mockRequestModelHelper + requestModelRepository, + displayedIamRepository, + buttonClickedRepository, + timestampProvider, + uuidProvider, + inAppEventHandlerInternal, + mockEventServiceProvider, + mockRequestModelHelper ) } - private fun customEvent(timestamp: Long, eventName: String, attributes: Map? = null): RequestModel { + private fun customEvent( + timestamp: Long, + eventName: String, + attributes: Map? = null + ): RequestModel { val event: MutableMap = HashMap() event["type"] = "custom" event["name"] = eventName @@ -484,13 +544,13 @@ class RequestRepositoryProxyTest { headers["custom_event_header1"] = "custom_event_value1" headers["custom_event_header2"] = "custom_event_value2" return RequestModel( - "https://mobile-events.eservice.emarsys.net/v3/apps/$APPLICATION_CODE/client/events", - RequestMethod.POST, - payload, - headers, - System.currentTimeMillis(), - 999, - uuidProvider.provideId() + "https://mobile-events.eservice.emarsys.net/v3/apps/$APPLICATION_CODE/client/events", + RequestMethod.POST, + payload, + headers, + System.currentTimeMillis(), + 999, + uuidProvider.provideId() ) } @@ -501,10 +561,10 @@ class RequestRepositoryProxyTest { headers["header1"] = "value1" headers["header2"] = "value2" return RequestModel.Builder(timestampProvider, uuidProvider) - .url("https://emarsys.com") - .payload(payload) - .headers(headers) - .build() + .url("https://emarsys.com") + .payload(payload) + .headers(headers) + .build() } companion object { diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/specification/FilterByCampaignIdTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/specification/FilterByCampaignIdTest.kt index b0bc18480..689db9d81 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/specification/FilterByCampaignIdTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/specification/FilterByCampaignIdTest.kt @@ -1,7 +1,11 @@ package com.emarsys.mobileengage.iam.model.specification +import android.os.Handler +import android.os.Looper +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.specification.Everything +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.mobileengage.iam.model.buttonclicked.ButtonClicked import com.emarsys.mobileengage.iam.model.buttonclicked.ButtonClickedRepository import com.emarsys.mobileengage.iam.model.displayediam.DisplayedIam @@ -10,6 +14,7 @@ import com.emarsys.testUtil.DatabaseTestUtils import com.emarsys.testUtil.InstrumentationRegistry import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -21,19 +26,21 @@ class FilterByCampaignIdTest { @Rule @JvmField val timeout: TestRule = TimeoutUtils.timeoutRule - + private lateinit var displayedIamRepository: DisplayedIamRepository private lateinit var buttonClickedRepository: ButtonClickedRepository + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Before fun init() { DatabaseTestUtils.deleteCoreDatabase() - + val uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() val context = InstrumentationRegistry.getTargetContext() val dbHelper = CoreDbHelper(context, HashMap()) - displayedIamRepository = DisplayedIamRepository(dbHelper) - buttonClickedRepository = ButtonClickedRepository(dbHelper) + displayedIamRepository = DisplayedIamRepository(dbHelper, concurrentHandlerHolder) + buttonClickedRepository = ButtonClickedRepository(dbHelper, concurrentHandlerHolder) } @Test @@ -41,10 +48,11 @@ class FilterByCampaignIdTest { val iam1 = DisplayedIam("campaign1", 10L) val iam2 = DisplayedIam("campaign2", 20L) val iam3 = DisplayedIam("campaign3", 30L) - - displayedIamRepository.add(iam1) - displayedIamRepository.add(iam2) - displayedIamRepository.add(iam3) + runBlocking { + displayedIamRepository.add(iam1) + displayedIamRepository.add(iam2) + displayedIamRepository.add(iam3) + } val result = displayedIamRepository.query(FilterByCampaignId("campaign2", "campaign3")) val expected = listOf(iam2, iam3) @@ -58,16 +66,18 @@ class FilterByCampaignIdTest { val iam2 = DisplayedIam("campaign2", 20L) val iam3 = DisplayedIam("campaign3", 30L) - displayedIamRepository.add(iam1) - displayedIamRepository.add(iam2) - displayedIamRepository.add(iam3) + runBlocking { + displayedIamRepository.add(iam1) + displayedIamRepository.add(iam2) + displayedIamRepository.add(iam3) - displayedIamRepository.remove(FilterByCampaignId("campaign2")) + displayedIamRepository.remove(FilterByCampaignId("campaign2")) - val result = displayedIamRepository.query(Everything()) - val expected = listOf(iam1, iam3) + val result = displayedIamRepository.query(Everything()) + val expected = listOf(iam1, iam3) - result shouldBe expected + result shouldBe expected + } } @Test @@ -77,17 +87,19 @@ class FilterByCampaignIdTest { val iam3 = DisplayedIam("campaign3", 30L) val iam4 = DisplayedIam("campaign4", 40L) - displayedIamRepository.add(iam1) - displayedIamRepository.add(iam2) - displayedIamRepository.add(iam3) - displayedIamRepository.add(iam4) + runBlocking { + displayedIamRepository.add(iam1) + displayedIamRepository.add(iam2) + displayedIamRepository.add(iam3) + displayedIamRepository.add(iam4) - displayedIamRepository.remove(FilterByCampaignId("campaign1", "campaign2")) + displayedIamRepository.remove(FilterByCampaignId("campaign1", "campaign2")) - val result = displayedIamRepository.query(Everything()) - val expected = listOf(iam3, iam4) + val result = displayedIamRepository.query(Everything()) + val expected = listOf(iam3, iam4) - result shouldBe expected + result shouldBe expected + } } @Test @@ -97,17 +109,19 @@ class FilterByCampaignIdTest { val iam3 = DisplayedIam("campaign3", 30L) val iam4 = DisplayedIam("campaign4", 40L) - displayedIamRepository.add(iam1) - displayedIamRepository.add(iam2) - displayedIamRepository.add(iam3) - displayedIamRepository.add(iam4) + runBlocking { + displayedIamRepository.add(iam1) + displayedIamRepository.add(iam2) + displayedIamRepository.add(iam3) + displayedIamRepository.add(iam4) - displayedIamRepository.remove(FilterByCampaignId()) + displayedIamRepository.remove(FilterByCampaignId()) - val result = displayedIamRepository.query(Everything()) - val expected = listOf(iam1, iam2, iam3, iam4) + val result = displayedIamRepository.query(Everything()) + val expected = listOf(iam1, iam2, iam3, iam4) - result shouldBe expected + result shouldBe expected + } } @Test @@ -116,9 +130,11 @@ class FilterByCampaignIdTest { val btn2 = ButtonClicked("campaign1", "button3", 10L) val btn3 = ButtonClicked("campaign2", "button10", 10L) - buttonClickedRepository.add(btn1) - buttonClickedRepository.add(btn2) - buttonClickedRepository.add(btn3) + runBlocking { + buttonClickedRepository.add(btn1) + buttonClickedRepository.add(btn2) + buttonClickedRepository.add(btn3) + } val result = buttonClickedRepository.query(FilterByCampaignId("campaign1")) val expected = listOf(btn1, btn2) @@ -132,16 +148,18 @@ class FilterByCampaignIdTest { val btn2 = ButtonClicked("campaign1", "button3", 10L) val btn3 = ButtonClicked("campaign2", "button10", 10L) - buttonClickedRepository.add(btn1) - buttonClickedRepository.add(btn2) - buttonClickedRepository.add(btn3) + runBlocking { + buttonClickedRepository.add(btn1) + buttonClickedRepository.add(btn2) + buttonClickedRepository.add(btn3) - buttonClickedRepository.remove(FilterByCampaignId("campaign2")) + buttonClickedRepository.remove(FilterByCampaignId("campaign2")) - val result = buttonClickedRepository.query(Everything()) - val expected = listOf(btn1, btn2) + val result = buttonClickedRepository.query(Everything()) + val expected = listOf(btn1, btn2) - result shouldBe expected + result shouldBe expected + } } @Test @@ -151,17 +169,19 @@ class FilterByCampaignIdTest { val btn3 = ButtonClicked("campaign2", "button10", 10L) val btn4 = ButtonClicked("campaign3", "button10", 10L) - buttonClickedRepository.add(btn1) - buttonClickedRepository.add(btn2) - buttonClickedRepository.add(btn3) - buttonClickedRepository.add(btn4) + runBlocking { + buttonClickedRepository.add(btn1) + buttonClickedRepository.add(btn2) + buttonClickedRepository.add(btn3) + buttonClickedRepository.add(btn4) - buttonClickedRepository.remove(FilterByCampaignId("campaign1", "campaign2")) + buttonClickedRepository.remove(FilterByCampaignId("campaign1", "campaign2")) - val result = buttonClickedRepository.query(Everything()) - val expected = listOf(btn4) + val result = buttonClickedRepository.query(Everything()) + val expected = listOf(btn4) - result shouldBe expected + result shouldBe expected + } } @Test @@ -170,16 +190,18 @@ class FilterByCampaignIdTest { val btn2 = ButtonClicked("campaign1", "button3", 10L) val btn3 = ButtonClicked("campaign2", "button10", 10L) - buttonClickedRepository.add(btn1) - buttonClickedRepository.add(btn2) - buttonClickedRepository.add(btn3) + runBlocking { + buttonClickedRepository.add(btn1) + buttonClickedRepository.add(btn2) + buttonClickedRepository.add(btn3) - buttonClickedRepository.remove(FilterByCampaignId()) + buttonClickedRepository.remove(FilterByCampaignId()) - val result = buttonClickedRepository.query(Everything()) - val expected = listOf(btn1, btn2, btn3) + val result = buttonClickedRepository.query(Everything()) + val expected = listOf(btn1, btn2, btn3) - result shouldBe expected + result shouldBe expected + } } } \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/inbox/DefaultMessageInboxInternalTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/inbox/DefaultMessageInboxInternalTest.kt index d30a0804b..367ca3a50 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/inbox/DefaultMessageInboxInternalTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/inbox/DefaultMessageInboxInternalTest.kt @@ -7,7 +7,8 @@ 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.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient import com.emarsys.core.request.factory.CompletionHandlerProxyProvider @@ -43,9 +44,9 @@ class DefaultMessageInboxInternalTest { private lateinit var mockRequestModel: RequestModel private lateinit var messageInboxInternal: DefaultMessageInboxInternal private lateinit var latch: CountDownLatch - private val coreSdkHandler = CoreSdkHandlerProvider().provideHandler() private lateinit var uiHandler : Handler - private lateinit var mockScope : CoroutineScope + private lateinit var mockScope: CoroutineScope + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -54,6 +55,7 @@ class DefaultMessageInboxInternalTest { @Before fun setUp() { uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() mockScope = mock() latch = CountDownLatch(1) mockMessageInboxResponseMapper = mock { @@ -94,7 +96,13 @@ class DefaultMessageInboxInternalTest { } val messageInboxInternal = DefaultMessageInboxInternal( mockScope, - requestManagerWithRestClient(FakeRestClient(mockResponse, FakeRestClient.Mode.SUCCESS, coreSdkHandler.handler)), + requestManagerWithRestClient( + FakeRestClient( + mockResponse, + FakeRestClient.Mode.SUCCESS, + concurrentHandlerHolder.coreHandler + ) + ), mockRequestModelFactory, mockMessageInboxResponseMapper ) @@ -119,7 +127,13 @@ class DefaultMessageInboxInternalTest { } val messageInboxInternal = DefaultMessageInboxInternal( mockScope, - requestManagerWithRestClient(FakeRestClient(errorResponse, FakeRestClient.Mode.ERROR_RESPONSE_MODEL, coreSdkHandler.handler)), + requestManagerWithRestClient( + FakeRestClient( + errorResponse, + FakeRestClient.Mode.ERROR_RESPONSE_MODEL, + concurrentHandlerHolder.coreHandler + ) + ), mockRequestModelFactory, mockMessageInboxResponseMapper ) @@ -145,7 +159,12 @@ class DefaultMessageInboxInternalTest { val messageInboxInternal = DefaultMessageInboxInternal( mockScope, - requestManagerWithRestClient(FakeRestClient(expectedException, coreSdkHandler.handler)), + requestManagerWithRestClient( + FakeRestClient( + expectedException, + concurrentHandlerHolder.coreHandler + ) + ), mockRequestModelFactory, mockMessageInboxResponseMapper ) diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/notification/command/PreloadedInappHandlerCommandTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/notification/command/PreloadedInappHandlerCommandTest.kt index 0ce2d28c0..01dbd0cf4 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/notification/command/PreloadedInappHandlerCommandTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/notification/command/PreloadedInappHandlerCommandTest.kt @@ -1,12 +1,12 @@ package com.emarsys.mobileengage.notification.command -import android.app.Application import android.content.Intent import android.os.Bundle +import android.os.Handler +import android.os.Looper import com.emarsys.core.activity.ActivityLifecycleActionRegistry -import com.emarsys.core.activity.ActivityLifecycleWatchdog -import com.emarsys.core.concurrency.CoreSdkHandlerProvider -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.util.FileDownloader import com.emarsys.mobileengage.di.MobileEngageComponent import com.emarsys.mobileengage.di.setupMobileEngageComponent @@ -33,7 +33,7 @@ class PreloadedInappHandlerCommandTest { } private lateinit var mockDependencyContainer: MobileEngageComponent - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var mockLifecycleActionRegistry: ActivityLifecycleActionRegistry private lateinit var fileUrl: String @@ -43,9 +43,10 @@ class PreloadedInappHandlerCommandTest { @Before fun setUp() { - fileUrl = InstrumentationRegistry.getTargetContext().applicationContext.cacheDir.absolutePath + "/test.file" - - coreSdkHandler = CoreSdkHandlerProvider().provideHandler() + fileUrl = + InstrumentationRegistry.getTargetContext().applicationContext.cacheDir.absolutePath + "/test.file" + val uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() mockLifecycleActionRegistry = mock() val mockFileDownloader: FileDownloader = mock { @@ -54,9 +55,9 @@ class PreloadedInappHandlerCommandTest { } mockDependencyContainer = FakeMobileEngageDependencyContainer( - coreSdkHandler = coreSdkHandler, - activityLifecycleActionRegistry = mockLifecycleActionRegistry, - fileDownloader = mockFileDownloader + concurrentHandlerHolder = concurrentHandlerHolder, + activityLifecycleActionRegistry = mockLifecycleActionRegistry, + fileDownloader = mockFileDownloader ) setupMobileEngageComponent(mockDependencyContainer) @@ -65,7 +66,7 @@ class PreloadedInappHandlerCommandTest { @After fun tearDown() { - coreSdkHandler.looper.quitSafely() + concurrentHandlerHolder.looper.quitSafely() tearDownMobileEngageComponent() } @@ -89,7 +90,7 @@ class PreloadedInappHandlerCommandTest { PreloadedInappHandlerCommand(intent).run() - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder) verify(mockLifecycleActionRegistry).addTriggerOnActivityAction(any()) } @@ -116,7 +117,7 @@ class PreloadedInappHandlerCommandTest { PreloadedInappHandlerCommand(intent).run() - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder) verify(mockLifecycleActionRegistry).addTriggerOnActivityAction(any()) } @@ -139,7 +140,7 @@ class PreloadedInappHandlerCommandTest { PreloadedInappHandlerCommand(intent).run() - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder) verify(mockLifecycleActionRegistry).addTriggerOnActivityAction(any()) } @@ -166,7 +167,7 @@ class PreloadedInappHandlerCommandTest { PreloadedInappHandlerCommand(intent).run() - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder) verify(mockLifecycleActionRegistry).addTriggerOnActivityAction(any()) @@ -185,14 +186,14 @@ class PreloadedInappHandlerCommandTest { PreloadedInappHandlerCommand(intent).run() - waitForEventLoopToFinish(coreSdkHandler) + waitForEventLoopToFinish(concurrentHandlerHolder) verifyNoInteractions(mockLifecycleActionRegistry) } - private fun waitForEventLoopToFinish(handler: CoreSdkHandler) { + private fun waitForEventLoopToFinish(handlerHolder: ConcurrentHandlerHolder) { val latch = CountDownLatch(1) - handler.post { latch.countDown() } + handlerHolder.coreHandler.post { latch.countDown() } latch.await() } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerTest.kt index 517c41c47..7b66c520e 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerTest.kt @@ -13,6 +13,7 @@ import com.emarsys.mobileengage.util.RequestModelHelper import com.emarsys.testUtil.TimeoutUtils import com.emarsys.testUtil.mockito.whenever import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -50,7 +51,11 @@ class InAppCleanUpResponseHandlerTest { on { isCustomEvent(any()) } doReturn true } - handler = InAppCleanUpResponseHandler(mockDisplayedIamRepository, mockButtonClickRepository, mockRequestModelHelper) + handler = InAppCleanUpResponseHandler( + mockDisplayedIamRepository, + mockButtonClickRepository, + mockRequestModelHelper + ) FeatureRegistry.disableFeature(InnerFeature.EVENT_SERVICE_V4) } @@ -120,36 +125,47 @@ class InAppCleanUpResponseHandlerTest { fun testHandleResponse_shouldDelete_oldInApp() { val response = buildResponseModel(mockRequestModel, "{'oldCampaigns': ['123']}") handler.handleResponse(response) - verify(mockDisplayedIamRepository).remove(FilterByCampaignId("123")) + runBlocking { + verify(mockDisplayedIamRepository).remove(FilterByCampaignId("123")) + } } @Test fun testHandleResponse_shouldDelete_multiple_oldInApps() { val response = buildResponseModel(mockRequestModel) handler.handleResponse(response) - verify(mockDisplayedIamRepository).remove(FilterByCampaignId("123", "456", "78910")) + runBlocking { + verify(mockDisplayedIamRepository).remove(FilterByCampaignId("123", "456", "78910")) + } } @Test fun testHandleResponse_shouldDelete_oldButtonClick() { val response = buildResponseModel(mockRequestModel, "{'oldCampaigns': ['123']}") handler.handleResponse(response) - verify(mockButtonClickRepository).remove(FilterByCampaignId("123")) + runBlocking { + verify(mockButtonClickRepository).remove(FilterByCampaignId("123")) + } } @Test fun testHandleResponse_shouldDelete_multiple_oldButtonClicks() { val response = buildResponseModel(mockRequestModel) handler.handleResponse(response) - verify(mockButtonClickRepository).remove(FilterByCampaignId("123", "456", "78910")) + runBlocking { + verify(mockButtonClickRepository).remove(FilterByCampaignId("123", "456", "78910")) + } } - private fun buildResponseModel(requestModel: RequestModel, responseBody: String = "{'oldCampaigns': ['123', '456', '78910']}"): ResponseModel { + private fun buildResponseModel( + requestModel: RequestModel, + responseBody: String = "{'oldCampaigns': ['123', '456', '78910']}" + ): ResponseModel { return ResponseModel.Builder() - .statusCode(200) - .message("OK") - .body(responseBody) - .requestModel(requestModel) - .build() + .statusCode(200) + .message("OK") + .body(responseBody) + .requestModel(requestModel) + .build() } } \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerV4Test.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerV4Test.kt index 4665e4e8d..254712945 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerV4Test.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerV4Test.kt @@ -14,6 +14,7 @@ import com.emarsys.testUtil.TimeoutUtils import io.kotlintest.data.forall import io.kotlintest.shouldBe import io.kotlintest.tables.row +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test @@ -42,13 +43,18 @@ class InAppCleanUpResponseHandlerV4Test { mockRequestModel = mock { on { url } doReturn URL(EVENT_BASE) on { payload } doReturn mapOf( - "clicks" to listOf( - mapOf("campaignId" to "123", "buttonId" to "123", "timestamp" to "1234"), - mapOf("campaignId" to "456", "buttonId" to "456", "timestamp" to "5678")), - "viewedMessages" to listOf( - mapOf("campaignId" to "78910", "buttonId" to "123123", "timestamp" to "1233214"), - mapOf("campaignId" to "6543", "buttonId" to "234", "timestamp" to "45321") - ) + "clicks" to listOf( + mapOf("campaignId" to "123", "buttonId" to "123", "timestamp" to "1234"), + mapOf("campaignId" to "456", "buttonId" to "456", "timestamp" to "5678") + ), + "viewedMessages" to listOf( + mapOf( + "campaignId" to "78910", + "buttonId" to "123123", + "timestamp" to "1233214" + ), + mapOf("campaignId" to "6543", "buttonId" to "234", "timestamp" to "45321") + ) ) } mockDisplayedIamRepository = mock() @@ -57,7 +63,11 @@ class InAppCleanUpResponseHandlerV4Test { on { isCustomEvent(any()) } doReturn true } - handler = InAppCleanUpResponseHandlerV4(mockDisplayedIamRepository, mockButtonClickRepository, mockRequestModelHelper) + handler = InAppCleanUpResponseHandlerV4( + mockDisplayedIamRepository, + mockButtonClickRepository, + mockRequestModelHelper + ) FeatureRegistry.enableFeature(InnerFeature.EVENT_SERVICE_V4) } @@ -86,9 +96,9 @@ class InAppCleanUpResponseHandlerV4Test { @Test fun testShouldHandleResponse_whenResponseWasSuccessful() { forall( - row(buildResponseModel(mockRequestModel, statusCode = 200), true), - row(buildResponseModel(mockRequestModel, statusCode = 299), true), - row(buildResponseModel(mockRequestModel, statusCode = 400), false) + row(buildResponseModel(mockRequestModel, statusCode = 200), true), + row(buildResponseModel(mockRequestModel, statusCode = 299), true), + row(buildResponseModel(mockRequestModel, statusCode = 400), false) ) { input, expected -> val result = handler.shouldHandleResponse(input) result shouldBe expected @@ -107,11 +117,14 @@ class InAppCleanUpResponseHandlerV4Test { @Test fun testShouldHandleResponse_shouldReturnTrue_whenRequestContainsOnlyClicksOrViewedMessages() { - whenever(mockRequestModel.payload).thenReturn(mapOf( + whenever(mockRequestModel.payload).thenReturn( + mapOf( "clicks" to listOf( - mapOf("campaignId" to "123", "buttonId" to "123", "timestamp" to "1234"), - mapOf("campaignId" to "456", "buttonId" to "456", "timestamp" to "5678")), - )) + mapOf("campaignId" to "123", "buttonId" to "123", "timestamp" to "1234"), + mapOf("campaignId" to "456", "buttonId" to "456", "timestamp" to "5678") + ), + ) + ) val responseModel = buildResponseModel(mockRequestModel) @@ -136,8 +149,9 @@ class InAppCleanUpResponseHandlerV4Test { val responseModel = buildResponseModel(mockRequestModel) handler.handleResponse(responseModel) - - verify(mockButtonClickRepository).remove(FilterByCampaignId("123", "456")) + runBlocking { + verify(mockButtonClickRepository).remove(FilterByCampaignId("123", "456")) + } } @Test @@ -145,16 +159,19 @@ class InAppCleanUpResponseHandlerV4Test { val responseModel = buildResponseModel(mockRequestModel) handler.handleResponse(responseModel) - - verify(mockDisplayedIamRepository).remove(FilterByCampaignId("78910", "6543")) + runBlocking { + verify(mockDisplayedIamRepository).remove(FilterByCampaignId("78910", "6543")) + } } @Test fun testHandleResponse_shouldNotCallRepository_whenClicksOrViewedMessagesAreEmpty() { - whenever(mockRequestModel.payload).thenReturn(mapOf( + whenever(mockRequestModel.payload).thenReturn( + mapOf( "clicks" to listOf>(), "viewedMessages" to listOf() - )) + ) + ) val responseModel = buildResponseModel(mockRequestModel) handler.handleResponse(responseModel) @@ -163,12 +180,16 @@ class InAppCleanUpResponseHandlerV4Test { verifyNoInteractions(mockDisplayedIamRepository) } - private fun buildResponseModel(requestModel: RequestModel, responseBody: String = "{'oldCampaigns': ['123', '456', '78910','6543']}", statusCode: Int = 200): ResponseModel { + private fun buildResponseModel( + requestModel: RequestModel, + responseBody: String = "{'oldCampaigns': ['123', '456', '78910','6543']}", + statusCode: Int = 200 + ): ResponseModel { return ResponseModel.Builder() - .statusCode(statusCode) - .message("OK") - .body(responseBody) - .requestModel(requestModel) - .build() + .statusCode(statusCode) + .message("OK") + .body(responseBody) + .requestModel(requestModel) + .build() } } \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/OnEventActionResponseHandlerTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/OnEventActionResponseHandlerTest.kt index e84b11b11..507d42789 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/OnEventActionResponseHandlerTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/responsehandler/OnEventActionResponseHandlerTest.kt @@ -1,10 +1,11 @@ package com.emarsys.mobileengage.responsehandler +import android.os.Handler import android.os.Looper -import com.emarsys.core.concurrency.CoreSdkHandlerProvider +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.model.RequestModel @@ -17,6 +18,7 @@ import com.emarsys.testUtil.TimeoutUtils import com.emarsys.testUtil.mockito.anyNotNull import com.emarsys.testUtil.mockito.whenever import io.kotlintest.shouldBe +import kotlinx.coroutines.runBlocking import org.json.JSONObject import org.junit.After import org.junit.Before @@ -33,7 +35,7 @@ class OnEventActionResponseHandlerTest { private lateinit var mockRepository: Repository private lateinit var mockEventServiceInternal: EventServiceInternal private lateinit var mockTimestampProvider: TimestampProvider - private lateinit var coreSdkHandler: CoreSdkHandler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -41,6 +43,7 @@ class OnEventActionResponseHandlerTest { @Before fun setUp() { + val uiHandler = Handler(Looper.getMainLooper()) mockAppEventCommand = mock() mockActionCommandFactory = mock() mockRepository = mock() @@ -48,14 +51,20 @@ class OnEventActionResponseHandlerTest { mockTimestampProvider = mock { on { provideTimestamp() } doReturn 1L } - coreSdkHandler = CoreSdkHandlerProvider().provideHandler() - responseHandler = OnEventActionResponseHandler(mockActionCommandFactory, mockRepository, mockEventServiceInternal, mockTimestampProvider, coreSdkHandler) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + responseHandler = OnEventActionResponseHandler( + mockActionCommandFactory, + mockRepository, + mockEventServiceInternal, + mockTimestampProvider, + concurrentHandlerHolder + ) } @After fun tearDown() { try { - val looper: Looper? = coreSdkHandler.looper + val looper: Looper? = concurrentHandlerHolder.looper looper?.quitSafely() } catch (e: Exception) { e.printStackTrace() @@ -66,12 +75,14 @@ class OnEventActionResponseHandlerTest { @Test fun testShouldHandlerResponse_whenResponseBodyIsEmpty() { val responseModel: ResponseModel = ResponseModel.Builder() - .statusCode(200) - .message("OK") - .requestModel(RequestModel.Builder(TimestampProvider(), UUIDProvider()) - .url("https://emarsys.com") - .build()) - .build() + .statusCode(200) + .message("OK") + .requestModel( + RequestModel.Builder(TimestampProvider(), UUIDProvider()) + .url("https://emarsys.com") + .build() + ) + .build() responseHandler.shouldHandleResponse(responseModel) shouldBe false } @@ -85,27 +96,31 @@ class OnEventActionResponseHandlerTest { @Test fun testShouldHandleResponse_shouldReturnTrue_whenResponseHasOnEventActionWithActions() { - val responseModel = createResponseModel(""" + val responseModel = createResponseModel( + """ { "onEventAction": { "campaignId": "1234", "actions": [] } } - """.trimIndent()) + """.trimIndent() + ) responseHandler.shouldHandleResponse(responseModel) shouldBe true } @Test fun testShouldHandleResponse_shouldReturnFalse_whenResponseHasNoActions() { - val responseModel = createResponseModel(""" + val responseModel = createResponseModel( + """ { "onEventAction": { "campaignId": "123" } } - """.trimIndent()) + """.trimIndent() + ) responseHandler.shouldHandleResponse(responseModel) shouldBe false } @@ -131,7 +146,9 @@ class OnEventActionResponseHandlerTest { @Test fun testHandleResponse_shouldRunActionCommand() { - whenever(mockActionCommandFactory.createActionCommand(anyNotNull())).thenReturn(mockAppEventCommand) + whenever(mockActionCommandFactory.createActionCommand(anyNotNull())).thenReturn( + mockAppEventCommand + ) val responseModel = createTestResponseModel() @@ -149,7 +166,9 @@ class OnEventActionResponseHandlerTest { responseHandler.handleResponse(responseModel) - verify(mockRepository, timeout(1000)).add(captor.capture()) + runBlocking { + verify(mockRepository, timeout(1000)).add(captor.capture()) + } captor.firstValue.campaignId shouldBe iamToSave.campaignId captor.firstValue.timestamp shouldBe iamToSave.timestamp @@ -161,38 +180,50 @@ class OnEventActionResponseHandlerTest { responseHandler.handleResponse(responseModel) - verify(mockEventServiceInternal, timeout(1000)).trackInternalCustomEventAsync(eq("inapp:viewed"), eq(mapOf("campaignId" to "1234")), anyOrNull()) + verify( + mockEventServiceInternal, + timeout(1000) + ).trackInternalCustomEventAsync( + eq("inapp:viewed"), + eq(mapOf("campaignId" to "1234")), + anyOrNull() + ) } @Test fun testHandleResponse_shouldNotCrashWhenNoCampaignIdIsNotPresentInResponse() { - val responseModel = createResponseModel(""" + val responseModel = createResponseModel( + """ { "onEventAction": { "campaignId": "123" } } - """.trimIndent()) + """.trimIndent() + ) responseHandler.handleResponse(responseModel) } private fun createResponseModel(body: String): ResponseModel { return ResponseModel.Builder() - .statusCode(200) - .message("OK") - .body(body) - .requestModel(RequestModel.Builder(TimestampProvider(), UUIDProvider()) - .url("https://emarsys.com") - .build()) - .build() + .statusCode(200) + .message("OK") + .body(body) + .requestModel( + RequestModel.Builder(TimestampProvider(), UUIDProvider()) + .url("https://emarsys.com") + .build() + ) + .build() } private fun createTestResponseModel(): ResponseModel { return ResponseModel.Builder() - .statusCode(200) - .message("OK") - .body(""" + .statusCode(200) + .message("OK") + .body( + """ { "onEventAction":{ "campaignId":"1234", @@ -209,10 +240,12 @@ class OnEventActionResponseHandlerTest { } } """.trimIndent() - ) - .requestModel(RequestModel.Builder(TimestampProvider(), UUIDProvider()) - .url("https://emarsys.com") - .build()) - .build() + ) + .requestModel( + RequestModel.Builder(TimestampProvider(), UUIDProvider()) + .url("https://emarsys.com") + .build() + ) + .build() } } \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/MessagingServiceUtilsTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/MessagingServiceUtilsTest.kt index aba713d2b..312a4148d 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/MessagingServiceUtilsTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/MessagingServiceUtilsTest.kt @@ -123,7 +123,7 @@ class MessagingServiceUtilsTest { @After fun tearDown() { - mobileEngage().coreSdkHandler.looper.quitSafely() + mobileEngage().concurrentHandlerHolder.looper.quitSafely() tearDownMobileEngageComponent() } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/NotificationActionUtilsTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/NotificationActionUtilsTest.kt index ee75fcfb5..3fc069b0f 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/NotificationActionUtilsTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/NotificationActionUtilsTest.kt @@ -37,7 +37,7 @@ class NotificationActionUtilsTest { @After fun tearDown() { - mobileEngage().coreSdkHandler.looper.quitSafely() + mobileEngage().concurrentHandlerHolder.looper.quitSafely() tearDownMobileEngageComponent() } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/RemoteMessageMapperTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/RemoteMessageMapperTest.kt index ac7c9c1f2..f87d4bfbd 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/RemoteMessageMapperTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/service/RemoteMessageMapperTest.kt @@ -117,7 +117,7 @@ class RemoteMessageMapperTest { @After fun tearDown() { - mobileEngage().coreSdkHandler.looper.quitSafely() + mobileEngage().concurrentHandlerHolder.looper.quitSafely() tearDownMobileEngageComponent() } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/util/AsyncSDKUtils.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/util/AsyncSDKUtils.kt index f87eb085a..5646b8ce7 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/util/AsyncSDKUtils.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/util/AsyncSDKUtils.kt @@ -6,9 +6,9 @@ import java.util.concurrent.CountDownLatch fun waitForTask() { val latch = CountDownLatch(2) - mobileEngage().coreSdkHandler.post { + mobileEngage().concurrentHandlerHolder.coreHandler.post { latch.countDown() - mobileEngage().coreSdkHandler.post { + mobileEngage().concurrentHandlerHolder.coreHandler.post { latch.countDown() } } diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/deeplink/DeepLinkAction.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/deeplink/DeepLinkAction.kt index 77000d7e4..dbed43ef1 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/deeplink/DeepLinkAction.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/deeplink/DeepLinkAction.kt @@ -14,7 +14,8 @@ class DeepLinkAction(private val deepLinkInternal: DeepLinkInternal, override fun execute(activity: Activity?) { if (activity != null && activity.intent != null) { - deepLinkInternal.proxyApi(mobileEngage().coreSdkHandler).trackDeepLinkOpen(activity, activity.intent, null) + deepLinkInternal.proxyApi(mobileEngage().concurrentHandlerHolder) + .trackDeepLinkOpen(activity, activity.intent, null) } } } \ No newline at end of file diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/device/DeviceInfoStartAction.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/device/DeviceInfoStartAction.kt index f0349bb3f..5b57ddca1 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/device/DeviceInfoStartAction.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/device/DeviceInfoStartAction.kt @@ -19,8 +19,8 @@ class DeviceInfoStartAction(private val clientInternal: ClientServiceInternal, ) : ActivityLifecycleAction { override fun execute(activity: Activity?) { - val coreSdkHandler = mobileEngage().coreSdkHandler - coreSdkHandler.post { + val coreSdkHandler = mobileEngage().concurrentHandlerHolder + coreSdkHandler.coreHandler.post { if (deviceInfoPayloadStorage.get() == null || deviceInfoPayloadStorage.get() != deviceInfo.deviceInfoPayload) { clientInternal.proxyApi(coreSdkHandler).trackDeviceInfo(null) } diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternal.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternal.kt index 591d507bd..f4b3b4e32 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternal.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternal.kt @@ -12,7 +12,7 @@ import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.Mockable import com.emarsys.core.api.MissingPermissionException import com.emarsys.core.api.result.CompletionListener -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.permission.PermissionChecker import com.emarsys.core.request.RequestManager import com.emarsys.core.response.ResponseModel @@ -48,7 +48,7 @@ class DefaultGeofenceInternal( private val geofenceCacheableEventHandler: CacheableEventHandler, private val geofenceEnabledStorage: Storage, private val geofencePendingIntentProvider: GeofencePendingIntentProvider, - coreSdkHandler: CoreSdkHandler, + concurrentHandlerHolder: ConcurrentHandlerHolder, private val uiHandler: Handler, private val initialEnterTriggerEnabledStorage: Storage ) : GeofenceInternal { @@ -58,7 +58,7 @@ class DefaultGeofenceInternal( const val MAX_WAIT_TIME: Long = 60_000 } - private val geofenceBroadcastReceiver = GeofenceBroadcastReceiver(coreSdkHandler) + private val geofenceBroadcastReceiver = GeofenceBroadcastReceiver(concurrentHandlerHolder) private var geofenceResponse: GeofenceResponse? = null private var nearestGeofences: MutableList = mutableListOf() override val registeredGeofences: List diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/FetchGeofencesAction.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/FetchGeofencesAction.kt index 6a3a185bf..1583fe614 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/FetchGeofencesAction.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/FetchGeofencesAction.kt @@ -13,6 +13,6 @@ class FetchGeofencesAction(private val geofenceInternal: GeofenceInternal, ) : ActivityLifecycleAction { override fun execute(activity: Activity?) { - geofenceInternal.proxyApi(mobileEngage().coreSdkHandler).fetchGeofences(null) + geofenceInternal.proxyApi(mobileEngage().concurrentHandlerHolder).fetchGeofences(null) } } \ No newline at end of file diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/GeofenceBroadcastReceiver.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/GeofenceBroadcastReceiver.kt index c0c980a6c..476fb65f7 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/GeofenceBroadcastReceiver.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/GeofenceBroadcastReceiver.kt @@ -4,7 +4,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import com.emarsys.core.Mockable -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.util.SystemUtils import com.emarsys.core.util.log.Logger import com.emarsys.core.util.log.entry.StatusLog @@ -14,15 +14,17 @@ import com.emarsys.mobileengage.geofence.model.TriggeringEmarsysGeofence import com.google.android.gms.location.GeofencingEvent @Mockable -class GeofenceBroadcastReceiver(val coreSdkHandler: CoreSdkHandler) : BroadcastReceiver() { +class GeofenceBroadcastReceiver(val concurrentHandlerHolder: ConcurrentHandlerHolder) : + BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val geofencingEvent = GeofencingEvent.fromIntent(intent) if (geofencingEvent.triggeringGeofences != null) { - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { val geofenceInternal = mobileEngage().geofenceInternal - val triggeringEmarsysGeofences = geofencingEvent.convertToTriggeringEmarsysGeofences() + val triggeringEmarsysGeofences = + geofencingEvent.convertToTriggeringEmarsysGeofences() geofenceInternal.onGeofenceTriggered(triggeringEmarsysGeofences) logTriggeringGeofences(triggeringEmarsysGeofences) diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/AppStartAction.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/AppStartAction.kt index d8eb55498..7a29a3725 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/AppStartAction.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/AppStartAction.kt @@ -17,10 +17,11 @@ class AppStartAction(private val eventServiceInternal: EventServiceInternal, pri ) : ActivityLifecycleAction { override fun execute(activity: Activity?) { - val coreSdkHandler = mobileEngage().coreSdkHandler - coreSdkHandler.post { + val coreSdkHandler = mobileEngage().concurrentHandlerHolder + coreSdkHandler.coreHandler.post { if (contactTokenStorage.get() != null) { - eventServiceInternal.proxyApi(coreSdkHandler).trackInternalCustomEventAsync("app:start", null, null) + eventServiceInternal.proxyApi(coreSdkHandler) + .trackInternalCustomEventAsync("app:start", null, null) } info(AppEventLog("app:start", null)) } diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/OverlayInAppPresenter.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/OverlayInAppPresenter.kt index bb0bba29b..3b46b43f3 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/OverlayInAppPresenter.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/OverlayInAppPresenter.kt @@ -6,7 +6,7 @@ import androidx.fragment.app.FragmentActivity import com.emarsys.core.Mockable import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.util.log.entry.InAppLoadingTime @@ -28,25 +28,31 @@ import org.json.JSONObject @Mockable class OverlayInAppPresenter( - private val coreSdkHandler: CoreSdkHandler, - private val uiHandler: Handler, - private val webViewProvider: IamStaticWebViewProvider, - private val inAppInternal: InAppInternal, - private val dialogProvider: IamDialogProvider, - private val buttonClickedRepository: Repository, - private val displayedIamRepository: Repository, - private val timestampProvider: TimestampProvider, - private val currentActivityProvider: CurrentActivityProvider, - private val jsBridgeFactory: IamJsBridgeFactory) { + private val concurrentHandlerHolder: ConcurrentHandlerHolder, + private val uiHandler: Handler, + private val webViewProvider: IamStaticWebViewProvider, + private val inAppInternal: InAppInternal, + private val dialogProvider: IamDialogProvider, + private val buttonClickedRepository: Repository, + private val displayedIamRepository: Repository, + private val timestampProvider: TimestampProvider, + private val currentActivityProvider: CurrentActivityProvider, + private val jsBridgeFactory: IamJsBridgeFactory +) { - fun present(campaignId: String, sid: String?, url: String?, requestId: String?, startTimestamp: Long, - html: String, messageLoadedListener: MessageLoadedListener?) { + fun present( + campaignId: String, sid: String?, url: String?, requestId: String?, startTimestamp: Long, + html: String, messageLoadedListener: MessageLoadedListener? + ) { val iamDialog = dialogProvider.provideDialog(campaignId, sid, url, requestId) setupDialogWithActions(iamDialog) - val jsCommandFactory = JSCommandFactory(currentActivityProvider, uiHandler, coreSdkHandler, inAppInternal, - buttonClickedRepository, onCloseTriggered(), onAppEventTriggered(), timestampProvider) + val jsCommandFactory = JSCommandFactory( + currentActivityProvider, uiHandler, concurrentHandlerHolder, inAppInternal, + buttonClickedRepository, onCloseTriggered(), onAppEventTriggered(), timestampProvider + ) - val jsBridge = jsBridgeFactory.createJsBridge(jsCommandFactory, InAppMessage(campaignId, sid, url)) + val jsBridge = + jsBridgeFactory.createJsBridge(jsCommandFactory, InAppMessage(campaignId, sid, url)) webViewProvider.loadMessageAsync(html, jsBridge) { val currentActivity = currentActivityProvider.get() @@ -91,12 +97,14 @@ class OverlayInAppPresenter( private fun setupDialogWithActions(iamDialog: IamDialog) { val saveDisplayedIamAction: OnDialogShownAction = SaveDisplayedIamAction( - coreSdkHandler, - displayedIamRepository, - timestampProvider) + concurrentHandlerHolder, + displayedIamRepository, + timestampProvider + ) val sendDisplayedIamAction: OnDialogShownAction = SendDisplayedIamAction( - coreSdkHandler, - inAppInternal) + concurrentHandlerHolder, + inAppInternal + ) iamDialog.setActions(listOf(saveDisplayedIamAction, sendDisplayedIamAction)) } diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SaveDisplayedIamAction.java b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SaveDisplayedIamAction.java deleted file mode 100644 index 1363e57d9..000000000 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SaveDisplayedIamAction.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.emarsys.mobileengage.iam.dialog.action; - -import com.emarsys.core.database.repository.Repository; -import com.emarsys.core.database.repository.SqlSpecification; -import com.emarsys.core.handler.CoreSdkHandler; -import com.emarsys.core.provider.timestamp.TimestampProvider; -import com.emarsys.core.util.Assert; -import com.emarsys.mobileengage.iam.model.displayediam.DisplayedIam; - -public class SaveDisplayedIamAction implements OnDialogShownAction { - - CoreSdkHandler handler; - Repository repository; - TimestampProvider timestampProvider; - - public SaveDisplayedIamAction(CoreSdkHandler handler, Repository repository, TimestampProvider timestampProvider) { - Assert.notNull(handler, "Handler must not be null!"); - Assert.notNull(repository, "Repository must not be null!"); - Assert.notNull(timestampProvider, "TimestampProvider must not be null!"); - this.handler = handler; - this.repository = repository; - this.timestampProvider = timestampProvider; - } - - @Override - public void execute(final String campaignId, String sid, String url) { - Assert.notNull(campaignId, "CampaignId must not be null!"); - handler.post(new Runnable() { - @Override - public void run() { - DisplayedIam iam = new DisplayedIam(campaignId, timestampProvider.provideTimestamp()); - repository.add(iam); - } - }); - } -} diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SaveDisplayedIamAction.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SaveDisplayedIamAction.kt new file mode 100644 index 000000000..c162bd7bd --- /dev/null +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SaveDisplayedIamAction.kt @@ -0,0 +1,24 @@ +package com.emarsys.mobileengage.iam.dialog.action + +import com.emarsys.core.database.repository.Repository +import com.emarsys.core.database.repository.SqlSpecification +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.core.provider.timestamp.TimestampProvider +import com.emarsys.mobileengage.iam.model.displayediam.DisplayedIam +import kotlinx.coroutines.runBlocking + +class SaveDisplayedIamAction( + var concurrentHandlerHolder: ConcurrentHandlerHolder, + var repository: Repository, + var timestampProvider: TimestampProvider +) : OnDialogShownAction { + + override fun execute(campaignId: String, sid: String?, url: String?) { + concurrentHandlerHolder.coreHandler.post { + runBlocking { + val iam = DisplayedIam(campaignId, timestampProvider.provideTimestamp()) + repository.add(iam) + } + } + } +} \ No newline at end of file diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamAction.java b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamAction.java index d9e6d3e07..e6e53e87a 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamAction.java +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamAction.java @@ -1,6 +1,6 @@ package com.emarsys.mobileengage.iam.dialog.action; -import com.emarsys.core.handler.CoreSdkHandler; +import com.emarsys.core.handler.ConcurrentHandlerHolder; import com.emarsys.core.util.Assert; import com.emarsys.mobileengage.event.EventServiceInternal; @@ -9,22 +9,22 @@ public class SendDisplayedIamAction implements OnDialogShownAction { - private final CoreSdkHandler handler; + private final ConcurrentHandlerHolder concurrentHandlerHolder; private final EventServiceInternal eventServiceInternal; public SendDisplayedIamAction( - CoreSdkHandler handler, + ConcurrentHandlerHolder handler, EventServiceInternal eventServiceInternal) { Assert.notNull(handler, "Handler must not be null!"); Assert.notNull(eventServiceInternal, "EventServiceInternal must not be null!"); - this.handler = handler; + this.concurrentHandlerHolder = handler; this.eventServiceInternal = eventServiceInternal; } @Override public void execute(final String campaignId, final String sid, final String url) { Assert.notNull(campaignId, "CampaignId must not be null!"); - handler.post(new Runnable() { + concurrentHandlerHolder.getCoreHandler().post(new Runnable() { @Override public void run() { Map attributes = new HashMap<>(); diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactory.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactory.kt index eb73ee5e3..b5b850816 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactory.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactory.kt @@ -6,24 +6,25 @@ import android.os.Handler import com.emarsys.core.Mockable import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.mobileengage.iam.InAppInternal import com.emarsys.mobileengage.iam.model.InAppMessage import com.emarsys.mobileengage.iam.model.buttonclicked.ButtonClicked +import kotlinx.coroutines.runBlocking import java.util.concurrent.CountDownLatch @Mockable class JSCommandFactory( - private val currentActivityProvider: CurrentActivityProvider, - private val uiHandler: Handler, - private val coreSdkHandler: CoreSdkHandler, - private val inAppInternal: InAppInternal, - private val buttonClickedRepository: Repository, - private val onCloseTriggered: OnCloseListener?, - private val onAppEventTriggered: OnAppEventListener?, - private val timestampProvider: TimestampProvider + private val currentActivityProvider: CurrentActivityProvider, + private val uiHandler: Handler, + private val concurrentHandlerHolder: ConcurrentHandlerHolder, + private val inAppInternal: InAppInternal, + private val buttonClickedRepository: Repository, + private val onCloseTriggered: OnCloseListener?, + private val onAppEventTriggered: OnAppEventListener?, + private val timestampProvider: TimestampProvider ) { @Throws(RuntimeException::class) @@ -47,14 +48,16 @@ class JSCommandFactory( CommandType.ON_BUTTON_CLICKED -> { { property, _ -> if (inAppMessage != null && property != null) { - coreSdkHandler.post { - buttonClickedRepository.add( - ButtonClicked( - inAppMessage.campaignId, - property, - timestampProvider.provideTimestamp() + concurrentHandlerHolder.coreHandler.post { + runBlocking { + buttonClickedRepository.add( + ButtonClicked( + inAppMessage.campaignId, + property, + timestampProvider.provideTimestamp() + ) ) - ) + } val eventName = "inapp:click" val attributes: MutableMap = mutableMapOf( "campaignId" to inAppMessage.campaignId, @@ -102,9 +105,10 @@ class JSCommandFactory( } CommandType.ON_ME_EVENT -> { { property, json -> - coreSdkHandler.post { + concurrentHandlerHolder.coreHandler.post { val payload = json.optJSONObject("payload") - val attributes = payload?.keys()?.asSequence()?.associateBy({ it }) { payload.getString(it) } + val attributes = payload?.keys()?.asSequence() + ?.associateBy({ it }) { payload.getString(it) } inAppInternal.trackCustomEventAsync(property, attributes, null) } } diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepository.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepository.kt index 286f0c496..5ff5c372f 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepository.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepository.kt @@ -6,9 +6,17 @@ import com.emarsys.core.Mockable import com.emarsys.core.database.DatabaseContract import com.emarsys.core.database.helper.DbHelper import com.emarsys.core.database.repository.AbstractSqliteRepository +import com.emarsys.core.handler.ConcurrentHandlerHolder @Mockable -class ButtonClickedRepository(dbHelper: DbHelper) : AbstractSqliteRepository(DatabaseContract.BUTTON_CLICKED_TABLE_NAME, dbHelper) { +class ButtonClickedRepository( + dbHelper: DbHelper, + concurrentHandlerHolder: ConcurrentHandlerHolder +) : AbstractSqliteRepository( + DatabaseContract.BUTTON_CLICKED_TABLE_NAME, + dbHelper, + concurrentHandlerHolder +) { override fun contentValuesFromItem(item: ButtonClicked): ContentValues { val contentValues = ContentValues() contentValues.put(DatabaseContract.BUTTON_CLICKED_COLUMN_NAME_CAMPAIGN_ID, item.campaignId) @@ -18,7 +26,8 @@ class ButtonClickedRepository(dbHelper: DbHelper) : AbstractSqliteRepository { - public DisplayedIamRepository(DbHelper dbHelper) { - super(DatabaseContract.DISPLAYED_IAM_TABLE_NAME, dbHelper); + public DisplayedIamRepository(DbHelper dbHelper, ConcurrentHandlerHolder concurrentHandlerHolder) { + super(DatabaseContract.DISPLAYED_IAM_TABLE_NAME, dbHelper, concurrentHandlerHolder); } @Override diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxy.java b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxy.java index 950fbbe65..e813594e8 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxy.java +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxy.java @@ -1,5 +1,8 @@ package com.emarsys.mobileengage.iam.model.requestRepositoryProxy; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.emarsys.core.database.repository.Repository; import com.emarsys.core.database.repository.SqlSpecification; import com.emarsys.core.database.repository.specification.Everything; @@ -16,12 +19,13 @@ import com.emarsys.mobileengage.util.RequestModelHelper; import com.emarsys.mobileengage.util.RequestPayloadUtils; -import org.jetbrains.annotations.NotNull; - import java.util.ArrayList; import java.util.List; import java.util.Map; +import kotlin.Unit; +import kotlin.coroutines.Continuation; + public class RequestRepositoryProxy implements Repository { @@ -62,16 +66,26 @@ public RequestRepositoryProxy( this.requestModelHelper = requestModelHelper; } + @Nullable + @Override + public Object update(RequestModel item, @NonNull SqlSpecification specification, @NonNull Continuation $completion) { + throw new UnsupportedOperationException("update method is not supported in RequestRepositoryProxy"); + } + + @Nullable @Override - public void add(RequestModel item) { + public Object add(RequestModel item, @NonNull Continuation $completion) { + Object result = null; if (!(item instanceof CompositeRequestModel)) { - requestRepository.add(item); + result = requestRepository.add(item, $completion); } + return result; } + @Nullable @Override - public void remove(SqlSpecification specification) { - requestRepository.remove(specification); + public Object remove(SqlSpecification specification, @NonNull Continuation $completion) { + return requestRepository.remove(specification, $completion); } @Override @@ -148,9 +162,4 @@ private String[] collectRequestIds(List models) { return result; } - - @Override - public int update(RequestModel item, @NotNull SqlSpecification specification) { - throw new UnsupportedOperationException("update method is not supported in RequestRepositoryProxy"); - } } diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/inbox/DefaultMessageInboxInternal.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/inbox/DefaultMessageInboxInternal.kt index f9d088c53..facbd3fcd 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/inbox/DefaultMessageInboxInternal.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/inbox/DefaultMessageInboxInternal.kt @@ -5,15 +5,15 @@ 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.handler.ConcurrentHandlerHolder import com.emarsys.core.request.RequestManager import com.emarsys.core.response.ResponseModel import com.emarsys.mobileengage.api.inbox.InboxResult import com.emarsys.mobileengage.request.MobileEngageRequestModelFactory -import kotlinx.coroutines.CoroutineScope import java.util.* class DefaultMessageInboxInternal( - private val uiScope: CoroutineScope, + private val concurrentHandlerHolder: ConcurrentHandlerHolder, private val requestManager: RequestManager, private val mobileEngageRequestModelFactory: MobileEngageRequestModelFactory, private val messageInboxResponseMapper: MessageInboxResponseMapper @@ -25,20 +25,30 @@ class DefaultMessageInboxInternal( override fun addTag(tag: String, messageId: String, completionListener: CompletionListener?) { val eventAttributes = mapOf( - "messageId" to messageId, - "tag" to tag.lowercase(Locale.ENGLISH) + "messageId" to messageId, + "tag" to tag.lowercase(Locale.ENGLISH) + ) + val requestModel = mobileEngageRequestModelFactory.createInternalCustomEventRequest( + "inbox:tag:add", + eventAttributes ) - val requestModel = mobileEngageRequestModelFactory.createInternalCustomEventRequest("inbox:tag:add", eventAttributes) requestManager.submit(requestModel, completionListener) } - override fun removeTag(tag: String, messageId: String, completionListener: CompletionListener?) { + override fun removeTag( + tag: String, + messageId: String, + completionListener: CompletionListener? + ) { val eventAttributes = mapOf( - "messageId" to messageId, - "tag" to tag.lowercase(Locale.ENGLISH) + "messageId" to messageId, + "tag" to tag.lowercase(Locale.ENGLISH) + ) + val requestModel = mobileEngageRequestModelFactory.createInternalCustomEventRequest( + "inbox:tag:remove", + eventAttributes ) - val requestModel = mobileEngageRequestModelFactory.createInternalCustomEventRequest("inbox:tag:remove", eventAttributes) requestManager.submit(requestModel, completionListener) } @@ -66,6 +76,6 @@ class DefaultMessageInboxInternal( override fun onError(id: String, cause: Exception) { resultListener.onResult(Try.failure(cause)) } - }, scope = uiScope) + }, scope = concurrentHandlerHolder.uiScope) } } \ No newline at end of file diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandler.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandler.kt index 3956d5b2c..a5fefdcee 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandler.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandler.kt @@ -10,11 +10,13 @@ import com.emarsys.mobileengage.iam.model.buttonclicked.ButtonClicked import com.emarsys.mobileengage.iam.model.displayediam.DisplayedIam import com.emarsys.mobileengage.iam.model.specification.FilterByCampaignId import com.emarsys.mobileengage.util.RequestModelHelper +import kotlinx.coroutines.runBlocking class InAppCleanUpResponseHandler( - private val displayedIamRepository: Repository, - private val buttonClickedRepository: Repository, - private val requestModelHelper: RequestModelHelper) : AbstractResponseHandler() { + private val displayedIamRepository: Repository, + private val buttonClickedRepository: Repository, + private val requestModelHelper: RequestModelHelper +) : AbstractResponseHandler() { companion object { private const val OLD_MESSAGES = "oldCampaigns" @@ -46,8 +48,10 @@ class InAppCleanUpResponseHandler( for (i in 0 until oldMessages.length()) { ids[i] = oldMessages.optString(i) } - displayedIamRepository.remove(FilterByCampaignId(*ids)) - buttonClickedRepository.remove(FilterByCampaignId(*ids)) + runBlocking { + displayedIamRepository.remove(FilterByCampaignId(*ids)) + buttonClickedRepository.remove(FilterByCampaignId(*ids)) + } } } } \ No newline at end of file diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerV4.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerV4.kt index 36cae80d1..9c8cca023 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerV4.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/InAppCleanUpResponseHandlerV4.kt @@ -11,6 +11,7 @@ import com.emarsys.mobileengage.iam.model.buttonclicked.ButtonClicked import com.emarsys.mobileengage.iam.model.displayediam.DisplayedIam import com.emarsys.mobileengage.iam.model.specification.FilterByCampaignId import com.emarsys.mobileengage.util.RequestModelHelper +import kotlinx.coroutines.runBlocking class InAppCleanUpResponseHandlerV4( private val displayedIamRepository: Repository, @@ -29,15 +30,21 @@ class InAppCleanUpResponseHandlerV4( override fun handleResponse(responseModel: ResponseModel) { val payload = responseModel.requestModel.payload if (payload != null && payload.containsKey("clicks")) { - val campaignIdsToRemove: Array = collectCampaignIds(payload["clicks"] as List>) + val campaignIdsToRemove: Array = + collectCampaignIds(payload["clicks"] as List>) if (campaignIdsToRemove.isNotEmpty()) { - buttonClickedRepository.remove(FilterByCampaignId(*campaignIdsToRemove)) + runBlocking { + buttonClickedRepository.remove(FilterByCampaignId(*campaignIdsToRemove)) + } } } if (payload != null && payload.containsKey("viewedMessages")) { - val campaignIdsToRemove: Array = collectCampaignIds(payload["viewedMessages"] as List>) + val campaignIdsToRemove: Array = + collectCampaignIds(payload["viewedMessages"] as List>) if (campaignIdsToRemove.isNotEmpty()) { - displayedIamRepository.remove(FilterByCampaignId(*campaignIdsToRemove)) + runBlocking { + displayedIamRepository.remove(FilterByCampaignId(*campaignIdsToRemove)) + } } } } diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/OnEventActionResponseHandler.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/OnEventActionResponseHandler.kt index 5c0caef45..d53c5d8cb 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/OnEventActionResponseHandler.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/OnEventActionResponseHandler.kt @@ -2,7 +2,7 @@ package com.emarsys.mobileengage.responsehandler import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification -import com.emarsys.core.handler.CoreSdkHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.response.AbstractResponseHandler import com.emarsys.core.response.ResponseModel @@ -17,17 +17,20 @@ import com.emarsys.mobileengage.notification.ActionCommandFactory import org.json.JSONException import org.json.JSONObject -class OnEventActionResponseHandler(private val actionCommandFactory: ActionCommandFactory, - private val repository: Repository, - private val eventServiceInternal: EventServiceInternal, - private val timestampProvider: TimestampProvider, - private val coreSdkHandler: CoreSdkHandler) : AbstractResponseHandler() { +class OnEventActionResponseHandler( + private val actionCommandFactory: ActionCommandFactory, + private val repository: Repository, + private val eventServiceInternal: EventServiceInternal, + private val timestampProvider: TimestampProvider, + private val concurrentHandlerHolder: ConcurrentHandlerHolder +) : AbstractResponseHandler() { override fun shouldHandleResponse(responseModel: ResponseModel): Boolean { var shouldHandle = false try { - val onEventAction: JSONObject? = responseModel.parsedBody?.getJSONObject("onEventAction") + val onEventAction: JSONObject? = + responseModel.parsedBody?.getJSONObject("onEventAction") shouldHandle = onEventAction?.has("actions") ?: false } catch (ignored: JSONException) { } @@ -46,8 +49,15 @@ class OnEventActionResponseHandler(private val actionCommandFactory: ActionComma it?.run() } - SaveDisplayedIamAction(coreSdkHandler, repository, timestampProvider).execute(campaignId, null, null) - SendDisplayedIamAction(coreSdkHandler, eventServiceInternal).execute(campaignId, null, null) + SaveDisplayedIamAction(concurrentHandlerHolder, repository, timestampProvider).execute( + campaignId, + null, + null + ) + SendDisplayedIamAction( + concurrentHandlerHolder, + eventServiceInternal + ).execute(campaignId, null, null) } catch (exception: JSONException) { Logger.error(CrashLog(exception)) diff --git a/mobile-engage/src/main/java/com/emarsys/mobileengage/service/NotificationActionUtils.kt b/mobile-engage/src/main/java/com/emarsys/mobileengage/service/NotificationActionUtils.kt index 48d08725a..978352881 100644 --- a/mobile-engage/src/main/java/com/emarsys/mobileengage/service/NotificationActionUtils.kt +++ b/mobile-engage/src/main/java/com/emarsys/mobileengage/service/NotificationActionUtils.kt @@ -12,7 +12,7 @@ import org.json.JSONObject object NotificationActionUtils { @JvmStatic fun handleAction(intent: Intent, commandFactory: NotificationCommandFactory) { - mobileEngage().coreSdkHandler.post { + mobileEngage().concurrentHandlerHolder.coreHandler.post { val command = commandFactory.createNotificationCommand(intent) command.run() } diff --git a/predict/src/androidTest/java/com/emarsys/predict/fake/FakeRestClient.java b/predict/src/androidTest/java/com/emarsys/predict/fake/FakeRestClient.java index ff39bfa43..ab8b46e14 100644 --- a/predict/src/androidTest/java/com/emarsys/predict/fake/FakeRestClient.java +++ b/predict/src/androidTest/java/com/emarsys/predict/fake/FakeRestClient.java @@ -1,10 +1,12 @@ package com.emarsys.predict.fake; +import static org.mockito.Mockito.mock; + import android.os.Handler; import android.os.Looper; import com.emarsys.core.CoreCompletionHandler; -import com.emarsys.core.concurrency.CoreSdkHandlerProvider; +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; import com.emarsys.core.connection.ConnectionProvider; import com.emarsys.core.provider.timestamp.TimestampProvider; import com.emarsys.core.request.RestClient; @@ -16,8 +18,6 @@ import java.util.Collections; import java.util.List; -import static org.mockito.Mockito.mock; - public class FakeRestClient extends RestClient { private Mode mode; @@ -33,7 +33,7 @@ public FakeRestClient(ResponseModel returnValue, Mode mode) { @SuppressWarnings("unchecked") public FakeRestClient(List responses, Mode mode) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), - mock(List.class), new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); + mock(List.class), new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); this.responses = new ArrayList<>(responses); this.mode = mode; } @@ -45,7 +45,7 @@ public FakeRestClient(Exception exception) { @SuppressWarnings("unchecked") public FakeRestClient(List exceptions) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), - mock(List.class), new Handler(Looper.getMainLooper()), new CoreSdkHandlerProvider().provideHandler()); + mock(List.class), new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); this.exceptions = new ArrayList<>(exceptions); this.mode = Mode.ERROR_EXCEPTION; } From fea1a919a196e61a478643653ae0e93b86c1929f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hunyady=20Mih=C3=A1ly?= Date: Thu, 20 Jan 2022 13:57:12 +0100 Subject: [PATCH 06/23] feat(handler): remove uiHandler usages from production code SUITEDEV-29941 Co-authored-by: davidSchuppa <32750715+davidSchuppa@users.noreply.github.com> Co-authored-by: LasOri <24588073+LasOri@users.noreply.github.com> Co-authored-by: LordAndras <49073629+LordAndras@users.noreply.github.com> Co-authored-by: eslesnjakovic <59649001+eslesnjakovic@users.noreply.github.com> --- .../ActivityLifecycleActionRegistryTest.kt | 6 +- .../com/emarsys/core/api/AsyncProxyTest.kt | 6 +- .../emarsys/core/api/LogExceptionProxyTest.kt | 6 +- .../core/app/AppLifecycleObserverTest.kt | 2 +- ... => ConcurrentHandlerHolderFactoryTest.kt} | 20 +- .../core/connection/ConnectionWatchDogTest.kt | 6 +- .../ConnectivityChangeReceiverTest.kt | 6 +- ...ty_getConnectionState_ParameterizedTest.kt | 6 +- .../AbstractSqliteRepositoryTest.kt | 4 +- .../specification/EverythingTest.kt | 6 +- .../specification/FilterByRequestIdsTest.kt | 5 +- .../QueryLatestRequestModelTest.kt | 5 +- .../core/di/FakeCoreDependencyContainer.kt | 15 +- .../core/fake/FakeConnectionWatchDog.java | 5 +- .../com/emarsys/core/fake/FakeRestClient.java | 5 +- .../core/request/RequestManagerDennaTest.kt | 19 +- .../core/request/RequestManagerOfflineTest.kt | 25 +- .../core/request/RequestManagerTest.kt | 32 +- .../emarsys/core/request/RestClientTest.kt | 6 +- ...CompletionHandlerMiddlewareProviderTest.kt | 10 +- .../model/RequestModelRepositoryTest.java | 182 ------ .../model/RequestModelRepositoryTest.kt | 176 ++++++ .../specification/FilterByUrlPatternTest.kt | 5 +- .../core/shard/ShardModelRepositoryTest.kt | 6 +- .../specification/FilterByShardIdsTest.kt | 4 +- .../specification/FilterByShardTypeTest.kt | 4 +- .../emarsys/core/util/FileDownloaderTest.kt | 5 +- .../com/emarsys/core/util/log/LoggerTest.kt | 5 +- .../CoreCompletionHandlerMiddlewareTest.kt | 60 +- .../emarsys/core/worker/DefaultWorkerTest.kt | 14 +- .../ConcurrentHandlerHolderFactory.kt | 6 +- .../java/com/emarsys/core/di/CoreComponent.kt | 3 - .../core/handler/ConcurrentHandlerHolder.kt | 2 +- .../emarsys/core/request/RequestManager.kt | 14 +- .../worker/CoreCompletionHandlerMiddleware.kt | 13 +- .../com/emarsys/core/worker/DefaultWorker.kt | 7 +- .../fake/FakeFirebaseDependencyContainer.kt | 14 +- .../EmarsysFirebaseMessagingServiceTest.kt | 34 +- .../fake/FakeHuaweiDependencyContainer.kt | 12 +- .../EmarsysHuaweiMessagingServiceTest.kt | 7 +- .../com/emarsys/di/FakeDependencyContainer.kt | 7 +- .../java/com/emarsys/fake/FakeRestClient.java | 4 +- .../emarsys/inapp/ui/InlineInAppViewTest.kt | 17 +- .../emarsys/testUtil/IntegrationTestUtils.kt | 13 +- .../src/main/java/com/emarsys/Emarsys.kt | 3 +- .../com/emarsys/di/DefaultEmarsysComponent.kt | 29 +- .../com/emarsys/inapp/ui/InlineInAppView.kt | 6 +- .../config/DefaultConfigInternalTest.kt | 532 ++++++++++-------- .../fake/FakeEmarsysDependencyContainer.kt | 12 +- .../java/com/emarsys/fake/FakeRestClient.java | 76 --- .../java/com/emarsys/fake/FakeRestClient.kt | 57 ++ .../DefaultMobileEngageInternalTest.kt | 7 - .../FakeMobileEngageDependencyContainer.kt | 14 +- .../mobileengage/fake/FakeRequestManager.kt | 16 +- .../mobileengage/fake/FakeRestClient.java | 4 +- .../geofence/DefaultGeofenceInternalTest.kt | 17 +- .../iam/OverlayInAppPresenterTest.kt | 161 +++--- .../iam/SaveDisplayedIamActionTest.kt | 4 +- .../iam/dialog/IamDialogProviderTest.kt | 8 +- .../mobileengage/iam/dialog/IamDialogTest.kt | 117 +++- .../action/SendDisplayedIamActionTest.java | 5 +- .../inline/InlineInAppWebViewFactoryTest.kt | 30 +- .../iam/jsbridge/IamJsBridgeTest.kt | 133 +++-- .../iam/jsbridge/JSCommandFactoryTest.kt | 7 +- .../ButtonClickedRepositoryTest.kt | 5 +- .../DisplayedIamRepositoryTest.kt | 4 +- .../RequestRepositoryProxyTest.kt | 5 +- .../specification/FilterByCampaignIdTest.kt | 5 +- .../webview/IamStaticWebViewProviderTest.kt | 19 +- .../iam/webview/IamWebViewClientTest.java | 83 --- .../iam/webview/IamWebViewClientTest.kt | 58 ++ .../inbox/DefaultMessageInboxInternalTest.kt | 74 ++- .../notification/ActionCommandFactoryTest.kt | 13 +- .../NotificationCommandFactoryTest.kt | 14 +- .../command/AppEventCommandTest.kt | 38 +- .../PreloadedInappHandlerCommandTest.kt | 2 +- .../push/DefaultPushInternalTest.kt | 10 +- .../InAppMessageResponseHandlerTest.kt | 7 +- .../OnEventActionResponseHandlerTest.kt | 2 +- .../geofence/DefaultGeofenceInternal.kt | 10 +- .../mobileengage/iam/OverlayInAppPresenter.kt | 12 +- .../mobileengage/iam/dialog/IamDialog.kt | 49 +- .../iam/dialog/IamDialogProvider.kt | 19 +- .../iam/inline/InlineInAppWebViewFactory.kt | 6 +- .../mobileengage/iam/jsbridge/IamJsBridge.kt | 38 +- .../iam/jsbridge/IamJsBridgeFactory.kt | 11 +- .../iam/jsbridge/JSCommandFactory.kt | 9 +- .../iam/webview/IamStaticWebViewProvider.kt | 21 +- .../iam/webview/IamWebViewClient.java | 31 - .../iam/webview/IamWebViewClient.kt | 16 + .../inbox/DefaultMessageInboxInternal.kt | 2 +- .../notification/ActionCommandFactory.kt | 22 +- .../notification/command/AppEventCommand.kt | 17 +- .../mobileengage/push/DefaultPushInternal.kt | 13 +- .../service/MessagingServiceUtils.kt | 162 ++++-- .../predict/DefaultPredictInternalTest.kt | 42 +- .../emarsys/predict/fake/FakeRestClient.java | 4 +- .../emarsys/predict/DefaultPredictInternal.kt | 11 +- 98 files changed, 1487 insertions(+), 1394 deletions(-) rename core/src/androidTest/java/com/emarsys/core/concurrency/{CoreHandlerProviderTest.kt => ConcurrentHandlerHolderFactoryTest.kt} (58%) delete mode 100644 core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.java create mode 100644 core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.kt delete mode 100644 emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.java create mode 100644 emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.kt delete mode 100644 mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/webview/IamWebViewClientTest.java create mode 100644 mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/webview/IamWebViewClientTest.kt delete mode 100644 mobile-engage/src/main/java/com/emarsys/mobileengage/iam/webview/IamWebViewClient.java create mode 100644 mobile-engage/src/main/java/com/emarsys/mobileengage/iam/webview/IamWebViewClient.kt diff --git a/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleActionRegistryTest.kt b/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleActionRegistryTest.kt index 55ef76534..bbb436544 100644 --- a/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleActionRegistryTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/activity/ActivityLifecycleActionRegistryTest.kt @@ -1,8 +1,6 @@ package com.emarsys.core.activity import android.app.Activity -import android.os.Handler -import android.os.Looper import com.emarsys.core.activity.ActivityLifecycleAction.ActivityLifecycle.CREATE import com.emarsys.core.activity.ActivityLifecycleAction.ActivityLifecycle.RESUME import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory @@ -24,11 +22,9 @@ class ActivityLifecycleActionRegistryTest { private lateinit var mockAction3: ActivityLifecycleAction private lateinit var mockActions: MutableList private lateinit var mockActivity: Activity - private lateinit var uiHandler: Handler @Before fun setup() { - uiHandler = Handler(Looper.getMainLooper()) mockActivity = mock() mockAction1 = mock { on { triggeringLifecycle } doReturn RESUME @@ -43,7 +39,7 @@ class ActivityLifecycleActionRegistryTest { on { get() } doReturn mockActivity } mockActions = mutableListOf(mockAction1, mockAction2, mockAction3) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() activityLifecycleActionRegistry = ActivityLifecycleActionRegistry( concurrentHandlerHolder, mockCurrentActivityProvider, mockActions diff --git a/core/src/androidTest/java/com/emarsys/core/api/AsyncProxyTest.kt b/core/src/androidTest/java/com/emarsys/core/api/AsyncProxyTest.kt index 5eadcba9a..5b6c4331e 100644 --- a/core/src/androidTest/java/com/emarsys/core/api/AsyncProxyTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/api/AsyncProxyTest.kt @@ -1,8 +1,6 @@ package com.emarsys.core.api -import android.os.Handler import android.os.HandlerThread -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.testUtil.TimeoutUtils @@ -19,7 +17,6 @@ import java.util.concurrent.CountDownLatch class AsyncProxyTest { private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder - private lateinit var uiHandler: Handler @Rule @JvmField @@ -29,8 +26,7 @@ class AsyncProxyTest { fun setUp() { val handlerThread = HandlerThread("CoreSDKHandlerThread-" + UUID.randomUUID().toString()) handlerThread.start() - uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() } @Test diff --git a/core/src/androidTest/java/com/emarsys/core/api/LogExceptionProxyTest.kt b/core/src/androidTest/java/com/emarsys/core/api/LogExceptionProxyTest.kt index 8ab799932..963615331 100644 --- a/core/src/androidTest/java/com/emarsys/core/api/LogExceptionProxyTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/api/LogExceptionProxyTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.api -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.di.FakeCoreDependencyContainer import com.emarsys.core.di.setupCoreComponent @@ -24,7 +22,6 @@ import java.util.concurrent.CountDownLatch class LogExceptionProxyTest { private lateinit var mockLogger: Logger private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder - private lateinit var uiHandler: Handler @Rule @JvmField @@ -32,8 +29,7 @@ class LogExceptionProxyTest { @Before fun setUp() { - uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockLogger = mock() val dependencyContainer = diff --git a/core/src/androidTest/java/com/emarsys/core/app/AppLifecycleObserverTest.kt b/core/src/androidTest/java/com/emarsys/core/app/AppLifecycleObserverTest.kt index fec76da1e..d6be66c8c 100644 --- a/core/src/androidTest/java/com/emarsys/core/app/AppLifecycleObserverTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/app/AppLifecycleObserverTest.kt @@ -35,7 +35,7 @@ class AppLifecycleObserverTest { uiHandler = Handler(Looper.getMainLooper()) mockSession = mock() mockLifecycleOwner = mock() - coreHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + coreHandlerHolder = ConcurrentHandlerHolderFactory.create() appLifecycleObserver = AppLifecycleObserver(mockSession, coreHandlerHolder) } diff --git a/core/src/androidTest/java/com/emarsys/core/concurrency/CoreHandlerProviderTest.kt b/core/src/androidTest/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactoryTest.kt similarity index 58% rename from core/src/androidTest/java/com/emarsys/core/concurrency/CoreHandlerProviderTest.kt rename to core/src/androidTest/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactoryTest.kt index e453ae9b6..b7fece0ed 100644 --- a/core/src/androidTest/java/com/emarsys/core/concurrency/CoreHandlerProviderTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactoryTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.concurrency -import android.os.Handler -import android.os.Looper import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.testUtil.TimeoutUtils.timeoutRule import io.kotlintest.shouldBe @@ -12,10 +10,8 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule -class CoreHandlerProviderTest { - private lateinit var holderFactory: ConcurrentHandlerHolderFactory - private lateinit var provided: ConcurrentHandlerHolder - private lateinit var uiHandler: Handler +class ConcurrentHandlerHolderFactoryTest { + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -23,30 +19,28 @@ class CoreHandlerProviderTest { @Before fun setUp() { - uiHandler = Handler(Looper.getMainLooper()) - holderFactory = ConcurrentHandlerHolderFactory(uiHandler) - provided = holderFactory.create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() } @After fun tearDown() { - provided.looper.quit() + concurrentHandlerHolder.looper.quit() } @Test fun testProvideHandler_shouldNotReturnNull() { - provided shouldNotBe null + concurrentHandlerHolder shouldNotBe null } @Test fun testProvideHandler_shouldReturnConcurrentHandlerHolder() { - provided.javaClass shouldBe ConcurrentHandlerHolder::class.java + concurrentHandlerHolder.javaClass shouldBe ConcurrentHandlerHolder::class.java } @Test fun testProvideHandler_shouldReturnConcurrentHandlerHolderWithCorrectName() { val expectedNamePrefix = "CoreSDKHandlerThread" - val actualName = provided.looper.thread.name + val actualName = concurrentHandlerHolder.looper.thread.name actualName.startsWith(expectedNamePrefix) shouldBe true } } \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/connection/ConnectionWatchDogTest.kt b/core/src/androidTest/java/com/emarsys/core/connection/ConnectionWatchDogTest.kt index d49da87e1..4653843b2 100644 --- a/core/src/androidTest/java/com/emarsys/core/connection/ConnectionWatchDogTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/connection/ConnectionWatchDogTest.kt @@ -4,8 +4,6 @@ import android.content.Context import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.os.Build -import android.os.Handler -import android.os.Looper import androidx.test.filters.SdkSuppress import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.handler.ConcurrentHandlerHolder @@ -24,7 +22,6 @@ import org.junit.rules.TestRule class ConnectionWatchDogTest { private lateinit var context: Context private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder - private lateinit var uiHandler: Handler @Rule @JvmField @@ -33,8 +30,7 @@ class ConnectionWatchDogTest { @Before fun setup() { context = getTargetContext().applicationContext - uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() } @Test diff --git a/core/src/androidTest/java/com/emarsys/core/connection/ConnectivityChangeReceiverTest.kt b/core/src/androidTest/java/com/emarsys/core/connection/ConnectivityChangeReceiverTest.kt index 420ef3374..0b19691b5 100644 --- a/core/src/androidTest/java/com/emarsys/core/connection/ConnectivityChangeReceiverTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/connection/ConnectivityChangeReceiverTest.kt @@ -2,8 +2,6 @@ package com.emarsys.core.connection import android.content.Context import android.os.Build -import android.os.Handler -import android.os.Looper import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory @@ -23,7 +21,6 @@ class ConnectivityChangeReceiverTest { private lateinit var receiver: ConnectivityChangeReceiver private lateinit var mockListener: ConnectionChangeListener private lateinit var context: Context - private lateinit var uiHandler: Handler @Rule @JvmField @@ -32,9 +29,8 @@ class ConnectivityChangeReceiverTest { @Before fun setup() { - uiHandler = Handler(Looper.getMainLooper()) context = InstrumentationRegistry.getInstrumentation().targetContext - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockListener = mock() } diff --git a/core/src/androidTest/java/com/emarsys/core/connection/Connectivity_getConnectionState_ParameterizedTest.kt b/core/src/androidTest/java/com/emarsys/core/connection/Connectivity_getConnectionState_ParameterizedTest.kt index 7113e2440..4afb96c1a 100644 --- a/core/src/androidTest/java/com/emarsys/core/connection/Connectivity_getConnectionState_ParameterizedTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/connection/Connectivity_getConnectionState_ParameterizedTest.kt @@ -2,8 +2,6 @@ package com.emarsys.core.connection import android.net.NetworkCapabilities import android.os.Build -import android.os.Handler -import android.os.Looper import androidx.test.filters.SdkSuppress import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.testUtil.ConnectionTestUtils.getConnectivityManagerMock @@ -37,9 +35,7 @@ class Connectivity_getConnectionState_ParameterizedTest { row(true, NetworkCapabilities.TRANSPORT_VPN, ConnectionState.CONNECTED) ) { isConnected, connectionType, expectedConnectionState -> val connectionWatchDog = ConnectionWatchDog( - getTargetContext(), ConcurrentHandlerHolderFactory( - Handler(Looper.getMainLooper()) - ).create() + getTargetContext(), ConcurrentHandlerHolderFactory.create() ) setInstanceField( connectionWatchDog, diff --git a/core/src/androidTest/java/com/emarsys/core/database/repository/AbstractSqliteRepositoryTest.kt b/core/src/androidTest/java/com/emarsys/core/database/repository/AbstractSqliteRepositoryTest.kt index 746f88a6c..96734168c 100644 --- a/core/src/androidTest/java/com/emarsys/core/database/repository/AbstractSqliteRepositoryTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/database/repository/AbstractSqliteRepositoryTest.kt @@ -2,8 +2,6 @@ package com.emarsys.core.database.repository import android.content.ContentValues import android.database.Cursor -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.CoreSQLiteDatabase import com.emarsys.core.database.DatabaseContract @@ -65,7 +63,7 @@ class AbstractSqliteRepositoryTest { fun init() { DatabaseTestUtils.deleteCoreDatabase() testConcurrentHandlerHolder = - ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() + ConcurrentHandlerHolderFactory.create() dummySpecification = sqlSpecification( DISTINCT, diff --git a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/EverythingTest.kt b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/EverythingTest.kt index e3b983688..7e06c2463 100644 --- a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/EverythingTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/EverythingTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.database.repository.specification -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.handler.ConcurrentHandlerHolder @@ -21,7 +19,6 @@ import org.junit.rules.TestRule class EverythingTest { - private lateinit var uiHandler: Handler private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @@ -31,8 +28,7 @@ class EverythingTest { @Before fun init() { DatabaseTestUtils.deleteCoreDatabase() - uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() } @Test diff --git a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/FilterByRequestIdsTest.kt b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/FilterByRequestIdsTest.kt index 186588954..b0e56db79 100644 --- a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/FilterByRequestIdsTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/FilterByRequestIdsTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.database.repository.specification -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.provider.timestamp.TimestampProvider @@ -52,8 +50,7 @@ class FilterByRequestIdsTest { .build() val coreDbHelper = CoreDbHelper(InstrumentationRegistry.getTargetContext(), HashMap()) - val uiHandler = Handler(Looper.getMainLooper()) - val concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + val concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() repository = RequestModelRepository(coreDbHelper, concurrentHandlerHolder) runBlocking { repository.add(requestModel1) diff --git a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/QueryLatestRequestModelTest.kt b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/QueryLatestRequestModelTest.kt index aab6e3ecc..8343e159f 100644 --- a/core/src/androidTest/java/com/emarsys/core/database/repository/specification/QueryLatestRequestModelTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/database/repository/specification/QueryLatestRequestModelTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.database.repository.specification -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.provider.timestamp.TimestampProvider @@ -52,8 +50,7 @@ class QueryLatestRequestModelTest { specification = QueryLatestRequestModel() val timestampProvider = TimestampProvider() val uuidProvider = UUIDProvider() - val uiHandler = Handler(Looper.getMainLooper()) - val concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + val concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() val context = InstrumentationRegistry.getTargetContext().applicationContext val coreDbHelper = CoreDbHelper(context, mutableMapOf()) diff --git a/core/src/androidTest/java/com/emarsys/core/di/FakeCoreDependencyContainer.kt b/core/src/androidTest/java/com/emarsys/core/di/FakeCoreDependencyContainer.kt index bb4737baa..90cdd24a6 100644 --- a/core/src/androidTest/java/com/emarsys/core/di/FakeCoreDependencyContainer.kt +++ b/core/src/androidTest/java/com/emarsys/core/di/FakeCoreDependencyContainer.kt @@ -1,8 +1,6 @@ package com.emarsys.core.di import android.content.SharedPreferences -import android.os.Handler -import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog @@ -21,7 +19,6 @@ import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient -import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.shard.ShardModel import com.emarsys.core.storage.KeyValueStore @@ -29,17 +26,10 @@ import com.emarsys.core.storage.Storage import com.emarsys.core.util.FileDownloader import com.emarsys.core.util.log.Logger import com.emarsys.core.worker.Worker -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.android.asCoroutineDispatcher import org.mockito.kotlin.mock class FakeCoreDependencyContainer( - override val uiHandler: Handler = Handler(Looper.getMainLooper()), - override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( - uiHandler - ).create(), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory.create(), override val activityLifecycleWatchdog: ActivityLifecycleWatchdog = mock(), override val currentActivityWatchdog: CurrentActivityWatchdog = mock(), override val coreSQLiteDatabase: CoreSQLiteDatabase = mock(), @@ -64,7 +54,4 @@ class FakeCoreDependencyContainer( override val coreCompletionHandler: CoreCompletionHandler = mock(), override val logLevelStorage: Storage = mock(), override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), - override val coreSdkScope: CoroutineScope = CoroutineScope(Job() + concurrentHandlerHolder.coreHandler.handler.asCoroutineDispatcher()), - override val uiScope: CoroutineScope = CoroutineScope(Job() + Dispatchers.Main), - override val runnableFactory: RunnableFactory = mock() ) : CoreComponent \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/fake/FakeConnectionWatchDog.java b/core/src/androidTest/java/com/emarsys/core/fake/FakeConnectionWatchDog.java index 5b4056d25..ff4515dd9 100644 --- a/core/src/androidTest/java/com/emarsys/core/fake/FakeConnectionWatchDog.java +++ b/core/src/androidTest/java/com/emarsys/core/fake/FakeConnectionWatchDog.java @@ -1,8 +1,5 @@ package com.emarsys.core.fake; -import android.os.Handler; -import android.os.Looper; - import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; import com.emarsys.core.connection.ConnectionChangeListener; import com.emarsys.core.connection.ConnectionWatchDog; @@ -20,7 +17,7 @@ public class FakeConnectionWatchDog extends ConnectionWatchDog { public CountDownLatch latch; public FakeConnectionWatchDog(CountDownLatch latch, Boolean... isConnectedReplies) { - super(InstrumentationRegistry.getTargetContext(), new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); + super(InstrumentationRegistry.getTargetContext(), ConcurrentHandlerHolderFactory.INSTANCE.create()); this.latch = latch; this.isConnectedReplies = new ArrayList<>(Arrays.asList(isConnectedReplies)); } diff --git a/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java b/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java index 1f6ba530d..3894feaf2 100644 --- a/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java +++ b/core/src/androidTest/java/com/emarsys/core/fake/FakeRestClient.java @@ -2,9 +2,6 @@ import static org.mockito.Mockito.mock; -import android.os.Handler; -import android.os.Looper; - import com.emarsys.core.CoreCompletionHandler; import com.emarsys.core.api.result.Try; import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; @@ -27,7 +24,7 @@ public class FakeRestClient extends RestClient { @SuppressWarnings("unchecked") public FakeRestClient(Object... fakeResults) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); + ConcurrentHandlerHolderFactory.INSTANCE.create()); for (Object o : fakeResults) { if (!(o instanceof Integer || o instanceof Exception)) { throw new IllegalArgumentException("FakeResults list can only contain Integers and Exceptions!"); diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.kt index 39011447f..50e1c6ba6 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerDennaTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.request -import android.os.Handler -import androidx.test.platform.app.InstrumentationRegistry import com.emarsys.core.Mapper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionProvider @@ -14,8 +12,6 @@ import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.factory.CoreCompletionHandlerMiddlewareProvider -import com.emarsys.core.request.factory.DefaultRunnableFactory -import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestMethod import com.emarsys.core.request.model.RequestModel import com.emarsys.core.request.model.RequestModelRepository @@ -43,14 +39,11 @@ class RequestManagerDennaTest { private lateinit var model: RequestModel private lateinit var latch: CountDownLatch private lateinit var fakeCompletionHandler: FakeCompletionHandler - private lateinit var concurrentHandlerHolderFactory: ConcurrentHandlerHolderFactory private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder - private lateinit var uiHandler: Handler private lateinit var worker: Worker private lateinit var timestampProvider: TimestampProvider private lateinit var uuidProvider: UUIDProvider private lateinit var coreCompletionHandlerMiddlewareProvider: CoreCompletionHandlerMiddlewareProvider - private lateinit var runnableFactory: RunnableFactory @Rule @JvmField @@ -71,10 +64,7 @@ class RequestManagerDennaTest { requestModelMappers.add(mockRequestModelMapper) val context = getTargetContext() checkConnection(context) - runnableFactory = DefaultRunnableFactory() - uiHandler = Handler(InstrumentationRegistry.getInstrumentation().targetContext.mainLooper) - concurrentHandlerHolderFactory = ConcurrentHandlerHolderFactory(uiHandler) - concurrentHandlerHolder = concurrentHandlerHolderFactory.create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() val connectionWatchDog = ConnectionWatchDog(context, concurrentHandlerHolder) val coreDbHelper = CoreDbHelper(context, mutableMapOf()) val requestRepository: Repository = @@ -92,14 +82,12 @@ class RequestManagerDennaTest { ) coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider( requestRepository, - uiHandler, - concurrentHandlerHolder, - runnableFactory + concurrentHandlerHolder ) worker = DefaultWorker( requestRepository, connectionWatchDog, - uiHandler, + concurrentHandlerHolder, fakeCompletionHandler, restClient, coreCompletionHandlerMiddlewareProvider @@ -115,7 +103,6 @@ class RequestManagerDennaTest { mock(), fakeCompletionHandler, mock(), - mock(), mock() ) headers = HashMap() diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt index e86600096..3492763b2 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerOfflineTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.request -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionState import com.emarsys.core.database.helper.CoreDbHelper @@ -16,8 +14,6 @@ import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.factory.CompletionHandlerProxyProvider import com.emarsys.core.request.factory.CoreCompletionHandlerMiddlewareProvider -import com.emarsys.core.request.factory.DefaultRunnableFactory -import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestMethod import com.emarsys.core.request.model.RequestModel import com.emarsys.core.request.model.RequestModelRepository @@ -33,6 +29,7 @@ import io.kotlintest.be import io.kotlintest.matchers.beEmpty import io.kotlintest.should import io.kotlintest.shouldBe +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -65,20 +62,16 @@ class RequestManagerOfflineTest { private lateinit var completionLatch: CountDownLatch private lateinit var completionHandler: FakeCompletionHandler private lateinit var fakeRestClient: RestClient - private lateinit var holderFactory: ConcurrentHandlerHolderFactory private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder - private lateinit var uiHandler: Handler private lateinit var worker: Worker private lateinit var coreCompletionHandlerMiddlewareProvider: CoreCompletionHandlerMiddlewareProvider private lateinit var mockProxyProvider: CompletionHandlerProxyProvider - private lateinit var runnableFactory: RunnableFactory @Before fun setup() { DatabaseTestUtils.deleteCoreDatabase() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() - uiHandler = Handler(Looper.getMainLooper()) - runnableFactory = DefaultRunnableFactory() } @After @@ -100,7 +93,7 @@ class RequestManagerOfflineTest { requestRepository.isEmpty() shouldBe false completionHandler.latch = CountDownLatch(1) - uiHandler.post { + concurrentHandlerHolder.uiScope.launch { watchDog.connectionChangeListener.onConnectionChanged( ConnectionState.CONNECTED, true @@ -238,9 +231,6 @@ class RequestManagerOfflineTest { watchDogLatch = CountDownLatch(watchDogCountDown) watchDog = FakeConnectionWatchDog(watchDogLatch, *connectionStates) val coreDbHelper = CoreDbHelper(InstrumentationRegistry.getTargetContext(), HashMap()) - val uiHandler: Handler = Handler(Looper.getMainLooper()) - holderFactory = ConcurrentHandlerHolderFactory(uiHandler) - val concurrentHandlerHolder = holderFactory.create() requestRepository = RequestModelRepository(coreDbHelper, concurrentHandlerHolder) shardRepository = ShardModelRepository(coreDbHelper, concurrentHandlerHolder) @@ -248,26 +238,23 @@ class RequestManagerOfflineTest { completionHandler = FakeCompletionHandler(completionLatch) fakeRestClient = FakeRestClient(*requestResults) - concurrentHandlerHolder = holderFactory.create() mockProxyProvider = mock() coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider( requestRepository, - uiHandler, - concurrentHandlerHolder, - runnableFactory + concurrentHandlerHolder ) worker = DefaultWorker( requestRepository, watchDog, - uiHandler, + concurrentHandlerHolder, completionHandler, fakeRestClient, coreCompletionHandlerMiddlewareProvider ) - concurrentHandlerHolder.sdkScope.launch { + runBlocking { requestModels.forEach { requestRepository.add(it) } diff --git a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt index 93039e69e..fb8781246 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RequestManagerTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.request -import android.os.Handler -import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.Mapper import com.emarsys.core.Registry @@ -15,7 +13,9 @@ import com.emarsys.core.fake.FakeCompletionHandler import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider -import com.emarsys.core.request.factory.* +import com.emarsys.core.request.factory.CompletionHandlerProxyProvider +import com.emarsys.core.request.factory.CoreCompletionHandlerMiddlewareProvider +import com.emarsys.core.request.factory.ScopeDelegatorCompletionHandlerProvider import com.emarsys.core.request.model.RequestMethod import com.emarsys.core.request.model.RequestModel import com.emarsys.core.shard.ShardModel @@ -50,9 +50,7 @@ class RequestManagerTest { private lateinit var completionHandlerLatch: CountDownLatch private lateinit var runnableFactoryLatch: CountDownLatch private lateinit var mockConnectionWatchDog: ConnectionWatchDog - private lateinit var concurrentHandlerHolderFactory: ConcurrentHandlerHolderFactory private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder - private lateinit var uiHandler: Handler private lateinit var mockRequestRepository: Repository private lateinit var mockShardRepository: Repository private lateinit var worker: Worker @@ -64,8 +62,6 @@ class RequestManagerTest { private lateinit var mockRequestModelMapper: Mapper private lateinit var mockCompletionHandlerProxyProvider: CompletionHandlerProxyProvider private lateinit var mockScopeDelegatorCompletionHandlerProvider: ScopeDelegatorCompletionHandlerProvider - private lateinit var mockScope: CoroutineScope - private lateinit var runnableFactory: RunnableFactory private lateinit var callbackRegistryThreadSpy: ThreadSpy> private lateinit var shardRepositoryThreadSpy: ThreadSpy> @@ -89,10 +85,7 @@ class RequestManagerTest { } val context = getTargetContext() checkConnection(context) - runnableFactory = DefaultRunnableFactory() - uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolderFactory = ConcurrentHandlerHolderFactory(uiHandler) - concurrentHandlerHolder = concurrentHandlerHolderFactory.create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockConnectionWatchDog = mock() mockRequestRepository = mock { on { isEmpty() } doReturn true @@ -122,14 +115,12 @@ class RequestManagerTest { mockRestClient = mock() coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider( mockRequestRepository, - uiHandler, - concurrentHandlerHolder, - runnableFactory + concurrentHandlerHolder ) worker = DefaultWorker( mockRequestRepository, mockConnectionWatchDog, - uiHandler, + concurrentHandlerHolder, fakeCompletionHandler, restClient, coreCompletionHandlerMiddlewareProvider @@ -140,7 +131,6 @@ class RequestManagerTest { mockScopeDelegatorCompletionHandlerProvider = mock { on { provide(any(), any()) } doReturn fakeCompletionHandler } - mockScope = mock() manager = RequestManager( concurrentHandlerHolder, mockRequestRepository, @@ -150,8 +140,7 @@ class RequestManagerTest { mockCallbackRegistry, mockDefaultHandler, mockCompletionHandlerProxyProvider, - mockScopeDelegatorCompletionHandlerProvider, - mockScope + mockScopeDelegatorCompletionHandlerProvider ) timestampProvider = TimestampProvider() uuidProvider = UUIDProvider() @@ -247,7 +236,10 @@ class RequestManagerTest { mockDefaultHandler ) manager.submitNow(requestModel) - verify(mockScopeDelegatorCompletionHandlerProvider).provide(mockDefaultHandler, mockScope) + verify(mockScopeDelegatorCompletionHandlerProvider).provide( + mockDefaultHandler, + concurrentHandlerHolder.sdkScope + ) verify(mockCompletionHandlerProxyProvider, times(2)) .provideProxy(null, mockDefaultHandler) verify(mockRestClient).execute(requestModel, mockDefaultHandler) @@ -258,7 +250,7 @@ class RequestManagerTest { manager.submitNow(requestModel, fakeCompletionHandler) verify(mockScopeDelegatorCompletionHandlerProvider).provide( fakeCompletionHandler, - mockScope + concurrentHandlerHolder.sdkScope ) verify(mockCompletionHandlerProxyProvider).provideProxy(null, fakeCompletionHandler) verify(mockRestClient).execute(requestModel, mockDefaultHandler) diff --git a/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt b/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt index b479a7ae6..4de695580 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/RestClientTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.request -import android.os.Handler -import android.os.Looper import com.emarsys.core.Mapper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionProvider @@ -42,7 +40,6 @@ class RestClientTest { private lateinit var mockResponseHandlersProcessor: ResponseHandlersProcessor private lateinit var mockRequestModelMapper: Mapper private lateinit var requestModelMappers: List> - private lateinit var uiHandler: Handler private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @@ -58,8 +55,7 @@ class RestClientTest { connectionProvider = ConnectionProvider() mockResponseHandlersProcessor = mock() mockRequestModelMapper = mock() as Mapper - uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() whenever(mockRequestModelMapper.map(any())).thenAnswer { invocation -> val args = invocation.arguments diff --git a/core/src/androidTest/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProviderTest.kt b/core/src/androidTest/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProviderTest.kt index 1bbb207de..46256597e 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProviderTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/factory/CoreCompletionHandlerMiddlewareProviderTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.request.factory -import android.os.Handler -import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository @@ -32,7 +30,6 @@ class CoreCompletionHandlerMiddlewareProviderTest { val timeout: TestRule = TimeoutUtils.timeoutRule private lateinit var mockRequestRepository: Repository - private lateinit var uiHandler: Handler private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var mockCoreCompletionHandler: CoreCompletionHandler private lateinit var mockWorker: Worker @@ -54,9 +51,8 @@ class CoreCompletionHandlerMiddlewareProviderTest { } mockRequestRepository = (mock()) runnableFactory = DefaultRunnableFactory() - uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockCoreCompletionHandler = mock { whenever(it.onSuccess(any(), any())).thenAnswer { latch.countDown() } } @@ -64,9 +60,7 @@ class CoreCompletionHandlerMiddlewareProviderTest { coreCompletionHandlerMiddlewareProvider = CoreCompletionHandlerMiddlewareProvider( mockRequestRepository, - uiHandler, - concurrentHandlerHolder, - runnableFactory + concurrentHandlerHolder ) } diff --git a/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.java b/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.java deleted file mode 100644 index 03efedc9a..000000000 --- a/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.emarsys.core.request.model; - -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_HEADERS; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_METHOD; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_PAYLOAD; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_REQUEST_ID; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TIMESTAMP; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TTL; -import static com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_URL; -import static com.emarsys.core.util.serialization.SerializationUtils.serializableToBlob; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.os.Handler; -import android.os.Looper; - -import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; -import com.emarsys.core.database.CoreSQLiteDatabase; -import com.emarsys.core.database.DatabaseContract; -import com.emarsys.core.database.helper.CoreDbHelper; -import com.emarsys.core.database.repository.specification.Everything; -import com.emarsys.core.database.trigger.TriggerKey; -import com.emarsys.core.handler.ConcurrentHandlerHolder; -import com.emarsys.testUtil.DatabaseTestUtils; -import com.emarsys.testUtil.InstrumentationRegistry; -import com.emarsys.testUtil.TimeoutUtils; - -import org.json.JSONException; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -public class RequestModelRepositoryTest { - - @Rule - public TestRule timeout = TimeoutUtils.getTimeoutRule(); - - - static { - mock(Cursor.class); - } - - private static final String URL_EMARSYS = "https://www.emarsys.com"; - private static final String REQUEST_ID = "idka"; - private static final long TTL = 600; - private static final long TIMESTAMP = System.currentTimeMillis(); - private static final String URL = "https://www.google.com"; - - private RequestModel request; - private RequestModelRepository repository; - private Context context; - private ConcurrentHandlerHolder concurrentHandlerHolder; - private HashMap headers; - private HashMap payload; - private Handler uiHandler; - - @Before - public void init() { - DatabaseTestUtils.deleteCoreDatabase(); - - context = InstrumentationRegistry.getTargetContext(); - CoreDbHelper coreDbHelper = new CoreDbHelper(context, new HashMap>()); - uiHandler = new Handler(Looper.getMainLooper()); - concurrentHandlerHolder = new ConcurrentHandlerHolderFactory(uiHandler).create(); - repository = new RequestModelRepository(coreDbHelper, concurrentHandlerHolder); - - - payload = new HashMap<>(); - payload.put("payload1", "payload_value1"); - payload.put("payload2", "payload_value2"); - - headers = new HashMap<>(); - headers.put("header1", "header_value1"); - headers.put("header2", "header_value2"); - - request = new RequestModel(URL, RequestMethod.GET, payload, headers, TIMESTAMP, TTL, REQUEST_ID); - } - - @Test - public void testContentValuesFromItem() { - ContentValues result = repository.contentValuesFromItem(request); - - assertEquals(request.getId(), result.getAsString(REQUEST_COLUMN_NAME_REQUEST_ID)); - assertEquals(request.getMethod().name(), result.getAsString(REQUEST_COLUMN_NAME_METHOD)); - assertEquals(request.getUrl().toString(), result.getAsString(REQUEST_COLUMN_NAME_URL)); - assertArrayEquals(serializableToBlob(request.getHeaders()), result.getAsByteArray(REQUEST_COLUMN_NAME_HEADERS)); - assertArrayEquals(serializableToBlob(request.getPayload()), result.getAsByteArray(REQUEST_COLUMN_NAME_PAYLOAD)); - assertEquals(request.getTimestamp(), (long) result.getAsLong(REQUEST_COLUMN_NAME_TIMESTAMP)); - assertEquals(request.getTtl(), (long) result.getAsLong(REQUEST_COLUMN_NAME_TTL)); - } - - @Test - public void testItemFromCursor() { - Cursor cursor = mock(Cursor.class); - - when(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_REQUEST_ID)).thenReturn(0); - when(cursor.getString(0)).thenReturn(REQUEST_ID); - - when(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_METHOD)).thenReturn(1); - when(cursor.getString(1)).thenReturn(RequestMethod.GET.name()); - - when(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_URL)).thenReturn(2); - when(cursor.getString(2)).thenReturn(URL); - - when(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_HEADERS)).thenReturn(3); - when(cursor.getBlob(3)).thenReturn(serializableToBlob(headers)); - - when(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_PAYLOAD)).thenReturn(4); - when(cursor.getBlob(4)).thenReturn(serializableToBlob(payload)); - - when(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_TIMESTAMP)).thenReturn(5); - when(cursor.getLong(5)).thenReturn(TIMESTAMP); - - when(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_TTL)).thenReturn(6); - when(cursor.getLong(6)).thenReturn(TTL); - - RequestModel result = repository.itemFromCursor(cursor); - - assertEquals(request, result); - } - - @Test - public void testQuery_shouldFallBack_toEmptyMap_shouldDeserializationFail() throws JSONException { - initializeDatabaseWithCorrectAndIncorrectData(); - - List result = repository.query(new Everything()); - - RequestModel model1 = new RequestModel(URL_EMARSYS, RequestMethod.POST, new HashMap(), new HashMap(), 100, 300, "id1"); - RequestModel model2 = new RequestModel(URL_EMARSYS, RequestMethod.POST, createAttribute(), new HashMap(), 100, 300, "id2"); - List expected = Arrays.asList(model1, model2); - - assertEquals(expected, result); - } - - private void initializeDatabaseWithCorrectAndIncorrectData() throws JSONException { - CoreDbHelper dbHelper = new CoreDbHelper( - context, - new HashMap>()); - CoreSQLiteDatabase db = dbHelper.getWritableCoreDatabase(); - - String jsonString = "{'key1': 'value1', 'key2':321}"; - - HashMap mapAttribute = createAttribute(); - - ContentValues record1 = createContentValues("id1", jsonString); - ContentValues record2 = createContentValues("id2", mapAttribute); - - db.insert(DatabaseContract.REQUEST_TABLE_NAME, null, record1); - db.insert(DatabaseContract.REQUEST_TABLE_NAME, null, record2); - } - - private HashMap createAttribute() { - HashMap mapAttribute = new HashMap<>(); - mapAttribute.put("key1", "value2"); - mapAttribute.put("key2", false); - mapAttribute.put("key3", 1000); - return mapAttribute; - } - - private ContentValues createContentValues(String id, Object attributes) { - ContentValues contentValues = new ContentValues(); - contentValues.put(REQUEST_COLUMN_NAME_REQUEST_ID, id); - contentValues.put(REQUEST_COLUMN_NAME_METHOD, RequestMethod.POST.toString()); - contentValues.put(REQUEST_COLUMN_NAME_URL, URL_EMARSYS); - contentValues.put(REQUEST_COLUMN_NAME_HEADERS, serializableToBlob(new HashMap<>())); - contentValues.put(REQUEST_COLUMN_NAME_PAYLOAD, serializableToBlob(attributes)); - contentValues.put(REQUEST_COLUMN_NAME_TIMESTAMP, 100); - contentValues.put(REQUEST_COLUMN_NAME_TTL, 300); - return contentValues; - } - -} \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.kt b/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.kt new file mode 100644 index 000000000..af00b3218 --- /dev/null +++ b/core/src/androidTest/java/com/emarsys/core/request/model/RequestModelRepositoryTest.kt @@ -0,0 +1,176 @@ +package com.emarsys.core.request.model + +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.database.DatabaseContract +import com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_HEADERS +import com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_METHOD +import com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_PAYLOAD +import com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_REQUEST_ID +import com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TIMESTAMP +import com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_TTL +import com.emarsys.core.database.DatabaseContract.REQUEST_COLUMN_NAME_URL +import com.emarsys.core.database.helper.CoreDbHelper +import com.emarsys.core.database.repository.specification.Everything +import com.emarsys.core.handler.ConcurrentHandlerHolder +import com.emarsys.core.util.serialization.SerializationUtils +import com.emarsys.testUtil.DatabaseTestUtils.deleteCoreDatabase +import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext +import com.emarsys.testUtil.TimeoutUtils.timeoutRule +import org.json.JSONException +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.mockito.Mockito +import java.util.* + +class RequestModelRepositoryTest { + @Rule + @JvmField + var timeout: TestRule = timeoutRule + + companion object { + private const val URL_EMARSYS = "https://www.emarsys.com" + private const val REQUEST_ID = "idka" + private const val TTL: Long = 600 + private val TIMESTAMP = System.currentTimeMillis() + private const val URL = "https://www.google.com" + + init { + Mockito.mock(Cursor::class.java) + } + } + + private var request: RequestModel? = null + private var repository: RequestModelRepository? = null + private var context: Context? = null + private var concurrentHandlerHolder: ConcurrentHandlerHolder? = null + private var headers: HashMap? = null + private var payload: HashMap? = null + + @Before + fun init() { + deleteCoreDatabase() + context = getTargetContext() + val coreDbHelper = CoreDbHelper(context!!, mutableMapOf()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() + repository = RequestModelRepository(coreDbHelper, concurrentHandlerHolder) + payload = HashMap() + payload!!["payload1"] = "payload_value1" + payload!!["payload2"] = "payload_value2" + headers = HashMap() + headers!!["header1"] = "header_value1" + headers!!["header2"] = "header_value2" + request = + RequestModel(URL, RequestMethod.GET, payload, headers!!, TIMESTAMP, TTL, REQUEST_ID) + } + + @Test + fun testContentValuesFromItem() { + val result = repository!!.contentValuesFromItem(request) + Assert.assertEquals(request!!.id, result.getAsString(REQUEST_COLUMN_NAME_REQUEST_ID)) + Assert.assertEquals(request!!.method.name, result.getAsString(REQUEST_COLUMN_NAME_METHOD)) + Assert.assertEquals(request!!.url.toString(), result.getAsString(REQUEST_COLUMN_NAME_URL)) + Assert.assertArrayEquals( + SerializationUtils.serializableToBlob( + request!!.headers + ), result.getAsByteArray(REQUEST_COLUMN_NAME_HEADERS) + ) + Assert.assertArrayEquals( + SerializationUtils.serializableToBlob( + request!!.payload + ), result.getAsByteArray(REQUEST_COLUMN_NAME_PAYLOAD) + ) + Assert.assertEquals( + request!!.timestamp, + result.getAsLong(REQUEST_COLUMN_NAME_TIMESTAMP) as Long + ) + Assert.assertEquals(request!!.ttl, result.getAsLong(REQUEST_COLUMN_NAME_TTL) as Long) + } + + @Test + fun testItemFromCursor() { + val cursor = Mockito.mock(Cursor::class.java) + Mockito.`when`(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_REQUEST_ID)).thenReturn(0) + Mockito.`when`(cursor.getString(0)).thenReturn(REQUEST_ID) + Mockito.`when`(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_METHOD)).thenReturn(1) + Mockito.`when`(cursor.getString(1)).thenReturn(RequestMethod.GET.name) + Mockito.`when`(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_URL)).thenReturn(2) + Mockito.`when`(cursor.getString(2)).thenReturn(URL) + Mockito.`when`(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_HEADERS)).thenReturn(3) + Mockito.`when`(cursor.getBlob(3)).thenReturn(SerializationUtils.serializableToBlob(headers)) + Mockito.`when`(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_PAYLOAD)).thenReturn(4) + Mockito.`when`(cursor.getBlob(4)).thenReturn(SerializationUtils.serializableToBlob(payload)) + Mockito.`when`(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_TIMESTAMP)).thenReturn(5) + Mockito.`when`(cursor.getLong(5)).thenReturn(TIMESTAMP) + Mockito.`when`(cursor.getColumnIndexOrThrow(REQUEST_COLUMN_NAME_TTL)).thenReturn(6) + Mockito.`when`(cursor.getLong(6)).thenReturn(TTL) + val result = repository!!.itemFromCursor(cursor) + Assert.assertEquals(request, result) + } + + @Test + @Throws(JSONException::class) + fun testQuery_shouldFallBack_toEmptyMap_shouldDeserializationFail() { + initializeDatabaseWithCorrectAndIncorrectData() + val result = repository!!.query(Everything()) + val model1 = + RequestModel(URL_EMARSYS, RequestMethod.POST, HashMap(), HashMap(), 100, 300, "id1") + val model2 = RequestModel( + URL_EMARSYS, + RequestMethod.POST, + createAttribute(), + HashMap(), + 100, + 300, + "id2" + ) + val expected = Arrays.asList(model1, model2) + Assert.assertEquals(expected, result) + } + + @Throws(JSONException::class) + private fun initializeDatabaseWithCorrectAndIncorrectData() { + val dbHelper = CoreDbHelper( + context!!, + mutableMapOf() + ) + val db = dbHelper.writableCoreDatabase + val jsonString = "{'key1': 'value1', 'key2':321}" + val mapAttribute = createAttribute() + val record1 = createContentValues("id1", jsonString) + val record2 = createContentValues("id2", mapAttribute) + db.insert(DatabaseContract.REQUEST_TABLE_NAME, null, record1) + db.insert(DatabaseContract.REQUEST_TABLE_NAME, null, record2) + } + + private fun createAttribute(): HashMap { + val mapAttribute = HashMap() + mapAttribute["key1"] = "value2" + mapAttribute["key2"] = false + mapAttribute["key3"] = 1000 + return mapAttribute + } + + private fun createContentValues(id: String, attributes: Any): ContentValues { + val contentValues = ContentValues() + contentValues.put(REQUEST_COLUMN_NAME_REQUEST_ID, id) + contentValues.put(REQUEST_COLUMN_NAME_METHOD, RequestMethod.POST.toString()) + contentValues.put(REQUEST_COLUMN_NAME_URL, URL_EMARSYS) + contentValues.put( + REQUEST_COLUMN_NAME_HEADERS, + SerializationUtils.serializableToBlob(HashMap()) + ) + contentValues.put( + REQUEST_COLUMN_NAME_PAYLOAD, + SerializationUtils.serializableToBlob(attributes) + ) + contentValues.put(REQUEST_COLUMN_NAME_TIMESTAMP, 100) + contentValues.put(REQUEST_COLUMN_NAME_TTL, 300) + return contentValues + } +} \ No newline at end of file diff --git a/core/src/androidTest/java/com/emarsys/core/request/model/specification/FilterByUrlPatternTest.kt b/core/src/androidTest/java/com/emarsys/core/request/model/specification/FilterByUrlPatternTest.kt index 8d760cfc3..ea4eefc64 100644 --- a/core/src/androidTest/java/com/emarsys/core/request/model/specification/FilterByUrlPatternTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/request/model/specification/FilterByUrlPatternTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.request.model.specification -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.specification.Everything @@ -43,9 +41,8 @@ class FilterByUrlPatternTest { val context = InstrumentationRegistry.getTargetContext().applicationContext val coreDbHelper = CoreDbHelper(context, mutableMapOf()) - val uiHandler = Handler(Looper.getMainLooper()) val concurrentHandlerHolder: ConcurrentHandlerHolder = - ConcurrentHandlerHolderFactory(uiHandler).create() + ConcurrentHandlerHolderFactory.create() repository = RequestModelRepository(coreDbHelper, concurrentHandlerHolder) } diff --git a/core/src/androidTest/java/com/emarsys/core/shard/ShardModelRepositoryTest.kt b/core/src/androidTest/java/com/emarsys/core/shard/ShardModelRepositoryTest.kt index 7e07fed71..326e93428 100644 --- a/core/src/androidTest/java/com/emarsys/core/shard/ShardModelRepositoryTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/shard/ShardModelRepositoryTest.kt @@ -2,8 +2,6 @@ package com.emarsys.core.shard import android.content.Context import android.database.Cursor -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.DatabaseContract.SHARD_COLUMN_DATA import com.emarsys.core.database.DatabaseContract.SHARD_COLUMN_ID @@ -31,7 +29,6 @@ class ShardModelRepositoryTest { private lateinit var repository: ShardModelRepository private lateinit var payload: Map private lateinit var context: Context - private lateinit var uiHandler: Handler @Rule @JvmField @@ -47,10 +44,9 @@ class ShardModelRepositoryTest { @Before fun init() { DatabaseTestUtils.deleteCoreDatabase() - uiHandler = Handler(Looper.getMainLooper()) context = InstrumentationRegistry.getTargetContext() val concurrentHandlerHolder: ConcurrentHandlerHolder = - ConcurrentHandlerHolderFactory(uiHandler).create() + ConcurrentHandlerHolderFactory.create() repository = ShardModelRepository(CoreDbHelper(context, mutableMapOf()), concurrentHandlerHolder) diff --git a/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardIdsTest.kt b/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardIdsTest.kt index 6bd9e9572..cffa95cbe 100644 --- a/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardIdsTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardIdsTest.kt @@ -1,8 +1,6 @@ package com.emarsys.core.shard.specification import android.content.Context -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.specification.Everything @@ -35,7 +33,7 @@ class FilterByShardIdsTest { DatabaseTestUtils.deleteCoreDatabase() context = InstrumentationRegistry.getTargetContext().applicationContext concurrentHadlerHolder = - ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() + ConcurrentHandlerHolderFactory.create() originalShardList = listOf( ShardModel("id1", "type1", mapOf(), 0, 0), diff --git a/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardTypeTest.kt b/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardTypeTest.kt index 96bea6139..45b627103 100644 --- a/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardTypeTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/shard/specification/FilterByShardTypeTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.shard.specification -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.specification.Everything @@ -40,7 +38,7 @@ class FilterByShardTypeTest { val context = InstrumentationRegistry.getTargetContext().applicationContext val coreDbHelper = CoreDbHelper(context, mutableMapOf()) concurrentHandlerHolder = - ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() + ConcurrentHandlerHolderFactory.create() repository = ShardModelRepository(coreDbHelper, concurrentHandlerHolder) shardList = mutableListOf( ShardModel("a1", "button_click", mapOf(), 0, 0), diff --git a/core/src/androidTest/java/com/emarsys/core/util/FileDownloaderTest.kt b/core/src/androidTest/java/com/emarsys/core/util/FileDownloaderTest.kt index 40aec238b..db36a390d 100644 --- a/core/src/androidTest/java/com/emarsys/core/util/FileDownloaderTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/util/FileDownloaderTest.kt @@ -1,8 +1,6 @@ package com.emarsys.core.util import android.content.Context -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.testUtil.FileTestUtils @@ -68,10 +66,9 @@ class FileDownloaderTest { @Test fun testDownload_downloadedAndRemoteFileShouldBeTheSame() { - val uiHandler = Handler(Looper.getMainLooper()) val latch = CountDownLatch(1) val concurrentHandlerHolder: ConcurrentHandlerHolder = - ConcurrentHandlerHolderFactory(uiHandler).create() + ConcurrentHandlerHolderFactory.create() concurrentHandlerHolder.coreHandler.post { val path: String = LARGE_IMAGE diff --git a/core/src/androidTest/java/com/emarsys/core/util/log/LoggerTest.kt b/core/src/androidTest/java/com/emarsys/core/util/log/LoggerTest.kt index 825c0af89..f934039d0 100644 --- a/core/src/androidTest/java/com/emarsys/core/util/log/LoggerTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/util/log/LoggerTest.kt @@ -1,7 +1,5 @@ package com.emarsys.core.util.log -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification @@ -50,8 +48,7 @@ class LoggerTest { @Before @Suppress("UNCHECKED_CAST") fun init() { - val uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() shardRepositoryMock = mock() timestampProviderMock = mock().apply { whenever(provideTimestamp()).thenReturn(TIMESTAMP) diff --git a/core/src/androidTest/java/com/emarsys/core/worker/CoreCompletionHandlerMiddlewareTest.kt b/core/src/androidTest/java/com/emarsys/core/worker/CoreCompletionHandlerMiddlewareTest.kt index e6090d214..8b507569a 100644 --- a/core/src/androidTest/java/com/emarsys/core/worker/CoreCompletionHandlerMiddlewareTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/worker/CoreCompletionHandlerMiddlewareTest.kt @@ -14,8 +14,8 @@ import com.emarsys.core.request.model.RequestMethod import com.emarsys.core.request.model.RequestModel import com.emarsys.core.request.model.specification.FilterByRequestIds import com.emarsys.core.response.ResponseModel -import com.emarsys.testUtil.ReflectionTestUtils import com.emarsys.testUtil.TimeoutUtils +import com.emarsys.testUtil.mockito.ThreadSpy import io.kotlintest.matchers.numerics.shouldBeLessThanOrEqual import io.kotlintest.shouldBe import kotlinx.coroutines.runBlocking @@ -30,13 +30,12 @@ import java.util.concurrent.CountDownLatch class CoreCompletionHandlerMiddlewareTest { private lateinit var coreCompletionHandler: CoreCompletionHandler private lateinit var requestRepository: Repository - private lateinit var worker: Worker + private lateinit var mockWorker: Worker private lateinit var expectedId: String private lateinit var middleware: CoreCompletionHandlerMiddleware private lateinit var captor: ArgumentCaptor private lateinit var uiHandler: Handler private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder - private lateinit var spyCoreHandler: SdkHandler @Rule @JvmField @@ -45,21 +44,15 @@ class CoreCompletionHandlerMiddlewareTest { @Before fun setup() { expectedId = "expectedId" - worker = mock() + mockWorker = mock() coreCompletionHandler = mock() requestRepository = mock() uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() - spyCoreHandler = spy(concurrentHandlerHolder.coreHandler) - ReflectionTestUtils.setInstanceField( - concurrentHandlerHolder, - "coreHandler", - spyCoreHandler - ) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() + middleware = CoreCompletionHandlerMiddleware( - worker, + mockWorker, requestRepository, - uiHandler, concurrentHandlerHolder, coreCompletionHandler ) @@ -76,9 +69,9 @@ class CoreCompletionHandlerMiddlewareTest { waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) runBlocking { - verify(worker).unlock() - verify(worker).run() - verifyNoMoreInteractions(worker) + verify(mockWorker).unlock() + verify(mockWorker).run() + verifyNoMoreInteractions(mockWorker) verify(requestRepository).remove(capture(captor)) val filter = captor.value @@ -110,9 +103,11 @@ class CoreCompletionHandlerMiddlewareTest { @Test fun testOnSuccess_callsHandlerPost() { middleware.concurrentHandlerHolder = concurrentHandlerHolder + val threadSpy = ThreadSpy() + whenever(mockWorker.unlock()).thenAnswer(threadSpy) middleware.onSuccess(expectedId, createResponseModel(200)) - verify(spyCoreHandler).post(any()) - verify(worker, timeout(50)).run() + threadSpy.verifyCalledOnCoreSdkThread() + verify(mockWorker, timeout(50)).run() } @Test @@ -151,19 +146,20 @@ class CoreCompletionHandlerMiddlewareTest { } verify(coreCompletionHandler).onError(expectedId, expectedModel) - verify(worker).unlock() - verify(worker).run() - verifyNoMoreInteractions(worker) + verify(mockWorker).unlock() + verify(mockWorker).run() + verifyNoMoreInteractions(mockWorker) } @Test fun testOnError_4xx_callsHandlerPost() { middleware.concurrentHandlerHolder = concurrentHandlerHolder - + val threadSpy = ThreadSpy() + whenever(mockWorker.unlock()).thenAnswer(threadSpy) middleware.onError(expectedId, createResponseModel(401)) - verify(spyCoreHandler).post(any()) - verify(worker, timeout(50)).run() + threadSpy.verifyCalledOnCoreSdkThread() + verify(mockWorker, timeout(50)).run() } @Test @@ -175,8 +171,8 @@ class CoreCompletionHandlerMiddlewareTest { waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) - verify(worker).unlock() - verifyNoMoreInteractions(worker) + verify(mockWorker).unlock() + verifyNoMoreInteractions(mockWorker) verifyNoInteractions(coreCompletionHandler) verifyNoInteractions(requestRepository) } @@ -190,8 +186,8 @@ class CoreCompletionHandlerMiddlewareTest { waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) - verify(worker).unlock() - verifyNoMoreInteractions(worker) + verify(mockWorker).unlock() + verifyNoMoreInteractions(mockWorker) verifyNoInteractions(coreCompletionHandler) verifyNoInteractions(requestRepository) } @@ -205,8 +201,8 @@ class CoreCompletionHandlerMiddlewareTest { waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) - verify(worker).unlock() - verifyNoMoreInteractions(worker) + verify(mockWorker).unlock() + verifyNoMoreInteractions(mockWorker) verifyNoInteractions(coreCompletionHandler) verifyNoInteractions(requestRepository) } @@ -295,8 +291,8 @@ class CoreCompletionHandlerMiddlewareTest { waitForEventLoopToFinish(concurrentHandlerHolder.coreHandler) waitForEventLoopToFinish(uiHandler) - verify(worker).unlock() - verifyNoMoreInteractions(worker) + verify(mockWorker).unlock() + verifyNoMoreInteractions(mockWorker) verify(coreCompletionHandler).onError(expectedId, expectedException) verifyNoMoreInteractions(coreCompletionHandler) verifyNoInteractions(requestRepository) diff --git a/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt b/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt index b02f1d15c..c262ae104 100644 --- a/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt +++ b/core/src/androidTest/java/com/emarsys/core/worker/DefaultWorkerTest.kt @@ -1,13 +1,13 @@ package com.emarsys.core.worker -import android.os.Handler -import android.os.Looper import com.emarsys.core.CoreCompletionHandler +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.connection.ConnectionState import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.fake.FakeCompletionHandler +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.request.RestClient import com.emarsys.core.request.factory.CompletionHandlerProxyProvider import com.emarsys.core.request.model.RequestMethod @@ -36,11 +36,11 @@ class DefaultWorkerTest { private lateinit var mockCoreCompletionHandler: CoreCompletionHandler private lateinit var mockProxyProvider: CompletionHandlerProxyProvider private lateinit var restClient: RestClient - private lateinit var uiHandler: Handler private var now: Long = 0 private lateinit var expiredModel1: RequestModel private lateinit var expiredModel2: RequestModel private lateinit var notExpiredModel: RequestModel + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -54,12 +54,12 @@ class DefaultWorkerTest { requestRepository = mock() mockCoreCompletionHandler = mock() restClient = mock() - uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockProxyProvider = mock() worker = DefaultWorker( requestRepository, watchDogMock, - uiHandler, + concurrentHandlerHolder, mockCoreCompletionHandler, restClient, mockProxyProvider @@ -108,7 +108,7 @@ class DefaultWorkerTest { worker = DefaultWorker( requestRepository, mock(), - uiHandler, + concurrentHandlerHolder, mockCoreCompletionHandler, restClient, mockProxyProvider @@ -122,7 +122,7 @@ class DefaultWorkerTest { worker = DefaultWorker( requestRepository, watchDog, - uiHandler, + concurrentHandlerHolder, mockCoreCompletionHandler, restClient, mockProxyProvider diff --git a/core/src/main/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactory.kt b/core/src/main/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactory.kt index bdb48886e..b118f83c6 100644 --- a/core/src/main/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactory.kt +++ b/core/src/main/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactory.kt @@ -1,18 +1,16 @@ package com.emarsys.core.concurrency -import android.os.Handler import android.os.HandlerThread import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.handler.SdkHandler import java.util.* -class ConcurrentHandlerHolderFactory(private val uiHandler: Handler) { +object ConcurrentHandlerHolderFactory { fun create(): ConcurrentHandlerHolder { val handlerThread = HandlerThread("CoreSDKHandlerThread-" + UUID.randomUUID().toString()) handlerThread.start() return ConcurrentHandlerHolder( - SdkHandler(CoreHandler(handlerThread)), - SdkHandler(uiHandler) + SdkHandler(CoreHandler(handlerThread)) ) } } \ No newline at end of file diff --git a/core/src/main/java/com/emarsys/core/di/CoreComponent.kt b/core/src/main/java/com/emarsys/core/di/CoreComponent.kt index 6066dcfb2..530132187 100644 --- a/core/src/main/java/com/emarsys/core/di/CoreComponent.kt +++ b/core/src/main/java/com/emarsys/core/di/CoreComponent.kt @@ -1,7 +1,6 @@ package com.emarsys.core.di import android.content.SharedPreferences -import android.os.Handler import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog @@ -51,8 +50,6 @@ interface CoreComponent { val concurrentHandlerHolder: ConcurrentHandlerHolder - val uiHandler: Handler - val activityLifecycleWatchdog: ActivityLifecycleWatchdog val currentActivityWatchdog: CurrentActivityWatchdog diff --git a/core/src/main/java/com/emarsys/core/handler/ConcurrentHandlerHolder.kt b/core/src/main/java/com/emarsys/core/handler/ConcurrentHandlerHolder.kt index d78bade0e..0e1c26741 100644 --- a/core/src/main/java/com/emarsys/core/handler/ConcurrentHandlerHolder.kt +++ b/core/src/main/java/com/emarsys/core/handler/ConcurrentHandlerHolder.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.android.asCoroutineDispatcher @Mockable -class ConcurrentHandlerHolder(final val coreHandler: SdkHandler, final val uiHandler: SdkHandler) { +class ConcurrentHandlerHolder(final val coreHandler: SdkHandler) { fun post(runnable: Runnable) { coreHandler.post(runnable) } diff --git a/core/src/main/java/com/emarsys/core/request/RequestManager.kt b/core/src/main/java/com/emarsys/core/request/RequestManager.kt index a48ac5368..32bfafaae 100644 --- a/core/src/main/java/com/emarsys/core/request/RequestManager.kt +++ b/core/src/main/java/com/emarsys/core/request/RequestManager.kt @@ -49,13 +49,23 @@ class RequestManager( submitNow(requestModel, handler) } + fun submitNow( + requestModel: RequestModel, + completionHandler: CoreCompletionHandler + ) { + submitNow(requestModel, completionHandler, concurrentHandlerHolder.sdkScope) + } + fun submitNow( requestModel: RequestModel, completionHandler: CoreCompletionHandler, - scope: CoroutineScope = concurrentHandlerHolder.sdkScope + scope: CoroutineScope ) { val scopedHandler = - scopeDelegatorCompletionHandlerProvider.provide(completionHandler, scope) + scopeDelegatorCompletionHandlerProvider.provide( + completionHandler, + scope + ) val handler = completionHandlerProxyProvider.provideProxy(null, scopedHandler) restClient.execute(requestModel, handler) } diff --git a/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.kt b/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.kt index 4a613ed31..691977db9 100644 --- a/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.kt +++ b/core/src/main/java/com/emarsys/core/worker/CoreCompletionHandlerMiddleware.kt @@ -10,6 +10,7 @@ import com.emarsys.core.request.model.collectRequestIds import com.emarsys.core.request.model.specification.FilterByRequestIds import com.emarsys.core.response.ResponseModel import com.emarsys.core.util.RequestModelUtils +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.util.* @@ -22,7 +23,7 @@ class CoreCompletionHandlerMiddleware( ) : CoreCompletionHandler { override fun onSuccess(id: String, responseModel: ResponseModel) { - concurrentHandlerHolder.coreHandler.post { + concurrentHandlerHolder.sdkScope.launch { removeRequestModel(responseModel) worker?.unlock() worker?.run() @@ -31,7 +32,7 @@ class CoreCompletionHandlerMiddleware( } override fun onError(id: String, responseModel: ResponseModel) { - concurrentHandlerHolder.coreHandler.post { + concurrentHandlerHolder.sdkScope.launch { if (isNonRetriableError(responseModel.statusCode)) { removeRequestModel(responseModel) handleError(responseModel) @@ -65,9 +66,9 @@ class CoreCompletionHandlerMiddleware( } override fun onError(id: String, cause: Exception) { - concurrentHandlerHolder.coreHandler.post { + concurrentHandlerHolder.sdkScope.launch { worker?.unlock() - concurrentHandlerHolder.uiHandler.post { + concurrentHandlerHolder.uiScope.launch { coreCompletionHandler?.onError( id, cause @@ -86,7 +87,7 @@ class CoreCompletionHandlerMiddleware( private fun handleSuccess(responseModel: ResponseModel) { for (id in RequestModelUtils.extractIdsFromCompositeRequestModel(responseModel.requestModel)) { - concurrentHandlerHolder.uiHandler.post { + concurrentHandlerHolder.uiScope.launch { coreCompletionHandler?.onSuccess( id, responseModel @@ -97,7 +98,7 @@ class CoreCompletionHandlerMiddleware( private fun handleError(responseModel: ResponseModel) { for (id in RequestModelUtils.extractIdsFromCompositeRequestModel(responseModel.requestModel)) { - concurrentHandlerHolder.uiHandler.post { + concurrentHandlerHolder.uiScope.launch { coreCompletionHandler?.onError( id, responseModel diff --git a/core/src/main/java/com/emarsys/core/worker/DefaultWorker.kt b/core/src/main/java/com/emarsys/core/worker/DefaultWorker.kt index b6fd5cf5e..2938891b1 100644 --- a/core/src/main/java/com/emarsys/core/worker/DefaultWorker.kt +++ b/core/src/main/java/com/emarsys/core/worker/DefaultWorker.kt @@ -1,6 +1,5 @@ package com.emarsys.core.worker -import android.os.Handler import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.Mockable import com.emarsys.core.connection.ConnectionChangeListener @@ -9,6 +8,7 @@ import com.emarsys.core.connection.ConnectionWatchDog import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification import com.emarsys.core.database.repository.specification.Everything +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.request.RequestExpiredException import com.emarsys.core.request.RestClient import com.emarsys.core.request.factory.CompletionHandlerProxyProvider @@ -17,13 +17,14 @@ import com.emarsys.core.request.model.specification.FilterByRequestIds import com.emarsys.core.request.model.specification.QueryLatestRequestModel import com.emarsys.core.util.log.Logger.Companion.debug import com.emarsys.core.util.log.entry.OfflineQueueSize +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @Mockable class DefaultWorker( var requestRepository: Repository, var connectionWatchDog: ConnectionWatchDog, - val uiHandler: Handler, + val concurrentHandlerHolder: ConcurrentHandlerHolder, var coreCompletionHandler: CoreCompletionHandler, var restClient: RestClient, val proxyProvider: CompletionHandlerProxyProvider @@ -93,7 +94,7 @@ class DefaultWorker( runBlocking { requestRepository.remove(FilterByRequestIds(ids)) } - uiHandler.post { + concurrentHandlerHolder.uiScope.launch { coreCompletionHandler.onError( expiredModel.id, RequestExpiredException("Request expired", expiredModel.url.path) diff --git a/emarsys-firebase/src/androidTest/java/com/emarsys/fake/FakeFirebaseDependencyContainer.kt b/emarsys-firebase/src/androidTest/java/com/emarsys/fake/FakeFirebaseDependencyContainer.kt index d2cd8fb6e..24d74c34c 100644 --- a/emarsys-firebase/src/androidTest/java/com/emarsys/fake/FakeFirebaseDependencyContainer.kt +++ b/emarsys-firebase/src/androidTest/java/com/emarsys/fake/FakeFirebaseDependencyContainer.kt @@ -2,8 +2,6 @@ package com.emarsys.fake import android.app.Activity import android.content.SharedPreferences -import android.os.Handler -import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog @@ -25,7 +23,6 @@ import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient -import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.shard.ShardModel @@ -65,14 +62,10 @@ import com.emarsys.mobileengage.session.MobileEngageSession import com.emarsys.mobileengage.session.SessionIdHolder import com.emarsys.mobileengage.util.RequestModelHelper import com.google.android.gms.location.FusedLocationProviderClient -import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock class FakeFirebaseDependencyContainer( - override val uiHandler: Handler = Handler(Looper.getMainLooper()), - override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( - uiHandler - ).create(), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory.create(), override val mobileEngageInternal: MobileEngageInternal = mock(), override val loggingMobileEngageInternal: MobileEngageInternal = mock(), override val clientServiceInternal: ClientServiceInternal = mock(), @@ -158,8 +151,5 @@ class FakeFirebaseDependencyContainer( override val geofenceInitialEnterTriggerEnabledStorage: Storage = mock(), override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), - override val notificationOpenedActivityClass: Class<*> = Activity::class.java, - override val coreSdkScope: CoroutineScope = mock(), - override val uiScope: CoroutineScope = mock(), - override val runnableFactory: RunnableFactory = mock() + override val notificationOpenedActivityClass: Class<*> = Activity::class.java ) : MobileEngageComponent \ No newline at end of file diff --git a/emarsys-firebase/src/androidTest/java/com/emarsys/service/EmarsysFirebaseMessagingServiceTest.kt b/emarsys-firebase/src/androidTest/java/com/emarsys/service/EmarsysFirebaseMessagingServiceTest.kt index 4e9aac63b..899ec5379 100644 --- a/emarsys-firebase/src/androidTest/java/com/emarsys/service/EmarsysFirebaseMessagingServiceTest.kt +++ b/emarsys-firebase/src/androidTest/java/com/emarsys/service/EmarsysFirebaseMessagingServiceTest.kt @@ -1,8 +1,6 @@ package com.emarsys.service import android.app.Application -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.device.DeviceInfo import com.emarsys.core.handler.ConcurrentHandlerHolder @@ -34,16 +32,14 @@ class EmarsysFirebaseMessagingServiceTest { private lateinit var mockPushInternal: PushInternal private lateinit var fakeDependencyContainer: FakeFirebaseDependencyContainer - private lateinit var concurrentHandlerHolderFactory: ConcurrentHandlerHolderFactory private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var spyCoreHandler: SdkHandler @Before fun setUp() { mockPushInternal = mock() - concurrentHandlerHolderFactory = - ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())) - concurrentHandlerHolder = concurrentHandlerHolderFactory.create() + + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() spyCoreHandler = spy(concurrentHandlerHolder.coreHandler) ReflectionTestUtils.setInstanceField( concurrentHandlerHolder, @@ -87,19 +83,19 @@ class EmarsysFirebaseMessagingServiceTest { private fun setupEmarsys(isAutomaticPushSending: Boolean) { val deviceInfo = DeviceInfo( - application, - mock { - on { provideHardwareId() } doReturn "hardwareId" - }, - mock { - on { provideSdkVersion() } doReturn "version" - }, - mock { - on { provideLanguage(any()) } doReturn "language" - }, - mock(), - isAutomaticPushSending, - true + application, + mock { + on { provideHardwareId() } doReturn "hardwareId" + }, + mock { + on { provideSdkVersion() } doReturn "version" + }, + mock { + on { provideLanguage(any()) } doReturn "language" + }, + mock(), + isAutomaticPushSending, + true ) fakeDependencyContainer = FakeFirebaseDependencyContainer( diff --git a/emarsys-huawei/src/androidTest/java/com/emarsys/fake/FakeHuaweiDependencyContainer.kt b/emarsys-huawei/src/androidTest/java/com/emarsys/fake/FakeHuaweiDependencyContainer.kt index 15241fbb4..919bccb93 100644 --- a/emarsys-huawei/src/androidTest/java/com/emarsys/fake/FakeHuaweiDependencyContainer.kt +++ b/emarsys-huawei/src/androidTest/java/com/emarsys/fake/FakeHuaweiDependencyContainer.kt @@ -2,8 +2,6 @@ package com.emarsys.fake import android.app.Activity import android.content.SharedPreferences -import android.os.Handler -import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog @@ -25,7 +23,6 @@ import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient -import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.shard.ShardModel @@ -65,14 +62,10 @@ import com.emarsys.mobileengage.session.MobileEngageSession import com.emarsys.mobileengage.session.SessionIdHolder import com.emarsys.mobileengage.util.RequestModelHelper import com.google.android.gms.location.FusedLocationProviderClient -import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock class FakeHuaweiDependencyContainer( - override val uiHandler: Handler = Handler(Looper.getMainLooper()), - override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( - uiHandler - ).create(), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory.create(), override val mobileEngageInternal: MobileEngageInternal = mock(), override val loggingMobileEngageInternal: MobileEngageInternal = mock(), override val clientServiceInternal: ClientServiceInternal = mock(), @@ -159,7 +152,4 @@ class FakeHuaweiDependencyContainer( override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), override val notificationOpenedActivityClass: Class<*> = Activity::class.java, - override val coreSdkScope: CoroutineScope = mock(), - override val uiScope: CoroutineScope = mock(), - override val runnableFactory: RunnableFactory = mock() ) : MobileEngageComponent \ No newline at end of file diff --git a/emarsys-huawei/src/androidTest/java/com/emarsys/service/EmarsysHuaweiMessagingServiceTest.kt b/emarsys-huawei/src/androidTest/java/com/emarsys/service/EmarsysHuaweiMessagingServiceTest.kt index dc0aa049c..4f199e187 100644 --- a/emarsys-huawei/src/androidTest/java/com/emarsys/service/EmarsysHuaweiMessagingServiceTest.kt +++ b/emarsys-huawei/src/androidTest/java/com/emarsys/service/EmarsysHuaweiMessagingServiceTest.kt @@ -1,7 +1,6 @@ package com.emarsys.service import android.app.Application -import android.os.Handler import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.device.DeviceInfo @@ -34,16 +33,14 @@ class EmarsysHuaweiMessagingServiceTest { private lateinit var mockPushInternal: PushInternal private lateinit var fakeDependencyContainer: FakeHuaweiDependencyContainer private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder - private lateinit var concurrentHandlerHolderFactory: ConcurrentHandlerHolderFactory private lateinit var emarsysHuaweiMessagingService: EmarsysHuaweiMessagingService private lateinit var spyCoreHandler: SdkHandler @Before fun setUp() { mockPushInternal = mock() - concurrentHandlerHolderFactory = - ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())) - concurrentHandlerHolder = concurrentHandlerHolderFactory.create() + + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() spyCoreHandler = spy(concurrentHandlerHolder.coreHandler) ReflectionTestUtils.setInstanceField( concurrentHandlerHolder, 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 6992c8b9b..282bb910d 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/di/FakeDependencyContainer.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/di/FakeDependencyContainer.kt @@ -2,8 +2,6 @@ package com.emarsys.di import android.app.Activity import android.content.SharedPreferences -import android.os.Handler -import android.os.Looper import com.emarsys.clientservice.ClientServiceApi import com.emarsys.config.ConfigApi import com.emarsys.config.ConfigInternal @@ -82,10 +80,7 @@ import com.google.android.gms.location.FusedLocationProviderClient import org.mockito.kotlin.mock class FakeDependencyContainer( - override val uiHandler: Handler = Handler(Looper.getMainLooper()), - override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( - uiHandler - ).create(), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory.create(), override val messageInbox: MessageInboxApi = mock(), override val loggingMessageInbox: MessageInboxApi = mock(), override val deepLink: DeepLinkApi = mock(), diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/fake/FakeRestClient.java b/emarsys-sdk/src/androidTest/java/com/emarsys/fake/FakeRestClient.java index 401436b97..1588836b1 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/fake/FakeRestClient.java +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/fake/FakeRestClient.java @@ -34,7 +34,7 @@ public FakeRestClient(ResponseModel returnValue, Mode mode) { @SuppressWarnings("unchecked") public FakeRestClient(List responses, Mode mode) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); + ConcurrentHandlerHolderFactory.INSTANCE.create()); this.responses = new ArrayList<>(responses); this.mode = mode; } @@ -51,7 +51,7 @@ public FakeRestClient(Exception exception) { @SuppressWarnings("unchecked") public FakeRestClient(List exceptions) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); + ConcurrentHandlerHolderFactory.INSTANCE.create()); this.exceptions = new ArrayList<>(exceptions); this.mode = Mode.ERROR_EXCEPTION; } diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/inapp/ui/InlineInAppViewTest.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/inapp/ui/InlineInAppViewTest.kt index 1720736d5..29ab74b3f 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/inapp/ui/InlineInAppViewTest.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/inapp/ui/InlineInAppViewTest.kt @@ -119,20 +119,19 @@ class InlineInAppViewTest { mockRequestManager = spy( RequestManager( - ConcurrentHandlerHolderFactory(uiHandler).create(), + ConcurrentHandlerHolderFactory.create(), mock(), mock(), mock(), FakeRestClient( mockResponseModel, FakeRestClient.Mode.SUCCESS, - ConcurrentHandlerHolderFactory(uiHandler).create().coreHandler.handler + ConcurrentHandlerHolderFactory.create().coreHandler.handler ), mock(), mock(), mockProvider, - mockScopeDelegatorCompletionHandlerProvider, - mock() + mockScopeDelegatorCompletionHandlerProvider ) ) mockRequestModelFactory = mock { @@ -297,7 +296,7 @@ class InlineInAppViewTest { whenever(mockResponseModel.statusCode).thenReturn(expectedStatusCode) mockRequestManager = spy( RequestManager( - ConcurrentHandlerHolderFactory(uiHandler).create(), + ConcurrentHandlerHolderFactory.create(), mock(), mock(), mock(), @@ -305,8 +304,7 @@ class InlineInAppViewTest { mock(), mock(), mockProvider, - mockScopeDelegatorCompletionHandlerProvider, - mock() + mockScopeDelegatorCompletionHandlerProvider ) ) setupEmarsysComponent( @@ -346,7 +344,7 @@ class InlineInAppViewTest { val expectedException = Exception("Error happened") mockRequestManager = spy( RequestManager( - ConcurrentHandlerHolderFactory(uiHandler).create(), + ConcurrentHandlerHolderFactory.create(), mock(), mock(), mock(), @@ -354,8 +352,7 @@ class InlineInAppViewTest { mock(), mock(), mockProvider, - mockScopeDelegatorCompletionHandlerProvider, - mock() + mockScopeDelegatorCompletionHandlerProvider ) ) setupEmarsysComponent( diff --git a/emarsys-sdk/src/androidTest/java/com/emarsys/testUtil/IntegrationTestUtils.kt b/emarsys-sdk/src/androidTest/java/com/emarsys/testUtil/IntegrationTestUtils.kt index eb5fddd28..1cd3d8f77 100644 --- a/emarsys-sdk/src/androidTest/java/com/emarsys/testUtil/IntegrationTestUtils.kt +++ b/emarsys-sdk/src/androidTest/java/com/emarsys/testUtil/IntegrationTestUtils.kt @@ -11,6 +11,7 @@ import com.emarsys.Emarsys import com.emarsys.di.emarsys import com.emarsys.di.tearDownEmarsysComponent import io.kotlintest.shouldBe +import kotlinx.coroutines.launch import java.util.concurrent.CountDownLatch object IntegrationTestUtils { @@ -61,17 +62,17 @@ object IntegrationTestUtils { latch.await() latch = CountDownLatch(1) - emarsys().uiHandler.post { + emarsys().concurrentHandlerHolder.uiScope.launch { val observerMap = ReflectionTestUtils.getInstanceField>( ProcessLifecycleOwner.get().lifecycle, "mObserverMap" ) if (observerMap != null) { - ReflectionTestUtils.getInstanceField>( - observerMap, - "mHashMap" - )?.entries?.toMutableList()?.forEach { - ProcessLifecycleOwner.get().lifecycle.removeObserver(it.key as LifecycleObserver) + ReflectionTestUtils.getInstanceField>( + observerMap, + "mHashMap" + )?.entries?.toMutableList()?.forEach { + ProcessLifecycleOwner.get().lifecycle.removeObserver(it.key as LifecycleObserver) } } latch.countDown() diff --git a/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt b/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt index a6d369516..afdd647c0 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/Emarsys.kt @@ -27,6 +27,7 @@ import com.emarsys.mobileengage.di.mobileEngage import com.emarsys.oneventaction.OnEventActionApi import com.emarsys.predict.PredictApi import com.emarsys.push.PushApi +import kotlinx.coroutines.launch object Emarsys { @@ -78,7 +79,7 @@ object Emarsys { DefaultEmarsysDependencies(emarsysConfig) } - emarsys().uiHandler.post { + emarsys().concurrentHandlerHolder.uiScope.launch { try { registerLifecycleObservers() } catch (e: Throwable) { 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 934c04365..86a4b0dfa 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysComponent.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysComponent.kt @@ -3,7 +3,6 @@ package com.emarsys.di import android.app.NotificationManager import android.content.Context import android.content.SharedPreferences -import android.os.Handler import android.util.Base64 import android.util.Log import androidx.core.app.NotificationManagerCompat @@ -155,10 +154,8 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val notificationOpenedActivityClass: Class<*> get() = com.emarsys.NotificationOpenedActivity::class.java - override val uiHandler: Handler = Handler(config.application.mainLooper) - final override val concurrentHandlerHolder: ConcurrentHandlerHolder = - ConcurrentHandlerHolderFactory(uiHandler).create() + ConcurrentHandlerHolderFactory.create() override val deepLink: DeepLinkApi = (DeepLink() as DeepLinkApi).proxyApi(concurrentHandlerHolder) @@ -231,10 +228,9 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val overlayInAppPresenter: OverlayInAppPresenter by lazy { OverlayInAppPresenter( concurrentHandlerHolder, - uiHandler, - IamStaticWebViewProvider(config.application, uiHandler), + IamStaticWebViewProvider(config.application, concurrentHandlerHolder), inAppInternal, - IamDialogProvider(uiHandler, timestampProvider), + IamDialogProvider(concurrentHandlerHolder, timestampProvider), buttonClickedRepository, displayedIamRepository, timestampProvider, @@ -298,7 +294,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { config.application, eventServiceInternal, onEventActionCacheableEventHandler, - uiHandler + concurrentHandlerHolder ), displayedIamRepository, eventServiceInternal, @@ -530,7 +526,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { DefaultWorker( requestModelRepository, connectionWatchdog, - uiHandler, + concurrentHandlerHolder, coreCompletionHandler, restClient, coreCompletionHandlerRefreshTokenProxyProvider @@ -648,7 +644,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { override val pushInternal: PushInternal by lazy { DefaultPushInternal( requestManager, - uiHandler, + concurrentHandlerHolder, mobileEngageRequestModelFactory, eventServiceInternal, pushTokenStorage, @@ -684,7 +680,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { } override val iamJsBridgeFactory: IamJsBridgeFactory by lazy { - IamJsBridgeFactory(uiHandler) + IamJsBridgeFactory(concurrentHandlerHolder) } override val deviceInfoPayloadStorage: Storage by lazy { @@ -708,7 +704,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { config.application, eventServiceInternal, notificationCacheableEventHandler, - uiHandler + concurrentHandlerHolder ) } @@ -717,7 +713,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { config.application, eventServiceInternal, silentMessageCacheableEventHandler, - uiHandler + concurrentHandlerHolder ) } @@ -732,7 +728,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { config.application, eventServiceInternal, geofenceCacheableEventHandler, - uiHandler + concurrentHandlerHolder ) DefaultGeofenceInternal( @@ -749,7 +745,6 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { BooleanStorage(MobileEngageStorageKey.GEOFENCE_ENABLED, sharedPreferences), GeofencePendingIntentProvider(config.application), concurrentHandlerHolder, - uiHandler, geofenceInitialEnterTriggerEnabledStorage ) } @@ -763,7 +758,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { } override val inlineInAppWebViewFactory: InlineInAppWebViewFactory by lazy { - InlineInAppWebViewFactory(webViewProvider, uiHandler) + InlineInAppWebViewFactory(webViewProvider, concurrentHandlerHolder) } override val fileDownloader: FileDownloader by lazy { @@ -864,7 +859,7 @@ open class DefaultEmarsysComponent(config: EmarsysConfig) : EmarsysComponent { DefaultPredictInternal( predictRequestContext, requestManager, - uiHandler, + concurrentHandlerHolder, predictRequestModelBuilderProvider, PredictResponseMapper() ) diff --git a/emarsys-sdk/src/main/java/com/emarsys/inapp/ui/InlineInAppView.kt b/emarsys-sdk/src/main/java/com/emarsys/inapp/ui/InlineInAppView.kt index c96a68382..988015914 100644 --- a/emarsys-sdk/src/main/java/com/emarsys/inapp/ui/InlineInAppView.kt +++ b/emarsys-sdk/src/main/java/com/emarsys/inapp/ui/InlineInAppView.kt @@ -21,6 +21,7 @@ import com.emarsys.mobileengage.iam.jsbridge.JSCommandFactory import com.emarsys.mobileengage.iam.jsbridge.OnAppEventListener import com.emarsys.mobileengage.iam.jsbridge.OnCloseListener import com.emarsys.mobileengage.iam.model.InAppMessage +import kotlinx.coroutines.launch import org.json.JSONArray import org.json.JSONObject import java.util.* @@ -77,7 +78,7 @@ class InlineInAppView : LinearLayout { onCompletionListener?.onCompleted(IllegalArgumentException("WebView can not be created, please try again later!")) } else { fetchInlineInAppMessage(viewId) { html -> - mobileEngage().uiHandler.post { + mobileEngage().concurrentHandlerHolder.uiScope.launch { if (html != null) { webView?.loadDataWithBaseURL( null, @@ -158,7 +159,6 @@ class InlineInAppView : LinearLayout { val jsCommandFactory = JSCommandFactory( mobileEngage().currentActivityProvider, - mobileEngage().uiHandler, mobileEngage().concurrentHandlerHolder, inAppInternal, buttonClickedRepository, @@ -175,7 +175,7 @@ class InlineInAppView : LinearLayout { ) val latch = CountDownLatch(1) - mobileEngage().uiHandler.post { + mobileEngage().concurrentHandlerHolder.uiScope.launch { webView?.addJavascriptInterface(jsBridge, "Android") latch.countDown() } diff --git a/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt b/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt index 40fff8fc3..e5186d7c8 100644 --- a/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt +++ b/emarsys/src/androidTest/java/com/emarsys/config/DefaultConfigInternalTest.kt @@ -9,6 +9,7 @@ import com.emarsys.core.api.notification.NotificationSettings 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.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.crypto.Crypto import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification @@ -37,9 +38,6 @@ import com.emarsys.testUtil.FeatureTestUtils import com.emarsys.testUtil.TimeoutUtils import com.emarsys.testUtil.mockito.ThreadSpy import io.kotlintest.shouldBe -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import org.junit.After import org.junit.Before import org.junit.Rule @@ -87,7 +85,7 @@ class DefaultConfigInternalTest { private lateinit var mockCrypto: Crypto private lateinit var mockClientServiceInternal: ClientServiceInternal private lateinit var mockCompletionListener: CompletionListener - private lateinit var mockConcurrentHandlerHolder: ConcurrentHandlerHolder + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -97,6 +95,7 @@ class DefaultConfigInternalTest { @Suppress("UNCHECKED_CAST") fun setUp() { FeatureTestUtils.resetFeatures() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() latch = CountDownLatch(1) @@ -158,15 +157,9 @@ class DefaultConfigInternalTest { } } - val scope = CoroutineScope(Job() + Dispatchers.Default) - val mainScope = CoroutineScope(Job() + Dispatchers.Main) - - mockConcurrentHandlerHolder = mock { - on { it.sdkScope } doReturn scope - on { it.uiScope } doReturn mainScope - } - - configInternal = spy(DefaultConfigInternal(mockMobileEngageRequestContext, + configInternal = spy( + DefaultConfigInternal( + mockMobileEngageRequestContext, mockMobileEngageInternal, mockPushInternal, mockPredictRequestContext, @@ -182,7 +175,9 @@ class DefaultConfigInternalTest { mockLogLevelStorage, mockCrypto, mockClientServiceInternal, - mockConcurrentHandlerHolder)) + concurrentHandlerHolder + ) + ) } @After @@ -236,7 +231,8 @@ class DefaultConfigInternalTest { } latch.await() - val inOrder = inOrder(mockMobileEngageInternal, mockPushInternal, mockMobileEngageRequestContext) + val inOrder = + inOrder(mockMobileEngageInternal, mockPushInternal, mockMobileEngageRequestContext) inOrder.verify(mockPushInternal).clearPushToken(any()) inOrder.verify(mockMobileEngageInternal).clearContact(any()) inOrder.verify(mockMobileEngageRequestContext).applicationCode = OTHER_APPLICATION_CODE @@ -256,23 +252,25 @@ class DefaultConfigInternalTest { (invocation.getArgument(0) as CompletionListener).onCompleted(Throwable()) } - configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, - mockMobileEngageInternal, - mockPushInternal, - mockPredictRequestContext, - mockDeviceInfo, - mockRequestManager, - mockEmarsysRequestModelFactory, - mockConfigResponseMapper, - mockClientServiceStorage, - mockEventServiceStorage, - mockDeeplinkServiceStorage, - mockPredictServiceStorage, - mockMessageInboxServiceStorage, - mockLogLevelStorage, - mockCrypto, - mockClientServiceInternal, - mockConcurrentHandlerHolder) + configInternal = DefaultConfigInternal( + mockMobileEngageRequestContext, + mockMobileEngageInternal, + mockPushInternal, + mockPredictRequestContext, + mockDeviceInfo, + mockRequestManager, + mockEmarsysRequestModelFactory, + mockConfigResponseMapper, + mockClientServiceStorage, + mockEventServiceStorage, + mockDeeplinkServiceStorage, + mockPredictServiceStorage, + mockMessageInboxServiceStorage, + mockLogLevelStorage, + mockCrypto, + mockClientServiceInternal, + concurrentHandlerHolder + ) val latch = CountDownLatch(1) val completionListener = CompletionListener { latch.countDown() @@ -306,23 +304,25 @@ class DefaultConfigInternalTest { whenever(mockPushInternal.pushToken).thenReturn(PUSH_TOKEN) - configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, - mockMobileEngageInternal, - mockPushInternal, - mockPredictRequestContext, - mockDeviceInfo, - mockRequestManager, - mockEmarsysRequestModelFactory, - mockConfigResponseMapper, - mockClientServiceStorage, - mockEventServiceStorage, - mockDeeplinkServiceStorage, - mockPredictServiceStorage, - mockMessageInboxServiceStorage, - mockLogLevelStorage, - mockCrypto, - mockClientServiceInternal, - mockConcurrentHandlerHolder) + configInternal = DefaultConfigInternal( + mockMobileEngageRequestContext, + mockMobileEngageInternal, + mockPushInternal, + mockPredictRequestContext, + mockDeviceInfo, + mockRequestManager, + mockEmarsysRequestModelFactory, + mockConfigResponseMapper, + mockClientServiceStorage, + mockEventServiceStorage, + mockDeeplinkServiceStorage, + mockPredictServiceStorage, + mockMessageInboxServiceStorage, + mockLogLevelStorage, + mockCrypto, + mockClientServiceInternal, + concurrentHandlerHolder + ) configInternal.changeApplicationCode(OTHER_APPLICATION_CODE) { latch.countDown() @@ -356,23 +356,25 @@ class DefaultConfigInternalTest { } whenever(mockPushInternal.pushToken).thenReturn(PUSH_TOKEN) - configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, - mockMobileEngageInternal, - mockPushInternal, - mockPredictRequestContext, - mockDeviceInfo, - mockRequestManager, - mockEmarsysRequestModelFactory, - mockConfigResponseMapper, - mockClientServiceStorage, - mockEventServiceStorage, - mockDeeplinkServiceStorage, - mockPredictServiceStorage, - mockMessageInboxServiceStorage, - mockLogLevelStorage, - mockCrypto, - mockClientServiceInternal, - mockConcurrentHandlerHolder) + configInternal = DefaultConfigInternal( + mockMobileEngageRequestContext, + mockMobileEngageInternal, + mockPushInternal, + mockPredictRequestContext, + mockDeviceInfo, + mockRequestManager, + mockEmarsysRequestModelFactory, + mockConfigResponseMapper, + mockClientServiceStorage, + mockEventServiceStorage, + mockDeeplinkServiceStorage, + mockPredictServiceStorage, + mockMessageInboxServiceStorage, + mockLogLevelStorage, + mockCrypto, + mockClientServiceInternal, + concurrentHandlerHolder + ) configInternal.changeApplicationCode(OTHER_APPLICATION_CODE) { latch.countDown() @@ -393,10 +395,12 @@ class DefaultConfigInternalTest { configInternal.changeApplicationCode(OTHER_APPLICATION_CODE, null) - val inOrder = inOrder(mockMobileEngageInternal, mockPushInternal, mockMobileEngageRequestContext) + val inOrder = + inOrder(mockMobileEngageInternal, mockPushInternal, mockMobileEngageRequestContext) inOrder.verify(mockPushInternal, timeout(50)).clearPushToken(any()) inOrder.verify(mockMobileEngageInternal, timeout(50)).clearContact(any()) - inOrder.verify(mockMobileEngageRequestContext, timeout(50)).applicationCode = OTHER_APPLICATION_CODE + inOrder.verify(mockMobileEngageRequestContext, timeout(50)).applicationCode = + OTHER_APPLICATION_CODE inOrder.verify(mockPushInternal, timeout(50)).setPushToken(eq(PUSH_TOKEN), any()) } @@ -497,7 +501,12 @@ class DefaultConfigInternalTest { } latch.await() - val inOrder = inOrder(mockMobileEngageInternal, mockPushInternal, mockMobileEngageRequestContext, mockClientServiceInternal) + val inOrder = inOrder( + mockMobileEngageInternal, + mockPushInternal, + mockMobileEngageRequestContext, + mockClientServiceInternal + ) inOrder.verify(mockPushInternal).pushToken inOrder.verify(mockMobileEngageInternal).clearContact(any()) inOrder.verify(mockMobileEngageRequestContext).applicationCode = OTHER_APPLICATION_CODE @@ -513,7 +522,11 @@ class DefaultConfigInternalTest { latch.countDown() } latch.await() - val inOrder = inOrder(mockMobileEngageInternal, mockMobileEngageRequestContext, mockClientServiceInternal) + val inOrder = inOrder( + mockMobileEngageInternal, + mockMobileEngageRequestContext, + mockClientServiceInternal + ) inOrder.verify(mockMobileEngageRequestContext).applicationCode = OTHER_APPLICATION_CODE inOrder.verify(mockClientServiceInternal).trackDeviceInfo(any()) inOrder.verify(mockMobileEngageInternal).clearContact(any()) @@ -527,7 +540,11 @@ class DefaultConfigInternalTest { latch.countDown() } latch.await() - val inOrder = inOrder(mockMobileEngageInternal, mockMobileEngageRequestContext, mockClientServiceInternal) + val inOrder = inOrder( + mockMobileEngageInternal, + mockMobileEngageRequestContext, + mockClientServiceInternal + ) inOrder.verify(mockMobileEngageInternal).clearContact(any()) inOrder.verify(mockMobileEngageRequestContext).applicationCode = OTHER_APPLICATION_CODE inOrder.verify(mockClientServiceInternal).trackDeviceInfo(any()) @@ -617,40 +634,50 @@ class DefaultConfigInternalTest { (configInternal as DefaultConfigInternal).fetchRemoteConfig { } - verify(mockRequestManager).submitNow(eq(requestModel), any(), anyOrNull()) + verify(mockRequestManager).submitNow(eq(requestModel), any()) } @Test fun testFetchRemoteConfigSignature_shouldCallRequestManager_withCorrectRequestModel() { val requestModel: RequestModel = mock() - whenever(mockEmarsysRequestModelFactory.createRemoteConfigSignatureRequest()).thenReturn(requestModel) + whenever(mockEmarsysRequestModelFactory.createRemoteConfigSignatureRequest()).thenReturn( + requestModel + ) configInternal.refreshRemoteConfig(null) - verify(mockRequestManager).submitNow(eq(requestModel), any(), anyOrNull()) + verify(mockRequestManager).submitNow(eq(requestModel), any()) } @Test fun testFetchRemoteConfig_shouldCallConfigResponseMapper_onSuccess() { - val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) + val resultListener = + FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) - val configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, - mockMobileEngageInternal, - mockPushInternal, - mockPredictRequestContext, - mockDeviceInfo, - requestManagerWithRestClient(FakeRestClient(mockResponseModel, FakeRestClient.Mode.SUCCESS)), - mockEmarsysRequestModelFactory, - mockConfigResponseMapper, - mockClientServiceStorage, - mockEventServiceStorage, - mockDeeplinkServiceStorage, - mockPredictServiceStorage, - mockMessageInboxServiceStorage, - mockLogLevelStorage, - mockCrypto, - mockClientServiceInternal, - mockConcurrentHandlerHolder) + val configInternal = DefaultConfigInternal( + mockMobileEngageRequestContext, + mockMobileEngageInternal, + mockPushInternal, + mockPredictRequestContext, + mockDeviceInfo, + requestManagerWithRestClient( + FakeRestClient( + response = mockResponseModel, + mode = FakeRestClient.Mode.SUCCESS + ) + ), + mockEmarsysRequestModelFactory, + mockConfigResponseMapper, + mockClientServiceStorage, + mockEventServiceStorage, + mockDeeplinkServiceStorage, + mockPredictServiceStorage, + mockMessageInboxServiceStorage, + mockLogLevelStorage, + mockCrypto, + mockClientServiceInternal, + concurrentHandlerHolder + ) configInternal.fetchRemoteConfig(resultListener) @@ -663,25 +690,32 @@ class DefaultConfigInternalTest { @Test fun testFetchRemoteConfig_shouldCallConfigResponseMapper_onFailure() { configInternal = DefaultConfigInternal( - mockMobileEngageRequestContext, - mockMobileEngageInternal, - mockPushInternal, - mockPredictRequestContext, - mockDeviceInfo, - requestManagerWithRestClient(FakeRestClient(mockResponseModel, FakeRestClient.Mode.ERROR_RESPONSE_MODEL)), - mockEmarsysRequestModelFactory, - mockConfigResponseMapper, - mockClientServiceStorage, - mockEventServiceStorage, - mockDeeplinkServiceStorage, - mockPredictServiceStorage, - mockMessageInboxServiceStorage, - mockLogLevelStorage, - mockCrypto, - mockClientServiceInternal, - mockConcurrentHandlerHolder) + mockMobileEngageRequestContext, + mockMobileEngageInternal, + mockPushInternal, + mockPredictRequestContext, + mockDeviceInfo, + requestManagerWithRestClient( + FakeRestClient( + response = mockResponseModel, + mode = FakeRestClient.Mode.ERROR_RESPONSE_MODEL + ) + ), + mockEmarsysRequestModelFactory, + mockConfigResponseMapper, + mockClientServiceStorage, + mockEventServiceStorage, + mockDeeplinkServiceStorage, + mockPredictServiceStorage, + mockMessageInboxServiceStorage, + mockLogLevelStorage, + mockCrypto, + mockClientServiceInternal, + concurrentHandlerHolder + ) - val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) + val resultListener = + FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) (configInternal as DefaultConfigInternal).fetchRemoteConfig(resultListener) latch.await() @@ -694,25 +728,27 @@ class DefaultConfigInternalTest { val mockException: Exception = mock() val configInternal = DefaultConfigInternal( - mockMobileEngageRequestContext, - mockMobileEngageInternal, - mockPushInternal, - mockPredictRequestContext, - mockDeviceInfo, - requestManagerWithRestClient(FakeRestClient(mockException)), - mockEmarsysRequestModelFactory, - mockConfigResponseMapper, - mockClientServiceStorage, - mockEventServiceStorage, - mockDeeplinkServiceStorage, - mockPredictServiceStorage, - mockMessageInboxServiceStorage, - mockLogLevelStorage, - mockCrypto, - mockClientServiceInternal, - mockConcurrentHandlerHolder) + mockMobileEngageRequestContext, + mockMobileEngageInternal, + mockPushInternal, + mockPredictRequestContext, + mockDeviceInfo, + requestManagerWithRestClient(FakeRestClient(exception = mockException)), + mockEmarsysRequestModelFactory, + mockConfigResponseMapper, + mockClientServiceStorage, + mockEventServiceStorage, + mockDeeplinkServiceStorage, + mockPredictServiceStorage, + mockMessageInboxServiceStorage, + mockLogLevelStorage, + mockCrypto, + mockClientServiceInternal, + concurrentHandlerHolder + ) - val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) + val resultListener = + FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) configInternal.fetchRemoteConfig(resultListener) @@ -727,23 +763,30 @@ class DefaultConfigInternalTest { val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) whenever(mockResponseModel.body).thenReturn("signature") - val configInternal = DefaultConfigInternal(mockMobileEngageRequestContext, - mockMobileEngageInternal, - mockPushInternal, - mockPredictRequestContext, - mockDeviceInfo, - requestManagerWithRestClient(FakeRestClient(mockResponseModel, FakeRestClient.Mode.SUCCESS)), - mockEmarsysRequestModelFactory, - mockConfigResponseMapper, - mockClientServiceStorage, - mockEventServiceStorage, - mockDeeplinkServiceStorage, - mockPredictServiceStorage, - mockMessageInboxServiceStorage, - mockLogLevelStorage, - mockCrypto, - mockClientServiceInternal, - mockConcurrentHandlerHolder) + val configInternal = DefaultConfigInternal( + mockMobileEngageRequestContext, + mockMobileEngageInternal, + mockPushInternal, + mockPredictRequestContext, + mockDeviceInfo, + requestManagerWithRestClient( + FakeRestClient( + response = mockResponseModel, + mode = FakeRestClient.Mode.SUCCESS + ) + ), + mockEmarsysRequestModelFactory, + mockConfigResponseMapper, + mockClientServiceStorage, + mockEventServiceStorage, + mockDeeplinkServiceStorage, + mockPredictServiceStorage, + mockMessageInboxServiceStorage, + mockLogLevelStorage, + mockCrypto, + mockClientServiceInternal, + concurrentHandlerHolder + ) configInternal.fetchRemoteConfigSignature(resultListener) @@ -756,23 +799,29 @@ class DefaultConfigInternalTest { @Test fun testFetchRemoteConfigSignature_shouldCallConfigResponseMapper_onFailure() { configInternal = DefaultConfigInternal( - mockMobileEngageRequestContext, - mockMobileEngageInternal, - mockPushInternal, - mockPredictRequestContext, - mockDeviceInfo, - requestManagerWithRestClient(FakeRestClient(mockResponseModel, FakeRestClient.Mode.ERROR_RESPONSE_MODEL)), - mockEmarsysRequestModelFactory, - mockConfigResponseMapper, - mockClientServiceStorage, - mockEventServiceStorage, - mockDeeplinkServiceStorage, - mockPredictServiceStorage, - mockMessageInboxServiceStorage, - mockLogLevelStorage, - mockCrypto, - mockClientServiceInternal, - mockConcurrentHandlerHolder) + mockMobileEngageRequestContext, + mockMobileEngageInternal, + mockPushInternal, + mockPredictRequestContext, + mockDeviceInfo, + requestManagerWithRestClient( + FakeRestClient( + response = mockResponseModel, + mode = FakeRestClient.Mode.ERROR_RESPONSE_MODEL + ) + ), + mockEmarsysRequestModelFactory, + mockConfigResponseMapper, + mockClientServiceStorage, + mockEventServiceStorage, + mockDeeplinkServiceStorage, + mockPredictServiceStorage, + mockMessageInboxServiceStorage, + mockLogLevelStorage, + mockCrypto, + mockClientServiceInternal, + concurrentHandlerHolder + ) val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) (configInternal as DefaultConfigInternal).fetchRemoteConfigSignature(resultListener) @@ -787,23 +836,24 @@ class DefaultConfigInternalTest { val mockException: Exception = mock() val configInternal = DefaultConfigInternal( - mockMobileEngageRequestContext, - mockMobileEngageInternal, - mockPushInternal, - mockPredictRequestContext, - mockDeviceInfo, - requestManagerWithRestClient(FakeRestClient(mockException)), - mockEmarsysRequestModelFactory, - mockConfigResponseMapper, - mockClientServiceStorage, - mockEventServiceStorage, - mockDeeplinkServiceStorage, - mockPredictServiceStorage, - mockMessageInboxServiceStorage, - mockLogLevelStorage, - mockCrypto, - mockClientServiceInternal, - mockConcurrentHandlerHolder) + mockMobileEngageRequestContext, + mockMobileEngageInternal, + mockPushInternal, + mockPredictRequestContext, + mockDeviceInfo, + requestManagerWithRestClient(FakeRestClient(exception = mockException)), + mockEmarsysRequestModelFactory, + mockConfigResponseMapper, + mockClientServiceStorage, + mockEventServiceStorage, + mockDeeplinkServiceStorage, + mockPredictServiceStorage, + mockMessageInboxServiceStorage, + mockLogLevelStorage, + mockCrypto, + mockClientServiceInternal, + concurrentHandlerHolder + ) val resultListener = FakeResultListener(latch, FakeResultListener.Mode.MAIN_THREAD) @@ -817,7 +867,7 @@ class DefaultConfigInternalTest { @Test fun testApplyRemoteConfig_applyOne() { val remoteConfig = RemoteConfig( - clientServiceUrl = CLIENT_SERVICE_URL + clientServiceUrl = CLIENT_SERVICE_URL ) (configInternal as DefaultConfigInternal).applyRemoteConfig(remoteConfig) @@ -836,19 +886,19 @@ class DefaultConfigInternalTest { FeatureRegistry.enableFeature(InnerFeature.EVENT_SERVICE_V4) val remoteConfig = RemoteConfig( - eventServiceUrl = EVENT_SERVICE_URL, - clientServiceUrl = CLIENT_SERVICE_URL, - deepLinkServiceUrl = DEEPLINK_SERVICE_URL, - inboxServiceUrl = INBOX_SERVICE_URL, - mobileEngageV2ServiceUrl = MOBILE_ENGAGE_V2_SERVICE_URL, - predictServiceUrl = PREDICT_SERVICE_URL, - messageInboxServiceUrl = MESSAGE_INBOX_SERVICE_URL, - logLevel = LogLevel.DEBUG, - features = mapOf( - InnerFeature.MOBILE_ENGAGE to false, - InnerFeature.PREDICT to true, - InnerFeature.EVENT_SERVICE_V4 to false - ) + eventServiceUrl = EVENT_SERVICE_URL, + clientServiceUrl = CLIENT_SERVICE_URL, + deepLinkServiceUrl = DEEPLINK_SERVICE_URL, + inboxServiceUrl = INBOX_SERVICE_URL, + mobileEngageV2ServiceUrl = MOBILE_ENGAGE_V2_SERVICE_URL, + predictServiceUrl = PREDICT_SERVICE_URL, + messageInboxServiceUrl = MESSAGE_INBOX_SERVICE_URL, + logLevel = LogLevel.DEBUG, + features = mapOf( + InnerFeature.MOBILE_ENGAGE to false, + InnerFeature.PREDICT to true, + InnerFeature.EVENT_SERVICE_V4 to false + ) ) (configInternal as DefaultConfigInternal).applyRemoteConfig(remoteConfig) @@ -870,9 +920,9 @@ class DefaultConfigInternalTest { FeatureRegistry.enableFeature(InnerFeature.PREDICT) val remoteConfig = RemoteConfig( - features = mapOf( - InnerFeature.MOBILE_ENGAGE to false - ) + features = mapOf( + InnerFeature.MOBILE_ENGAGE to false + ) ) (configInternal as DefaultConfigInternal).applyRemoteConfig(remoteConfig) @@ -912,17 +962,21 @@ class DefaultConfigInternalTest { @Test fun testRefreshRemoteConfig_verifyApplyRemoteConfigCalled_onSuccess() { val expectedRemoteConfig = RemoteConfig(eventServiceUrl = "https://test.emarsys.com") - val expectedResponseModel = ResponseModel.Builder().body(""" + val expectedResponseModel = ResponseModel.Builder().body( + """ { "serviceUrls":{ "eventService":"https://test.emarsys.com" } } - """.trimIndent()) - .statusCode(200) - .requestModel(mockRequestModel) - .message("responseMessage").build() - whenever(mockConfigResponseMapper.map(expectedResponseModel)).thenReturn(expectedRemoteConfig) + """.trimIndent() + ) + .statusCode(200) + .requestModel(mockRequestModel) + .message("responseMessage").build() + whenever(mockConfigResponseMapper.map(expectedResponseModel)).thenReturn( + expectedRemoteConfig + ) doAnswer { val result: Try = Try.success("signature") @@ -933,7 +987,12 @@ class DefaultConfigInternalTest { val result: Try = Try.success(expectedResponseModel) it.arguments[0].tryCast>> { onResult(result) } }.whenever(configInternal as DefaultConfigInternal).fetchRemoteConfig(any()) - whenever(mockCrypto.verify(expectedResponseModel.body!!.toByteArray(), "signature")).thenReturn(true) + whenever( + mockCrypto.verify( + expectedResponseModel.body!!.toByteArray(), + "signature" + ) + ).thenReturn(true) configInternal.refreshRemoteConfig(mockCompletionListener) @@ -946,18 +1005,27 @@ class DefaultConfigInternalTest { @Test fun testRefreshRemoteConfig_verifyAndResetRemoteConfigCalled_onVerificationFailed() { val expectedRemoteConfig = RemoteConfig(eventServiceUrl = "https://test.emarsys.com") - val expectedResponseModel = ResponseModel.Builder().body(""" + val expectedResponseModel = ResponseModel.Builder().body( + """ { "serviceUrls":{ "eventService":"https://test.emarsys.com" } } - """.trimIndent()) - .statusCode(200) - .requestModel(mockRequestModel) - .message("responseMessage").build() - whenever(mockConfigResponseMapper.map(expectedResponseModel)).thenReturn(expectedRemoteConfig) - whenever(mockCrypto.verify(expectedResponseModel.body!!.toByteArray(), "signature")).thenReturn(false) + """.trimIndent() + ) + .statusCode(200) + .requestModel(mockRequestModel) + .message("responseMessage").build() + whenever(mockConfigResponseMapper.map(expectedResponseModel)).thenReturn( + expectedRemoteConfig + ) + whenever( + mockCrypto.verify( + expectedResponseModel.body!!.toByteArray(), + "signature" + ) + ).thenReturn(false) doAnswer { val result: Try = Try.success("signature") @@ -975,7 +1043,9 @@ class DefaultConfigInternalTest { verify(mockCrypto).verify(expectedResponseModel.body!!.toByteArray(), "signature") verify((configInternal as DefaultConfigInternal)).resetRemoteConfig() verifyNoInteractions(mockConfigResponseMapper) - verify((configInternal as DefaultConfigInternal), times(0)).applyRemoteConfig(expectedRemoteConfig) + verify((configInternal as DefaultConfigInternal), times(0)).applyRemoteConfig( + expectedRemoteConfig + ) } @Test @@ -1003,7 +1073,7 @@ class DefaultConfigInternalTest { val latch = CountDownLatch(1) val threadSpy = ThreadSpy() - configInternal.changeApplicationCode(null) { + configInternal.changeApplicationCode(null) { threadSpy.call() latch.countDown() } @@ -1014,34 +1084,34 @@ class DefaultConfigInternalTest { @Suppress("UNCHECKED_CAST") fun requestManagerWithRestClient(restClient: RestClient): RequestManager { - val mockScopeDelegatorCompletionHandlerProvider: ScopeDelegatorCompletionHandlerProvider = mock { - on { provide(any(), any()) } doAnswer { - it.arguments[0] as CoreCompletionHandler + val mockScopeDelegatorCompletionHandlerProvider: ScopeDelegatorCompletionHandlerProvider = + mock { + on { provide(anyOrNull(), any()) } doAnswer { + it.arguments[0] as CoreCompletionHandler + } + on { provide(anyOrNull(), any()) } doAnswer { + it.arguments[0] as CoreCompletionHandler + } } - on { provide(any(), any()) } doAnswer { - it.arguments[0] as CoreCompletionHandler - } - } val mockProvider: CompletionHandlerProxyProvider = mock { - on { provideProxy(isNull(), any()) } doAnswer { + on { provideProxy(anyOrNull(), any()) } doAnswer { it.arguments[1] as CoreCompletionHandler } - on { provideProxy(any(), any()) } doAnswer { + on { provideProxy(anyOrNull(), any()) } doAnswer { it.arguments[1] as CoreCompletionHandler } } return RequestManager( - mock(), - mock() as Repository, - mock() as Repository, - mock(), - restClient, - mock() as Registry, - mock(), - mockProvider, - mockScopeDelegatorCompletionHandlerProvider, - mock() + ConcurrentHandlerHolderFactory.create(), + mock() as Repository, + mock() as Repository, + mock(), + restClient, + mock() as Registry, + mock(), + mockProvider, + mockScopeDelegatorCompletionHandlerProvider ) } } \ No newline at end of file diff --git a/emarsys/src/androidTest/java/com/emarsys/fake/FakeEmarsysDependencyContainer.kt b/emarsys/src/androidTest/java/com/emarsys/fake/FakeEmarsysDependencyContainer.kt index a3652699d..0ec07c700 100644 --- a/emarsys/src/androidTest/java/com/emarsys/fake/FakeEmarsysDependencyContainer.kt +++ b/emarsys/src/androidTest/java/com/emarsys/fake/FakeEmarsysDependencyContainer.kt @@ -3,8 +3,6 @@ package com.emarsys.fake import android.app.Activity import android.content.SharedPreferences -import android.os.Handler -import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog @@ -26,7 +24,6 @@ import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient -import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.shard.ShardModel @@ -66,14 +63,10 @@ import com.emarsys.mobileengage.session.MobileEngageSession import com.emarsys.mobileengage.session.SessionIdHolder import com.emarsys.mobileengage.util.RequestModelHelper import com.google.android.gms.location.FusedLocationProviderClient -import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock class FakeEmarsysDependencyContainer( - override val uiHandler: Handler = Handler(Looper.getMainLooper()), - override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( - uiHandler - ).create(), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory.create(), override val mobileEngageInternal: MobileEngageInternal = mock(), override val loggingMobileEngageInternal: MobileEngageInternal = mock(), override val clientServiceInternal: ClientServiceInternal = mock(), @@ -160,7 +153,4 @@ class FakeEmarsysDependencyContainer( override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), override val notificationOpenedActivityClass: Class<*> = Activity::class.java, - override val coreSdkScope: CoroutineScope = mock(), - override val uiScope: CoroutineScope = mock(), - override val runnableFactory: RunnableFactory = mock() ) : MobileEngageComponent \ No newline at end of file diff --git a/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.java b/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.java deleted file mode 100644 index 195eca3ea..000000000 --- a/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.emarsys.fake; - -import static org.mockito.Mockito.mock; - -import android.os.Handler; -import android.os.Looper; - -import com.emarsys.core.CoreCompletionHandler; -import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; -import com.emarsys.core.connection.ConnectionProvider; -import com.emarsys.core.provider.timestamp.TimestampProvider; -import com.emarsys.core.request.RestClient; -import com.emarsys.core.request.model.RequestModel; -import com.emarsys.core.response.ResponseHandlersProcessor; -import com.emarsys.core.response.ResponseModel; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class FakeRestClient extends RestClient { - - private Mode mode; - private List responses; - private List exceptions; - - public enum Mode {SUCCESS, ERROR_RESPONSE_MODEL, ERROR_EXCEPTION} - - public FakeRestClient(ResponseModel returnValue, Mode mode) { - this(Collections.singletonList(returnValue), mode); - } - - @SuppressWarnings("unchecked") - public FakeRestClient(List responses, Mode mode) { - super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), - mock(List.class), new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); - this.responses = new ArrayList<>(responses); - this.mode = mode; - } - - public FakeRestClient(Exception exception) { - this(Collections.singletonList(exception)); - } - - @SuppressWarnings("unchecked") - public FakeRestClient(List exceptions) { - super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), - mock(List.class), new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); - this.exceptions = new ArrayList<>(exceptions); - this.mode = Mode.ERROR_EXCEPTION; - } - - @Override - public void execute(final RequestModel model, final CoreCompletionHandler completionHandler) { - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - if (mode == Mode.SUCCESS) { - completionHandler.onSuccess(model.getId(), getCurrentItem(responses)); - } else if (mode == Mode.ERROR_RESPONSE_MODEL) { - completionHandler.onError(model.getId(), getCurrentItem(responses)); - } else if (mode == Mode.ERROR_EXCEPTION) { - completionHandler.onError(model.getId(), getCurrentItem(exceptions)); - } - } - }, 100); - } - - private T getCurrentItem(List list) { - T result = list.get(0); - if (list.size() > 1) { - list.remove(0); - } - return result; - } -} diff --git a/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.kt b/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.kt new file mode 100644 index 000000000..6f8e4372d --- /dev/null +++ b/emarsys/src/androidTest/java/com/emarsys/fake/FakeRestClient.kt @@ -0,0 +1,57 @@ +package com.emarsys.fake + +import android.os.Handler +import android.os.Looper +import com.emarsys.core.CoreCompletionHandler +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.request.RestClient +import com.emarsys.core.request.model.RequestModel +import com.emarsys.core.response.ResponseModel +import org.mockito.kotlin.mock + +class FakeRestClient( + private var mode: Mode = Mode.SUCCESS, + private var responses: List = listOf(), + private var exceptions: List = listOf(), + response: ResponseModel? = null, + exception: Exception? = null +) : RestClient(mock(), mock(), mock(), mock(), ConcurrentHandlerHolderFactory.create()) { + init { + if (exception != null) { + exceptions = mutableListOf(exception) + mode = Mode.ERROR_EXCEPTION + } + if (response != null) { + responses = mutableListOf(response) + } + } + + enum class Mode { + SUCCESS, ERROR_RESPONSE_MODEL, ERROR_EXCEPTION + } + + override fun execute(model: RequestModel, completionHandler: CoreCompletionHandler) { + Handler(Looper.getMainLooper()).postDelayed({ + when (mode) { + Mode.SUCCESS -> { + completionHandler.onSuccess(model.id, getCurrentItem(responses)) + } + Mode.ERROR_RESPONSE_MODEL -> { + completionHandler.onError(model.id, getCurrentItem(responses)) + } + Mode.ERROR_EXCEPTION -> { + completionHandler.onError(model.id, getCurrentItem(exceptions)) + } + } + }, 100) + } + + private fun getCurrentItem(sourceList: List): T { + val mutableList = sourceList.toMutableList() + val result = mutableList[0] + if (mutableList.size > 1) { + mutableList.removeAt(0) + } + return result + } +} diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/DefaultMobileEngageInternalTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/DefaultMobileEngageInternalTest.kt index 5f58dbcf9..cafa3d080 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/DefaultMobileEngageInternalTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/DefaultMobileEngageInternalTest.kt @@ -1,7 +1,5 @@ package com.emarsys.mobileengage -import android.os.Handler -import android.os.Looper import com.emarsys.core.api.result.CompletionListener import com.emarsys.core.device.DeviceInfo import com.emarsys.core.provider.timestamp.TimestampProvider @@ -20,7 +18,6 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.mockito.kotlin.* -import org.mockito.kotlin.verify import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -70,8 +67,6 @@ class DefaultMobileEngageInternalTest { private lateinit var mockSession: MobileEngageSession private lateinit var mockSessionIdHolder: SessionIdHolder - private lateinit var uiHandler: Handler - @Rule @JvmField val timeout: TestRule = TimeoutUtils.timeoutRule @@ -144,8 +139,6 @@ class DefaultMobileEngageInternalTest { on { createRemovePushTokenRequest() }.thenReturn(mockRequestModel) } - uiHandler = Handler(Looper.getMainLooper()) - mockCompletionListener = mock() mobileEngageInternal = DefaultMobileEngageInternal( diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeMobileEngageDependencyContainer.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeMobileEngageDependencyContainer.kt index 66a343c26..16fb45bfe 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeMobileEngageDependencyContainer.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeMobileEngageDependencyContainer.kt @@ -2,8 +2,6 @@ package com.emarsys.mobileengage.fake import android.app.Activity import android.content.SharedPreferences -import android.os.Handler -import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.activity.ActivityLifecycleActionRegistry import com.emarsys.core.activity.ActivityLifecycleWatchdog @@ -25,7 +23,6 @@ import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.request.RequestManager import com.emarsys.core.request.RestClient -import com.emarsys.core.request.factory.RunnableFactory import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseHandlersProcessor import com.emarsys.core.shard.ShardModel @@ -65,14 +62,10 @@ import com.emarsys.mobileengage.session.MobileEngageSession import com.emarsys.mobileengage.session.SessionIdHolder import com.emarsys.mobileengage.util.RequestModelHelper import com.google.android.gms.location.FusedLocationProviderClient -import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock class FakeMobileEngageDependencyContainer( - override val uiHandler: Handler = Handler(Looper.getMainLooper()), - override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory( - uiHandler - ).create(), + override val concurrentHandlerHolder: ConcurrentHandlerHolder = ConcurrentHandlerHolderFactory.create(), override val mobileEngageInternal: MobileEngageInternal = mock(), override val loggingMobileEngageInternal: MobileEngageInternal = mock(), override val clientServiceInternal: ClientServiceInternal = mock(), @@ -158,8 +151,5 @@ class FakeMobileEngageDependencyContainer( override val geofenceInitialEnterTriggerEnabledStorage: Storage = mock(), override val fusedLocationProviderClient: FusedLocationProviderClient = mock(), override val activityLifecycleActionRegistry: ActivityLifecycleActionRegistry = mock(), - override val notificationOpenedActivityClass: Class<*> = Activity::class.java, - override val coreSdkScope: CoroutineScope = mock(), - override val uiScope: CoroutineScope = mock(), - override val runnableFactory: RunnableFactory = mock() + override val notificationOpenedActivityClass: Class<*> = Activity::class.java ) : MobileEngageComponent \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRequestManager.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRequestManager.kt index 023aba432..074c826ad 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRequestManager.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRequestManager.kt @@ -1,7 +1,5 @@ package com.emarsys.mobileengage.fake -import android.os.Handler -import android.os.Looper import com.emarsys.core.CoreCompletionHandler import com.emarsys.core.Mockable import com.emarsys.core.Registry @@ -13,12 +11,14 @@ import com.emarsys.core.request.RequestManager import com.emarsys.core.request.model.RequestModel import com.emarsys.core.response.ResponseModel import com.emarsys.core.shard.ShardModel -import kotlinx.coroutines.CoroutineScope import org.mockito.kotlin.mock @Mockable -class FakeRequestManager(private val responseType: ResponseType, private val response: ResponseModel) : RequestManager( - ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create(), +class FakeRequestManager( + private val responseType: ResponseType, + private val response: ResponseModel +) : RequestManager( + ConcurrentHandlerHolderFactory.create(), mock() as Repository, mock() as Repository, mock(), @@ -26,7 +26,6 @@ class FakeRequestManager(private val responseType: ResponseType, private val res mock() as Registry, mock(), mock(), - mock(), mock() ) { @@ -36,7 +35,10 @@ class FakeRequestManager(private val responseType: ResponseType, private val res EXCEPTION } - override fun submitNow(requestModel: RequestModel, completionHandler: CoreCompletionHandler, scope: CoroutineScope) { + override fun submitNow( + requestModel: RequestModel, + completionHandler: CoreCompletionHandler + ) { when (responseType) { ResponseType.SUCCESS -> { completionHandler.onSuccess("id", response) diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRestClient.java b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRestClient.java index f6fb8fd70..3d1397c5e 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRestClient.java +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeRestClient.java @@ -35,7 +35,7 @@ public FakeRestClient(ResponseModel returnValue, Mode mode) { @SuppressWarnings("unchecked") public FakeRestClient(List responses, Mode mode) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); + ConcurrentHandlerHolderFactory.INSTANCE.create()); this.responses = new ArrayList<>(responses); this.mode = mode; } @@ -52,7 +52,7 @@ public FakeRestClient(Exception exception, SdkHandler handler) { @SuppressWarnings("unchecked") public FakeRestClient(List exceptions) { super(mock(ConnectionProvider.class), mock(TimestampProvider.class), mock(ResponseHandlersProcessor.class), mock(List.class), - new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create()); + ConcurrentHandlerHolderFactory.INSTANCE.create()); this.exceptions = new ArrayList<>(exceptions); this.mode = Mode.ERROR_EXCEPTION; } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternalTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternalTest.kt index 64de525e2..70a8b4492 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternalTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/geofence/DefaultGeofenceInternalTest.kt @@ -8,9 +8,8 @@ import android.content.pm.PackageManager import android.location.Location import android.location.LocationManager import android.os.Build -import android.os.Handler -import android.os.Looper import com.emarsys.core.api.MissingPermissionException +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.permission.PermissionChecker import com.emarsys.core.request.RequestManager @@ -91,16 +90,14 @@ class DefaultGeofenceInternalTest { private lateinit var mockEnabledStorage: Storage private lateinit var mockInitialEnterTriggerEnabledStorage: Storage private lateinit var mockPendingIntentProvider: GeofencePendingIntentProvider - private lateinit var mockHandlerHolder: ConcurrentHandlerHolder - private lateinit var uiHandler: Handler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Before fun setUp() { - mockHandlerHolder = mock() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockInitialEnterTriggerEnabledStorage = mock { on { get() } doReturn false } - uiHandler = Handler(Looper.getMainLooper()) context = InstrumentationRegistry.getTargetContext() mockResponseModel = mock() mockRequestModelFactory = mock { @@ -146,8 +143,7 @@ class DefaultGeofenceInternalTest { mockCacheableEventHandler, mockEnabledStorage, mockPendingIntentProvider, - mockHandlerHolder, - uiHandler, + concurrentHandlerHolder, mockInitialEnterTriggerEnabledStorage ) @@ -164,8 +160,7 @@ class DefaultGeofenceInternalTest { mockCacheableEventHandler, mockEnabledStorage, mockPendingIntentProvider, - mockHandlerHolder, - uiHandler, + concurrentHandlerHolder, mockInitialEnterTriggerEnabledStorage ) } @@ -174,7 +169,7 @@ class DefaultGeofenceInternalTest { fun testFetchGeofences_shouldSendRequest_viaRequestManager_submitNow() { geofenceInternal.fetchGeofences(null) - verify(fakeRequestManager).submitNow(any(), any(), anyOrNull()) + verify(fakeRequestManager).submitNow(any(), any()) } @Test diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/OverlayInAppPresenterTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/OverlayInAppPresenterTest.kt index 1be73d363..b1b0b7f88 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/OverlayInAppPresenterTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/OverlayInAppPresenterTest.kt @@ -1,8 +1,6 @@ package com.emarsys.mobileengage.iam import android.app.Activity -import android.os.Handler -import android.os.Looper import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment @@ -12,6 +10,7 @@ import androidx.test.rule.ActivityTestRule import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.activity.CurrentActivityProvider import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.mobileengage.MobileEngageInternal @@ -31,6 +30,7 @@ import com.emarsys.testUtil.fake.FakeActivity import com.emarsys.testUtil.mockito.ThreadSpy import com.emarsys.testUtil.mockito.anyNotNull import com.emarsys.testUtil.mockito.whenever +import kotlinx.coroutines.launch import org.json.JSONObject import org.junit.Before import org.junit.Rule @@ -65,11 +65,15 @@ class OverlayInAppPresenterTest { private lateinit var mockIamJsBridgeFactory: IamJsBridgeFactory private lateinit var mockJsBridge: IamJsBridge private lateinit var mockJSCommandFactory: JSCommandFactory - private lateinit var uiHandler: Handler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Before fun setUp() { - iamStaticWebViewProvider = IamStaticWebViewProvider(InstrumentationRegistry.getTargetContext(), Handler(Looper.getMainLooper())) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() + iamStaticWebViewProvider = IamStaticWebViewProvider( + InstrumentationRegistry.getTargetContext(), + concurrentHandlerHolder + ) mockInAppInternal = mock() mockIamDialogProvider = mock() mockButtonClickedRepository = mock() @@ -83,13 +87,10 @@ class OverlayInAppPresenterTest { on { createJsBridge(anyOrNull(), anyOrNull()) } doReturn mockJsBridge } - uiHandler = Handler(Looper.getMainLooper()) - val concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() overlayPresenter = OverlayInAppPresenter( concurrentHandlerHolder, - uiHandler, iamStaticWebViewProvider, mockInAppInternal, mockIamDialogProvider, @@ -112,20 +113,22 @@ class OverlayInAppPresenterTest { whenever(activityMock.supportFragmentManager).thenReturn(fragmentManager) whenever(fragmentManager.findFragmentById(any())).thenReturn(fragmentMock) whenever(mockActivityProvider.get()).thenReturn(activityMock) - whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn(iamDialog) + whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn( + iamDialog + ) val countDownLatch = CountDownLatch(1) overlayPresenter.present( - "1", - SID, - URL, - "requestId", - 0L, - "

Hello

", - MessageLoadedListener { - countDownLatch.countDown() - } + "1", + SID, + URL, + "requestId", + 0L, + "

Hello

", + MessageLoadedListener { + countDownLatch.countDown() + } ) countDownLatch.await() @@ -144,20 +147,22 @@ class OverlayInAppPresenterTest { whenever(activityMock.supportFragmentManager).thenReturn(fragmentManager) whenever(fragmentManager.findFragmentById(any())).thenReturn(fragmentMock) whenever(mockActivityProvider.get()).thenReturn(activityMock) - whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn(iamDialog) + whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn( + iamDialog + ) val countDownLatch = CountDownLatch(1) overlayPresenter.present( - "1", - SID, - URL, - "requestId", - 0L, - "

Hello

", - MessageLoadedListener { - countDownLatch.countDown() - } + "1", + SID, + URL, + "requestId", + 0L, + "

Hello

", + MessageLoadedListener { + countDownLatch.countDown() + } ) countDownLatch.await() @@ -171,20 +176,22 @@ class OverlayInAppPresenterTest { val activity: Activity = mock() whenever(mockActivityProvider.get()).thenReturn(activity) - whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn(iamDialog) + whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn( + iamDialog + ) val countDownLatch = CountDownLatch(1) overlayPresenter.present( - "1", - SID, - URL, - "requestId", - 0L, - "

Hello

", - MessageLoadedListener { - countDownLatch.countDown() - } + "1", + SID, + URL, + "requestId", + 0L, + "

Hello

", + MessageLoadedListener { + countDownLatch.countDown() + } ) countDownLatch.await() @@ -197,20 +204,22 @@ class OverlayInAppPresenterTest { val iamDialog: IamDialog = mock() whenever(mockActivityProvider.get()).thenReturn(null) - whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn(iamDialog) + whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn( + iamDialog + ) val countDownLatch = CountDownLatch(1) overlayPresenter.present( - "1", - SID, - URL, - "requestId", - 0L, - "

Hello

", - MessageLoadedListener { - countDownLatch.countDown() - } + "1", + SID, + URL, + "requestId", + 0L, + "

Hello

", + MessageLoadedListener { + countDownLatch.countDown() + } ) countDownLatch.await() @@ -225,23 +234,27 @@ class OverlayInAppPresenterTest { val fragmentManager: FragmentManager = mock() val fragment: Fragment = mock() - whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn(iamDialog) + whenever(mockIamDialogProvider.provideDialog(anyNotNull(), any(), any(), any())).thenReturn( + iamDialog + ) whenever(mockActivityProvider.get()).thenReturn(activity) whenever(activity.supportFragmentManager).thenReturn(fragmentManager) - whenever(fragmentManager.findFragmentByTag("MOBILE_ENGAGE_IAM_DIALOG_TAG")).thenReturn(fragment) + whenever(fragmentManager.findFragmentByTag("MOBILE_ENGAGE_IAM_DIALOG_TAG")).thenReturn( + fragment + ) val countDownLatch = CountDownLatch(1) overlayPresenter.present( - "1", - SID, - URL, - "requestId", - 0L, - "

Hello

", - MessageLoadedListener { - countDownLatch.countDown() - } + "1", + SID, + URL, + "requestId", + 0L, + "

Hello

", + MessageLoadedListener { + countDownLatch.countDown() + } ) countDownLatch.await() @@ -260,7 +273,7 @@ class OverlayInAppPresenterTest { overlayPresenter.onCloseTriggered().invoke() val latch = CountDownLatch(1) - uiHandler.post { latch.countDown() } + concurrentHandlerHolder.uiScope.launch { latch.countDown() } latch.await() verify(fragment).dismiss() @@ -274,13 +287,15 @@ class OverlayInAppPresenterTest { whenever(mockInAppInternal.eventHandler).thenReturn(mockEventHandler) val payload = JSONObject() - .put("payloadKey1", - JSONObject() - .put("payloadKey2", "payloadValue1")) + .put( + "payloadKey1", + JSONObject() + .put("payloadKey2", "payloadValue1") + ) val json = JSONObject() - .put("name", "eventName") - .put("id", "123456789") - .put("payload", payload) + .put("name", "eventName") + .put("id", "123456789") + .put("payload", payload) overlayPresenter.onAppEventTriggered().invoke("eventName", json) @@ -295,13 +310,19 @@ class OverlayInAppPresenterTest { whenever(mockInAppInternal.eventHandler).thenReturn(mockEventHandler) val threadSpy: ThreadSpy<*> = ThreadSpy() - whenever(mockInAppInternal.eventHandler?.handleEvent(anyOrNull(), anyOrNull(), anyOrNull())).doAnswer(threadSpy) + whenever( + mockInAppInternal.eventHandler?.handleEvent( + anyOrNull(), + anyOrNull(), + anyOrNull() + ) + ).doAnswer(threadSpy) val id = "12346789" val eventName = "eventName" val json = JSONObject() - .put("id", id) - .put("name", eventName) + .put("id", id) + .put("name", eventName) overlayPresenter.onAppEventTriggered().invoke("eventName", json) @@ -317,8 +338,8 @@ class OverlayInAppPresenterTest { val id = "12346789" val eventName = "eventName" val json = JSONObject() - .put("id", id) - .put("name", eventName) + .put("id", id) + .put("name", eventName) overlayPresenter.onAppEventTriggered().invoke("eventName", json) diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.kt index 0b307fa81..7fe27d64c 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/SaveDisplayedIamActionTest.kt @@ -1,7 +1,5 @@ package com.emarsys.mobileengage.iam -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository import com.emarsys.core.database.repository.SqlSpecification @@ -47,7 +45,7 @@ class SaveDisplayedIamActionTest { threadSpy = ThreadSpy() repository = mock() handler = - ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() + ConcurrentHandlerHolderFactory.create() timestampProvider = mock { on { provideTimestamp() } doReturn TIMESTAMP } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/IamDialogProviderTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/IamDialogProviderTest.kt index 120d1997f..75516a5a9 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/IamDialogProviderTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/IamDialogProviderTest.kt @@ -1,8 +1,7 @@ package com.emarsys.mobileengage.iam.dialog -import android.os.Handler -import android.os.Looper import androidx.test.rule.ActivityTestRule +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.testUtil.fake.FakeActivity import org.junit.Before import org.junit.Rule @@ -21,7 +20,7 @@ class IamDialogProviderTest { @Before fun setUp() { - iamDialogProvider = IamDialogProvider(Handler(Looper.getMainLooper()), mock()) + iamDialogProvider = IamDialogProvider(ConcurrentHandlerHolderFactory.create(), mock()) } @Test @@ -29,7 +28,8 @@ class IamDialogProviderTest { val latch = CountDownLatch(1) thread(start = true) { iamDialogProvider.provideDialog( - "campaignId", "id", "https://www.example.com", "reqId") + "campaignId", "id", "https://www.example.com", "reqId" + ) latch.countDown() } latch.await() diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/IamDialogTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/IamDialogTest.kt index aeb05b7f1..a78cc7075 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/IamDialogTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/IamDialogTest.kt @@ -10,6 +10,8 @@ import androidx.fragment.app.testing.FragmentScenario import androidx.fragment.app.testing.launchFragment import androidx.lifecycle.Lifecycle import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.core.provider.timestamp.TimestampProvider import com.emarsys.core.provider.uuid.UUIDProvider import com.emarsys.core.util.log.entry.InAppLoadingTime @@ -25,6 +27,7 @@ import com.emarsys.testUtil.TimeoutUtils.timeoutRule import com.emarsys.testUtil.fake.FakeActivity import io.kotlintest.shouldBe import io.kotlintest.shouldNotBe +import kotlinx.coroutines.launch import org.junit.After import org.junit.Before import org.junit.Rule @@ -44,7 +47,7 @@ class IamDialogTest { } private lateinit var mockTimestampProvider: TimestampProvider - private lateinit var uiHandler: Handler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Rule @JvmField @@ -52,18 +55,19 @@ class IamDialogTest { @Rule @JvmField - var activityScenarioRule: ActivityScenarioRule = ActivityScenarioRule(FakeActivity::class.java) + var activityScenarioRule: ActivityScenarioRule = + ActivityScenarioRule(FakeActivity::class.java) @Before fun setUp() { - uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockTimestampProvider = mock() val mockUuidProvider: UUIDProvider = mock { on { provideId() } doReturn "uuid" } setupMobileEngageComponent( FakeMobileEngageDependencyContainer( - uiHandler = uiHandler, + concurrentHandlerHolder = concurrentHandlerHolder, timestampProvider = mockTimestampProvider, uuidProvider = mockUuidProvider ) @@ -81,17 +85,27 @@ class IamDialogTest { fun testDefaultConstructor() { val dialog = IamDialog() - val dialogUiHandler = ReflectionTestUtils.getInstanceField(dialog, "uiHandler") + val dialogUiHandler = + ReflectionTestUtils.getInstanceField( + dialog, + "concurrentHandlerHolder" + ) val dialogTimestampProvider = ReflectionTestUtils.getInstanceField(dialog, "timestampProvider") - dialogUiHandler shouldBe uiHandler + dialogUiHandler shouldBe concurrentHandlerHolder dialogTimestampProvider shouldBe mockTimestampProvider } @Test fun testCreate_shouldReturnImageDialogInstance() { - val fragmentScenario = launchFragment { IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) } + val fragmentScenario = + launchFragment { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ) + } fragmentScenario.onFragment { fragment -> fragment shouldNotBe null } @@ -105,7 +119,12 @@ class IamDialogTest { bundle.putString(IamDialog.SID, null) bundle.putString(IamDialog.URL, null) bundle.putString(IamDialog.REQUEST_ID, null) - val fragmentScenario = launchFragment(bundle) { IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) } + val fragmentScenario = launchFragment(bundle) { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ) + } fragmentScenario.onFragment { fragment -> val result = fragment.arguments @@ -123,7 +142,12 @@ class IamDialogTest { bundle.putString(IamDialog.SID, null) bundle.putString(IamDialog.URL, null) bundle.putString(IamDialog.REQUEST_ID, requestId) - val fragmentScenario = launchFragment(bundle) { IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) } + val fragmentScenario = launchFragment(bundle) { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ) + } fragmentScenario.onFragment { fragment -> val result = fragment.arguments @@ -142,7 +166,12 @@ class IamDialogTest { bundle.putString(IamDialog.URL, null) bundle.putString(IamDialog.REQUEST_ID, requestId) - val fragmentScenario = launchFragment(bundle) { IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) } + val fragmentScenario = launchFragment(bundle) { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ) + } fragmentScenario.onFragment { fragment -> val result = fragment.arguments @@ -161,7 +190,12 @@ class IamDialogTest { bundle.putString(IamDialog.URL, URL) bundle.putString(IamDialog.REQUEST_ID, requestId) - val fragmentScenario = launchFragment(bundle) { IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) } + val fragmentScenario = launchFragment(bundle) { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ) + } fragmentScenario.onFragment { fragment -> val result = fragment.arguments @@ -178,7 +212,12 @@ class IamDialogTest { bundle.putString(IamDialog.SID, SID) bundle.putString(IamDialog.URL, URL) bundle.putString(IamDialog.REQUEST_ID, null) - val fragmentScenario = launchFragment(bundle) { IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) } + val fragmentScenario = launchFragment(bundle) { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ) + } fragmentScenario.onFragment { fragment -> val result = fragment.arguments @@ -195,7 +234,12 @@ class IamDialogTest { bundle.putString(IamDialog.SID, SID) bundle.putString(IamDialog.URL, URL) bundle.putString(IamDialog.REQUEST_ID, null) - val fragmentScenario = launchFragment(bundle) { IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) } + val fragmentScenario = launchFragment(bundle) { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ) + } displayDialog(fragmentScenario) fragmentScenario.onFragment { val expected = 0.0f @@ -213,7 +257,7 @@ class IamDialogTest { bundle.putString(IamDialog.REQUEST_ID, null) val fragmentScenario = launchFragment(bundle) { - IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) + IamDialog(mobileEngage().concurrentHandlerHolder, mobileEngage().timestampProvider) } displayDialog(fragmentScenario) @@ -240,7 +284,12 @@ class IamDialogTest { bundle.putString(IamDialog.REQUEST_ID, REQUEST_ID_KEY) bundle.putSerializable("loading_time", InAppLoadingTime(0, 0)) - val fragmentScenario = launchFragment(bundle) { IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) } + val fragmentScenario = launchFragment(bundle) { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ) + } displayDialog(fragmentScenario) @@ -271,7 +320,8 @@ class IamDialogTest { bundle.putString(IamDialog.URL, URL) bundle.putString(IamDialog.REQUEST_ID, REQUEST_ID_KEY) - val fragmentScenario = launchFragment(bundle) { IamDialog(uiHandler, mockTimestampProvider) } + val fragmentScenario = + launchFragment(bundle) { IamDialog(concurrentHandlerHolder, mockTimestampProvider) } val fragmentLatch = CountDownLatch(1) displayDialog(fragmentScenario) @@ -300,7 +350,12 @@ class IamDialogTest { bundle.putString(IamDialog.URL, URL) bundle.putString(IamDialog.REQUEST_ID, REQUEST_ID_KEY) - val fragmentScenario = launchFragment(bundle) { IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) } + val fragmentScenario = launchFragment(bundle) { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ) + } val fragmentLatch = CountDownLatch(1) displayDialog(fragmentScenario) @@ -327,7 +382,10 @@ class IamDialogTest { args.putString(CAMPAIGN_ID_KEY, "123456789") val actions: List = listOf(mock(), mock(), mock()) val fragmentScenario = launchFragment(args) { - IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider).apply { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ).apply { setActions(actions) } } @@ -349,7 +407,10 @@ class IamDialogTest { args.putString(IamDialog.SID, SID) args.putString(IamDialog.URL, URL) val fragmentScenario = launchFragment(args) { - IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider).apply { + IamDialog( + mobileEngage().concurrentHandlerHolder, + mobileEngage().timestampProvider + ).apply { setActions(actions) } } @@ -372,7 +433,7 @@ class IamDialogTest { args.putString(IamDialog.SID, SID) args.putString(IamDialog.URL, URL) val fragmentScenario = launchFragment(args) { - IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) + IamDialog(mobileEngage().concurrentHandlerHolder, mobileEngage().timestampProvider) } fragmentScenario.onFragment { @@ -396,7 +457,7 @@ class IamDialogTest { args.putString(IamDialog.SID, SID) args.putString(IamDialog.URL, URL) val fragmentScenario = launchFragment(args) { - IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) + IamDialog(mobileEngage().concurrentHandlerHolder, mobileEngage().timestampProvider) } fragmentScenario.onFragment { it.activity?.runOnUiThread { @@ -419,7 +480,7 @@ class IamDialogTest { var result: Exception? = null try { val fragmentScenario = launchFragment { - IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) + IamDialog(mobileEngage().concurrentHandlerHolder, mobileEngage().timestampProvider) } displayDialog(fragmentScenario) fragmentScenario.onFragment { @@ -438,12 +499,16 @@ class IamDialogTest { var result: Exception? = null try { initWebViewProvider() - uiHandler.post { - val webView = IamStaticWebViewProvider(getTargetContext(), uiHandler).provideWebView() + concurrentHandlerHolder.uiScope.launch { + val webView = + IamStaticWebViewProvider( + getTargetContext(), + concurrentHandlerHolder + ).provideWebView() LinearLayout(getTargetContext()).addView(webView) } val fragmentScenario = launchFragment { - IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) + IamDialog(mobileEngage().concurrentHandlerHolder, mobileEngage().timestampProvider) } displayDialog(fragmentScenario) fragmentScenario.onFragment { @@ -465,7 +530,7 @@ class IamDialogTest { args.putSerializable("loading_time", InAppLoadingTime(0, 0)) try { val fragmentScenario = launchFragment(args) { - IamDialog(mobileEngage().uiHandler, mobileEngage().timestampProvider) + IamDialog(mobileEngage().concurrentHandlerHolder, mobileEngage().timestampProvider) } displayDialog(fragmentScenario) } catch (exception: IllegalArgumentException) { diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamActionTest.java b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamActionTest.java index 0a70455bd..49e8a282d 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamActionTest.java +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/dialog/action/SendDisplayedIamActionTest.java @@ -6,9 +6,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.os.Handler; -import android.os.Looper; - import com.emarsys.core.api.result.CompletionListener; import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory; import com.emarsys.core.handler.ConcurrentHandlerHolder; @@ -42,7 +39,7 @@ public class SendDisplayedIamActionTest { @Before public void init() { - handler = new ConcurrentHandlerHolderFactory(new Handler(Looper.getMainLooper())).create(); + handler = ConcurrentHandlerHolderFactory.INSTANCE.create(); inAppInternal = mock(InAppInternal.class); action = new SendDisplayedIamAction(handler, inAppInternal); } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/inline/InlineInAppWebViewFactoryTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/inline/InlineInAppWebViewFactoryTest.kt index 94fa8a7c9..8ab94d6b3 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/inline/InlineInAppWebViewFactoryTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/inline/InlineInAppWebViewFactoryTest.kt @@ -2,19 +2,20 @@ package com.emarsys.mobileengage.iam.inline import android.graphics.Color import android.os.Build.VERSION_CODES.O -import android.os.Handler -import android.os.Looper import android.webkit.JavascriptInterface import android.webkit.WebView import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import com.emarsys.core.Mockable +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.mobileengage.iam.jsbridge.IamJsBridge import com.emarsys.mobileengage.iam.webview.MessageLoadedListener import com.emarsys.mobileengage.iam.webview.WebViewProvider import com.emarsys.testUtil.ReflectionTestUtils import io.kotlintest.matchers.types.shouldBeSameInstanceAs import io.kotlintest.shouldBe +import kotlinx.coroutines.launch import org.junit.After import org.junit.Before import org.junit.Test @@ -41,13 +42,13 @@ class InlineInAppWebViewFactoryTest { private lateinit var mockWebViewProvider: WebViewProvider private lateinit var inlineWebViewFactory: InlineInAppWebViewFactory private lateinit var mockMessageLoadedListener: MessageLoadedListener - private lateinit var uiHandler: Handler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder @Before fun setUp() { - uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() val setUpLatch = CountDownLatch(1) - uiHandler.post { + concurrentHandlerHolder.uiScope.launch { val webView = WebView(InstrumentationRegistry.getInstrumentation().targetContext) mockWebView = spy(webView) @@ -59,7 +60,8 @@ class InlineInAppWebViewFactoryTest { on { provideWebView() }.doReturn(mockWebView) } - inlineWebViewFactory = InlineInAppWebViewFactory(mockWebViewProvider, uiHandler) + inlineWebViewFactory = + InlineInAppWebViewFactory(mockWebViewProvider, concurrentHandlerHolder) mockMessageLoadedListener = mock() } @@ -70,7 +72,8 @@ class InlineInAppWebViewFactoryTest { @Test fun testCreateShouldReturnWebView() { - inlineWebViewFactory = InlineInAppWebViewFactory(mockWebViewProvider, uiHandler) + inlineWebViewFactory = + InlineInAppWebViewFactory(mockWebViewProvider, concurrentHandlerHolder) val response = runOnUiThread { inlineWebViewFactory.create(mockMessageLoadedListener) } response shouldBe mockWebView @@ -79,7 +82,8 @@ class InlineInAppWebViewFactoryTest { @Test fun testCreateShouldReturnNull_whenWebViewCanNotBeCreated() { whenever(mockWebViewProvider.provideWebView()).thenReturn(null) - inlineWebViewFactory = InlineInAppWebViewFactory(mockWebViewProvider, uiHandler) + inlineWebViewFactory = + InlineInAppWebViewFactory(mockWebViewProvider, concurrentHandlerHolder) val response = runOnUiThread { inlineWebViewFactory.create(mockMessageLoadedListener) } response shouldBe null @@ -98,7 +102,7 @@ class InlineInAppWebViewFactoryTest { val webView = runOnUiThread { inlineWebViewFactory.create(mockMessageLoadedListener) } var result: MessageLoadedListener? = null val latch = CountDownLatch(1) - uiHandler.post { + concurrentHandlerHolder.uiScope.launch { val webViewClient = webView!!.webViewClient result = ReflectionTestUtils.getInstanceField(webViewClient, "listener") latch.countDown() @@ -110,9 +114,9 @@ class InlineInAppWebViewFactoryTest { @Mockable class TestJSInterface : IamJsBridge( - mock(), - mock(), - mock() + mock(), + mock(), + mock() ) { @JavascriptInterface fun onPageLoaded(json: String?) { @@ -122,7 +126,7 @@ class InlineInAppWebViewFactoryTest { private fun runOnUiThread(lambda: () -> T): T? { var result: T? = null val latch = CountDownLatch(1) - uiHandler.post { + concurrentHandlerHolder.uiScope.launch { result = lambda.invoke() latch.countDown() } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/IamJsBridgeTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/IamJsBridgeTest.kt index 0303ad2a6..6f1d7a53b 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/IamJsBridgeTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/IamJsBridgeTest.kt @@ -1,9 +1,9 @@ package com.emarsys.mobileengage.iam.jsbridge -import android.os.Handler -import android.os.Looper import android.webkit.WebView import androidx.test.rule.ActivityTestRule +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.mobileengage.api.event.EventHandler import com.emarsys.mobileengage.iam.model.InAppMessage import com.emarsys.testUtil.TimeoutUtils.timeoutRule @@ -18,15 +18,18 @@ import org.mockito.kotlin.* class IamJsBridgeTest { - private val jsonObject = JSONObject(mapOf( + private val jsonObject = JSONObject( + mapOf( "id" to "testId", "buttonId" to "testButtonId", - "name" to "testName")) + "name" to "testName" + ) + ) private lateinit var jsBridge: IamJsBridge private lateinit var mockWebView: WebView private lateinit var mockEventHandler: EventHandler - private lateinit var uiHandler: Handler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var inAppMessage: InAppMessage private lateinit var mockJsCommandFactory: JSCommandFactory private lateinit var mockOnCloseListener: JSCommand @@ -47,26 +50,31 @@ class IamJsBridgeTest { @Before fun setUp() { inAppMessage = InAppMessage("campaignId", "sid", "url") - uiHandler = Handler(Looper.getMainLooper()) + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockOnCloseListener = mock() mockOnAppEventListener = mock() mockOnButtonClickedListener = mock() mockOnOpenExternalUrlListener = mock() mockOnMEEventListener = mock() - mockJsCommandFactory = mock() { + mockJsCommandFactory = mock { on { create(JSCommandFactory.CommandType.ON_CLOSE) } doReturn (mockOnCloseListener) on { create(JSCommandFactory.CommandType.ON_ME_EVENT) } doReturn (mockOnMEEventListener) on { create(JSCommandFactory.CommandType.ON_OPEN_EXTERNAL_URL) } doReturn (mockOnOpenExternalUrlListener) on { create(JSCommandFactory.CommandType.ON_APP_EVENT) } doReturn (mockOnAppEventListener) - on { create(JSCommandFactory.CommandType.ON_BUTTON_CLICKED, inAppMessage) } doReturn (mockOnButtonClickedListener) + on { + create( + JSCommandFactory.CommandType.ON_BUTTON_CLICKED, + inAppMessage + ) + } doReturn (mockOnButtonClickedListener) } mockEventHandler = mock() mockWebView = mock() jsBridge = IamJsBridge( - uiHandler, - mockJsCommandFactory, - inAppMessage + concurrentHandlerHolder, + mockJsCommandFactory, + inAppMessage ) jsBridge.webView = mockWebView } @@ -91,7 +99,10 @@ class IamJsBridgeTest { fun testOnButtonClickedEvent_shouldInvokeOnAppEventListener_createdByFactory() { jsBridge.buttonClicked(jsonObject.toString()) - verify(mockJsCommandFactory).create(JSCommandFactory.CommandType.ON_BUTTON_CLICKED, inAppMessage) + verify(mockJsCommandFactory).create( + JSCommandFactory.CommandType.ON_BUTTON_CLICKED, + inAppMessage + ) verify(mockOnButtonClickedListener, timeout(2500)).invoke(anyOrNull(), any()) } @@ -105,12 +116,15 @@ class IamJsBridgeTest { @Test fun testOnOpenExternalUrlEvent_shouldCreateOnCloseAndOpenExternalUrlCommands_then_InvokeOnExternalUrlListenerAndOnCloseListener() { - val json = JSONObject(mapOf( + val json = JSONObject( + mapOf( "id" to "testId", "buttonId" to "testButtonId", "name" to "testName", "url" to "https://emarsys.com", - "keepInAppOpen" to false)) + "keepInAppOpen" to false + ) + ) jsBridge.openExternalLink(json.toString()) verify(mockJsCommandFactory).create(JSCommandFactory.CommandType.ON_CLOSE) @@ -121,11 +135,14 @@ class IamJsBridgeTest { @Test fun testOnOpenExternalUrlEvent_shouldCreateOnCloseCommand_whenNokeepInAppOpenIsInJson() { - val json = JSONObject(mapOf( + val json = JSONObject( + mapOf( "id" to "testId", "buttonId" to "testButtonId", "name" to "testName", - "url" to "https://emarsys.com")) + "url" to "https://emarsys.com" + ) + ) jsBridge.openExternalLink(json.toString()) verify(mockJsCommandFactory).create(JSCommandFactory.CommandType.ON_CLOSE) @@ -136,12 +153,15 @@ class IamJsBridgeTest { @Test fun testOnOpenExternalUrlEvent_shouldNotCreateAndInvokeCloseCommand_whenkeepInAppOpenIsTrueInJson() { - val json = JSONObject(mapOf( + val json = JSONObject( + mapOf( "id" to "testId", "buttonId" to "testButtonId", "name" to "testName", "url" to "https://emarsys.com", - "keepInAppOpen" to true)) + "keepInAppOpen" to true + ) + ) jsBridge.openExternalLink(json.toString()) verify(mockJsCommandFactory).create(JSCommandFactory.CommandType.ON_OPEN_EXTERNAL_URL) @@ -158,7 +178,10 @@ class IamJsBridgeTest { jsBridge.triggerAppEvent(json.toString()) val result = JSONObject().put("id", id).put("success", true) - verify(mockWebView, timeout(1000)).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) + verify( + mockWebView, + timeout(1000) + ).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) } @Test @@ -169,7 +192,10 @@ class IamJsBridgeTest { jsBridge.triggerMEEvent(json.toString()) val result = JSONObject().put("id", id).put("success", true) - verify(mockWebView, timeout(1000)).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) + verify( + mockWebView, + timeout(1000) + ).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) } @Test @@ -179,9 +205,12 @@ class IamJsBridgeTest { val json = JSONObject().put("id", id).put("buttonId", buttonId) jsBridge.buttonClicked(json.toString()) val result = JSONObject() - .put("id", id) - .put("success", true) - verify(mockWebView, timeout(1000)).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) + .put("id", id) + .put("success", true) + verify( + mockWebView, + timeout(1000) + ).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) } @Test @@ -191,10 +220,13 @@ class IamJsBridgeTest { val json = JSONObject().put("id", id).put("url", url).put("keepInAppOpen", false) jsBridge.openExternalLink(json.toString()) val result = JSONObject() - .put("id", id) - .put("success", true) + .put("id", id) + .put("success", true) - verify(mockWebView, timeout(1000)).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) + verify( + mockWebView, + timeout(1000) + ).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) } @Test @@ -202,13 +234,16 @@ class IamJsBridgeTest { val id = "123456789" val json = JSONObject().put("id", id) val result = JSONObject() - .put("id", id) - .put("success", false) - .put("error", "Missing name!") + .put("id", id) + .put("success", false) + .put("error", "Missing name!") jsBridge.triggerAppEvent(json.toString()) - verify(mockWebView, timeout(1000)).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) + verify( + mockWebView, + timeout(1000) + ).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) } @Test @@ -216,13 +251,16 @@ class IamJsBridgeTest { val id = "123456789" val json = JSONObject().put("id", id) val result = JSONObject() - .put("id", id) - .put("success", false) - .put("error", "Missing name!") + .put("id", id) + .put("success", false) + .put("error", "Missing name!") jsBridge.triggerMEEvent(json.toString()) - verify(mockWebView, timeout(1000)).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) + verify( + mockWebView, + timeout(1000) + ).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) } @Test @@ -231,10 +269,13 @@ class IamJsBridgeTest { val json = JSONObject().put("id", id) jsBridge.buttonClicked(json.toString()) val result = JSONObject() - .put("id", id) - .put("success", false) - .put("error", "Missing buttonId!") - verify(mockWebView, timeout(1000)).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) + .put("id", id) + .put("success", false) + .put("error", "Missing buttonId!") + verify( + mockWebView, + timeout(1000) + ).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) } @@ -244,10 +285,13 @@ class IamJsBridgeTest { val json = JSONObject().put("id", id).put("keepInAppOpen", false) jsBridge.openExternalLink(json.toString()) val result = JSONObject() - .put("id", id) - .put("success", false) - .put("error", "Missing url!") - verify(mockWebView, timeout(1000)).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) + .put("id", id) + .put("success", false) + .put("error", "Missing url!") + verify( + mockWebView, + timeout(1000) + ).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", result), null) } @Test(expected = IllegalArgumentException::class) @@ -259,6 +303,9 @@ class IamJsBridgeTest { fun testSendResult_shouldInvokeEvaluateJavascript_onWebView() { val json = JSONObject().put("id", "123456789").put("key", "value") jsBridge.sendResult(json) - verify(mockWebView, timeout(1000)).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", json), null) + verify( + mockWebView, + timeout(1000) + ).evaluateJavascript(String.format("MEIAM.handleResponse(%s);", json), null) } } \ No newline at end of file diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactoryTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactoryTest.kt index b682e7118..3a1f49449 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactoryTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/JSCommandFactoryTest.kt @@ -1,8 +1,6 @@ package com.emarsys.mobileengage.iam.jsbridge import android.app.Activity -import android.os.Handler -import android.os.Looper import androidx.test.rule.ActivityTestRule import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.repository.Repository @@ -38,7 +36,6 @@ class JSCommandFactoryTest { private lateinit var jsCommandFactory: JSCommandFactory private lateinit var mockCurrentActivityProvider: CurrentActivityProvider - private lateinit var uiHandler: Handler private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var mockInAppInternal: InAppInternal private lateinit var mockButtonClickedRepository: Repository @@ -57,8 +54,7 @@ class JSCommandFactoryTest { mockCurrentActivityProvider = mock() { on { get() } doReturn mockActivity } - uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockInAppInternal = mock() mockButtonClickedRepository = mock() mockOnCloseListener = mock() @@ -68,7 +64,6 @@ class JSCommandFactoryTest { } jsCommandFactory = JSCommandFactory( mockCurrentActivityProvider, - uiHandler, concurrentHandlerHolder, mockInAppInternal, mockButtonClickedRepository, diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepositoryTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepositoryTest.kt index f26bda95e..5d826372f 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepositoryTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClickedRepositoryTest.kt @@ -2,8 +2,6 @@ package com.emarsys.mobileengage.iam.model.buttonclicked import android.content.ContentValues import android.database.Cursor -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.DatabaseContract import com.emarsys.core.database.helper.CoreDbHelper @@ -40,8 +38,7 @@ class ButtonClickedRepositoryTest { deleteCoreDatabase() val context = getTargetContext() val dbHelper: DbHelper = CoreDbHelper(context, HashMap()) - val uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() repository = ButtonClickedRepository(dbHelper, concurrentHandlerHolder) buttonClicked1 = ButtonClicked("campaign1", "button1", Date().time) } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.kt index edf250e33..0a6292a62 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/displayediam/DisplayedIamRepositoryTest.kt @@ -2,8 +2,6 @@ package com.emarsys.mobileengage.iam.model.displayediam import android.content.ContentValues import android.database.Cursor -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.DatabaseContract.DISPLAYED_IAM_COLUMN_NAME_CAMPAIGN_ID import com.emarsys.core.database.DatabaseContract.DISPLAYED_IAM_COLUMN_NAME_TIMESTAMP @@ -39,7 +37,7 @@ class DisplayedIamRepositoryTest { val context = getTargetContext() val dbHelper: DbHelper = CoreDbHelper(context, mutableMapOf()) concurrentHandlerHolder = - ConcurrentHandlerHolderFactory(Handler(Looper.getMainLooper())).create() + ConcurrentHandlerHolderFactory.create() iamRepository = DisplayedIamRepository(dbHelper, concurrentHandlerHolder) displayedIam1 = DisplayedIam("campaign1", Date().time) } diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxyTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxyTest.kt index 801036325..e36edc8ad 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxyTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/requestRepositoryProxy/RequestRepositoryProxyTest.kt @@ -1,7 +1,5 @@ package com.emarsys.mobileengage.iam.model.requestRepositoryProxy -import android.os.Handler -import android.os.Looper import com.emarsys.common.feature.InnerFeature import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper @@ -62,8 +60,7 @@ class RequestRepositoryProxyTest { fun setUp() { deleteCoreDatabase() val context = getTargetContext() - val uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() mockRequestContext = mock() mockRequestModelRepository = mock() mockDisplayedIamRepository = mock() diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/specification/FilterByCampaignIdTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/specification/FilterByCampaignIdTest.kt index 689db9d81..1498c79cf 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/specification/FilterByCampaignIdTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/model/specification/FilterByCampaignIdTest.kt @@ -1,7 +1,5 @@ package com.emarsys.mobileengage.iam.model.specification -import android.os.Handler -import android.os.Looper import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory import com.emarsys.core.database.helper.CoreDbHelper import com.emarsys.core.database.repository.specification.Everything @@ -34,8 +32,7 @@ class FilterByCampaignIdTest { @Before fun init() { DatabaseTestUtils.deleteCoreDatabase() - val uiHandler = Handler(Looper.getMainLooper()) - concurrentHandlerHolder = ConcurrentHandlerHolderFactory(uiHandler).create() + concurrentHandlerHolder = ConcurrentHandlerHolderFactory.create() val context = InstrumentationRegistry.getTargetContext() val dbHelper = CoreDbHelper(context, HashMap()) diff --git a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/webview/IamStaticWebViewProviderTest.kt b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/webview/IamStaticWebViewProviderTest.kt index b417d681a..547a28815 100644 --- a/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/webview/IamStaticWebViewProviderTest.kt +++ b/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/webview/IamStaticWebViewProviderTest.kt @@ -1,15 +1,17 @@ package com.emarsys.mobileengage.iam.webview import android.os.Handler -import android.os.Looper import android.webkit.JavascriptInterface import android.webkit.WebView +import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory +import com.emarsys.core.handler.ConcurrentHandlerHolder import com.emarsys.mobileengage.fake.FakeMessageLoadedListener import com.emarsys.mobileengage.iam.dialog.IamDialog import com.emarsys.mobileengage.iam.jsbridge.IamJsBridge import com.emarsys.mobileengage.iam.webview.IamStaticWebViewProvider.Companion.webView import com.emarsys.testUtil.InstrumentationRegistry.Companion.getTargetContext import com.emarsys.testUtil.TimeoutUtils.timeoutRule +import kotlinx.coroutines.launch import org.junit.Assert import org.junit.Before import org.junit.Rule @@ -37,10 +39,11 @@ class IamStaticWebViewProviderTest { private lateinit var provider: IamStaticWebViewProvider private lateinit var listener: MessageLoadedListener - private lateinit var handler: Handler + private lateinit var concurrentHandlerHolder: ConcurrentHandlerHolder private lateinit var latch: CountDownLatch private lateinit var dummyJsBridge: IamJsBridge - var html = String.format(""" + var html = String.format( + """