diff --git a/ConfigCat.podspec b/ConfigCat.podspec index 3601738..8027ea8 100755 --- a/ConfigCat.podspec +++ b/ConfigCat.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |spec| spec.name = "ConfigCat" - spec.version = "9.2.4" + spec.version = "9.3.0" spec.summary = "ConfigCat Swift SDK" spec.swift_version = "4.2" diff --git a/ConfigCat.xcconfig b/ConfigCat.xcconfig index 0439b24..84b4c94 100644 --- a/ConfigCat.xcconfig +++ b/ConfigCat.xcconfig @@ -47,4 +47,4 @@ SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator watchos watchsimulator app SWIFT_VERSION = 4.2 // ConfigCat SDK version -MARKETING_VERSION = 9.2.4 +MARKETING_VERSION = 9.3.0 diff --git a/README.md b/README.md index cdcd3d4..5e9ea73 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ If you want to use ConfigCat in a [SwiftPM](https://swift.org/package-manager/) ``` swift dependencies: [ - .package(url: "https://github.com/configcat/swift-sdk", from: "9.2.4") + .package(url: "https://github.com/configcat/swift-sdk", from: "9.3.0") ] ``` diff --git a/Sources/ConfigCat/ConfigCache.swift b/Sources/ConfigCat/ConfigCache.swift index d30be08..d691dca 100755 --- a/Sources/ConfigCat/ConfigCache.swift +++ b/Sources/ConfigCat/ConfigCache.swift @@ -22,3 +22,15 @@ import Foundation */ func write(for key: String, value: String) throws } + +class UserDefaultsCache: ConfigCache { + private let prefix = "com.configcat-" + + func read(for key: String) throws -> String { + UserDefaults.standard.string(forKey: prefix + key) ?? "" + } + + func write(for key: String, value: String) throws { + UserDefaults.standard.set(value, forKey: prefix + key) + } +} diff --git a/Sources/ConfigCat/ConfigCatClient.swift b/Sources/ConfigCat/ConfigCatClient.swift index da9611e..d31894c 100755 --- a/Sources/ConfigCat/ConfigCatClient.swift +++ b/Sources/ConfigCat/ConfigCatClient.swift @@ -50,7 +50,7 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { flagOverrides: OverrideDataSource? = nil, logLevel: LogLevel = .warning) { self.init(sdkKey: sdkKey, pollingMode: refreshMode, httpEngine: URLSessionEngine(session: URLSession(configuration: sessionConfiguration)), - configCache: configCache, baseUrl: baseUrl, dataGovernance: dataGovernance, flagOverrides: flagOverrides, logLevel: logLevel) + configCache: configCache ?? UserDefaultsCache(), baseUrl: baseUrl, dataGovernance: dataGovernance, flagOverrides: flagOverrides, logLevel: logLevel) } init(sdkKey: String, @@ -197,6 +197,7 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { if key.isEmpty { assert(false, "key cannot be empty") } + let evalUser = user ?? defaultUser if Value.self != String.self && Value.self != String?.self && Value.self != Int.self && @@ -211,7 +212,8 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { log.error(message: message) hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, - error: message)) + error: message, + user: evalUser)) completion(defaultValue) return } @@ -221,7 +223,8 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { self.log.error(message: message) self.hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, - error: message)) + error: message, + user: evalUser)) completion(defaultValue) return } @@ -230,12 +233,13 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { self.log.error(message: message) self.hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, - error: message)) + error: message, + user: evalUser)) completion(defaultValue) return } - let evalDetails = self.evaluate(setting: setting, key: key, user: user ?? self.defaultUser, fetchTime: result.fetchTime) + let evalDetails = self.evaluate(setting: setting, key: key, user: evalUser, fetchTime: result.fetchTime) completion(evalDetails.value as? Value ?? defaultValue) } } @@ -252,6 +256,7 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { if key.isEmpty { assert(false, "key cannot be empty") } + let evalUser = user ?? defaultUser if Value.self != String.self && Value.self != String?.self && Value.self != Int.self && @@ -264,16 +269,16 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { Value.self != Any?.self { let message = "Only String, Integer, Double, Bool or Any types are supported." log.error(message: message) - hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, error: message)) - completion(TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: message)) + hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, error: message, user: evalUser)) + completion(TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: message, user: evalUser)) return } getSettings { result in if result.settings.isEmpty { let message = String(format: "Config is not present. Returning defaultValue: [%@].", "\(defaultValue)") self.log.error(message: message) - self.hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, error: message)) - completion(TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: message)) + self.hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, error: message, user: evalUser)) + completion(TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: message, user: evalUser)) return } guard let setting = result.settings[key] else { @@ -281,12 +286,13 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { self.log.error(message: message) self.hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, - error: message)) - completion(TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: message)) + error: message, + user: evalUser)) + completion(TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: message, user: evalUser)) return } - let details = self.evaluate(setting: setting, key: key, user: user ?? self.defaultUser, fetchTime: result.fetchTime) + let details = self.evaluate(setting: setting, key: key, user: evalUser, fetchTime: result.fetchTime) guard let typedValue = details.value as? Value else { let message = String(format: """ The value '%@' cannot be converted to the requested type. @@ -294,8 +300,8 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { Here are the available keys: %@ """, "\(details.value)", "\(defaultValue)", [String](result.settings.keys)) self.log.error(message: message) - self.hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, error: message)) - completion(TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: message)) + self.hooks.invokeOnFlagEvaluated(details: EvaluationDetails.fromError(key: key, value: defaultValue, error: message, user: evalUser)) + completion(TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: message, user: evalUser)) return } @@ -311,6 +317,26 @@ public final class ConfigCatClient: NSObject, ConfigCatClientProtocol { } } + /** + Gets the values along with evaluation details of all feature flags and settings. + + - Parameter user: the user object to identify the caller. + - Parameter completion: the function which will be called when the feature flag or setting is evaluated. + */ + @objc public func getAllValueDetails(user: ConfigCatUser? = nil, completion: @escaping ([EvaluationDetails]) -> ()) { + getSettings { result in + var detailsResult = [EvaluationDetails]() + for key in result.settings.keys { + guard let setting = result.settings[key] else { + continue + } + let details = self.evaluate(setting: setting, key: key, user: user ?? self.defaultUser, fetchTime: result.fetchTime) + detailsResult.append(details) + } + completion(detailsResult) + } + } + /// Gets all the setting keys asynchronously. @objc public func getAllKeys(completion: @escaping ([String]) -> ()) { getSettings { result in diff --git a/Sources/ConfigCat/ConfigCatClientProtocol.swift b/Sources/ConfigCat/ConfigCatClientProtocol.swift index 2a83168..d0f0acc 100755 --- a/Sources/ConfigCat/ConfigCatClientProtocol.swift +++ b/Sources/ConfigCat/ConfigCatClientProtocol.swift @@ -22,13 +22,23 @@ public protocol ConfigCatClientProtocol { */ func getValueDetails(for key: String, defaultValue: Value, user: ConfigCatUser?, completion: @escaping (TypedEvaluationDetails) -> ()) + /** + Gets the values along with evaluation details of all feature flags and settings. + + - Parameter user: the user object to identify the caller. + - Parameter completion: the function which will be called when the feature flag or setting is evaluated. + */ + func getAllValueDetails(user: ConfigCatUser?, completion: @escaping ([EvaluationDetails]) -> ()) + /// Gets all the setting keys asynchronously. func getAllKeys(completion: @escaping ([String]) -> ()) /// Gets the Variation ID (analytics) of a feature flag or setting based on it's key asynchronously. + @available(*, deprecated, message: "This method is obsolete and will be removed in a future major version. Please use getValueDetails() instead.") func getVariationId(for key: String, defaultVariationId: String?, user: ConfigCatUser?, completion: @escaping (String?) -> ()) /// Gets the Variation IDs (analytics) of all feature flags or settings asynchronously. + @available(*, deprecated, message: "This method is obsolete and will be removed in a future major version. Please use getAllValueDetails() instead.") func getAllVariationIds(user: ConfigCatUser?, completion: @escaping ([String]) -> ()) /// Gets the key of a setting and it's value identified by the given Variation ID (analytics) @@ -85,21 +95,30 @@ public protocol ConfigCatClientProtocol { - Parameter key: the identifier of the feature flag or setting. - Parameter defaultValue: in case of any failure, this value will be returned. - Parameter user: the user object to identify the caller. - - Parameter completion: the function which will be called when the feature flag or setting is evaluated. */ @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) func getValueDetails(for key: String, defaultValue: Value, user: ConfigCatUser?) async -> TypedEvaluationDetails + /** + Gets the values along with evaluation details of all feature flags and settings. + + - Parameter user: the user object to identify the caller. + */ + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + func getAllValueDetails(user: ConfigCatUser?) async -> [EvaluationDetails] + /// Gets all the setting keys asynchronously. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) func getAllKeys() async -> [String] /// Gets the Variation ID (analytics) of a feature flag or setting based on it's key asynchronously. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @available(*, deprecated, message: "This method is obsolete and will be removed in a future major version. Please use getValueDetails() instead.") func getVariationId(for key: String, defaultVariationId: String?, user: ConfigCatUser?) async -> String? /// Gets the Variation IDs (analytics) of all feature flags or settings asynchronously. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @available(*, deprecated, message: "This method is obsolete and will be removed in a future major version. Please use getAllValueDetails() instead.") func getAllVariationIds(user: ConfigCatUser?) async -> [String] /// Gets the key of a setting and it's value identified by the given Variation ID (analytics) diff --git a/Sources/ConfigCat/ConfigCatOptions.swift b/Sources/ConfigCat/ConfigCatOptions.swift index c909b5f..c49f6f0 100644 --- a/Sources/ConfigCat/ConfigCatOptions.swift +++ b/Sources/ConfigCat/ConfigCatOptions.swift @@ -10,7 +10,7 @@ public final class ConfigCatOptions: NSObject { @objc public var dataGovernance: DataGovernance = .global /// The cache implementation used to cache the downloaded config.json. - @objc public var configCache: ConfigCache? = nil + @objc public var configCache: ConfigCache? = UserDefaultsCache() /// The polling mode. @objc public var pollingMode: PollingMode = PollingModes.autoPoll() diff --git a/Sources/ConfigCat/ConfigFetcher.swift b/Sources/ConfigCat/ConfigFetcher.swift index 8e6328e..7e78226 100755 --- a/Sources/ConfigCat/ConfigFetcher.swift +++ b/Sources/ConfigCat/ConfigFetcher.swift @@ -9,7 +9,7 @@ enum RedirectMode: Int { enum FetchResponse: Equatable { case fetched(ConfigEntry) case notModified - case failure(String) + case failure(message: String, isTransient: Bool) public var entry: ConfigEntry? { switch self { @@ -25,7 +25,7 @@ func ==(lhs: FetchResponse, rhs: FetchResponse) -> Bool { switch (lhs, rhs) { case (.fetched(_), .fetched(_)), (.notModified, .notModified), - (.failure(_), .failure(_)): + (.failure(_, _), .failure(_, _)): return true default: return false @@ -131,7 +131,7 @@ class ConfigFetcher: NSObject { } let message = String(format: "An error occurred during the config fetch: %@%@", error.localizedDescription, extraInfo) self.log.error(message: message) - completion(.failure(message)) + completion(.failure(message: message, isTransient: true)) } else { let response = resp as! HTTPURLResponse if response.statusCode >= 200 && response.statusCode < 300, let data = data { @@ -145,17 +145,23 @@ class ConfigFetcher: NSObject { case .failure(let error): let message = String(format: "An error occurred during JSON deserialization. %@", error.localizedDescription) self.log.error(message: message) - completion(.failure(message)) + completion(.failure(message: message, isTransient: true)) } } else if response.statusCode == 304 { self.log.debug(message: "Fetch was successful: not modified") completion(.notModified) + } else if response.statusCode == 404 || response.statusCode == 403 { + let message = String(format: """ + Double-check your SDK Key at https://app.configcat.com/sdkkey. Status code: %@ + """, String(response.statusCode)) + self.log.error(message: message) + completion(.failure(message: message, isTransient: false)) } else { let message = String(format: """ - Double-check your SDK Key at https://app.configcat.com/sdkkey. Non success status code: %@ + Unexpected HTTP response was received: %@ """, String(response.statusCode)) self.log.error(message: message) - completion(.failure(message)) + completion(.failure(message: message, isTransient: true)) } } } diff --git a/Sources/ConfigCat/ConfigService.swift b/Sources/ConfigCat/ConfigService.swift index dc4988a..53b63e5 100644 --- a/Sources/ConfigCat/ConfigService.swift +++ b/Sources/ConfigCat/ConfigService.swift @@ -214,7 +214,11 @@ class ConfigService { cachedEntry = cachedEntry.withFetchTime(time: Date()) writeCache(entry: cachedEntry) callCompletions(result: .success(cachedEntry)) - case .failure(let error): + case .failure(let error, let isTransient): + if !isTransient && !cachedEntry.isEmpty { + cachedEntry = cachedEntry.withFetchTime(time: Date()) + writeCache(entry: cachedEntry) + } callCompletions(result: .failure(error, cachedEntry)) } completions = nil diff --git a/Sources/ConfigCat/EvaluationDetails.swift b/Sources/ConfigCat/EvaluationDetails.swift index ad1a4a7..ac3d3f0 100644 --- a/Sources/ConfigCat/EvaluationDetails.swift +++ b/Sources/ConfigCat/EvaluationDetails.swift @@ -45,8 +45,8 @@ public final class EvaluationDetails: EvaluationDetailsBase { super.init(key: key, variationId: variationId, fetchTime: fetchTime, user: user, isDefaultValue: isDefaultValue, error: error, matchedEvaluationRule: matchedEvaluationRule, matchedEvaluationPercentageRule: matchedEvaluationPercentageRule) } - static func fromError(key: String, value: Any, error: String) -> EvaluationDetails { - EvaluationDetails(key: key, value: value, variationId: "", isDefaultValue: true, error: error) + static func fromError(key: String, value: Any, error: String, user: ConfigCatUser?) -> EvaluationDetails { + EvaluationDetails(key: key, value: value, variationId: "", user: user, isDefaultValue: true, error: error) } } @@ -106,8 +106,8 @@ public final class TypedEvaluationDetails: EvaluationDetailsBase { super.init(key: key, variationId: variationId, fetchTime: fetchTime, user: user, isDefaultValue: isDefaultValue, error: error, matchedEvaluationRule: matchedEvaluationRule, matchedEvaluationPercentageRule: matchedEvaluationPercentageRule) } - static func fromError(key: String, value: Value, error: String) -> TypedEvaluationDetails { - TypedEvaluationDetails(key: key, value: value, variationId: "", isDefaultValue: true, error: error) + static func fromError(key: String, value: Value, error: String, user: ConfigCatUser?) -> TypedEvaluationDetails { + TypedEvaluationDetails(key: key, value: value, variationId: "", user: user, isDefaultValue: true, error: error) } func toStringDetails() -> StringEvaluationDetails { diff --git a/Sources/ConfigCat/Extensions.swift b/Sources/ConfigCat/Extensions.swift index d0edf7a..90c62dd 100644 --- a/Sources/ConfigCat/Extensions.swift +++ b/Sources/ConfigCat/Extensions.swift @@ -78,6 +78,15 @@ extension ConfigCatClient { } } + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + public func getAllValueDetails(user: ConfigCatUser? = nil) async -> [EvaluationDetails] { + await withCheckedContinuation { continuation in + getAllValueDetails(user: user) { details in + continuation.resume(returning: details) + } + } + } + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public func getAllKeys() async -> [String] { await withCheckedContinuation { continuation in @@ -88,6 +97,7 @@ extension ConfigCatClient { } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @available(*, deprecated, message: "This method is obsolete and will be removed in a future major version. Please use getValueDetails() instead.") public func getVariationId(for key: String, defaultVariationId: String?, user: ConfigCatUser? = nil) async -> String? { await withCheckedContinuation { continuation in getVariationId(for: key, defaultVariationId: defaultVariationId, user: user) { variationId in @@ -97,6 +107,7 @@ extension ConfigCatClient { } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @available(*, deprecated, message: "This method is obsolete and will be removed in a future major version. Please use getAllValueDetails() instead.") public func getAllVariationIds(user: ConfigCatUser? = nil) async -> [String] { await withCheckedContinuation { continuation in getAllVariationIds(user: user) { variationIds in @@ -165,7 +176,18 @@ extension ConfigCatClient { semaphore.signal() } semaphore.wait() - return result ?? TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: String(format: "Could not get the evaluation details for '%@'.", key)) + return result ?? TypedEvaluationDetails.fromError(key: key, value: defaultValue, error: String(format: "Could not get the evaluation details for '%@'.", key), user: user) + } + + @objc public func getAllValueDetailsSync(user: ConfigCatUser? = nil) -> [EvaluationDetails] { + let semaphore = DispatchSemaphore(value: 0) + var result = [EvaluationDetails]() + getAllValueDetails(user: user) { details in + result = details + semaphore.signal() + } + semaphore.wait() + return result } @objc public func getAllKeysSync() -> [String] { @@ -179,6 +201,7 @@ extension ConfigCatClient { return result } + @available(*, deprecated, message: "This method is obsolete and will be removed in a future major version. Please use getValueDetailsSync() instead.") @objc public func getVariationIdSync(for key: String, defaultVariationId: String?, user: ConfigCatUser? = nil) -> String? { let semaphore = DispatchSemaphore(value: 0) var result: String? @@ -190,6 +213,7 @@ extension ConfigCatClient { return result ?? defaultVariationId } + @available(*, deprecated, message: "This method is obsolete and will be removed in a future major version. Please use getAllValueDetailsSync() instead.") @objc public func getAllVariationIdsSync(user: ConfigCatUser? = nil) -> [String] { let semaphore = DispatchSemaphore(value: 0) var result = [String]() diff --git a/Sources/ConfigCat/Utils.swift b/Sources/ConfigCat/Utils.swift index 3fdc7ef..73f5260 100644 --- a/Sources/ConfigCat/Utils.swift +++ b/Sources/ConfigCat/Utils.swift @@ -38,7 +38,7 @@ extension Date { } class Constants { - static let version: String = "9.2.4" + static let version: String = "9.3.0" static let configJsonName: String = "config_v5" static let globalBaseUrl: String = "https://cdn-global.configcat.com" static let euOnlyBaseUrl: String = "https://cdn-eu.configcat.com" diff --git a/Tests/ConfigCatTests/AsyncAwaitTests.swift b/Tests/ConfigCatTests/AsyncAwaitTests.swift index 227fd93..c4af34a 100644 --- a/Tests/ConfigCatTests/AsyncAwaitTests.swift +++ b/Tests/ConfigCatTests/AsyncAwaitTests.swift @@ -61,6 +61,16 @@ class AsyncAwaitTests: XCTestCase { XCTAssertEqual(2, values.count) } + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + func testGetAllValueDetails() async { + let engine = MockEngine() + engine.enqueueResponse(response: Response(body: testJsonMultiple, statusCode: 200)) + + let client = ConfigCatClient(sdkKey: "test", pollingMode: PollingModes.autoPoll(), httpEngine: engine) + let values = await client.getAllValueDetails() + XCTAssertEqual(2, values.count) + } + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) func testRefresh() async { let engine = MockEngine() diff --git a/Tests/ConfigCatTests/ConfigCatClientIntegrationTests.swift b/Tests/ConfigCatTests/ConfigCatClientIntegrationTests.swift index e75c690..8db1cf5 100755 --- a/Tests/ConfigCatTests/ConfigCatClientIntegrationTests.swift +++ b/Tests/ConfigCatTests/ConfigCatClientIntegrationTests.swift @@ -28,6 +28,18 @@ class ConfigCatClientIntegrationTests: XCTestCase { wait(for: [expectation], timeout: 20) } + func testGetAllValueDetails() { + let client = ConfigCatClient.get(sdkKey: "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A") { options in + options.pollingMode = PollingModes.lazyLoad() + } + let expectation = expectation(description: "wait for all values") + client.getAllValueDetails { details in + XCTAssertEqual(16, details.count) + expectation.fulfill() + } + wait(for: [expectation], timeout: 20) + } + func testEvalDetails() { let client = ConfigCatClient.get(sdkKey: "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A") { options in options.pollingMode = PollingModes.lazyLoad() diff --git a/Tests/ConfigCatTests/ConfigCatClientTests.swift b/Tests/ConfigCatTests/ConfigCatClientTests.swift index 8fbb514..d42cb64 100755 --- a/Tests/ConfigCatTests/ConfigCatClientTests.swift +++ b/Tests/ConfigCatTests/ConfigCatClientTests.swift @@ -497,7 +497,7 @@ class ConfigCatClientTests: XCTestCase { func testHooks() { let engine = MockEngine() engine.enqueueResponse(response: Response(body: String(format: testJsonFormat, "\"fake\""), statusCode: 200)) - engine.enqueueResponse(response: Response(body: "", statusCode: 500)) + engine.enqueueResponse(response: Response(body: "", statusCode: 404)) var error = "" var changed = false var ready = false @@ -526,14 +526,14 @@ class ConfigCatClientTests: XCTestCase { wait(for: [expectation2], timeout: 5) waitFor { - changed && ready && error.starts(with: "Double-check your SDK Key at https://app.configcat.com/sdkkey.") && error.contains("500") + changed && ready && error.starts(with: "Double-check your SDK Key at https://app.configcat.com/sdkkey.") && error.contains("404") } } func testHooksSub() { let engine = MockEngine() engine.enqueueResponse(response: Response(body: String(format: testJsonFormat, "\"fake\""), statusCode: 200)) - engine.enqueueResponse(response: Response(body: "", statusCode: 500)) + engine.enqueueResponse(response: Response(body: "", statusCode: 404)) var error = "" var changed = false let hooks = Hooks() @@ -559,8 +559,32 @@ class ConfigCatClientTests: XCTestCase { wait(for: [expectation2], timeout: 5) waitFor { - changed && error.starts(with: "Double-check your SDK Key at https://app.configcat.com/sdkkey.") && error.contains("500") + changed && error.starts(with: "Double-check your SDK Key at https://app.configcat.com/sdkkey.") && error.contains("404") + } + } + + func testDefaultCache() { + let engine = MockEngine() + let cache = UserDefaultsCache() + engine.enqueueResponse(response: Response(body: String(format: testJsonFormat, "\"fake\""), statusCode: 200)) + let client = ConfigCatClient(sdkKey: "testDefaultCache", pollingMode: PollingModes.lazyLoad(), httpEngine: engine, configCache: cache) + + let expectation = self.expectation(description: "wait for response") + client.getValue(for: "fakeKey", defaultValue: "") { r in + XCTAssertEqual("fake", r) + expectation.fulfill() } + wait(for: [expectation], timeout: 5) + + let expectation2 = self.expectation(description: "wait for response") + client.getValue(for: "fakeKey", defaultValue: "") { r in + XCTAssertEqual("fake", r) + expectation2.fulfill() + } + wait(for: [expectation2], timeout: 5) + + XCTAssertEqual(1, engine.requests.count) + try XCTAssertFalse(cache.read(for: "ca67405a97c0f10ec01fdc65276fc6f4f009bc48").isEmpty) } func testOnFlagEvaluationError() { diff --git a/Tests/ConfigCatTests/ConfigFetcherTests.swift b/Tests/ConfigCatTests/ConfigFetcherTests.swift index 6cbfff7..a3da1ef 100755 --- a/Tests/ConfigCatTests/ConfigFetcherTests.swift +++ b/Tests/ConfigCatTests/ConfigFetcherTests.swift @@ -38,7 +38,7 @@ class ConfigFetcherTests: XCTestCase { let expectation = self.expectation(description: "wait for response") let fetcher = ConfigFetcher(httpEngine: engine, logger: Logger.noLogger, sdkKey: "", mode: "m", dataGovernance: DataGovernance.global) fetcher.fetch(eTag: "") { response in - XCTAssertEqual(.failure(""), response) + XCTAssertEqual(.failure(message: "", isTransient: false), response) XCTAssertNil(response.entry) expectation.fulfill() } diff --git a/Tests/ConfigCatTests/ManualPollingTests.swift b/Tests/ConfigCatTests/ManualPollingTests.swift index d91e614..86c3f84 100755 --- a/Tests/ConfigCatTests/ManualPollingTests.swift +++ b/Tests/ConfigCatTests/ManualPollingTests.swift @@ -39,7 +39,7 @@ class ManualPollingTests: XCTestCase { func testGetFailedRefresh() throws { let engine = MockEngine() engine.enqueueResponse(response: Response(body: String(format: testJsonFormat, "test"), statusCode: 200)) - engine.enqueueResponse(response: Response(body: String(format: testJsonFormat, "test2"), statusCode: 500)) + engine.enqueueResponse(response: Response(body: String(format: testJsonFormat, "test2"), statusCode: 404)) var called = false let hooks = Hooks() @@ -67,7 +67,7 @@ class ManualPollingTests: XCTestCase { let expectation2 = self.expectation(description: "wait for response") service.refresh { result in XCTAssertFalse(result.success) - XCTAssertTrue(result.error?.starts(with: "Double-check your SDK Key at https://app.configcat.com/sdkkey.") ?? false && result.error?.contains("500") ?? false) + XCTAssertTrue(result.error?.starts(with: "Double-check your SDK Key at https://app.configcat.com/sdkkey.") ?? false && result.error?.contains("404") ?? false) service.settings { settingsResult in XCTAssertEqual("test", settingsResult.settings["fakeKey"]?.value as? String) expectation2.fulfill() diff --git a/Tests/ConfigCatTests/SyncTests.swift b/Tests/ConfigCatTests/SyncTests.swift index c1df11a..71cfac6 100644 --- a/Tests/ConfigCatTests/SyncTests.swift +++ b/Tests/ConfigCatTests/SyncTests.swift @@ -50,6 +50,14 @@ class SyncTests: XCTestCase { XCTAssertEqual(2, values.count) } + func testGetAllValueDetails() { + let engine = MockEngine() + engine.enqueueResponse(response: Response(body: testJsonMultiple, statusCode: 200)) + let client = ConfigCatClient(sdkKey: "test", pollingMode: PollingModes.autoPoll(), httpEngine: engine) + let values = client.getAllValuesSync() + XCTAssertEqual(2, values.count) + } + func testRefresh() { let engine = MockEngine() engine.enqueueResponse(response: Response(body: testJsonMultiple, statusCode: 200))