Skip to content

Commit b894f1d

Browse files
committed
Implement Messages.get() according to CHA-M13
1 parent e53e2a6 commit b894f1d

File tree

8 files changed

+129
-8
lines changed

8 files changed

+129
-8
lines changed

Example/AblyChatExample/Mocks/MockClients.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,23 @@ class MockMessages: Messages {
219219
mockSubscriptions.emit(ChatMessageEvent(message: message))
220220
return message
221221
}
222+
223+
func get(withSerial serial: String) async throws(ARTErrorInfo) -> Message {
224+
Message(
225+
serial: serial,
226+
action: .messageCreate,
227+
clientID: clientID,
228+
text: MockStrings.randomPhrase(),
229+
metadata: [:],
230+
headers: [:],
231+
version: .init(
232+
serial: serial,
233+
timestamp: Date(),
234+
),
235+
timestamp: Date(),
236+
reactions: .init(unique: [:], distinct: [:], multiple: [:]),
237+
)
238+
}
222239
}
223240

224241
class MockMessageReactions: MessageReactions {

Sources/AblyChat/ChatAPI.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ internal final class ChatAPI {
3333
return try await makePaginatedRequest(endpoint, params: params.asQueryItems())
3434
}
3535

36+
// (CHA-M13) Get a single message by its serial
37+
internal func getMessage(roomName: String, serial: String) async throws(InternalError) -> Message {
38+
let endpoint = messageUrl(roomName: roomName, serial: serial)
39+
return try await makeRequest(endpoint, method: "GET")
40+
}
41+
3642
internal struct SendMessageReactionParams: Sendable {
3743
internal let type: MessageReactionType
3844
internal let name: String

Sources/AblyChat/DefaultMessages.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ internal final class DefaultMessages: Messages {
147147
}
148148
}
149149

150+
// (CHA-M13) A single message must be retrievable from the REST API.
151+
internal func get(withSerial serial: String) async throws(ARTErrorInfo) -> Message {
152+
do {
153+
return try await chatAPI.getMessage(roomName: roomName, serial: serial)
154+
} catch {
155+
throw error.toARTErrorInfo()
156+
}
157+
}
158+
150159
private func resolveSubscriptionStart() async throws(InternalError) -> String {
151160
logger.log(message: "Resolving subscription start serial", level: .debug)
152161
// (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.

Sources/AblyChat/Messages.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ public protocol Messages: AnyObject, Sendable {
7979
*/
8080
func delete(message: Message, details: OperationDetails?) async throws(ARTErrorInfo) -> Message
8181

82+
/**
83+
* Get a message by its serial.
84+
*
85+
* - Parameters:
86+
* - serial: The serial of the message to get.
87+
*
88+
* - Returns: The message with the specified serial.
89+
*/
90+
func get(withSerial serial: String) async throws(ARTErrorInfo) -> Message
91+
8292
/**
8393
* Add, delete, and subscribe to message reactions.
8494
*/

Tests/AblyChatTests/ChatAPITests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,24 +143,24 @@ struct ChatAPITests {
143143
paginatedResponse: paginatedResponse,
144144
items: [
145145
Message(
146-
serial: "3446456",
146+
serial: "123456789-000@123456789:000",
147147
action: .messageCreate,
148148
clientID: "random",
149149
text: "hello",
150150
metadata: [:],
151151
headers: [:],
152-
version: .init(serial: "3446456", timestamp: Date(timeIntervalSince1970: 1_730_943_049.269)), // from successGetMessagesWithItems
152+
version: .init(serial: "123456789-000@123456789:000", timestamp: Date(timeIntervalSince1970: 1_730_943_049.269)), // from successGetMessagesWithItems
153153
timestamp: Date(timeIntervalSince1970: 1_730_943_049.269),
154154
reactions: .empty,
155155
),
156156
Message(
157-
serial: "3446457",
157+
serial: "123456789-000@123456789:001",
158158
action: .messageCreate,
159159
clientID: "random",
160160
text: "hello response",
161161
metadata: [:],
162162
headers: [:],
163-
version: .init(serial: "3446457", timestamp: Date(timeIntervalSince1970: 1_730_943_051.269)),
163+
version: .init(serial: "123456789-000@123456789:001", timestamp: Date(timeIntervalSince1970: 1_730_943_051.269)),
164164
timestamp: Date(timeIntervalSince1970: 1_730_943_051.269),
165165
reactions: .empty,
166166
),

Tests/AblyChatTests/DefaultMessagesTests.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,77 @@ struct DefaultMessagesTests {
241241
}
242242
}
243243

244+
// @spec CHA-M13a
245+
@Test
246+
func getMessage() async throws {
247+
// Given
248+
let realtime = MockRealtime {
249+
MockHTTPPaginatedResponse(
250+
items: [
251+
[
252+
"serial": "123456789-000@123456789:000",
253+
"version": [
254+
"serial": "123456789-000@123456789:000",
255+
"timestamp": 1_631_840_000_000,
256+
],
257+
"metadata": ["key1": "val1"],
258+
"headers": ["key2": "val2"],
259+
"timestamp": 1_631_840_000_000,
260+
"text": "hey",
261+
"clientId": "clientId",
262+
"action": "message.create",
263+
],
264+
],
265+
statusCode: 200,
266+
headers: [:],
267+
)
268+
}
269+
let chatAPI = ChatAPI(realtime: realtime)
270+
let channel = MockRealtimeChannel(initialState: .attached)
271+
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())
272+
273+
// When
274+
let retrievedMessage = try await defaultMessages.get(withSerial: "123456789-000@123456789:000")
275+
276+
// Then
277+
#expect(retrievedMessage.serial == "123456789-000@123456789:000")
278+
#expect(retrievedMessage.action == .messageCreate)
279+
#expect(retrievedMessage.text == "hey")
280+
#expect(retrievedMessage.clientID == "clientId")
281+
#expect(retrievedMessage.version.serial == "123456789-000@123456789:000")
282+
#expect(retrievedMessage.version.timestamp == Date(timeIntervalSince1970: 1_631_840_000_000 / 1000))
283+
#expect(retrievedMessage.metadata == ["key1": "val1"])
284+
#expect(retrievedMessage.headers == ["key2": "val2"])
285+
#expect(retrievedMessage.timestamp == Date(timeIntervalSince1970: 1_631_840_000_000 / 1000))
286+
#expect(realtime.callRecorder.hasRecord(
287+
matching: "request(_:path:params:body:headers:)",
288+
arguments: ["method": "GET", "path": "/chat/v4/rooms/basketball/messages/123456789-000@123456789:000", "body": [:], "params": [:], "headers": [:]],
289+
))
290+
}
291+
292+
// @spec CHA-M13b
293+
@Test
294+
func errorShouldBeThrownIfErrorIsReturnedFromGetRESTChatAPI() async throws {
295+
// Given
296+
let realtime = MockRealtime { @Sendable () throws(ARTErrorInfo) in
297+
throw ARTErrorInfo(domain: "SomeDomain", code: 123)
298+
}
299+
let chatAPI = ChatAPI(realtime: realtime)
300+
let channel = MockRealtimeChannel()
301+
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())
302+
303+
// Then
304+
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
305+
let doIt = {
306+
_ = try await defaultMessages.get(withSerial: "123456789-000@123456789:000")
307+
}
308+
await #expect {
309+
try await doIt()
310+
} throws: { error in
311+
error as? ARTErrorInfo == ARTErrorInfo(domain: "SomeDomain", code: 123)
312+
}
313+
}
314+
244315
// @spec CHA-M5a
245316
// @specOneOf(4/6) CHA-RST6 - Escaping room name for API get messages
246317
@Test

Tests/AblyChatTests/IntegrationTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,14 @@ struct IntegrationTests {
262262
#expect(value.clientIDs == [messageToReact.clientID])
263263
}
264264

265+
// (8) Get a single message by its serial
266+
let retrievedMessage = try await rxRoom.messages.get(withSerial: rxMessageFromHistory.serial)
267+
#expect(retrievedMessage.serial == rxMessageFromHistory.serial)
268+
#expect(retrievedMessage.text == rxMessageFromHistory.text)
269+
#expect(retrievedMessage.clientID == rxMessageFromHistory.clientID)
270+
// Verify the retrieved message has the same reaction summary
271+
#expect(retrievedMessage.reactions.distinct.count == rxMessageFromHistory.reactions.distinct.count)
272+
265273
// MARK: - Editing and Deleting Messages
266274

267275
// Reuse message subscription and message from (5) and (6) above

Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,25 +101,25 @@ extension MockHTTPPaginatedResponse {
101101
items: [
102102
[
103103
"clientId": "random",
104-
"serial": "3446456",
104+
"serial": "123456789-000@123456789:000",
105105
"action": "message.create",
106106
"text": "hello",
107107
"metadata": [:],
108108
"headers": [:],
109109
"version": [
110-
"serial": "3446456",
110+
"serial": "123456789-000@123456789:000",
111111
],
112112
"timestamp": 1_730_943_049_269,
113113
],
114114
[
115115
"clientId": "random",
116-
"serial": "3446457",
116+
"serial": "123456789-000@123456789:001",
117117
"action": "message.create",
118118
"text": "hello response",
119119
"metadata": [:],
120120
"headers": [:],
121121
"version": [
122-
"serial": "3446457",
122+
"serial": "123456789-000@123456789:001",
123123
],
124124
"timestamp": 1_730_943_051_269,
125125
],

0 commit comments

Comments
 (0)