From ef866cf803b6616d9e3d104048d29b920db3d1ff Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Mon, 24 Aug 2026 11:23:31 +0200 Subject: [PATCH 1/2] Migrate the queryBannedUsers response to the generated BanResponse --- .../chat/android/client/api2/MoshiChatApi.kt | 12 +++-- .../client/api2/endpoint/ModerationApi.kt | 2 +- .../client/api2/mapping/DomainMapping.kt | 17 +++--- .../api2/model/response/BannedUserResponse.kt | 33 ------------ .../android/network/models/BanResponse.kt | 53 +++++++++++++++++++ .../models}/QueryBannedUsersResponse.kt | 22 ++++++-- .../getstream/chat/android/client/Mother.kt | 18 +++---- .../android/client/api2/MoshiChatApiTest.kt | 2 +- .../client/api2/MoshiChatApiTestArguments.kt | 6 ++- .../client/api2/mapping/DomainMappingTest.kt | 39 ++++++++++---- 10 files changed, 133 insertions(+), 71 deletions(-) delete mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/BannedUserResponse.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/BanResponse.kt rename stream-chat-android-client/src/main/java/io/getstream/chat/android/{client/api2/model/response => network/models}/QueryBannedUsersResponse.kt (59%) 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 fe7ade61f741..37b609b41647 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 @@ -998,10 +998,16 @@ constructor( created_at_before = createdAtBefore, created_at_before_or_equal = createdAtBeforeOrEqual, ), - ).mapDomain { response -> - response.bans.map { - it.toDomain() + ).flatMapDomain { response -> + val bans = response.bans.mapNotNull { it.toDomain() } + val result = if (bans.size == response.bans.size) { + Result.Success(bans) + } else { + Result.Failure( + Error.GenericError("A ban in the query banned users response carried no user"), + ) } + CoroutineCall(coroutineScope) { result } } } diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/ModerationApi.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/ModerationApi.kt index b46d64359d14..6991d87773ff 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/ModerationApi.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/ModerationApi.kt @@ -24,9 +24,9 @@ import io.getstream.chat.android.client.api2.model.requests.MuteUserRequest import io.getstream.chat.android.client.api2.model.requests.QueryBannedUsersRequest import io.getstream.chat.android.client.api2.model.response.FlagResponse import io.getstream.chat.android.client.api2.model.response.MuteUserResponse -import io.getstream.chat.android.client.api2.model.response.QueryBannedUsersResponse import io.getstream.chat.android.client.call.RetrofitCall import io.getstream.chat.android.network.models.MuteChannelRequest +import io.getstream.chat.android.network.models.QueryBannedUsersResponse import io.getstream.chat.android.network.models.Response import retrofit2.http.Body import retrofit2.http.DELETE 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 1927b76df6b3..2a8a1cc86a5d 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 @@ -56,7 +56,6 @@ import io.getstream.chat.android.client.api2.model.dto.PrivacySettingsDto import io.getstream.chat.android.client.api2.model.dto.ReadReceiptsDto import io.getstream.chat.android.client.api2.model.dto.SearchWarningDto import io.getstream.chat.android.client.api2.model.dto.TypingIndicatorsDto -import io.getstream.chat.android.client.api2.model.response.BannedUserResponse import io.getstream.chat.android.client.api2.model.response.MessageResponse import io.getstream.chat.android.client.api2.model.response.QueryPollVotesResponse import io.getstream.chat.android.client.api2.model.response.QueryPollsResponse @@ -128,6 +127,7 @@ import io.getstream.chat.android.models.querysort.QuerySortByField import io.getstream.chat.android.models.querysort.QuerySorter import io.getstream.chat.android.models.querysort.SortDirection import io.getstream.chat.android.network.models.AppResponseFields +import io.getstream.chat.android.network.models.BanResponse import io.getstream.chat.android.network.models.BlockUsersResponse import io.getstream.chat.android.network.models.BlockedUserResponse import io.getstream.chat.android.network.models.ChannelConfigWithInfo @@ -748,16 +748,19 @@ internal class DomainMapping( ) /** - * Transforms [BannedUserResponse] to [BannedUser]. + * Transforms [BanResponse] to [BannedUser], or to null when the ban carries no user. + * + * A ban always names the user it targets, so a null one means the response cannot be mapped rather + * than that the ban has no target. */ - internal fun BannedUserResponse.toDomain(): BannedUser { + internal fun BanResponse.toDomain(): BannedUser? { return BannedUser( - user = user.toDomain(), - bannedBy = banned_by?.toDomain(), + user = user?.toDomain() ?: return null, + bannedBy = bannedBy?.toDomain(), channel = channel?.toDomain(), - createdAt = created_at, + createdAt = createdAt, expires = expires, - shadow = shadow, + shadow = shadow ?: false, reason = reason, ) } diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/BannedUserResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/BannedUserResponse.kt deleted file mode 100644 index 0734b54d1306..000000000000 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/BannedUserResponse.kt +++ /dev/null @@ -1,33 +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.model.dto.DownstreamChannelDto -import io.getstream.chat.android.client.api2.model.dto.DownstreamUserDto -import java.util.Date - -@JsonClass(generateAdapter = true) -internal data class BannedUserResponse( - val user: DownstreamUserDto, - val banned_by: DownstreamUserDto?, - val channel: DownstreamChannelDto?, - val created_at: Date?, - val expires: Date?, - val shadow: Boolean = false, - val reason: String?, -) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/BanResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/BanResponse.kt new file mode 100644 index 000000000000..12da15b294e0 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/BanResponse.kt @@ -0,0 +1,53 @@ +/* + * 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 BanResponse( + @Json(name = "created_at") + internal val createdAt: java.util.Date, + + @Json(name = "expires") + internal val expires: java.util.Date? = null, + + @Json(name = "reason") + internal val reason: String? = null, + + @Json(name = "shadow") + internal val shadow: Boolean? = null, + + @Json(name = "banned_by") + internal val bannedBy: io.getstream.chat.android.network.models.UserResponse? = null, + + @Json(name = "channel") + internal val channel: io.getstream.chat.android.network.models.ChannelResponse? = null, + + @Json(name = "user") + internal val user: io.getstream.chat.android.network.models.UserResponse? = null, +) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/QueryBannedUsersResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/QueryBannedUsersResponse.kt similarity index 59% rename from stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/QueryBannedUsersResponse.kt rename to stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/QueryBannedUsersResponse.kt index 4d062aba2d32..401d08ebd497 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/QueryBannedUsersResponse.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/QueryBannedUsersResponse.kt @@ -14,11 +14,25 @@ * 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 com.squareup.moshi.Json -@JsonClass(generateAdapter = true) +/** + * + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) internal data class QueryBannedUsersResponse( - val bans: List, + @Json(name = "duration") + internal val duration: String, + + @Json(name = "bans") + internal val bans: List = emptyList(), ) 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 59ff9fb4d832..3229a3e0a65e 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 @@ -58,7 +58,6 @@ import io.getstream.chat.android.client.api2.model.dto.PrivacySettingsDto import io.getstream.chat.android.client.api2.model.dto.ReadReceiptsDto import io.getstream.chat.android.client.api2.model.dto.SearchWarningDto import io.getstream.chat.android.client.api2.model.dto.TypingIndicatorsDto -import io.getstream.chat.android.client.api2.model.response.BannedUserResponse import io.getstream.chat.android.client.api2.model.response.DraftMessageResponse import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesResponse import io.getstream.chat.android.client.api2.model.response.QueryPollVotesResponse @@ -90,6 +89,7 @@ import io.getstream.chat.android.models.VotingVisibility import io.getstream.chat.android.models.querysort.QuerySortByField import io.getstream.chat.android.models.querysort.QuerySorter import io.getstream.chat.android.network.models.AppResponseFields +import io.getstream.chat.android.network.models.BanResponse import io.getstream.chat.android.network.models.BlockUsersResponse import io.getstream.chat.android.network.models.BlockedUserResponse import io.getstream.chat.android.network.models.ChannelMemberResponse @@ -845,19 +845,19 @@ internal object Mother { extraData = extraData, ) - fun randomBannedUserResponse( - user: DownstreamUserDto = randomDownstreamUserDto(), - bannedBy: DownstreamUserDto = randomDownstreamUserDto(), - channel: DownstreamChannelDto = randomDownstreamChannelDto(), + fun randomBanResponse( + user: UserResponse? = randomUserResponse(), + bannedBy: UserResponse = randomUserResponse(), + channel: ChannelResponse = randomChannelResponse(), createdAt: Date = randomDate(), expires: Date = randomDate(), - shadow: Boolean = randomBoolean(), + shadow: Boolean? = randomBoolean(), reason: String = randomString(), - ): BannedUserResponse = BannedUserResponse( + ): BanResponse = BanResponse( user = user, - banned_by = bannedBy, + bannedBy = bannedBy, channel = channel, - created_at = createdAt, + createdAt = createdAt, expires = expires, shadow = shadow, reason = reason, 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 225a652b474b..5f19413f37c6 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 @@ -60,7 +60,6 @@ import io.getstream.chat.android.client.api2.model.response.MuteUserResponse import io.getstream.chat.android.client.api2.model.response.ParsedPredefinedFilterResponse import io.getstream.chat.android.client.api2.model.response.PollResponse import io.getstream.chat.android.client.api2.model.response.PollVoteResponse -import io.getstream.chat.android.client.api2.model.response.QueryBannedUsersResponse import io.getstream.chat.android.client.api2.model.response.QueryChannelsResponse import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesResponse import io.getstream.chat.android.client.api2.model.response.QueryGroupedChannelsGroup @@ -154,6 +153,7 @@ import io.getstream.chat.android.network.models.PollOptionInput import io.getstream.chat.android.network.models.PollOptionRequest import io.getstream.chat.android.network.models.PollOptionResponse import io.getstream.chat.android.network.models.PushPreferencesResponse +import io.getstream.chat.android.network.models.QueryBannedUsersResponse import io.getstream.chat.android.network.models.QueryDraftsRequest import io.getstream.chat.android.network.models.QueryPollVotesRequest import io.getstream.chat.android.network.models.QueryPollsRequest 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 8d42f8761ae0..509d94eeefbc 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 @@ -40,7 +40,6 @@ import io.getstream.chat.android.client.api2.model.response.MuteUserResponse import io.getstream.chat.android.client.api2.model.response.ParsedPredefinedFilterResponse import io.getstream.chat.android.client.api2.model.response.PollResponse import io.getstream.chat.android.client.api2.model.response.PollVoteResponse -import io.getstream.chat.android.client.api2.model.response.QueryBannedUsersResponse import io.getstream.chat.android.client.api2.model.response.QueryChannelsResponse import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesResponse import io.getstream.chat.android.client.api2.model.response.QueryGroupedChannelsGroup @@ -78,6 +77,7 @@ import io.getstream.chat.android.network.models.ListDevicesResponse import io.getstream.chat.android.network.models.ListUserGroupsResponse import io.getstream.chat.android.network.models.MembersResponse import io.getstream.chat.android.network.models.PollOptionResponse +import io.getstream.chat.android.network.models.QueryBannedUsersResponse import io.getstream.chat.android.network.models.RemoveUserGroupMembersResponse import io.getstream.chat.android.network.models.Response import io.getstream.chat.android.network.models.SearchRolesResponse @@ -289,7 +289,9 @@ internal object MoshiChatApiTestArguments { @JvmStatic fun queryBannedUsersInput() = listOf( Arguments.of( - RetroSuccess(QueryBannedUsersResponse(listOf(Mother.randomBannedUserResponse()))).toRetrofitCall(), + RetroSuccess( + QueryBannedUsersResponse(duration = randomString(), bans = listOf(Mother.randomBanResponse())), + ).toRetrofitCall(), Result.Success::class, ), Arguments.of( 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 e398f027b5b2..4504091cd239 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 @@ -23,7 +23,7 @@ import io.getstream.chat.android.client.Mother.randomAnswerDownstreamVoteDto import io.getstream.chat.android.client.Mother.randomAppResponseFields import io.getstream.chat.android.client.Mother.randomAppSettingsResponse import io.getstream.chat.android.client.Mother.randomAttachmentDto -import io.getstream.chat.android.client.Mother.randomBannedUserResponse +import io.getstream.chat.android.client.Mother.randomBanResponse import io.getstream.chat.android.client.Mother.randomBlockUsersResponse import io.getstream.chat.android.client.Mother.randomBlockedUserResponse import io.getstream.chat.android.client.Mother.randomChannelInfoDto @@ -144,6 +144,7 @@ import io.getstream.chat.android.randomString import io.getstream.chat.android.randomUser import org.amshove.kluent.shouldBeEqualTo import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -892,22 +893,38 @@ internal class DomainMappingTest { } @Test - fun `BannedUserResponse is correctly mapped to BannedUser`() { - val bannedUserResponse = randomBannedUserResponse() + fun `BanResponse is correctly mapped to BannedUser`() { + val banResponse = randomBanResponse() val sut = Fixture().get() - val bannedUser = with(sut) { bannedUserResponse.toDomain() } + val bannedUser = with(sut) { banResponse.toDomain() } val expected = BannedUser( - user = with(sut) { bannedUserResponse.user.toDomain() }, - bannedBy = with(sut) { bannedUserResponse.banned_by?.toDomain() }, - channel = with(sut) { bannedUserResponse.channel?.toDomain() }, - createdAt = bannedUserResponse.created_at, - expires = bannedUserResponse.expires, - shadow = bannedUserResponse.shadow, - reason = bannedUserResponse.reason, + user = with(sut) { banResponse.user!!.toDomain() }, + bannedBy = with(sut) { banResponse.bannedBy?.toDomain() }, + channel = with(sut) { banResponse.channel?.toDomain() }, + createdAt = banResponse.createdAt, + expires = banResponse.expires, + shadow = banResponse.shadow!!, + reason = banResponse.reason, ) assertEquals(expected, bannedUser) } + @Test + fun `BanResponse without a user is not mapped`() { + val banResponse = randomBanResponse(user = null) + val sut = Fixture().get() + + assertNull(with(sut) { banResponse.toDomain() }) + } + + @Test + fun `BanResponse without a shadow flag is mapped to a ban that is not shadow`() { + val banResponse = randomBanResponse(shadow = null) + val sut = Fixture().get() + + assertFalse(with(sut) { banResponse.toDomain()!!.shadow }) + } + @Test fun `ChannelInfoDto is correctly mapped to ChannelInfo`() { val channelInfoDto = randomChannelInfoDto() From ed1187c4542ebdff4e70a129b734378aae7ec1d4 Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Mon, 24 Aug 2026 16:25:20 +0200 Subject: [PATCH 2/2] Build the query banned users failure with ErrorCall --- .../chat/android/client/api2/MoshiChatApi.kt | 11 +++++------ .../android/client/api2/MoshiChatApiTestArguments.kt | 10 ++++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) 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 37b609b41647..3e983d0965f5 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 @@ -1000,14 +1000,13 @@ constructor( ), ).flatMapDomain { response -> val bans = response.bans.mapNotNull { it.toDomain() } - val result = if (bans.size == response.bans.size) { - Result.Success(bans) + if (bans.size == response.bans.size) { + CoroutineCall(coroutineScope) { Result.Success(bans) } } else { - Result.Failure( - Error.GenericError("A ban in the query banned users response carried no user"), - ) + // A ban always names the user it targets, so a missing one means the response cannot be mapped. + val error = Error.GenericError("A ban in the query banned users response carried no user") + ErrorCall(coroutineScope, error) } - CoroutineCall(coroutineScope) { result } } } 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 509d94eeefbc..35b89159f91f 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 @@ -298,6 +298,16 @@ internal object MoshiChatApiTestArguments { RetroError(statusCode = 500).toRetrofitCall(), Result.Failure::class, ), + // A ban the mapper cannot map fails the whole call rather than being dropped from the list. + Arguments.of( + RetroSuccess( + QueryBannedUsersResponse( + duration = randomString(), + bans = listOf(Mother.randomBanResponse(user = null)), + ), + ).toRetrofitCall(), + Result.Failure::class, + ), ) @JvmStatic