diff --git a/Examples/xx-messenger/Sources/AppCore/SendMessage/SendGroupMessage.swift b/Examples/xx-messenger/Sources/AppCore/SendMessage/SendGroupMessage.swift index 65bceac997e7dbb7190d08376a1890ed54f7c4a8..9427053788da3f869e9dd6b0845d581c7e8905c2 100644 --- a/Examples/xx-messenger/Sources/AppCore/SendMessage/SendGroupMessage.swift +++ b/Examples/xx-messenger/Sources/AppCore/SendMessage/SendGroupMessage.swift @@ -27,12 +27,52 @@ extension SendGroupMessage { now: @escaping () -> Date ) -> SendGroupMessage { SendGroupMessage { text, groupId, onError, completion in - // TODO: implement sending group message - struct Unimplemented: Error, LocalizedError { - var errorDescription: String? { "SendGroupMessage is not implemented!" } + do { + let chat = try messenger.groupChat.tryGet() + let myContactId = try messenger.e2e.tryGet().getContact().getId() + var message = try db().saveMessage(.init( + senderId: myContactId, + recipientId: nil, + groupId: groupId, + date: now(), + status: .sending, + isUnread: false, + text: text + )) + let payload = MessagePayload(text: message.text) + let report = try chat.send( + groupId: groupId, + message: try payload.encode() + ) + message.networkId = report.messageId + message.roundURL = report.roundURL + message = try db().saveMessage(message) + try messenger.cMix.tryGet().waitForRoundResult( + roundList: try report.encode(), + timeoutMS: 30_000, + callback: .init { result in + let status: XXModels.Message.Status + switch result { + case .delivered(_): + status = .sent + case .notDelivered(let timedOut): + status = timedOut ? .sendingTimedOut : .sendingFailed + } + do { + try db().bulkUpdateMessages( + .init(id: [message.id]), + .init(status: status) + ) + } catch { + onError(error) + } + completion() + } + ) + } catch { + onError(error) + completion() } - onError(Unimplemented()) - completion() } } } diff --git a/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendGroupMessageTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendGroupMessageTests.swift index a53e5e35e49a1fda9f12a0a11b751951da4a2df5..1df2eaa3717344509c590a462036218c0ff416b0 100644 --- a/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendGroupMessageTests.swift +++ b/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendGroupMessageTests.swift @@ -10,6 +10,13 @@ final class SendGroupMessageTests: XCTestCase { enum Action: Equatable { case didReceiveError(String) case didComplete + case didSaveMessage(XXModels.Message) + case didSend(groupId: Data, message: Data, tag: String?) + case didWaitForRoundResults(roundList: Data, timeoutMS: Int) + case didUpdateMessage( + query: XXModels.Message.Query, + assignments: XXModels.Message.Assignments + ) } var actions: [Action]! @@ -25,9 +32,58 @@ final class SendGroupMessageTests: XCTestCase { func testSend() { let text = "Hello!" let groupId = "group-id".data(using: .utf8)! + let myContactId = "my-contact-id".data(using: .utf8)! + let messageId: Int64 = 321 + let sendReport = GroupSendReport( + rounds: [], + roundURL: "round-url", + timestamp: 1234, + messageId: "message-id".data(using: .utf8)! + ) + + var messageDeliveryCallback: MessageDeliveryCallback? var messenger: Messenger = .unimplemented + messenger.groupChat.get = { + var groupChat: GroupChat = .unimplemented + groupChat.send.run = { groupId, message, tag in + self.actions.append(.didSend(groupId: groupId, message: message, tag: tag)) + return sendReport + } + return groupChat + } + messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in myContactId } + return contact + } + return e2e + } + messenger.cMix.get = { + var cMix: CMix = .unimplemented + cMix.waitForRoundResult.run = { roundList, timeoutMS, callback in + self.actions.append(.didWaitForRoundResults(roundList: roundList, timeoutMS: timeoutMS)) + messageDeliveryCallback = callback + } + return cMix + } var db: DBManagerGetDB = .unimplemented + db.run = { + var db: Database = .unimplemented + db.saveMessage.run = { message in + self.actions.append(.didSaveMessage(message)) + var message = message + message.id = messageId + return message + } + db.bulkUpdateMessages.run = { query, assignments in + self.actions.append(.didUpdateMessage(query: query, assignments: assignments)) + return 1 + } + return db + } let now = Date() let send: SendGroupMessage = .live( messenger: messenger, @@ -47,9 +103,177 @@ final class SendGroupMessageTests: XCTestCase { ) XCTAssertNoDifference(actions, [ - .didReceiveError("SendGroupMessage is not implemented!"), + .didSaveMessage(.init( + senderId: myContactId, + recipientId: nil, + groupId: groupId, + date: now, + status: .sending, + isUnread: false, + text: text + )), + .didSend( + groupId: groupId, + message: try! MessagePayload(text: text).encode(), + tag: nil + ), + .didSaveMessage(.init( + id: messageId, + networkId: sendReport.messageId, + senderId: myContactId, + recipientId: nil, + groupId: groupId, + date: now, + status: .sending, + isUnread: false, + text: text, + roundURL: sendReport.roundURL + )), + .didWaitForRoundResults( + roundList: try! sendReport.encode(), + timeoutMS: 30_000 + ), + ]) + + actions = [] + messageDeliveryCallback?.handle(.delivered(roundResults: [])) + + XCTAssertNoDifference(actions, [ + .didUpdateMessage( + query: .init(id: [messageId]), + assignments: .init(status: .sent) + ), + .didComplete, + ]) + + actions = [] + messageDeliveryCallback?.handle(.notDelivered(timedOut: true)) + + XCTAssertNoDifference(actions, [ + .didUpdateMessage( + query: .init(id: [messageId]), + assignments: .init(status: .sendingTimedOut) + ), + .didComplete, + ]) + + actions = [] + messageDeliveryCallback?.handle(.notDelivered(timedOut: false)) + + XCTAssertNoDifference(actions, [ + .didUpdateMessage( + query: .init(id: [messageId]), + assignments: .init(status: .sendingFailed) + ), + .didComplete, + ]) + } + + func testSendDatabaseFailure() { + struct Failure: Error, Equatable {} + let failure = Failure() + + var messenger: Messenger = .unimplemented + messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in Data() } + return contact + } + return e2e + } + messenger.groupChat.get = { .unimplemented } + var db: DBManagerGetDB = .unimplemented + db.run = { throw failure } + let send: SendGroupMessage = .live( + messenger: messenger, + db: db, + now: XCTestDynamicOverlay.unimplemented("now", placeholder: Date()) + ) + + send( + text: "Hello", + to: "group-id".data(using: .utf8)!, + onError: { error in + self.actions.append(.didReceiveError(error.localizedDescription)) + }, + completion: { + self.actions.append(.didComplete) + } + ) + + XCTAssertNoDifference(actions, [ + .didReceiveError(failure.localizedDescription), .didComplete ]) } -} + func testBulkUpdateOnDeliveryFailure() { + struct Failure: Error, Equatable {} + let failure = Failure() + + var messageDeliveryCallback: MessageDeliveryCallback? + + var messenger: Messenger = .unimplemented + messenger.groupChat.get = { + var groupChat: GroupChat = .unimplemented + groupChat.send.run = { _, _, _ in + GroupSendReport( + rounds: [], + roundURL: "", + timestamp: 0, + messageId: Data() + ) + } + return groupChat + } + messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in Data() } + return contact + } + return e2e + } + messenger.cMix.get = { + var cMix: CMix = .unimplemented + cMix.waitForRoundResult.run = { _, _, callback in + messageDeliveryCallback = callback + } + return cMix + } + var db: DBManagerGetDB = .unimplemented + db.run = { + var db: Database = .unimplemented + db.saveMessage.run = { message in message } + db.bulkUpdateMessages.run = { _, _ in throw failure } + return db + } + let now = Date() + let send: SendGroupMessage = .live( + messenger: messenger, + db: db, + now: { now } + ) + + send( + text: "Hello", + to: Data(), + onError: { error in + self.actions.append(.didReceiveError(error.localizedDescription)) + }, + completion: { + self.actions.append(.didComplete) + } + ) + + messageDeliveryCallback?.handle(.delivered(roundResults: [])) + + XCTAssertNoDifference(actions, [ + .didReceiveError(failure.localizedDescription), + .didComplete, + ]) + } +}