feat: update tests, migration fixes, factory method for migration storage added
This commit is contained in:
parent
46ecd6970f
commit
25c0d04d11
|
|
@ -72,7 +72,7 @@ let package = Package(
|
|||
plugins: [.plugin(name: "TISwiftLintPlugin")]),
|
||||
|
||||
.target(name: "TIFoundationUtils",
|
||||
dependencies: ["TISwiftUtils"],
|
||||
dependencies: ["TISwiftUtils", "TILogging"],
|
||||
path: "TIFoundationUtils",
|
||||
exclude: ["TIFoundationUtils.app"],
|
||||
plugins: [.plugin(name: "TISwiftLintPlugin")]),
|
||||
|
|
@ -148,7 +148,7 @@ let package = Package(
|
|||
path: "Tests/TITextProcessingTests"),
|
||||
.testTarget(
|
||||
name: "TIFoundationUtilsTests",
|
||||
dependencies: ["TIFoundationUtils", "TISwiftUtils"],
|
||||
dependencies: ["TIFoundationUtils", "TISwiftUtils", "TILogging"],
|
||||
path: "Tests/TIFoundationUtilsTests")
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,14 @@ public extension MigratingBackingStore where MigratingStorages: CodableKeyValueS
|
|||
where StoreContent == Value? {
|
||||
|
||||
let getClosure: GetClosure = { container in
|
||||
try? container.codableObject(forKey: key, decoder: decoder).get()
|
||||
// If source storage has value and target doesn't we can't migrate it from storate container
|
||||
try? container.codableObject(forKey: key, decoder: decoder)
|
||||
.flatMap {
|
||||
let _ = container.set(encodableObject: $0, forKey: key, encoder: encoder)
|
||||
|
||||
return .success($0)
|
||||
}
|
||||
.get()
|
||||
}
|
||||
|
||||
let setClosure: SetClosure = { container, value in
|
||||
|
|
@ -50,7 +57,18 @@ public extension MigratingBackingStore where MigratingStorages: CodableKeyValueS
|
|||
where StoreContent == Value? {
|
||||
|
||||
let getClosure: GetClosure = { container in
|
||||
container.codableObject(forKey: key, defaultValue: wrappedValue, decoder: decoder)
|
||||
// If source storage has value and target doesn't we can't migrate it from storate container
|
||||
try? container.codableObject(forKey: key, decoder: decoder)
|
||||
.flatMap {
|
||||
let _ = container.set(encodableObject: $0, forKey: key, encoder: encoder)
|
||||
|
||||
return .success($0)
|
||||
}
|
||||
.flatMapError { _ in
|
||||
container.set(encodableObject: wrappedValue, forKey: key, encoder: encoder)
|
||||
.flatMap { _ in .success(wrappedValue) }
|
||||
}
|
||||
.get()
|
||||
}
|
||||
|
||||
let setClosure: SetClosure = { container, value in
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ open class BaseSingleValueStorage<ValueType, StorageType>: SingleValueStorage {
|
|||
public let getValueClosure: GetValueClosure
|
||||
public let storeValueClosure: StoreValueClosure
|
||||
|
||||
public var migrationStorage: StorageType?
|
||||
|
||||
public init(storage: StorageType,
|
||||
storageKey: StorageKey<ValueType>,
|
||||
hasValueClosure: @escaping HasValueClosure,
|
||||
|
|
@ -53,43 +51,18 @@ open class BaseSingleValueStorage<ValueType, StorageType>: SingleValueStorage {
|
|||
// MARK: - SingleValueStorage
|
||||
|
||||
open func hasStoredValue() -> Bool {
|
||||
if let migrationStorage, (try? hasValueClosure(migrationStorage, storageKey).get()) == true {
|
||||
let _ = deleteValueClosure(storage, storageKey)
|
||||
return true
|
||||
}
|
||||
|
||||
return (try? hasValueClosure(storage, storageKey).get()) ?? false
|
||||
(try? hasValueClosure(storage, storageKey).get()) ?? false
|
||||
}
|
||||
|
||||
open func store(value: ValueType) -> Result<Void, StorageError> {
|
||||
let targetStorate = migrationStorage ?? storage
|
||||
|
||||
return storeValueClosure(targetStorate, value, storageKey)
|
||||
storeValueClosure(storage, value, storageKey)
|
||||
}
|
||||
|
||||
open func getValue() -> Result<ValueType, StorageError> {
|
||||
guard let migrationStorage else {
|
||||
return getValueClosure(storage, storageKey)
|
||||
}
|
||||
|
||||
if let value = try? getValueClosure(migrationStorage, storageKey).get() {
|
||||
return .success(value)
|
||||
}
|
||||
|
||||
let result = getValueClosure(storage, storageKey)
|
||||
|
||||
if case let .success(value) = result, case .success = store(value: value) {
|
||||
let _ = deleteValueClosure(storage, storageKey)
|
||||
}
|
||||
|
||||
return result
|
||||
getValueClosure(storage, storageKey)
|
||||
}
|
||||
|
||||
open func deleteValue() -> Result<Void, StorageError> {
|
||||
if let migrationStorage {
|
||||
return deleteValueClosure(migrationStorage, storageKey)
|
||||
}
|
||||
|
||||
return deleteValueClosure(storage, storageKey)
|
||||
deleteValueClosure(storage, storageKey)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TILogging
|
||||
|
||||
open class BaseMigratingSingleValueStorage<SourceStorage: SingleValueStorage,
|
||||
TargetStorage: SingleValueStorage>: SingleValueStorage
|
||||
where SourceStorage.ErrorType == TargetStorage.ErrorType,
|
||||
|
|
@ -30,12 +32,15 @@ where SourceStorage.ErrorType == TargetStorage.ErrorType,
|
|||
|
||||
public let sourceStorage: SourceStorage
|
||||
public let targetStorage: TargetStorage
|
||||
public let errorLogger: ErrorLogger
|
||||
|
||||
public init(sourceStorage: SourceStorage,
|
||||
targetStorage: TargetStorage) {
|
||||
targetStorage: TargetStorage,
|
||||
errorLogger: ErrorLogger = TIFoundationLogger(category: "BaseMigratingSingleValueStorage")) {
|
||||
|
||||
self.sourceStorage = sourceStorage
|
||||
self.targetStorage = targetStorage
|
||||
self.errorLogger = errorLogger
|
||||
}
|
||||
|
||||
// MARK: - SingleValueStorage
|
||||
|
|
@ -46,6 +51,11 @@ where SourceStorage.ErrorType == TargetStorage.ErrorType,
|
|||
|
||||
open func store(value: ValueType) -> Result<Void, StorageError> {
|
||||
let _ = sourceStorage.deleteValue()
|
||||
.flatMapError {
|
||||
errorLogger.log(error: $0, file: #file, line: #line)
|
||||
|
||||
return .failure($0)
|
||||
}
|
||||
|
||||
return targetStorage.store(value: value)
|
||||
}
|
||||
|
|
@ -54,6 +64,11 @@ where SourceStorage.ErrorType == TargetStorage.ErrorType,
|
|||
targetStorage.getValue()
|
||||
.flatMap {
|
||||
let _ = sourceStorage.deleteValue()
|
||||
.flatMapError {
|
||||
errorLogger.log(error: $0, file: #file, line: #line)
|
||||
|
||||
return .failure($0)
|
||||
}
|
||||
|
||||
return .success($0)
|
||||
}
|
||||
|
|
@ -79,5 +94,20 @@ where SourceStorage.ErrorType == TargetStorage.ErrorType,
|
|||
|
||||
return .success($0)
|
||||
}
|
||||
.flatMapError {
|
||||
errorLogger.log(error: $0, file: #file, line: #line)
|
||||
|
||||
return sourceStorage.deleteValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension SingleValueStorage where ErrorType == StorageError {
|
||||
|
||||
func migrating<S: SingleValueStorage>(to targetStorage: S) -> BaseMigratingSingleValueStorage<Self, S>
|
||||
where ErrorType == S.ErrorType,
|
||||
ValueType == S.ValueType {
|
||||
|
||||
BaseMigratingSingleValueStorage(sourceStorage: self, targetStorage: targetStorage)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,18 +21,39 @@
|
|||
//
|
||||
|
||||
import TISwiftUtils
|
||||
import TILogging
|
||||
|
||||
open class BaseCodableMigratingStorageContainer<SourceStorage,
|
||||
TargetStorage>: MigratingStorageContainer<SourceStorage, TargetStorage>,
|
||||
CodableKeyValueStorage
|
||||
where SourceStorage: CodableKeyValueStorage, TargetStorage: CodableKeyValueStorage {
|
||||
|
||||
public enum CodableTypeError: Error {
|
||||
case unableToConvertToEncodable
|
||||
}
|
||||
|
||||
public let errorLogger: ErrorLogger
|
||||
|
||||
public init(sourceStorage: SourceStorage,
|
||||
targetStorage: TargetStorage,
|
||||
errorLogger: ErrorLogger = TIFoundationLogger(category: "BaseCodableMigratingStorageContainer")) {
|
||||
|
||||
self.errorLogger = errorLogger
|
||||
|
||||
super.init(sourceStorage: sourceStorage, targetStorage: targetStorage)
|
||||
}
|
||||
|
||||
open func codableObject<Value: Decodable>(forKey key: StorageKey<Value>,
|
||||
decoder: CodableKeyValueDecoder) -> Result<Value, StorageError> {
|
||||
|
||||
targetStorage.codableObject(forKey: key, decoder: decoder)
|
||||
.flatMap {
|
||||
removeSourceValue(forKey: key)
|
||||
let _ = removeSourceValue(forKey: key)
|
||||
.flatMapError {
|
||||
errorLogger.log(error: $0, file: #file, line: #line)
|
||||
|
||||
return .failure($0)
|
||||
}
|
||||
|
||||
return .success($0)
|
||||
}
|
||||
|
|
@ -45,19 +66,26 @@ where SourceStorage: CodableKeyValueStorage, TargetStorage: CodableKeyValueStora
|
|||
forKey key: StorageKey<Value>,
|
||||
encoder: CodableKeyValueEncoder) -> Result<Void, StorageError> {
|
||||
|
||||
removeSourceValue(forKey: key)
|
||||
let _ = removeSourceValue(forKey: key)
|
||||
.flatMapError {
|
||||
errorLogger.log(error: $0, file: #file, line: #line)
|
||||
|
||||
return .failure($0)
|
||||
}
|
||||
|
||||
return targetStorage.set(encodableObject: encodableObject, forKey: key, encoder: encoder)
|
||||
}
|
||||
|
||||
open func removeCodableValue<Value: Codable>(forKey key: StorageKey<Value>) -> Result<Void, StorageError> {
|
||||
targetStorage.removeCodableValue(forKey: key)
|
||||
.flatMap {
|
||||
if let value = try? sourceStorage.hasCodableValue(forKey: key).get(), value {
|
||||
return sourceStorage.removeCodableValue(forKey: key)
|
||||
}
|
||||
.flatMap { value in
|
||||
sourceStorage.removeCodableValue(forKey: key)
|
||||
.flatMapError { _ in .success(value) }
|
||||
}
|
||||
.flatMapError {
|
||||
errorLogger.log(error: $0, file: #file, line: #line)
|
||||
|
||||
return .success($0)
|
||||
return removeSourceValue(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +98,8 @@ where SourceStorage: CodableKeyValueStorage, TargetStorage: CodableKeyValueStora
|
|||
|
||||
// MARK: - Open methods
|
||||
|
||||
open func removeSourceValue<Value>(forKey key: StorageKey<Value>) {
|
||||
open func removeSourceValue<Value>(forKey key: StorageKey<Value>) -> Result<Void, StorageError> {
|
||||
// override in subclasses
|
||||
.failure(.valueNotFound)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import Foundation
|
|||
|
||||
public final class DefaultsMigratingStorageContainer: BaseCodableMigratingStorageContainer<UserDefaults, UserDefaults> {
|
||||
|
||||
public override func removeSourceValue<Value>(forKey key: StorageKey<Value>) {
|
||||
let _ = sourceStorage.removeValue(forKey: key)
|
||||
public override func removeSourceValue<Value>(forKey key: StorageKey<Value>) -> Result<Void, StorageError> {
|
||||
sourceStorage.removeValue(forKey: key)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@ target 'TIFoundationUtils' do
|
|||
use_frameworks!
|
||||
|
||||
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
|
||||
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
|
||||
pod 'TIFoundationUtils', :path => '../../../../TIFoundationUtils/TIFoundationUtils.podspec'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TILogging
|
||||
import os
|
||||
|
||||
public final class TIFoundationLogger: DefaultOSLogErrorLogger {
|
||||
public init(category: String) {
|
||||
super.init(log: OSLog(subsystem: "TIFoundationUtils", category: category))
|
||||
}
|
||||
}
|
||||
|
|
@ -20,5 +20,6 @@ Pod::Spec.new do |s|
|
|||
end
|
||||
|
||||
s.dependency 'TISwiftUtils', s.version.to_s
|
||||
s.dependency 'TILogging', s.version.to_s
|
||||
s.framework = 'Foundation'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import KeychainAccess
|
|||
|
||||
public final class KeychainMigratingStorageContainer: BaseCodableMigratingStorageContainer<Keychain, Keychain> {
|
||||
|
||||
public override func removeSourceValue<Value>(forKey key: StorageKey<Value>) {
|
||||
let _ = sourceStorage.removeValue(forKey: key)
|
||||
public override func removeSourceValue<Value>(forKey key: StorageKey<Value>) -> Result<Void, StorageError> {
|
||||
sourceStorage.removeValue(forKey: key)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,10 +127,6 @@ case let .failure(storageError):
|
|||
let groupKeychain = Keychain(service: "app.group.identifier")
|
||||
let targetApiTokenKeychainStorage = StringValueKeychainStorage(keychain: groupKeychain, storageKey: .apiToken)
|
||||
|
||||
let migratingApiTokenKeychainStorage = BaseMigratingSingleValueStorage(sourceStorage: apiTokenKeychainStorage,
|
||||
targetStorage: targetApiTokenKeychainStorage)
|
||||
let migratingApiTokenKeychainStorage = apiTokenKeychainStorage.migrating(to: targetApiTokenKeychainStorage)
|
||||
|
||||
let token = migratingApiTokenKeychainStorage.getValue()
|
||||
|
||||
//: Если нет необходимости в создании target storage, можно воспользоваться переменной `migrationStorage`, которая есть у каждого хранилища, наследованного от `BaseSingleValueStorage`
|
||||
apiTokenKeychainStorage.migrationStorage = groupKeychain
|
||||
|
|
|
|||
|
|
@ -10,7 +10,14 @@ Pod::Spec.new do |s|
|
|||
s.ios.deployment_target = '11.0'
|
||||
s.swift_versions = ['5.7']
|
||||
|
||||
s.source_files = s.name + '/Sources/**/*'
|
||||
sources = 'Sources/**/*'
|
||||
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
|
||||
s.source_files = sources
|
||||
s.exclude_files = s.name + '.app'
|
||||
else
|
||||
s.source_files = s.name + '/' + sources
|
||||
s.exclude_files = s.name + '/*.app'
|
||||
end
|
||||
|
||||
s.dependency 'TIUIElements', s.version.to_s
|
||||
s.dependency 'TISwiftUtils', s.version.to_s
|
||||
|
|
|
|||
|
|
@ -33,61 +33,192 @@ final class MigratingBackingStoreTests: XCTestCase {
|
|||
|
||||
override func setUp() {
|
||||
profile = nil
|
||||
let _ = MockMigratingStorageContainer.defaultContainer.sourceStorage.removeValue(forKey: .profile)
|
||||
let _ = MockMigratingStorageContainer.defaultContainer.targetStorage.removeValue(forKey: .profile)
|
||||
}
|
||||
|
||||
// MARK: - Tests
|
||||
// MARK: - Read Tests
|
||||
|
||||
func testMigrationOnGetValue() throws {
|
||||
let oldProfile = Profile(name: "some", age: 0)
|
||||
// Precondition - source: ✅ target: ✅
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testGetValue() throws {
|
||||
let oldProfile = Profile(name: "old", age: 0)
|
||||
let newProfile = Profile(name: "new", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = sourceStorage.set(encodableObject: oldProfile, forKey: .profile, encoder: encoder)
|
||||
let _ = targetStorage.set(encodableObject: newProfile, forKey: .profile, encoder: encoder)
|
||||
|
||||
XCTAssertEqual(profile, oldProfile)
|
||||
XCTAssertEqual(profile, newProfile)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertNoThrow(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
}
|
||||
|
||||
func testGetValueFromTarget() throws {
|
||||
let newProfile = Profile(name: "any", age: 1)
|
||||
// Precondition - source: ❌ target: ✅
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testGetValueWithNoSource() throws {
|
||||
let newProfile = Profile(name: "new", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = targetStorage.set(encodableObject: newProfile, forKey: .profile, encoder: encoder)
|
||||
|
||||
XCTAssertEqual(profile, newProfile)
|
||||
XCTAssertEqual(try? targetStorage.codableObject(forKey: .profile, decoder: decoder).get(), newProfile)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertNoThrow(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
}
|
||||
|
||||
func testGetValue() throws {
|
||||
let newProfile = Profile(name: "any", age: 1)
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
profile = newProfile
|
||||
|
||||
XCTAssertEqual(profile, newProfile)
|
||||
XCTAssertEqual(try? targetStorage.codableObject(forKey: .profile, decoder: decoder).get(), newProfile)
|
||||
}
|
||||
|
||||
func testMigrationOnStoreValue() throws {
|
||||
let oldProfile = Profile(name: "some", age: 0)
|
||||
let newProfile = Profile(name: "any", age: 1)
|
||||
// Precondition - source: ✅ target: ❌
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testGetValueWithNoTarget() throws {
|
||||
let oldProfile = Profile(name: "old", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = sourceStorage.set(encodableObject: oldProfile, forKey: .profile, encoder: encoder)
|
||||
profile = newProfile
|
||||
|
||||
XCTAssertEqual(profile, newProfile)
|
||||
XCTAssertEqual(try? targetStorage.codableObject(forKey: .profile, decoder: decoder).get(), newProfile)
|
||||
XCTAssertEqual(profile, oldProfile)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertNoThrow(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
}
|
||||
|
||||
func testStoreValueFromTarget() throws {
|
||||
let oldProfile = Profile(name: "some", age: 0)
|
||||
let newProfile = Profile(name: "any", age: 1)
|
||||
// Precondition - source: ❌ target: ❌
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testGetValueWithNoValues() throws {
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = targetStorage.set(encodableObject: oldProfile, forKey: .profile, encoder: encoder)
|
||||
profile = newProfile
|
||||
XCTAssertEqual(profile, nil)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertThrowsError(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
}
|
||||
|
||||
XCTAssertEqual(profile, newProfile)
|
||||
XCTAssertEqual(try? targetStorage.codableObject(forKey: .profile, decoder: decoder).get(), newProfile)
|
||||
// MARK: - Write Tests
|
||||
|
||||
// Precondition - source: ✅ target: ✅
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testStoreValue() throws {
|
||||
let oldProfile = Profile(name: "old", age: 0)
|
||||
let newProfile = Profile(name: "new", age: 0)
|
||||
let currentProfile = Profile(name: "name", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = sourceStorage.set(encodableObject: oldProfile, forKey: .profile, encoder: encoder)
|
||||
let _ = targetStorage.set(encodableObject: newProfile, forKey: .profile, encoder: encoder)
|
||||
profile = currentProfile
|
||||
|
||||
XCTAssertEqual(profile, currentProfile)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertEqual(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get(), currentProfile)
|
||||
}
|
||||
|
||||
// Precondition - source: ❌ target: ✅
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testStoreValueWithNoSource() throws {
|
||||
let newProfile = Profile(name: "new", age: 0)
|
||||
let currentProfile = Profile(name: "name", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = targetStorage.set(encodableObject: newProfile, forKey: .profile, encoder: encoder)
|
||||
profile = currentProfile
|
||||
|
||||
XCTAssertEqual(profile, currentProfile)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertEqual(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get(), currentProfile)
|
||||
}
|
||||
|
||||
// Precondition - source: ✅ target: ❌
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testStoreValueWithNoTarget() throws {
|
||||
let oldProfile = Profile(name: "old", age: 0)
|
||||
let currentProfile = Profile(name: "name", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = sourceStorage.set(encodableObject: oldProfile, forKey: .profile, encoder: encoder)
|
||||
profile = currentProfile
|
||||
|
||||
XCTAssertEqual(profile, currentProfile)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertEqual(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get(), currentProfile)
|
||||
}
|
||||
|
||||
// Precondition - source: ❌ target: ❌
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testStoreValueWithNoValues() throws {
|
||||
let currentProfile = Profile(name: "name", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
profile = currentProfile
|
||||
|
||||
XCTAssertEqual(profile, currentProfile)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertEqual(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get(), currentProfile)
|
||||
}
|
||||
|
||||
// MARK: - Delete Tests
|
||||
|
||||
// Precondition - source: ✅ target: ✅
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testDeleteValue() throws {
|
||||
let oldProfile = Profile(name: "old", age: 0)
|
||||
let newProfile = Profile(name: "new", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = sourceStorage.set(encodableObject: oldProfile, forKey: .profile, encoder: encoder)
|
||||
let _ = targetStorage.set(encodableObject: newProfile, forKey: .profile, encoder: encoder)
|
||||
profile = nil
|
||||
|
||||
XCTAssertEqual(profile, nil)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertThrowsError(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
}
|
||||
|
||||
// Precondition - source: ❌ target: ✅
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testDeleteValueWithNoSource() throws {
|
||||
let newProfile = Profile(name: "new", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = targetStorage.set(encodableObject: newProfile, forKey: .profile, encoder: encoder)
|
||||
profile = nil
|
||||
|
||||
XCTAssertEqual(profile, nil)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertThrowsError(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
}
|
||||
|
||||
// Precondition - source: ✅ target: ❌
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testDeleteValueWithNoTarget() throws {
|
||||
let oldProfile = Profile(name: "old", age: 0)
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
let _ = sourceStorage.set(encodableObject: oldProfile, forKey: .profile, encoder: encoder)
|
||||
profile = nil
|
||||
|
||||
XCTAssertEqual(profile, nil)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertThrowsError(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
}
|
||||
|
||||
// Precondition - source: ❌ target: ❌
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testDeleteValueWithNoValues() throws {
|
||||
let sourceStorage = MockMigratingStorageContainer.defaultContainer.sourceStorage
|
||||
let targetStorage = MockMigratingStorageContainer.defaultContainer.targetStorage
|
||||
|
||||
profile = nil
|
||||
|
||||
XCTAssertEqual(profile, nil)
|
||||
XCTAssertThrowsError(try sourceStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
XCTAssertThrowsError(try targetStorage.codableObject(forKey: .profile, decoder: decoder).get())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,136 +32,171 @@ final class MigratingSingleValueStorageTests: XCTestCase {
|
|||
storageKey: .refreshToken)
|
||||
|
||||
public lazy var refreshToken = BaseMigratingSingleValueStorage(sourceStorage: sourceRefreshTokenStorage,
|
||||
targetStorage: targetRefreshTokenStorage)
|
||||
targetStorage: targetRefreshTokenStorage,
|
||||
errorLogger: MockStorageLogger.defaultLogger)
|
||||
|
||||
override func setUp() {
|
||||
let _ = sourceRefreshTokenStorage.deleteValue()
|
||||
let _ = targetRefreshTokenStorage.deleteValue()
|
||||
let _ = MockStorageLogger.defaultLogger.getError()
|
||||
}
|
||||
|
||||
// MARK: - Tests
|
||||
|
||||
func testMigrationOnGetValue() throws {
|
||||
let oldToken = "old-token"
|
||||
|
||||
let _ = sourceRefreshTokenStorage.store(value: oldToken)
|
||||
|
||||
XCTAssertEqual(try? refreshToken.getValue().get(), oldToken)
|
||||
XCTAssertEqual(try? targetRefreshTokenStorage.getValue().get(), oldToken)
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
func testGetValueFromTarget() throws {
|
||||
let token = "new-token"
|
||||
|
||||
let _ = targetRefreshTokenStorage.store(value: token)
|
||||
|
||||
XCTAssertEqual(try? refreshToken.getValue().get(), token)
|
||||
XCTAssertEqual(try? targetRefreshTokenStorage.getValue().get(), token)
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
// MARK: - Read Tests
|
||||
|
||||
// Precondition - source: ✅ target: ✅
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testGetValue() throws {
|
||||
let token = "token"
|
||||
|
||||
let _ = refreshToken.store(value: token)
|
||||
|
||||
XCTAssertEqual(try? refreshToken.getValue().get(), token)
|
||||
XCTAssertEqual(try? targetRefreshTokenStorage.getValue().get(), token)
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
func testMigrationOnHasValue() throws {
|
||||
let oldToken = "old-token"
|
||||
|
||||
let _ = sourceRefreshTokenStorage.store(value: oldToken)
|
||||
|
||||
XCTAssertTrue(refreshToken.hasStoredValue())
|
||||
XCTAssertFalse(targetRefreshTokenStorage.hasStoredValue())
|
||||
XCTAssertTrue(sourceRefreshTokenStorage.hasStoredValue())
|
||||
}
|
||||
|
||||
func testHasValueFromTarget() throws {
|
||||
let token = "new-token"
|
||||
|
||||
let _ = targetRefreshTokenStorage.store(value: token)
|
||||
|
||||
XCTAssertTrue(refreshToken.hasStoredValue())
|
||||
XCTAssertTrue(targetRefreshTokenStorage.hasStoredValue())
|
||||
XCTAssertFalse(sourceRefreshTokenStorage.hasStoredValue())
|
||||
}
|
||||
|
||||
func testHasValue() throws {
|
||||
let token = "token"
|
||||
|
||||
let _ = refreshToken.store(value: token)
|
||||
|
||||
XCTAssertTrue(refreshToken.hasStoredValue())
|
||||
XCTAssertTrue(targetRefreshTokenStorage.hasStoredValue())
|
||||
XCTAssertFalse(sourceRefreshTokenStorage.hasStoredValue())
|
||||
}
|
||||
|
||||
func testMigrationOnDeleteValue() throws {
|
||||
let oldToken = "old-token"
|
||||
|
||||
let _ = sourceRefreshTokenStorage.store(value: oldToken)
|
||||
|
||||
XCTAssertNoThrow(try refreshToken.deleteValue().get())
|
||||
XCTAssertThrowsError(try targetRefreshTokenStorage.getValue().get())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
func testDeleteValueFromTarget() throws {
|
||||
let token = "new-token"
|
||||
|
||||
let _ = targetRefreshTokenStorage.store(value: token)
|
||||
|
||||
XCTAssertNoThrow(try refreshToken.deleteValue().get())
|
||||
XCTAssertThrowsError(try targetRefreshTokenStorage.getValue().get())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
func testDeleteValue() throws {
|
||||
let token = "token"
|
||||
|
||||
let _ = refreshToken.store(value: token)
|
||||
|
||||
XCTAssertNoThrow(try refreshToken.deleteValue().get())
|
||||
XCTAssertThrowsError(try targetRefreshTokenStorage.getValue().get())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
func testMigrationOnStoreValue() throws {
|
||||
let oldToken = "old-token"
|
||||
let newToken = "new-token"
|
||||
|
||||
let _ = sourceRefreshTokenStorage.store(value: oldToken)
|
||||
let _ = refreshToken.store(value: newToken)
|
||||
let _ = targetRefreshTokenStorage.store(value: newToken)
|
||||
|
||||
XCTAssertEqual(try refreshToken.getValue().get(), newToken)
|
||||
XCTAssertNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertEqual(try? refreshToken.getValue().get(), newToken)
|
||||
XCTAssertEqual(try? targetRefreshTokenStorage.getValue().get(), newToken)
|
||||
XCTAssertNoThrow(try targetRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
func testStoreValueFromTarget() throws {
|
||||
let oldToken = "old-token"
|
||||
// Precondition - source: ❌ target: ✅
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testGetValueWithNoSource() throws {
|
||||
let newToken = "new-token"
|
||||
|
||||
let _ = targetRefreshTokenStorage.store(value: oldToken)
|
||||
let _ = refreshToken.store(value: newToken)
|
||||
let _ = targetRefreshTokenStorage.store(value: newToken)
|
||||
|
||||
XCTAssertEqual(try refreshToken.getValue().get(), newToken)
|
||||
XCTAssertNotNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertEqual(try? refreshToken.getValue().get(), newToken)
|
||||
XCTAssertEqual(try? targetRefreshTokenStorage.getValue().get(), newToken)
|
||||
XCTAssertNoThrow(try targetRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
// Precondition - source: ✅ target: ❌
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testGetValueWithNoTarget() throws {
|
||||
let oldToken = "old-token"
|
||||
|
||||
let _ = sourceRefreshTokenStorage.store(value: oldToken)
|
||||
|
||||
XCTAssertEqual(try refreshToken.getValue().get(), oldToken)
|
||||
XCTAssertNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertNoThrow(try targetRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
// Precondition - source: ❌ target: ❌
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testGetValueWithNoValues() throws {
|
||||
XCTAssertThrowsError(try refreshToken.getValue().get())
|
||||
XCTAssertNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertThrowsError(try targetRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
// MARK: - Write Tests
|
||||
|
||||
// Precondition - source: ✅ target: ✅
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testStoreValue() throws {
|
||||
let token = "token"
|
||||
let oldToken = "old-token"
|
||||
let newToken = "new-token"
|
||||
let currentToken = "token"
|
||||
|
||||
let _ = refreshToken.store(value: token)
|
||||
let _ = sourceRefreshTokenStorage.store(value: oldToken)
|
||||
let _ = targetRefreshTokenStorage.store(value: newToken)
|
||||
|
||||
XCTAssertEqual(try? refreshToken.getValue().get(), token)
|
||||
XCTAssertNoThrow(try refreshToken.store(value: currentToken).get())
|
||||
XCTAssertNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertEqual(try? targetRefreshTokenStorage.getValue().get(), token)
|
||||
XCTAssertEqual(try targetRefreshTokenStorage.getValue().get(), currentToken)
|
||||
}
|
||||
|
||||
// Precondition - source: ❌ target: ✅
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testStoreValueWithNoSource() throws {
|
||||
let newToken = "new-token"
|
||||
let currentToken = "token"
|
||||
|
||||
let _ = targetRefreshTokenStorage.store(value: newToken)
|
||||
|
||||
XCTAssertNoThrow(try refreshToken.store(value: currentToken).get())
|
||||
XCTAssertNotNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertEqual(try targetRefreshTokenStorage.getValue().get(), currentToken)
|
||||
}
|
||||
|
||||
// Precondition - source: ✅ target: ❌
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testStoreValueWithNoTarget() throws {
|
||||
let oldToken = "old-token"
|
||||
let currentToken = "token"
|
||||
|
||||
let _ = sourceRefreshTokenStorage.store(value: oldToken)
|
||||
|
||||
XCTAssertNoThrow(try refreshToken.store(value: currentToken).get())
|
||||
XCTAssertNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertEqual(try targetRefreshTokenStorage.getValue().get(), currentToken)
|
||||
}
|
||||
|
||||
// Precondition - source: ❌ target: ❌
|
||||
// PostCondition - source: ❌ target: ✅
|
||||
func testStoreValueWithNoValues() throws {
|
||||
let currentToken = "token"
|
||||
|
||||
XCTAssertNoThrow(try refreshToken.store(value: currentToken).get())
|
||||
XCTAssertNotNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertEqual(try targetRefreshTokenStorage.getValue().get(), currentToken)
|
||||
}
|
||||
|
||||
// MARK: - Delete Tests
|
||||
|
||||
// Precondition - source: ✅ target: ✅
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testDeleteValue() throws {
|
||||
let oldToken = "old-token"
|
||||
let newToken = "new-token"
|
||||
|
||||
let _ = sourceRefreshTokenStorage.store(value: oldToken)
|
||||
let _ = targetRefreshTokenStorage.store(value: newToken)
|
||||
|
||||
XCTAssertNoThrow(try refreshToken.deleteValue().get())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertThrowsError(try targetRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
// Precondition - source: ❌ target: ✅
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testDeleteValueWithNoSource() throws {
|
||||
let newToken = "new-token"
|
||||
|
||||
let _ = targetRefreshTokenStorage.store(value: newToken)
|
||||
|
||||
XCTAssertNoThrow(try refreshToken.deleteValue().get())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertThrowsError(try targetRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
// Precondition - source: ✅ target: ❌
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testDeleteValueWithNoTarget() throws {
|
||||
let oldToken = "old-token"
|
||||
|
||||
let _ = sourceRefreshTokenStorage.store(value: oldToken)
|
||||
|
||||
XCTAssertNoThrow(try refreshToken.deleteValue().get())
|
||||
XCTAssertNotNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertThrowsError(try targetRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
|
||||
// Precondition - source: ❌ target: ❌
|
||||
// PostCondition - source: ❌ target: ❌
|
||||
func testDeleteValueWithNoValues() throws {
|
||||
XCTAssertThrowsError(try refreshToken.deleteValue().get())
|
||||
XCTAssertNotNil(MockStorageLogger.defaultLogger.getError())
|
||||
XCTAssertThrowsError(try sourceRefreshTokenStorage.getValue().get())
|
||||
XCTAssertThrowsError(try targetRefreshTokenStorage.getValue().get())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,11 @@ class BaseSingleValueMockStorage<ValueType>: BaseSingleValueStorage<ValueType, M
|
|||
}
|
||||
|
||||
let deleteValueClosure: DeleteValueClosure = { storage, storageKey in
|
||||
.success(storage.deleteValue(forKey: storageKey))
|
||||
if storage.hasValue(forKey: storageKey) {
|
||||
return .success(storage.deleteValue(forKey: storageKey))
|
||||
}
|
||||
|
||||
return .failure(.valueNotFound)
|
||||
}
|
||||
|
||||
super.init(storage: storage,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ final class MockMigratingStorageContainer: BaseCodableMigratingStorageContainer<
|
|||
static let defaultContainer = MockMigratingStorageContainer(sourceStorage: .defaultStorage,
|
||||
targetStorage: .defaultGroupStorage)
|
||||
|
||||
override func removeSourceValue<Value>(forKey key: StorageKey<Value>) {
|
||||
let _ = sourceStorage.removeValue(forKey: key)
|
||||
override func removeSourceValue<Value>(forKey key: StorageKey<Value>) -> Result<Void, StorageError> {
|
||||
sourceStorage.removeValue(forKey: key)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIFoundationUtils
|
||||
import TILogging
|
||||
import os
|
||||
|
||||
final class MockStorageLogger: DefaultOSLogErrorLogger {
|
||||
|
||||
static let defaultLogger = MockStorageLogger(category: "MockStorageLogger")
|
||||
|
||||
private var recentError: StorageError?
|
||||
|
||||
init(category: String) {
|
||||
super.init(log: OSLog(subsystem: "TIFoundationUtilsTests", category: category))
|
||||
}
|
||||
|
||||
func getError() -> StorageError? {
|
||||
defer { recentError = nil }
|
||||
|
||||
return recentError
|
||||
}
|
||||
|
||||
override func log(error: Error, file: StaticString, line: Int) {
|
||||
recentError = error as? StorageError
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ final class StringSingleValueMockStorage: BaseSingleValueMockStorage<String> {
|
|||
}
|
||||
|
||||
let storeValueClosure: StoreValueClosure = { storage, value, storageKey in
|
||||
.success(storage.store(value: value, forKey: storageKey))
|
||||
.success(storage.store(value: value, forKey: storageKey))
|
||||
}
|
||||
|
||||
super.init(storage: storage,
|
||||
|
|
|
|||
|
|
@ -125,14 +125,7 @@ case let .failure(storageError):
|
|||
let groupKeychain = Keychain(service: "app.group.identifier")
|
||||
let targetApiTokenKeychainStorage = StringValueKeychainStorage(keychain: groupKeychain, storageKey: .apiToken)
|
||||
|
||||
let migratingApiTokenKeychainStorage = BaseMigratingSingleValueStorage(sourceStorage: apiTokenKeychainStorage,
|
||||
targetStorage: targetApiTokenKeychainStorage)
|
||||
let migratingApiTokenKeychainStorage = apiTokenKeychainStorage.migrating(to: targetApiTokenKeychainStorage)
|
||||
|
||||
let token = migratingApiTokenKeychainStorage.getValue()
|
||||
```
|
||||
|
||||
Если нет необходимости в создании target storage, можно воспользоваться переменной `migrationStorage`, которая есть у каждого хранилища, наследованного от `BaseSingleValueStorage`
|
||||
|
||||
```swift
|
||||
apiTokenKeychainStorage.migrationStorage = groupKeychain
|
||||
```
|
||||
|
|
|
|||
Loading…
Reference in New Issue