// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation @testable import SessionUtilitiesKit public class TestDependencies: Dependencies { private var singletonInstances: [Int: Any] = [:] private var cacheInstances: [Int: MutableCacheType] = [:] private var defaultsInstances: [Int: (any UserDefaultsType)] = [:] // MARK: - Subscript Access override public subscript(singleton singleton: SingletonInfo.Config) -> S { return getValueSettingIfNull(singleton: singleton, &singletonInstances) } public subscript(singleton singleton: SingletonInfo.Config) -> S? { get { return (singletonInstances[singleton.key] as? S) } set { singletonInstances[singleton.key] = newValue } } override public subscript(cache cache: CacheInfo.Config) -> I { return getValueSettingIfNull(cache: cache, &cacheInstances) } public subscript(cache cache: CacheInfo.Config) -> M? { get { return (cacheInstances[cache.key] as? M) } set { cacheInstances[cache.key] = newValue.map { cache.mutableInstance($0) } } } override public subscript(defaults defaults: UserDefaultsInfo.Config) -> U { return getValueSettingIfNull(defaults: defaults, &defaultsInstances) } public subscript(defaults defaults: UserDefaultsInfo.Config) -> U? { get { return (defaultsInstances[defaults.key] as? U) } set { defaultsInstances[defaults.key] = newValue } } // MARK: - Timing and Async Handling private var _dateNow: Atomic override public var dateNow: Date { get { (_dateNow.wrappedValue ?? Date()) } set { _dateNow.mutate { $0 = newValue } } } private var _fixedTime: Atomic override public var fixedTime: Int { get { (_fixedTime.wrappedValue ?? 0) } set { _fixedTime.mutate { $0 = newValue } } } // MARK: - Initialization public init( dateNow: Date? = nil, fixedTime: Int? = nil, forceSynchronous: Bool = false ) { _dateNow = Atomic(dateNow) _fixedTime = Atomic(fixedTime) super.init() self.forceSynchronous = forceSynchronous } // MARK: - Functions @discardableResult override public func mutate( cache: CacheInfo.Config, _ mutation: (inout M) -> R ) -> R { var value: M = ((cacheInstances[cache.key] as? M) ?? cache.createInstance(self)) return mutation(&value) } @discardableResult override public func mutate( cache: CacheInfo.Config, _ mutation: (inout M) throws -> R ) throws -> R { var value: M = ((cacheInstances[cache.key] as? M) ?? cache.createInstance(self)) return try mutation(&value) } public func stepForwardInTime() { let targetTime: Int = ((_fixedTime.wrappedValue ?? 0) + 1) _fixedTime.mutate { $0 = targetTime } if let currentDate: Date = _dateNow.wrappedValue { _dateNow.mutate { $0 = Date(timeIntervalSince1970: currentDate.timeIntervalSince1970 + 1) } } // Run and clear any executions which should run at the target time let targetKeys: [Int] = asyncExecutions.keys .filter { $0 <= targetTime } targetKeys.forEach { key in asyncExecutions[key]?.forEach { $0() } asyncExecutions[key] = nil } } // MARK: - Instance upserting @discardableResult private func getValueSettingIfNull( singleton: SingletonInfo.Config, _ store: inout [Int: Any] ) -> S { guard let value: S = (store[singleton.key] as? S) else { let value: S = singleton.createInstance(self) store[singleton.key] = value return value } return value } @discardableResult private func getValueSettingIfNull( cache: CacheInfo.Config, _ store: inout [Int: MutableCacheType] ) -> I { guard let value: M = (store[cache.key] as? M) else { let value: M = cache.createInstance(self) let mutableInstance: MutableCacheType = cache.mutableInstance(value) store[cache.key] = mutableInstance return cache.immutableInstance(value) } return cache.immutableInstance(value) } @discardableResult private func getValueSettingIfNull( defaults: UserDefaultsInfo.Config, _ store: inout [Int: (any UserDefaultsType)] ) -> U { guard let value: U = (store[defaults.key] as? U) else { let value: U = defaults.createInstance(self) store[defaults.key] = value return value } return value } }