From dfb51c9fac4f15e85dd61d069246cb0b838e0a5a Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 13 Sep 2022 12:22:49 +0200
Subject: [PATCH] Add MessengerSendMessage function

---
 .../Functions/MessengerSendMessage.swift      |  85 +++++++
 .../Messenger/Messenger.swift                 |   7 +-
 .../Functions/MessengerSendMessageTests.swift | 233 ++++++++++++++++++
 3 files changed, 323 insertions(+), 2 deletions(-)
 create mode 100644 Sources/XXMessengerClient/Messenger/Functions/MessengerSendMessage.swift
 create mode 100644 Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendMessageTests.swift

diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerSendMessage.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendMessage.swift
new file mode 100644
index 00000000..118ca046
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendMessage.swift
@@ -0,0 +1,85 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerSendMessage {
+  public struct DeliveryReport: Equatable {
+    public enum Result: Equatable {
+      case delivered
+      case notDelivered(timedOut: Bool)
+      case failure(NSError)
+    }
+
+    public init(
+      report: E2ESendReport,
+      result: Result
+    ) {
+      self.report = report
+      self.result = result
+    }
+
+    public var report: E2ESendReport
+    public var result: Result
+  }
+
+  public typealias DeliveryCallback = (DeliveryReport) -> Void
+
+  public enum Error: Swift.Error, Equatable {
+    case notLoaded
+    case notConnected
+  }
+
+  public var run: (Data, Data, DeliveryCallback?) throws -> E2ESendReport
+
+  public func callAsFunction(
+    recipientId: Data,
+    payload: Data,
+    deliveryCallback: DeliveryCallback?
+  ) throws -> E2ESendReport {
+    try run(recipientId, payload, deliveryCallback)
+  }
+}
+
+extension MessengerSendMessage {
+  public static func live(_ env: MessengerEnvironment) -> MessengerSendMessage {
+    MessengerSendMessage { recipientId, payload, deliveryCallback in
+      guard let cMix = env.cMix() else {
+        throw Error.notLoaded
+      }
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      let report = try e2e.send(
+        messageType: 2,
+        recipientId: recipientId,
+        payload: payload,
+        e2eParams: env.getE2EParams()
+      )
+      if let deliveryCallback = deliveryCallback {
+        do {
+          try cMix.waitForRoundResult(
+            roundList: try report.encode(),
+            timeoutMS: 30_000,
+            callback: .init { result in
+              switch result {
+              case .delivered(_):
+                deliveryCallback(.init(report: report, result: .delivered))
+              case .notDelivered(let timedOut):
+                deliveryCallback(.init(report: report, result: .notDelivered(timedOut: timedOut)))
+              }
+            }
+          )
+        } catch {
+          deliveryCallback(.init(report: report, result: .failure(error as NSError)))
+        }
+      }
+      return report
+    }
+  }
+}
+
+extension MessengerSendMessage {
+  public static let unimplemented = MessengerSendMessage(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift
index 8e50a4ae..4995f9a1 100644
--- a/Sources/XXMessengerClient/Messenger/Messenger.swift
+++ b/Sources/XXMessengerClient/Messenger/Messenger.swift
@@ -24,6 +24,7 @@ public struct Messenger {
   public var searchUsers: MessengerSearchUsers
   public var registerForNotifications: MessengerRegisterForNotifications
   public var verifyContact: MessengerVerifyContact
+  public var sendMessage: MessengerSendMessage
 }
 
 extension Messenger {
@@ -51,7 +52,8 @@ extension Messenger {
       destroy: .live(env),
       searchUsers: .live(env),
       registerForNotifications: .live(env),
-      verifyContact: .live(env)
+      verifyContact: .live(env),
+      sendMessage: .live(env)
     )
   }
 }
@@ -80,6 +82,7 @@ extension Messenger {
     destroy: .unimplemented,
     searchUsers: .unimplemented,
     registerForNotifications: .unimplemented,
-    verifyContact: .unimplemented
+    verifyContact: .unimplemented,
+    sendMessage: .unimplemented
   )
 }
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendMessageTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendMessageTests.swift
new file mode 100644
index 00000000..c429c742
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendMessageTests.swift
@@ -0,0 +1,233 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerSendMessageTests: XCTestCase {
+  func testSend() throws {
+    struct E2ESendParams: Equatable {
+      var messageType: Int
+      var recipientId: Data
+      var payload: Data
+      var e2eParams: Data
+    }
+    var e2eDidSendWithParams: [E2ESendParams] = []
+
+    let e2eSendReport = E2ESendReport(
+      rounds: [1, 2, 3],
+      roundURL: "round-url",
+      messageId: "message-id".data(using: .utf8)!,
+      timestamp: 123,
+      keyResidue: "key-residue".data(using: .utf8)!
+    )
+
+    var env: MessengerEnvironment = .unimplemented
+    env.getE2EParams.run = { "e2e-params".data(using: .utf8)! }
+    env.cMix.get = { .unimplemented }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.send.run = { messageType, recipientId, payload, e2eParams in
+        e2eDidSendWithParams.append(.init(
+          messageType: messageType,
+          recipientId: recipientId,
+          payload: payload,
+          e2eParams: e2eParams
+        ))
+        return e2eSendReport
+      }
+      return e2e
+    }
+    let send: MessengerSendMessage = .live(env)
+
+    let report = try send(
+      recipientId: "recipient-id".data(using: .utf8)!,
+      payload: "payload".data(using: .utf8)!,
+      deliveryCallback: nil
+    )
+
+    XCTAssertNoDifference(e2eDidSendWithParams, [.init(
+      messageType: 2,
+      recipientId: "recipient-id".data(using: .utf8)!,
+      payload: "payload".data(using: .utf8)!,
+      e2eParams: "e2e-params".data(using: .utf8)!
+    )])
+
+    XCTAssertNoDifference(report, e2eSendReport)
+  }
+
+  func testSendWithDeliveryCallback() throws {
+    struct WaitForRoundResultsParams: Equatable {
+      var roundList: Data
+      var timeoutMS: Int
+    }
+    var didWaitForRoundResultsWithParams: [WaitForRoundResultsParams] = []
+    var didWaitForRoundResultsWithCallback: [MessageDeliveryCallback] = []
+    var didReceiveDeliveryReport: [MessengerSendMessage.DeliveryReport] = []
+
+    let e2eSendReport = E2ESendReport(
+      rounds: [1, 2, 3],
+      roundURL: "round-url",
+      messageId: "message-id".data(using: .utf8)!,
+      timestamp: 123,
+      keyResidue: "key-residue".data(using: .utf8)!
+    )
+
+    var env: MessengerEnvironment = .unimplemented
+    env.getE2EParams.run = { "e2e-params".data(using: .utf8)! }
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.waitForRoundResult.run = { roundList, timeoutMS, callback in
+        didWaitForRoundResultsWithParams.append(.init(roundList: roundList, timeoutMS: timeoutMS))
+        didWaitForRoundResultsWithCallback.append(callback)
+      }
+      return cMix
+    }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.send.run = { _, _, _, _ in e2eSendReport }
+      return e2e
+    }
+    let send: MessengerSendMessage = .live(env)
+
+    let report = try send(
+      recipientId: "recipient-id".data(using: .utf8)!,
+      payload: "payload".data(using: .utf8)!,
+      deliveryCallback: .init { deliveryReport in
+        didReceiveDeliveryReport.append(deliveryReport)
+      }
+    )
+
+    XCTAssertNoDifference(report, e2eSendReport)
+
+    XCTAssertNoDifference(didWaitForRoundResultsWithParams, [
+      .init(roundList: try! e2eSendReport.encode(), timeoutMS: 30_000),
+    ])
+
+    didWaitForRoundResultsWithCallback.first?.handle(.delivered(roundResults: [1, 2, 3]))
+
+    XCTAssertNoDifference(didReceiveDeliveryReport, [
+      .init(report: report, result: .delivered)
+    ])
+
+    didReceiveDeliveryReport.removeAll()
+    didWaitForRoundResultsWithCallback.first?.handle(.notDelivered(timedOut: false))
+
+    XCTAssertNoDifference(didReceiveDeliveryReport, [
+      .init(report: report, result: .notDelivered(timedOut: false))
+    ])
+
+    didReceiveDeliveryReport.removeAll()
+    didWaitForRoundResultsWithCallback.first?.handle(.notDelivered(timedOut: true))
+
+    XCTAssertNoDifference(didReceiveDeliveryReport, [
+      .init(report: report, result: .notDelivered(timedOut: true))
+    ])
+  }
+
+  func testSendWhenNotLoaded() {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = { nil }
+    let send: MessengerSendMessage = .live(env)
+
+    XCTAssertThrowsError(
+      try send(
+        recipientId: Data(),
+        payload: Data(),
+        deliveryCallback: nil
+      )
+    ) { error in
+      XCTAssertNoDifference(
+        error as? MessengerSendMessage.Error,
+        .notLoaded
+      )
+    }
+  }
+
+  func testSendWhenNotConnected() {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = { .unimplemented }
+    env.e2e.get = { nil }
+    let send: MessengerSendMessage = .live(env)
+
+    XCTAssertThrowsError(
+      try send(
+        recipientId: Data(),
+        payload: Data(),
+        deliveryCallback: nil
+      )
+    ) { error in
+      XCTAssertNoDifference(
+        error as? MessengerSendMessage.Error,
+        .notConnected
+      )
+    }
+  }
+
+  func testSendFailure() {
+    struct Failure: Error, Equatable {}
+    let error = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.getE2EParams.run = { "e2e-params".data(using: .utf8)! }
+    env.cMix.get = { .unimplemented }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.send.run = { _, _, _, _ in throw error }
+      return e2e
+    }
+    let send: MessengerSendMessage = .live(env)
+
+    XCTAssertThrowsError(
+      try send(
+        recipientId: "recipient-id".data(using: .utf8)!,
+        payload: "payload".data(using: .utf8)!,
+        deliveryCallback: nil
+      )
+    ) { err in
+      XCTAssertNoDifference(err as? Failure, error)
+    }
+  }
+
+  func testSendDeliveryFailure() throws {
+    let e2eSendReport = E2ESendReport(
+      rounds: [1, 2, 3],
+      roundURL: "round-url",
+      messageId: "message-id".data(using: .utf8)!,
+      timestamp: 123,
+      keyResidue: "key-residue".data(using: .utf8)!
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    var didReceiveDeliveryReport: [MessengerSendMessage.DeliveryReport] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.getE2EParams.run = { "e2e-params".data(using: .utf8)! }
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.waitForRoundResult.run = { _, _, _ in throw error }
+      return cMix
+    }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.send.run = { _, _, _, _ in e2eSendReport }
+      return e2e
+    }
+    let send: MessengerSendMessage = .live(env)
+
+    let report = try send(
+      recipientId: "recipient-id".data(using: .utf8)!,
+      payload: "payload".data(using: .utf8)!,
+      deliveryCallback: .init { deliveryReport in
+        didReceiveDeliveryReport.append(deliveryReport)
+      }
+    )
+
+    XCTAssertNoDifference(report, e2eSendReport)
+
+    XCTAssertNoDifference(didReceiveDeliveryReport, [
+      .init(report: report, result: .failure(error as NSError))
+    ])
+  }
+}
-- 
GitLab