From 47d1a2b33c59807bc67e90357c7d44914d08854f Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 6 Jun 2022 13:55:14 +0200
Subject: [PATCH] Monitor network health in SessionFeature

---
 .../SessionFeature/SessionFeature.swift       | 39 ++++++++++++++++++-
 .../Sources/SessionFeature/SessionView.swift  |  8 ++++
 .../SessionFeatureTests.swift                 | 29 ++++++++++++++
 3 files changed, 75 insertions(+), 1 deletion(-)

diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
index b07a14cc..0756f738 100644
--- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift
+++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
@@ -7,15 +7,18 @@ public struct SessionState: Equatable {
   public init(
     id: UUID,
     networkFollowerStatus: NetworkFollowerStatus? = nil,
+    isNetworkHealthy: Bool? = nil,
     error: ErrorState? = nil
   ) {
     self.id = id
     self.networkFollowerStatus = networkFollowerStatus
+    self.isNetworkHealthy = isNetworkHealthy
     self.error = error
   }
 
   public var id: UUID
   public var networkFollowerStatus: NetworkFollowerStatus?
+  public var isNetworkHealthy: Bool?
   public var error: ErrorState?
 }
 
@@ -25,8 +28,10 @@ public enum SessionAction: Equatable {
   case didUpdateNetworkFollowerStatus(NetworkFollowerStatus?)
   case runNetworkFollower(Bool)
   case networkFollowerDidFail(NSError)
-  case error(ErrorAction)
+  case monitorNetworkHealth(Bool)
+  case didUpdateNetworkHealth(Bool?)
   case didDismissError
+  case error(ErrorAction)
 }
 
 public struct SessionEnvironment {
@@ -51,6 +56,7 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm
   case .viewDidLoad:
     return .merge([
       .init(value: .updateNetworkFollowerStatus),
+      .init(value: .monitorNetworkHealth(true)),
     ])
 
   case .updateNetworkFollowerStatus:
@@ -91,9 +97,40 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm
     state.error = ErrorState(error: error)
     return .none
 
+  case .monitorNetworkHealth(let start):
+    struct MonitorEffectId: Hashable {
+      var id: UUID
+    }
+    let effectId = MonitorEffectId(id: state.id)
+    if start {
+      return Effect.run { subscriber in
+        var cancellable = env.getClient()?.monitorNetworkHealth { isHealthy in
+          subscriber.send(.didUpdateNetworkHealth(isHealthy))
+        }
+        return AnyCancellable {
+          cancellable?.cancel()
+        }
+      }
+      .subscribe(on: env.bgScheduler)
+      .receive(on: env.mainScheduler)
+      .eraseToEffect()
+      .cancellable(id: effectId, cancelInFlight: true)
+    } else {
+      return Effect.cancel(id: effectId)
+        .subscribe(on: env.bgScheduler)
+        .eraseToEffect()
+    }
+
+  case .didUpdateNetworkHealth(let isHealthy):
+    state.isNetworkHealthy = isHealthy
+    return .none
+
   case .didDismissError:
     state.error = nil
     return .none
+
+  case .error(_):
+    return .none
   }
 }
 
diff --git a/Example/example-app/Sources/SessionFeature/SessionView.swift b/Example/example-app/Sources/SessionFeature/SessionView.swift
index 74b4d130..395cfcb1 100644
--- a/Example/example-app/Sources/SessionFeature/SessionView.swift
+++ b/Example/example-app/Sources/SessionFeature/SessionView.swift
@@ -13,9 +13,11 @@ public struct SessionView: View {
 
   struct ViewState: Equatable {
     let networkFollowerStatus: NetworkFollowerStatus?
+    let isNetworkHealthy: Bool?
 
     init(state: SessionState) {
       networkFollowerStatus = state.networkFollowerStatus
+      isNetworkHealthy = state.isNetworkHealthy
     }
   }
 
@@ -41,6 +43,12 @@ public struct SessionView: View {
         } header: {
           Text("Network follower")
         }
+
+        Section {
+          NetworkHealthStatusView(status: viewStore.isNetworkHealthy)
+        } header: {
+          Text("Network health")
+        }
       }
       .navigationTitle("Session")
       .task {
diff --git a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
index 092573ed..2ada840d 100644
--- a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
+++ b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
@@ -7,6 +7,9 @@ import XCTest
 final class SessionFeatureTests: XCTestCase {
   func testViewDidLoad() {
     var networkFollowerStatus: NetworkFollowerStatus!
+    var didStartMonitoringNetworkHealth = 0
+    var didStopMonitoringNetworkHealth = 0
+    var networkHealthCallback: ((Bool) -> Void)!
     let bgScheduler = DispatchQueue.test
     let mainScheduler = DispatchQueue.test
 
@@ -14,6 +17,13 @@ final class SessionFeatureTests: XCTestCase {
     env.getClient = {
       var client = Client.failing
       client.networkFollower.status.status = { networkFollowerStatus }
+      client.monitorNetworkHealth.listen = { callback in
+        networkHealthCallback = callback
+        didStartMonitoringNetworkHealth += 1
+        return Cancellable {
+          didStopMonitoringNetworkHealth += 1
+        }
+      }
       return client
     }
     env.bgScheduler = bgScheduler.eraseToAnyScheduler()
@@ -28,6 +38,7 @@ final class SessionFeatureTests: XCTestCase {
     store.send(.viewDidLoad)
 
     store.receive(.updateNetworkFollowerStatus)
+    store.receive(.monitorNetworkHealth(true))
 
     networkFollowerStatus = .stopped
     bgScheduler.advance()
@@ -36,6 +47,24 @@ final class SessionFeatureTests: XCTestCase {
     store.receive(.didUpdateNetworkFollowerStatus(.stopped)) {
       $0.networkFollowerStatus = .stopped
     }
+
+    XCTAssertEqual(didStartMonitoringNetworkHealth, 1)
+    XCTAssertEqual(didStopMonitoringNetworkHealth, 0)
+
+    networkHealthCallback(true)
+    bgScheduler.advance()
+    mainScheduler.advance()
+
+    store.receive(.didUpdateNetworkHealth(true)) {
+      $0.isNetworkHealthy = true
+    }
+
+    store.send(.monitorNetworkHealth(false))
+
+    bgScheduler.advance()
+
+    XCTAssertEqual(didStartMonitoringNetworkHealth, 1)
+    XCTAssertEqual(didStopMonitoringNetworkHealth, 1)
   }
 
   func testStartStopNetworkFollower() {
-- 
GitLab