diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerWaitForNodes.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerWaitForNodes.swift new file mode 100644 index 0000000000000000000000000000000000000000..39048a37f158036f2f7b346d2c18cd201807afaf --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerWaitForNodes.swift @@ -0,0 +1,57 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerWaitForNodes { + public typealias Progress = (Double) -> Void + + public enum Error: Swift.Error { + case notLoaded + case timeout + } + + public var run: (Double, Int, Int, @escaping Progress) throws -> Void + + public func callAsFunction( + targetRatio: Double = 0.8, + sleepMS: Int = 1_000, + retries: Int = 10, + onProgress: @escaping Progress = { _ in } + ) throws { + try run(targetRatio, sleepMS, retries, onProgress) + } +} + +extension MessengerWaitForNodes { + public static func live(_ env: MessengerEnvironment) -> MessengerWaitForNodes { + MessengerWaitForNodes { targetRatio, sleepMS, retries, onProgress in + guard let cMix = env.ctx.getCMix() else { + throw Error.notLoaded + } + + func getProgress(_ report: NodeRegistrationReport) -> Double { + min(1, ((report.ratio / targetRatio) * 100).rounded() / 100) + } + + var report = try cMix.getNodeRegistrationStatus() + var retries = retries + onProgress(getProgress(report)) + + while report.ratio < targetRatio && retries > 0 { + env.sleep(sleepMS) + retries -= 1 + report = try cMix.getNodeRegistrationStatus() + onProgress(getProgress(report)) + } + + if report.ratio < targetRatio { + throw Error.timeout + } + } + } +} + +extension MessengerWaitForNodes { + public static let unimplemented = MessengerWaitForNodes( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift index 959aff7ffec86419698f34b9512c5939a31be1ad..5a442ac9aa17220dcaa1f2b166c74a51be641c2b 100644 --- a/Sources/XXMessengerClient/Messenger/Messenger.swift +++ b/Sources/XXMessengerClient/Messenger/Messenger.swift @@ -16,6 +16,7 @@ public struct Messenger { public var isLoggedIn: MessengerIsLoggedIn public var logIn: MessengerLogIn public var waitForNetwork: MessengerWaitForNetwork + public var waitForNodes: MessengerWaitForNodes } extension Messenger { @@ -35,7 +36,8 @@ extension Messenger { register: .live(env), isLoggedIn: .live(env), logIn: .live(env), - waitForNetwork: .live(env) + waitForNetwork: .live(env), + waitForNodes: .live(env) ) } } @@ -56,6 +58,7 @@ extension Messenger { register: .unimplemented, isLoggedIn: .unimplemented, logIn: .unimplemented, - waitForNetwork: .unimplemented + waitForNetwork: .unimplemented, + waitForNodes: .unimplemented ) } diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift index b3009ae051ab0ea89c0e8e3c07ae81a4ece8e283..eb7646f3ecc8f9c0996ddc19fda93ef87fc8efe8 100644 --- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift +++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift @@ -16,6 +16,7 @@ public struct MessengerEnvironment { public var newCMix: NewCMix public var newOrLoadUd: NewOrLoadUd public var passwordStorage: PasswordStorage + public var sleep: (Int) -> Void public var storageDir: String public var udAddress: String? public var udCert: Data? @@ -44,6 +45,7 @@ extension MessengerEnvironment { newCMix: .live, newOrLoadUd: .live, passwordStorage: .keychain, + sleep: { Foundation.sleep(UInt32($0)) }, storageDir: MessengerEnvironment.defaultStorageDir, udAddress: nil, udCert: nil, @@ -67,6 +69,7 @@ extension MessengerEnvironment { newCMix: .unimplemented, newOrLoadUd: .unimplemented, passwordStorage: .unimplemented, + sleep: XCTUnimplemented("\(Self.self).sleep"), storageDir: "unimplemented", udAddress: nil, udCert: nil, diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerWaitForNodesTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerWaitForNodesTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..3154758f2ee12e766a12ccf5156b63e728262497 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerWaitForNodesTests.swift @@ -0,0 +1,108 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerWaitForNodesTests: XCTestCase { + func testWaitWhenNotLoaded() { + var env: MessengerEnvironment = .unimplemented + env.ctx.getCMix = { nil } + let waitForNodes: MessengerWaitForNodes = .live(env) + + XCTAssertThrowsError(try waitForNodes()) { error in + XCTAssertEqual( + error as? MessengerWaitForNodes.Error, + MessengerWaitForNodes.Error.notLoaded + ) + } + } + + func testWaitWhenHasTargetRatio() throws { + var didProgress: [Double] = [] + + var env: MessengerEnvironment = .unimplemented + env.ctx.getCMix = { + var cMix: CMix = .unimplemented + cMix.getNodeRegistrationStatus.run = { + NodeRegistrationReport(registered: 8, total: 10) + } + return cMix + } + let waitForNodes: MessengerWaitForNodes = .live(env) + + try waitForNodes( + targetRatio: 0.7, + sleepMS: 123, + retries: 3, + onProgress: { didProgress.append($0) } + ) + + XCTAssertNoDifference(didProgress, [1]) + } + + func testWaitForTargetRatio() throws { + var didSleep: [Int] = [] + var didProgress: [Double] = [] + + var reports: [NodeRegistrationReport] = [ + .init(registered: 0, total: 10), + .init(registered: 3, total: 10), + .init(registered: 8, total: 10), + ] + + var env: MessengerEnvironment = .unimplemented + env.ctx.getCMix = { + var cMix: CMix = .unimplemented + cMix.getNodeRegistrationStatus.run = { reports.removeFirst() } + return cMix + } + env.sleep = { didSleep.append($0) } + let waitForNodes: MessengerWaitForNodes = .live(env) + + try waitForNodes( + targetRatio: 0.7, + sleepMS: 123, + retries: 3, + onProgress: { didProgress.append($0) } + ) + + XCTAssertNoDifference(didSleep, [123, 123]) + XCTAssertNoDifference(didProgress, [0, 0.43, 1]) + } + + func testWaitTimeout() { + var didSleep: [Int] = [] + var didProgress: [Double] = [] + + var reports: [NodeRegistrationReport] = [ + .init(registered: 0, total: 10), + .init(registered: 3, total: 10), + .init(registered: 5, total: 10), + .init(registered: 6, total: 10), + ] + + var env: MessengerEnvironment = .unimplemented + env.ctx.getCMix = { + var cMix: CMix = .unimplemented + cMix.getNodeRegistrationStatus.run = { reports.removeFirst() } + return cMix + } + env.sleep = { didSleep.append($0) } + let waitForNodes: MessengerWaitForNodes = .live(env) + + XCTAssertThrowsError(try waitForNodes( + targetRatio: 0.7, + sleepMS: 123, + retries: 3, + onProgress: { didProgress.append($0) } + )) { error in + XCTAssertEqual( + error as? MessengerWaitForNodes.Error, + MessengerWaitForNodes.Error.timeout + ) + } + + XCTAssertNoDifference(didSleep, [123, 123, 123]) + XCTAssertNoDifference(didProgress, [0, 0.43, 0.71, 0.86]) + } +}