diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/MoshiChatApi.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/MoshiChatApi.kt index 0ebe77fbcff..0a2c532f481 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/MoshiChatApi.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/MoshiChatApi.kt @@ -1698,12 +1698,16 @@ constructor( prev = query.prev, replyLimit = query.replyLimit, ), - ).mapDomain { response -> - QueryThreadsResult( - threads = response.threads.map { it.toDomain() }, - prev = response.prev, - next = response.next, - ) + ).flatMapDomain { response -> + val threads = response.threads.mapNotNull { it.toDomain() } + if (threads.size == response.threads.size) { + CoroutineCall(coroutineScope) { + val result = QueryThreadsResult(threads = threads, prev = response.prev, next = response.next) + Result.Success(result) + } + } else { + ErrorCall(coroutineScope, Error.GenericError(MISSING_THREAD_FIELDS)) + } } } return if (connectionId.isBlank() && query.watch) { @@ -1726,8 +1730,10 @@ constructor( messageId, connectionId, options.toMap(), - ).mapDomain { response -> + ).flatMapDomain { response -> response.thread.toDomain() + ?.let { thread -> CoroutineCall(coroutineScope) { Result.Success(thread) } } + ?: ErrorCall(coroutineScope, Error.GenericError(MISSING_THREAD_FIELDS)) } } return if (connectionId.isBlank() && options.watch) { @@ -2069,3 +2075,6 @@ internal fun QuerySorter<*>.toSortParams(): List = type = it[QuerySorter.KEY_TYPE] as? String, ) } + +// A thread always has a parent message and a last message date, so a thread missing either cannot be mapped. +private const val MISSING_THREAD_FIELDS = "A thread in the response carried no parent message or last message date" diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/ThreadsApi.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/ThreadsApi.kt index a73b4881b3b..37b9eb9fa3f 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/ThreadsApi.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/ThreadsApi.kt @@ -18,12 +18,12 @@ package io.getstream.chat.android.client.api2.endpoint import io.getstream.chat.android.client.api.AuthenticatedApi import io.getstream.chat.android.client.api.QueryParams -import io.getstream.chat.android.client.api2.model.response.QueryThreadsResponse -import io.getstream.chat.android.client.api2.model.response.ThreadInfoResponse -import io.getstream.chat.android.client.api2.model.response.ThreadResponse import io.getstream.chat.android.client.call.RetrofitCall +import io.getstream.chat.android.network.models.GetThreadResponse import io.getstream.chat.android.network.models.QueryThreadsRequest +import io.getstream.chat.android.network.models.QueryThreadsResponse import io.getstream.chat.android.network.models.UpdateThreadPartialRequest +import io.getstream.chat.android.network.models.UpdateThreadPartialResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.PATCH @@ -52,7 +52,7 @@ internal interface ThreadsApi { @Path("message_id") messageId: String, @Query(QueryParams.CONNECTION_ID) connectionId: String, @QueryMap options: Map, - ): RetrofitCall + ): RetrofitCall /** * [REST documentation](https://getstream.github.io/protocol/#/product%3Achat/UpdateThreadPartial) @@ -61,5 +61,5 @@ internal interface ThreadsApi { fun partialUpdateThread( @Path("message_id") messageId: String, @Body body: UpdateThreadPartialRequest, - ): RetrofitCall + ): RetrofitCall } diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DomainMapping.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DomainMapping.kt index 8a5afd21747..021c9a8cfca 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DomainMapping.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DomainMapping.kt @@ -29,8 +29,6 @@ import io.getstream.chat.android.client.api2.model.dto.DownstreamModerationDetai import io.getstream.chat.android.client.api2.model.dto.DownstreamPendingMessageDto import io.getstream.chat.android.client.api2.model.dto.DownstreamReminderDto import io.getstream.chat.android.client.api2.model.dto.DownstreamReminderInfoDto -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadDto -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadInfoDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserDto import io.getstream.chat.android.client.extensions.enrichWithCid import io.getstream.chat.android.client.extensions.internal.sortedByLastReply @@ -132,6 +130,8 @@ import io.getstream.chat.android.network.models.ReminderResponseData import io.getstream.chat.android.network.models.SharedLocationResponse import io.getstream.chat.android.network.models.SharedLocationResponseData import io.getstream.chat.android.network.models.SortParamRequest +import io.getstream.chat.android.network.models.ThreadResponse +import io.getstream.chat.android.network.models.ThreadStateResponse import io.getstream.chat.android.network.models.UnreadCountsChannel import io.getstream.chat.android.network.models.UnreadCountsChannelType import io.getstream.chat.android.network.models.UnreadCountsThread @@ -1159,55 +1159,56 @@ internal class DomainMapping( ) /** - * Transforms [DownstreamThreadDto] into [Thread] + * Transforms [ThreadStateResponse] into [Thread], or null when it lacks the parent message or the last + * message date the domain thread requires. */ - internal fun DownstreamThreadDto.toDomain(): Thread = - Thread( - activeParticipantCount = active_participant_count ?: 0, - cid = channel_cid, + internal fun ThreadStateResponse.toDomain(): Thread? { + val parent = parentMessage ?: return null + val lastMessage = lastMessageAt ?: return null + val channelInfo = channel?.toChannelInfo() + return Thread( + activeParticipantCount = activeParticipantCount, + cid = channelCid, channel = channel?.toDomain(), - parentMessageId = parent_message_id, - parentMessage = parent_message.toDomain(channel?.toChannelInfo()), - createdByUserId = created_by_user_id, - createdBy = created_by?.toDomain(), - participantCount = participant_count, - threadParticipants = thread_participants.orEmpty().map { it.toDomain() }.sortedByLastReply(), - lastMessageAt = last_message_at, - createdAt = created_at, - updatedAt = updated_at, - deletedAt = deleted_at, + parentMessageId = parentMessageId, + parentMessage = parent.toDomain(channelInfo), + createdByUserId = createdByUserId, + createdBy = createdBy?.toDomain(), + participantCount = participantCount, + threadParticipants = threadParticipants.orEmpty().map { it.toDomain() }.sortedByLastReply(), + lastMessageAt = lastMessage, + createdAt = createdAt, + updatedAt = updatedAt, + deletedAt = deletedAt, title = title, - latestReplies = latest_replies.map { it.toDomain(channel?.toChannelInfo()) }, - read = read.orEmpty().map { - it.toDomain( - lastReceivedEventDate = last_message_at, - ) - }, - draft = draft?.toDomain(channel?.toChannelInfo()), - extraData = extraData, + latestReplies = latestReplies.map { it.toDomain(channelInfo) }, + read = read.orEmpty().map { it.toDomain(lastReceivedEventDate = lastMessage) }, + draft = draft?.toDomain(channelInfo), + extraData = custom.mapNotNull { (key, value) -> value?.let { key to it } }.toMap(), ) + } /** - * Transforms [DownstreamThreadInfoDto] into [ThreadInfo] + * Transforms [ThreadResponse] into [ThreadInfo] */ - internal fun DownstreamThreadInfoDto.toDomain(): ThreadInfo = + internal fun ThreadResponse.toDomain(): ThreadInfo = ThreadInfo( - activeParticipantCount = active_participant_count ?: 0, - cid = channel_cid, - createdAt = created_at, - createdBy = created_by?.toDomain(), - createdByUserId = created_by_user_id, - deletedAt = deleted_at, - lastMessageAt = last_message_at, - parentMessage = parent_message?.toDomain(channel?.toChannelInfo()), - parentMessageId = parent_message_id, - participantCount = participant_count ?: 0, - replyCount = reply_count ?: 0, + activeParticipantCount = activeParticipantCount, + cid = channelCid, + createdAt = createdAt, + createdBy = createdBy?.toDomain(), + createdByUserId = createdByUserId, + deletedAt = deletedAt, + lastMessageAt = lastMessageAt, + parentMessage = parentMessage?.toDomain(channel?.toChannelInfo()), + parentMessageId = parentMessageId, + participantCount = participantCount, + replyCount = replyCount, title = title, - updatedAt = updated_at, + updatedAt = updatedAt, channel = channel?.toDomain(), - threadParticipants = thread_participants.orEmpty().map { it.toDomain() }.sortedByLastReply(), - extraData = extraData, + threadParticipants = threadParticipants.orEmpty().map { it.toDomain() }.sortedByLastReply(), + extraData = custom.mapNotNull { (key, value) -> value?.let { key to it } }.toMap(), ) /** diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/EventDtos.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/EventDtos.kt index 293ef843f2c..7125452b1fe 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/EventDtos.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/EventDtos.kt @@ -23,6 +23,7 @@ import io.getstream.chat.android.network.models.ChannelMemberResponse import io.getstream.chat.android.network.models.DraftResponse import io.getstream.chat.android.network.models.OwnUserResponse import io.getstream.chat.android.network.models.ReactionResponse +import io.getstream.chat.android.network.models.ThreadResponse import io.getstream.chat.android.network.models.UserResponseCommonFields import io.getstream.chat.android.network.models.UserResponsePrivacyFields import io.getstream.chat.android.network.models.WSEvent @@ -174,7 +175,7 @@ internal data class MessageReadEventDto( val cid: String, val channel_type: String, val channel_id: String, - val thread: DownstreamThreadInfoDto? = null, + val thread: ThreadResponse? = null, val last_read_message_id: String?, val team: String? = null, ) : ChatEventDto() @@ -314,7 +315,7 @@ internal data class NotificationMarkReadEventDto( val total_unread_count: Int = 0, val unread_channels: Int = 0, val thread_id: String? = null, - val thread: DownstreamThreadInfoDto? = null, + val thread: ThreadResponse? = null, val unread_threads: Int? = null, val unread_thread_messages: Int? = null, val last_read_message_id: String?, @@ -371,7 +372,7 @@ internal data class ThreadUpdatedEventDto( val cid: String, val channel_type: String, val channel_id: String, - val thread: DownstreamThreadInfoDto, + val thread: ThreadResponse, ) : ChatEventDto() @JsonClass(generateAdapter = true) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/ThreadDtos.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/ThreadDtos.kt deleted file mode 100644 index 28e10195941..00000000000 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/ThreadDtos.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.client.api2.model.dto - -import com.squareup.moshi.JsonClass -import io.getstream.chat.android.network.models.ChannelResponse -import io.getstream.chat.android.network.models.DraftResponse -import io.getstream.chat.android.network.models.ReadStateResponse -import io.getstream.chat.android.network.models.ThreadParticipant -import io.getstream.chat.android.network.models.UserResponse -import java.util.Date - -/** - * The DTO for a thread. - * Corresponds to [ThreadStateResponse]. - * - * @param active_participant_count The number of active participants. - * @param channel The channel info. - * @param channel_cid The channel CID. - * @param created_at The date when the thread was created. - * @param created_by The user who created the thread. - * @param created_by_user_id The ID of the user who created the thread. - * @param deleted_at The date when the thread was deleted. - * @param draft The draft message in the thread. - * @param last_message_at The date of the last message in the thread. - * @param latest_replies The latest replies in the thread. - * @param parent_message The parent message. - * @param parent_message_id The parent message ID. - * @param participant_count The number of participants in the thread. - * @param read The read states of the thread. - * @param reply_count The number of replies in the thread. - * @param thread_participants The participants in the thread. - * @param title The title of the thread. - * @param updated_at The date when the thread was updated. - * @param extraData Any additional data. - */ -@JsonClass(generateAdapter = true) -internal data class DownstreamThreadDto( - val active_participant_count: Int?, - val channel: ChannelResponse?, - val channel_cid: String, - val created_at: Date, - val created_by: UserResponse?, - val created_by_user_id: String, - val deleted_at: Date?, - val draft: DraftResponse?, - val last_message_at: Date, - val latest_replies: List, - val parent_message: DownstreamMessageDto, - val parent_message_id: String, - val participant_count: Int, - val read: List?, - val reply_count: Int?, - val thread_participants: List?, - val title: String, - val updated_at: Date, - val extraData: Map, -) : ExtraDataDto - -/** - * The DTO for a shortened thread info. - * Corresponds to [ThreadResponse]. - * - * @param channel_cid The channel CID. - * @param channel The channel info. - * @param parent_message_id The parent message ID. - * @param parent_message The parent message. - * @param created_by_user_id The ID of the user who created the thread. - * @param created_by The user who created the thread. - * @param reply_count The number of replies in the thread. - * @param participant_count The number of participants in the thread. - * @param active_participant_count The number of active participants. - * @param thread_participants The participants in the thread. - * @param last_message_at The date of the last message in the thread. - * @param created_at The date when the thread was created. - * @param updated_at The date when the thread was updated. - * @param deleted_at The date when the thread was deleted. - * @param title The title of the thread. - * @param extraData Any additional data. - */ -@JsonClass(generateAdapter = true) -internal data class DownstreamThreadInfoDto( - val channel_cid: String, - val channel: ChannelResponse?, - val parent_message_id: String, - val parent_message: DownstreamMessageDto?, - val created_by_user_id: String, - val created_by: UserResponse?, - val reply_count: Int?, - val participant_count: Int?, - val active_participant_count: Int?, - val thread_participants: List?, - val last_message_at: Date?, - val created_at: Date, - val updated_at: Date, - val deleted_at: Date?, - val title: String, - val extraData: Map, -) : ExtraDataDto diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/ThreadResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/ThreadResponse.kt deleted file mode 100644 index 92e84c81607..00000000000 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/ThreadResponse.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.client.api2.model.response - -import com.squareup.moshi.JsonClass -import io.getstream.chat.android.client.api2.endpoint.ThreadsApi -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadDto -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadInfoDto - -/** - * Response for [ThreadsApi.getThread] - * - * @param thread The thread object. - * @param duration The duration of the request. - */ -@JsonClass(generateAdapter = true) -internal data class ThreadResponse( - val thread: DownstreamThreadDto, - val duration: String, -) - -/** - * Response for [ThreadsApi.partialUpdateThread] - * - * @param thread The Thread info object. - * @param duration The duration of the request. - */ -@JsonClass(generateAdapter = true) -internal data class ThreadInfoResponse( - val thread: DownstreamThreadInfoDto, - val duration: String, -) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/MoshiChatParser.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/MoshiChatParser.kt index 621826167ab..7d8dec056e3 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/MoshiChatParser.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/MoshiChatParser.kt @@ -41,8 +41,6 @@ import io.getstream.chat.android.client.parser2.adapters.CreatePollRequestAdapte import io.getstream.chat.android.client.parser2.adapters.DownstreamChannelDtoAdapter import io.getstream.chat.android.client.parser2.adapters.DownstreamMessageDtoAdapter import io.getstream.chat.android.client.parser2.adapters.DownstreamModerationDetailsDtoAdapter -import io.getstream.chat.android.client.parser2.adapters.DownstreamThreadDtoAdapter -import io.getstream.chat.android.client.parser2.adapters.DownstreamThreadInfoDtoAdapter import io.getstream.chat.android.client.parser2.adapters.DownstreamUserDtoAdapter import io.getstream.chat.android.client.parser2.adapters.DraftPayloadResponseAdapter import io.getstream.chat.android.client.parser2.adapters.EventAdapterFactory @@ -62,6 +60,8 @@ import io.getstream.chat.android.client.parser2.adapters.PollOptionResponseDataA import io.getstream.chat.android.client.parser2.adapters.PollResponseDataAdapter import io.getstream.chat.android.client.parser2.adapters.ReactionRequestAdapter import io.getstream.chat.android.client.parser2.adapters.ReactionResponseAdapter +import io.getstream.chat.android.client.parser2.adapters.ThreadResponseAdapter +import io.getstream.chat.android.client.parser2.adapters.ThreadStateResponseAdapter import io.getstream.chat.android.client.parser2.adapters.UpdatePollOptionRequestAdapter import io.getstream.chat.android.client.parser2.adapters.UpdatePollRequestAdapter import io.getstream.chat.android.client.parser2.adapters.UpstreamUserDtoAdapter @@ -122,8 +122,8 @@ internal class MoshiChatParser( .add(ChannelMemberResponseAdapter) .add(ChannelMemberPartialResponseAdapter) .add(FlagRequestAdapterFactory) - .add(DownstreamThreadDtoAdapter) - .add(DownstreamThreadInfoDtoAdapter) + .add(ThreadStateResponseAdapter) + .add(ThreadResponseAdapter) .add(CreatePollRequestAdapter) .add(UpdatePollRequestAdapter) .add(CreatePollOptionRequestAdapter) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/adapters/ThreadDtoAdapters.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/adapters/ThreadDtoAdapters.kt index fba168eab11..ad68bcbd9a8 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/adapters/ThreadDtoAdapters.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/adapters/ThreadDtoAdapters.kt @@ -21,41 +21,35 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadDto -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadInfoDto +import io.getstream.chat.android.network.models.ThreadResponse +import io.getstream.chat.android.network.models.ThreadStateResponse -/** - * Moshi adapter that handles deserialization of [DownstreamThreadDto] with extra data. - */ -internal object DownstreamThreadDtoAdapter : - CustomObjectDtoAdapter(DownstreamThreadDto::class) { +// Downstream (read-only) adapters for the generated thread models: collect the root-level custom fields +// into `custom`, matching the wire's flattened extra data. extraDataPropertyName is their @Json name. +internal object ThreadStateResponseAdapter : + CustomObjectDtoAdapter(ThreadStateResponse::class, extraDataPropertyName = "custom") { @FromJson fun fromJson( jsonReader: JsonReader, mapAdapter: JsonAdapter>, - messageAdapter: JsonAdapter, - ): DownstreamThreadDto? = parseWithExtraData(jsonReader, mapAdapter, messageAdapter) + valueAdapter: JsonAdapter, + ): ThreadStateResponse? = parseWithExtraData(jsonReader, mapAdapter, valueAdapter) @ToJson - @Suppress("UNUSED_PARAMETER") - fun toJson(jsonWriter: JsonWriter, value: DownstreamThreadDto): Unit = error("Can't convert this to Json") + fun toJson(jsonWriter: JsonWriter, value: ThreadStateResponse): Unit = error("Can't convert this to Json") } -/** - * Moshi adapter that handles deserialization of [DownstreamThreadInfoDto] with extra data. - */ -internal object DownstreamThreadInfoDtoAdapter : - CustomObjectDtoAdapter(DownstreamThreadInfoDto::class) { +internal object ThreadResponseAdapter : + CustomObjectDtoAdapter(ThreadResponse::class, extraDataPropertyName = "custom") { @FromJson fun fromJson( jsonReader: JsonReader, mapAdapter: JsonAdapter>, - messageAdapter: JsonAdapter, - ): DownstreamThreadInfoDto? = parseWithExtraData(jsonReader, mapAdapter, messageAdapter) + valueAdapter: JsonAdapter, + ): ThreadResponse? = parseWithExtraData(jsonReader, mapAdapter, valueAdapter) @ToJson - @Suppress("UNUSED_PARAMETER") - fun toJson(jsonWriter: JsonWriter, value: DownstreamThreadInfoDto): Unit = error("Can't convert this to Json") + fun toJson(jsonWriter: JsonWriter, value: ThreadResponse): Unit = error("Can't convert this to Json") } diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/GetThreadResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/GetThreadResponse.kt new file mode 100644 index 00000000000..70cfb577122 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/GetThreadResponse.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport", +) + +package io.getstream.chat.android.network.models + +import com.squareup.moshi.Json + +/** + * + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class GetThreadResponse( + @Json(name = "duration") + internal val duration: String, + + @Json(name = "thread") + internal val thread: io.getstream.chat.android.network.models.ThreadStateResponse, +) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/QueryThreadsResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/QueryThreadsResponse.kt similarity index 51% rename from stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/QueryThreadsResponse.kt rename to stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/QueryThreadsResponse.kt index 079623c9f18..2ada24c77ce 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/QueryThreadsResponse.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/QueryThreadsResponse.kt @@ -14,24 +14,31 @@ * limitations under the License. */ -package io.getstream.chat.android.client.api2.model.response +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport", +) + +package io.getstream.chat.android.network.models -import com.squareup.moshi.JsonClass -import io.getstream.chat.android.client.api2.endpoint.ThreadsApi -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadDto +import com.squareup.moshi.Json /** - * Response for [ThreadsApi.queryThreads] * - * @param threads: The list of threads. - * @param duration: The duration of the request. - * @param prev: The identifier for the previous page of threads. - * @param next: The identifier for the next page of threads. */ -@JsonClass(generateAdapter = true) +@com.squareup.moshi.JsonClass(generateAdapter = true) internal data class QueryThreadsResponse( - val threads: List, - val duration: String, - val prev: String?, - val next: String?, + @Json(name = "duration") + internal val duration: String, + + @Json(name = "threads") + internal val threads: List = emptyList(), + + @Json(name = "next") + internal val next: String? = null, + + @Json(name = "prev") + internal val prev: String? = null, ) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/ThreadResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/ThreadResponse.kt new file mode 100644 index 00000000000..ff59121f5e4 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/ThreadResponse.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport", +) + +package io.getstream.chat.android.network.models + +import com.squareup.moshi.Json + +/** + * + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class ThreadResponse( + @Json(name = "active_participant_count") + internal val activeParticipantCount: Int, + + @Json(name = "channel_cid") + internal val channelCid: String, + + @Json(name = "created_at") + internal val createdAt: java.util.Date, + + @Json(name = "created_by_user_id") + internal val createdByUserId: String, + + @Json(name = "parent_message_id") + internal val parentMessageId: String, + + @Json(name = "participant_count") + internal val participantCount: Int, + + @Json(name = "reply_count") + internal val replyCount: Int, + + @Json(name = "title") + internal val title: String, + + @Json(name = "updated_at") + internal val updatedAt: java.util.Date, + + @Json(name = "custom") + internal val custom: Map = emptyMap(), + + @Json(name = "deleted_at") + internal val deletedAt: java.util.Date? = null, + + @Json(name = "last_message_at") + internal val lastMessageAt: java.util.Date? = null, + + @Json(name = "thread_participants") + internal val threadParticipants: List? = null, + + @Json(name = "channel") + internal val channel: io.getstream.chat.android.network.models.ChannelResponse? = null, + + @Json(name = "created_by") + internal val createdBy: io.getstream.chat.android.network.models.UserResponse? = null, + + @Json(name = "parent_message") + internal val parentMessage: io.getstream.chat.android.network.models.MessageResponse? = null, +) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/ThreadStateResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/ThreadStateResponse.kt new file mode 100644 index 00000000000..bb0d565d107 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/ThreadStateResponse.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport", +) + +package io.getstream.chat.android.network.models + +import com.squareup.moshi.Json + +/** + * + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class ThreadStateResponse( + @Json(name = "active_participant_count") + internal val activeParticipantCount: Int, + + @Json(name = "channel_cid") + internal val channelCid: String, + + @Json(name = "created_at") + internal val createdAt: java.util.Date, + + @Json(name = "created_by_user_id") + internal val createdByUserId: String, + + @Json(name = "parent_message_id") + internal val parentMessageId: String, + + @Json(name = "participant_count") + internal val participantCount: Int, + + @Json(name = "reply_count") + internal val replyCount: Int, + + @Json(name = "title") + internal val title: String, + + @Json(name = "updated_at") + internal val updatedAt: java.util.Date, + + @Json(name = "latest_replies") + internal val latestReplies: List = emptyList(), + + @Json(name = "custom") + internal val custom: Map = emptyMap(), + + @Json(name = "deleted_at") + internal val deletedAt: java.util.Date? = null, + + @Json(name = "last_message_at") + internal val lastMessageAt: java.util.Date? = null, + + @Json(name = "read") + internal val read: List? = null, + + @Json(name = "thread_participants") + internal val threadParticipants: List? = null, + + @Json(name = "channel") + internal val channel: io.getstream.chat.android.network.models.ChannelResponse? = null, + + @Json(name = "created_by") + internal val createdBy: io.getstream.chat.android.network.models.UserResponse? = null, + + @Json(name = "draft") + internal val draft: io.getstream.chat.android.network.models.DraftResponse? = null, + + @Json(name = "parent_message") + internal val parentMessage: io.getstream.chat.android.network.models.MessageResponse? = null, +) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/UpdateThreadPartialResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/UpdateThreadPartialResponse.kt new file mode 100644 index 00000000000..f518f9b4d7b --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/UpdateThreadPartialResponse.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport", +) + +package io.getstream.chat.android.network.models + +import com.squareup.moshi.Json + +/** + * + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class UpdateThreadPartialResponse( + @Json(name = "duration") + internal val duration: String, + + @Json(name = "thread") + internal val thread: io.getstream.chat.android.network.models.ThreadResponse, +) diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt index 3b26d0a3bb6..d40d9876c51 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt @@ -31,8 +31,6 @@ import io.getstream.chat.android.client.api2.model.dto.DownstreamMessageDto import io.getstream.chat.android.client.api2.model.dto.DownstreamModerationDetailsDto import io.getstream.chat.android.client.api2.model.dto.DownstreamPendingMessageDto import io.getstream.chat.android.client.api2.model.dto.DownstreamReminderDto -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadDto -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadInfoDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserDto import io.getstream.chat.android.client.api2.model.dto.ErrorDetailDto import io.getstream.chat.android.client.api2.model.dto.ErrorDto @@ -98,6 +96,8 @@ import io.getstream.chat.android.network.models.ReadStateResponse import io.getstream.chat.android.network.models.ReminderResponseData import io.getstream.chat.android.network.models.SharedLocationResponseData import io.getstream.chat.android.network.models.ThreadParticipant +import io.getstream.chat.android.network.models.ThreadResponse +import io.getstream.chat.android.network.models.ThreadStateResponse import io.getstream.chat.android.network.models.UnblockUsersResponse import io.getstream.chat.android.network.models.UnreadCountsChannel import io.getstream.chat.android.network.models.UnreadCountsChannelType @@ -993,46 +993,46 @@ internal object Mother { memberLimit = memberLimit, ) - fun randomDownstreamThreadDto( + fun randomThreadStateResponse( activeParticipantCount: Int = randomInt(), channelCid: String = randomString(), channel: ChannelResponse? = randomChannelResponse(id = channelCid), parentMessageId: String = randomString(), - parentMessage: DownstreamMessageDto = randomDownstreamMessageDto(), + parentMessage: MessageResponse? = randomMessageResponse(), createdByUserId: String = randomString(), - createdBy: UserResponse = randomUserResponse(id = createdByUserId), + createdBy: UserResponse? = randomUserResponse(id = createdByUserId), participantCount: Int = randomInt(), threadParticipants: List = emptyList(), - lastMessageAt: Date = randomDate(), + lastMessageAt: Date? = randomDate(), createdAt: Date = randomDate(), updatedAt: Date = randomDate(), deletedAt: Date? = randomDateOrNull(), title: String = randomString(), - latestReplies: List = listOf(randomDownstreamMessageDto()), + latestReplies: List = listOf(randomMessageResponse()), read: List = listOf(randomReadStateResponse()), replyCount: Int = randomInt(), draft: DraftResponse? = randomDraftResponse(), extraData: Map = randomExtraData(maxPossibleEntries = 2), - ): DownstreamThreadDto = DownstreamThreadDto( - active_participant_count = activeParticipantCount, - channel_cid = channelCid, + ): ThreadStateResponse = ThreadStateResponse( + activeParticipantCount = activeParticipantCount, + channelCid = channelCid, channel = channel, - parent_message_id = parentMessageId, - parent_message = parentMessage, - created_by_user_id = createdByUserId, - created_by = createdBy, - participant_count = participantCount, - thread_participants = threadParticipants, - last_message_at = lastMessageAt, - created_at = createdAt, - updated_at = updatedAt, - deleted_at = deletedAt, + parentMessageId = parentMessageId, + parentMessage = parentMessage, + createdByUserId = createdByUserId, + createdBy = createdBy, + participantCount = participantCount, + threadParticipants = threadParticipants, + lastMessageAt = lastMessageAt, + createdAt = createdAt, + updatedAt = updatedAt, + deletedAt = deletedAt, title = title, - latest_replies = latestReplies, + latestReplies = latestReplies, read = read, - reply_count = replyCount, + replyCount = replyCount, draft = draft, - extraData = extraData, + custom = extraData, ) fun randomThreadParticipantDto( @@ -1197,40 +1197,40 @@ internal object Mother { custom = custom, ) - fun randomDownstreamThreadInfoDto( + fun randomThreadResponse( channelCid: String = randomString(), channel: ChannelResponse? = randomChannelResponse(id = channelCid), parentMessageId: String = randomString(), - parentMessage: DownstreamMessageDto = randomDownstreamMessageDto(id = parentMessageId), + parentMessage: MessageResponse? = randomMessageResponse(id = parentMessageId), createdByUserId: String = randomString(), - createdBy: UserResponse = randomUserResponse(id = createdByUserId), + createdBy: UserResponse? = randomUserResponse(id = createdByUserId), replyCount: Int = randomInt(), participantCount: Int = randomInt(), activeParticipantCount: Int = randomInt(), threadParticipants: List = emptyList(), - lastMessageAt: Date = randomDate(), + lastMessageAt: Date? = randomDate(), createdAt: Date = randomDate(), updatedAt: Date = randomDate(), deletedAt: Date? = randomDateOrNull(), title: String = randomString(), extraData: Map = randomExtraData(maxPossibleEntries = 2), - ): DownstreamThreadInfoDto = DownstreamThreadInfoDto( - channel_cid = channelCid, + ): ThreadResponse = ThreadResponse( + channelCid = channelCid, channel = channel, - parent_message_id = parentMessageId, - parent_message = parentMessage, - created_by_user_id = createdByUserId, - created_by = createdBy, - reply_count = replyCount, - participant_count = participantCount, - active_participant_count = activeParticipantCount, - thread_participants = threadParticipants, - last_message_at = lastMessageAt, - created_at = createdAt, - updated_at = updatedAt, - deleted_at = deletedAt, + parentMessageId = parentMessageId, + parentMessage = parentMessage, + createdByUserId = createdByUserId, + createdBy = createdBy, + replyCount = replyCount, + participantCount = participantCount, + activeParticipantCount = activeParticipantCount, + threadParticipants = threadParticipants, + lastMessageAt = lastMessageAt, + createdAt = createdAt, + updatedAt = updatedAt, + deletedAt = deletedAt, title = title, - extraData = extraData, + custom = extraData, ) fun randomBlockedUserResponse( diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/MoshiChatApiTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/MoshiChatApiTest.kt index 0180e2b0854..103118467dd 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/MoshiChatApiTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/MoshiChatApiTest.kt @@ -54,12 +54,9 @@ import io.getstream.chat.android.client.api2.model.response.MuteUserResponse import io.getstream.chat.android.client.api2.model.response.QueryChannelsResponse import io.getstream.chat.android.client.api2.model.response.QueryGroupedChannelsGroup import io.getstream.chat.android.client.api2.model.response.QueryGroupedChannelsResponse -import io.getstream.chat.android.client.api2.model.response.QueryThreadsResponse import io.getstream.chat.android.client.api2.model.response.ReactionResponse import io.getstream.chat.android.client.api2.model.response.SearchMessagesResponse import io.getstream.chat.android.client.api2.model.response.SyncHistoryResponse -import io.getstream.chat.android.client.api2.model.response.ThreadInfoResponse -import io.getstream.chat.android.client.api2.model.response.ThreadResponse import io.getstream.chat.android.client.call.RetrofitCall import io.getstream.chat.android.client.parser.toMap import io.getstream.chat.android.client.scope.ClientScope @@ -126,6 +123,7 @@ import io.getstream.chat.android.network.models.GetApplicationResponse import io.getstream.chat.android.network.models.GetBlockedUsersResponse import io.getstream.chat.android.network.models.GetOGResponse import io.getstream.chat.android.network.models.GetReactionsResponse +import io.getstream.chat.android.network.models.GetThreadResponse import io.getstream.chat.android.network.models.GetUserGroupResponse import io.getstream.chat.android.network.models.GroupedChannelsGroupRequest import io.getstream.chat.android.network.models.GroupedQueryChannelsRequest @@ -162,6 +160,7 @@ import io.getstream.chat.android.network.models.QueryReactionsRequest import io.getstream.chat.android.network.models.QueryReactionsResponse import io.getstream.chat.android.network.models.QueryRemindersRequest import io.getstream.chat.android.network.models.QueryRemindersResponse +import io.getstream.chat.android.network.models.QueryThreadsResponse import io.getstream.chat.android.network.models.QueryUsersResponse import io.getstream.chat.android.network.models.RemoveUserGroupMembersRequest import io.getstream.chat.android.network.models.RemoveUserGroupMembersResponse @@ -186,6 +185,7 @@ import io.getstream.chat.android.network.models.UpdatePollPartialRequest import io.getstream.chat.android.network.models.UpdateReminderRequest import io.getstream.chat.android.network.models.UpdateReminderResponse import io.getstream.chat.android.network.models.UpdateThreadPartialRequest +import io.getstream.chat.android.network.models.UpdateThreadPartialResponse import io.getstream.chat.android.network.models.UpdateUserGroupRequest import io.getstream.chat.android.network.models.UpdateUserGroupResponse import io.getstream.chat.android.network.models.UpdateUserPartialRequest @@ -2620,7 +2620,7 @@ internal class MoshiChatApiTest { @ParameterizedTest @MethodSource("io.getstream.chat.android.client.api2.MoshiChatApiTestArguments#getThreadInput") - fun testGetThread(call: RetrofitCall, expected: KClass<*>) = runTest { + fun testGetThread(call: RetrofitCall, expected: KClass<*>) = runTest { // given val api = mock() whenever(api.getThread(any(), any(), any())).doReturn(call) @@ -2642,7 +2642,7 @@ internal class MoshiChatApiTest { @ParameterizedTest @MethodSource("io.getstream.chat.android.client.api2.MoshiChatApiTestArguments#partialUpdateThreadInput") - fun testPartialUpdateThread(call: RetrofitCall, expected: KClass<*>) = runTest { + fun testPartialUpdateThread(call: RetrofitCall, expected: KClass<*>) = runTest { // given val api = mock() whenever(api.partialUpdateThread(any(), any())).doReturn(call) diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/MoshiChatApiTestArguments.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/MoshiChatApiTestArguments.kt index 2b450729db4..4d77e3137c4 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/MoshiChatApiTestArguments.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/MoshiChatApiTestArguments.kt @@ -37,12 +37,9 @@ import io.getstream.chat.android.client.api2.model.response.MuteUserResponse import io.getstream.chat.android.client.api2.model.response.QueryChannelsResponse import io.getstream.chat.android.client.api2.model.response.QueryGroupedChannelsGroup import io.getstream.chat.android.client.api2.model.response.QueryGroupedChannelsResponse -import io.getstream.chat.android.client.api2.model.response.QueryThreadsResponse import io.getstream.chat.android.client.api2.model.response.ReactionResponse import io.getstream.chat.android.client.api2.model.response.SearchMessagesResponse import io.getstream.chat.android.client.api2.model.response.SyncHistoryResponse -import io.getstream.chat.android.client.api2.model.response.ThreadInfoResponse -import io.getstream.chat.android.client.api2.model.response.ThreadResponse import io.getstream.chat.android.client.utils.RetroError import io.getstream.chat.android.client.utils.RetroSuccess import io.getstream.chat.android.models.EventType @@ -61,6 +58,7 @@ import io.getstream.chat.android.network.models.GetApplicationResponse import io.getstream.chat.android.network.models.GetBlockedUsersResponse import io.getstream.chat.android.network.models.GetOGResponse import io.getstream.chat.android.network.models.GetReactionsResponse +import io.getstream.chat.android.network.models.GetThreadResponse import io.getstream.chat.android.network.models.GetUserGroupResponse import io.getstream.chat.android.network.models.ListDevicesResponse import io.getstream.chat.android.network.models.ListUserGroupsResponse @@ -74,6 +72,7 @@ import io.getstream.chat.android.network.models.QueryBannedUsersResponse import io.getstream.chat.android.network.models.QueryDraftsResponse import io.getstream.chat.android.network.models.QueryPollsResponse import io.getstream.chat.android.network.models.QueryReactionsResponse +import io.getstream.chat.android.network.models.QueryThreadsResponse import io.getstream.chat.android.network.models.QueryUsersResponse import io.getstream.chat.android.network.models.RemoveUserGroupMembersResponse import io.getstream.chat.android.network.models.Response @@ -85,6 +84,7 @@ import io.getstream.chat.android.network.models.UnblockUsersResponse import io.getstream.chat.android.network.models.UpdateLiveLocationRequest import io.getstream.chat.android.network.models.UpdateMemberPartialResponse import io.getstream.chat.android.network.models.UpdateReminderResponse +import io.getstream.chat.android.network.models.UpdateThreadPartialResponse import io.getstream.chat.android.network.models.UpdateUserGroupResponse import io.getstream.chat.android.network.models.UpdateUsersResponse import io.getstream.chat.android.positiveRandomInt @@ -662,7 +662,7 @@ internal object MoshiChatApiTestArguments { Arguments.of( RetroSuccess( QueryThreadsResponse( - threads = listOf(Mother.randomDownstreamThreadDto()), + threads = listOf(Mother.randomThreadStateResponse()), duration = randomString(), prev = randomString(), next = randomString(), @@ -670,6 +670,15 @@ internal object MoshiChatApiTestArguments { ).toRetrofitCall(), Result.Success::class, ), + Arguments.of( + RetroSuccess( + QueryThreadsResponse( + threads = listOf(Mother.randomThreadStateResponse(parentMessage = null)), + duration = randomString(), + ), + ).toRetrofitCall(), + Result.Failure::class, + ), Arguments.of(RetroError(statusCode = 500).toRetrofitCall(), Result.Failure::class), ) @@ -961,27 +970,36 @@ internal object MoshiChatApiTestArguments { private fun threadResponseArguments() = listOf( Arguments.of( RetroSuccess( - ThreadResponse( - thread = Mother.randomDownstreamThreadDto(), + GetThreadResponse( + thread = Mother.randomThreadStateResponse(), duration = randomString(), ), ).toRetrofitCall(), Result.Success::class, ), - Arguments.of(RetroError(statusCode = 500).toRetrofitCall(), Result.Failure::class), + Arguments.of( + RetroSuccess( + GetThreadResponse( + thread = Mother.randomThreadStateResponse(lastMessageAt = null), + duration = randomString(), + ), + ).toRetrofitCall(), + Result.Failure::class, + ), + Arguments.of(RetroError(statusCode = 500).toRetrofitCall(), Result.Failure::class), ) private fun threadInfoResponseArguments() = listOf( Arguments.of( RetroSuccess( - ThreadInfoResponse( - thread = Mother.randomDownstreamThreadInfoDto(), + UpdateThreadPartialResponse( + thread = Mother.randomThreadResponse(), duration = randomString(), ), ).toRetrofitCall(), Result.Success::class, ), - Arguments.of(RetroError(statusCode = 500).toRetrofitCall(), Result.Failure::class), + Arguments.of(RetroError(statusCode = 500).toRetrofitCall(), Result.Failure::class), ) private fun pollResponseArguments() = listOf( diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt index de3e01cb4a6..7378dbbd3cc 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt @@ -35,8 +35,6 @@ import io.getstream.chat.android.client.Mother.randomDownstreamMessageDto import io.getstream.chat.android.client.Mother.randomDownstreamModerationDetailsDto import io.getstream.chat.android.client.Mother.randomDownstreamPendingMessageDto import io.getstream.chat.android.client.Mother.randomDownstreamReminderDto -import io.getstream.chat.android.client.Mother.randomDownstreamThreadDto -import io.getstream.chat.android.client.Mother.randomDownstreamThreadInfoDto import io.getstream.chat.android.client.Mother.randomDownstreamUserDto import io.getstream.chat.android.client.Mother.randomDraftPayloadResponse import io.getstream.chat.android.client.Mother.randomDraftResponse @@ -55,6 +53,8 @@ import io.getstream.chat.android.client.Mother.randomReadStateResponse import io.getstream.chat.android.client.Mother.randomRoleDto import io.getstream.chat.android.client.Mother.randomSearchWarningResponse import io.getstream.chat.android.client.Mother.randomThreadParticipantDto +import io.getstream.chat.android.client.Mother.randomThreadResponse +import io.getstream.chat.android.client.Mother.randomThreadStateResponse import io.getstream.chat.android.client.Mother.randomUnreadChannelByTypeDto import io.getstream.chat.android.client.Mother.randomUnreadChannelDto import io.getstream.chat.android.client.Mother.randomUnreadDto @@ -1617,83 +1617,91 @@ internal class DomainMappingTest { } @Test - fun `DownstreamThreadDto is correctly mapped to Thread`() { - val user1 = randomUserResponse(id = "user1") - val user2 = randomUserResponse(id = "user2") + fun `ThreadStateResponse is correctly mapped to Thread`() { val participant1Dto = randomThreadParticipantDto( - userId = user1.id, - user = randomUserResponse(id = user1.id), + userId = "user1", + user = randomUserResponse(id = "user1"), lastThreadMessageAt = Date(2000), ) val participant2Dto = randomThreadParticipantDto( - userId = user2.id, - user = randomUserResponse(id = user2.id), + userId = "user2", + user = randomUserResponse(id = "user2"), lastThreadMessageAt = Date(1000), ) - val downstreamThreadDto = randomDownstreamThreadDto( - createdByUserId = user1.id, - createdBy = user1, + val response = randomThreadStateResponse( + createdByUserId = "user1", // Intentionally unsorted to validate sortedByLastReply() in mapping. threadParticipants = listOf(participant2Dto, participant1Dto), draft = randomDraftResponse( message = randomDraftPayloadResponse(text = "Draft message"), channelCid = "messaging:123", ), + extraData = mapOf("flair" to "gold"), ) val sut = Fixture().get() - val thread = with(sut) { downstreamThreadDto.toDomain() } - val fallbackChannelInfo = with(sut) { downstreamThreadDto.channel?.toChannelInfo() } + val thread = with(sut) { response.toDomain() } + val fallbackChannelInfo = with(sut) { response.channel?.toChannelInfo() } val expected = Thread( - activeParticipantCount = downstreamThreadDto.active_participant_count ?: 0, - cid = downstreamThreadDto.channel_cid, - channel = with(sut) { downstreamThreadDto.channel?.toDomain() }, - parentMessageId = downstreamThreadDto.parent_message_id, - parentMessage = with(sut) { downstreamThreadDto.parent_message.toDomain(fallbackChannelInfo) }, - createdByUserId = downstreamThreadDto.created_by_user_id, - createdBy = with(sut) { downstreamThreadDto.created_by?.toDomain() }, - participantCount = downstreamThreadDto.participant_count, + activeParticipantCount = response.activeParticipantCount, + cid = response.channelCid, + channel = with(sut) { response.channel?.toDomain() }, + parentMessageId = response.parentMessageId, + parentMessage = with(sut) { response.parentMessage!!.toDomain(fallbackChannelInfo) }, + createdByUserId = response.createdByUserId, + createdBy = with(sut) { response.createdBy?.toDomain() }, + participantCount = response.participantCount, threadParticipants = with(sut) { listOf(participant1Dto, participant2Dto).map { it.toDomain() }.sortedByLastReply() }, - lastMessageAt = downstreamThreadDto.last_message_at, - createdAt = downstreamThreadDto.created_at, - updatedAt = downstreamThreadDto.updated_at, - deletedAt = downstreamThreadDto.deleted_at, - title = downstreamThreadDto.title, - latestReplies = with(sut) { - downstreamThreadDto.latest_replies.map { it.toDomain(fallbackChannelInfo) } - }, - read = with(sut) { - downstreamThreadDto.read.orEmpty().map { it.toDomain(downstreamThreadDto.last_message_at) } - }, - draft = with(sut) { downstreamThreadDto.draft?.toDomain(fallbackChannelInfo) }, - extraData = downstreamThreadDto.extraData, + lastMessageAt = response.lastMessageAt!!, + createdAt = response.createdAt, + updatedAt = response.updatedAt, + deletedAt = response.deletedAt, + title = response.title, + latestReplies = with(sut) { response.latestReplies.map { it.toDomain(fallbackChannelInfo) } }, + read = with(sut) { response.read.orEmpty().map { it.toDomain(response.lastMessageAt) } }, + draft = with(sut) { response.draft?.toDomain(fallbackChannelInfo) }, + extraData = mapOf("flair" to "gold"), ) assertEquals(expected, thread) } @Test - fun `DownstreamThreadInfoDto is correctly mapped to ThreadInfo`() { - val downstreamThreadInfoDto = randomDownstreamThreadInfoDto() + fun `ThreadStateResponse without a parent message or last message date cannot be mapped`() { val sut = Fixture().get() - val threadInfo = with(sut) { downstreamThreadInfoDto.toDomain() } + + assertNull(with(sut) { randomThreadStateResponse(parentMessage = null).toDomain() }) + assertNull(with(sut) { randomThreadStateResponse(lastMessageAt = null).toDomain() }) + } + + @Test + fun `ThreadResponse is correctly mapped to ThreadInfo`() { + val response = randomThreadResponse( + // Non-empty, or a mapper that drops the participants would pass. + threadParticipants = listOf( + randomThreadParticipantDto(userId = "user1", user = randomUserResponse(id = "user1")), + ), + extraData = mapOf("flair" to "gold"), + ) + val sut = Fixture().get() + val threadInfo = with(sut) { response.toDomain() } val expected = ThreadInfo( - activeParticipantCount = downstreamThreadInfoDto.active_participant_count ?: 0, - cid = downstreamThreadInfoDto.channel_cid, - createdAt = downstreamThreadInfoDto.created_at, - createdBy = with(sut) { downstreamThreadInfoDto.created_by?.toDomain() }, - createdByUserId = downstreamThreadInfoDto.created_by_user_id, - deletedAt = downstreamThreadInfoDto.deleted_at, - lastMessageAt = downstreamThreadInfoDto.last_message_at, - parentMessage = with(sut) { downstreamThreadInfoDto.parent_message?.toDomain(downstreamThreadInfoDto.channel?.toChannelInfo()) }, - parentMessageId = downstreamThreadInfoDto.parent_message_id, - participantCount = downstreamThreadInfoDto.participant_count ?: 0, - replyCount = downstreamThreadInfoDto.reply_count ?: 0, - title = downstreamThreadInfoDto.title, - updatedAt = downstreamThreadInfoDto.updated_at, - channel = with(sut) { downstreamThreadInfoDto.channel?.toDomain() }, - threadParticipants = with(sut) { downstreamThreadInfoDto.thread_participants.orEmpty().map { it.toDomain() } }, - extraData = downstreamThreadInfoDto.extraData, + activeParticipantCount = response.activeParticipantCount, + cid = response.channelCid, + createdAt = response.createdAt, + createdBy = with(sut) { response.createdBy?.toDomain() }, + createdByUserId = response.createdByUserId, + deletedAt = response.deletedAt, + lastMessageAt = response.lastMessageAt, + parentMessage = with(sut) { response.parentMessage?.toDomain(response.channel?.toChannelInfo()) }, + parentMessageId = response.parentMessageId, + participantCount = response.participantCount, + replyCount = response.replyCount, + title = response.title, + updatedAt = response.updatedAt, + channel = with(sut) { response.channel?.toDomain() }, + threadParticipants = with(sut) { response.threadParticipants.orEmpty().map { it.toDomain() } }, + extraData = mapOf("flair" to "gold"), ) assertEquals(expected, threadInfo) } diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/EventMappingTestArguments.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/EventMappingTestArguments.kt index 7bdac0feaed..0a120f76aa9 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/EventMappingTestArguments.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/EventMappingTestArguments.kt @@ -218,7 +218,7 @@ internal object EventMappingTestArguments { private val POLL_VOTE = Mother.randomPollVoteResponseData(isAnswer = false) private val ANSWER_VOTE = Mother.randomPollVoteResponseData(isAnswer = true, answerText = "answer") private val REMINDER = Mother.randomDownstreamReminderDto() - private val THREAD_INFO = Mother.randomDownstreamThreadInfoDto() + private val THREAD_INFO = Mother.randomThreadResponse() private val AI_MESSAGE_ID = randomString() private val AI_STATE = randomString() private val DELETED_FOR_ME = randomBoolean() diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/DownstreamThreadInfoDtoAdapterTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ThreadResponseAdapterTest.kt similarity index 86% rename from stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/DownstreamThreadInfoDtoAdapterTest.kt rename to stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ThreadResponseAdapterTest.kt index 4f54c5edda5..0f41e7a9f28 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/DownstreamThreadInfoDtoAdapterTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ThreadResponseAdapterTest.kt @@ -16,19 +16,19 @@ package io.getstream.chat.android.client.parser2 -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadInfoDto import io.getstream.chat.android.client.parser2.testdata.ThreadDtoTestData +import io.getstream.chat.android.network.models.ThreadResponse import org.amshove.kluent.shouldBeEqualTo import org.junit.jupiter.api.Test -internal class DownstreamThreadInfoDtoAdapterTest { +internal class ThreadResponseAdapterTest { private val parser = ParserFactory.createMoshiChatParser() @Test fun `Deserialize JSON thread info with custom fields`() { val threadInfo = parser.fromJson( ThreadDtoTestData.downstreamThreadInfoJson, - DownstreamThreadInfoDto::class.java, + ThreadResponse::class.java, ) threadInfo shouldBeEqualTo ThreadDtoTestData.downstreamThreadInfo } @@ -37,7 +37,7 @@ internal class DownstreamThreadInfoDtoAdapterTest { fun `Deserialize JSON thread info without custom fields`() { val threadInfo = parser.fromJson( ThreadDtoTestData.downstreamThreadInfoJsonWithoutExtraData, - DownstreamThreadInfoDto::class.java, + ThreadResponse::class.java, ) threadInfo shouldBeEqualTo ThreadDtoTestData.downstreamThreadInfoWithoutExtraData } diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/DownstreamThreadDtoAdapterTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ThreadStateResponseAdapterTest.kt similarity index 86% rename from stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/DownstreamThreadDtoAdapterTest.kt rename to stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ThreadStateResponseAdapterTest.kt index 57eeeaccb61..673802bbb3e 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/DownstreamThreadDtoAdapterTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ThreadStateResponseAdapterTest.kt @@ -16,19 +16,19 @@ package io.getstream.chat.android.client.parser2 -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadDto import io.getstream.chat.android.client.parser2.testdata.ThreadDtoTestData +import io.getstream.chat.android.network.models.ThreadStateResponse import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -internal class DownstreamThreadDtoAdapterTest { +internal class ThreadStateResponseAdapterTest { private val parser = ParserFactory.createMoshiChatParser() @Test fun `Deserialize JSON thread with custom fields`() { val thread = parser.fromJson( ThreadDtoTestData.downstreamThreadJson, - DownstreamThreadDto::class.java, + ThreadStateResponse::class.java, ) Assertions.assertEquals(ThreadDtoTestData.downstreamThread, thread) } @@ -37,7 +37,7 @@ internal class DownstreamThreadDtoAdapterTest { fun `Deserialize JSON thread without custom fields`() { val thread = parser.fromJson( ThreadDtoTestData.downstreamThreadJsonWithoutExtraData, - DownstreamThreadDto::class.java, + ThreadStateResponse::class.java, ) Assertions.assertEquals(ThreadDtoTestData.downstreamThreadWithoutExtraData, thread) } diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ThreadDtoTestData.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ThreadDtoTestData.kt index bb5abb2b19e..c7664450c85 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ThreadDtoTestData.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ThreadDtoTestData.kt @@ -16,15 +16,42 @@ package io.getstream.chat.android.client.parser2.testdata -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadDto -import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadInfoDto +import io.getstream.chat.android.client.parser2.ParserFactory +import io.getstream.chat.android.network.models.MessageResponse import io.getstream.chat.android.network.models.ReadStateResponse import io.getstream.chat.android.network.models.ThreadParticipant +import io.getstream.chat.android.network.models.ThreadResponse +import io.getstream.chat.android.network.models.ThreadStateResponse import org.intellij.lang.annotations.Language import java.util.Date internal object ThreadDtoTestData { + // A message as the wire sends it: every field the backend always emits (plain Go tags). + @Language("JSON") + private val messageJson = + """{ + "id": "message-id", + "cid": "messaging:123", + "text": "hello", + "html": "

hello

", + "type": "regular", + "created_at": "2020-06-10T11:04:31.000Z", + "updated_at": "2020-06-10T11:04:31.000Z", + "mentioned_channel": false, + "mentioned_here": false, + "pinned": false, + "shadowed": false, + "silent": false, + "reply_count": 0, + "deleted_reply_count": 0, + "user": ${UserDtoTestData.userResponseJson} + }""" + + // Nested messages go through the generated MessageResponse, whose parsing has its own tests. + private val message: MessageResponse = ParserFactory.createMoshiChatParser() + .fromJson(messageJson, MessageResponse::class.java) + @Language("JSON") val downstreamThreadJson = """{ @@ -37,8 +64,8 @@ internal object ThreadDtoTestData { "deleted_at": null, "draft": null, "last_message_at": "2020-06-10T11:04:31.588Z", - "latest_replies": [${MessageDtoTestData.downstreamJsonWithoutExtraData}], - "parent_message": ${MessageDtoTestData.downstreamJsonWithoutExtraData}, + "latest_replies": [$messageJson], + "parent_message": $messageJson, "parent_message_id": "parent_msg_id", "participant_count": 5, "read": [ @@ -80,20 +107,18 @@ internal object ThreadDtoTestData { ] }""" - val downstreamThread = DownstreamThreadDto( - active_participant_count = 3, + val downstreamThread = ThreadStateResponse( + activeParticipantCount = 3, channel = ChannelDtoTestData.channelResponse, - channel_cid = "messaging:123", - created_at = Date(1591787071000), - created_by = UserDtoTestData.userResponse, - created_by_user_id = "user1", - deleted_at = null, - draft = null, - last_message_at = Date(1591787071588), - latest_replies = listOf(MessageDtoTestData.downstreamMessageWithoutExtraData), - parent_message = MessageDtoTestData.downstreamMessageWithoutExtraData, - parent_message_id = "parent_msg_id", - participant_count = 5, + channelCid = "messaging:123", + createdAt = Date(1591787071000), + createdBy = UserDtoTestData.userResponse, + createdByUserId = "user1", + lastMessageAt = Date(1591787071588), + latestReplies = listOf(message), + parentMessage = message, + parentMessageId = "parent_msg_id", + participantCount = 5, read = listOf( ReadStateResponse( user = UserDtoTestData.userResponse, @@ -102,8 +127,8 @@ internal object ThreadDtoTestData { lastReadMessageId = "messageId", ), ), - reply_count = 10, - thread_participants = listOf( + replyCount = 10, + threadParticipants = listOf( ThreadParticipant( channelCid = "messaging:channelId", createdAt = Date(1591787071588), @@ -114,8 +139,8 @@ internal object ThreadDtoTestData { ), ), title = "Thread Title", - updated_at = Date(1591787071588), - extraData = mapOf( + updatedAt = Date(1591787071588), + custom = mapOf( "extraData" to mapOf( "key1" to "value1", "key2" to true, @@ -146,7 +171,7 @@ internal object ThreadDtoTestData { "draft": null, "last_message_at": "2020-06-10T11:04:31.588Z", "latest_replies": [], - "parent_message": ${MessageDtoTestData.downstreamJsonWithoutExtraData}, + "parent_message": $messageJson, "parent_message_id": "parent_msg_id_2", "participant_count": 2, "read": [], @@ -156,26 +181,24 @@ internal object ThreadDtoTestData { "updated_at": "2020-06-10T11:04:31.588Z" }""" - val downstreamThreadWithoutExtraData = DownstreamThreadDto( - active_participant_count = 2, + val downstreamThreadWithoutExtraData = ThreadStateResponse( + activeParticipantCount = 2, channel = ChannelDtoTestData.channelResponse, - channel_cid = "messaging:456", - created_at = Date(1591787071000), - created_by = UserDtoTestData.userResponse, - created_by_user_id = "user2", - deleted_at = null, - draft = null, - last_message_at = Date(1591787071588), - latest_replies = emptyList(), - parent_message = MessageDtoTestData.downstreamMessageWithoutExtraData, - parent_message_id = "parent_msg_id_2", - participant_count = 2, + channelCid = "messaging:456", + createdAt = Date(1591787071000), + createdBy = UserDtoTestData.userResponse, + createdByUserId = "user2", + lastMessageAt = Date(1591787071588), + latestReplies = emptyList(), + parentMessage = message, + parentMessageId = "parent_msg_id_2", + participantCount = 2, read = emptyList(), - reply_count = 0, - thread_participants = emptyList(), + replyCount = 0, + threadParticipants = emptyList(), title = "Simple Thread", - updated_at = Date(1591787071588), - extraData = emptyMap(), + updatedAt = Date(1591787071588), + custom = emptyMap(), ) @Language("JSON") @@ -184,7 +207,7 @@ internal object ThreadDtoTestData { "channel_cid": "messaging:789", "channel": ${ChannelDtoTestData.channelResponseJson}, "parent_message_id": "parent_msg_id_3", - "parent_message": ${MessageDtoTestData.downstreamJsonWithoutExtraData}, + "parent_message": $messageJson, "created_by_user_id": "user3", "created_by": ${UserDtoTestData.userResponseJson}, "reply_count": 15, @@ -214,17 +237,17 @@ internal object ThreadDtoTestData { "customInfoKey": "customInfoVal" }""" - val downstreamThreadInfo = DownstreamThreadInfoDto( - channel_cid = "messaging:789", + val downstreamThreadInfo = ThreadResponse( + channelCid = "messaging:789", channel = ChannelDtoTestData.channelResponse, - parent_message_id = "parent_msg_id_3", - parent_message = MessageDtoTestData.downstreamMessageWithoutExtraData, - created_by_user_id = "user3", - created_by = UserDtoTestData.userResponse, - reply_count = 15, - participant_count = 8, - active_participant_count = 4, - thread_participants = listOf( + parentMessageId = "parent_msg_id_3", + parentMessage = message, + createdByUserId = "user3", + createdBy = UserDtoTestData.userResponse, + replyCount = 15, + participantCount = 8, + activeParticipantCount = 4, + threadParticipants = listOf( ThreadParticipant( channelCid = "messaging:channelId", createdAt = Date(1591787071588), @@ -234,12 +257,11 @@ internal object ThreadDtoTestData { lastThreadMessageAt = null, ), ), - last_message_at = Date(1591787071588), - created_at = Date(1591787071000), - updated_at = Date(1591787071588), - deleted_at = null, + lastMessageAt = Date(1591787071588), + createdAt = Date(1591787071000), + updatedAt = Date(1591787071588), title = "Thread Info Title", - extraData = mapOf( + custom = mapOf( "extraData" to mapOf( "info_key1" to "info_value1", "info_key2" to false, @@ -268,22 +290,21 @@ internal object ThreadDtoTestData { "title": "Minimal Thread Info" }""" - val downstreamThreadInfoWithoutExtraData = DownstreamThreadInfoDto( - channel_cid = "messaging:000", + val downstreamThreadInfoWithoutExtraData = ThreadResponse( + channelCid = "messaging:000", channel = null, - parent_message_id = "parent_msg_id_4", - parent_message = null, - created_by_user_id = "user4", - created_by = UserDtoTestData.userResponse, - reply_count = 0, - participant_count = 1, - active_participant_count = 1, - thread_participants = emptyList(), - last_message_at = null, - created_at = Date(1591787071000), - updated_at = Date(1591787071588), - deleted_at = null, + parentMessageId = "parent_msg_id_4", + parentMessage = null, + createdByUserId = "user4", + createdBy = UserDtoTestData.userResponse, + replyCount = 0, + participantCount = 1, + activeParticipantCount = 1, + threadParticipants = emptyList(), + lastMessageAt = null, + createdAt = Date(1591787071000), + updatedAt = Date(1591787071588), title = "Minimal Thread Info", - extraData = emptyMap(), + custom = emptyMap(), ) }