From 23b7b82a0a92aa2839cc239c0fbe9a34db5d3908 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 28 Sep 2022 22:56:33 +0200
Subject: [PATCH] Add MessengerStop function wrapper

---
 .../Messenger/Functions/MessengerStop.swift   |  60 +++++++++
 .../Messenger/Messenger.swift                 |   3 +
 .../Functions/MessengerStopTests.swift        | 123 ++++++++++++++++++
 3 files changed, 186 insertions(+)
 create mode 100644 Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift
 create mode 100644 Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopTests.swift

diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift
new file mode 100644
index 00000000..6e49054f
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift
@@ -0,0 +1,60 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerStop {
+  public struct Wait: Equatable {
+    public init(
+      sleepInterval: TimeInterval = 1,
+      retries: Int = 10
+    ) {
+      self.sleepInterval = sleepInterval
+      self.retries = retries
+    }
+
+    public var sleepInterval: TimeInterval
+    public var retries: Int
+  }
+
+  public enum Error: Swift.Error {
+    case notLoaded
+    case timedOut
+  }
+
+  public var run: (Wait?) throws -> Void
+
+  public func callAsFunction(wait: Wait? = nil) throws -> Void {
+    try run(wait)
+  }
+}
+
+extension MessengerStop {
+  public static func live(_ env: MessengerEnvironment) -> MessengerStop {
+    MessengerStop { wait in
+      guard let cMix = env.cMix() else {
+        throw Error.notLoaded
+      }
+      guard cMix.networkFollowerStatus() == .running else {
+        return
+      }
+      try cMix.stopNetworkFollower()
+      guard let wait else { return }
+      var retries = wait.retries
+      var hasRunningProcesses = cMix.hasRunningProcesses()
+      while retries > 0 && hasRunningProcesses {
+        env.sleep(wait.sleepInterval)
+        hasRunningProcesses = cMix.hasRunningProcesses()
+        retries -= 1
+      }
+      if hasRunningProcesses {
+        throw Error.timedOut
+      }
+    }
+  }
+}
+
+extension MessengerStop {
+  public static let unimplemented = MessengerStop(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift
index ce598c54..41c61329 100644
--- a/Sources/XXMessengerClient/Messenger/Messenger.swift
+++ b/Sources/XXMessengerClient/Messenger/Messenger.swift
@@ -13,6 +13,7 @@ public struct Messenger {
   public var registerAuthCallbacks: MessengerRegisterAuthCallbacks
   public var registerMessageListener: MessengerRegisterMessageListener
   public var start: MessengerStart
+  public var stop: MessengerStop
   public var isConnected: MessengerIsConnected
   public var connect: MessengerConnect
   public var isListeningForMessages: MessengerIsListeningForMessages
@@ -53,6 +54,7 @@ extension Messenger {
       registerAuthCallbacks: .live(env),
       registerMessageListener: .live(env),
       start: .live(env),
+      stop: .live(env),
       isConnected: .live(env),
       connect: .live(env),
       isListeningForMessages: .live(env),
@@ -94,6 +96,7 @@ extension Messenger {
     registerAuthCallbacks: .unimplemented,
     registerMessageListener: .unimplemented,
     start: .unimplemented,
+    stop: .unimplemented,
     isConnected: .unimplemented,
     connect: .unimplemented,
     isListeningForMessages: .unimplemented,
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopTests.swift
new file mode 100644
index 00000000..e9cfd928
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopTests.swift
@@ -0,0 +1,123 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerStopTests: XCTestCase {
+  func testStop() throws {
+    var didStopNetworkFollower = 0
+
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .running }
+      cMix.stopNetworkFollower.run = { didStopNetworkFollower += 1 }
+      return cMix
+    }
+    let stop: MessengerStop = .live(env)
+
+    try stop()
+
+    XCTAssertNoDifference(didStopNetworkFollower, 1)
+  }
+
+  func testStopWhenNotLoaded() {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = { nil }
+    let stop: MessengerStop = .live(env)
+
+    XCTAssertThrowsError(try stop()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStop.Error.notLoaded as NSError
+      )
+    }
+  }
+
+  func testStopWhenNotRunning() throws {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .stopped }
+      return cMix
+    }
+    let stop: MessengerStop = .live(env)
+
+    try stop()
+  }
+
+  func testStopFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .running }
+      cMix.stopNetworkFollower.run = { throw failure }
+      return cMix
+    }
+    let stop: MessengerStop = .live(env)
+
+    XCTAssertThrowsError(try stop()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        failure as NSError
+      )
+    }
+  }
+
+  func testStopAndWait() throws {
+    var hasRunningProcesses: [Bool] = [true, true, false]
+    var didStopNetworkFollower = 0
+    var didSleep: [TimeInterval] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .running }
+      cMix.stopNetworkFollower.run = { didStopNetworkFollower += 1 }
+      cMix.hasRunningProcesses.run = { hasRunningProcesses.removeFirst() }
+      return cMix
+    }
+    env.sleep = { didSleep.append($0) }
+    let stop: MessengerStop = .live(env)
+
+    try stop(wait: .init(sleepInterval: 123, retries: 3))
+
+    XCTAssertNoDifference(didStopNetworkFollower, 1)
+    XCTAssertNoDifference(didSleep, [123, 123])
+  }
+
+  func testStopAndWaitTimeout() {
+    var hasRunningProcesses: [Bool] = [true, true, true, true]
+    var didStopNetworkFollower = 0
+    var didSleep: [TimeInterval] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .running }
+      cMix.stopNetworkFollower.run = { didStopNetworkFollower += 1 }
+      cMix.hasRunningProcesses.run = { hasRunningProcesses.removeFirst() }
+      return cMix
+    }
+    env.sleep = { didSleep.append($0) }
+    let stop: MessengerStop = .live(env)
+
+    XCTAssertThrowsError(
+      try stop(wait: .init(
+        sleepInterval: 123,
+        retries: 3
+      ))
+    ) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStop.Error.timedOut as NSError
+      )
+    }
+
+    XCTAssertNoDifference(didStopNetworkFollower, 1)
+    XCTAssertNoDifference(didSleep, [123, 123, 123])
+  }
+}
-- 
GitLab