Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import io.getstream.chat.android.client.api.QueryParams
import io.getstream.chat.android.client.api2.UrlQueryPayload
import io.getstream.chat.android.client.api2.model.requests.QueryMembersRequest
import io.getstream.chat.android.client.api2.model.requests.SyncHistoryRequest
import io.getstream.chat.android.client.api2.model.response.QueryMembersResponse
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.call.RetrofitCall
import io.getstream.chat.android.network.models.MembersResponse
import io.getstream.chat.android.network.models.SearchPayload
import io.getstream.chat.android.network.models.WrappedUnreadCountsResponse
import okhttp3.ResponseBody
Expand Down Expand Up @@ -53,7 +53,7 @@ internal interface GeneralApi {
@GET("/members")
fun queryMembers(
@UrlQueryPayload @Query("payload") payload: QueryMembersRequest,
): RetrofitCall<QueryMembersResponse>
): RetrofitCall<MembersResponse>

@GET("/unread")
fun getUnreadCounts(): RetrofitCall<WrappedUnreadCountsResponse>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.DownstreamMemberDto
/**
*
*/
@com.squareup.moshi.JsonClass(generateAdapter = true)
internal data class MembersResponse(
@Json(name = "duration")
internal val duration: String,

@JsonClass(generateAdapter = true)
internal data class QueryMembersResponse(
val members: List<DownstreamMemberDto>,
@Json(name = "members")
internal val members: List<io.getstream.chat.android.network.models.ChannelMemberResponse> = emptyList(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ 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.ChannelMemberResponse
import io.getstream.chat.android.network.models.ChannelResponse
import io.getstream.chat.android.network.models.CreateGuestResponse
import io.getstream.chat.android.network.models.DeviceResponse
Expand Down Expand Up @@ -728,6 +729,20 @@ internal object Mother {
last_reaction_at = lastReactionAt,
)

fun randomChannelMemberResponse(
Comment thread
andremion marked this conversation as resolved.
user: UserResponse = randomUserResponse(),
channelRole: String = randomString(),
): ChannelMemberResponse = ChannelMemberResponse(
user = user,
userId = user.id,
channelRole = channelRole,
createdAt = randomDate(),
updatedAt = randomDate(),
banned = randomBoolean(),
shadowBanned = randomBoolean(),
notificationsMuted = randomBoolean(),
)

fun randomDownstreamMemberDto(
user: DownstreamUserDto = randomDownstreamUserDto(),
createdAt: Date = randomDate(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ import io.getstream.chat.android.client.api2.model.response.QueryChannelsRespons
import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesResponse
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.QueryMembersResponse
import io.getstream.chat.android.client.api2.model.response.QueryPollVotesResponse
import io.getstream.chat.android.client.api2.model.response.QueryPollsResponse
import io.getstream.chat.android.client.api2.model.response.QueryReactionsResponse
Expand Down Expand Up @@ -147,6 +146,7 @@ import io.getstream.chat.android.network.models.ListUserGroupsResponse
import io.getstream.chat.android.network.models.MarkDeliveredRequest
import io.getstream.chat.android.network.models.MarkReadRequest
import io.getstream.chat.android.network.models.MarkUnreadRequest
import io.getstream.chat.android.network.models.MembersResponse
import io.getstream.chat.android.network.models.MessageActionRequest
import io.getstream.chat.android.network.models.MessageRequest
import io.getstream.chat.android.network.models.MuteChannelRequest
Expand Down Expand Up @@ -2378,7 +2378,7 @@ internal class MoshiChatApiTest {

@ParameterizedTest
@MethodSource("io.getstream.chat.android.client.api2.MoshiChatApiTestArguments#queryMembersInput")
fun testQueryMembers(call: RetrofitCall<QueryMembersResponse>, expected: KClass<*>) = runTest {
fun testQueryMembers(call: RetrofitCall<MembersResponse>, expected: KClass<*>) = runTest {
// given
val api = mock<GeneralApi>()
whenever(api.queryMembers(any())).doReturn(call)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import io.getstream.chat.android.client.api2.model.response.QueryChannelsRespons
import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesResponse
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.QueryMembersResponse
import io.getstream.chat.android.client.api2.model.response.QueryPollVotesResponse
import io.getstream.chat.android.client.api2.model.response.QueryPollsResponse
import io.getstream.chat.android.client.api2.model.response.QueryReactionsResponse
Expand Down Expand Up @@ -77,6 +76,7 @@ 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
import io.getstream.chat.android.network.models.MembersResponse
import io.getstream.chat.android.network.models.PollOptionResponse
import io.getstream.chat.android.network.models.RemoveUserGroupMembersResponse
import io.getstream.chat.android.network.models.Response
Expand Down Expand Up @@ -532,10 +532,12 @@ internal object MoshiChatApiTestArguments {
@JvmStatic
fun queryMembersInput() = listOf(
Arguments.of(
RetroSuccess(QueryMembersResponse(listOf(Mother.randomDownstreamMemberDto()))).toRetrofitCall(),
RetroSuccess(
MembersResponse(duration = randomString(), members = listOf(Mother.randomChannelMemberResponse())),
).toRetrofitCall(),
Result.Success::class,
),
Arguments.of(RetroError<QueryMembersResponse>(statusCode = 500).toRetrofitCall(), Result.Failure::class),
Arguments.of(RetroError<MembersResponse>(statusCode = 500).toRetrofitCall(), Result.Failure::class),
)

@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ 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.randomChannelMemberResponse
import io.getstream.chat.android.client.Mother.randomChannelResponse
import io.getstream.chat.android.client.Mother.randomCommandDto
import io.getstream.chat.android.client.Mother.randomConfigDto
Expand Down Expand Up @@ -673,6 +674,54 @@ internal class DomainMappingTest {
assertEquals(expected, member)
}

@Test
fun `ChannelMemberResponse is correctly mapped to Member`() {
val memberResponse = randomChannelMemberResponse()
val sut = Fixture().get()

val member = with(sut) { memberResponse.toDomain() }

val expected = Member(
user = with(sut) { memberResponse.user!!.toDomain() },
createdAt = memberResponse.createdAt,
updatedAt = memberResponse.updatedAt,
isInvited = memberResponse.invited,
inviteAcceptedAt = memberResponse.inviteAcceptedAt,
inviteRejectedAt = memberResponse.inviteRejectedAt,
shadowBanned = memberResponse.shadowBanned,
banned = memberResponse.banned,
channelRole = memberResponse.channelRole,
notificationsMuted = memberResponse.notificationsMuted,
status = memberResponse.status,
banExpires = memberResponse.banExpires,
pinnedAt = memberResponse.pinnedAt,
archivedAt = memberResponse.archivedAt,
extraData = emptyMap(),
)
assertEquals(expected, member)
}

@Test
fun `ChannelMemberResponse without a user is mapped to a Member holding only the user id`() {
val memberResponse = randomChannelMemberResponse().copy(user = null)
val sut = Fixture().get()

val member = with(sut) { memberResponse.toDomain() }

assertEquals(User(id = memberResponse.userId.orEmpty()), member.user)
}

@Test
fun `ChannelMemberResponse custom data is mapped to extraData without its null values`() {
val memberResponse = randomChannelMemberResponse()
.copy(custom = mapOf("customKey" to "customValue", "nullKey" to null))
val sut = Fixture().get()

val member = with(sut) { memberResponse.toDomain() }

assertEquals(mapOf<String, Any>("customKey" to "customValue"), member.extraData)
}

@Test
@Suppress("LongMethod")
fun `DownstreamPollDto is correctly mapped to Poll`() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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.network.models.MembersResponse
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldHaveSize
import org.junit.jupiter.api.Test

internal class ChannelMemberResponseParsingTest {
Comment thread
andremion marked this conversation as resolved.
private val parser = ParserFactory.createMoshiChatParser()

@Test
fun `Parse the members of a MembersResponse`() {
val members = parser.fromJson(MEMBERS_JSON, MembersResponse::class.java).members

members shouldHaveSize 1
members.first().channelRole shouldBeEqualTo "channel_member"
members.first().userId shouldBeEqualTo "leandro"
members.first().custom["memberProbe"] shouldBeEqualTo "sentinel"
}

@Test
fun `Collect the root-level custom fields of a nested user`() {
val member = parser.fromJson(MEMBERS_JSON, MembersResponse::class.java).members.first()

member.user?.custom shouldBeEqualTo mapOf("birthland" to "Polis Massa")
}

companion object {
private const val MEMBERS_JSON =
"""{
"duration": "7ms",
"members": [
{
"user_id": "leandro",
"channel_role": "channel_member",
"created_at": "2021-10-22T00:07:24.000Z",
"updated_at": "2026-05-28T07:40:11.000Z",
"banned": false,
"shadow_banned": false,
"notifications_muted": false,
"role": "member",
"is_moderator": true,
"deleted_at": "2026-08-14T12:00:00.000Z",
"deleted_messages": ["m1"],
"memberProbe": "sentinel",
"user": {
"id": "leandro",
"role": "user",
"language": "pt",
"banned": false,
"online": true,
"created_at": "2021-07-20T14:17:07.000Z",
"updated_at": "2026-07-31T11:38:42.000Z",
"birthland": "Polis Massa"
}
}
]
}"""
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,16 @@ internal class GeneratedExtraDataParityTest {
ChannelMemberResponse::class.java,
)

member.custom.keys shouldContainAll setOf(
"sentinel", "user_id", "role", "is_moderator", "deleted_messages", "deleted_at",
// Asserted as an exact map so a key silently dropping out of the keep set fails here.
member.custom shouldBeEqualTo mapOf(
"user_id" to "u1",
"role" to "member",
"is_moderator" to true,
"deleted_messages" to emptyList<String>(),
"deleted_at" to "2026-08-14T12:00:00.000Z",
"sentinel" to "keep-me",
)
member.custom["role"] shouldBeEqualTo "member"
member.custom["is_moderator"] shouldBeEqualTo true
member.custom["deleted_at"] shouldBeEqualTo "2026-08-14T12:00:00.000Z"
// Still parsed into its own field, not only kept in the map.
member.role shouldBeEqualTo "member"
}
}
Loading