Signal-iOS/SignalServiceKit/Mocks/DB/InMemoryKeyValueStore.swift
2023-08-02 16:53:29 -05:00

283 lines
9.7 KiB
Swift

//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
#if TESTABLE_BUILD
/// Produces instances of `InMemoryKeyValueStore`.
public class InMemoryKeyValueStoreFactory: KeyValueStoreFactory {
public var stores = [String: InMemoryKeyValueStore]()
public init() {}
public func keyValueStore(collection: String) -> SignalServiceKit.KeyValueStore {
if let store = stores[collection] {
return store
}
let store = InMemoryKeyValueStore(collection: collection)
stores[collection] = store
return store
}
}
/// In memory implementation of a KeyValueStore, requiring no database setup.
/// Should only be used in test environments to simplify testing while replicating "real" storage.
public class InMemoryKeyValueStore: KeyValueStore {
private var dict = [String: Any]()
public required init(collection: String) {}
private func read<T>(_ key: String) -> T? {
guard let value = dict[key] else {
return nil
}
let val = value as! T
return val
}
private func read<T>(_ key: String, defaultValue: T) -> T {
guard let value = dict[key] else {
return defaultValue
}
let val = value as! T
return val
}
public func hasValue(_ key: String, transaction: DBReadTransaction) -> Bool {
return dict[key] != nil
}
public func getString(_ key: String, transaction: DBReadTransaction) -> String? {
return read(key)
}
public func setString(_ value: String?, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getDate(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> Date? {
return read(key)
}
public func setDate(_ value: Date, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getBool(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> Bool? {
return read(key)
}
public func getBool(_ key: String, defaultValue: Bool, transaction: SignalServiceKit.DBReadTransaction) -> Bool {
return read(key, defaultValue: defaultValue)
}
public func setBool(_ value: Bool, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func setBoolIfChanged(_ value: Bool, defaultValue: Bool, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getUInt(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> UInt? {
return read(key)
}
public func getUInt(_ key: String, defaultValue: UInt, transaction: SignalServiceKit.DBReadTransaction) -> UInt {
return read(key, defaultValue: defaultValue)
}
public func setUInt(_ value: UInt, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getData(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> Data? {
return read(key)
}
public func setData(_ value: Data?, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getNSNumber(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> NSNumber? {
return read(key)
}
public func getInt(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> Int? {
return read(key)
}
public func getInt(_ key: String, defaultValue: Int, transaction: SignalServiceKit.DBReadTransaction) -> Int {
return read(key, defaultValue: defaultValue)
}
public func setInt(_ value: Int, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getInt32(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> Int32? {
return read(key)
}
public func getInt32(_ key: String, defaultValue: Int32, transaction: SignalServiceKit.DBReadTransaction) -> Int32 {
return read(key, defaultValue: defaultValue)
}
public func setInt32(_ value: Int32, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getUInt32(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> UInt32? {
return read(key)
}
public func getUInt32(_ key: String, defaultValue: UInt32, transaction: SignalServiceKit.DBReadTransaction) -> UInt32 {
return read(key, defaultValue: defaultValue)
}
public func setUInt32(_ value: UInt32, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getUInt64(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> UInt64? {
return read(key)
}
public func getUInt64(_ key: String, defaultValue: UInt64, transaction: SignalServiceKit.DBReadTransaction) -> UInt64 {
return read(key, defaultValue: defaultValue)
}
public func setUInt64(_ value: UInt64, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getInt64(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> Int64? {
return read(key)
}
public func getInt64(_ key: String, defaultValue: Int64, transaction: SignalServiceKit.DBReadTransaction) -> Int64 {
return read(key, defaultValue: defaultValue)
}
public func setInt64(_ value: Int64, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getDouble(_ key: String, transaction: SignalServiceKit.DBReadTransaction) -> Double? {
return read(key)
}
public func getDouble(_ key: String, defaultValue: Double, transaction: SignalServiceKit.DBReadTransaction) -> Double {
return read(key, defaultValue: defaultValue)
}
public func setDouble(_ value: Double, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = value
}
public func getObject(forKey key: String, transaction: SignalServiceKit.DBReadTransaction) -> Any? {
return read(key)
}
public func setObject(_ anyValue: Any?, key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = anyValue
}
public func removeValue(forKey key: String, transaction: SignalServiceKit.DBWriteTransaction) {
dict[key] = nil
}
public func removeValues(forKeys keys: [String], transaction: SignalServiceKit.DBWriteTransaction) {
keys.forEach {
removeValue(forKey: $0, transaction: transaction)
}
}
public func removeAll(transaction: SignalServiceKit.DBWriteTransaction) {
dict = [:]
}
public func enumerateKeysAndObjects(transaction: SignalServiceKit.DBReadTransaction, block: (String, Any, UnsafeMutablePointer<ObjCBool>) -> Void) {
for key in dict.keys {
var bool: ObjCBool = .init(booleanLiteral: false)
block(key, dict[key]!, &bool)
if bool.boolValue {
return
}
}
}
public func enumerateKeys(transaction: SignalServiceKit.DBReadTransaction, block: (String, UnsafeMutablePointer<ObjCBool>) -> Void) {
enumerateKeysAndObjects(transaction: transaction, block: { key, _, stop in
block(key, stop)
})
}
public func allValues(transaction: SignalServiceKit.DBReadTransaction) -> [Any] {
return Array(dict.values)
}
public func allDataValues(transaction: SignalServiceKit.DBReadTransaction) -> [Data] {
return allValues(transaction: transaction).compactMap { $0 as? Data }
}
public func allBoolValuesMap(transaction: SignalServiceKit.DBReadTransaction) -> [String: Bool] {
return dict.compactMapValues { $0 as? Bool }
}
public func allUIntValuesMap(transaction: SignalServiceKit.DBReadTransaction) -> [String: UInt] {
return dict.compactMapValues { $0 as? UInt }
}
public func anyDataValue(transaction: SignalServiceKit.DBReadTransaction) -> Data? {
return allDataValues(transaction: transaction).randomElement()
}
public func allKeys(transaction: SignalServiceKit.DBReadTransaction) -> [String] {
return Array(dict.keys)
}
public func numberOfKeys(transaction: SignalServiceKit.DBReadTransaction) -> UInt {
return UInt(dict.count)
}
public func setCodable<T>(_ value: T, key: String, transaction: SignalServiceKit.DBWriteTransaction) throws where T: Encodable {
let encoded = try JSONEncoder().encode(value)
setData(encoded, key: key, transaction: transaction)
}
public func setCodable<T>(optional value: T, key: String, transaction: SignalServiceKit.DBWriteTransaction) throws where T: Encodable {
let encoded = try JSONEncoder().encode(value)
setData(encoded, key: key, transaction: transaction)
}
public func getCodableValue<T>(forKey key: String, transaction: SignalServiceKit.DBReadTransaction) throws -> T? where T: Decodable {
guard let raw = dict[key] else {
return nil
}
let data = raw as! Data
let decoded = try JSONDecoder().decode(T.self, from: data)
return decoded
}
public func allCodableValues<T>(transaction: SignalServiceKit.DBReadTransaction) throws -> [T] where T: Decodable {
let decoder = JSONDecoder()
return dict.values.compactMap { raw in
guard
let data = raw as? Data,
let decoded = try? decoder.decode(T.self, from: data)
else {
return nil
}
return decoded
}
}
}
#endif