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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Sources/AblyChat/ChatAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ internal final class ChatAPI: Sendable {
}

// (CHA-M8c) An update operation has PUT semantics. If a field is not specified in the update, it is assumed to be removed.
// CHA-M8c is not actually respected here, see https://github.com/ably/ably-chat-swift/issues/333
let response: UpdateMessageResponse = try await makeRequest(endpoint, method: "PUT", body: .jsonObject(body))

// response.timestamp is in milliseconds, convert it to seconds
Expand Down Expand Up @@ -177,10 +178,10 @@ internal final class ChatAPI: Sendable {
serial: message.serial,
action: .delete,
clientID: message.clientID,
text: message.text,
text: "", // CHA-M9b (When a message is deleted successfully via the REST API, the caller shall receive a struct representing the Message in response, as if it were received via Realtime event.) Currently realtime sends an empty text for deleted message, so set it to empty text here as well.
createdAt: message.createdAt,
metadata: message.metadata,
headers: message.headers,
metadata: [:], // CHA-M9b
headers: [:], // CHA-M9b
version: response.version,
timestamp: Date(timeIntervalSince1970: timestampInSeconds),
operation: .init(
Expand Down
99 changes: 91 additions & 8 deletions Tests/AblyChatTests/DefaultMessagesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ struct DefaultMessagesTests {
// @spec CHA-M3a
// @spec CHA-M3b
// @spec CHA-M3f
// @spec CHA-M8a
// @spec CHA-M8b
// @spec CHA-M9a
// @spec CHA-M9b
@Test
func clientMaySendMessageViaRESTChatAPI() async throws {
func sendAndUpdateAndDeleteMessageInTheRoom() async throws {
// Given
let realtime = MockRealtime {
MockHTTPPaginatedResponse.successSendMessage
Expand All @@ -20,19 +24,50 @@ struct DefaultMessagesTests {
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", clientID: "clientId", logger: TestLogger())

// When
_ = try await defaultMessages.send(params: .init(text: "hey"))
let sentMessage = try await defaultMessages.send(params: .init(text: "hey", metadata: ["key1": "val1"], headers: ["key2": "val2"]))

// Then
#expect(sentMessage.text == "hey")
#expect(sentMessage.metadata == ["key1": "val1"])
#expect(sentMessage.headers == ["key2": "val2"])
#expect(realtime.callRecorder.hasRecord(
matching: "request(_:path:params:body:headers:)",
arguments: ["method": "POST", "path": "/chat/v3/rooms/basketball/messages", "body": ["text": "hey"], "params": [:], "headers": [:]]
arguments: ["method": "POST", "path": "/chat/v3/rooms/basketball/messages", "body": ["text": "hey", "metadata": ["key1": "val1"], "headers": ["key2": "val2"]], "params": [:], "headers": [:]]
)
)

// When
var newMessage = sentMessage
newMessage.text = "hey!" // see https://github.com/ably/ably-chat-swift/issues/333
let updatedMessage = try await defaultMessages.update(newMessage: newMessage, description: "add exclamation", metadata: ["key3": "val3"])

// Then
#expect(updatedMessage.text == "hey!")
#expect(updatedMessage.operation?.metadata == ["key3": "val3"])
#expect(updatedMessage.operation?.description == "add exclamation")
#expect(realtime.callRecorder.hasRecord(
matching: "request(_:path:params:body:headers:)",
arguments: ["method": "PUT", "path": "/chat/v3/rooms/basketball/messages/\(sentMessage.serial)", "body": ["message": ["text": "hey!", "metadata": ["key1": "val1"], "headers": ["key2": "val2"]], "description": "add exclamation", "metadata": ["key3": "val3"]], "params": [:], "headers": [:]]
)
)

// When
let deletedMessage = try await defaultMessages.delete(message: sentMessage, params: .init())

// Then
#expect(deletedMessage.text.isEmpty)
#expect(deletedMessage.headers.isEmpty)
#expect(deletedMessage.metadata.isEmpty)
#expect(realtime.callRecorder.hasRecord(
matching: "request(_:path:params:body:headers:)",
arguments: ["method": "POST", "path": "/chat/v3/rooms/basketball/messages/\(sentMessage.serial)/delete", "body": [:], "params": [:], "headers": [:]]
)
)
}

// @spec CHA-M3e
@Test
func errorShouldBeThrownIfErrorIsReturnedFromRESTChatAPI() async throws {
func errorShouldBeThrownIfErrorIsReturnedFromSendRESTChatAPI() async throws {
// Given
let realtime = MockRealtime { @Sendable () throws(ARTErrorInfo) in
throw ARTErrorInfo(domain: "SomeDomain", code: 123)
Expand All @@ -53,12 +88,60 @@ struct DefaultMessagesTests {
}
}

// @spec CHA-M8d
@Test
func errorShouldBeThrownIfErrorIsReturnedFromUpdateRESTChatAPI() async throws {
// Given
let realtime = MockRealtime { @Sendable () throws(ARTErrorInfo) in
throw ARTErrorInfo(domain: "SomeDomain", code: 123)
}
let chatAPI = ChatAPI(realtime: realtime)
let channel = MockRealtimeChannel()
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", clientID: "clientId", logger: TestLogger())

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let message = try Message(jsonObject: ["serial": "0", "version": "0", "text": "hey", "clientId": "0", "action": "message.update", "metadata": [:], "headers": [:]]) // arbitrary
_ = try await defaultMessages.update(newMessage: message, description: "", metadata: [:])
}
await #expect {
try await doIt()
} throws: { error in
error as? ARTErrorInfo == ARTErrorInfo(domain: "SomeDomain", code: 123)
}
}

// @spec CHA-M9c
@Test
func errorShouldBeThrownIfErrorIsReturnedFromDeleteRESTChatAPI() async throws {
// Given
let realtime = MockRealtime { @Sendable () throws(ARTErrorInfo) in
throw ARTErrorInfo(domain: "SomeDomain", code: 123)
}
let chatAPI = ChatAPI(realtime: realtime)
let channel = MockRealtimeChannel()
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", clientID: "clientId", logger: TestLogger())

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let message = try Message(jsonObject: ["serial": "0", "version": "0", "text": "hey", "clientId": "0", "action": "message.update", "metadata": [:], "headers": [:]]) // arbitrary
_ = try await defaultMessages.delete(message: message, params: .init())
}
await #expect {
try await doIt()
} throws: { error in
error as? ARTErrorInfo == ARTErrorInfo(domain: "SomeDomain", code: 123)
}
}

// @spec CHA-M5a
@Test
func subscriptionPointIsChannelSerialWhenUnderlyingRealtimeChannelIsAttached() async throws {
// Given
let realtime = MockRealtime {
MockHTTPPaginatedResponse.successSendMessageWithNoItems
MockHTTPPaginatedResponse.successGetMessagesWithNoItems
}
let channelSerial = "123"
let chatAPI = ChatAPI(realtime: realtime)
Expand All @@ -83,7 +166,7 @@ struct DefaultMessagesTests {
func subscriptionPointIsAttachSerialWhenUnderlyingRealtimeChannelIsNotAttached() async throws {
// Given
let realtime = MockRealtime {
MockHTTPPaginatedResponse.successSendMessageWithNoItems
MockHTTPPaginatedResponse.successGetMessagesWithNoItems
}
let attachSerial = "attach123"
let chatAPI = ChatAPI(realtime: realtime)
Expand Down Expand Up @@ -113,7 +196,7 @@ struct DefaultMessagesTests {
let attachSerial = "attach123"
let channelSerial = "channel456"
let realtime = MockRealtime {
MockHTTPPaginatedResponse.successSendMessageWithNoItems
MockHTTPPaginatedResponse.successGetMessagesWithNoItems
}
let chatAPI = ChatAPI(realtime: realtime)
let channel = MockRealtimeChannel(
Expand Down Expand Up @@ -157,7 +240,7 @@ struct DefaultMessagesTests {
let attachSerial = "attach123"
let channelSerial = "channel456"
let realtime = MockRealtime {
MockHTTPPaginatedResponse.successSendMessageWithNoItems
MockHTTPPaginatedResponse.successGetMessagesWithNoItems
}
let chatAPI = ChatAPI(realtime: realtime)
let channel = MockRealtimeChannel(
Expand Down
2 changes: 2 additions & 0 deletions Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ extension MockHTTPPaginatedResponse {
items: [
[
"serial": "3446456",
"version": "3446456",
"timestamp": 1_631_840_000_000,
"createdAt": 1_631_840_000_000,
"text": "hello",
],
Expand Down