From 2044aaf747ec980d26f88ce6bab0fb47805a6df5 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Wed, 20 Nov 2024 18:46:08 +0100 Subject: [PATCH] Partial transition to v2 chat. --- Example/AblyChatExample/ContentView.swift | 4 +- Example/AblyChatExample/Mocks/Misc.swift | 3 +- .../AblyChatExample/Mocks/MockClients.swift | 6 +- Sources/AblyChat/ChatAPI.swift | 10 +- Sources/AblyChat/DefaultMessages.swift | 54 +++++------ Sources/AblyChat/Events.swift | 4 +- Sources/AblyChat/Message.swift | 31 ++---- Sources/AblyChat/Timeserial.swift | 94 ------------------- Tests/AblyChatTests/ChatAPITests.swift | 13 ++- .../MessageSubscriptionTests.swift | 2 +- Tests/AblyChatTests/MessageTests.swift | 48 +++------- .../Mocks/MockHTTPPaginatedResponse.swift | 8 +- 12 files changed, 75 insertions(+), 202 deletions(-) delete mode 100644 Sources/AblyChat/Timeserial.swift diff --git a/Example/AblyChatExample/ContentView.swift b/Example/AblyChatExample/ContentView.swift index 30036b35..b13f2a40 100644 --- a/Example/AblyChatExample/ContentView.swift +++ b/Example/AblyChatExample/ContentView.swift @@ -171,13 +171,13 @@ struct ContentView: View { for message in previousMessages.items { withAnimation { - messages.append(BasicListItem(id: message.timeserial, title: message.clientID, text: message.text)) + messages.append(BasicListItem(id: message.serial, title: message.clientID, text: message.text)) } } for await message in messagesSubscription { withAnimation { - messages.insert(BasicListItem(id: message.timeserial, title: message.clientID, text: message.text), at: 0) + messages.insert(BasicListItem(id: message.serial, title: message.clientID, text: message.text), at: 0) } } } diff --git a/Example/AblyChatExample/Mocks/Misc.swift b/Example/AblyChatExample/Mocks/Misc.swift index cf6b9578..e4cbb4c1 100644 --- a/Example/AblyChatExample/Mocks/Misc.swift +++ b/Example/AblyChatExample/Mocks/Misc.swift @@ -11,7 +11,8 @@ final class MockMessagesPaginatedResult: PaginatedResult { var items: [T] { Array(repeating: 0, count: numberOfMockMessages).map { _ in Message( - timeserial: "\(Date().timeIntervalSince1970)", + serial: "\(Date().timeIntervalSince1970)", + latestAction: .new, clientID: self.clientID, roomID: self.roomID, text: MockStrings.randomPhrase(), diff --git a/Example/AblyChatExample/Mocks/MockClients.swift b/Example/AblyChatExample/Mocks/MockClients.swift index 9c356e54..840b7e2b 100644 --- a/Example/AblyChatExample/Mocks/MockClients.swift +++ b/Example/AblyChatExample/Mocks/MockClients.swift @@ -105,7 +105,8 @@ actor MockMessages: Messages { private func createSubscription() -> MockSubscription { let subscription = MockSubscription(randomElement: { Message( - timeserial: "\(Date().timeIntervalSince1970)", + serial: "\(Date().timeIntervalSince1970)", + latestAction: .new, clientID: MockStrings.names.randomElement()!, roomID: self.roomID, text: MockStrings.randomPhrase(), @@ -130,7 +131,8 @@ actor MockMessages: Messages { func send(params: SendMessageParams) async throws -> Message { let message = Message( - timeserial: "\(Date().timeIntervalSince1970)", + serial: "\(Date().timeIntervalSince1970)", + latestAction: .new, clientID: clientID, roomID: roomID, text: params.text, diff --git a/Sources/AblyChat/ChatAPI.swift b/Sources/AblyChat/ChatAPI.swift index 5142db50..e8f88ba4 100644 --- a/Sources/AblyChat/ChatAPI.swift +++ b/Sources/AblyChat/ChatAPI.swift @@ -3,6 +3,7 @@ import Ably internal final class ChatAPI: Sendable { private let realtime: RealtimeClient private let apiVersion = "/chat/v1" + private let apiVersionV2 = "/chat/v2" // TODO: remove v1 after full transition to v2 public init(realtime: RealtimeClient) { self.realtime = realtime @@ -10,12 +11,12 @@ internal final class ChatAPI: Sendable { // (CHA-M6) Messages should be queryable from a paginated REST API. internal func getMessages(roomId: String, params: QueryOptions) async throws -> any PaginatedResult { - let endpoint = "\(apiVersion)/rooms/\(roomId)/messages" + let endpoint = "\(apiVersionV2)/rooms/\(roomId)/messages" return try await makePaginatedRequest(endpoint, params: params.asQueryItems()) } internal struct SendMessageResponse: Codable { - internal let timeserial: String + internal let serial: String internal let createdAt: Int64 } @@ -26,7 +27,7 @@ internal final class ChatAPI: Sendable { throw ARTErrorInfo.create(withCode: 40000, message: "Ensure your Realtime instance is initialized with a clientId.") } - let endpoint = "\(apiVersion)/rooms/\(roomId)/messages" + let endpoint = "\(apiVersionV2)/rooms/\(roomId)/messages" var body: [String: Any] = ["text": params.text] // (CHA-M3b) A message may be sent without metadata or headers. When these are not specified by the user, they must be omitted from the REST payload. @@ -44,7 +45,8 @@ internal final class ChatAPI: Sendable { let createdAtInSeconds = TimeInterval(Double(response.createdAt) / 1000) let message = Message( - timeserial: response.timeserial, + serial: response.serial, + latestAction: .new, clientID: clientId, roomID: roomId, text: params.text, diff --git a/Sources/AblyChat/DefaultMessages.swift b/Sources/AblyChat/DefaultMessages.swift index ebda8a7e..f465f1bf 100644 --- a/Sources/AblyChat/DefaultMessages.swift +++ b/Sources/AblyChat/DefaultMessages.swift @@ -1,18 +1,15 @@ import Ably -// Typealias for the timeserial used to sync message subscriptions with. This is a string representation of a timestamp. -private typealias TimeserialString = String - -// Wraps the MessageSubscription with the timeserial of when the subscription was attached or resumed. +// Wraps the MessageSubscription with the message serial of when the subscription was attached or resumed. private struct MessageSubscriptionWrapper { let subscription: MessageSubscription - var timeserial: TimeserialString + var serial: String } // Temp extension to showcase changes in the chat sdk -extension ARTMessage { - var action: String? { fatalError() } - var serial: String? { fatalError() } +private extension ARTMessage { + var action: String? { fatalError("Not implemented") } + var serial: String? { fatalError("Not implemented") } } // TODO: Don't have a strong understanding of why @MainActor is needed here. Revisit as part of https://github.com/ably-labs/ably-chat-swift/issues/83 @@ -45,7 +42,7 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { // (CHA-M4) Messages can be received via a subscription in realtime. internal func subscribe(bufferingPolicy: BufferingPolicy) async throws -> MessageSubscription { let uuid = UUID() - let timeserial = try await resolveSubscriptionStart() + let serial = try await resolveSubscriptionStart() let messageSubscription = MessageSubscription( bufferingPolicy: bufferingPolicy ) { [weak self] queryOptions in @@ -54,12 +51,12 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { } // (CHA-M4a) A subscription can be registered to receive incoming messages. Adding a subscription has no side effects on the status of the room or the underlying realtime channel. - subscriptionPoints[uuid] = .init(subscription: messageSubscription, timeserial: timeserial) + subscriptionPoints[uuid] = .init(subscription: messageSubscription, serial: serial) // (CHA-M4c) When a realtime message with name set to message.created is received, it is translated into a message event, which contains a type field with the event type as well as a message field containing the Message Struct. This event is then broadcast to all subscribers. // (CHA-M4d) If a realtime message with an unknown name is received, the SDK shall silently discard the message, though it may log at DEBUG or TRACE level. // (CHA-M5k) Incoming realtime events that are malformed (unknown field should be ignored) shall not be emitted to subscribers. - channel.subscribe(MessageEvent.created.rawValue) { message in + channel.subscribe(MessageEvent.new.rawValue) { message in Task { // TODO: Revisit errors thrown as part of https://github.com/ably-labs/ably-chat-swift/issues/32 guard let data = message.data as? [String: Any], @@ -72,8 +69,8 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without extras") } - guard let timeserial = extras["timeserial"] as? String else { - throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without timeserial") + guard let serial = message.serial else { + throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without serial") } guard let clientID = message.clientId else { @@ -83,8 +80,13 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { let metadata = data["metadata"] as? Metadata let headers = extras["headers"] as? Headers + guard let rawAction = message.action, let action = MessageEvent(rawValue: rawAction), action == .new else { + return // ignore any other actions except `chat.message` for now + } + let message = Message( - timeserial: timeserial, + serial: serial, + latestAction: action, clientID: clientID, roomID: self.roomID, text: text, @@ -115,7 +117,7 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { } private func getBeforeSubscriptionStart(_ uuid: UUID, params: QueryOptions) async throws -> any PaginatedResult { - guard let subscriptionPoint = subscriptionPoints[uuid]?.timeserial else { + guard let subscriptionPoint = subscriptionPoints[uuid]?.serial else { throw ARTErrorInfo.create( withCode: 40000, status: 400, @@ -123,16 +125,6 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { ) } - // (CHA-M5j) If the end parameter is specified and is more recent than the subscription point timeserial, the method must throw an ErrorInfo with code 40000. - let parseSerial = try? DefaultTimeserial.calculateTimeserial(from: subscriptionPoint) - if let end = params.end, dateToMilliseconds(end) > parseSerial?.timestamp ?? 0 { - throw ARTErrorInfo.create( - withCode: 40000, - status: 400, - message: "cannot query history; end time is after the subscription point of the listener" - ) - } - // (CHA-M5f) This method must accept any of the standard history query options, except for direction, which must always be backwards. var queryOptions = params queryOptions.orderBy = .newestFirst // newestFirst is equivalent to backwards @@ -174,17 +166,17 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { } do { - let timeserialOnChannelAttach = try await timeserialOnChannelAttach() + let serialOnChannelAttach = try await serialOnChannelAttach() for uuid in subscriptionPoints.keys { - subscriptionPoints[uuid]?.timeserial = timeserialOnChannelAttach + subscriptionPoints[uuid]?.serial = serialOnChannelAttach } } catch { throw ARTErrorInfo.create(from: error) } } - private func resolveSubscriptionStart() async throws -> TimeserialString { + private func resolveSubscriptionStart() async throws -> String { // (CHA-M5a) If a subscription is added when the underlying realtime channel is ATTACHED, then the subscription point is the current channelSerial of the realtime channel. if channel.state == .attached { if let channelSerial = channel.properties.channelSerial { @@ -195,11 +187,11 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { } // (CHA-M5b) If a subscription is added when the underlying realtime channel is in any other state, then its subscription point becomes the attachSerial at the the point of channel attachment. - return try await timeserialOnChannelAttach() + return try await serialOnChannelAttach() } // Always returns the attachSerial and not the channelSerial to also serve (CHA-M5c) - If a channel leaves the ATTACHED state and then re-enters ATTACHED with resumed=false, then it must be assumed that messages have been missed. The subscription point of any subscribers must be reset to the attachSerial. - private func timeserialOnChannelAttach() async throws -> TimeserialString { + private func serialOnChannelAttach() async throws -> String { // If the state is already 'attached', return the attachSerial immediately if channel.state == .attached { if let attachSerial = channel.properties.attachSerial { @@ -212,7 +204,7 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities { // (CHA-M5b) If a subscription is added when the underlying realtime channel is in any other state, then its subscription point becomes the attachSerial at the the point of channel attachment. return try await withCheckedThrowingContinuation { continuation in // avoids multiple invocations of the continuation - var nillableContinuation: CheckedContinuation? = continuation + var nillableContinuation: CheckedContinuation? = continuation channel.on { [weak self] stateChange in guard let self else { diff --git a/Sources/AblyChat/Events.swift b/Sources/AblyChat/Events.swift index 31f27c29..79ee0860 100644 --- a/Sources/AblyChat/Events.swift +++ b/Sources/AblyChat/Events.swift @@ -1,5 +1,5 @@ -internal enum MessageEvent: String { - case created = "message.created" +public enum MessageEvent: String, Codable, Sendable { + case new = "chat.message" } internal enum RoomReactionEvents: String { diff --git a/Sources/AblyChat/Message.swift b/Sources/AblyChat/Message.swift index d3b9b418..8f320e45 100644 --- a/Sources/AblyChat/Message.swift +++ b/Sources/AblyChat/Message.swift @@ -6,9 +6,10 @@ public typealias MessageMetadata = Metadata // (CHA-M2) A Message corresponds to a single message in a chat room. This is analogous to a single user-specified message on an Ably channel (NOTE: not a ProtocolMessage). public struct Message: Sendable, Codable, Identifiable, Equatable { // id to meet Identifiable conformance. 2 messages in the same channel cannot have the same timeserial. - public var id: String { timeserial } + public var id: String { serial } - public var timeserial: String + public var serial: String + public var latestAction: MessageEvent public var clientID: String public var roomID: String public var text: String @@ -16,8 +17,9 @@ public struct Message: Sendable, Codable, Identifiable, Equatable { public var metadata: MessageMetadata public var headers: MessageHeaders - public init(timeserial: String, clientID: String, roomID: String, text: String, createdAt: Date?, metadata: MessageMetadata, headers: MessageHeaders) { - self.timeserial = timeserial + public init(serial: String, latestAction: MessageEvent, clientID: String, roomID: String, text: String, createdAt: Date?, metadata: MessageMetadata, headers: MessageHeaders) { + self.serial = serial + self.latestAction = latestAction self.clientID = clientID self.roomID = roomID self.text = text @@ -27,7 +29,8 @@ public struct Message: Sendable, Codable, Identifiable, Equatable { } internal enum CodingKeys: String, CodingKey { - case timeserial + case serial + case latestAction case clientID = "clientId" case roomID = "roomId" case text @@ -35,22 +38,4 @@ public struct Message: Sendable, Codable, Identifiable, Equatable { case metadata case headers } - - // (CHA-M2a) A Message is considered before another Message in the global order if the timeserial of the corresponding realtime channel message comes first. - public func isBefore(_ otherMessage: Message) throws -> Bool { - let otherMessageTimeserial = try DefaultTimeserial.calculateTimeserial(from: otherMessage.timeserial) - return try DefaultTimeserial.calculateTimeserial(from: timeserial).before(otherMessageTimeserial) - } - - // CHA-M2b) A Message is considered after another Message in the global order if the timeserial of the corresponding realtime channel message comes second. - public func isAfter(_ otherMessage: Message) throws -> Bool { - let otherMessageTimeserial = try DefaultTimeserial.calculateTimeserial(from: otherMessage.timeserial) - return try DefaultTimeserial.calculateTimeserial(from: timeserial).after(otherMessageTimeserial) - } - - // (CHA-M2c) A Message is considered to be equal to another Message if they have the same timeserial. - public func isEqual(_ otherMessage: Message) throws -> Bool { - let otherMessageTimeserial = try DefaultTimeserial.calculateTimeserial(from: otherMessage.timeserial) - return try DefaultTimeserial.calculateTimeserial(from: timeserial).equal(otherMessageTimeserial) - } } diff --git a/Sources/AblyChat/Timeserial.swift b/Sources/AblyChat/Timeserial.swift deleted file mode 100644 index 97aadb71..00000000 --- a/Sources/AblyChat/Timeserial.swift +++ /dev/null @@ -1,94 +0,0 @@ -import Foundation - -internal protocol Timeserial: Sendable { - var seriesId: String { get } - var timestamp: Int { get } - var counter: Int { get } - var index: Int? { get } - - func before(_ timeserial: Timeserial) -> Bool - func after(_ timeserial: Timeserial) -> Bool - func equal(_ timeserial: Timeserial) -> Bool -} - -internal struct DefaultTimeserial: Timeserial { - internal let seriesId: String - internal let timestamp: Int - internal let counter: Int - internal let index: Int? - - private init(seriesId: String, timestamp: Int, counter: Int, index: Int?) { - self.seriesId = seriesId - self.timestamp = timestamp - self.counter = counter - self.index = index - } - - // Static method to parse a timeserial string - internal static func calculateTimeserial(from timeserial: String) throws -> DefaultTimeserial { - let components = timeserial.split(separator: "@") - guard components.count == 2, let rest = components.last else { - throw TimeserialError.invalidFormat - } - - let seriesId = String(components[0]) - let parts = rest.split(separator: "-") - guard parts.count == 2 else { - throw TimeserialError.invalidFormat - } - - let timestamp = Int(parts[0]) ?? 0 - let counterAndIndex = parts[1].split(separator: ":") - let counter = Int(counterAndIndex[0]) ?? 0 - let index = counterAndIndex.count > 1 ? Int(counterAndIndex[1]) : nil - - return DefaultTimeserial(seriesId: seriesId, timestamp: timestamp, counter: counter, index: index) - } - - // Compare timeserials - private func timeserialCompare(_ other: Timeserial) -> Int { - // Compare timestamps - let timestampDiff = timestamp - other.timestamp - if timestampDiff != 0 { - return timestampDiff - } - - // Compare counters - let counterDiff = counter - other.counter - if counterDiff != 0 { - return counterDiff - } - - // Compare seriesId lexicographically - if seriesId != other.seriesId { - return seriesId < other.seriesId ? -1 : 1 - } - - // Compare index if present - if let idx1 = index, let idx2 = other.index { - return idx1 - idx2 - } - - return 0 - } - - // Check if this timeserial is before the given timeserial - internal func before(_ timeserial: Timeserial) -> Bool { - timeserialCompare(timeserial) < 0 - } - - // Check if this timeserial is after the given timeserial - internal func after(_ timeserial: Timeserial) -> Bool { - timeserialCompare(timeserial) > 0 - } - - // Check if this timeserial is equal to the given timeserial - internal func equal(_ timeserial: Timeserial) -> Bool { - timeserialCompare(timeserial) == 0 - } - - // TODO: Revisit as part of https://github.com/ably-labs/ably-chat-swift/issues/32 (should we only throw ARTErrors?) - internal enum TimeserialError: Error { - case invalidFormat - } -} diff --git a/Tests/AblyChatTests/ChatAPITests.swift b/Tests/AblyChatTests/ChatAPITests.swift index 4c37d4df..47745f8e 100644 --- a/Tests/AblyChatTests/ChatAPITests.swift +++ b/Tests/AblyChatTests/ChatAPITests.swift @@ -40,7 +40,8 @@ struct ChatAPITests { // Then let expectedMessage = Message( - timeserial: "3446456", + serial: "3446456", + latestAction: .new, clientID: "mockClientId", roomID: roomId, text: "hello", @@ -89,7 +90,8 @@ struct ChatAPITests { paginatedResponse: paginatedResponse, items: [ Message( - timeserial: "3446456", + serial: "3446456", + latestAction: .new, clientID: "random", roomID: roomId, text: "hello", @@ -98,7 +100,8 @@ struct ChatAPITests { headers: [:] ), Message( - timeserial: "3446457", + serial: "3446457", + latestAction: .new, clientID: "random", roomID: roomId, text: "hello response", @@ -110,10 +113,10 @@ struct ChatAPITests { ) // When - let getMessages = try? await chatAPI.getMessages(roomId: roomId, params: .init()) as? PaginatedResultWrapper + let getMessagesResult = try? await chatAPI.getMessages(roomId: roomId, params: .init()) as? PaginatedResultWrapper // Then - #expect(getMessages == expectedPaginatedResult) + #expect(getMessagesResult == expectedPaginatedResult) } // @spec CHA-M5i diff --git a/Tests/AblyChatTests/MessageSubscriptionTests.swift b/Tests/AblyChatTests/MessageSubscriptionTests.swift index fbea2808..48e83975 100644 --- a/Tests/AblyChatTests/MessageSubscriptionTests.swift +++ b/Tests/AblyChatTests/MessageSubscriptionTests.swift @@ -26,7 +26,7 @@ private final class MockPaginatedResult: PaginatedResult { struct MessageSubscriptionTests { let messages = ["First", "Second"].map { text in - Message(timeserial: "", clientID: "", roomID: "", text: text, createdAt: .init(), metadata: [:], headers: [:]) + Message(serial: "", latestAction: .new, clientID: "", roomID: "", text: text, createdAt: .init(), metadata: [:], headers: [:]) } @Test diff --git a/Tests/AblyChatTests/MessageTests.swift b/Tests/AblyChatTests/MessageTests.swift index bbb88a53..6b2a0642 100644 --- a/Tests/AblyChatTests/MessageTests.swift +++ b/Tests/AblyChatTests/MessageTests.swift @@ -3,7 +3,8 @@ import Testing struct MessageTests { let earlierMessage = Message( - timeserial: "ABC123@1631840000000-5:2", + serial: "ABC123@1631840000000-5:2", + latestAction: .new, clientID: "testClientID", roomID: "roomId", text: "hello", @@ -13,7 +14,8 @@ struct MessageTests { ) let laterMessage = Message( - timeserial: "ABC123@1631840000001-5:2", + serial: "ABC123@1631840000001-5:2", + latestAction: .new, clientID: "testClientID", roomID: "roomId", text: "hello", @@ -23,7 +25,8 @@ struct MessageTests { ) let invalidMessage = Message( - timeserial: "invalid", + serial: "invalid", + latestAction: .new, clientID: "testClientID", roomID: "roomId", text: "hello", @@ -37,21 +40,13 @@ struct MessageTests { // @specOneOf(1/3) CHA-M2a @Test func isBefore_WhenMessageIsBefore_ReturnsTrue() async throws { - #expect(try earlierMessage.isBefore(laterMessage)) + #expect(earlierMessage.serial < laterMessage.serial) } // @specOneOf(2/3) CHA-M2a @Test func isBefore_WhenMessageIsNotBefore_ReturnsFalse() async throws { - #expect(try !laterMessage.isBefore(earlierMessage)) - } - - // @specOneOf(3/3) CHA-M2a - @Test - func isBefore_whenTimeserialIsInvalid_throwsInvalidMessage() async throws { - #expect(throws: DefaultTimeserial.TimeserialError.invalidFormat, performing: { - try earlierMessage.isBefore(invalidMessage) - }) + #expect(laterMessage.serial > earlierMessage.serial) } // MARK: isAfter Tests @@ -59,21 +54,13 @@ struct MessageTests { // @specOneOf(1/3) CHA-M2b @Test func isAfter_whenMessageIsAfter_ReturnsTrue() async throws { - #expect(try laterMessage.isAfter(earlierMessage)) + #expect(laterMessage.serial > earlierMessage.serial) } // @specOneOf(2/3) CHA-M2b @Test func isAfter_whenMessageIsNotAfter_ReturnsFalse() async throws { - #expect(try !earlierMessage.isAfter(laterMessage)) - } - - // @specOneOf(3/3) CHA-M2b - @Test - func isAfter_whenTimeserialIsInvalid_throwsInvalidMessage() async throws { - #expect(throws: DefaultTimeserial.TimeserialError.invalidFormat, performing: { - try earlierMessage.isAfter(invalidMessage) - }) + #expect(earlierMessage.serial < laterMessage.serial) } // MARK: isEqual Tests @@ -82,7 +69,8 @@ struct MessageTests { @Test func isEqual_whenMessageIsEqual_ReturnsTrue() async throws { let duplicateOfEarlierMessage = Message( - timeserial: "ABC123@1631840000000-5:2", + serial: "ABC123@1631840000000-5:2", + latestAction: .new, clientID: "random", roomID: "", text: "", @@ -90,20 +78,12 @@ struct MessageTests { metadata: [:], headers: [:] ) - #expect(try earlierMessage.isEqual(duplicateOfEarlierMessage)) + #expect(earlierMessage.serial == duplicateOfEarlierMessage.serial) } // @specOneOf(2/3) CHA-M2c @Test func isEqual_whenMessageIsNotEqual_ReturnsFalse() async throws { - #expect(try !earlierMessage.isEqual(laterMessage)) - } - - // @specOneOf(3/3) CHA-M2c - @Test - func isEqual_whenTimeserialIsInvalid_throwsInvalidMessage() async throws { - #expect(throws: DefaultTimeserial.TimeserialError.invalidFormat, performing: { - try earlierMessage.isEqual(invalidMessage) - }) + #expect(earlierMessage.serial != laterMessage.serial) } } diff --git a/Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift b/Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift index 466979fd..5d860314 100644 --- a/Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift +++ b/Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift @@ -61,7 +61,7 @@ extension MockHTTPPaginatedResponse { static let successSendMessage = MockHTTPPaginatedResponse( items: [ [ - "timeserial": "3446456", + "serial": "3446456", "createdAt": 1_631_840_000_000, "text": "hello", ], @@ -98,7 +98,8 @@ extension MockHTTPPaginatedResponse { items: [ [ "clientId": "random", - "timeserial": "3446456", + "serial": "3446456", + "latestAction": "chat.message", "createdAt": 1_730_943_049_269, "roomId": "basketball::$chat::$chatMessages", "text": "hello", @@ -107,7 +108,8 @@ extension MockHTTPPaginatedResponse { ], [ "clientId": "random", - "timeserial": "3446457", + "serial": "3446457", + "latestAction": "chat.message", "roomId": "basketball::$chat::$chatMessages", "text": "hello response", "metadata": [:],