Skip to content

Commit 84991d6

Browse files
authored
Merge pull request #712 from mindbox-cloud/release/2.15.1
Release 2.15.1
2 parents 771b834 + 2b2865e commit 84991d6

11 files changed

Lines changed: 1018 additions & 24 deletions

File tree

example/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ dependencies {
9292
implementation 'com.google.code.gson:gson:2.11.0'
9393

9494
//Mindbox
95-
implementation 'cloud.mindbox:mobile-sdk:2.15.0'
95+
implementation 'cloud.mindbox:mobile-sdk:2.15.1'
9696
implementation 'cloud.mindbox:mindbox-firebase'
9797
implementation 'cloud.mindbox:mindbox-huawei'
9898
implementation 'cloud.mindbox:mindbox-rustore'

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ android.enableJetifier=true
2020
# Kotlin code style for this project: "official" or "obsolete":
2121
kotlin.code.style=official
2222
# SDK version property
23-
SDK_VERSION_NAME=2.15.0
23+
SDK_VERSION_NAME=2.15.1
2424
USE_LOCAL_MINDBOX_COMMON=true
2525
android.nonTransitiveRClass=false
2626
kotlin.mpp.androidGradlePluginCompatibility.nowarn=true

sdk/consumer-rules.pro

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
# Keep model classes
22
-keepclassmembers class cloud.mindbox.mobile_sdk.models** { *; }
3-
-keepclassmembers enum cloud.mindbox.mobile_sdk.models** { *; }
43
-keep class cloud.mindbox.mobile_sdk.MindboxConfiguration { *; }
54
-keep class cloud.mindbox.mobile_sdk.pushes.PushAction { *; }
6-
-keep class cloud.mindbox.mobile_sdk.inapp.data** { *; }
5+
-keepclassmembers class cloud.mindbox.mobile_sdk.inapp.data.dto.** { *; }
76
-keep class cloud.mindbox.mobile_sdk.inapp.domain.models** { *; }
8-
9-
-keep public class * extends android.preference.Preference

sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/presentation/view/WebViewAction.kt

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,30 +84,30 @@ public sealed class BridgeMessage {
8484
public abstract val timestamp: Long
8585

8686
public data class Request(
87-
override val version: Int,
88-
override val action: WebViewAction,
89-
override val payload: String?,
90-
override val id: String,
91-
override val timestamp: Long,
92-
override val type: String = TYPE_REQUEST,
87+
@SerializedName("version") override val version: Int,
88+
@SerializedName("action") override val action: WebViewAction,
89+
@SerializedName("payload") override val payload: String?,
90+
@SerializedName("id") override val id: String,
91+
@SerializedName("timestamp") override val timestamp: Long,
92+
@SerializedName("type") override val type: String = TYPE_REQUEST,
9393
) : BridgeMessage()
9494

9595
public data class Response(
96-
override val version: Int,
97-
override val action: WebViewAction,
98-
override val payload: String?,
99-
override val id: String,
100-
override val timestamp: Long,
101-
override val type: String = TYPE_RESPONSE,
96+
@SerializedName("version") override val version: Int,
97+
@SerializedName("action") override val action: WebViewAction,
98+
@SerializedName("payload") override val payload: String?,
99+
@SerializedName("id") override val id: String,
100+
@SerializedName("timestamp") override val timestamp: Long,
101+
@SerializedName("type") override val type: String = TYPE_RESPONSE,
102102
) : BridgeMessage()
103103

104104
public data class Error(
105-
override val version: Int,
106-
override val action: WebViewAction,
107-
override val payload: String?,
108-
override val id: String,
109-
override val timestamp: Long,
110-
override val type: String = TYPE_ERROR,
105+
@SerializedName("version") override val version: Int,
106+
@SerializedName("action") override val action: WebViewAction,
107+
@SerializedName("payload") override val payload: String?,
108+
@SerializedName("id") override val id: String,
109+
@SerializedName("timestamp") override val timestamp: Long,
110+
@SerializedName("type") override val type: String = TYPE_ERROR,
111111
) : BridgeMessage()
112112

113113
public companion object {

sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/presentation/view/WebViewInappViewHolder.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,10 +782,12 @@ internal class WebViewInAppViewHolder(
782782
}
783783

784784
private data class NavigationInterceptedPayload(
785+
@SerializedName("url")
785786
val url: String
786787
)
787788

788789
private data class ErrorPayload(
790+
@SerializedName("error")
789791
val error: String
790792
)
791793

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
package cloud.mindbox.mobile_sdk.models
2+
3+
import com.google.gson.Gson
4+
import org.junit.Assert.assertEquals
5+
import org.junit.Assert.assertFalse
6+
import org.junit.Assert.assertNotNull
7+
import org.junit.Assert.assertNull
8+
import org.junit.Assert.assertTrue
9+
import org.junit.Test
10+
11+
/**
12+
* Tests for [MindboxErrorAdapter] via the public [MindboxError.toJson] API.
13+
*
14+
* The adapter handles two directions:
15+
* - write (toJson) — used by SDK clients to log/pass errors around — fully implemented.
16+
* - read (fromJson) — parsing is effectively non-functional in the current implementation
17+
* (the read() method reads the JSON key name instead of key value, causing it to always
18+
* fall through to `else -> null`). Tests below document this known behavior.
19+
*/
20+
class MindboxErrorAdapterTest {
21+
22+
// MindboxError subtypes have @JsonAdapter(MindboxErrorAdapter::class)
23+
private val gson = Gson()
24+
25+
// region write / toJson — Validation
26+
27+
@Test
28+
fun `toJson Validation - contains type MindboxError`() {
29+
val error = MindboxError.Validation(
30+
statusCode = 200,
31+
status = "ValidationError",
32+
validationMessages = emptyList(),
33+
)
34+
val json = error.toJson()
35+
assertTrue(json.contains(""""type":"MindboxError""""))
36+
}
37+
38+
@Test
39+
fun `toJson Validation - contains statusCode`() {
40+
val error = MindboxError.Validation(200, "ValidationError", emptyList())
41+
val json = error.toJson()
42+
assertTrue(json.contains(""""statusCode":200"""))
43+
}
44+
45+
@Test
46+
fun `toJson Validation - contains status`() {
47+
val error = MindboxError.Validation(200, "ValidationError", emptyList())
48+
val json = error.toJson()
49+
assertTrue(json.contains(""""status":"ValidationError""""))
50+
}
51+
52+
@Test
53+
fun `toJson Validation - contains empty validationMessages array`() {
54+
val error = MindboxError.Validation(200, "ValidationError", emptyList())
55+
val json = error.toJson()
56+
assertTrue(json.contains(""""validationMessages":[]"""))
57+
}
58+
59+
@Test
60+
fun `toJson Validation - contains validationMessages with entries`() {
61+
val error = MindboxError.Validation(
62+
statusCode = 200,
63+
status = "ValidationError",
64+
validationMessages = listOf(
65+
ValidationMessage(message = "field required", location = "email"),
66+
),
67+
)
68+
val json = error.toJson()
69+
assertTrue(json.contains(""""message":"field required""""))
70+
assertTrue(json.contains(""""location":"email""""))
71+
}
72+
73+
@Test
74+
fun `toJson Validation - full JSON structure`() {
75+
val error = MindboxError.Validation(200, "Ok", emptyList())
76+
val json = error.toJson()
77+
assertEquals(
78+
"""{"type":"MindboxError","data":{"statusCode":200,"status":"Ok","validationMessages":[]}}""",
79+
json,
80+
)
81+
}
82+
83+
// endregion
84+
85+
// region write / toJson — Protocol
86+
87+
@Test
88+
fun `toJson Protocol - contains type MindboxError`() {
89+
val error = MindboxError.Protocol(
90+
statusCode = 400,
91+
status = "Error",
92+
errorMessage = "Bad request",
93+
errorId = "err-1",
94+
httpStatusCode = 400,
95+
)
96+
val json = error.toJson()
97+
assertTrue(json.contains(""""type":"MindboxError""""))
98+
}
99+
100+
@Test
101+
fun `toJson Protocol - full JSON structure`() {
102+
val error = MindboxError.Protocol(400, "Error", "Bad request", "err-1", 400)
103+
val json = error.toJson()
104+
assertEquals(
105+
"""{"type":"MindboxError","data":{"statusCode":400,"status":"Error","errorMessage":"Bad request","errorId":"err-1","httpStatusCode":400}}""",
106+
json,
107+
)
108+
}
109+
110+
@Test
111+
fun `toJson Protocol - null optional fields omitted from JSON`() {
112+
// GSON's nullValue() silently skips a name+value pair when serializeNulls = false
113+
// (the default). MindboxErrorAdapter does not override this, so null fields are
114+
// absent from the output — not present as "null". This is the current behavior.
115+
val error = MindboxError.Protocol(403, "Forbidden", null, null, null)
116+
val json = error.toJson()
117+
assertFalse("null errorMessage should be omitted", json.contains("errorMessage"))
118+
assertFalse("null errorId should be omitted", json.contains("errorId"))
119+
assertFalse("null httpStatusCode should be omitted", json.contains("httpStatusCode"))
120+
}
121+
122+
// endregion
123+
124+
// region write / toJson — InternalServer
125+
126+
@Test
127+
fun `toJson InternalServer - contains type MindboxError`() {
128+
val error = MindboxError.InternalServer(500, "ServerError", "Internal error", "id-1", 500)
129+
val json = error.toJson()
130+
assertTrue(json.contains(""""type":"MindboxError""""))
131+
}
132+
133+
@Test
134+
fun `toJson InternalServer - full JSON structure`() {
135+
val error = MindboxError.InternalServer(500, "ServerError", "Internal error", "id-1", 500)
136+
val json = error.toJson()
137+
assertEquals(
138+
"""{"type":"MindboxError","data":{"statusCode":500,"status":"ServerError","errorMessage":"Internal error","errorId":"id-1","httpStatusCode":500}}""",
139+
json,
140+
)
141+
}
142+
143+
// endregion
144+
145+
// region write / toJson — UnknownServer
146+
147+
@Test
148+
fun `toJson UnknownServer - contains type NetworkError`() {
149+
val error = MindboxError.UnknownServer()
150+
val json = error.toJson()
151+
assertTrue(json.contains(""""type":"NetworkError""""))
152+
}
153+
154+
@Test
155+
fun `toJson UnknownServer - default constructor full JSON`() {
156+
val error = MindboxError.UnknownServer()
157+
val json = error.toJson()
158+
// Default constructor sets errorMessage = "Cannot reach server", all else null
159+
assertTrue(json.contains(""""errorMessage":"Cannot reach server""""))
160+
}
161+
162+
@Test
163+
fun `toJson UnknownServer - with all fields`() {
164+
val error = MindboxError.UnknownServer(503, "Unavailable", "Service down", "id-2", 503)
165+
val json = error.toJson()
166+
assertEquals(
167+
"""{"type":"NetworkError","data":{"statusCode":503,"status":"Unavailable","errorMessage":"Service down","errorId":"id-2","httpStatusCode":503}}""",
168+
json,
169+
)
170+
}
171+
172+
// endregion
173+
174+
// region write / toJson — Unknown
175+
176+
@Test
177+
fun `toJson Unknown - contains type InternalError`() {
178+
val error = MindboxError.Unknown()
179+
val json = error.toJson()
180+
assertTrue(json.contains(""""type":"InternalError""""))
181+
}
182+
183+
@Test
184+
fun `toJson Unknown - null throwable produces empty data object`() {
185+
// Both errorName and errorMessage are null → both name+null pairs are silently
186+
// dropped by GSON (serializeNulls = false). The data object is empty.
187+
val error = MindboxError.Unknown(throwable = null)
188+
val json = error.toJson()
189+
assertFalse("null errorName should be omitted", json.contains("errorName"))
190+
assertFalse("null errorMessage should be omitted", json.contains("errorMessage"))
191+
assertTrue("data object should be present but empty", json.contains(""""data":{}"""))
192+
}
193+
194+
@Test
195+
fun `toJson Unknown - throwable class name and message included`() {
196+
val throwable = RuntimeException("something went wrong")
197+
val error = MindboxError.Unknown(throwable)
198+
val json = error.toJson()
199+
assertTrue(json.contains("RuntimeException"))
200+
assertTrue(json.contains("something went wrong"))
201+
}
202+
203+
// endregion
204+
205+
// region read / fromJson — current behavior documentation
206+
207+
@Test
208+
fun `fromJson Validation - current behavior returns null (read is not implemented)`() {
209+
// MindboxErrorAdapter.read() calls nextString() after beginObject() which reads
210+
// the key name "type" instead of its value. None of the when-branches match "type",
211+
// so the method always returns null. This test documents that known limitation so
212+
// that a future fix or migration will be noticed immediately.
213+
val json = MindboxError.Validation(200, "Ok", emptyList()).toJson()
214+
val result = gson.fromJson(json, MindboxError.Validation::class.java)
215+
assertNull(result)
216+
}
217+
218+
@Test
219+
fun `fromJson Protocol - current behavior returns null`() {
220+
val json = MindboxError.Protocol(400, "Error", null, null, null).toJson()
221+
val result = gson.fromJson(json, MindboxError.Protocol::class.java)
222+
assertNull(result)
223+
}
224+
225+
@Test
226+
fun `fromJson UnknownServer - current behavior returns null`() {
227+
val json = MindboxError.UnknownServer().toJson()
228+
val result = gson.fromJson(json, MindboxError.UnknownServer::class.java)
229+
assertNull(result)
230+
}
231+
232+
// endregion
233+
234+
// region toJson output is valid JSON
235+
236+
@Test
237+
fun `toJson output is parseable by Gson as JsonObject`() {
238+
listOf(
239+
MindboxError.Validation(200, "Ok", emptyList()),
240+
MindboxError.Protocol(400, "Error", null, null, null),
241+
MindboxError.InternalServer(500, "Err", null, null, null),
242+
MindboxError.UnknownServer(),
243+
MindboxError.Unknown(),
244+
).forEach { error ->
245+
val json = error.toJson()
246+
val parsed = gson.fromJson(json, com.google.gson.JsonObject::class.java)
247+
assertNotNull("toJson() should produce valid JSON for ${error::class.simpleName}", parsed)
248+
assertTrue(
249+
"${error::class.simpleName} JSON should have 'type' key",
250+
parsed.has("type"),
251+
)
252+
assertTrue(
253+
"${error::class.simpleName} JSON should have 'data' key",
254+
parsed.has("data"),
255+
)
256+
}
257+
}
258+
259+
// endregion
260+
}

0 commit comments

Comments
 (0)