Skip to content

Commit 3de3c12

Browse files
committed
Improve tests.
1 parent 963a212 commit 3de3c12

7 files changed

Lines changed: 463 additions & 414 deletions

File tree

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/direct/JsonParsingUtils.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import kotlin.contracts.contract
2525
/**
2626
* Utility functions for parsing JSON with consistent null-handling.
2727
*/
28+
@Suppress("TooManyFunctions")
2829
internal object JsonParsingUtils {
2930

3031
/**
@@ -40,6 +41,21 @@ internal object JsonParsingUtils {
4041
)
4142
}
4243

44+
/**
45+
* Throws [JsonDataException] if the next JSON value is an explicit `null`. Use for fields
46+
* whose DTO declaration is non-nullable but has a default value — Moshi codegen rejects
47+
* explicit JSON null for such fields, so we mirror that for parser parity. Fields that are
48+
* absent altogether do not reach this check (the surrounding `when` branch isn't entered),
49+
* which preserves the default-on-missing behavior.
50+
*/
51+
fun rejectExplicitNull(reader: JsonReader, fieldName: String) {
52+
if (reader.peek() == JsonReader.Token.NULL) {
53+
throw JsonDataException(
54+
"Non-null value '$fieldName' was null at ${reader.path}",
55+
)
56+
}
57+
}
58+
4359
/** Reads a nullable Int (returns null if JSON value is null). */
4460
fun readNullableInt(reader: JsonReader): Int? {
4561
return if (reader.peek() == JsonReader.Token.NULL) reader.nextNull() else reader.nextInt()

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/direct/MessageAdapter.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,12 @@ internal class MessageAdapter(
111111
"created_at" -> createdAt = dateAdapter.fromJson(reader)
112112
"deleted_at" -> deletedAt = dateAdapter.fromJson(reader)
113113
"html" -> html = reader.nextString()
114-
"i18n" -> i18n = JsonParsingUtils.parseStringMap(reader)
114+
// i18n is non-nullable in DownstreamMessageDto (with default), so the DTO path
115+
// throws on explicit JSON null even though missing is fine. Match that here.
116+
"i18n" -> {
117+
JsonParsingUtils.rejectExplicitNull(reader, "i18n")
118+
i18n = JsonParsingUtils.parseStringMap(reader)
119+
}
115120
"id" -> id = reader.nextString()
116121
"latest_reactions" -> latestReactions = JsonParsingUtils.parseList(reader, reactionAdapter)
117122
"mentioned_users" -> mentionedUsers = JsonParsingUtils.parseList(reader, userAdapter)
@@ -138,7 +143,12 @@ internal class MessageAdapter(
138143
"show_in_channel" -> showInChannel = reader.nextBoolean()
139144
"silent" -> silent = reader.nextBoolean()
140145
"text" -> text = reader.nextString()
141-
"thread_participants" -> threadParticipants = JsonParsingUtils.parseList(reader, userAdapter)
146+
// thread_participants is non-nullable in DownstreamMessageDto (with default),
147+
// so the DTO path throws on explicit JSON null. Match that here.
148+
"thread_participants" -> {
149+
JsonParsingUtils.rejectExplicitNull(reader, "thread_participants")
150+
threadParticipants = JsonParsingUtils.parseList(reader, userAdapter)
151+
}
142152
"type" -> type = reader.nextString()
143153
"updated_at" -> updatedAt = dateAdapter.fromJson(reader)
144154
"user" -> user = userAdapter.fromJson(reader)

stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/DirectEventParserTest.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616

1717
package io.getstream.chat.android.client.parser2
1818

19+
import io.getstream.chat.android.client.api2.mapping.DomainMapping
20+
import io.getstream.chat.android.client.api2.mapping.EventMapping
21+
import io.getstream.chat.android.client.api2.model.dto.NewMessageEventDto
1922
import io.getstream.chat.android.client.events.NewMessageEvent
2023
import io.getstream.chat.android.client.parser2.DirectEventParser.Companion.extractType
2124
import io.getstream.chat.android.client.parser2.testdata.NewMessageEventTestData
2225
import io.getstream.chat.android.models.MessageTransformer
26+
import io.getstream.chat.android.models.NoOpChannelTransformer
2327
import io.getstream.chat.android.models.NoOpMessageTransformer
2428
import io.getstream.chat.android.models.NoOpUserTransformer
2529
import io.getstream.chat.android.models.UserTransformer
@@ -90,7 +94,30 @@ internal class DirectEventParserTest {
9094
fun `returns NewMessageEvent for message_new type`() {
9195
val event = parser.parse(NewMessageEventTestData.jsonAllFields)
9296
assertTrue(event is NewMessageEvent)
93-
assertEquals(NewMessageEventTestData.expectedAllFields, event)
97+
}
98+
99+
@Test
100+
fun `direct path produces the same NewMessageEvent as the DTO path`() {
101+
// Parity check: the DirectEventParser output must match what the legacy
102+
// JSON → NewMessageEventDto → toDomain() pipeline produces for the same JSON.
103+
val moshiChatParser = ParserFactory.createMoshiChatParser()
104+
val eventMapping = EventMapping(
105+
DomainMapping(
106+
currentUserIdProvider = { "" },
107+
channelTransformer = NoOpChannelTransformer,
108+
messageTransformer = NoOpMessageTransformer,
109+
userTransformer = NoOpUserTransformer,
110+
),
111+
)
112+
113+
val directResult = parser.parse(NewMessageEventTestData.jsonAllFields)
114+
val dtoResult = with(eventMapping) {
115+
moshiChatParser
116+
.fromJson(NewMessageEventTestData.jsonAllFields, NewMessageEventDto::class.java)
117+
.toDomain()
118+
}
119+
120+
assertEquals(dtoResult, directResult)
94121
}
95122

96123
@Test

0 commit comments

Comments
 (0)