From 36e2be451572b75926be8a9ef243e17f72bb122f Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:10:34 +0200 Subject: [PATCH] Parse the invite and channel notification events with the generated event models --- .../client/api2/mapping/EventMapping.kt | 207 ++++++++++-------- .../client/api2/model/dto/EventDtos.kt | 61 ------ .../client/parser2/adapters/EventAdapter.kt | 27 ++- .../models/NotificationChannelDeletedEvent.kt | 90 ++++++++ .../NotificationChannelTruncatedEvent.kt | 96 ++++++++ .../models/NotificationInviteAcceptedEvent.kt | 84 +++++++ .../models/NotificationInviteRejectedEvent.kt | 84 +++++++ .../models/NotificationInvitedEvent.kt | 84 +++++++ .../android/client/EventChatJsonProvider.kt | 19 +- .../client/api2/mapping/DomainMappingTest.kt | 61 ++++++ .../api2/mapping/EventMappingTestArguments.kt | 169 +++++++------- .../android/client/parser/EventArguments.kt | 14 +- .../GeneratedNotificationEventParsingTest.kt | 94 ++++++++ 13 files changed, 822 insertions(+), 268 deletions(-) create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationChannelDeletedEvent.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationChannelTruncatedEvent.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInviteAcceptedEvent.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInviteRejectedEvent.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInvitedEvent.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/GeneratedNotificationEventParsingTest.kt diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/EventMapping.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/EventMapping.kt index 9d335cf6b4f..64a2dc1f862 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/EventMapping.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/EventMapping.kt @@ -44,12 +44,7 @@ import io.getstream.chat.android.client.api2.model.dto.MessageReadEventDto import io.getstream.chat.android.client.api2.model.dto.MessageUpdatedEventDto import io.getstream.chat.android.client.api2.model.dto.NewMessageEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationAddedToChannelEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationChannelDeletedEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationChannelMutesUpdatedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationChannelTruncatedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationInviteAcceptedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationInviteRejectedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationInvitedEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationMarkReadEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationMarkUnreadEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationMutesUpdatedEventDto @@ -140,6 +135,11 @@ import io.getstream.chat.android.network.models.WSEvent import io.getstream.chat.android.network.models.MemberAddedEvent as GeneratedMemberAddedEvent import io.getstream.chat.android.network.models.MemberRemovedEvent as GeneratedMemberRemovedEvent import io.getstream.chat.android.network.models.MemberUpdatedEvent as GeneratedMemberUpdatedEvent +import io.getstream.chat.android.network.models.NotificationChannelDeletedEvent as GeneratedNotificationChannelDeletedEvent +import io.getstream.chat.android.network.models.NotificationChannelTruncatedEvent as GeneratedNotificationChannelTruncatedEvent +import io.getstream.chat.android.network.models.NotificationInviteAcceptedEvent as GeneratedNotificationInviteAcceptedEvent +import io.getstream.chat.android.network.models.NotificationInviteRejectedEvent as GeneratedNotificationInviteRejectedEvent +import io.getstream.chat.android.network.models.NotificationInvitedEvent as GeneratedNotificationInvitedEvent import io.getstream.chat.android.network.models.NotificationNewMessageEvent as GeneratedNotificationNewMessageEvent import io.getstream.chat.android.network.models.PollClosedEvent as GeneratedPollClosedEvent import io.getstream.chat.android.network.models.PollDeletedEvent as GeneratedPollDeletedEvent @@ -194,12 +194,7 @@ internal class EventMapping( is MessageReadEventDto -> toDomain() is MessageUpdatedEventDto -> toDomain() is NotificationAddedToChannelEventDto -> toDomain() - is NotificationChannelDeletedEventDto -> toDomain() is NotificationChannelMutesUpdatedEventDto -> toDomain() - is NotificationChannelTruncatedEventDto -> toDomain() - is NotificationInviteAcceptedEventDto -> toDomain() - is NotificationInviteRejectedEventDto -> toDomain() - is NotificationInvitedEventDto -> toDomain() is NotificationMarkReadEventDto -> toDomain() is NotificationMarkUnreadEventDto -> toDomain() is NotificationThreadMessageNewEventDto -> toDomain() @@ -446,24 +441,6 @@ internal class EventMapping( ) } - /** - * Transforms [NotificationChannelDeletedEventDto] to [NotificationChannelDeletedEvent]. - */ - private fun NotificationChannelDeletedEventDto.toDomain(): NotificationChannelDeletedEvent = with(domainMapping) { - NotificationChannelDeletedEvent( - type = type, - createdAt = created_at.date, - rawCreatedAt = created_at.rawDate, - cid = cid, - channelType = channel_type, - channelId = channel_id, - channel = channel.toDomain(), - totalUnreadCount = total_unread_count, - unreadChannels = unread_channels, - groupedUnreadChannels = grouped_unread_channels, - ) - } - /** * Transforms [NotificationChannelMutesUpdatedEventDto] to [NotificationChannelMutesUpdatedEvent]. */ @@ -477,75 +454,6 @@ internal class EventMapping( ) } - /** - * Transforms [NotificationChannelTruncatedEventDto] to [NotificationChannelTruncatedEvent]. - */ - private fun NotificationChannelTruncatedEventDto.toDomain(): NotificationChannelTruncatedEvent = - with(domainMapping) { - NotificationChannelTruncatedEvent( - type = type, - createdAt = created_at.date, - rawCreatedAt = created_at.rawDate, - cid = cid, - channelType = channel_type, - channelId = channel_id, - channel = channel.toDomain(), - totalUnreadCount = total_unread_count, - unreadChannels = unread_channels, - groupedUnreadChannels = grouped_unread_channels, - ) - } - - /** - * Transforms [NotificationInviteAcceptedEventDto] to [NotificationInviteAcceptedEvent]. - */ - private fun NotificationInviteAcceptedEventDto.toDomain(): NotificationInviteAcceptedEvent = with(domainMapping) { - NotificationInviteAcceptedEvent( - type = type, - createdAt = created_at.date, - rawCreatedAt = created_at.rawDate, - cid = cid, - channelType = channel_type, - channelId = channel_id, - user = user.toDomain(), - member = member.toDomain(), - channel = channel.toDomain(), - ) - } - - /** - * Transforms [NotificationInviteRejectedEventDto] to [NotificationInviteRejectedEvent]. - */ - private fun NotificationInviteRejectedEventDto.toDomain(): NotificationInviteRejectedEvent = with(domainMapping) { - NotificationInviteRejectedEvent( - type = type, - createdAt = created_at.date, - rawCreatedAt = created_at.rawDate, - cid = cid, - channelType = channel_type, - channelId = channel_id, - user = user.toDomain(), - member = member.toDomain(), - channel = channel.toDomain(), - ) - } - - /** - * Transforms [NotificationInvitedEventDto] to [NotificationInvitedEvent]. - */ - private fun NotificationInvitedEventDto.toDomain(): NotificationInvitedEvent = with(domainMapping) { - NotificationInvitedEvent( - type = type, - createdAt = created_at.date, - rawCreatedAt = created_at.rawDate, - cid = cid, - channelType = channel_type, - channelId = channel_id, - user = user.toDomain(), - member = member.toDomain(), - ) - } - /** * Transforms [NotificationMarkReadEventDto] to [NotificationMarkReadEvent]. */ @@ -781,6 +689,11 @@ internal class EventMapping( is GeneratedMemberUpdatedEvent -> toDomain() is GeneratedTypingStartEvent -> toDomain() is GeneratedTypingStopEvent -> toDomain() + is GeneratedNotificationInvitedEvent -> toDomain() + is GeneratedNotificationInviteAcceptedEvent -> toDomain() + is GeneratedNotificationInviteRejectedEvent -> toDomain() + is GeneratedNotificationChannelDeletedEvent -> toDomain() + is GeneratedNotificationChannelTruncatedEvent -> toDomain() is GeneratedPollClosedEvent -> toDomain() is GeneratedPollDeletedEvent -> toDomain() is GeneratedPollUpdatedEvent -> toDomain() @@ -993,6 +906,106 @@ internal class EventMapping( ) } + /** + * Transforms the generated [GeneratedNotificationInvitedEvent] to [NotificationInvitedEvent]. + */ + private fun GeneratedNotificationInvitedEvent.toDomain(): NotificationInvitedEvent = with(domainMapping) { + val cid = requireNotNull(cid) + val (channelType, channelId) = cid.cidToTypeAndId() + return NotificationInvitedEvent( + type = type, + createdAt = createdAt.date, + rawCreatedAt = createdAt.raw, + cid = cid, + channelType = channelType, + channelId = channelId, + user = requireNotNull(user).toDomain(), + member = member.toDomain(), + ) + } + + /** + * Transforms the generated [GeneratedNotificationInviteAcceptedEvent] to [NotificationInviteAcceptedEvent]. + */ + private fun GeneratedNotificationInviteAcceptedEvent.toDomain(): NotificationInviteAcceptedEvent = + with(domainMapping) { + val cid = requireNotNull(cid) + val (channelType, channelId) = cid.cidToTypeAndId() + return NotificationInviteAcceptedEvent( + type = type, + createdAt = createdAt.date, + rawCreatedAt = createdAt.raw, + cid = cid, + channelType = channelType, + channelId = channelId, + user = requireNotNull(user).toDomain(), + member = member.toDomain(), + channel = channel.toDomain(), + ) + } + + /** + * Transforms the generated [GeneratedNotificationInviteRejectedEvent] to [NotificationInviteRejectedEvent]. + */ + private fun GeneratedNotificationInviteRejectedEvent.toDomain(): NotificationInviteRejectedEvent = + with(domainMapping) { + val cid = requireNotNull(cid) + val (channelType, channelId) = cid.cidToTypeAndId() + return NotificationInviteRejectedEvent( + type = type, + createdAt = createdAt.date, + rawCreatedAt = createdAt.raw, + cid = cid, + channelType = channelType, + channelId = channelId, + user = requireNotNull(user).toDomain(), + member = member.toDomain(), + channel = channel.toDomain(), + ) + } + + /** + * Transforms the generated [GeneratedNotificationChannelDeletedEvent] to [NotificationChannelDeletedEvent]. + */ + private fun GeneratedNotificationChannelDeletedEvent.toDomain(): NotificationChannelDeletedEvent = + with(domainMapping) { + val cid = requireNotNull(cid) + val (channelType, channelId) = cid.cidToTypeAndId() + return NotificationChannelDeletedEvent( + type = type, + createdAt = createdAt.date, + rawCreatedAt = createdAt.raw, + cid = cid, + channelType = channelType, + channelId = channelId, + channel = channel.toDomain(), + totalUnreadCount = totalUnreadCount ?: 0, + unreadChannels = unreadChannels ?: 0, + groupedUnreadChannels = groupedUnreadChannels, + ) + } + + /** + * Transforms the generated [GeneratedNotificationChannelTruncatedEvent] to [NotificationChannelTruncatedEvent]. + */ + private fun GeneratedNotificationChannelTruncatedEvent.toDomain(): NotificationChannelTruncatedEvent = + with(domainMapping) { + val cid = requireNotNull(cid) + val (channelType, channelId) = cid.cidToTypeAndId() + return NotificationChannelTruncatedEvent( + type = type, + createdAt = createdAt.date, + rawCreatedAt = createdAt.raw, + cid = cid, + channelType = channelType, + channelId = channelId, + channel = channel.toDomain(), + totalUnreadCount = totalUnreadCount ?: 0, + unreadChannels = unreadChannels ?: 0, + groupedUnreadChannels = groupedUnreadChannels, + ) + } + /** * Transforms the generated [GeneratedPollClosedEvent] to [PollClosedEvent]. */ 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 fefcd2511ca..e6b0b729d29 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 @@ -202,19 +202,6 @@ internal data class NotificationAddedToChannelEventDto( val unread_channels: Int = 0, ) : ChatEventDto() -@JsonClass(generateAdapter = true) -internal data class NotificationChannelDeletedEventDto( - val type: String, - val created_at: ExactDate, - val cid: String, - val channel_type: String, - val channel_id: String, - val channel: DownstreamChannelDto, - val total_unread_count: Int = 0, - val unread_channels: Int = 0, - val grouped_unread_channels: Map? = null, -) : ChatEventDto() - @JsonClass(generateAdapter = true) internal data class NotificationChannelMutesUpdatedEventDto( val type: String, @@ -222,54 +209,6 @@ internal data class NotificationChannelMutesUpdatedEventDto( val me: OwnUserResponse, ) : ChatEventDto() -@JsonClass(generateAdapter = true) -internal data class NotificationChannelTruncatedEventDto( - val type: String, - val created_at: ExactDate, - val cid: String, - val channel_type: String, - val channel_id: String, - val channel: DownstreamChannelDto, - val total_unread_count: Int = 0, - val unread_channels: Int = 0, - val grouped_unread_channels: Map? = null, -) : ChatEventDto() - -@JsonClass(generateAdapter = true) -internal data class NotificationInviteAcceptedEventDto( - val type: String, - val created_at: ExactDate, - val cid: String, - val channel_type: String, - val channel_id: String, - val user: DownstreamUserDto, - val member: ChannelMemberResponse, - val channel: DownstreamChannelDto, -) : ChatEventDto() - -@JsonClass(generateAdapter = true) -internal data class NotificationInviteRejectedEventDto( - val type: String, - val created_at: ExactDate, - val cid: String, - val channel_type: String, - val channel_id: String, - val user: DownstreamUserDto, - val member: ChannelMemberResponse, - val channel: DownstreamChannelDto, -) : ChatEventDto() - -@JsonClass(generateAdapter = true) -internal data class NotificationInvitedEventDto( - val type: String, - val created_at: ExactDate, - val cid: String, - val channel_type: String, - val channel_id: String, - val user: DownstreamUserDto, - val member: ChannelMemberResponse, -) : ChatEventDto() - @JsonClass(generateAdapter = true) internal data class NotificationMarkReadEventDto( val type: String, diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/adapters/EventAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/adapters/EventAdapter.kt index 8ec0850bd90..3ec1b44e6ea 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/adapters/EventAdapter.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/adapters/EventAdapter.kt @@ -47,12 +47,7 @@ import io.getstream.chat.android.client.api2.model.dto.MessageReadEventDto import io.getstream.chat.android.client.api2.model.dto.MessageUpdatedEventDto import io.getstream.chat.android.client.api2.model.dto.NewMessageEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationAddedToChannelEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationChannelDeletedEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationChannelMutesUpdatedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationChannelTruncatedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationInviteAcceptedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationInviteRejectedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationInvitedEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationMarkReadEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationMarkUnreadEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationMutesUpdatedEventDto @@ -69,6 +64,11 @@ import io.getstream.chat.android.models.EventType import io.getstream.chat.android.network.models.MemberAddedEvent import io.getstream.chat.android.network.models.MemberRemovedEvent import io.getstream.chat.android.network.models.MemberUpdatedEvent +import io.getstream.chat.android.network.models.NotificationChannelDeletedEvent +import io.getstream.chat.android.network.models.NotificationChannelTruncatedEvent +import io.getstream.chat.android.network.models.NotificationInviteAcceptedEvent +import io.getstream.chat.android.network.models.NotificationInviteRejectedEvent +import io.getstream.chat.android.network.models.NotificationInvitedEvent import io.getstream.chat.android.network.models.NotificationNewMessageEvent import io.getstream.chat.android.network.models.PollClosedEvent import io.getstream.chat.android.network.models.PollDeletedEvent @@ -151,17 +151,24 @@ internal class EventDtoAdapter( private val threadUpdatedEventAdapter = generatedEventAdapter { mapOf("cid" to cid, "channel_type" to channelType, "channel_id" to channelId, "thread" to thread) } - private val notificationInvitedEventAdapter = moshi.adapter(NotificationInvitedEventDto::class.java) - private val notificationInviteAcceptedEventAdapter = moshi.adapter(NotificationInviteAcceptedEventDto::class.java) - private val notificationInviteRejectedEventAdapter = moshi.adapter(NotificationInviteRejectedEventDto::class.java) + private val notificationInvitedEventAdapter = generatedEventAdapter { + mapOf("cid" to cid, "user" to user) + } + private val notificationInviteAcceptedEventAdapter = generatedEventAdapter { + mapOf("cid" to cid, "user" to user) + } + private val notificationInviteRejectedEventAdapter = generatedEventAdapter { + mapOf("cid" to cid, "user" to user) + } private val notificationRemovedFromChannelEventAdapter = moshi.adapter(NotificationRemovedFromChannelEventDto::class.java) private val notificationMutesUpdatedEventAdapter = moshi.adapter(NotificationMutesUpdatedEventDto::class.java) private val notificationChannelMutesUpdatedEventAdapter = moshi.adapter(NotificationChannelMutesUpdatedEventDto::class.java) - private val notificationChannelDeletedEventAdapter = moshi.adapter(NotificationChannelDeletedEventDto::class.java) + private val notificationChannelDeletedEventAdapter = + generatedEventAdapter { mapOf("cid" to cid) } private val notificationChannelTruncatedEventAdapter = - moshi.adapter(NotificationChannelTruncatedEventDto::class.java) + generatedEventAdapter { mapOf("cid" to cid) } private val userPresenceChangedEventAdapter = generatedEventAdapter { emptyMap() } private val userUpdatedEventAdapter = moshi.adapter(UserUpdatedEventDto::class.java) private val userDeletedEventAdapter = generatedEventAdapter { emptyMap() } diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationChannelDeletedEvent.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationChannelDeletedEvent.kt new file mode 100644 index 00000000000..b3d9485268e --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationChannelDeletedEvent.kt @@ -0,0 +1,90 @@ +/* + * 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 + +/** + * Emitted when a channel is successfully deleted. + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class NotificationChannelDeletedEvent( + @Json(name = "created_at") + internal val createdAt: io.getstream.chat.android.network.infrastructure.ExactDate, + + @Json(name = "channel") + internal val channel: io.getstream.chat.android.network.models.ChannelResponse, + + @Json(name = "custom") + internal val custom: Map = emptyMap(), + + @Json(name = "type") + internal val type: String = "notification.channel_deleted", + + @Json(name = "channel_id") + internal val channelId: String? = null, + + @Json(name = "channel_member_count") + internal val channelMemberCount: Int? = null, + + @Json(name = "channel_message_count") + internal val channelMessageCount: Int? = null, + + @Json(name = "channel_type") + internal val channelType: String? = null, + + @Json(name = "cid") + internal val cid: String? = null, + + @Json(name = "received_at") + internal val receivedAt: java.util.Date? = null, + + @Json(name = "team") + internal val team: String? = null, + + @Json(name = "total_unread_count") + internal val totalUnreadCount: Int? = null, + + @Json(name = "unread_channels") + internal val unreadChannels: Int? = null, + + @Json(name = "unread_count") + internal val unreadCount: Int? = null, + + @Json(name = "channel_custom") + internal val channelCustom: Map? = null, + + @Json(name = "grouped_unread_channels") + internal val groupedUnreadChannels: Map? = null, +) : + io.getstream.chat.android.network.models.WSClientEvent, io.getstream.chat.android.network.models.WSEvent { + + override fun getWSClientEventType(): String { + return type + } + + override fun getWSEventType(): String { + return type + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationChannelTruncatedEvent.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationChannelTruncatedEvent.kt new file mode 100644 index 00000000000..47f4abdf76a --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationChannelTruncatedEvent.kt @@ -0,0 +1,96 @@ +/* + * 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 + +/** + * Emitted when a channel is successfully truncated. + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class NotificationChannelTruncatedEvent( + @Json(name = "created_at") + internal val createdAt: io.getstream.chat.android.network.infrastructure.ExactDate, + + @Json(name = "channel") + internal val channel: io.getstream.chat.android.network.models.ChannelResponse, + + @Json(name = "custom") + internal val custom: Map = emptyMap(), + + @Json(name = "type") + internal val type: String = "notification.channel_truncated", + + @Json(name = "channel_id") + internal val channelId: String? = null, + + @Json(name = "channel_member_count") + internal val channelMemberCount: Int? = null, + + @Json(name = "channel_message_count") + internal val channelMessageCount: Int? = null, + + @Json(name = "channel_type") + internal val channelType: String? = null, + + @Json(name = "cid") + internal val cid: String? = null, + + @Json(name = "message_id") + internal val messageId: String? = null, + + @Json(name = "received_at") + internal val receivedAt: java.util.Date? = null, + + @Json(name = "team") + internal val team: String? = null, + + @Json(name = "total_unread_count") + internal val totalUnreadCount: Int? = null, + + @Json(name = "unread_channels") + internal val unreadChannels: Int? = null, + + @Json(name = "unread_count") + internal val unreadCount: Int? = null, + + @Json(name = "channel_custom") + internal val channelCustom: Map? = null, + + @Json(name = "grouped_unread_channels") + internal val groupedUnreadChannels: Map? = null, + + @Json(name = "message") + internal val message: io.getstream.chat.android.network.models.MessageResponse? = null, +) : + io.getstream.chat.android.network.models.WSClientEvent, io.getstream.chat.android.network.models.WSEvent { + + override fun getWSClientEventType(): String { + return type + } + + override fun getWSEventType(): String { + return type + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInviteAcceptedEvent.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInviteAcceptedEvent.kt new file mode 100644 index 00000000000..f2750d9f904 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInviteAcceptedEvent.kt @@ -0,0 +1,84 @@ +/* + * 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 + +/** + * Sent to a user when they accept an invite to a channel (as a personal notification to update their channel list). + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class NotificationInviteAcceptedEvent( + @Json(name = "created_at") + internal val createdAt: io.getstream.chat.android.network.infrastructure.ExactDate, + + @Json(name = "channel") + internal val channel: io.getstream.chat.android.network.models.ChannelResponse, + + @Json(name = "custom") + internal val custom: Map = emptyMap(), + + @Json(name = "member") + internal val member: io.getstream.chat.android.network.models.ChannelMemberResponse, + + @Json(name = "type") + internal val type: String = "notification.invite_accepted", + + @Json(name = "channel_id") + internal val channelId: String? = null, + + @Json(name = "channel_member_count") + internal val channelMemberCount: Int? = null, + + @Json(name = "channel_message_count") + internal val channelMessageCount: Int? = null, + + @Json(name = "channel_type") + internal val channelType: String? = null, + + @Json(name = "cid") + internal val cid: String? = null, + + @Json(name = "received_at") + internal val receivedAt: java.util.Date? = null, + + @Json(name = "team") + internal val team: String? = null, + + @Json(name = "channel_custom") + internal val channelCustom: Map? = null, + + @Json(name = "user") + internal val user: io.getstream.chat.android.network.models.UserResponseCommonFields? = null, +) : + io.getstream.chat.android.network.models.WSClientEvent, io.getstream.chat.android.network.models.WSEvent { + + override fun getWSClientEventType(): String { + return type + } + + override fun getWSEventType(): String { + return type + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInviteRejectedEvent.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInviteRejectedEvent.kt new file mode 100644 index 00000000000..57d21781bd8 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInviteRejectedEvent.kt @@ -0,0 +1,84 @@ +/* + * 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 + +/** + * Sent to a user when they reject an invite to a channel (as a personal notification to update their channel list). + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class NotificationInviteRejectedEvent( + @Json(name = "created_at") + internal val createdAt: io.getstream.chat.android.network.infrastructure.ExactDate, + + @Json(name = "channel") + internal val channel: io.getstream.chat.android.network.models.ChannelResponse, + + @Json(name = "custom") + internal val custom: Map = emptyMap(), + + @Json(name = "member") + internal val member: io.getstream.chat.android.network.models.ChannelMemberResponse, + + @Json(name = "type") + internal val type: String = "notification.invite_rejected", + + @Json(name = "channel_id") + internal val channelId: String? = null, + + @Json(name = "channel_member_count") + internal val channelMemberCount: Int? = null, + + @Json(name = "channel_message_count") + internal val channelMessageCount: Int? = null, + + @Json(name = "channel_type") + internal val channelType: String? = null, + + @Json(name = "cid") + internal val cid: String? = null, + + @Json(name = "received_at") + internal val receivedAt: java.util.Date? = null, + + @Json(name = "team") + internal val team: String? = null, + + @Json(name = "channel_custom") + internal val channelCustom: Map? = null, + + @Json(name = "user") + internal val user: io.getstream.chat.android.network.models.UserResponseCommonFields? = null, +) : + io.getstream.chat.android.network.models.WSClientEvent, io.getstream.chat.android.network.models.WSEvent { + + override fun getWSClientEventType(): String { + return type + } + + override fun getWSEventType(): String { + return type + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInvitedEvent.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInvitedEvent.kt new file mode 100644 index 00000000000..b840a406160 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/network/models/NotificationInvitedEvent.kt @@ -0,0 +1,84 @@ +/* + * 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 + +/** + * Emitted when a user is invited to a channel. + */ +@com.squareup.moshi.JsonClass(generateAdapter = true) +internal data class NotificationInvitedEvent( + @Json(name = "created_at") + internal val createdAt: io.getstream.chat.android.network.infrastructure.ExactDate, + + @Json(name = "channel") + internal val channel: io.getstream.chat.android.network.models.ChannelResponse, + + @Json(name = "custom") + internal val custom: Map = emptyMap(), + + @Json(name = "member") + internal val member: io.getstream.chat.android.network.models.ChannelMemberResponse, + + @Json(name = "type") + internal val type: String = "notification.invited", + + @Json(name = "channel_id") + internal val channelId: String? = null, + + @Json(name = "channel_member_count") + internal val channelMemberCount: Int? = null, + + @Json(name = "channel_message_count") + internal val channelMessageCount: Int? = null, + + @Json(name = "channel_type") + internal val channelType: String? = null, + + @Json(name = "cid") + internal val cid: String? = null, + + @Json(name = "received_at") + internal val receivedAt: java.util.Date? = null, + + @Json(name = "team") + internal val team: String? = null, + + @Json(name = "channel_custom") + internal val channelCustom: Map? = null, + + @Json(name = "user") + internal val user: io.getstream.chat.android.network.models.UserResponseCommonFields? = null, +) : + io.getstream.chat.android.network.models.WSClientEvent, io.getstream.chat.android.network.models.WSEvent { + + override fun getWSClientEventType(): String { + return type + } + + override fun getWSEventType(): String { + return type + } +} diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/EventChatJsonProvider.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/EventChatJsonProvider.kt index 53d6fcd0e6b..068eb4590c7 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/EventChatJsonProvider.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/EventChatJsonProvider.kt @@ -233,8 +233,8 @@ internal fun createNotificationChannelDeletedEventStringJson() = "channel_type": "channelType", "channel_id": "channelId", "cid": "channelType:channelId", - "user": ${createUserJsonString()}, - "channel": ${createChannelJsonString()}, + "user": ${createCommonFieldsUserJsonString()}, + "channel": ${createGeneratedChannelJsonString()}, "grouped_unread_channels": {"direct": 2, "support": 5}, "channel_last_message_at": "2020-06-29T06:14:28.000Z" """.trimIndent(), @@ -247,8 +247,8 @@ internal fun createNotificationChannelTruncatedEventStringJson() = "channel_type": "channelType", "channel_id": "channelId", "cid": "channelType:channelId", - "user": ${createUserJsonString()}, - "channel": ${createChannelJsonString()}, + "user": ${createCommonFieldsUserJsonString()}, + "channel": ${createGeneratedChannelJsonString()}, "channel_last_message_at": "2020-06-29T06:14:28.000Z" """.trimIndent(), ) @@ -260,9 +260,9 @@ internal fun createNotificationInviteAcceptedEventStringJson() = "channel_type": "channelType", "channel_id": "channelId", "cid": "channelType:channelId", - "user": ${createUserJsonString()}, + "user": ${createCommonFieldsUserJsonString()}, "member": ${createMemberJsonString()}, - "channel": ${createChannelJsonString()}, + "channel": ${createGeneratedChannelJsonString()}, "channel_last_message_at": "2020-06-29T06:14:28.000Z" """.trimIndent(), ) @@ -274,9 +274,9 @@ internal fun createNotificationInviteRejectedEventStringJson() = "channel_type": "channelType", "channel_id": "channelId", "cid": "channelType:channelId", - "user": ${createUserJsonString()}, + "user": ${createCommonFieldsUserJsonString()}, "member": ${createMemberJsonString()}, - "channel": ${createChannelJsonString()}, + "channel": ${createGeneratedChannelJsonString()}, "channel_last_message_at": "2020-06-29T06:14:28.000Z" """.trimIndent(), ) @@ -288,8 +288,9 @@ internal fun createNotificationInvitedEventStringJson() = "channel_type": "channelType", "channel_id": "channelId", "cid": "channelType:channelId", - "user": ${createUserJsonString()}, + "user": ${createCommonFieldsUserJsonString()}, "member": ${createMemberJsonString()}, + "channel": ${createGeneratedChannelJsonString()}, "channel_last_message_at": "2020-06-29T06:14:28.000Z" """.trimIndent(), ) 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 f40d61e9f8e..8a2ddb624cd 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 @@ -124,7 +124,9 @@ import io.getstream.chat.android.models.querysort.QuerySortByField.Companion.asc import io.getstream.chat.android.models.querysort.QuerySortByField.Companion.descByName import io.getstream.chat.android.models.querysort.QuerySorter import io.getstream.chat.android.network.models.ChannelConfigWithInfo +import io.getstream.chat.android.network.models.ChannelOwnCapability import io.getstream.chat.android.network.models.ChannelPushPreferencesResponse +import io.getstream.chat.android.network.models.ChannelResponse import io.getstream.chat.android.network.models.ChatPreferencesResponse import io.getstream.chat.android.network.models.DeliveryReceiptsResponse import io.getstream.chat.android.network.models.FullUserResponse @@ -766,6 +768,65 @@ internal class DomainMappingTest { assertEquals(with(sut) { channelResponse.config?.toDomain() }, channel.config) } + @Test + fun `ChannelResponse maps every field onto the Channel`() { + val creator = Mother.randomUserResponse() + val member = Mother.randomChannelMemberResponse() + val response = ChannelResponse( + cid = "messaging:general", + id = "general", + type = "messaging", + createdAt = Date(1_000), + updatedAt = Date(2_000), + deletedAt = Date(3_000), + truncatedAt = Date(4_000), + lastMessageAt = Date(5_000), + hideMessagesBefore = Date(6_000), + disabled = true, + frozen = true, + blocked = true, + hidden = true, + memberCount = 7, + messageCount = 8, + cooldown = 9, + team = "team-a", + filterTags = listOf("tag-a", "tag-b"), + members = listOf(member), + ownCapabilities = listOf(ChannelOwnCapability.SendMessage), + config = ChannelDtoTestData.channelResponse.config, + createdBy = creator, + custom = mapOf("name" to "General", "image" to "https://example.com/g.png", "color" to "teal"), + ) + val sut = Fixture().get() + + val channel = with(sut) { response.toDomain() } + + assertEquals("general", channel.id) + assertEquals("messaging", channel.type) + assertEquals("General", channel.name) + assertEquals("https://example.com/g.png", channel.image) + assertEquals(Date(1_000), channel.createdAt) + assertEquals(Date(2_000), channel.updatedAt) + assertEquals(Date(3_000), channel.deletedAt) + assertEquals(Date(4_000), channel.truncatedAt) + assertEquals(Date(5_000), channel.lastMessageAt) + assertEquals(Date(6_000), channel.hiddenMessagesBefore) + assertEquals(true, channel.disabled) + assertEquals(true, channel.frozen) + assertEquals(true, channel.blocked) + assertEquals(true, channel.hidden) + assertEquals(7, channel.memberCount) + assertEquals(8, channel.messageCount) + assertEquals(9, channel.cooldown) + assertEquals("team-a", channel.team) + assertEquals(listOf("tag-a", "tag-b"), channel.filterTags) + assertEquals(listOf(member.user!!.id), channel.members.map { it.user.id }) + assertEquals(setOf("send-message"), channel.ownCapabilities) + assertEquals(with(sut) { response.config?.toDomain() }, channel.config) + assertEquals(creator.id, channel.createdBy.id) + assertEquals(mapOf("color" to "teal"), channel.extraData) + } + @Test fun `ChannelResponse maps the channel state to properties and keeps it in extraData`() { val channelResponse = ChannelDtoTestData.channelResponse 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 972a4ed6725..22c5ea3a84d 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 @@ -43,12 +43,7 @@ import io.getstream.chat.android.client.api2.model.dto.MessageReadEventDto import io.getstream.chat.android.client.api2.model.dto.MessageUpdatedEventDto import io.getstream.chat.android.client.api2.model.dto.NewMessageEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationAddedToChannelEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationChannelDeletedEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationChannelMutesUpdatedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationChannelTruncatedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationInviteAcceptedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationInviteRejectedEventDto -import io.getstream.chat.android.client.api2.model.dto.NotificationInvitedEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationMarkReadEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationMarkUnreadEventDto import io.getstream.chat.android.client.api2.model.dto.NotificationMutesUpdatedEventDto @@ -143,6 +138,11 @@ import io.getstream.chat.android.network.infrastructure.ExactDate as GeneratedEx import io.getstream.chat.android.network.models.MemberAddedEvent as GeneratedMemberAddedEvent import io.getstream.chat.android.network.models.MemberRemovedEvent as GeneratedMemberRemovedEvent import io.getstream.chat.android.network.models.MemberUpdatedEvent as GeneratedMemberUpdatedEvent +import io.getstream.chat.android.network.models.NotificationChannelDeletedEvent as GeneratedNotificationChannelDeletedEvent +import io.getstream.chat.android.network.models.NotificationChannelTruncatedEvent as GeneratedNotificationChannelTruncatedEvent +import io.getstream.chat.android.network.models.NotificationInviteAcceptedEvent as GeneratedNotificationInviteAcceptedEvent +import io.getstream.chat.android.network.models.NotificationInviteRejectedEvent as GeneratedNotificationInviteRejectedEvent +import io.getstream.chat.android.network.models.NotificationInvitedEvent as GeneratedNotificationInvitedEvent import io.getstream.chat.android.network.models.NotificationNewMessageEvent as GeneratedNotificationNewMessageEvent import io.getstream.chat.android.network.models.PollClosedEvent as GeneratedPollClosedEvent import io.getstream.chat.android.network.models.PollDeletedEvent as GeneratedPollDeletedEvent @@ -485,64 +485,69 @@ internal object EventMappingTestArguments { member = MEMBER, ) - private val notificationChannelDeletedDto = NotificationChannelDeletedEventDto( + private val notificationChannelDeletedEvent = GeneratedNotificationChannelDeletedEvent( type = EventType.NOTIFICATION_CHANNEL_DELETED, - created_at = EXACT_DATE, + createdAt = GENERATED_EXACT_DATE, cid = CID, - channel_type = CHANNEL_TYPE, - channel_id = CHANNEL_ID, - channel = CHANNEL, - grouped_unread_channels = GROUPED_UNREAD_CHANNELS, + channel = GENERATED_CHANNEL, + totalUnreadCount = TOTAL_UNREAD_COUNT, + unreadChannels = UNREAD_CHANNELS, + groupedUnreadChannels = GROUPED_UNREAD_CHANNELS, ) + private val notificationChannelDeletedDto = GeneratedEventDto(notificationChannelDeletedEvent) + private val notificationChannelMutesUpdatesDto = NotificationChannelMutesUpdatedEventDto( type = EventType.NOTIFICATION_CHANNEL_MUTES_UPDATED, created_at = EXACT_DATE, me = OWN_USER, ) - private val notificationChannelTruncatedDto = NotificationChannelTruncatedEventDto( + private val notificationChannelTruncatedEvent = GeneratedNotificationChannelTruncatedEvent( type = EventType.NOTIFICATION_CHANNEL_TRUNCATED, - created_at = EXACT_DATE, + createdAt = GENERATED_EXACT_DATE, cid = CID, - channel_type = CHANNEL_TYPE, - channel_id = CHANNEL_ID, - channel = CHANNEL, - grouped_unread_channels = GROUPED_UNREAD_CHANNELS, + channel = GENERATED_CHANNEL, + totalUnreadCount = TOTAL_UNREAD_COUNT, + unreadChannels = UNREAD_CHANNELS, + groupedUnreadChannels = GROUPED_UNREAD_CHANNELS, ) - private val notificationInviteAcceptedDto = NotificationInviteAcceptedEventDto( + private val notificationChannelTruncatedDto = GeneratedEventDto(notificationChannelTruncatedEvent) + + private val notificationInviteAcceptedEvent = GeneratedNotificationInviteAcceptedEvent( type = EventType.NOTIFICATION_INVITE_ACCEPTED, - created_at = EXACT_DATE, + createdAt = GENERATED_EXACT_DATE, cid = CID, - channel_type = CHANNEL_TYPE, - channel_id = CHANNEL_ID, - user = USER, + channel = GENERATED_CHANNEL, + user = COMMON_USER, member = MEMBER, - channel = CHANNEL, ) - private val notificationInviteRejectedDto = NotificationInviteRejectedEventDto( + private val notificationInviteAcceptedDto = GeneratedEventDto(notificationInviteAcceptedEvent) + + private val notificationInviteRejectedEvent = GeneratedNotificationInviteRejectedEvent( type = EventType.NOTIFICATION_INVITE_REJECTED, - created_at = EXACT_DATE, + createdAt = GENERATED_EXACT_DATE, cid = CID, - channel_type = CHANNEL_TYPE, - channel_id = CHANNEL_ID, - user = USER, + channel = GENERATED_CHANNEL, + user = COMMON_USER, member = MEMBER, - channel = CHANNEL, ) - private val notificationInvitedDto = NotificationInvitedEventDto( + private val notificationInviteRejectedDto = GeneratedEventDto(notificationInviteRejectedEvent) + + private val notificationInvitedEvent = GeneratedNotificationInvitedEvent( type = EventType.NOTIFICATION_INVITED, - created_at = EXACT_DATE, + createdAt = GENERATED_EXACT_DATE, cid = CID, - channel_type = CHANNEL_TYPE, - channel_id = CHANNEL_ID, - user = USER, + channel = GENERATED_CHANNEL, + user = COMMON_USER, member = MEMBER, ) + private val notificationInvitedDto = GeneratedEventDto(notificationInvitedEvent) + private val notificationMarkReadDto = NotificationMarkReadEventDto( type = EventType.NOTIFICATION_MARK_READ, created_at = EXACT_DATE, @@ -1189,16 +1194,16 @@ internal object EventMappingTestArguments { ) private val notificationChannelDeleted = NotificationChannelDeletedEvent( - type = notificationChannelDeletedDto.type, - createdAt = notificationChannelDeletedDto.created_at.date, - rawCreatedAt = notificationChannelDeletedDto.created_at.rawDate, - cid = notificationChannelDeletedDto.cid, - channelType = notificationChannelDeletedDto.channel_type, - channelId = notificationChannelDeletedDto.channel_id, - channel = with(domainMapping) { - notificationChannelDeletedDto.channel.toDomain() - }, - groupedUnreadChannels = notificationChannelDeletedDto.grouped_unread_channels, + type = notificationChannelDeletedEvent.type, + createdAt = notificationChannelDeletedEvent.createdAt.date, + rawCreatedAt = notificationChannelDeletedEvent.createdAt.raw, + cid = CID, + channelType = CHANNEL_TYPE, + channelId = CHANNEL_ID, + channel = with(domainMapping) { GENERATED_CHANNEL.toDomain() }, + totalUnreadCount = TOTAL_UNREAD_COUNT, + unreadChannels = UNREAD_CHANNELS, + groupedUnreadChannels = GROUPED_UNREAD_CHANNELS, ) private val notificationChannelMutesUpdates = NotificationChannelMutesUpdatedEvent( @@ -1209,55 +1214,51 @@ internal object EventMappingTestArguments { ) private val notificationChannelTruncated = NotificationChannelTruncatedEvent( - type = notificationChannelTruncatedDto.type, - createdAt = notificationChannelTruncatedDto.created_at.date, - rawCreatedAt = notificationChannelTruncatedDto.created_at.rawDate, - cid = notificationChannelTruncatedDto.cid, - channelType = notificationChannelTruncatedDto.channel_type, - channelId = notificationChannelTruncatedDto.channel_id, - channel = with(domainMapping) { - notificationChannelTruncatedDto.channel.toDomain() - }, - groupedUnreadChannels = notificationChannelTruncatedDto.grouped_unread_channels, + type = notificationChannelTruncatedEvent.type, + createdAt = notificationChannelTruncatedEvent.createdAt.date, + rawCreatedAt = notificationChannelTruncatedEvent.createdAt.raw, + cid = CID, + channelType = CHANNEL_TYPE, + channelId = CHANNEL_ID, + channel = with(domainMapping) { GENERATED_CHANNEL.toDomain() }, + totalUnreadCount = TOTAL_UNREAD_COUNT, + unreadChannels = UNREAD_CHANNELS, + groupedUnreadChannels = GROUPED_UNREAD_CHANNELS, ) private val notificationInviteAccepted = NotificationInviteAcceptedEvent( - type = notificationInviteAcceptedDto.type, - createdAt = notificationInviteAcceptedDto.created_at.date, - rawCreatedAt = notificationInviteAcceptedDto.created_at.rawDate, - cid = notificationInviteAcceptedDto.cid, - channelType = notificationInviteAcceptedDto.channel_type, - channelId = notificationInviteAcceptedDto.channel_id, - user = with(domainMapping) { notificationInviteAcceptedDto.user.toDomain() }, - member = with(domainMapping) { notificationInviteAcceptedDto.member.toDomain() }, - channel = with(domainMapping) { - notificationInviteAcceptedDto.channel.toDomain() - }, + type = notificationInviteAcceptedEvent.type, + createdAt = notificationInviteAcceptedEvent.createdAt.date, + rawCreatedAt = notificationInviteAcceptedEvent.createdAt.raw, + cid = CID, + channelType = CHANNEL_TYPE, + channelId = CHANNEL_ID, + user = with(domainMapping) { COMMON_USER.toDomain() }, + member = with(domainMapping) { MEMBER.toDomain() }, + channel = with(domainMapping) { GENERATED_CHANNEL.toDomain() }, ) private val notificationInviteRejected = NotificationInviteRejectedEvent( - type = notificationInviteRejectedDto.type, - createdAt = notificationInviteRejectedDto.created_at.date, - rawCreatedAt = notificationInviteRejectedDto.created_at.rawDate, - cid = notificationInviteRejectedDto.cid, - channelType = notificationInviteRejectedDto.channel_type, - channelId = notificationInviteRejectedDto.channel_id, - user = with(domainMapping) { notificationInviteRejectedDto.user.toDomain() }, - member = with(domainMapping) { notificationInviteRejectedDto.member.toDomain() }, - channel = with(domainMapping) { - notificationInviteRejectedDto.channel.toDomain() - }, + type = notificationInviteRejectedEvent.type, + createdAt = notificationInviteRejectedEvent.createdAt.date, + rawCreatedAt = notificationInviteRejectedEvent.createdAt.raw, + cid = CID, + channelType = CHANNEL_TYPE, + channelId = CHANNEL_ID, + user = with(domainMapping) { COMMON_USER.toDomain() }, + member = with(domainMapping) { MEMBER.toDomain() }, + channel = with(domainMapping) { GENERATED_CHANNEL.toDomain() }, ) private val notificationInvited = NotificationInvitedEvent( - type = notificationInvitedDto.type, - createdAt = notificationInvitedDto.created_at.date, - rawCreatedAt = notificationInvitedDto.created_at.rawDate, - cid = notificationInvitedDto.cid, - channelType = notificationInvitedDto.channel_type, - channelId = notificationInvitedDto.channel_id, - user = with(domainMapping) { notificationInvitedDto.user.toDomain() }, - member = with(domainMapping) { notificationInvitedDto.member.toDomain() }, + type = notificationInvitedEvent.type, + createdAt = notificationInvitedEvent.createdAt.date, + rawCreatedAt = notificationInvitedEvent.createdAt.raw, + cid = CID, + channelType = CHANNEL_TYPE, + channelId = CHANNEL_ID, + user = with(domainMapping) { COMMON_USER.toDomain() }, + member = with(domainMapping) { MEMBER.toDomain() }, ) private val notificationMarkRead = NotificationMarkReadEvent( diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser/EventArguments.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser/EventArguments.kt index d7b782348d5..f1ccad52f8d 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser/EventArguments.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser/EventArguments.kt @@ -575,7 +575,7 @@ internal object EventArguments { cid = cid, channelType = channelType, channelId = channelId, - channel = channel, + channel = wireChannel, groupedUnreadChannels = groupedUnreadChannels, ) private val notificationChannelTruncatedEvent = NotificationChannelTruncatedEvent( @@ -585,7 +585,7 @@ internal object EventArguments { cid = cid, channelType = channelType, channelId = channelId, - channel = channel, + channel = wireChannel, ) private val notificationInviteAcceptedEvent = NotificationInviteAcceptedEvent( type = EventType.NOTIFICATION_INVITE_ACCEPTED, @@ -594,9 +594,9 @@ internal object EventArguments { cid = cid, channelType = channelType, channelId = channelId, - user = user, + user = commonFieldsUser, member = member, - channel = channel, + channel = wireChannel, ) private val notificationInviteRejectedEvent = NotificationInviteRejectedEvent( type = EventType.NOTIFICATION_INVITE_REJECTED, @@ -605,9 +605,9 @@ internal object EventArguments { cid = cid, channelType = channelType, channelId = channelId, - user = user, + user = commonFieldsUser, member = member, - channel = channel, + channel = wireChannel, ) private val notificationInvitedEvent = NotificationInvitedEvent( type = EventType.NOTIFICATION_INVITED, @@ -616,7 +616,7 @@ internal object EventArguments { cid = cid, channelType = channelType, channelId = channelId, - user = user, + user = commonFieldsUser, member = member, ) private val notificationMarkReadEvent = NotificationMarkReadEvent( diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/GeneratedNotificationEventParsingTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/GeneratedNotificationEventParsingTest.kt new file mode 100644 index 00000000000..8cbd1787561 --- /dev/null +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/GeneratedNotificationEventParsingTest.kt @@ -0,0 +1,94 @@ +/* + * 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 com.squareup.moshi.JsonDataException +import io.getstream.chat.android.client.createNotificationChannelDeletedEventStringJson +import io.getstream.chat.android.client.createNotificationChannelTruncatedEventStringJson +import io.getstream.chat.android.client.createNotificationInviteAcceptedEventStringJson +import io.getstream.chat.android.client.createNotificationInviteRejectedEventStringJson +import io.getstream.chat.android.client.createNotificationInvitedEventStringJson +import io.getstream.chat.android.client.events.ChatEvent +import io.getstream.chat.android.client.events.NotificationChannelDeletedEvent +import io.getstream.chat.android.client.events.NotificationChannelTruncatedEvent +import io.getstream.chat.android.client.events.NotificationInviteAcceptedEvent +import io.getstream.chat.android.client.events.NotificationInviteRejectedEvent +import io.getstream.chat.android.client.events.NotificationInvitedEvent +import org.amshove.kluent.shouldBeEqualTo +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import kotlin.reflect.KClass + +/** Invite and channel notification events parsed through their generated models. */ +internal class GeneratedNotificationEventParsingTest { + + private val parser = ParserFactory.createMoshiChatParser() + + @ParameterizedTest + @MethodSource("events") + fun `A notification event keeps the created_at string as sent`(json: String, expected: KClass) { + val event = parser.fromJson(json.withNanosecondCreatedAt(), ChatEvent::class.java) + + event::class shouldBeEqualTo expected + event.rawCreatedAt shouldBeEqualTo NANOSECOND_CREATED_AT + event.createdAt.time shouldBeEqualTo CREATED_AT_MILLIS + } + + @ParameterizedTest + @MethodSource("missingRequiredFields") + fun `A notification event without a field the domain event requires is rejected`(json: String, field: String) { + assertThrows { + parser.fromJson(json.without(field), ChatEvent::class.java) + } + } + + private fun String.withNanosecondCreatedAt() = + replaceFirst(""""created_at": "2020-06-29T06:14:28.000Z"""", """"created_at": "$NANOSECOND_CREATED_AT"""") + + private fun String.without(field: String): String = + parser.toJson(parser.fromJson(this, Map::class.java).minus(field)) + + companion object { + private const val NANOSECOND_CREATED_AT = "2026-09-23T14:57:25.025029486Z" + + // The same instant truncated to milliseconds. + private const val CREATED_AT_MILLIS = 1790175445025L + + private val inviteEvents = listOf( + createNotificationInvitedEventStringJson() to NotificationInvitedEvent::class, + createNotificationInviteAcceptedEventStringJson() to NotificationInviteAcceptedEvent::class, + createNotificationInviteRejectedEventStringJson() to NotificationInviteRejectedEvent::class, + ) + private val channelEvents = listOf( + createNotificationChannelDeletedEventStringJson() to NotificationChannelDeletedEvent::class, + createNotificationChannelTruncatedEventStringJson() to NotificationChannelTruncatedEvent::class, + ) + + @JvmStatic + fun events(): List = (inviteEvents + channelEvents).map { (json, type) -> Arguments.of(json, type) } + + @JvmStatic + fun missingRequiredFields(): List = + inviteEvents.flatMap { (json, _) -> + listOf("cid", "user", "member", "channel").map { Arguments.of(json, it) } + } + channelEvents.flatMap { (json, _) -> + listOf("cid", "channel").map { Arguments.of(json, it) } + } + } +}