From 5a729d9f7e577211d5abcc48deb976396ad9e345 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Thu, 29 Sep 2022 15:04:13 +0200
Subject: [PATCH] Add BackupStorage utility

---
 .../Utils/BackupStorage.swift                 | 66 +++++++++++++++++
 .../Utils/BackupStorageTests.swift            | 70 +++++++++++++++++++
 2 files changed, 136 insertions(+)
 create mode 100644 Sources/XXMessengerClient/Utils/BackupStorage.swift
 create mode 100644 Tests/XXMessengerClientTests/Utils/BackupStorageTests.swift

diff --git a/Sources/XXMessengerClient/Utils/BackupStorage.swift b/Sources/XXMessengerClient/Utils/BackupStorage.swift
new file mode 100644
index 00000000..b13c86cb
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/BackupStorage.swift
@@ -0,0 +1,66 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct BackupStorage {
+  public struct Backup: Equatable {
+    public init(
+      date: Date,
+      data: Data
+    ) {
+      self.date = date
+      self.data = data
+    }
+
+    public var date: Date
+    public var data: Data
+  }
+
+  public typealias Observer = (Backup?) -> Void
+
+  public var store: (Data) -> Void
+  public var observe: (@escaping Observer) -> Cancellable
+  public var remove: () -> Void
+}
+
+extension BackupStorage {
+  public static func live(
+    now: @escaping () -> Date
+  ) -> BackupStorage {
+    var observers: [UUID: Observer] = [:]
+    var backup: Backup?
+    func notifyObservers() {
+      observers.values.forEach { $0(backup) }
+    }
+
+    return BackupStorage(
+      store: { data in
+        backup = Backup(
+          date: now(),
+          data: data
+        )
+        notifyObservers()
+      },
+      observe: { observer in
+        let id = UUID()
+        observers[id] = observer
+        defer { observers[id]?(backup) }
+        return Cancellable {
+          observers[id] = nil
+        }
+      },
+      remove: {
+        backup = nil
+        notifyObservers()
+      }
+    )
+  }
+}
+
+extension BackupStorage {
+  public static let unimplemented = BackupStorage(
+    store: XCTUnimplemented("\(Self.self).store"),
+    observe: XCTUnimplemented("\(Self.self).observe", placeholder: Cancellable {}),
+    remove: XCTUnimplemented("\(Self.self).remove")
+  )
+}
diff --git a/Tests/XXMessengerClientTests/Utils/BackupStorageTests.swift b/Tests/XXMessengerClientTests/Utils/BackupStorageTests.swift
new file mode 100644
index 00000000..3dc8272e
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/BackupStorageTests.swift
@@ -0,0 +1,70 @@
+import CustomDump
+import XCTest
+@testable import XXMessengerClient
+
+final class BackupStorageTests: XCTestCase {
+  func testStorage() {
+    var now: Date = .init(0)
+    let storage: BackupStorage = .live(
+      now: { now }
+    )
+
+    var didObserveA: [BackupStorage.Backup?] = []
+    let observerA = storage.observe { backup in
+      didObserveA.append(backup)
+    }
+
+    XCTAssertNoDifference(didObserveA, [nil])
+
+    now = .init(1)
+    didObserveA = []
+    let data1 = "data-1".data(using: .utf8)!
+    storage.store(data1)
+
+    XCTAssertNoDifference(didObserveA, [.init(date: .init(1), data: data1)])
+
+    now = .init(2)
+    didObserveA = []
+    var didObserveB: [BackupStorage.Backup?] = []
+    let observerB = storage.observe { backup in
+      didObserveB.append(backup)
+    }
+
+    XCTAssertNoDifference(didObserveA, [])
+    XCTAssertNoDifference(didObserveB, [.init(date: .init(1), data: data1)])
+
+    now = .init(3)
+    didObserveA = []
+    didObserveB = []
+    let data2 = "data-2".data(using: .utf8)!
+    storage.store(data2)
+
+    XCTAssertNoDifference(didObserveA, [.init(date: .init(3), data: data2)])
+    XCTAssertNoDifference(didObserveB, [.init(date: .init(3), data: data2)])
+
+    now = .init(4)
+    didObserveA = []
+    didObserveB = []
+    observerA.cancel()
+    storage.remove()
+
+    XCTAssertNoDifference(didObserveA, [])
+    XCTAssertNoDifference(didObserveB, [nil])
+
+    now = .init(5)
+    didObserveA = []
+    didObserveB = []
+    observerB.cancel()
+    let data3 = "data-3".data(using: .utf8)!
+    storage.store(data3)
+
+    XCTAssertNoDifference(didObserveA, [])
+    XCTAssertNoDifference(didObserveB, [])
+  }
+}
+
+private extension Date {
+  init(_ timeIntervalSince1970: TimeInterval) {
+    self.init(timeIntervalSince1970: timeIntervalSince1970)
+  }
+}
-- 
GitLab