Skip to content

Commit 6e9b690

Browse files
committed
ensure internalEnqueue can't add the same operation
This fixes a bug where loadSavedOperations would duplicate operations in OperationRepo.queue. This is because it is possible for operations to be added before it is started and these are also persisted to disk. Improved OperationRepoTests by making mockOperationModelStore modify a in memory list. This was required to write a test to ensure loadSavedOperations wasn't duplicating operations.
1 parent 702d13c commit 6e9b690

File tree

2 files changed

+38
-10
lines changed

2 files changed

+38
-10
lines changed

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ internal class OperationRepo(
4848
)
4949

5050
private val executorsMap: Map<String, IOperationExecutor>
51-
private val queue = mutableListOf<OperationQueueItem>()
51+
internal val queue = mutableListOf<OperationQueueItem>()
5252
private val waiter = WaiterWithValue<LoopWaiterMessage>()
5353
private var paused = false
5454
private var coroutineScope = CoroutineScope(newSingleThreadContext(name = "OpRepo"))
@@ -143,6 +143,12 @@ internal class OperationRepo(
143143
addToStore: Boolean,
144144
index: Int? = null,
145145
) {
146+
val hasExisting = queue.any { it.operation.id == queueItem.operation.id }
147+
if (hasExisting) {
148+
Logging.debug("OperationRepo: internalEnqueue - operation.id: ${queueItem.operation.id} already exists in the queue.")
149+
return
150+
}
151+
146152
synchronized(queue) {
147153
if (index != null) {
148154
queue.add(index, queueItem)
@@ -402,7 +408,7 @@ internal class OperationRepo(
402408
* NOTE: Sometimes the loading might take longer than expected due to I/O reads from disk
403409
* Any I/O implies executing time will vary greatly.
404410
*/
405-
private fun loadSavedOperations() {
411+
internal fun loadSavedOperations() {
406412
_operationModelStore.loadOperations()
407413
for (operation in _operationModelStore.list().withIndex()) {
408414
internalEnqueue(

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,24 @@ import kotlinx.coroutines.delay
2727
import kotlinx.coroutines.launch
2828
import kotlinx.coroutines.withTimeoutOrNull
2929
import kotlinx.coroutines.yield
30+
import java.util.UUID
3031

3132
// Mocks used by every test in this file
3233
private class Mocks {
3334
val configModelStore = MockHelper.configModelStore()
3435

3536
val operationModelStore: OperationModelStore =
3637
run {
38+
val operationStoreList = mutableListOf<Operation>()
3739
val mockOperationModelStore = mockk<OperationModelStore>()
3840
every { mockOperationModelStore.loadOperations() } just runs
39-
every { mockOperationModelStore.list() } returns listOf()
40-
every { mockOperationModelStore.add(any()) } just runs
41-
every { mockOperationModelStore.remove(any()) } just runs
41+
every { mockOperationModelStore.list() } returns operationStoreList
42+
every { mockOperationModelStore.add(any()) } answers { operationStoreList.add(firstArg<Operation>()) }
43+
every { mockOperationModelStore.remove(any()) } answers {
44+
val id = firstArg<String>()
45+
val op = operationStoreList.firstOrNull { it.id == id }
46+
operationStoreList.remove(op)
47+
}
4248
mockOperationModelStore
4349
}
4450

@@ -117,9 +123,9 @@ class OperationRepoTests : FunSpec({
117123
test("enqueue operation executes and is removed when executed") {
118124
// Given
119125
val mocks = Mocks()
120-
121126
val operationIdSlot = slot<String>()
122127
val operation = mockOperation(operationIdSlot = operationIdSlot)
128+
val opId = operation.id
123129

124130
// When
125131
mocks.operationRepo.start()
@@ -136,7 +142,7 @@ class OperationRepoTests : FunSpec({
136142
it[0] shouldBe operation
137143
},
138144
)
139-
mocks.operationModelStore.remove("operationId")
145+
mocks.operationModelStore.remove(opId)
140146
}
141147
}
142148

@@ -151,6 +157,7 @@ class OperationRepoTests : FunSpec({
151157

152158
val operationIdSlot = slot<String>()
153159
val operation = mockOperation(operationIdSlot = operationIdSlot)
160+
val opId = operation.id
154161

155162
// When
156163
opRepo.start()
@@ -174,7 +181,7 @@ class OperationRepoTests : FunSpec({
174181
it[0] shouldBe operation
175182
},
176183
)
177-
mocks.operationModelStore.remove("operationId")
184+
mocks.operationModelStore.remove(opId)
178185
}
179186
}
180187

@@ -210,6 +217,7 @@ class OperationRepoTests : FunSpec({
210217

211218
val operationIdSlot = slot<String>()
212219
val operation = mockOperation(operationIdSlot = operationIdSlot)
220+
val opId = operation.id
213221

214222
// When
215223
mocks.operationRepo.start()
@@ -226,7 +234,7 @@ class OperationRepoTests : FunSpec({
226234
it[0] shouldBe operation
227235
},
228236
)
229-
mocks.operationModelStore.remove("operationId")
237+
mocks.operationModelStore.remove(opId)
230238
}
231239
}
232240

@@ -608,10 +616,24 @@ class OperationRepoTests : FunSpec({
608616
spyListener.onOperationRepoLoaded()
609617
}
610618
}
619+
620+
test("ensure loadSavedOperations doesn't duplicate existing OperationItems") {
621+
// Given
622+
val mocks = Mocks()
623+
val op = mockOperation()
624+
mocks.operationRepo.enqueue(op)
625+
626+
// When
627+
mocks.operationRepo.loadSavedOperations()
628+
629+
// Then
630+
mocks.operationRepo.queue.size shouldBe 1
631+
mocks.operationRepo.queue.first().operation shouldBe op
632+
}
611633
}) {
612634
companion object {
613635
private fun mockOperation(
614-
id: String = "operationId",
636+
id: String = UUID.randomUUID().toString(),
615637
name: String = "DUMMY_OPERATION",
616638
canStartExecute: Boolean = true,
617639
groupComparisonType: GroupComparisonType = GroupComparisonType.NONE,

0 commit comments

Comments
 (0)