From 305c2f8a7cbf3eb766c03bd6690eec87bdafd23e Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 14 Sep 2022 13:41:29 +0200 Subject: [PATCH] Implement SendMessage function --- .../AppCore/SendMessage/SendMessage.swift | 80 ++++++ .../SendMessage/SendMessageTests.swift | 253 ++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 Examples/xx-messenger/Sources/AppCore/SendMessage/SendMessage.swift create mode 100644 Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendMessageTests.swift diff --git a/Examples/xx-messenger/Sources/AppCore/SendMessage/SendMessage.swift b/Examples/xx-messenger/Sources/AppCore/SendMessage/SendMessage.swift new file mode 100644 index 00000000..b79a441e --- /dev/null +++ b/Examples/xx-messenger/Sources/AppCore/SendMessage/SendMessage.swift @@ -0,0 +1,80 @@ +import Foundation +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels + +public struct SendMessage { + public typealias OnError = (Error) -> Void + + public var run: (String, Data, @escaping OnError) -> Void + + public func callAsFunction( + text: String, + to recipientId: Data, + onError: @escaping OnError + ) { + run(text, recipientId, onError) + } +} + +extension SendMessage { + public static func live( + messenger: Messenger, + db: DBManagerGetDB, + now: @escaping () -> Date + ) -> SendMessage { + SendMessage { text, recipientId, onError in + do { + let myContactId = try messenger.e2e.tryGet().getContact().getId() + let message = try db().saveMessage(.init( + senderId: myContactId, + recipientId: recipientId, + groupId: nil, + date: now(), + status: .sending, + isUnread: false, + text: text + )) + let payload = MessagePayload(text: message.text) + let report = try messenger.sendMessage( + recipientId: recipientId, + payload: try payload.encode(), + deliveryCallback: { deliveryReport in + let status: XXModels.Message.Status + switch deliveryReport.result { + case .delivered: + status = .sent + case .notDelivered(let timedOut): + status = timedOut ? .sendingTimedOut : .sendingFailed + case .failure(let error): + status = .sendingFailed + onError(error) + } + do { + try db().bulkUpdateMessages( + .init(id: [message.id]), + .init(status: status) + ) + } catch { + onError(error) + } + } + ) + if var message = try db().fetchMessages(.init(id: [message.id])).first { + message.networkId = report.messageId + message.roundURL = report.roundURL + _ = try db().saveMessage(message) + } + } catch { + onError(error) + } + } + } +} + +extension SendMessage { + public static let unimplemented = SendMessage( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendMessageTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendMessageTests.swift new file mode 100644 index 00000000..9f7600f4 --- /dev/null +++ b/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendMessageTests.swift @@ -0,0 +1,253 @@ +import CustomDump +import XCTest +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels +@testable import AppCore + +final class SendMessageTests: XCTestCase { + func testSend() { + struct MessengerSendMessageParams: Equatable { + var recipientId: Data + var payload: Data + } + struct MessageBulkUpdate: Equatable { + var query: XXModels.Message.Query + var assignments: XXModels.Message.Assignments + } + + var messengerDidSendMessageWithParams: [MessengerSendMessageParams] = [] + var messengerDidSendMessageWithDeliveryCallback: [MessengerSendMessage.DeliveryCallback?] = [] + var dbDidSaveMessage: [XXModels.Message] = [] + var dbDidFetchMessagesWithQuery: [XXModels.Message.Query] = [] + var dbDidBulkUpdateMessages: [MessageBulkUpdate] = [] + var didReceiveError: [Error] = [] + + let myContactId = "my-contact-id".data(using: .utf8)! + let text = "Hello" + let recipientId = "recipient-id".data(using: .utf8)! + let messageId: Int64 = 123 + let sendReport = E2ESendReport( + rounds: [], + roundURL: "round-url", + messageId: "message-id".data(using: .utf8)!, + timestamp: 0, + keyResidue: Data() + ) + var dbFetchMessagesResult: [XXModels.Message] = [] + + var messenger: Messenger = .unimplemented + 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.sendMessage.run = { recipientId, payload, deliveryCallback in + messengerDidSendMessageWithParams.append(.init(recipientId: recipientId, payload: payload)) + messengerDidSendMessageWithDeliveryCallback.append(deliveryCallback) + return sendReport + } + var db: DBManagerGetDB = .unimplemented + db.run = { + var db: Database = .failing + db.saveMessage.run = { message in + dbDidSaveMessage.append(message) + var message = message + message.id = messageId + dbFetchMessagesResult = [message] + return message + } + db.fetchMessages.run = { query in + dbDidFetchMessagesWithQuery.append(query) + return dbFetchMessagesResult + } + db.bulkUpdateMessages.run = { query, assignments in + dbDidBulkUpdateMessages.append(.init(query: query, assignments: assignments)) + return 0 + } + return db + } + let now = Date() + let send: SendMessage = .live( + messenger: messenger, + db: db, + now: { now } + ) + + send( + text: text, + to: recipientId, + onError: { error in didReceiveError.append(error) } + ) + + XCTAssertNoDifference(dbDidSaveMessage, [ + .init( + senderId: myContactId, + recipientId: recipientId, + groupId: nil, + date: now, + status: .sending, + isUnread: false, + text: text + ), + .init( + id: messageId, + networkId: sendReport.messageId!, + senderId: myContactId, + recipientId: recipientId, + groupId: nil, + date: now, + status: .sending, + isUnread: false, + text: text, + roundURL: sendReport.roundURL! + ), + ]) + XCTAssertNoDifference(messengerDidSendMessageWithParams, [ + .init(recipientId: recipientId, payload: try! MessagePayload(text: text).encode()) + ]) + XCTAssertNoDifference(dbDidFetchMessagesWithQuery, [ + .init(id: [messageId]) + ]) + + dbDidBulkUpdateMessages = [] + messengerDidSendMessageWithDeliveryCallback.first??(.init( + report: sendReport, + result: .delivered + )) + + XCTAssertNoDifference(dbDidBulkUpdateMessages, [ + .init(query: .init(id: [messageId]), assignments: .init(status: .sent)) + ]) + + dbDidBulkUpdateMessages = [] + messengerDidSendMessageWithDeliveryCallback.first??(.init( + report: sendReport, + result: .notDelivered(timedOut: true) + )) + + XCTAssertNoDifference(dbDidBulkUpdateMessages, [ + .init(query: .init(id: [messageId]), assignments: .init(status: .sendingTimedOut)) + ]) + + dbDidBulkUpdateMessages = [] + messengerDidSendMessageWithDeliveryCallback.first??(.init( + report: sendReport, + result: .notDelivered(timedOut: false) + )) + + XCTAssertNoDifference(dbDidBulkUpdateMessages, [ + .init(query: .init(id: [messageId]), assignments: .init(status: .sendingFailed)) + ]) + + dbDidBulkUpdateMessages = [] + let deliveryFailure = NSError(domain: "test", code: 123) + messengerDidSendMessageWithDeliveryCallback.first??(.init( + report: sendReport, + result: .failure(deliveryFailure) + )) + + XCTAssertNoDifference(dbDidBulkUpdateMessages, [ + .init(query: .init(id: [messageId]), assignments: .init(status: .sendingFailed)) + ]) + XCTAssertNoDifference(didReceiveError.count, 1) + XCTAssertNoDifference(didReceiveError.first as NSError?, deliveryFailure) + } + + func testSendDatabaseFailure() { + struct Failure: Error, Equatable {} + let error = Failure() + + var didReceiveError: [Error] = [] + + 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 + } + var db: DBManagerGetDB = .unimplemented + db.run = { throw error } + let send: SendMessage = .live( + messenger: messenger, + db: db, + now: XCTUnimplemented("now", placeholder: Date()) + ) + + send( + text: "Hello", + to: "recipient-id".data(using: .utf8)!, + onError: { error in didReceiveError.append(error) } + ) + + XCTAssertNoDifference(didReceiveError.count, 1) + XCTAssertNoDifference(didReceiveError.first as? Failure, error) + } + + func testBulkUpdateOnDeliveryFailure() { + struct Failure: Error, Equatable {} + let error = Failure() + let sendReport = E2ESendReport( + rounds: [], + roundURL: "", + messageId: Data(), + timestamp: 0, + keyResidue: Data() + ) + + var messengerDidSendMessageWithDeliveryCallback: [MessengerSendMessage.DeliveryCallback?] = [] + var didReceiveError: [Error] = [] + + 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.sendMessage.run = { _, _, deliveryCallback in + messengerDidSendMessageWithDeliveryCallback.append(deliveryCallback) + return sendReport + } + var db: DBManagerGetDB = .unimplemented + db.run = { + var db: Database = .failing + db.saveMessage.run = { $0 } + db.fetchMessages.run = { _ in [] } + db.bulkUpdateMessages.run = { _, _ in throw error } + return db + } + let send: SendMessage = .live( + messenger: messenger, + db: db, + now: Date.init + ) + + send( + text: "Hello", + to: "recipient-id".data(using: .utf8)!, + onError: { error in didReceiveError.append(error) } + ) + + messengerDidSendMessageWithDeliveryCallback.first??(.init( + report: sendReport, + result: .delivered + )) + + XCTAssertNoDifference(didReceiveError.count, 1) + XCTAssertNoDifference(didReceiveError.first as? Failure, error) + } +} + -- GitLab