feat: update tests, migration fixes, factory method for migration storage added

This commit is contained in:
Nikita Semenov 2023-07-06 12:34:48 +03:00
parent 46ecd6970f
commit 25c0d04d11
19 changed files with 495 additions and 201 deletions

View File

@ -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")
]
)

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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))
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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())
}
}

View File

@ -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())
}
}

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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,

View File

@ -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
```