* Extended `MutableFeatureFlagStoreProtocol` with an optional method `saveChanges()` * Implemented `saveChanges()` in `UserDefaultsStore` * Added change saving to `FeatureFlagResolver`’s `deinit` * Extended `FeatureFlagStore` with an `asMutable` computed property * Added `MutableStoreTests` * Updated Linux tests
This commit is contained in:
parent
8769b41da0
commit
4c83b8d58a
|
@ -23,6 +23,12 @@ final public class FeatureFlagResolver {
|
|||
self.configuration = configuration
|
||||
}
|
||||
|
||||
deinit {
|
||||
configuration.stores
|
||||
.compactMap({ $0.asMutable })
|
||||
.forEach({ $0.saveChanges() })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - FeatureFlagResolverProtocol
|
||||
|
@ -106,13 +112,7 @@ extension FeatureFlagResolver {
|
|||
}
|
||||
|
||||
private func findMutableStores() throws -> [MutableFeatureFlagStoreProtocol] {
|
||||
var stores = [MutableFeatureFlagStoreProtocol]()
|
||||
|
||||
for store in configuration.stores {
|
||||
if case .mutable(let mutableStore) = store {
|
||||
stores.append(mutableStore)
|
||||
}
|
||||
}
|
||||
let stores = configuration.stores.compactMap({ $0.asMutable })
|
||||
|
||||
if stores.isEmpty {
|
||||
throw FeatureFlagResolverError.noMutableStoreAvailable
|
||||
|
|
|
@ -48,6 +48,10 @@ extension UserDefaultsStore: MutableFeatureFlagStoreProtocol {
|
|||
userDefaults.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
public func saveChanges() {
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -25,4 +25,13 @@ public extension FeatureFlagStore {
|
|||
}
|
||||
}
|
||||
|
||||
var asMutable: MutableFeatureFlagStoreProtocol? {
|
||||
switch self {
|
||||
case .immutable:
|
||||
return nil
|
||||
case .mutable(let store):
|
||||
return store
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,4 +21,18 @@ public protocol MutableFeatureFlagStoreProtocol: AnyObject, FeatureFlagStoreProt
|
|||
/// - Parameter key: *Required.* The key used to address the value.
|
||||
func removeValue(forKey key: String)
|
||||
|
||||
/// Immediately saves changed values so they’re not lost.
|
||||
///
|
||||
/// + This method can be called when work with the feature flag store is finished.
|
||||
func saveChanges()
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Default Implementation
|
||||
|
||||
extension MutableFeatureFlagStoreProtocol {
|
||||
|
||||
// Not all kinds of feature flag stores need this method, so it’s optional to implement.
|
||||
public func saveChanges() { }
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// MutableStoreTests.swift
|
||||
// YMFF
|
||||
//
|
||||
// Created by Yakov Manshin on 4/17/21.
|
||||
// Copyright © 2021 Yakov Manshin. See the LICENSE file for license info.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import YMFFProtocols
|
||||
|
||||
@testable import YMFF
|
||||
|
||||
// MARK: - MutableStoreTests
|
||||
|
||||
final class MutableStoreTests: XCTestCase {
|
||||
|
||||
private var mutableStore: MutableFeatureFlagStoreProtocol!
|
||||
private var resolver: FeatureFlagResolverProtocol!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
mutableStore = MutableFeatureFlagStore(store: .init())
|
||||
resolver = FeatureFlagResolver(configuration: .init(stores: [.mutable(mutableStore)]))
|
||||
}
|
||||
|
||||
func testOverride() {
|
||||
let overrideKey = "OVERRIDE_KEY"
|
||||
let overrideValue = 123
|
||||
|
||||
let initialValueFromResolver = try? resolver.value(for: overrideKey) as Int
|
||||
let initialValueFromStore: Int? = mutableStore.value(forKey: overrideKey)
|
||||
|
||||
XCTAssertNil(initialValueFromResolver)
|
||||
XCTAssertNil(initialValueFromStore)
|
||||
|
||||
try? resolver.overrideInRuntime(overrideKey, with: overrideValue)
|
||||
let overrideValueFromResolver = try? resolver.value(for: overrideKey) as Int
|
||||
let overrideValueFromStore: Int? = mutableStore.value(forKey: overrideKey)
|
||||
|
||||
XCTAssertEqual(overrideValueFromResolver, overrideValue)
|
||||
XCTAssertEqual(overrideValueFromStore, overrideValue)
|
||||
}
|
||||
|
||||
func testOverrideRemoval() {
|
||||
let overrideKey = "OVERRIDE_KEY"
|
||||
let overrideValue = 123
|
||||
|
||||
mutableStore.setValue(overrideValue, forKey: overrideKey)
|
||||
|
||||
let overrideValueFromResolver = try? resolver.value(for: overrideKey) as Int
|
||||
let overrideValueFromStore: Int? = mutableStore.value(forKey: overrideKey)
|
||||
|
||||
XCTAssertEqual(overrideValueFromResolver, overrideValue)
|
||||
XCTAssertEqual(overrideValueFromStore, overrideValue)
|
||||
|
||||
resolver.removeRuntimeOverride(for: overrideKey)
|
||||
|
||||
let removedValueFromResolver = try? resolver.value(for: overrideKey) as Int
|
||||
let removedValueFromStore: Int? = mutableStore.value(forKey: overrideKey)
|
||||
|
||||
XCTAssertNil(removedValueFromResolver)
|
||||
XCTAssertNil(removedValueFromStore)
|
||||
}
|
||||
|
||||
func testChangeSaving() {
|
||||
var saveChangesCount = 0
|
||||
|
||||
mutableStore = MutableFeatureFlagStore(store: .init()) {
|
||||
saveChangesCount += 1
|
||||
}
|
||||
resolver = FeatureFlagResolver(configuration: .init(stores: [.mutable(mutableStore)]))
|
||||
|
||||
resolver = nil
|
||||
|
||||
XCTAssertEqual(saveChangesCount, 1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MutableFeatureFlagStore
|
||||
|
||||
final private class MutableFeatureFlagStore: MutableFeatureFlagStoreProtocol {
|
||||
|
||||
private var store: TransparentFeatureFlagStore
|
||||
private var onSaveChangesClosure: () -> Void
|
||||
|
||||
init(store: TransparentFeatureFlagStore, onSaveChanges: @escaping () -> Void = { }) {
|
||||
self.store = store
|
||||
self.onSaveChangesClosure = onSaveChanges
|
||||
}
|
||||
|
||||
func containsValue(forKey key: String) -> Bool {
|
||||
store[key] != nil
|
||||
}
|
||||
|
||||
func value<Value>(forKey key: String) -> Value? {
|
||||
let expectedValueType = Value.self
|
||||
|
||||
switch expectedValueType {
|
||||
case is Bool.Type,
|
||||
is Int.Type,
|
||||
is String.Type,
|
||||
is Optional<Bool>.Type,
|
||||
is Optional<Int>.Type,
|
||||
is Optional<String>.Type:
|
||||
return store[key] as? Value
|
||||
default:
|
||||
assertionFailure("The expected feature flag value type (\(expectedValueType)) is not supported")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func setValue<Value>(_ value: Value, forKey key: String) {
|
||||
store[key] = value
|
||||
}
|
||||
|
||||
func removeValue(forKey key: String) {
|
||||
store.removeValue(forKey: key)
|
||||
}
|
||||
|
||||
func saveChanges() {
|
||||
onSaveChangesClosure()
|
||||
}
|
||||
|
||||
}
|
|
@ -44,6 +44,17 @@ extension FeatureFlagTests {
|
|||
]
|
||||
}
|
||||
|
||||
extension MutableStoreTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__MutableStoreTests = [
|
||||
("testChangeSaving", testChangeSaving),
|
||||
("testOverride", testOverride),
|
||||
("testOverrideRemoval", testOverrideRemoval),
|
||||
]
|
||||
}
|
||||
|
||||
extension RuntimeOverridesStoreTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
|
@ -70,6 +81,7 @@ public func __allTests() -> [XCTestCaseEntry] {
|
|||
return [
|
||||
testCase(FeatureFlagResolverTests.__allTests__FeatureFlagResolverTests),
|
||||
testCase(FeatureFlagTests.__allTests__FeatureFlagTests),
|
||||
testCase(MutableStoreTests.__allTests__MutableStoreTests),
|
||||
testCase(RuntimeOverridesStoreTests.__allTests__RuntimeOverridesStoreTests),
|
||||
testCase(UserDefaultsStoreTests.__allTests__UserDefaultsStoreTests),
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue