From 8d4c046f86db911ed664988f32c83a956f65cebb Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Mon, 8 Jul 2024 16:23:38 +0200 Subject: [PATCH 01/10] feat: add timeout to fetchAndActivate --- Sources/Confidence/Confidence.swift | 12 +++++------ Sources/Confidence/ConfidenceClient.swift | 2 +- .../RemoteResolveConfidenceClient.swift | 21 ++++++++++++++----- .../ConfidenceProviderTest.swift | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index a3abefc2..41aa7bba 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -81,13 +81,13 @@ public class Confidence: ConfidenceEventSender { /** Fetches latest flag evaluations and store them on disk. Regardless of the fetch outcome (success or failure), this - function activates the cache after the fetch. + function activates the cache after the fetch. Timeout for this function is configurable. Activating the cache means that the flag data on disk is loaded into memory, so consumers can access flag values. Fetching is best-effort, so no error is propagated. Errors can still be thrown if something goes wrong access data on disk. */ - public func fetchAndActivate() async throws { + public func fetchAndActivate(fetchTimeoutSec: Int = 10) async throws { do { - try await internalFetch() + try await internalFetch(fetchTimeoutSec: fetchTimeoutSec) } catch { debugLogger?.logMessage( message: "\(error)", @@ -97,9 +97,9 @@ public class Confidence: ConfidenceEventSender { try activate() } - func internalFetch() async throws { + func internalFetch(fetchTimeoutSec: Int? = nil) async throws { let context = getContext() - let resolvedFlags = try await remoteFlagResolver.resolve(ctx: context) + let resolvedFlags = try await remoteFlagResolver.resolve(ctx: context, withTimeout: fetchTimeoutSec) let resolution = FlagResolution( context: context, flags: resolvedFlags.resolvedValues, @@ -116,7 +116,7 @@ public class Confidence: ConfidenceEventSender { public func asyncFetch() { Task { do { - try await internalFetch() + try await internalFetch(fetchTimeoutSec: nil) } catch { debugLogger?.logMessage( message: "\(error )", diff --git a/Sources/Confidence/ConfidenceClient.swift b/Sources/Confidence/ConfidenceClient.swift index 45f91428..8a78e126 100644 --- a/Sources/Confidence/ConfidenceClient.swift +++ b/Sources/Confidence/ConfidenceClient.swift @@ -7,7 +7,7 @@ protocol ConfidenceClient { protocol ConfidenceResolveClient { // Async - func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult + func resolve(ctx: ConfidenceStruct, withTimeout: Int?) async throws -> ResolvesResult } struct ResolvedValue: Codable, Equatable { diff --git a/Sources/Confidence/RemoteResolveConfidenceClient.swift b/Sources/Confidence/RemoteResolveConfidenceClient.swift index b2f168cd..38fb8e9f 100644 --- a/Sources/Confidence/RemoteResolveConfidenceClient.swift +++ b/Sources/Confidence/RemoteResolveConfidenceClient.swift @@ -24,7 +24,7 @@ class RemoteConfidenceResolveClient: ConfidenceResolveClient { // MARK: Resolver - public func resolve(flags: [String], ctx: ConfidenceStruct) async throws -> ResolvesResult { + public func resolve(flags: [String], ctx: ConfidenceStruct, withTimeout: Int? = nil) async throws -> ResolvesResult { let request = ResolveFlagsRequest( flags: flags.map { "flags/\($0)" }, evaluationContext: TypeMapper.convert(structure: ctx), @@ -34,8 +34,19 @@ class RemoteConfidenceResolveClient: ConfidenceResolveClient { ) do { - let result: HttpClientResult = - try await self.httpClient.post(path: ":resolve", data: request) + let resolveTask = Task { + let resolve: HttpClientResult = + try await self.httpClient.post(path: ":resolve", data: request) + try Task.checkCancellation() + return resolve + } + + _ = Task { + try await Task.sleep(nanoseconds: UInt64(withTimeout ?? 0) * NSEC_PER_SEC) + resolveTask.cancel() + } + + let result = try await resolveTask.value switch result { case .success(let successData): guard successData.response.status == .ok else { @@ -54,8 +65,8 @@ class RemoteConfidenceResolveClient: ConfidenceResolveClient { } } - public func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult { - return try await resolve(flags: [], ctx: ctx) + public func resolve(ctx: ConfidenceStruct, withTimeout: Int? = nil) async throws -> ResolvesResult { + return try await resolve(flags: [], ctx: ctx, withTimeout: withTimeout) } // MARK: Private diff --git a/Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift b/Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift index edf4945a..9033a16e 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift @@ -12,7 +12,7 @@ class ConfidenceProviderTest: XCTestCase { func testErrorFetchOnInit() async throws { class FakeClient: ConfidenceResolveClient { - func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult { + func resolve(ctx: ConfidenceStruct, withTimeout: Int?) async throws -> ResolvesResult { throw ConfidenceError.internalError(message: "test") } } From f358cc9bcd12148a26ca26d51480c494d6c57f9b Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Fri, 12 Jul 2024 16:08:00 +0200 Subject: [PATCH 02/10] put the timeout into confidence options --- Sources/Confidence/Confidence.swift | 29 ++++++---- Sources/Confidence/ConfidenceClient.swift | 2 +- .../Confidence/ConfidenceClientOptions.swift | 5 +- Sources/Confidence/Http/NetworkClient.swift | 6 ++- .../Confidence/RemoteConfidenceClient.swift | 6 ++- .../RemoteResolveConfidenceClient.swift | 54 ++++++++----------- .../ConfidenceProviderTest.swift | 2 +- 7 files changed, 58 insertions(+), 46 deletions(-) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index 41aa7bba..5ea24bb6 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -81,13 +81,13 @@ public class Confidence: ConfidenceEventSender { /** Fetches latest flag evaluations and store them on disk. Regardless of the fetch outcome (success or failure), this - function activates the cache after the fetch. Timeout for this function is configurable. + function activates the cache after the fetch. Activating the cache means that the flag data on disk is loaded into memory, so consumers can access flag values. Fetching is best-effort, so no error is propagated. Errors can still be thrown if something goes wrong access data on disk. */ - public func fetchAndActivate(fetchTimeoutSec: Int = 10) async throws { + public func fetchAndActivate() async throws { do { - try await internalFetch(fetchTimeoutSec: fetchTimeoutSec) + try await internalFetch() } catch { debugLogger?.logMessage( message: "\(error)", @@ -97,9 +97,9 @@ public class Confidence: ConfidenceEventSender { try activate() } - func internalFetch(fetchTimeoutSec: Int? = nil) async throws { + func internalFetch() async throws { let context = getContext() - let resolvedFlags = try await remoteFlagResolver.resolve(ctx: context, withTimeout: fetchTimeoutSec) + let resolvedFlags = try await remoteFlagResolver.resolve(ctx: context) let resolution = FlagResolution( context: context, flags: resolvedFlags.resolvedValues, @@ -116,7 +116,7 @@ public class Confidence: ConfidenceEventSender { public func asyncFetch() { Task { do { - try await internalFetch(fetchTimeoutSec: nil) + try await internalFetch() } catch { debugLogger?.logMessage( message: "\(error )", @@ -272,7 +272,8 @@ public class Confidence: ConfidenceEventSender { storage: storage, context: context, parent: self, - debugLogger: debugLogger) + debugLogger: debugLogger + ) } } @@ -288,6 +289,7 @@ extension Confidence { internal var region: ConfidenceRegion = .global internal var metadata: ConfidenceMetadata? internal var initialContext: ConfidenceStruct = [:] + internal var timeout: Double = 0 // Injectable for testing internal var flagApplier: FlagApplier? @@ -337,6 +339,11 @@ extension Confidence { return self } + public func withTimeout(timeout: Double) -> Builder { + self.timeout = timeout + return self + } + public func build() -> Confidence { var debugLogger: DebugLogger? if loggerLevel != LoggerLevel.NONE { @@ -347,7 +354,8 @@ extension Confidence { } let options = ConfidenceClientOptions( credentials: ConfidenceClientCredentials.clientSecret(secret: clientSecret), - region: region) + region: region, + timeoutIntervalForRequest: timeout) let metadata = ConfidenceMetadata( name: sdkId, version: "0.2.4") // x-release-please-version @@ -355,7 +363,10 @@ extension Confidence { options: options, metadata: metadata ) - let httpClient = NetworkClient(baseUrl: BaseUrlMapper.from(region: options.region)) + let httpClient = NetworkClient( + baseUrl: BaseUrlMapper.from(region: options.region), + timeoutIntervalForRequests: options.timeoutIntervalForRequest + ) let flagApplier = flagApplier ?? FlagApplierWithRetries( httpClient: httpClient, storage: DefaultStorage(filePath: "confidence.flags.apply"), diff --git a/Sources/Confidence/ConfidenceClient.swift b/Sources/Confidence/ConfidenceClient.swift index 8a78e126..45f91428 100644 --- a/Sources/Confidence/ConfidenceClient.swift +++ b/Sources/Confidence/ConfidenceClient.swift @@ -7,7 +7,7 @@ protocol ConfidenceClient { protocol ConfidenceResolveClient { // Async - func resolve(ctx: ConfidenceStruct, withTimeout: Int?) async throws -> ResolvesResult + func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult } struct ResolvedValue: Codable, Equatable { diff --git a/Sources/Confidence/ConfidenceClientOptions.swift b/Sources/Confidence/ConfidenceClientOptions.swift index 7ddb17ee..c6c10a2f 100644 --- a/Sources/Confidence/ConfidenceClientOptions.swift +++ b/Sources/Confidence/ConfidenceClientOptions.swift @@ -4,15 +4,18 @@ struct ConfidenceClientOptions { public var credentials: ConfidenceClientCredentials public var region: ConfidenceRegion public var initializationStrategy: InitializationStrategy + public var timeoutIntervalForRequest: Double public init( credentials: ConfidenceClientCredentials, region: ConfidenceRegion? = nil, - initializationStrategy: InitializationStrategy = .fetchAndActivate + initializationStrategy: InitializationStrategy = .fetchAndActivate, + timeoutIntervalForRequest: Double = 0 ) { self.credentials = credentials self.region = region ?? .global self.initializationStrategy = initializationStrategy + self.timeoutIntervalForRequest = timeoutIntervalForRequest } } diff --git a/Sources/Confidence/Http/NetworkClient.swift b/Sources/Confidence/Http/NetworkClient.swift index b9898a6f..2d8406cb 100644 --- a/Sources/Confidence/Http/NetworkClient.swift +++ b/Sources/Confidence/Http/NetworkClient.swift @@ -5,18 +5,21 @@ final class NetworkClient: HttpClient { private let retry: Retry private let session: URLSession private let baseUrl: String + private var timeoutIntervalForRequests: Double public init( session: URLSession? = nil, baseUrl: String, defaultHeaders: [String: String] = [:], - retry: Retry = .none + retry: Retry = .none, + timeoutIntervalForRequests: Double ) { self.session = session ?? { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = defaultHeaders + configuration.timeoutIntervalForRequest = timeoutIntervalForRequests return URLSession(configuration: configuration) }() @@ -24,6 +27,7 @@ final class NetworkClient: HttpClient { self.headers = defaultHeaders self.retry = retry self.baseUrl = baseUrl + self.timeoutIntervalForRequests = timeoutIntervalForRequests } public func post( diff --git a/Sources/Confidence/RemoteConfidenceClient.swift b/Sources/Confidence/RemoteConfidenceClient.swift index 15bf28c5..adfdf410 100644 --- a/Sources/Confidence/RemoteConfidenceClient.swift +++ b/Sources/Confidence/RemoteConfidenceClient.swift @@ -23,7 +23,11 @@ public class RemoteConfidenceClient: ConfidenceClient { case .usa: self.baseUrl = "https://events.us.confidence.dev/v1/events" } - self.httpClient = NetworkClient(session: session, baseUrl: baseUrl) + self.httpClient = NetworkClient( + session: session, + baseUrl: baseUrl, + timeoutIntervalForRequests: options.timeoutIntervalForRequest + ) self.metadata = metadata self.debugLogger = debugLogger } diff --git a/Sources/Confidence/RemoteResolveConfidenceClient.swift b/Sources/Confidence/RemoteResolveConfidenceClient.swift index 38fb8e9f..c5a6a808 100644 --- a/Sources/Confidence/RemoteResolveConfidenceClient.swift +++ b/Sources/Confidence/RemoteResolveConfidenceClient.swift @@ -19,12 +19,13 @@ class RemoteConfidenceResolveClient: ConfidenceResolveClient { self.metadata = metadata self.httpClient = NetworkClient( session: session, - baseUrl: BaseUrlMapper.from(region: options.region)) + baseUrl: BaseUrlMapper.from(region: options.region), + timeoutIntervalForRequests: options.timeoutIntervalForRequest) } // MARK: Resolver - public func resolve(flags: [String], ctx: ConfidenceStruct, withTimeout: Int? = nil) async throws -> ResolvesResult { + public func resolve(flags: [String], ctx: ConfidenceStruct) async throws -> ResolvesResult { let request = ResolveFlagsRequest( flags: flags.map { "flags/\($0)" }, evaluationContext: TypeMapper.convert(structure: ctx), @@ -34,39 +35,28 @@ class RemoteConfidenceResolveClient: ConfidenceResolveClient { ) do { - let resolveTask = Task { - let resolve: HttpClientResult = - try await self.httpClient.post(path: ":resolve", data: request) - try Task.checkCancellation() - return resolve - } - - _ = Task { - try await Task.sleep(nanoseconds: UInt64(withTimeout ?? 0) * NSEC_PER_SEC) - resolveTask.cancel() - } - - let result = try await resolveTask.value - switch result { - case .success(let successData): - guard successData.response.status == .ok else { - throw successData.response.mapStatusToError(error: successData.decodedError) + let result: HttpClientResult = + try await self.httpClient.post(path: ":resolve", data: request) + switch result { + case .success(let successData): + guard successData.response.status == .ok else { + throw successData.response.mapStatusToError(error: successData.decodedError) + } + guard let response = successData.decodedData else { + throw ConfidenceError.parseError(message: "Unable to parse request response") + } + let resolvedValues = try response.resolvedFlags.map { resolvedFlag in + try convert(resolvedFlag: resolvedFlag) + } + return ResolvesResult(resolvedValues: resolvedValues, resolveToken: response.resolveToken) + case .failure(let errorData): + throw handleError(error: errorData) + } } - guard let response = successData.decodedData else { - throw ConfidenceError.parseError(message: "Unable to parse request response") - } - let resolvedValues = try response.resolvedFlags.map { resolvedFlag in - try convert(resolvedFlag: resolvedFlag) - } - return ResolvesResult(resolvedValues: resolvedValues, resolveToken: response.resolveToken) - case .failure(let errorData): - throw handleError(error: errorData) - } - } } - public func resolve(ctx: ConfidenceStruct, withTimeout: Int? = nil) async throws -> ResolvesResult { - return try await resolve(flags: [], ctx: ctx, withTimeout: withTimeout) + public func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult { + return try await resolve(flags: [], ctx: ctx) } // MARK: Private diff --git a/Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift b/Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift index 9033a16e..edf4945a 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift @@ -12,7 +12,7 @@ class ConfidenceProviderTest: XCTestCase { func testErrorFetchOnInit() async throws { class FakeClient: ConfidenceResolveClient { - func resolve(ctx: ConfidenceStruct, withTimeout: Int?) async throws -> ResolvesResult { + func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult { throw ConfidenceError.internalError(message: "test") } } From 9870a6d8901f9639cbbebf411f7dfcbf2f564611 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Mon, 15 Jul 2024 10:44:01 +0200 Subject: [PATCH 03/10] add test --- Sources/Confidence/Confidence.swift | 6 +++- Sources/Confidence/Http/NetworkClient.swift | 1 - Tests/ConfidenceTests/ConfidenceTest.swift | 39 +++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index 5ea24bb6..0647b186 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -289,7 +289,7 @@ extension Confidence { internal var region: ConfidenceRegion = .global internal var metadata: ConfidenceMetadata? internal var initialContext: ConfidenceStruct = [:] - internal var timeout: Double = 0 + internal var timeout: Double = 10 // Injectable for testing internal var flagApplier: FlagApplier? @@ -339,6 +339,10 @@ extension Confidence { return self } + /** + Sets the timeout for the network requests to the Confidence backend. + The default is 10 seconds. + */ public func withTimeout(timeout: Double) -> Builder { self.timeout = timeout return self diff --git a/Sources/Confidence/Http/NetworkClient.swift b/Sources/Confidence/Http/NetworkClient.swift index 2d8406cb..0ce563e7 100644 --- a/Sources/Confidence/Http/NetworkClient.swift +++ b/Sources/Confidence/Http/NetworkClient.swift @@ -19,7 +19,6 @@ final class NetworkClient: HttpClient { ?? { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = defaultHeaders - configuration.timeoutIntervalForRequest = timeoutIntervalForRequests return URLSession(configuration: configuration) }() diff --git a/Tests/ConfidenceTests/ConfidenceTest.swift b/Tests/ConfidenceTests/ConfidenceTest.swift index 25543438..41112266 100644 --- a/Tests/ConfidenceTests/ConfidenceTest.swift +++ b/Tests/ConfidenceTests/ConfidenceTest.swift @@ -581,6 +581,45 @@ class ConfidenceTest: XCTestCase { XCTAssertEqual(error as? ConfidenceError, ConfidenceError.invalidContextInMessage) } } + + func testRequestTimedOut() async throws { + class FakeClient: ConfidenceResolveClient { + var resolvedValues: [ResolvedValue] = [] + func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult { + try await Task.sleep(nanoseconds: 5000000000) + return .init(resolvedValues: resolvedValues, resolveToken: "token") + } + } + + let client = FakeClient() + client.resolvedValues = [ + ResolvedValue( + variant: "default", + value: .init(structure: ["size": .init(integer: 3)]), + flag: "flag", + resolveReason: .match) + ] + + let confidence = Confidence.Builder(clientSecret: "test") + .withContext(initialContext: ["targeting_key": .init(string: "user2")]) + .withFlagResolverClient(flagResolver: client) + .withFlagApplier(flagApplier: flagApplier) + .withTimeout(timeout: 1) + .build() + + try await confidence.fetchAndActivate() + let evaluation = try confidence.getEvaluation( + key: "flag.size", + defaultValue: 0) + + XCTAssertEqual(evaluation.value, 3) + XCTAssertNil(evaluation.errorCode) + XCTAssertNil(evaluation.errorMessage) + XCTAssertEqual(evaluation.reason, .match) + XCTAssertEqual(evaluation.variant, "default") + await fulfillment(of: [flagApplier.applyExpectation], timeout: 1) + XCTAssertEqual(flagApplier.applyCallCount, 1) + } } final class DispatchQueueFake: DispatchQueueType { From aba3ac5705374ce7b2eb44ac0dd2b667059b0bfe Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Mon, 15 Jul 2024 10:50:20 +0200 Subject: [PATCH 04/10] lint --- .../RemoteResolveConfidenceClient.swift | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Sources/Confidence/RemoteResolveConfidenceClient.swift b/Sources/Confidence/RemoteResolveConfidenceClient.swift index c5a6a808..479dead8 100644 --- a/Sources/Confidence/RemoteResolveConfidenceClient.swift +++ b/Sources/Confidence/RemoteResolveConfidenceClient.swift @@ -35,24 +35,24 @@ class RemoteConfidenceResolveClient: ConfidenceResolveClient { ) do { - let result: HttpClientResult = - try await self.httpClient.post(path: ":resolve", data: request) - switch result { - case .success(let successData): - guard successData.response.status == .ok else { - throw successData.response.mapStatusToError(error: successData.decodedError) - } - guard let response = successData.decodedData else { - throw ConfidenceError.parseError(message: "Unable to parse request response") - } - let resolvedValues = try response.resolvedFlags.map { resolvedFlag in - try convert(resolvedFlag: resolvedFlag) - } - return ResolvesResult(resolvedValues: resolvedValues, resolveToken: response.resolveToken) - case .failure(let errorData): - throw handleError(error: errorData) - } + let result: HttpClientResult = + try await self.httpClient.post(path: ":resolve", data: request) + switch result { + case .success(let successData): + guard successData.response.status == .ok else { + throw successData.response.mapStatusToError(error: successData.decodedError) } + guard let response = successData.decodedData else { + throw ConfidenceError.parseError(message: "Unable to parse request response") + } + let resolvedValues = try response.resolvedFlags.map { resolvedFlag in + try convert(resolvedFlag: resolvedFlag) + } + return ResolvesResult(resolvedValues: resolvedValues, resolveToken: response.resolveToken) + case .failure(let errorData): + throw handleError(error: errorData) + } + } } public func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult { From 040d741169122cf110f389c69c8a2126e8eec31c Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Mon, 15 Jul 2024 11:02:05 +0200 Subject: [PATCH 05/10] update public api --- scripts/Confidence_raw_api.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 scripts/Confidence_raw_api.json diff --git a/scripts/Confidence_raw_api.json b/scripts/Confidence_raw_api.json new file mode 100644 index 00000000..e69de29b From 6675ec30f65a735b2e58e0537da2eac7159d00f1 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Mon, 15 Jul 2024 11:02:50 +0200 Subject: [PATCH 06/10] lint --- Sources/Confidence/Confidence.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index 34c839dc..29a83436 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -358,10 +358,9 @@ extension Confidence { return self } - /** - Sets the timeout for the network requests to the Confidence backend. - The default is 10 seconds. - */ + /** + Set the timeout for the network request, defaulting to 10 seconds. + */ public func withTimeout(timeout: Double) -> Builder { self.timeout = timeout return self From cf003dde6773f6210e650454daa4e9475db86be8 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Tue, 16 Jul 2024 10:48:07 +0200 Subject: [PATCH 07/10] remove test, generate new api --- Tests/ConfidenceTests/ConfidenceTest.swift | 39 ---------------------- api/Confidence_public_api.json | 4 +++ scripts/Confidence_raw_api.json | 0 3 files changed, 4 insertions(+), 39 deletions(-) delete mode 100644 scripts/Confidence_raw_api.json diff --git a/Tests/ConfidenceTests/ConfidenceTest.swift b/Tests/ConfidenceTests/ConfidenceTest.swift index bf132e9f..e26d7d3a 100644 --- a/Tests/ConfidenceTests/ConfidenceTest.swift +++ b/Tests/ConfidenceTests/ConfidenceTest.swift @@ -617,45 +617,6 @@ class ConfidenceTest: XCTestCase { XCTAssertEqual(error as? ConfidenceError, ConfidenceError.invalidContextInMessage) } } - - func testRequestTimedOut() async throws { - class FakeClient: ConfidenceResolveClient { - var resolvedValues: [ResolvedValue] = [] - func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult { - try await Task.sleep(nanoseconds: 5000000000) - return .init(resolvedValues: resolvedValues, resolveToken: "token") - } - } - - let client = FakeClient() - client.resolvedValues = [ - ResolvedValue( - variant: "default", - value: .init(structure: ["size": .init(integer: 3)]), - flag: "flag", - resolveReason: .match) - ] - - let confidence = Confidence.Builder(clientSecret: "test") - .withContext(initialContext: ["targeting_key": .init(string: "user2")]) - .withFlagResolverClient(flagResolver: client) - .withFlagApplier(flagApplier: flagApplier) - .withTimeout(timeout: 1) - .build() - - try await confidence.fetchAndActivate() - let evaluation = try confidence.getEvaluation( - key: "flag.size", - defaultValue: 0) - - XCTAssertEqual(evaluation.value, 3) - XCTAssertNil(evaluation.errorCode) - XCTAssertNil(evaluation.errorMessage) - XCTAssertEqual(evaluation.reason, .match) - XCTAssertEqual(evaluation.variant, "default") - await fulfillment(of: [flagApplier.applyExpectation], timeout: 1) - XCTAssertEqual(flagApplier.applyCallCount, 1) - } } final class DispatchQueueFake: DispatchQueueType { diff --git a/api/Confidence_public_api.json b/api/Confidence_public_api.json index 98200bc9..be9aaed5 100644 --- a/api/Confidence_public_api.json +++ b/api/Confidence_public_api.json @@ -79,6 +79,10 @@ "name": "withRegion(region:)", "declaration": "public func withRegion(region: ConfidenceRegion) -> Builder" }, + { + "name": "withTimeout(timeout:)", + "declaration": "public func withTimeout(timeout: Double) -> Builder" + }, { "name": "build()", "declaration": "public func build() -> Confidence" diff --git a/scripts/Confidence_raw_api.json b/scripts/Confidence_raw_api.json deleted file mode 100644 index e69de29b..00000000 From b1da9397356a6b22f4b4e20a54792328b0ac086a Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Tue, 16 Jul 2024 11:16:27 +0200 Subject: [PATCH 08/10] fix build --- ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift index 073efd95..c9822026 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift @@ -20,6 +20,7 @@ struct ConfidenceDemoApp: App { WindowGroup { let secret = ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? "" let confidence = Confidence.Builder(clientSecret: secret, loggerLevel: .TRACE) + .withTimeout(timeout: 0.001) .withContext(initialContext: ["targeting_key": ConfidenceValue(string: UUID.init().uuidString)]) .build() From 850bce5d5abdada5811a0cff56d0c2732f5670ec Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Tue, 16 Jul 2024 11:41:33 +0200 Subject: [PATCH 09/10] cleanup --- ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift | 1 - Sources/Confidence/ConfidenceClientOptions.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift index c9822026..073efd95 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift @@ -20,7 +20,6 @@ struct ConfidenceDemoApp: App { WindowGroup { let secret = ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? "" let confidence = Confidence.Builder(clientSecret: secret, loggerLevel: .TRACE) - .withTimeout(timeout: 0.001) .withContext(initialContext: ["targeting_key": ConfidenceValue(string: UUID.init().uuidString)]) .build() diff --git a/Sources/Confidence/ConfidenceClientOptions.swift b/Sources/Confidence/ConfidenceClientOptions.swift index c6c10a2f..fe64d3bb 100644 --- a/Sources/Confidence/ConfidenceClientOptions.swift +++ b/Sources/Confidence/ConfidenceClientOptions.swift @@ -10,7 +10,7 @@ struct ConfidenceClientOptions { credentials: ConfidenceClientCredentials, region: ConfidenceRegion? = nil, initializationStrategy: InitializationStrategy = .fetchAndActivate, - timeoutIntervalForRequest: Double = 0 + timeoutIntervalForRequest: Double ) { self.credentials = credentials self.region = region ?? .global From acf30beefb682362735cea3237cc1bd36ccdf923 Mon Sep 17 00:00:00 2001 From: Nicky Bondarenko Date: Tue, 16 Jul 2024 11:48:15 +0200 Subject: [PATCH 10/10] fix tests --- .../ConfidenceContextTests.swift | 20 +++++++++---------- .../EventSenderEngineTest.swift | 10 ++++++++-- .../FlagApplierWithRetriesTest.swift | 5 ++++- .../RemoteConfidenceClientTests.swift | 8 ++++---- .../RemoteResolveConfidenceClientTest.swift | 2 +- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Tests/ConfidenceTests/ConfidenceContextTests.swift b/Tests/ConfidenceTests/ConfidenceContextTests.swift index c41447cc..155c952d 100644 --- a/Tests/ConfidenceTests/ConfidenceContextTests.swift +++ b/Tests/ConfidenceTests/ConfidenceContextTests.swift @@ -6,7 +6,7 @@ final class ConfidenceContextTests: XCTestCase { func testWithContext() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -33,7 +33,7 @@ final class ConfidenceContextTests: XCTestCase { func testWithContextUpdateParent() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -65,7 +65,7 @@ final class ConfidenceContextTests: XCTestCase { func testUpdateLocalContext() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -92,7 +92,7 @@ final class ConfidenceContextTests: XCTestCase { func testUpdateLocalContextWithoutOverride() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -123,7 +123,7 @@ final class ConfidenceContextTests: XCTestCase { func testUpdateParentContextWithOverride() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -154,7 +154,7 @@ final class ConfidenceContextTests: XCTestCase { func testRemoveContextEntry() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -179,7 +179,7 @@ final class ConfidenceContextTests: XCTestCase { func testRemoveContextEntryFromParent() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -207,7 +207,7 @@ final class ConfidenceContextTests: XCTestCase { func testRemoveContextEntryFromParentAndChild() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -238,7 +238,7 @@ final class ConfidenceContextTests: XCTestCase { func testRemoveContextEntryFromParentAndChildThenUpdate() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -271,7 +271,7 @@ final class ConfidenceContextTests: XCTestCase { func testVisitorId() { let client = RemoteConfidenceResolveClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) diff --git a/Tests/ConfidenceTests/EventSenderEngineTest.swift b/Tests/ConfidenceTests/EventSenderEngineTest.swift index 7db33b67..6be4d436 100644 --- a/Tests/ConfidenceTests/EventSenderEngineTest.swift +++ b/Tests/ConfidenceTests/EventSenderEngineTest.swift @@ -110,7 +110,10 @@ final class EventSenderEngineTest: XCTestCase { func testRemoveEventsFromStorageOnBadRequest() throws { MockedClientURLProtocol.mockedOperation = .badRequest let badRequestUploader = RemoteConfidenceClient( - options: ConfidenceClientOptions(credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + options: ConfidenceClientOptions( + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), + timeoutIntervalForRequest: 10 + ), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -135,7 +138,10 @@ final class EventSenderEngineTest: XCTestCase { func testKeepEventsInStorageForRetry() throws { MockedClientURLProtocol.mockedOperation = .needRetryLater let retryLaterUploader = RemoteConfidenceClient( - options: ConfidenceClientOptions(credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + options: ConfidenceClientOptions( + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), + timeoutIntervalForRequest: 10 + ), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) diff --git a/Tests/ConfidenceTests/FlagApplierWithRetriesTest.swift b/Tests/ConfidenceTests/FlagApplierWithRetriesTest.swift index 5f781e5f..6ff64d34 100644 --- a/Tests/ConfidenceTests/FlagApplierWithRetriesTest.swift +++ b/Tests/ConfidenceTests/FlagApplierWithRetriesTest.swift @@ -8,7 +8,10 @@ import XCTest @available(macOS 13.0, iOS 16.0, *) class FlagApplierWithRetriesTest: XCTestCase { - private let options = ConfidenceClientOptions(credentials: .clientSecret(secret: "test")) + private let options = ConfidenceClientOptions( + credentials: .clientSecret(secret: "test"), + timeoutIntervalForRequest: 10 + ) private var storage = StorageMock() private var httpClient = HttpClientMock() private let metadata = ConfidenceMetadata(name: "test-provider-name", version: "0.0.0.") diff --git a/Tests/ConfidenceTests/RemoteConfidenceClientTests.swift b/Tests/ConfidenceTests/RemoteConfidenceClientTests.swift index 2d62d03b..bac83b1e 100644 --- a/Tests/ConfidenceTests/RemoteConfidenceClientTests.swift +++ b/Tests/ConfidenceTests/RemoteConfidenceClientTests.swift @@ -12,7 +12,7 @@ class RemoteConfidenceClientTest: XCTestCase { func testUploadDoesntThrow() async throws { let client = RemoteConfidenceClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -29,7 +29,7 @@ class RemoteConfidenceClientTest: XCTestCase { func testUploadEmptyEventsDoesntThrow() async throws { let client = RemoteConfidenceClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -41,7 +41,7 @@ class RemoteConfidenceClientTest: XCTestCase { MockedClientURLProtocol.mockedOperation = .firstEventFails let client = RemoteConfidenceClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) @@ -59,7 +59,7 @@ class RemoteConfidenceClientTest: XCTestCase { MockedClientURLProtocol.mockedOperation = .malformedResponse let client = RemoteConfidenceClient( options: ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: "")), + credentials: ConfidenceClientCredentials.clientSecret(secret: ""), timeoutIntervalForRequest: 10), session: MockedClientURLProtocol.mockedSession(), metadata: ConfidenceMetadata(name: "", version: "")) diff --git a/Tests/ConfidenceTests/RemoteResolveConfidenceClientTest.swift b/Tests/ConfidenceTests/RemoteResolveConfidenceClientTest.swift index 16519b27..c2d310ae 100644 --- a/Tests/ConfidenceTests/RemoteResolveConfidenceClientTest.swift +++ b/Tests/ConfidenceTests/RemoteResolveConfidenceClientTest.swift @@ -26,7 +26,7 @@ class RemoteResolveConfidenceClientTest: XCTestCase { let session = MockedResolveClientURLProtocol.mockedSession(flags: flags) let client = RemoteConfidenceResolveClient( - options: .init(credentials: .clientSecret(secret: "test")), + options: .init(credentials: .clientSecret(secret: "test"), timeoutIntervalForRequest: 10), session: session, applyOnResolve: true, metadata: ConfidenceMetadata(name: "", version: "")