Skip to content

Commit f7a7a5d

Browse files
authored
Merge pull request #2157 from OneSignal/fix/null_onesignalid_crash
Recover null onesignal ID crashes for Operations
2 parents 65d40f4 + f7b0e5f commit f7a7a5d

File tree

3 files changed

+101
-6
lines changed

3 files changed

+101
-6
lines changed

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ internal class OperationModelStore(prefs: IPreferencesService) : ModelStore<Oper
3838
return null
3939
}
4040

41-
if (!jsonObject.has(Operation::name.name)) {
42-
Logging.error("jsonObject must have '${Operation::name.name}' attribute")
41+
if (!isValidOperation(jsonObject)) {
4342
return null
4443
}
4544

@@ -69,4 +68,34 @@ internal class OperationModelStore(prefs: IPreferencesService) : ModelStore<Oper
6968

7069
return operation
7170
}
71+
72+
/**
73+
* Checks if a JSONObject is a valid Operation. Contains a check for onesignalId.
74+
* This is a rare case that a cached Operation is missing the onesignalId,
75+
* which would continuously cause crashes when the Operation is processed.
76+
*
77+
* @param jsonObject The [JSONObject] that represents an Operation
78+
*/
79+
private fun isValidOperation(jsonObject: JSONObject): Boolean {
80+
if (!jsonObject.has(Operation::name.name)) {
81+
Logging.error("jsonObject must have '${Operation::name.name}' attribute")
82+
return false
83+
}
84+
85+
val operationName = jsonObject.getString(Operation::name.name)
86+
87+
val excluded =
88+
setOf(
89+
LoginUserOperationExecutor.LOGIN_USER,
90+
LoginUserFromSubscriptionOperationExecutor.LOGIN_USER_FROM_SUBSCRIPTION_USER,
91+
)
92+
93+
// Must have onesignalId if it is not one of the excluded operations above
94+
if (!jsonObject.has("onesignalId") && !excluded.contains(operationName)) {
95+
Logging.error("$operationName jsonObject must have 'onesignalId' attribute")
96+
return false
97+
}
98+
99+
return true
100+
}
72101
}

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/common/ModelingTests.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
99
import com.onesignal.core.internal.preferences.PreferenceStores
1010
import com.onesignal.mocks.MockHelper
1111
import com.onesignal.mocks.MockPreferencesService
12-
import com.onesignal.user.internal.operations.SetPropertyOperation
13-
import com.onesignal.user.internal.operations.SetTagOperation
12+
import com.onesignal.user.internal.operations.LoginUserFromSubscriptionOperation
13+
import com.onesignal.user.internal.operations.LoginUserOperation
1414
import com.onesignal.user.internal.subscriptions.SubscriptionModel
1515
import com.onesignal.user.internal.subscriptions.SubscriptionModelStore
1616
import io.kotest.core.spec.style.FunSpec
@@ -150,15 +150,15 @@ class ModelingTests : FunSpec({
150150
val operationModelStore = OperationModelStore(prefs)
151151
val jsonArray = JSONArray()
152152

153-
val cachedOperation = SetTagOperation()
153+
val cachedOperation = LoginUserFromSubscriptionOperation()
154154
cachedOperation.id = UUID.randomUUID().toString()
155155
// Add duplicate operations to the cache
156156
jsonArray.put(cachedOperation.toJSON())
157157
jsonArray.put(cachedOperation.toJSON())
158158
prefs.saveString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + "operations", jsonArray.toString())
159159

160160
// When - adding an operation first and then loading from cache
161-
val newOperation = SetPropertyOperation()
161+
val newOperation = LoginUserOperation()
162162
newOperation.id = UUID.randomUUID().toString()
163163
operationModelStore.add(newOperation)
164164
operationModelStore.loadOperations()
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.onesignal.core.internal.operations
2+
3+
import com.onesignal.core.internal.operations.impl.OperationModelStore
4+
import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
5+
import com.onesignal.core.internal.preferences.PreferenceStores
6+
import com.onesignal.debug.LogLevel
7+
import com.onesignal.debug.internal.logging.Logging
8+
import com.onesignal.mocks.MockPreferencesService
9+
import com.onesignal.user.internal.operations.LoginUserOperation
10+
import com.onesignal.user.internal.operations.SetPropertyOperation
11+
import io.kotest.core.spec.style.FunSpec
12+
import io.kotest.matchers.shouldBe
13+
import io.kotest.matchers.shouldNotBe
14+
import org.json.JSONArray
15+
import org.json.JSONObject
16+
import java.util.UUID
17+
18+
class OperationModelStoreTests : FunSpec({
19+
20+
beforeAny {
21+
Logging.logLevel = LogLevel.NONE
22+
}
23+
24+
test("does not load invalid cached operations") {
25+
// Given
26+
val prefs = MockPreferencesService()
27+
val operationModelStore = OperationModelStore(prefs)
28+
val jsonArray = JSONArray()
29+
30+
// 1. Create a VALID Operation with onesignalId
31+
val validOperation = SetPropertyOperation(UUID.randomUUID().toString(), UUID.randomUUID().toString(), "property", "value")
32+
validOperation.id = UUID.randomUUID().toString()
33+
34+
// 2. Create a VALID operation missing onesignalId
35+
val validOperationMissingOnesignalId = LoginUserOperation()
36+
validOperationMissingOnesignalId.id = UUID.randomUUID().toString()
37+
38+
// 3. Create an INVALID Operation missing onesignalId
39+
val invalidOperationMissingOnesignalId = SetPropertyOperation()
40+
invalidOperationMissingOnesignalId.id = UUID.randomUUID().toString()
41+
42+
// 4. Create an INVALID Operation missing operation name
43+
val invalidOperationMissingName =
44+
JSONObject()
45+
.put("app_id", UUID.randomUUID().toString())
46+
.put("onesignalId", UUID.randomUUID().toString())
47+
.put("id", UUID.randomUUID().toString())
48+
49+
// Add the Operations to the cache
50+
jsonArray.put(validOperation.toJSON())
51+
jsonArray.put(validOperationMissingOnesignalId.toJSON())
52+
jsonArray.put(invalidOperationMissingOnesignalId.toJSON())
53+
jsonArray.put(invalidOperationMissingName)
54+
prefs.saveString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + "operations", jsonArray.toString())
55+
56+
// When
57+
operationModelStore.loadOperations()
58+
59+
// Then
60+
operationModelStore.list().count() shouldBe 2
61+
operationModelStore.get(validOperation.id) shouldNotBe null
62+
operationModelStore.get(validOperationMissingOnesignalId.id) shouldNotBe null
63+
operationModelStore.get(invalidOperationMissingOnesignalId.id) shouldBe null
64+
operationModelStore.get(invalidOperationMissingName["id"] as String) shouldBe null
65+
}
66+
})

0 commit comments

Comments
 (0)