Skip to content

Commit 19b6901

Browse files
committed
Added messages tests.
1 parent 3e11ea0 commit 19b6901

File tree

9 files changed

+477
-135
lines changed

9 files changed

+477
-135
lines changed

Sources/AblyChat/DefaultMessages.swift

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ private struct MessageSubscriptionWrapper {
66
var serial: String
77
}
88

9+
#if DEBUG
10+
extension ARTMessage: @retroactive @unchecked Sendable {}
11+
#endif
12+
913
// 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
1014
@MainActor
1115
internal final class DefaultMessages: Messages, EmitsDiscontinuities {
@@ -54,52 +58,62 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities {
5458
// (CHA-M5k) Incoming realtime events that are malformed (unknown field should be ignored) shall not be emitted to subscribers.
5559
let eventListener = channel.subscribe(RealtimeMessageName.chatMessage.rawValue) { message in
5660
Task {
57-
// TODO: Revisit errors thrown as part of https://github.com/ably-labs/ably-chat-swift/issues/32
58-
guard let ablyCocoaData = message.data,
59-
let data = JSONValue(ablyCocoaData: ablyCocoaData).objectValue,
60-
let text = data["text"]?.stringValue
61-
else {
62-
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without data or text")
63-
}
61+
do {
62+
// TODO: Revisit errors thrown as part of https://github.com/ably-labs/ably-chat-swift/issues/32
63+
guard let ablyCocoaData = message.data,
64+
let data = JSONValue(ablyCocoaData: ablyCocoaData).objectValue,
65+
let text = data["text"]?.stringValue
66+
else {
67+
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without data or text")
68+
}
6469

65-
guard let ablyCocoaExtras = message.extras else {
66-
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without extras")
67-
}
70+
guard let ablyCocoaExtras = message.extras else {
71+
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without extras")
72+
}
6873

69-
let extras = JSONValue.objectFromAblyCocoaExtras(ablyCocoaExtras)
74+
let extras = JSONValue.objectFromAblyCocoaExtras(ablyCocoaExtras)
7075

71-
guard let serial = message.serial else {
72-
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without serial")
73-
}
76+
guard let serial = message.serial else {
77+
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without serial")
78+
}
7479

75-
guard let clientID = message.clientId else {
76-
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without clientId")
77-
}
80+
guard let clientID = message.clientId else {
81+
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without clientId")
82+
}
7883

79-
let metadata = try data.optionalObjectValueForKey("metadata")
84+
let metadata = try data.optionalObjectValueForKey("metadata")
8085

81-
let headers: Headers? = if let headersJSONObject = try extras.optionalObjectValueForKey("headers") {
82-
try headersJSONObject.mapValues { try HeadersValue(jsonValue: $0) }
83-
} else {
84-
nil
85-
}
86+
let headers: Headers? = if let headersJSONObject = try extras.optionalObjectValueForKey("headers") {
87+
try headersJSONObject.mapValues { try HeadersValue(jsonValue: $0) }
88+
} else {
89+
nil
90+
}
8691

87-
guard let action = MessageAction.fromRealtimeAction(message.action) else {
88-
return
89-
}
92+
guard let action = MessageAction.fromRealtimeAction(message.action) else {
93+
return
94+
}
95+
96+
let message = Message(
97+
serial: serial,
98+
action: action,
99+
clientID: clientID,
100+
roomID: self.roomID,
101+
text: text,
102+
createdAt: message.timestamp,
103+
metadata: metadata ?? .init(),
104+
headers: headers ?? .init()
105+
)
90106

91-
let message = Message(
92-
serial: serial,
93-
action: action,
94-
clientID: clientID,
95-
roomID: self.roomID,
96-
text: text,
97-
createdAt: message.timestamp,
98-
metadata: metadata ?? .init(),
99-
headers: headers ?? .init()
100-
)
101-
102-
messageSubscription.emit(message)
107+
messageSubscription.emit(message)
108+
} catch {
109+
self.logger.log(message: "Malformed message received: \(error)", level: .debug)
110+
#if DEBUG
111+
for subscription in self.malformedMessageSubscriptions {
112+
subscription.emit(message)
113+
}
114+
#endif
115+
throw error
116+
}
103117
}
104118
}
105119

@@ -118,6 +132,18 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities {
118132
return messageSubscription
119133
}
120134

135+
#if DEBUG
136+
/// Subscription of malformed message events for testing purposes.
137+
private var malformedMessageSubscriptions: [Subscription<ARTMessage>] = []
138+
139+
/// Returns a subscription which emits malformed message events for testing purposes.
140+
internal func testsOnly_subscribeToMalformedMessageEvents() -> Subscription<ARTMessage> {
141+
let subscription = Subscription<ARTMessage>(bufferingPolicy: .unbounded)
142+
malformedMessageSubscriptions.append(subscription)
143+
return subscription
144+
}
145+
#endif
146+
121147
// (CHA-M6a) A method must be exposed that accepts the standard Ably REST API query parameters. It shall call the “REST API”#rest-fetching-messages and return a PaginatedResult containing messages, which can then be paginated through.
122148
internal func get(options: QueryOptions) async throws -> any PaginatedResult<Message> {
123149
try await chatAPI.getMessages(roomId: roomID, params: options)
@@ -163,7 +189,7 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities {
163189
}
164190
}
165191

166-
// (CHA-M4d) If a channel UPDATE event is received and resumed=false, then it must be assumed that messages have been missed. The subscription point of any subscribers must be reset to the attachSerial.
192+
// (CHA-M5d) If a channel UPDATE event is received and resumed=false, then it must be assumed that messages have been missed. The subscription point of any subscribers must be reset to the attachSerial.
167193
channel.on(.update) { [weak self] stateChange in
168194
Task {
169195
do {

Tests/AblyChatTests/ChatAPITests.swift

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -164,27 +164,4 @@ struct ChatAPITests {
164164
// Then
165165
#expect(getMessagesResult == expectedPaginatedResult)
166166
}
167-
168-
// @spec CHA-M5i
169-
@Test
170-
func getMessages_whenGetMessagesReturnsServerError_throwsARTError() async {
171-
// Given
172-
let paginatedResponse = MockHTTPPaginatedResponse.successGetMessagesWithNoItems
173-
let artError = ARTErrorInfo.create(withCode: 50000, message: "Internal server error")
174-
let realtime = MockRealtime.create {
175-
(paginatedResponse, artError)
176-
}
177-
let chatAPI = ChatAPI(realtime: realtime)
178-
let roomId = "basketball::$chat::$chatMessages"
179-
180-
await #expect(
181-
performing: {
182-
// When
183-
try await chatAPI.getMessages(roomId: roomId, params: .init()) as? PaginatedResultWrapper<Message>
184-
}, throws: { error in
185-
// Then
186-
error as? ARTErrorInfo == artError
187-
}
188-
)
189-
}
190167
}

0 commit comments

Comments
 (0)