@@ -5,11 +5,15 @@ import com.onesignal.common.threading.WaiterWithValue
55import com.onesignal.core.internal.operations.impl.OperationModelStore
66import com.onesignal.core.internal.operations.impl.OperationRepo
77import com.onesignal.core.internal.operations.impl.OperationRepo.OperationQueueItem
8+ import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
9+ import com.onesignal.core.internal.preferences.PreferenceStores
810import com.onesignal.core.internal.time.impl.Time
911import com.onesignal.debug.LogLevel
1012import com.onesignal.debug.internal.logging.Logging
1113import com.onesignal.mocks.MockHelper
14+ import com.onesignal.mocks.MockPreferencesService
1215import com.onesignal.user.internal.operations.ExecutorMocks.Companion.getNewRecordState
16+ import com.onesignal.user.internal.operations.LoginUserOperation
1317import io.kotest.core.spec.style.FunSpec
1418import io.kotest.matchers.shouldBe
1519import io.mockk.CapturingSlot
@@ -28,6 +32,7 @@ import kotlinx.coroutines.launch
2832import kotlinx.coroutines.withTimeout
2933import kotlinx.coroutines.withTimeoutOrNull
3034import kotlinx.coroutines.yield
35+ import org.json.JSONArray
3136import java.util.UUID
3237
3338// Mocks used by every test in this file
@@ -76,6 +81,65 @@ class OperationRepoTests : FunSpec({
7681 Logging .logLevel = LogLevel .NONE
7782 }
7883
84+ test("ensure loading in the background thread does not block enqueue") {
85+ // Given
86+ val prefs = MockPreferencesService ()
87+ val mocks = Mocks ()
88+ val operationModelStore : OperationModelStore = spyk(OperationModelStore (prefs))
89+ val operationRepo =
90+ spyk(
91+ OperationRepo (
92+ listOf(mocks.executor),
93+ operationModelStore,
94+ mocks.configModelStore,
95+ Time (),
96+ getNewRecordState(mocks.configModelStore),
97+ ),
98+ )
99+
100+ val cachedOperation = LoginUserOperation ()
101+ val newOperation = LoginUserOperation ()
102+ val jsonArray = JSONArray ()
103+
104+ // cache the operation
105+ jsonArray.put(cachedOperation.toJSON())
106+ prefs.saveString(PreferenceStores .ONESIGNAL , PreferenceOneSignalKeys .MODEL_STORE_PREFIX + "operations", jsonArray.toString())
107+
108+ cachedOperation.id = UUID .randomUUID().toString()
109+ newOperation.id = UUID .randomUUID().toString()
110+ every { operationModelStore.create(any()) } answers {
111+ // simulate a prolonged loading from cache
112+ Thread .sleep(1000)
113+ cachedOperation
114+ }
115+
116+ // simulate a background thread to load operations
117+ val backgroundThread =
118+ Thread {
119+ operationRepo.loadSavedOperations()
120+ }
121+
122+ val mainThread =
123+ Thread {
124+ operationRepo.enqueue(newOperation)
125+ }
126+
127+ // When
128+ backgroundThread.start()
129+ mainThread.start()
130+
131+ // Then
132+ // insertion from the main thread is done without blocking
133+ mainThread.join(500)
134+ operationRepo.queue.size shouldBe 1
135+ mainThread.state shouldBe Thread .State .TERMINATED
136+
137+ // after loading is completed, the cached operation should be at the beginning of the queue
138+ backgroundThread.join()
139+ operationRepo.queue.size shouldBe 2
140+ operationRepo.queue.first().operation shouldBe cachedOperation
141+ }
142+
79143 test("containsInstanceOf") {
80144 // Given
81145 val operationRepo = Mocks ().operationRepo
0 commit comments