diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/UserApi.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/UserApi.kt index 05595c5cfcb..7a56911bdfa 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/UserApi.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/UserApi.kt @@ -21,12 +21,12 @@ import io.getstream.chat.android.client.api.QueryParams import io.getstream.chat.android.client.api2.UrlQueryPayload import io.getstream.chat.android.client.api2.model.dto.DownstreamLocationDto import io.getstream.chat.android.client.api2.model.response.LiveLocationsResponse -import io.getstream.chat.android.client.api2.model.response.QueryBlockedUsersResponse import io.getstream.chat.android.client.api2.model.response.UpdateUsersResponse import io.getstream.chat.android.client.api2.model.response.UsersResponse import io.getstream.chat.android.client.call.RetrofitCall import io.getstream.chat.android.network.models.BlockUsersRequest import io.getstream.chat.android.network.models.BlockUsersResponse +import io.getstream.chat.android.network.models.GetBlockedUsersResponse import io.getstream.chat.android.network.models.QueryUsersPayload import io.getstream.chat.android.network.models.UnblockUsersRequest import io.getstream.chat.android.network.models.UnblockUsersResponse @@ -57,7 +57,7 @@ internal interface UserApi { fun unblockUser(@Body body: UnblockUsersRequest): RetrofitCall @GET("/users/block") - fun queryBlockedUsers(): RetrofitCall + fun queryBlockedUsers(): RetrofitCall @PATCH("/users") @JvmSuppressWildcards // See issue: https://github.com/square/retrofit/issues/3275 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 d339f6b8f73..1b5230e2966 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 @@ -48,7 +48,6 @@ 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.DownstreamUserBlockDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupMemberDto @@ -130,6 +129,7 @@ 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.BlockUsersResponse +import io.getstream.chat.android.network.models.BlockedUserResponse import io.getstream.chat.android.network.models.ChannelPushPreferencesResponse import io.getstream.chat.android.network.models.ChatPreferencesResponse import io.getstream.chat.android.network.models.DeviceResponse @@ -902,18 +902,18 @@ internal class DomainMapping( ) /** - * Transforms [DownstreamUserBlockDto] into [UserBlock] + * Transforms [BlockedUserResponse] into [UserBlock] */ - internal fun DownstreamUserBlockDto.toDomain(): UserBlock = UserBlock( - blockedBy = user_id, - userId = blocked_user_id, - blockedAt = created_at, + internal fun BlockedUserResponse.toDomain(): UserBlock = UserBlock( + blockedBy = userId, + userId = blockedUserId, + blockedAt = createdAt, ) /** - * Transforms a list of [DownstreamUserBlockDto] into a list of [UserBlock] + * Transforms a list of [BlockedUserResponse] into a list of [UserBlock] */ - internal fun List.toDomain(): List = map { it.toDomain() } + internal fun List.toDomain(): List = map { it.toDomain() } /** * Transforms [BlockUsersResponse] into [UserBlock]. diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/UserDtos.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/UserDtos.kt index 3b120398e56..d7360fe1f0e 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/UserDtos.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/UserDtos.kt @@ -82,10 +82,3 @@ internal data class DownstreamUserDto( val push_preferences: DownstreamPushPreferenceDto?, val extraData: Map, ) : ExtraDataDto - -@JsonClass(generateAdapter = true) -internal data class DownstreamUserBlockDto( - val user_id: String, - val blocked_user_id: String, - val created_at: Date, -) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/BlockedUserResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/BlockedUserResponse.kt new file mode 100644 index 00000000000..3a47b852371 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/BlockedUserResponse.kt @@ -0,0 +1,47 @@ +/* + * 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 BlockedUserResponse( + @Json(name = "blocked_user_id") + internal val blockedUserId: String, + + @Json(name = "created_at") + internal val createdAt: java.util.Date, + + @Json(name = "user_id") + internal val userId: String, + + @Json(name = "blocked_user") + internal val blockedUser: io.getstream.chat.android.network.models.UserResponse, + + @Json(name = "user") + internal val user: io.getstream.chat.android.network.models.UserResponse, +) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/QueryBlockedUsersResponse.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/GetBlockedUsersResponse.kt similarity index 55% rename from stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/QueryBlockedUsersResponse.kt rename to stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/GetBlockedUsersResponse.kt index 5cc30fd0644..8725d47bafb 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/response/QueryBlockedUsersResponse.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/GetBlockedUsersResponse.kt @@ -14,12 +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.Json -import com.squareup.moshi.JsonClass -import io.getstream.chat.android.client.api2.model.dto.DownstreamUserBlockDto +/** + * + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class GetBlockedUsersResponse( + @Json(name = "duration") + internal val duration: String, -@JsonClass(generateAdapter = true) -internal data class QueryBlockedUsersResponse( - val blocks: List, + @Json(name = "blocks") + internal val blocks: 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 55fbb028ca3..9b54a69da1a 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 @@ -48,7 +48,6 @@ import io.getstream.chat.android.client.api2.model.dto.DownstreamReactionGroupDt 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.DownstreamUserBlockDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupMemberDto @@ -92,6 +91,7 @@ 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.BlockUsersResponse +import io.getstream.chat.android.network.models.BlockedUserResponse import io.getstream.chat.android.network.models.CreateGuestResponse import io.getstream.chat.android.network.models.DeviceResponse import io.getstream.chat.android.network.models.FileUploadConfig @@ -1114,14 +1114,16 @@ internal object Mother { extraData = extraData, ) - fun randomDownstreamUserBlockDto( + fun randomBlockedUserResponse( userId: String = randomString(), blockedUserId: String = randomString(), createdAt: Date = randomDate(), - ): DownstreamUserBlockDto = DownstreamUserBlockDto( - user_id = userId, - blocked_user_id = blockedUserId, - created_at = createdAt, + ): BlockedUserResponse = BlockedUserResponse( + userId = userId, + blockedUserId = blockedUserId, + createdAt = createdAt, + blockedUser = randomUserResponse(id = blockedUserId), + user = randomUserResponse(id = userId), ) fun randomBlockUsersResponse( 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 dc3e07ee86b..34fadec9c0e 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 @@ -61,7 +61,6 @@ import io.getstream.chat.android.client.api2.model.response.ParsedPredefinedFilt 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.QueryBlockedUsersResponse 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 @@ -138,6 +137,7 @@ import io.getstream.chat.android.network.models.CreateUserGroupResponse import io.getstream.chat.android.network.models.DeliveredMessagePayload import io.getstream.chat.android.network.models.EventRequest import io.getstream.chat.android.network.models.GetApplicationResponse +import io.getstream.chat.android.network.models.GetBlockedUsersResponse import io.getstream.chat.android.network.models.GetUserGroupResponse import io.getstream.chat.android.network.models.GroupedChannelsGroupRequest import io.getstream.chat.android.network.models.GroupedQueryChannelsRequest @@ -1836,7 +1836,7 @@ internal class MoshiChatApiTest { @ParameterizedTest @MethodSource("io.getstream.chat.android.client.api2.MoshiChatApiTestArguments#queryBlockedUsersInput") - fun testQueryBlockedUsers(call: RetrofitCall, expected: KClass<*>) = runTest { + fun testQueryBlockedUsers(call: RetrofitCall, expected: KClass<*>) = runTest { // given val api = mock() whenever(api.queryBlockedUsers()).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 c2dd83d693d..f2b60fd2092 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 @@ -41,7 +41,6 @@ import io.getstream.chat.android.client.api2.model.response.ParsedPredefinedFilt 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.QueryBlockedUsersResponse 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 @@ -74,6 +73,7 @@ import io.getstream.chat.android.network.models.BlockUsersResponse import io.getstream.chat.android.network.models.CreateGuestResponse import io.getstream.chat.android.network.models.CreateUserGroupResponse import io.getstream.chat.android.network.models.GetApplicationResponse +import io.getstream.chat.android.network.models.GetBlockedUsersResponse import io.getstream.chat.android.network.models.GetUserGroupResponse import io.getstream.chat.android.network.models.ListDevicesResponse import io.getstream.chat.android.network.models.ListUserGroupsResponse @@ -406,11 +406,13 @@ internal object MoshiChatApiTestArguments { @JvmStatic fun queryBlockedUsersInput() = listOf( Arguments.of( - RetroSuccess(QueryBlockedUsersResponse(listOf(Mother.randomDownstreamUserBlockDto()))).toRetrofitCall(), + RetroSuccess( + GetBlockedUsersResponse(duration = "1ms", blocks = listOf(Mother.randomBlockedUserResponse())), + ).toRetrofitCall(), Result.Success::class, ), Arguments.of( - RetroError(statusCode = 500).toRetrofitCall(), + RetroError(statusCode = 500).toRetrofitCall(), Result.Failure::class, ), ) 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 189591d7245..43ecb6a3c9f 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 @@ -25,6 +25,7 @@ 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.randomBlockUsersResponse +import io.getstream.chat.android.client.Mother.randomBlockedUserResponse import io.getstream.chat.android.client.Mother.randomChannelInfoDto import io.getstream.chat.android.client.Mother.randomCommandDto import io.getstream.chat.android.client.Mother.randomConfigDto @@ -48,7 +49,6 @@ import io.getstream.chat.android.client.Mother.randomDownstreamReactionGroupDto 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.randomDownstreamUserBlockDto import io.getstream.chat.android.client.Mother.randomDownstreamUserDto import io.getstream.chat.android.client.Mother.randomDownstreamUserGroupDto import io.getstream.chat.android.client.Mother.randomDownstreamVoteDto @@ -1007,19 +1007,21 @@ internal class DomainMappingTest { } @Test - fun `DownstreamUserBlockDto is correctly mapped to UserBlock`() { - val downstreamUserBlockDto = randomDownstreamUserBlockDto() - val downstreamBlocklist = listOf(downstreamUserBlockDto) + fun `BlockedUserResponse is correctly mapped to UserBlock`() { + val blockedAt = Date(1000) + val response = randomBlockedUserResponse( + userId = "blocker-1", + blockedUserId = "blocked-1", + createdAt = blockedAt, + ) val sut = Fixture().get() - val blocklist = with(sut) { downstreamBlocklist.toDomain() } - val expected = listOf( - UserBlock( - blockedBy = downstreamUserBlockDto.user_id, - userId = downstreamUserBlockDto.blocked_user_id, - blockedAt = downstreamUserBlockDto.created_at, - ), + + val blocklist = with(sut) { listOf(response).toDomain() } + + assertEquals( + listOf(UserBlock(blockedBy = "blocker-1", userId = "blocked-1", blockedAt = blockedAt)), + blocklist, ) - assertEquals(expected, blocklist) } @Test diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/BlockedUsersParsingTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/BlockedUsersParsingTest.kt new file mode 100644 index 00000000000..2dae42247fc --- /dev/null +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/BlockedUsersParsingTest.kt @@ -0,0 +1,86 @@ +/* + * 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.parser2 + +import io.getstream.chat.android.client.api2.mapping.DomainMapping +import io.getstream.chat.android.models.NoOpChannelTransformer +import io.getstream.chat.android.models.NoOpMessageTransformer +import io.getstream.chat.android.models.NoOpUserTransformer +import io.getstream.chat.android.models.UserBlock +import io.getstream.chat.android.network.models.GetBlockedUsersResponse +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.Date + +internal class BlockedUsersParsingTest { + + private val parser = ParserFactory.createMoshiChatParser() + + private val domainMapping = DomainMapping( + currentUserIdProvider = { "" }, + channelTransformer = NoOpChannelTransformer, + messageTransformer = NoOpMessageTransformer, + userTransformer = NoOpUserTransformer, + ) + + // Each block carries two full UserResponse objects; this locks that the generated UserResponse + // (with its required non-null `language`) parses from the real wire shape. + @Language("JSON") + private val json = + """{ + "duration": "1ms", + "blocks": [ + { + "user_id": "blocker-1", + "blocked_user_id": "blocked-1", + "created_at": "1970-01-01T00:00:01.000Z", + "user": { + "id": "blocker-1", "role": "user", "language": "en", "banned": false, + "online": true, "created_at": "2020-01-01T00:00:00.000Z", + "updated_at": "2020-01-01T00:00:00.000Z", "birthland": "Polis Massa" + }, + "blocked_user": { + "id": "blocked-1", "role": "user", "language": "", "banned": true, + "online": false, "created_at": "2020-01-01T00:00:00.000Z", + "updated_at": "2020-01-01T00:00:00.000Z" + } + } + ] + }""" + + @Test + fun `deserializes the blocked-users wire shape and maps it to UserBlock`() { + val dto = parser.fromJson(json, GetBlockedUsersResponse::class.java) + + assertEquals("1ms", dto.duration) + val block = dto.blocks.single() + assertEquals("blocker-1", block.user.id) + assertEquals("blocked-1", block.blockedUser.id) + // The adapter collects root-level custom data, and `language` is required non-null but arrives empty. + assertEquals(mapOf("birthland" to "Polis Massa"), block.user.custom) + assertEquals("en", block.user.language) + assertEquals("", block.blockedUser.language) + + val blocks = with(domainMapping) { dto.blocks.toDomain() } + + assertEquals( + listOf(UserBlock(blockedBy = "blocker-1", userId = "blocked-1", blockedAt = Date(1000))), + blocks, + ) + } +}