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