diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/CoroutineDispatcherProvider.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/CoroutineDispatcherProvider.kt deleted file mode 100644 index 8b57c5659d..0000000000 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/CoroutineDispatcherProvider.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.onesignal.common.threading - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Job - -/** - * Provider interface for coroutine dispatchers. - * This allows for proper dependency injection and easier testing. - */ -interface CoroutineDispatcherProvider { - val io: CoroutineDispatcher - val default: CoroutineDispatcher - - /** - * Launch a coroutine on the IO dispatcher. - */ - fun launchOnIO(block: suspend () -> Unit): Job - - /** - * Launch a coroutine on the Default dispatcher. - */ - fun launchOnDefault(block: suspend () -> Unit): Job -} diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/DefaultDispatcherProvider.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/DefaultDispatcherProvider.kt deleted file mode 100644 index 8ca50d5b5e..0000000000 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/DefaultDispatcherProvider.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.onesignal.common.threading - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Job - -/** - * Production implementation of [CoroutineDispatcherProvider] that uses OneSignalDispatchers. - * - * This delegates to the existing scopes in OneSignalDispatchers to avoid creating duplicate scopes. - * The OneSignalDispatchers already maintains IOScope and DefaultScope with SupervisorJob, - * so we reuse those instead of creating new ones. - */ -class DefaultDispatcherProvider : CoroutineDispatcherProvider { - override val io: CoroutineDispatcher = OneSignalDispatchers.IO - override val default: CoroutineDispatcher = OneSignalDispatchers.Default - - override fun launchOnIO(block: suspend () -> Unit): Job { - // Delegate to OneSignalDispatchers which already has IOScope with SupervisorJob - return OneSignalDispatchers.launchOnIO(block) - } - - override fun launchOnDefault(block: suspend () -> Unit): Job { - // Delegate to OneSignalDispatchers which already has DefaultScope with SupervisorJob - return OneSignalDispatchers.launchOnDefault(block) - } -} diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/startup/StartupService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/startup/StartupService.kt index 6d209734f9..9d1c112d64 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/startup/StartupService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/startup/StartupService.kt @@ -1,20 +1,18 @@ package com.onesignal.core.internal.startup import com.onesignal.common.services.ServiceProvider -import com.onesignal.common.threading.CoroutineDispatcherProvider -import com.onesignal.common.threading.DefaultDispatcherProvider +import com.onesignal.common.threading.OneSignalDispatchers internal class StartupService( private val services: ServiceProvider, - private val dispatchers: CoroutineDispatcherProvider = DefaultDispatcherProvider(), ) { fun bootstrap() { services.getAllServices().forEach { it.bootstrap() } } - // schedule to start all startable services using the provided dispatcher + // schedule to start all startable services using OneSignal dispatcher fun scheduleStart() { - dispatchers.launchOnDefault { + OneSignalDispatchers.launchOnDefault { services.getAllServices().forEach { it.start() } } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsRepository.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsRepository.kt index 16c4f2d32f..8a9b7ecb66 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsRepository.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsRepository.kt @@ -1,7 +1,6 @@ package com.onesignal.session.internal.outcomes.impl import android.content.ContentValues -import com.onesignal.common.threading.OneSignalDispatchers import com.onesignal.core.internal.database.IDatabaseProvider import com.onesignal.core.internal.database.impl.OneSignalDbContract import com.onesignal.debug.internal.logging.Logging @@ -10,7 +9,7 @@ import com.onesignal.session.internal.influence.InfluenceChannel import com.onesignal.session.internal.influence.InfluenceType import com.onesignal.session.internal.influence.InfluenceType.Companion.fromString import com.onesignal.session.internal.outcomes.migrations.RemoveInvalidSessionTimeRecords -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONArray import org.json.JSONException @@ -18,13 +17,12 @@ import java.util.Locale internal class OutcomeEventsRepository( private val _databaseProvider: IDatabaseProvider, - private val ioDispatcher: CoroutineDispatcher = OneSignalDispatchers.IO, ) : IOutcomeEventsRepository { /** * Delete event from the DB */ override suspend fun deleteOldOutcomeEvent(event: OutcomeEventParams) { - withContext(ioDispatcher) { + withContext(Dispatchers.IO) { _databaseProvider.os.delete( OutcomeEventsTable.TABLE_NAME, OutcomeEventsTable.COLUMN_NAME_TIMESTAMP + " = ?", @@ -38,7 +36,7 @@ internal class OutcomeEventsRepository( * For offline mode and contingency of errors */ override suspend fun saveOutcomeEvent(eventParams: OutcomeEventParams) { - withContext(ioDispatcher) { + withContext(Dispatchers.IO) { var notificationIds = JSONArray() var iamIds = JSONArray() var notificationInfluenceType = InfluenceType.UNATTRIBUTED @@ -103,7 +101,7 @@ internal class OutcomeEventsRepository( */ override suspend fun getAllEventsToSend(): List { val events: MutableList = ArrayList() - withContext(ioDispatcher) { + withContext(Dispatchers.IO) { RemoveInvalidSessionTimeRecords.run(_databaseProvider) _databaseProvider.os.query(OutcomeEventsTable.TABLE_NAME) { cursor -> if (cursor.moveToFirst()) { @@ -250,7 +248,7 @@ internal class OutcomeEventsRepository( override suspend fun saveUniqueOutcomeEventParams(eventParams: OutcomeEventParams) { Logging.debug("OutcomeEventsCache.saveUniqueOutcomeEventParams(eventParams: $eventParams)") - withContext(ioDispatcher) { + withContext(Dispatchers.IO) { val outcomeName = eventParams.outcomeId val cachedUniqueOutcomes: MutableList = ArrayList() val directBody = eventParams.outcomeSource?.directBody @@ -285,7 +283,7 @@ internal class OutcomeEventsRepository( ): List { val uniqueInfluences: MutableList = ArrayList() - withContext(ioDispatcher) { + withContext(Dispatchers.IO) { try { for (influence in influences) { val availableInfluenceIds = JSONArray() @@ -335,7 +333,7 @@ internal class OutcomeEventsRepository( val notificationTableName = OneSignalDbContract.NotificationTable.TABLE_NAME val notificationIdColumnName = OneSignalDbContract.NotificationTable.COLUMN_NAME_NOTIFICATION_ID - withContext(ioDispatcher) { + withContext(Dispatchers.IO) { val whereStr = "NOT EXISTS(" + "SELECT NULL FROM " + notificationTableName + " n " + diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/startup/StartupServiceTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/startup/StartupServiceTests.kt index 820ed4fe37..7416b2910c 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/startup/StartupServiceTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/startup/StartupServiceTests.kt @@ -4,7 +4,8 @@ import com.onesignal.common.services.ServiceBuilder import com.onesignal.common.services.ServiceProvider import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging -import com.onesignal.mocks.TestDispatcherProvider +import com.onesignal.mocks.IOMockHelper +import com.onesignal.mocks.IOMockHelper.awaitIO import io.kotest.assertions.throwables.shouldThrowUnit import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.comparables.shouldBeLessThan @@ -13,12 +14,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.spyk import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runTest -@OptIn(ExperimentalCoroutinesApi::class) class StartupServiceTests : FunSpec({ fun setupServiceProvider( bootstrapServices: List, @@ -31,127 +27,111 @@ class StartupServiceTests : FunSpec({ serviceBuilder.register(reg).provides() return serviceBuilder.build() } - val testDispatcher = StandardTestDispatcher() - val dispatcherProvider = TestDispatcherProvider(testDispatcher) + + listener(IOMockHelper) beforeAny { Logging.logLevel = LogLevel.NONE } test("bootstrap with no IBootstrapService dependencies is a no-op") { - runTest(testDispatcher.scheduler) { - // Given - val startupService = StartupService(setupServiceProvider(listOf(), listOf()), dispatcherProvider) + // Given + val startupService = StartupService(setupServiceProvider(listOf(), listOf())) - // When - startupService.bootstrap() + // When + startupService.bootstrap() - // Then - } + // Then } test("bootstrap will call all IBootstrapService dependencies successfully") { - runTest(testDispatcher.scheduler) { - // Given - val mockBootstrapService1 = mockk(relaxed = true) - val mockBootstrapService2 = mockk(relaxed = true) + // Given + val mockBootstrapService1 = mockk(relaxed = true) + val mockBootstrapService2 = mockk(relaxed = true) - val startupService = StartupService(setupServiceProvider(listOf(mockBootstrapService1, mockBootstrapService2), listOf()), dispatcherProvider) + val startupService = StartupService(setupServiceProvider(listOf(mockBootstrapService1, mockBootstrapService2), listOf())) - // When - startupService.bootstrap() + // When + startupService.bootstrap() - // Then - verify(exactly = 1) { mockBootstrapService1.bootstrap() } - verify(exactly = 1) { mockBootstrapService2.bootstrap() } - } + // Then + verify(exactly = 1) { mockBootstrapService1.bootstrap() } + verify(exactly = 1) { mockBootstrapService2.bootstrap() } } test("bootstrap will propagate exception when an IBootstrapService throws an exception") { - runTest(testDispatcher.scheduler) { - // Given - val exception = Exception("SOMETHING BAD") - - val mockBootstrapService1 = mockk() - every { mockBootstrapService1.bootstrap() } throws exception - val mockBootstrapService2 = spyk() - - val startupService = StartupService(setupServiceProvider(listOf(mockBootstrapService1, mockBootstrapService2), listOf()), dispatcherProvider) - - // When - val actualException = - shouldThrowUnit { - startupService.bootstrap() - } - - // Then - actualException shouldBe exception - verify(exactly = 1) { mockBootstrapService1.bootstrap() } - verify(exactly = 0) { mockBootstrapService2.bootstrap() } - } + // Given + val exception = Exception("SOMETHING BAD") + + val mockBootstrapService1 = mockk() + every { mockBootstrapService1.bootstrap() } throws exception + val mockBootstrapService2 = spyk() + + val startupService = StartupService(setupServiceProvider(listOf(mockBootstrapService1, mockBootstrapService2), listOf())) + + // When + val actualException = + shouldThrowUnit { + startupService.bootstrap() + } + + // Then + actualException shouldBe exception + verify(exactly = 1) { mockBootstrapService1.bootstrap() } + verify(exactly = 0) { mockBootstrapService2.bootstrap() } } test("startup will call all IStartableService dependencies successfully after a short delay") { - runTest(testDispatcher.scheduler) { - // Given - val mockStartupService1 = mockk(relaxed = true) - val mockStartupService2 = mockk(relaxed = true) - - val startupService = StartupService( - setupServiceProvider(listOf(), listOf(mockStartupService1, mockStartupService2)), - dispatcherProvider - ) - - // When - startupService.scheduleStart() - - // Then - wait deterministically for both services to start using advanceUntilIdle - advanceUntilIdle() - verify(exactly = 1) { mockStartupService1.start() } - verify(exactly = 1) { mockStartupService2.start() } - } + // Given + val mockStartupService1 = mockk(relaxed = true) + val mockStartupService2 = mockk(relaxed = true) + + val startupService = StartupService(setupServiceProvider(listOf(), listOf(mockStartupService1, mockStartupService2))) + + // When + startupService.scheduleStart() + + // Then - wait deterministically for both services to start using IOMockHelper + awaitIO() + verify(exactly = 1) { mockStartupService1.start() } + verify(exactly = 1) { mockStartupService2.start() } } test("scheduleStart does not block main thread") { - runTest(testDispatcher.scheduler) { - // Given - val mockStartableService1 = mockk(relaxed = true) - val mockStartableService2 = spyk() - val mockStartableService3 = spyk() - // Only service1 and service2 are scheduled - service3 is NOT scheduled - val startupService = StartupService( - setupServiceProvider(listOf(), listOf(mockStartableService1, mockStartableService2)), - dispatcherProvider - ) - - // When - scheduleStart() is async, so it doesn't block - val startTime = System.currentTimeMillis() - startupService.scheduleStart() - val scheduleTime = System.currentTimeMillis() - startTime - - // This should execute immediately since scheduleStart() doesn't block - // service3 is NOT part of scheduled services, so this is a direct call - mockStartableService3.start() - val immediateTime = System.currentTimeMillis() - startTime - - // Then - verify scheduleStart() returned quickly (non-blocking) - // Should return in < 50ms (proving it doesn't wait for services to start) - scheduleTime shouldBeLessThan 50L - immediateTime shouldBeLessThan 50L - - // Verify service3 was called immediately (proving main thread wasn't blocked) - verify(exactly = 1) { mockStartableService3.start() } - - // Wait deterministically for async execution using advanceUntilIdle - advanceUntilIdle() - - // Verify scheduled services were called - verify(exactly = 1) { mockStartableService1.start() } - verify(exactly = 1) { mockStartableService2.start() } - - // The key assertion: scheduleStart() returned immediately without blocking, - // allowing service3.start() to be called synchronously before scheduled services - // complete. This proves scheduleStart() is non-blocking. - } + // Given + val mockStartableService1 = mockk(relaxed = true) + val mockStartableService2 = spyk() + val mockStartableService3 = spyk() + // Only service1 and service2 are scheduled - service3 is NOT scheduled + val startupService = StartupService(setupServiceProvider(listOf(), listOf(mockStartableService1, mockStartableService2))) + + // When - scheduleStart() is async, so it doesn't block + val startTime = System.currentTimeMillis() + startupService.scheduleStart() + val scheduleTime = System.currentTimeMillis() - startTime + + // This should execute immediately since scheduleStart() doesn't block + // service3 is NOT part of scheduled services, so this is a direct call + mockStartableService3.start() + val immediateTime = System.currentTimeMillis() - startTime + + // Then - verify scheduleStart() returned quickly (non-blocking) + // Should return in < 50ms (proving it doesn't wait for services to start) + scheduleTime shouldBeLessThan 50L + immediateTime shouldBeLessThan 50L + + // Verify service3 was called immediately (proving main thread wasn't blocked) + verify(exactly = 1) { mockStartableService3.start() } + + // Wait deterministically for async execution using IOMockHelper + awaitIO() + + // Verify scheduled services were called + verify(exactly = 1) { mockStartableService1.start() } + verify(exactly = 1) { mockStartableService2.start() } + + // The key assertion: scheduleStart() returned immediately without blocking, + // allowing service3.start() to be called synchronously before scheduled services + // complete. This proves scheduleStart() is non-blocking. } }) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OneSignalImpTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OneSignalImpTests.kt index 08cfc6d79f..d660fa2525 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OneSignalImpTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OneSignalImpTests.kt @@ -2,51 +2,41 @@ package com.onesignal.internal import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging -import com.onesignal.mocks.TestDispatcherProvider import io.kotest.assertions.throwables.shouldThrowUnit import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.runTest class OneSignalImpTests : FunSpec({ beforeAny { Logging.logLevel = LogLevel.NONE } - val testDispatcher = StandardTestDispatcher() - val dispatcherProvider = TestDispatcherProvider(testDispatcher) - test("attempting login before initWithContext throws exception") { - runTest(testDispatcher.scheduler) { - // Given - val os = OneSignalImp() + // Given + val os = OneSignalImp() - // When - val exception = - shouldThrowUnit { - os.login("login-id") - } + // When + val exception = + shouldThrowUnit { + os.login("login-id") + } - // Then - exception.message shouldBe "Must call 'initWithContext' before 'login'" - } + // Then + exception.message shouldBe "Must call 'initWithContext' before 'login'" } test("attempting logout before initWithContext throws exception") { - runTest(testDispatcher.scheduler) { - // Given - val os = OneSignalImp() + // Given + val os = OneSignalImp() - // When - val exception = - shouldThrowUnit { - os.logout() - } + // When + val exception = + shouldThrowUnit { + os.logout() + } - // Then - exception.message shouldBe "Must call 'initWithContext' before 'logout'" - } + // Then + exception.message shouldBe "Must call 'initWithContext' before 'logout'" } // Comprehensive tests for deprecated properties that should work before and after initialization @@ -54,7 +44,7 @@ class OneSignalImpTests : FunSpec({ context("before initWithContext") { test("get returns false by default") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When & Then os.consentRequired shouldBe false @@ -62,7 +52,7 @@ class OneSignalImpTests : FunSpec({ test("set and get works correctly") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When os.consentRequired = true @@ -79,7 +69,7 @@ class OneSignalImpTests : FunSpec({ test("set should not throw") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When & Then - should not throw os.consentRequired = false @@ -92,7 +82,7 @@ class OneSignalImpTests : FunSpec({ context("before initWithContext") { test("get returns false by default") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When & Then os.consentGiven shouldBe false @@ -100,7 +90,7 @@ class OneSignalImpTests : FunSpec({ test("set and get works correctly") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When os.consentGiven = true @@ -117,7 +107,7 @@ class OneSignalImpTests : FunSpec({ test("set should not throw") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When & Then - should not throw os.consentGiven = true @@ -130,7 +120,7 @@ class OneSignalImpTests : FunSpec({ context("before initWithContext") { test("get returns false by default") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When & Then os.disableGMSMissingPrompt shouldBe false @@ -138,7 +128,7 @@ class OneSignalImpTests : FunSpec({ test("set and get works correctly") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When os.disableGMSMissingPrompt = true @@ -155,7 +145,7 @@ class OneSignalImpTests : FunSpec({ test("set should not throw") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When & Then - should not throw os.disableGMSMissingPrompt = true @@ -167,7 +157,7 @@ class OneSignalImpTests : FunSpec({ context("property consistency tests") { test("all properties maintain state correctly") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When - set all properties to true os.consentRequired = true @@ -192,7 +182,7 @@ class OneSignalImpTests : FunSpec({ test("properties are independent of each other") { // Given - val os = OneSignalImp(dispatcherProvider.io) + val os = OneSignalImp() // When - set only consentRequired to true os.consentRequired = true @@ -228,7 +218,7 @@ class OneSignalImpTests : FunSpec({ // waitForInit() would timeout after 30 seconds and log a warning (not throw) // Given - a fresh OneSignalImp instance - val oneSignalImp = OneSignalImp(dispatcherProvider.io) + val oneSignalImp = OneSignalImp() // The timeout behavior is built into waitUntilInitInternal() // which uses withTimeout() to wait for up to 30 seconds (or 4.8 seconds on main thread) @@ -246,7 +236,7 @@ class OneSignalImpTests : FunSpec({ // until initialization completes (per PR #2412) // Given - val oneSignalImp = OneSignalImp(dispatcherProvider.io) + val oneSignalImp = OneSignalImp() // We can verify the wait behavior by checking: // 1. The suspendCompletion (CompletableDeferred) is properly initialized diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsRepositoryTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsRepositoryTests.kt index f1cd47491c..e7fad98ac3 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsRepositoryTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsRepositoryTests.kt @@ -4,7 +4,6 @@ import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging import com.onesignal.mocks.DatabaseMockHelper -import com.onesignal.mocks.TestDispatcherProvider import com.onesignal.session.internal.influence.Influence import com.onesignal.session.internal.influence.InfluenceChannel import com.onesignal.session.internal.influence.InfluenceType @@ -20,9 +19,6 @@ import io.kotest.matchers.shouldNotBe import io.mockk.verify import io.mockk.verifyAll import io.mockk.verifySequence -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestDispatcher -import kotlinx.coroutines.test.runTest import org.json.JSONArray @RobolectricTest @@ -31,512 +27,481 @@ class OutcomeEventsRepositoryTests : FunSpec({ Logging.logLevel = LogLevel.NONE } - // avoids initialization happening too early (before Robolectric’s environment exists). - lateinit var testDispatcher: TestDispatcher - lateinit var dispatcherProvider: TestDispatcherProvider - - beforeTest { - testDispatcher = StandardTestDispatcher() - dispatcherProvider = TestDispatcherProvider(testDispatcher) - } - test("delete outcome event should use the timestamp to delete row from database") { - runTest(dispatcherProvider.io) { - val mockDatabasePair = DatabaseMockHelper.databaseProvider(OutcomeEventsTable.TABLE_NAME) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - outcomeEventsRepository.deleteOldOutcomeEvent(OutcomeEventParams("outcomeId", null, 0f, 0, 1111)) - - // Then - verify(exactly = 1) { - mockDatabasePair.second.delete( - OutcomeEventsTable.TABLE_NAME, - withArg { - it.contains(OutcomeEventsTable.COLUMN_NAME_TIMESTAMP) - }, - withArg { it.contains("1111") }, - ) - } + // Given + val mockDatabasePair = DatabaseMockHelper.databaseProvider(OutcomeEventsTable.TABLE_NAME) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + outcomeEventsRepository.deleteOldOutcomeEvent(OutcomeEventParams("outcomeId", null, 0f, 0, 1111)) + + // Then + verify(exactly = 1) { + mockDatabasePair.second.delete( + OutcomeEventsTable.TABLE_NAME, + withArg { + it.contains(OutcomeEventsTable.COLUMN_NAME_TIMESTAMP) + }, + withArg { it.contains("1111") }, + ) } } test("save outcome event should insert row into database") { - runTest(dispatcherProvider.io) { - // Given - val mockDatabasePair = DatabaseMockHelper.databaseProvider(OutcomeEventsTable.TABLE_NAME) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - outcomeEventsRepository.saveOutcomeEvent(OutcomeEventParams("outcomeId1", null, 0f, 0, 1111)) - outcomeEventsRepository.saveOutcomeEvent( - OutcomeEventParams( - "outcomeId2", - OutcomeSource( - OutcomeSourceBody(JSONArray().put("notificationId1")), - OutcomeSourceBody(null, JSONArray().put("iamId1").put("iamId2")), - ), - .2f, - 0, - 2222, + // Given + val mockDatabasePair = DatabaseMockHelper.databaseProvider(OutcomeEventsTable.TABLE_NAME) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + outcomeEventsRepository.saveOutcomeEvent(OutcomeEventParams("outcomeId1", null, 0f, 0, 1111)) + outcomeEventsRepository.saveOutcomeEvent( + OutcomeEventParams( + "outcomeId2", + OutcomeSource( + OutcomeSourceBody(JSONArray().put("notificationId1")), + OutcomeSourceBody(null, JSONArray().put("iamId1").put("iamId2")), ), - ) - outcomeEventsRepository.saveOutcomeEvent( - OutcomeEventParams( - "outcomeId3", - OutcomeSource( - OutcomeSourceBody(JSONArray().put("notificationId1"), JSONArray().put("iamId1")), - null, - ), - .4f, - 0, - 3333, + .2f, + 0, + 2222, + ), + ) + outcomeEventsRepository.saveOutcomeEvent( + OutcomeEventParams( + "outcomeId3", + OutcomeSource( + OutcomeSourceBody(JSONArray().put("notificationId1"), JSONArray().put("iamId1")), + null, ), - ) - outcomeEventsRepository.saveOutcomeEvent( - OutcomeEventParams( - "outcomeId4", - OutcomeSource( - null, - OutcomeSourceBody(JSONArray().put("notificationId1"), JSONArray().put("iamId1").put("iamId2")), - ), - .6f, - 0, - 4444, + .4f, + 0, + 3333, + ), + ) + outcomeEventsRepository.saveOutcomeEvent( + OutcomeEventParams( + "outcomeId4", + OutcomeSource( + null, + OutcomeSourceBody(JSONArray().put("notificationId1"), JSONArray().put("iamId1").put("iamId2")), ), + .6f, + 0, + 4444, + ), + ) + + // Then + verifySequence { + mockDatabasePair.second.insert( + OutcomeEventsTable.TABLE_NAME, + null, + withArg { + it[OutcomeEventsTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[OutcomeEventsTable.COLUMN_NAME_WEIGHT] shouldBe 0f + it[OutcomeEventsTable.COLUMN_NAME_TIMESTAMP] shouldBe 1111L + it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE] shouldBe "unattributed" + it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS] shouldBe JSONArray().toString() + it[OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE] shouldBe "unattributed" + it[OutcomeEventsTable.COLUMN_NAME_IAM_IDS] shouldBe JSONArray().toString() + }, + ) + mockDatabasePair.second.insert( + OutcomeEventsTable.TABLE_NAME, + null, + withArg { + it[OutcomeEventsTable.COLUMN_NAME_NAME] shouldBe "outcomeId2" + it[OutcomeEventsTable.COLUMN_NAME_WEIGHT] shouldBe .2f + it[OutcomeEventsTable.COLUMN_NAME_TIMESTAMP] shouldBe 2222L + it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE] shouldBe "direct" + it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS] shouldBe JSONArray("[\"notificationId1\"]").toString() + it[OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE] shouldBe "indirect" + it[OutcomeEventsTable.COLUMN_NAME_IAM_IDS] shouldBe JSONArray("[\"iamId1\", \"iamId2\"]").toString() + }, + ) + mockDatabasePair.second.insert( + OutcomeEventsTable.TABLE_NAME, + null, + withArg { + it[OutcomeEventsTable.COLUMN_NAME_NAME] shouldBe "outcomeId3" + it[OutcomeEventsTable.COLUMN_NAME_WEIGHT] shouldBe .4f + it[OutcomeEventsTable.COLUMN_NAME_TIMESTAMP] shouldBe 3333L + it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE] shouldBe "direct" + it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS] shouldBe JSONArray("[\"notificationId1\"]").toString() + it[OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE] shouldBe "direct" + it[OutcomeEventsTable.COLUMN_NAME_IAM_IDS] shouldBe JSONArray("[\"iamId1\"]").toString() + }, + ) + mockDatabasePair.second.insert( + OutcomeEventsTable.TABLE_NAME, + null, + withArg { + it[OutcomeEventsTable.COLUMN_NAME_NAME] shouldBe "outcomeId4" + it[OutcomeEventsTable.COLUMN_NAME_WEIGHT] shouldBe .6f + it[OutcomeEventsTable.COLUMN_NAME_TIMESTAMP] shouldBe 4444L + it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE] shouldBe "indirect" + it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS] shouldBe JSONArray("[\"notificationId1\"]").toString() + it[OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE] shouldBe "indirect" + it[OutcomeEventsTable.COLUMN_NAME_IAM_IDS] shouldBe JSONArray("[\"iamId1\", \"iamId2\"]").toString() + }, ) - - // Then - verifySequence { - mockDatabasePair.second.insert( - OutcomeEventsTable.TABLE_NAME, - null, - withArg { - it[OutcomeEventsTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[OutcomeEventsTable.COLUMN_NAME_WEIGHT] shouldBe 0f - it[OutcomeEventsTable.COLUMN_NAME_TIMESTAMP] shouldBe 1111L - it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE] shouldBe "unattributed" - it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS] shouldBe JSONArray().toString() - it[OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE] shouldBe "unattributed" - it[OutcomeEventsTable.COLUMN_NAME_IAM_IDS] shouldBe JSONArray().toString() - }, - ) - mockDatabasePair.second.insert( - OutcomeEventsTable.TABLE_NAME, - null, - withArg { - it[OutcomeEventsTable.COLUMN_NAME_NAME] shouldBe "outcomeId2" - it[OutcomeEventsTable.COLUMN_NAME_WEIGHT] shouldBe .2f - it[OutcomeEventsTable.COLUMN_NAME_TIMESTAMP] shouldBe 2222L - it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE] shouldBe "direct" - it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS] shouldBe JSONArray("[\"notificationId1\"]").toString() - it[OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE] shouldBe "indirect" - it[OutcomeEventsTable.COLUMN_NAME_IAM_IDS] shouldBe JSONArray("[\"iamId1\", \"iamId2\"]").toString() - }, - ) - mockDatabasePair.second.insert( - OutcomeEventsTable.TABLE_NAME, - null, - withArg { - it[OutcomeEventsTable.COLUMN_NAME_NAME] shouldBe "outcomeId3" - it[OutcomeEventsTable.COLUMN_NAME_WEIGHT] shouldBe .4f - it[OutcomeEventsTable.COLUMN_NAME_TIMESTAMP] shouldBe 3333L - it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE] shouldBe "direct" - it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS] shouldBe JSONArray("[\"notificationId1\"]").toString() - it[OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE] shouldBe "direct" - it[OutcomeEventsTable.COLUMN_NAME_IAM_IDS] shouldBe JSONArray("[\"iamId1\"]").toString() - }, - ) - mockDatabasePair.second.insert( - OutcomeEventsTable.TABLE_NAME, - null, - withArg { - it[OutcomeEventsTable.COLUMN_NAME_NAME] shouldBe "outcomeId4" - it[OutcomeEventsTable.COLUMN_NAME_WEIGHT] shouldBe .6f - it[OutcomeEventsTable.COLUMN_NAME_TIMESTAMP] shouldBe 4444L - it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE] shouldBe "indirect" - it[OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS] shouldBe JSONArray("[\"notificationId1\"]").toString() - it[OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE] shouldBe "indirect" - it[OutcomeEventsTable.COLUMN_NAME_IAM_IDS] shouldBe JSONArray("[\"iamId1\", \"iamId2\"]").toString() - }, - ) - } } } test("get events should retrieve return empty list when database is empty") { - runTest(dispatcherProvider.io) { - // Given - val mockDatabasePair = DatabaseMockHelper.databaseProvider(OutcomeEventsTable.TABLE_NAME) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) + // Given + val mockDatabasePair = DatabaseMockHelper.databaseProvider(OutcomeEventsTable.TABLE_NAME) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) - // When - val events = outcomeEventsRepository.getAllEventsToSend() + // When + val events = outcomeEventsRepository.getAllEventsToSend() - // Then - events.count() shouldBe 0 - } + // Then + events.count() shouldBe 0 } test("get events should retrieve return an item per row in database") { - runTest(dispatcherProvider.io) { - // Given - val records = - listOf( - mapOf( - OutcomeEventsTable.COLUMN_NAME_NAME to "outcomeId1", - OutcomeEventsTable.COLUMN_NAME_WEIGHT to 0.2f, - OutcomeEventsTable.COLUMN_NAME_TIMESTAMP to 1111L, - OutcomeEventsTable.COLUMN_NAME_SESSION_TIME to 1L, - OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE to "unattributed", - OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE to "unattributed", - ), - mapOf( - OutcomeEventsTable.COLUMN_NAME_NAME to "outcomeId2", - OutcomeEventsTable.COLUMN_NAME_WEIGHT to 0.4f, - OutcomeEventsTable.COLUMN_NAME_TIMESTAMP to 2222L, - OutcomeEventsTable.COLUMN_NAME_SESSION_TIME to 2L, - OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE to "indirect", - OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS to "[\"notificationId1\",\"notificationId2\"]", - OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE to "indirect", - OutcomeEventsTable.COLUMN_NAME_IAM_IDS to "[\"iamId1\",\"iamId2\"]", - ), - mapOf( - OutcomeEventsTable.COLUMN_NAME_NAME to "outcomeId3", - OutcomeEventsTable.COLUMN_NAME_WEIGHT to 0.6f, - OutcomeEventsTable.COLUMN_NAME_TIMESTAMP to 3333L, - OutcomeEventsTable.COLUMN_NAME_SESSION_TIME to 3L, - OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE to "direct", - OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS to "[\"notificationId3\"]", - OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE to "direct", - OutcomeEventsTable.COLUMN_NAME_IAM_IDS to "[\"iamId3\"]", - ), - ) - val mockDatabasePair = DatabaseMockHelper.databaseProvider(OutcomeEventsTable.TABLE_NAME, records) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - val events = outcomeEventsRepository.getAllEventsToSend() - - // Then - events.count() shouldBe 3 - events[0].outcomeId shouldBe "outcomeId1" - events[0].weight shouldBe 0.2f - events[0].timestamp shouldBe 1111L - events[0].sessionTime shouldBe 1L - events[0].outcomeSource shouldNotBe null - events[0].outcomeSource!!.directBody shouldBe null - events[0].outcomeSource!!.indirectBody shouldBe null - events[1].outcomeId shouldBe "outcomeId2" - events[1].weight shouldBe 0.4f - events[1].timestamp shouldBe 2222L - events[1].sessionTime shouldBe 2L - events[1].outcomeSource shouldNotBe null - events[1].outcomeSource!!.directBody shouldBe null - events[1].outcomeSource!!.indirectBody shouldNotBe null - events[1].outcomeSource!!.indirectBody!!.notificationIds!!.length() shouldBe 2 - events[1].outcomeSource!!.indirectBody!!.notificationIds!!.getString(0) shouldBe "notificationId1" - events[1].outcomeSource!!.indirectBody!!.notificationIds!!.getString(1) shouldBe "notificationId2" - events[1].outcomeSource!!.indirectBody!!.inAppMessagesIds!!.length() shouldBe 2 - events[1].outcomeSource!!.indirectBody!!.inAppMessagesIds!!.getString(0) shouldBe "iamId1" - events[1].outcomeSource!!.indirectBody!!.inAppMessagesIds!!.getString(1) shouldBe "iamId2" - events[2].outcomeId shouldBe "outcomeId3" - events[2].weight shouldBe 0.6f - events[2].timestamp shouldBe 3333L - events[2].sessionTime shouldBe 3L - events[2].outcomeSource shouldNotBe null - events[2].outcomeSource!!.indirectBody shouldBe null - events[2].outcomeSource!!.directBody shouldNotBe null - events[2].outcomeSource!!.directBody!!.notificationIds!!.length() shouldBe 1 - events[2].outcomeSource!!.directBody!!.notificationIds!!.getString(0) shouldBe "notificationId3" - events[2].outcomeSource!!.directBody!!.inAppMessagesIds!!.length() shouldBe 1 - events[2].outcomeSource!!.directBody!!.inAppMessagesIds!!.getString(0) shouldBe "iamId3" - } + // Given + val records = + listOf( + mapOf( + OutcomeEventsTable.COLUMN_NAME_NAME to "outcomeId1", + OutcomeEventsTable.COLUMN_NAME_WEIGHT to 0.2f, + OutcomeEventsTable.COLUMN_NAME_TIMESTAMP to 1111L, + OutcomeEventsTable.COLUMN_NAME_SESSION_TIME to 1L, + OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE to "unattributed", + OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE to "unattributed", + ), + mapOf( + OutcomeEventsTable.COLUMN_NAME_NAME to "outcomeId2", + OutcomeEventsTable.COLUMN_NAME_WEIGHT to 0.4f, + OutcomeEventsTable.COLUMN_NAME_TIMESTAMP to 2222L, + OutcomeEventsTable.COLUMN_NAME_SESSION_TIME to 2L, + OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE to "indirect", + OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS to "[\"notificationId1\",\"notificationId2\"]", + OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE to "indirect", + OutcomeEventsTable.COLUMN_NAME_IAM_IDS to "[\"iamId1\",\"iamId2\"]", + ), + mapOf( + OutcomeEventsTable.COLUMN_NAME_NAME to "outcomeId3", + OutcomeEventsTable.COLUMN_NAME_WEIGHT to 0.6f, + OutcomeEventsTable.COLUMN_NAME_TIMESTAMP to 3333L, + OutcomeEventsTable.COLUMN_NAME_SESSION_TIME to 3L, + OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE to "direct", + OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS to "[\"notificationId3\"]", + OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE to "direct", + OutcomeEventsTable.COLUMN_NAME_IAM_IDS to "[\"iamId3\"]", + ), + ) + val mockDatabasePair = DatabaseMockHelper.databaseProvider(OutcomeEventsTable.TABLE_NAME, records) + + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + val events = outcomeEventsRepository.getAllEventsToSend() + + // Then + events.count() shouldBe 3 + events[0].outcomeId shouldBe "outcomeId1" + events[0].weight shouldBe 0.2f + events[0].timestamp shouldBe 1111L + events[0].sessionTime shouldBe 1L + events[0].outcomeSource shouldNotBe null + events[0].outcomeSource!!.directBody shouldBe null + events[0].outcomeSource!!.indirectBody shouldBe null + events[1].outcomeId shouldBe "outcomeId2" + events[1].weight shouldBe 0.4f + events[1].timestamp shouldBe 2222L + events[1].sessionTime shouldBe 2L + events[1].outcomeSource shouldNotBe null + events[1].outcomeSource!!.directBody shouldBe null + events[1].outcomeSource!!.indirectBody shouldNotBe null + events[1].outcomeSource!!.indirectBody!!.notificationIds!!.length() shouldBe 2 + events[1].outcomeSource!!.indirectBody!!.notificationIds!!.getString(0) shouldBe "notificationId1" + events[1].outcomeSource!!.indirectBody!!.notificationIds!!.getString(1) shouldBe "notificationId2" + events[1].outcomeSource!!.indirectBody!!.inAppMessagesIds!!.length() shouldBe 2 + events[1].outcomeSource!!.indirectBody!!.inAppMessagesIds!!.getString(0) shouldBe "iamId1" + events[1].outcomeSource!!.indirectBody!!.inAppMessagesIds!!.getString(1) shouldBe "iamId2" + events[2].outcomeId shouldBe "outcomeId3" + events[2].weight shouldBe 0.6f + events[2].timestamp shouldBe 3333L + events[2].sessionTime shouldBe 3L + events[2].outcomeSource shouldNotBe null + events[2].outcomeSource!!.indirectBody shouldBe null + events[2].outcomeSource!!.directBody shouldNotBe null + events[2].outcomeSource!!.directBody!!.notificationIds!!.length() shouldBe 1 + events[2].outcomeSource!!.directBody!!.notificationIds!!.getString(0) shouldBe "notificationId3" + events[2].outcomeSource!!.directBody!!.inAppMessagesIds!!.length() shouldBe 1 + events[2].outcomeSource!!.directBody!!.inAppMessagesIds!!.getString(0) shouldBe "iamId3" } test("save unique outcome should insert no rows when no influences") { - runTest(dispatcherProvider.io) { - // Given - val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.COLUMN_NAME_NAME) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) + // Given + val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.COLUMN_NAME_NAME) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) - // When - outcomeEventsRepository.saveUniqueOutcomeEventParams(OutcomeEventParams("outcomeId1", null, 0f, 0, 1111)) + // When + outcomeEventsRepository.saveUniqueOutcomeEventParams(OutcomeEventParams("outcomeId1", null, 0f, 0, 1111)) - // Then - verify(exactly = 0) { mockDatabasePair.second.insert(CachedUniqueOutcomeTable.COLUMN_NAME_NAME, null, any()) } - } + // Then + verify(exactly = 0) { mockDatabasePair.second.insert(CachedUniqueOutcomeTable.COLUMN_NAME_NAME, null, any()) } } test("save unique outcome should insert 1 row for each unique influence when direct notification and indiract iam") { - runTest(dispatcherProvider.io) { - // Given - val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - outcomeEventsRepository.saveUniqueOutcomeEventParams( - OutcomeEventParams( - "outcomeId1", - OutcomeSource( - OutcomeSourceBody(JSONArray().put("notificationId1")), - OutcomeSourceBody(null, JSONArray().put("iamId1").put("iamId2")), - ), - .2f, - 0, - 2222, + // Given + val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + outcomeEventsRepository.saveUniqueOutcomeEventParams( + OutcomeEventParams( + "outcomeId1", + OutcomeSource( + OutcomeSourceBody(JSONArray().put("notificationId1")), + OutcomeSourceBody(null, JSONArray().put("iamId1").put("iamId2")), ), + .2f, + 0, + 2222, + ), + ) + + // Then + verifyAll { + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId1" + }, + ) + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId1" + }, + ) + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId2" + }, ) - - // Then - verifyAll { - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId1" - }, - ) - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId1" - }, - ) - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId2" - }, - ) - } } } test("save unique outcome should insert 1 row for each unique influence when direct iam and indiract notifications") { - runTest(dispatcherProvider.io) { - // Given - val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - outcomeEventsRepository.saveUniqueOutcomeEventParams( - OutcomeEventParams( - "outcomeId1", - OutcomeSource( - OutcomeSourceBody(null, JSONArray().put("iamId1")), - OutcomeSourceBody(JSONArray().put("notificationId1").put("notificationId2")), - ), - .2f, - 0, - 2222, + // Given + val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + outcomeEventsRepository.saveUniqueOutcomeEventParams( + OutcomeEventParams( + "outcomeId1", + OutcomeSource( + OutcomeSourceBody(null, JSONArray().put("iamId1")), + OutcomeSourceBody(JSONArray().put("notificationId1").put("notificationId2")), ), + .2f, + 0, + 2222, + ), + ) + + // Then + verifyAll { + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId1" + }, + ) + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId2" + }, + ) + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId1" + }, ) - - // Then - verifyAll { - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId1" - }, - ) - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId2" - }, - ) - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId1" - }, - ) - } } } test("save unique outcome should insert 1 row for each unique influence when direct notification and iam") { - runTest(dispatcherProvider.io) { - // Given - val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - outcomeEventsRepository.saveUniqueOutcomeEventParams( - OutcomeEventParams( - "outcomeId1", - OutcomeSource( - OutcomeSourceBody(JSONArray().put("notificationId1"), JSONArray().put("iamId1")), - null, - ), - .2f, - 0, - 2222, + // Given + val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + outcomeEventsRepository.saveUniqueOutcomeEventParams( + OutcomeEventParams( + "outcomeId1", + OutcomeSource( + OutcomeSourceBody(JSONArray().put("notificationId1"), JSONArray().put("iamId1")), + null, ), + .2f, + 0, + 2222, + ), + ) + + // Then + verifyAll { + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId1" + }, + ) + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId1" + }, ) - - // Then - verifyAll { - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId1" - }, - ) - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId1" - }, - ) - } } } test("save unique outcome should insert 1 row for each unique influence when indirect notification and iam") { - runTest(dispatcherProvider.io) { - // Given - val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - outcomeEventsRepository.saveUniqueOutcomeEventParams( - OutcomeEventParams( - "outcomeId1", - OutcomeSource( - null, - OutcomeSourceBody(JSONArray().put("notificationId1").put("notificationId2"), JSONArray().put("iamId1").put("iamId2")), - ), - .2f, - 0, - 2222, + // Given + val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + outcomeEventsRepository.saveUniqueOutcomeEventParams( + OutcomeEventParams( + "outcomeId1", + OutcomeSource( + null, + OutcomeSourceBody(JSONArray().put("notificationId1").put("notificationId2"), JSONArray().put("iamId1").put("iamId2")), ), + .2f, + 0, + 2222, + ), + ) + + // Then + verifyAll { + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId1" + }, + ) + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId2" + }, + ) + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId1" + }, + ) + mockDatabasePair.second.insert( + CachedUniqueOutcomeTable.TABLE_NAME, + null, + withArg { + it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" + it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId2" + }, ) - - // Then - verifyAll { - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId1" - }, - ) - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "notification" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "notificationId2" - }, - ) - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId1" - }, - ) - mockDatabasePair.second.insert( - CachedUniqueOutcomeTable.TABLE_NAME, - null, - withArg { - it[CachedUniqueOutcomeTable.COLUMN_NAME_NAME] shouldBe "outcomeId1" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE] shouldBe "iam" - it[CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID] shouldBe "iamId2" - }, - ) - } } } test("retrieve non-cached influence should return full list when there are no cached unique influences") { - runTest(dispatcherProvider.io) { - // Given - val records = listOf>() - val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME, records) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - val influences = - outcomeEventsRepository.getNotCachedUniqueInfluencesForOutcome( - "outcomeId1", - listOf( - Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray().put("notificationId1")), - Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray().put("notificationId2")), - ), - ) - - // Then - influences.count() shouldBe 2 - } + // Given + val records = listOf>() + val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME, records) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + val influences = + outcomeEventsRepository.getNotCachedUniqueInfluencesForOutcome( + "outcomeId1", + listOf( + Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray().put("notificationId1")), + Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray().put("notificationId2")), + ), + ) + + // Then + influences.count() shouldBe 2 } test("retrieve non-cached influence should filter out an influence when there are is a matching influence") { - runTest(dispatcherProvider.io) { - // Given - val records = listOf(mapOf(CachedUniqueOutcomeTable.COLUMN_NAME_NAME to "outcomeId1")) - val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME, records) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - val influences = - outcomeEventsRepository.getNotCachedUniqueInfluencesForOutcome( - "outcomeId1", - listOf( - Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray().put("notificationId1")), - Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray().put("notificationId2")), - ), - ) - - // Then - influences.count() shouldBe 0 - } + // Given + val records = listOf(mapOf(CachedUniqueOutcomeTable.COLUMN_NAME_NAME to "outcomeId1")) + val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME, records) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + val influences = + outcomeEventsRepository.getNotCachedUniqueInfluencesForOutcome( + "outcomeId1", + listOf( + Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray().put("notificationId1")), + Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray().put("notificationId2")), + ), + ) + + // Then + influences.count() shouldBe 0 } test("clear unique influence should delete out an influence when there are is a matching influence") { - runTest(dispatcherProvider.io) { - // Given - val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) - val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first, testDispatcher) - - // When - outcomeEventsRepository.cleanCachedUniqueOutcomeEventNotifications() - - // Then - verifyAll { - mockDatabasePair.second.delete(CachedUniqueOutcomeTable.TABLE_NAME, any(), any()) - } + // Given + val mockDatabasePair = DatabaseMockHelper.databaseProvider(CachedUniqueOutcomeTable.TABLE_NAME) + val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) + + // When + outcomeEventsRepository.cleanCachedUniqueOutcomeEventNotifications() + + // Then + verifyAll { + mockDatabasePair.second.delete(CachedUniqueOutcomeTable.TABLE_NAME, any(), any()) } } }) diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/data/impl/NotificationRepository.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/data/impl/NotificationRepository.kt index d1ad883e12..66c750e3c0 100644 --- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/data/impl/NotificationRepository.kt +++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/data/impl/NotificationRepository.kt @@ -4,8 +4,6 @@ import android.app.NotificationManager import android.content.ContentValues import android.provider.BaseColumns import android.text.TextUtils -import com.onesignal.common.threading.CoroutineDispatcherProvider -import com.onesignal.common.threading.DefaultDispatcherProvider import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.database.IDatabaseProvider import com.onesignal.core.internal.database.impl.OneSignalDbContract @@ -16,6 +14,7 @@ import com.onesignal.notifications.internal.common.NotificationHelper import com.onesignal.notifications.internal.data.INotificationQueryHelper import com.onesignal.notifications.internal.data.INotificationRepository import com.onesignal.notifications.internal.limiting.INotificationLimitManager +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONException @@ -25,7 +24,6 @@ internal class NotificationRepository( private val _databaseProvider: IDatabaseProvider, private val _time: ITime, private val _badgeCountUpdater: IBadgeCountUpdater, - private val dispatchers: CoroutineDispatcherProvider = DefaultDispatcherProvider(), ) : INotificationRepository { /** * Deletes notifications with created timestamps older than 7 days @@ -33,7 +31,7 @@ internal class NotificationRepository( * 1. NotificationTable.TABLE_NAME */ override suspend fun deleteExpiredNotifications() { - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { val whereStr: String = OneSignalDbContract.NotificationTable.COLUMN_NAME_CREATED_TIME.toString() + " < ?" val sevenDaysAgoInSeconds: String = java.lang.String.valueOf( @@ -50,7 +48,7 @@ internal class NotificationRepository( } override suspend fun markAsDismissedForOutstanding() { - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { val appContext = _applicationService.appContext val notificationManager: NotificationManager = NotificationHelper.getNotificationManager(appContext) val retColumn = arrayOf(OneSignalDbContract.NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID) @@ -81,7 +79,7 @@ internal class NotificationRepository( } override suspend fun markAsDismissedForGroup(group: String) { - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { val appContext = _applicationService.appContext val notificationManager: NotificationManager = NotificationHelper.getNotificationManager(appContext) @@ -126,7 +124,7 @@ internal class NotificationRepository( override suspend fun markAsDismissed(androidId: Int): Boolean { var didDismiss: Boolean = false - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { didDismiss = internalMarkAsDismissed(androidId) } @@ -161,7 +159,7 @@ internal class NotificationRepository( var result = false - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { val retColumn = arrayOf(OneSignalDbContract.NotificationTable.COLUMN_NAME_NOTIFICATION_ID) val whereArgs = arrayOf(id!!) @@ -190,7 +188,7 @@ internal class NotificationRepository( androidId: Int, groupId: String, ) { - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { // There currently isn't a visible notification from for this group_id. // Save the group summary notification id so it can be updated later. val values = ContentValues() @@ -220,7 +218,7 @@ internal class NotificationRepository( expireTime: Long, jsonPayload: String, ) { - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { Logging.debug("Saving Notification id=$id") try { @@ -304,7 +302,7 @@ internal class NotificationRepository( summaryGroup: String?, clearGroupOnSummaryClick: Boolean, ) { - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { var whereStr: String var whereArgs: Array? = null if (summaryGroup != null) { @@ -360,7 +358,7 @@ internal class NotificationRepository( override suspend fun getGroupId(androidId: Int): String? { var groupId: String? = null - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { _databaseProvider.os.query( OneSignalDbContract.NotificationTable.TABLE_NAME, // retColumn @@ -380,7 +378,7 @@ internal class NotificationRepository( override suspend fun getAndroidIdFromCollapseKey(collapseKey: String): Int? { var androidId: Int? = null - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { _databaseProvider.os.query( OneSignalDbContract.NotificationTable.TABLE_NAME, // retColumn @@ -404,7 +402,7 @@ internal class NotificationRepository( notificationsToMakeRoomFor: Int, maxNumberOfNotificationsInt: Int, ) { - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { val maxNumberOfNotificationsString = maxNumberOfNotificationsInt.toString() try { @@ -439,7 +437,7 @@ internal class NotificationRepository( override suspend fun listNotificationsForGroup(summaryGroup: String): List { val listOfNotifications = mutableListOf() - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { val whereArgs = arrayOf(summaryGroup) _databaseProvider.os.query( @@ -514,7 +512,7 @@ internal class NotificationRepository( val whereArgs = if (isGroupless) null else arrayOf(group) - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { // Order by timestamp in descending and limit to 1 _databaseProvider.os.query( OneSignalDbContract.NotificationTable.TABLE_NAME, @@ -540,7 +538,7 @@ internal class NotificationRepository( override suspend fun listNotificationsForOutstanding(excludeAndroidIds: List?): List { val listOfNotifications = mutableListOf() - withContext(dispatchers.io) { + withContext(Dispatchers.IO) { val dbQuerySelection = _queryHelper.recentUninteractedWithNotificationsWhere() if (excludeAndroidIds != null) { diff --git a/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/TestDispatcherProvider.kt b/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/TestDispatcherProvider.kt deleted file mode 100644 index 2288659dc8..0000000000 --- a/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/TestDispatcherProvider.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.onesignal.mocks - -import com.onesignal.common.threading.CoroutineDispatcherProvider -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestDispatcher - -/** - * Test implementation of [CoroutineDispatcherProvider] for unit tests. - * Uses a [TestDispatcher] for deterministic testing. - * - * Usage in tests: - * ``` - * test("my test") { - * val testDispatcher = StandardTestDispatcher() - * val dispatcherProvider = TestDispatcherProvider(testDispatcher) - * - * runTest(testDispatcher.scheduler) { - * val service = MyService(dispatcherProvider) - * service.doWork() - * - * // Option 1: Advance until all pending coroutines complete - * advanceUntilIdle() - * - * // Option 2: Advance virtual time by a specific amount (e.g., 100ms) - * // advanceTimeBy(100) - * - * // Option 3: Run only coroutines scheduled at current time - * // runCurrent() - * - * // Make assertions - * } - * } - * ``` - * - * Methods to control execution: - * - [advanceUntilIdle()] - Runs all pending coroutines until there's nothing left to execute - * - [advanceTimeBy(delayTime)] - Advances virtual time by the specified amount and runs - * coroutines scheduled for that time period - * - [runCurrent()] - Runs only the coroutines that are scheduled to run at the current - * virtual time (doesn't advance time) - * - [currentTime] - Property to check the current virtual time - */ -class TestDispatcherProvider( - private val testDispatcher: TestDispatcher = StandardTestDispatcher() -) : CoroutineDispatcherProvider { - override val io: CoroutineDispatcher = testDispatcher - override val default: CoroutineDispatcher = testDispatcher - - private val scope: CoroutineScope by lazy { - CoroutineScope(SupervisorJob() + testDispatcher) - } - - override fun launchOnIO(block: suspend () -> Unit): Job { - return scope.launch { block() } - } - - override fun launchOnDefault(block: suspend () -> Unit): Job { - return scope.launch { block() } - } -}