From 9efa8f47088940a56d998d0e464791fea457fb1a Mon Sep 17 00:00:00 2001 From: Polat Olu Date: Thu, 9 Oct 2025 18:43:54 +0100 Subject: [PATCH 1/4] Initial work, tests failing still --- Example/NetworkConfigExample.swift | 129 +++++++++ FlagsmithClient/Classes/Flagsmith.swift | 40 +++ .../Classes/Internal/APIManager.swift | 70 ++++- .../Classes/Internal/SSEManager.swift | 56 +++- .../Tests/NetworkConfigIntegrationTests.swift | 260 ++++++++++++++++++ .../Tests/NetworkConfigTests.swift | 151 ++++++++++ 6 files changed, 695 insertions(+), 11 deletions(-) create mode 100644 Example/NetworkConfigExample.swift create mode 100644 FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift create mode 100644 FlagsmithClient/Tests/NetworkConfigTests.swift diff --git a/Example/NetworkConfigExample.swift b/Example/NetworkConfigExample.swift new file mode 100644 index 0000000..125ab53 --- /dev/null +++ b/Example/NetworkConfigExample.swift @@ -0,0 +1,129 @@ + +import Foundation +import FlagsmithClient + +class NetworkConfigExample { + + func demonstrateNetworkConfiguration() { + // Get the shared Flagsmith instance + let flagsmith = Flagsmith.shared + + // Set your API key + flagsmith.apiKey = "your-api-key-here" + + // Configure network settings + configureNetworkSettings(flagsmith) + + // Now all network requests will use these settings + fetchFeatureFlags(flagsmith) + } + + private func configureNetworkSettings(_ flagsmith: Flagsmith) { + // Set a custom request timeout (customer requested 1 second instead of 60) + flagsmith.networkConfig.requestTimeout = 1.0 + + // Set resource timeout (total time for the entire request) + flagsmith.networkConfig.resourceTimeout = 30.0 + + // Configure connectivity settings + flagsmith.networkConfig.waitsForConnectivity = true + flagsmith.networkConfig.allowsCellularAccess = true + + // Configure HTTP settings + flagsmith.networkConfig.httpMaximumConnectionsPerHost = 4 + flagsmith.networkConfig.httpShouldUsePipelining = true + flagsmith.networkConfig.httpShouldSetCookies = true + + // Add custom headers + flagsmith.networkConfig.httpAdditionalHeaders = [ + "X-Custom-Header": "Custom-Value", + "User-Agent": "MyApp/1.0" + ] + } + + private func fetchFeatureFlags(_ flagsmith: Flagsmith) { + flagsmith.getFeatureFlags { result in + switch result { + case .success(let flags): + print("Successfully fetched \(flags.count) feature flags") + for flag in flags { + print("Flag: \(flag.feature.name) - Enabled: \(flag.enabled)") + } + case .failure(let error): + print("Failed to fetch feature flags: \(error)") + } + } + } + + func demonstrateTimeoutBehavior() { + let flagsmith = Flagsmith.shared + flagsmith.apiKey = "your-api-key-here" + + // Set a very short timeout to demonstrate timeout behavior + flagsmith.networkConfig.requestTimeout = 0.1 // 100ms + + print("Testing with 100ms timeout...") + + flagsmith.getFeatureFlags { result in + switch result { + case .success(let flags): + print("Unexpected success with short timeout: \(flags.count) flags") + case .failure(let error): + if let urlError = error as? URLError, urlError.code == .timedOut { + print("Expected timeout error occurred") + } else { + print("Unexpected error: \(error)") + } + } + } + } + + func demonstrateCustomHeaders() { + let flagsmith = Flagsmith.shared + flagsmith.apiKey = "your-api-key-here" + + // Add custom headers that will be sent with every request + flagsmith.networkConfig.httpAdditionalHeaders = [ + "X-Client-Version": "1.0.0", + "X-Platform": "iOS", + "X-Environment": "Production" + ] + + print("Configured custom headers for all requests") + + // These headers will now be included in all API requests + flagsmith.getFeatureFlags { result in + // Handle result... + } + } + + func demonstrateCellularAccessControl() { + let flagsmith = Flagsmith.shared + flagsmith.apiKey = "your-api-key-here" + + // Disable cellular access (WiFi only) + flagsmith.networkConfig.allowsCellularAccess = false + + print("Configured to use WiFi only (no cellular)") + + // This will only work on WiFi connections + flagsmith.getFeatureFlags { result in + // Handle result... + } + } + + func demonstrateConnectionLimits() { + let flagsmith = Flagsmith.shared + flagsmith.apiKey = "your-api-key-here" + + // Limit concurrent connections to the same host + flagsmith.networkConfig.httpMaximumConnectionsPerHost = 2 + + print("Limited to 2 concurrent connections per host") + + // This helps control resource usage + flagsmith.getFeatureFlags { result in + // Handle result... + } + } +} diff --git a/FlagsmithClient/Classes/Flagsmith.swift b/FlagsmithClient/Classes/Flagsmith.swift index e8c0e88..d98865d 100644 --- a/FlagsmithClient/Classes/Flagsmith.swift +++ b/FlagsmithClient/Classes/Flagsmith.swift @@ -111,6 +111,19 @@ public final class Flagsmith: @unchecked Sendable { } } + /// Configuration class for network settings + private var _networkConfig: NetworkConfig = .init() + public var networkConfig: NetworkConfig { + get { + apiManager.propertiesSerialAccessQueue.sync { _networkConfig } + } + set { + apiManager.propertiesSerialAccessQueue.sync { + _networkConfig = newValue + } + } + } + private init() { apiManager = APIManager() sseManager = SSEManager() @@ -408,3 +421,30 @@ public final class CacheConfig { /// Skip API if there is a cache available public var skipAPI: Bool = false } + +/// Configuration class for network settings +public final class NetworkConfig { + /// The timeout interval for URL requests, in seconds. Default is 60.0 seconds. + public var requestTimeout: TimeInterval = 60.0 + + /// The timeout interval for the entire resource request, in seconds. Default is 7 days (604800 seconds). + public var resourceTimeout: TimeInterval = 604800.0 + + /// A Boolean value that determines whether the session should wait for connectivity to become available. Default is true. + public var waitsForConnectivity: Bool = true + + /// A Boolean value that determines whether the session should use cellular access. Default is true. + public var allowsCellularAccess: Bool = true + + /// The maximum number of simultaneous connections to make to a given host. Default is 6. + public var httpMaximumConnectionsPerHost: Int = 6 + + /// Additional HTTP headers to be sent with requests. Default is empty. + public var httpAdditionalHeaders: [String: String] = [:] + + /// A Boolean value that determines whether the session should use HTTP pipelining. Default is true. + public var httpShouldUsePipelining: Bool = true + + /// A Boolean value that determines whether the session should automatically set the "Accept-Encoding" header. Default is true. + public var httpShouldSetCookies: Bool = true +} diff --git a/FlagsmithClient/Classes/Internal/APIManager.swift b/FlagsmithClient/Classes/Internal/APIManager.swift index 38622fd..9db9710 100644 --- a/FlagsmithClient/Classes/Internal/APIManager.swift +++ b/FlagsmithClient/Classes/Internal/APIManager.swift @@ -13,7 +13,7 @@ import Foundation /// Handles interaction with a **Flagsmith** api. final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { private var _session: URLSession! - private var session: URLSession { + internal var session: URLSession { get { propertiesSerialAccessQueue.sync { _session } } @@ -70,10 +70,61 @@ final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { override init() { super.init() + let configuration = createURLSessionConfiguration() + session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main) + } + + /// Creates a URLSessionConfiguration with current network and cache settings + private func createURLSessionConfiguration() -> URLSessionConfiguration { let configuration = URLSessionConfiguration.default - // Set initial cache configuration - this will be updated when cache settings change + + // Apply network configuration - use default values during initialization + configuration.timeoutIntervalForRequest = 60.0 + configuration.timeoutIntervalForResource = 604800.0 + configuration.waitsForConnectivity = true + configuration.allowsCellularAccess = true + configuration.httpMaximumConnectionsPerHost = 6 + configuration.httpAdditionalHeaders = [:] + configuration.httpShouldUsePipelining = true + configuration.httpShouldSetCookies = true + + // Apply cache configuration configuration.urlCache = URLCache.shared - session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main) + + return configuration + } + + /// Creates a URLSessionConfiguration with specific network and cache settings + private func createURLSessionConfiguration(networkConfig: NetworkConfig, cacheConfig: CacheConfig) -> URLSessionConfiguration { + let configuration = URLSessionConfiguration.default + + // Apply network configuration + configuration.timeoutIntervalForRequest = networkConfig.requestTimeout + configuration.timeoutIntervalForResource = networkConfig.resourceTimeout + configuration.waitsForConnectivity = networkConfig.waitsForConnectivity + configuration.allowsCellularAccess = networkConfig.allowsCellularAccess + configuration.httpMaximumConnectionsPerHost = networkConfig.httpMaximumConnectionsPerHost + configuration.httpAdditionalHeaders = networkConfig.httpAdditionalHeaders + configuration.httpShouldUsePipelining = networkConfig.httpShouldUsePipelining + configuration.httpShouldSetCookies = networkConfig.httpShouldSetCookies + + // Apply cache configuration + configuration.urlCache = cacheConfig.cache + + return configuration + } + + /// Helper function to compare HTTP headers dictionaries + private func areHeadersEqual(_ headers1: [AnyHashable: Any]?, _ headers2: [AnyHashable: Any]?) -> Bool { + guard let h1 = headers1, let h2 = headers2 else { + return headers1 == nil && headers2 == nil + } + + // Convert to [String: String] for comparison + let dict1 = h1.compactMapValues { $0 as? String } + let dict2 = h2.compactMapValues { $0 as? String } + + return dict1 == dict2 } func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) { @@ -153,12 +204,13 @@ final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { // we must use the delegate form here, not the completion handler, to be able to modify the cache serialAccessQueue.sync { - // Update session cache configuration if it has changed (must be done inside the serial queue) - if session.configuration.urlCache !== Flagsmith.shared.cacheConfig.cache { - let configuration = URLSessionConfiguration.default - configuration.urlCache = Flagsmith.shared.cacheConfig.cache - session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main) - } + // Always recreate session with current network and cache configuration + // This ensures that any changes to network config are applied immediately + let networkConfig = Flagsmith.shared.networkConfig + let cacheConfig = Flagsmith.shared.cacheConfig + + let newConfig = createURLSessionConfiguration(networkConfig: networkConfig, cacheConfig: cacheConfig) + session = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main) let task = session.dataTask(with: request) tasksToCompletionHandlers[task.taskIdentifier] = completion diff --git a/FlagsmithClient/Classes/Internal/SSEManager.swift b/FlagsmithClient/Classes/Internal/SSEManager.swift index b760aad..4a39086 100644 --- a/FlagsmithClient/Classes/Internal/SSEManager.swift +++ b/FlagsmithClient/Classes/Internal/SSEManager.swift @@ -15,7 +15,7 @@ import Foundation /// and handles reconnection logic with a backoff strategy. final class SSEManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { private var _session: URLSession! - private var session: URLSession { + internal var session: URLSession { get { propertiesSerialAccessQueue.sync { _session } } @@ -75,9 +75,56 @@ final class SSEManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { override init() { super.init() - let configuration = URLSessionConfiguration.default + let configuration = createURLSessionConfiguration() session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main) } + + /// Creates a URLSessionConfiguration with current network settings + private func createURLSessionConfiguration() -> URLSessionConfiguration { + let configuration = URLSessionConfiguration.default + + // Apply network configuration - use default values during initialization + configuration.timeoutIntervalForRequest = 60.0 + configuration.timeoutIntervalForResource = 604800.0 + configuration.waitsForConnectivity = true + configuration.allowsCellularAccess = true + configuration.httpMaximumConnectionsPerHost = 6 + configuration.httpAdditionalHeaders = [:] + configuration.httpShouldUsePipelining = true + configuration.httpShouldSetCookies = true + + return configuration + } + + /// Creates a URLSessionConfiguration with specific network settings + private func createURLSessionConfiguration(networkConfig: NetworkConfig) -> URLSessionConfiguration { + let configuration = URLSessionConfiguration.default + + // Apply network configuration + configuration.timeoutIntervalForRequest = networkConfig.requestTimeout + configuration.timeoutIntervalForResource = networkConfig.resourceTimeout + configuration.waitsForConnectivity = networkConfig.waitsForConnectivity + configuration.allowsCellularAccess = networkConfig.allowsCellularAccess + configuration.httpMaximumConnectionsPerHost = networkConfig.httpMaximumConnectionsPerHost + configuration.httpAdditionalHeaders = networkConfig.httpAdditionalHeaders + configuration.httpShouldUsePipelining = networkConfig.httpShouldUsePipelining + configuration.httpShouldSetCookies = networkConfig.httpShouldSetCookies + + return configuration + } + + /// Helper function to compare HTTP headers dictionaries + private func areHeadersEqual(_ headers1: [AnyHashable: Any]?, _ headers2: [AnyHashable: Any]?) -> Bool { + guard let h1 = headers1, let h2 = headers2 else { + return headers1 == nil && headers2 == nil + } + + // Convert to [String: String] for comparison + let dict1 = h1.compactMapValues { $0 as? String } + let dict2 = h2.compactMapValues { $0 as? String } + + return dict1 == dict2 + } // Helper function to process SSE data internal func processSSEData(_ data: String) { @@ -158,6 +205,11 @@ final class SSEManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { request.setValue("no-cache", forHTTPHeaderField: "Cache-Control") request.setValue("keep-alive", forHTTPHeaderField: "Connection") + // Always recreate session with current network configuration + // This ensures that any changes to network config are applied immediately + let newConfig = createURLSessionConfiguration(networkConfig: Flagsmith.shared.networkConfig) + session = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main) + completionHandler = completion dataTask = session.dataTask(with: request) dataTask?.resume() diff --git a/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift b/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift new file mode 100644 index 0000000..21b109e --- /dev/null +++ b/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift @@ -0,0 +1,260 @@ + +@testable import FlagsmithClient +import XCTest + +final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { + + func testNetworkConfigValues() { + let flagsmith = Flagsmith.shared + + // Set custom network configuration + flagsmith.networkConfig.requestTimeout = 1.0 + flagsmith.networkConfig.resourceTimeout = 5.0 + flagsmith.networkConfig.allowsCellularAccess = false + flagsmith.networkConfig.httpMaximumConnectionsPerHost = 2 + flagsmith.networkConfig.httpAdditionalHeaders = ["Test-Header": "Test-Value"] + flagsmith.networkConfig.httpShouldUsePipelining = false + flagsmith.networkConfig.httpShouldSetCookies = false + + // Verify the configuration is set correctly + XCTAssertEqual(flagsmith.networkConfig.requestTimeout, 1.0) + XCTAssertEqual(flagsmith.networkConfig.resourceTimeout, 5.0) + XCTAssertFalse(flagsmith.networkConfig.allowsCellularAccess) + XCTAssertEqual(flagsmith.networkConfig.httpMaximumConnectionsPerHost, 2) + XCTAssertEqual(flagsmith.networkConfig.httpAdditionalHeaders["Test-Header"], "Test-Value") + XCTAssertFalse(flagsmith.networkConfig.httpShouldUsePipelining) + XCTAssertFalse(flagsmith.networkConfig.httpShouldSetCookies) + } + + func testURLSessionConfigurationCreation() { + let flagsmith = Flagsmith.shared + + // Set custom network configuration + flagsmith.networkConfig.requestTimeout = 1.0 + flagsmith.networkConfig.resourceTimeout = 5.0 + flagsmith.networkConfig.allowsCellularAccess = false + flagsmith.networkConfig.httpMaximumConnectionsPerHost = 2 + flagsmith.networkConfig.httpAdditionalHeaders = ["Test-Header": "Test-Value"] + flagsmith.networkConfig.httpShouldUsePipelining = false + flagsmith.networkConfig.httpShouldSetCookies = false + + // Create a URLSessionConfiguration directly + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = flagsmith.networkConfig.requestTimeout + config.timeoutIntervalForResource = flagsmith.networkConfig.resourceTimeout + config.waitsForConnectivity = flagsmith.networkConfig.waitsForConnectivity + config.allowsCellularAccess = flagsmith.networkConfig.allowsCellularAccess + config.httpMaximumConnectionsPerHost = flagsmith.networkConfig.httpMaximumConnectionsPerHost + config.httpAdditionalHeaders = flagsmith.networkConfig.httpAdditionalHeaders + config.httpShouldUsePipelining = flagsmith.networkConfig.httpShouldUsePipelining + config.httpShouldSetCookies = flagsmith.networkConfig.httpShouldSetCookies + + // Verify the configuration is applied correctly + XCTAssertEqual(config.timeoutIntervalForRequest, 1.0, "Request timeout should be applied") + XCTAssertEqual(config.timeoutIntervalForResource, 5.0, "Resource timeout should be applied") + XCTAssertFalse(config.allowsCellularAccess, "Cellular access should be disabled") + XCTAssertEqual(config.httpMaximumConnectionsPerHost, 2, "Max connections per host should be applied") + XCTAssertEqual(config.httpAdditionalHeaders?["Test-Header"] as? String, "Test-Value", "Additional headers should be applied") + XCTAssertFalse(config.httpShouldUsePipelining, "HTTP pipelining should be disabled") + XCTAssertFalse(config.httpShouldSetCookies, "HTTP cookies should be disabled") + } + + func testAPIManagerUsesNetworkConfig() { + let flagsmith = Flagsmith.shared + + // Set custom network configuration + flagsmith.networkConfig.requestTimeout = 1.0 + flagsmith.networkConfig.resourceTimeout = 5.0 + flagsmith.networkConfig.allowsCellularAccess = false + flagsmith.networkConfig.httpMaximumConnectionsPerHost = 2 + flagsmith.networkConfig.httpAdditionalHeaders = ["Test-Header": "Test-Value"] + flagsmith.networkConfig.httpShouldUsePipelining = false + flagsmith.networkConfig.httpShouldSetCookies = false + + // Create a new APIManager to test the configuration + let apiManager = APIManager() + + // Get initial session for comparison + let initialSession = apiManager.session + + // Trigger a request to apply the network configuration + flagsmith.apiKey = TestConfig.apiKey + let expectation = XCTestExpectation(description: "Request to apply config") + apiManager.request(.getFlags) { result in + // Check configuration inside the completion handler to ensure it's applied + let sessionConfig = apiManager.session.configuration + print("Inside completion - timeout: \(sessionConfig.timeoutIntervalForRequest)") + print("Inside completion - headers: \(sessionConfig.httpAdditionalHeaders)") + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + + // Get the session after the request + let finalSession = apiManager.session + + // Debug: Print session information + print("Initial session: \(initialSession)") + print("Final session: \(finalSession)") + print("Session recreated: \(initialSession !== finalSession)") + print("Final timeout: \(finalSession.configuration.timeoutIntervalForRequest)") + print("Final headers: \(finalSession.configuration.httpAdditionalHeaders)") + + // Verify that the session was recreated + XCTAssertNotEqual(initialSession, finalSession, "Session should be recreated") + + // Verify that the session configuration reflects our network config + let sessionConfig = finalSession.configuration + XCTAssertEqual(sessionConfig.timeoutIntervalForRequest, 1.0, "Request timeout should be applied") + XCTAssertEqual(sessionConfig.timeoutIntervalForResource, 5.0, "Resource timeout should be applied") + XCTAssertFalse(sessionConfig.allowsCellularAccess, "Cellular access should be disabled") + XCTAssertEqual(sessionConfig.httpMaximumConnectionsPerHost, 2, "Max connections per host should be applied") + XCTAssertEqual(sessionConfig.httpAdditionalHeaders?["Test-Header"] as? String, "Test-Value", "Additional headers should be applied") + XCTAssertFalse(sessionConfig.httpShouldUsePipelining, "HTTP pipelining should be disabled") + XCTAssertFalse(sessionConfig.httpShouldSetCookies, "HTTP cookies should be disabled") + } + + func testSSEManagerUsesNetworkConfig() { + let flagsmith = Flagsmith.shared + + // Set custom network configuration + flagsmith.networkConfig.requestTimeout = 2.0 + flagsmith.networkConfig.resourceTimeout = 10.0 + flagsmith.networkConfig.waitsForConnectivity = false + flagsmith.networkConfig.allowsCellularAccess = false + flagsmith.networkConfig.httpMaximumConnectionsPerHost = 1 + flagsmith.networkConfig.httpAdditionalHeaders = ["SSE-Header": "SSE-Value"] + + // Create a new SSEManager to test the configuration + let sseManager = SSEManager() + + // Trigger the start method to apply the network configuration + flagsmith.apiKey = TestConfig.apiKey + let expectation = XCTestExpectation(description: "SSE start to apply config") + sseManager.start { result in + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + + // Verify that the session configuration reflects our network config + let sessionConfig = sseManager.session.configuration + XCTAssertEqual(sessionConfig.timeoutIntervalForRequest, 2.0, "Request timeout should be applied") + XCTAssertEqual(sessionConfig.timeoutIntervalForResource, 10.0, "Resource timeout should be applied") + XCTAssertFalse(sessionConfig.waitsForConnectivity, "Wait for connectivity should be disabled") + XCTAssertFalse(sessionConfig.allowsCellularAccess, "Cellular access should be disabled") + XCTAssertEqual(sessionConfig.httpMaximumConnectionsPerHost, 1, "Max connections per host should be applied") + XCTAssertEqual(sessionConfig.httpAdditionalHeaders?["SSE-Header"] as? String, "SSE-Value", "Additional headers should be applied") + + // Clean up + sseManager.stop() + } + + func testNetworkConfigChangesTriggerSessionRecreation() { + let flagsmith = Flagsmith.shared + let apiManager = APIManager() + + // Set initial configuration and trigger a request to create the initial session + flagsmith.apiKey = TestConfig.apiKey + flagsmith.networkConfig.requestTimeout = 60.0 + flagsmith.networkConfig.httpAdditionalHeaders = ["Initial": "Value"] + + let initialExpectation = XCTestExpectation(description: "Initial request") + apiManager.request(.getFlags) { result in + initialExpectation.fulfill() + } + wait(for: [initialExpectation], timeout: 1.0) + + // Get initial session + let initialSession = apiManager.session + + // Change network configuration + flagsmith.networkConfig.requestTimeout = 30.0 + flagsmith.networkConfig.httpAdditionalHeaders = ["Changed": "Value"] + + // Trigger a request to cause session recreation + let expectation = XCTestExpectation(description: "Request completion") + + // Make a request that will trigger session recreation + apiManager.request(.getFlags) { result in + expectation.fulfill() + } + + wait(for: [expectation], timeout: 5.0) + + // Verify that the session was recreated with new configuration + let newSession = apiManager.session + XCTAssertNotEqual(initialSession, newSession, "Session should be recreated") + XCTAssertEqual(newSession.configuration.timeoutIntervalForRequest, 30.0, "New timeout should be applied") + XCTAssertEqual(newSession.configuration.httpAdditionalHeaders?["Changed"] as? String, "Value", "New headers should be applied") + } + + func testNetworkConfigWithRealAPIKey() throws { + // Skip this test if we don't have a real API key + guard TestConfig.hasRealApiKey else { + throw XCTSkip("Real API key required for integration test") + } + + let flagsmith = Flagsmith.shared + flagsmith.apiKey = TestConfig.apiKey + flagsmith.baseURL = TestConfig.baseURL + + // Set a very short timeout to test timeout behavior + flagsmith.networkConfig.requestTimeout = 0.1 // 100ms + + let expectation = XCTestExpectation(description: "Request with short timeout") + + flagsmith.getFeatureFlags { result in + switch result { + case .success: + // If this succeeds, it means the request completed within 100ms + expectation.fulfill() + case .failure(let error): + // We expect a timeout error with such a short timeout + if let urlError = error as? URLError, urlError.code == .timedOut { + expectation.fulfill() + } else { + XCTFail("Expected timeout error, got: \(error)") + } + } + } + + wait(for: [expectation], timeout: 2.0) + } + + func testNetworkConfigWithCustomHeaders() { + let flagsmith = Flagsmith.shared + flagsmith.apiKey = TestConfig.apiKey + + // Set custom headers + flagsmith.networkConfig.httpAdditionalHeaders = [ + "X-Custom-Header": "Custom-Value", + "User-Agent": "FlagsmithTest/1.0" + ] + + let apiManager = APIManager() + + // Trigger a request to apply the network configuration + let expectation = XCTestExpectation(description: "Request to apply headers") + apiManager.request(.getFlags) { result in + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + + let sessionConfig = apiManager.session.configuration + + // Verify custom headers are applied + XCTAssertEqual(sessionConfig.httpAdditionalHeaders?["X-Custom-Header"] as? String, "Custom-Value") + XCTAssertEqual(sessionConfig.httpAdditionalHeaders?["User-Agent"] as? String, "FlagsmithTest/1.0") + } + + func testNetworkConfigPerformance() { + let flagsmith = Flagsmith.shared + + // Measure time to create and configure multiple network configs + measure { + for i in 0..<100 { + flagsmith.networkConfig.requestTimeout = Double(i) + flagsmith.networkConfig.httpAdditionalHeaders = ["Key-\(i)": "Value-\(i)"] + } + } + } +} diff --git a/FlagsmithClient/Tests/NetworkConfigTests.swift b/FlagsmithClient/Tests/NetworkConfigTests.swift new file mode 100644 index 0000000..63c43ac --- /dev/null +++ b/FlagsmithClient/Tests/NetworkConfigTests.swift @@ -0,0 +1,151 @@ + +@testable import FlagsmithClient +import XCTest + +final class NetworkConfigTests: FlagsmithClientTestCase { + + func testDefaultNetworkConfigValues() { + let networkConfig = NetworkConfig() + + XCTAssertEqual(networkConfig.requestTimeout, 60.0, "Default request timeout should be 60 seconds") + XCTAssertEqual(networkConfig.resourceTimeout, 604800.0, "Default resource timeout should be 7 days") + XCTAssertTrue(networkConfig.waitsForConnectivity, "Default waitsForConnectivity should be true") + XCTAssertTrue(networkConfig.allowsCellularAccess, "Default allowsCellularAccess should be true") + XCTAssertEqual(networkConfig.httpMaximumConnectionsPerHost, 6, "Default httpMaximumConnectionsPerHost should be 6") + XCTAssertTrue(networkConfig.httpAdditionalHeaders.isEmpty, "Default httpAdditionalHeaders should be empty") + XCTAssertTrue(networkConfig.httpShouldUsePipelining, "Default httpShouldUsePipelining should be true") + XCTAssertTrue(networkConfig.httpShouldSetCookies, "Default httpShouldSetCookies should be true") + } + + func testNetworkConfigCustomization() { + let networkConfig = NetworkConfig() + + // Test request timeout customization + networkConfig.requestTimeout = 30.0 + XCTAssertEqual(networkConfig.requestTimeout, 30.0) + + // Test resource timeout customization + networkConfig.resourceTimeout = 300.0 + XCTAssertEqual(networkConfig.resourceTimeout, 300.0) + + // Test connectivity settings + networkConfig.waitsForConnectivity = false + XCTAssertFalse(networkConfig.waitsForConnectivity) + + networkConfig.allowsCellularAccess = false + XCTAssertFalse(networkConfig.allowsCellularAccess) + + // Test HTTP settings + networkConfig.httpMaximumConnectionsPerHost = 10 + XCTAssertEqual(networkConfig.httpMaximumConnectionsPerHost, 10) + + networkConfig.httpAdditionalHeaders = ["Custom-Header": "Custom-Value"] + XCTAssertEqual(networkConfig.httpAdditionalHeaders["Custom-Header"], "Custom-Value") + + networkConfig.httpShouldUsePipelining = false + XCTAssertFalse(networkConfig.httpShouldUsePipelining) + + networkConfig.httpShouldSetCookies = false + XCTAssertFalse(networkConfig.httpShouldSetCookies) + } + + func testFlagsmithNetworkConfigProperty() { + let flagsmith = Flagsmith.shared + + // Test default values + XCTAssertEqual(flagsmith.networkConfig.requestTimeout, 60.0) + XCTAssertEqual(flagsmith.networkConfig.resourceTimeout, 604800.0) + XCTAssertTrue(flagsmith.networkConfig.waitsForConnectivity) + XCTAssertTrue(flagsmith.networkConfig.allowsCellularAccess) + XCTAssertEqual(flagsmith.networkConfig.httpMaximumConnectionsPerHost, 6) + XCTAssertTrue(flagsmith.networkConfig.httpAdditionalHeaders.isEmpty) + XCTAssertTrue(flagsmith.networkConfig.httpShouldUsePipelining) + XCTAssertTrue(flagsmith.networkConfig.httpShouldSetCookies) + + // Test customization + flagsmith.networkConfig.requestTimeout = 1.0 + XCTAssertEqual(flagsmith.networkConfig.requestTimeout, 1.0) + + flagsmith.networkConfig.httpAdditionalHeaders = ["Test-Header": "Test-Value"] + XCTAssertEqual(flagsmith.networkConfig.httpAdditionalHeaders["Test-Header"], "Test-Value") + } + + func testNetworkConfigThreadSafety() { + let flagsmith = Flagsmith.shared + let expectation = XCTestExpectation(description: "Thread safety test") + expectation.expectedFulfillmentCount = 10 + + // Store initial values to restore later + let initialTimeout = flagsmith.networkConfig.requestTimeout + let initialHeaders = flagsmith.networkConfig.httpAdditionalHeaders + + // Test concurrent access to network config + for i in 0..<10 { + DispatchQueue.global().async { + // Test that we can safely set values concurrently + flagsmith.networkConfig.requestTimeout = Double(i) + flagsmith.networkConfig.httpAdditionalHeaders = ["Thread-\(i)": "Value-\(i)"] + expectation.fulfill() + } + } + + wait(for: [expectation], timeout: 5.0) + + // Verify that the configuration was updated and no crashes occurred + // The exact final value is unpredictable due to concurrent access, but it should be valid + XCTAssertTrue(flagsmith.networkConfig.requestTimeout >= 0.0) + XCTAssertTrue(flagsmith.networkConfig.requestTimeout <= 9.0) + XCTAssertTrue(flagsmith.networkConfig.httpAdditionalHeaders.count >= 0) + + // Restore initial values to avoid affecting other tests + flagsmith.networkConfig.requestTimeout = initialTimeout + flagsmith.networkConfig.httpAdditionalHeaders = initialHeaders + } + + func testNetworkConfigValidation() { + let networkConfig = NetworkConfig() + + // Test negative timeout values (should be allowed as URLSessionConfiguration accepts them) + networkConfig.requestTimeout = -1.0 + XCTAssertEqual(networkConfig.requestTimeout, -1.0) + + networkConfig.resourceTimeout = 0.0 + XCTAssertEqual(networkConfig.resourceTimeout, 0.0) + + // Test large timeout values + networkConfig.requestTimeout = 3600.0 // 1 hour + XCTAssertEqual(networkConfig.requestTimeout, 3600.0) + + // Test zero connections per host + networkConfig.httpMaximumConnectionsPerHost = 0 + XCTAssertEqual(networkConfig.httpMaximumConnectionsPerHost, 0) + + // Test large number of connections per host + networkConfig.httpMaximumConnectionsPerHost = 100 + XCTAssertEqual(networkConfig.httpMaximumConnectionsPerHost, 100) + } + + func testNetworkConfigEquality() { + let config1 = NetworkConfig() + let config2 = NetworkConfig() + + // Identical configs should be equal + XCTAssertEqual(config1.requestTimeout, config2.requestTimeout) + XCTAssertEqual(config1.resourceTimeout, config2.resourceTimeout) + XCTAssertEqual(config1.waitsForConnectivity, config2.waitsForConnectivity) + XCTAssertEqual(config1.allowsCellularAccess, config2.allowsCellularAccess) + XCTAssertEqual(config1.httpMaximumConnectionsPerHost, config2.httpMaximumConnectionsPerHost) + XCTAssertEqual(config1.httpAdditionalHeaders, config2.httpAdditionalHeaders) + XCTAssertEqual(config1.httpShouldUsePipelining, config2.httpShouldUsePipelining) + XCTAssertEqual(config1.httpShouldSetCookies, config2.httpShouldSetCookies) + + // Modify one config + config1.requestTimeout = 30.0 + XCTAssertNotEqual(config1.requestTimeout, config2.requestTimeout) + + // Reset and test other properties + config1.requestTimeout = 60.0 + config1.httpAdditionalHeaders = ["Test": "Value"] + XCTAssertNotEqual(config1.httpAdditionalHeaders, config2.httpAdditionalHeaders) + } +} From 57c9258e56f5731136824c6dc83c1acb273b7e9f Mon Sep 17 00:00:00 2001 From: Polat Olu Date: Thu, 9 Oct 2025 18:45:26 +0100 Subject: [PATCH 2/4] Temporary Readme file for Network Config --- NETWORK_CONFIG_README.md | 156 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 NETWORK_CONFIG_README.md diff --git a/NETWORK_CONFIG_README.md b/NETWORK_CONFIG_README.md new file mode 100644 index 0000000..f0ab359 --- /dev/null +++ b/NETWORK_CONFIG_README.md @@ -0,0 +1,156 @@ +# Network Configuration + +The Flagsmith iOS SDK now provides a `NetworkConfig` class that allows you to customize network communication parameters without exposing the entire `URLSessionConfiguration`. This addresses the customer requirement to reduce request timeouts and configure other network settings. + +## Features + +The `NetworkConfig` class exposes the following commonly used `URLSessionConfiguration` parameters: + +- **Request Timeout**: `requestTimeout` - Timeout for individual requests (default: 60.0 seconds) +- **Resource Timeout**: `resourceTimeout` - Total timeout for the entire resource request (default: 604800.0 seconds / 7 days) +- **Connectivity**: `waitsForConnectivity` - Whether to wait for connectivity (default: true) +- **Cellular Access**: `allowsCellularAccess` - Whether to allow cellular access (default: true) +- **Connection Limits**: `httpMaximumConnectionsPerHost` - Max concurrent connections per host (default: 6) +- **Custom Headers**: `httpAdditionalHeaders` - Additional HTTP headers to send with requests (default: empty) +- **HTTP Pipelining**: `httpShouldUsePipelining` - Whether to use HTTP pipelining (default: true) +- **HTTP Cookies**: `httpShouldSetCookies` - Whether to automatically set cookies (default: true) + +## Usage + +### Basic Configuration + +```swift +import FlagsmithClient + +let flagsmith = Flagsmith.shared +flagsmith.apiKey = "your-api-key-here" + +// Configure network settings +flagsmith.networkConfig.requestTimeout = 1.0 // 1 second timeout +flagsmith.networkConfig.resourceTimeout = 30.0 // 30 second total timeout +flagsmith.networkConfig.allowsCellularAccess = false // WiFi only +``` + +### Custom Headers + +```swift +// Add custom headers that will be sent with every request +flagsmith.networkConfig.httpAdditionalHeaders = [ + "X-Client-Version": "1.0.0", + "X-Platform": "iOS", + "X-Environment": "Production" +] +``` + +### Connection Limits + +```swift +// Limit concurrent connections to the same host +flagsmith.networkConfig.httpMaximumConnectionsPerHost = 2 +``` + +### Complete Example + +```swift +import FlagsmithClient + +class MyFlagsmithManager { + private let flagsmith = Flagsmith.shared + + func setupFlagsmith() { + // Set API key + flagsmith.apiKey = "your-api-key-here" + + // Configure network settings + configureNetworkSettings() + + // Configure cache settings (existing feature) + configureCacheSettings() + } + + private func configureNetworkSettings() { + // Customer requested 1 second timeout instead of 60 seconds + flagsmith.networkConfig.requestTimeout = 1.0 + + // Set total resource timeout + flagsmith.networkConfig.resourceTimeout = 30.0 + + // Configure connectivity + flagsmith.networkConfig.waitsForConnectivity = true + flagsmith.networkConfig.allowsCellularAccess = true + + // Configure HTTP settings + flagsmith.networkConfig.httpMaximumConnectionsPerHost = 4 + flagsmith.networkConfig.httpShouldUsePipelining = true + flagsmith.networkConfig.httpShouldSetCookies = true + + // Add custom headers + flagsmith.networkConfig.httpAdditionalHeaders = [ + "X-Custom-Header": "Custom-Value", + "User-Agent": "MyApp/1.0" + ] + } + + private func configureCacheSettings() { + // Existing cache configuration + flagsmith.cacheConfig.useCache = true + flagsmith.cacheConfig.cacheTTL = 300.0 // 5 minutes + } + + func fetchFeatureFlags() { + flagsmith.getFeatureFlags { result in + switch result { + case .success(let flags): + print("Fetched \(flags.count) feature flags") + case .failure(let error): + print("Error: \(error)") + } + } + } +} +``` + +## Thread Safety + +The `NetworkConfig` is thread-safe and can be modified from any thread. Changes to the configuration will automatically trigger the recreation of the underlying `URLSession` with the new settings. + +## Backward Compatibility + +This feature is fully backward compatible. Existing code will continue to work without any changes, using the default network configuration values. + +## Implementation Details + +- The `NetworkConfig` class is similar to the existing `CacheConfig` class +- Network configuration changes are applied to both `APIManager` and `SSEManager` +- The underlying `URLSession` is recreated when network settings change +- All network configuration parameters are applied consistently across all network requests + +## Testing + +The implementation includes comprehensive unit tests covering: +- Default configuration values +- Configuration customization +- Thread safety +- Integration with `APIManager` and `SSEManager` +- Session recreation when configuration changes + +## Migration from URLSessionConfiguration + +If you were previously trying to inject a custom `URLSessionConfiguration`, you can now use the `NetworkConfig` instead: + +**Before (not supported):** +```swift +// This approach is not supported +let customConfig = URLSessionConfiguration.default +customConfig.timeoutIntervalForRequest = 1.0 +// ... other customizations +``` + +**After (supported):** +```swift +// Use the new NetworkConfig +flagsmith.networkConfig.requestTimeout = 1.0 +// ... other customizations +``` + +This approach provides better encapsulation and ensures that all network settings are applied consistently across the SDK. From 3e312d1df106e2f79ef583b441623f4a8895485a Mon Sep 17 00:00:00 2001 From: Polat Olu Date: Fri, 10 Oct 2025 12:20:55 +0100 Subject: [PATCH 3/4] Test updates --- FlagsmithClient/Classes/Flagsmith.swift | 21 --- .../Classes/Internal/APIManager.swift | 26 +-- .../Classes/Internal/SSEManager.swift | 26 +-- .../Tests/NetworkConfigIntegrationTests.swift | 173 ++---------------- .../Tests/NetworkConfigTests.swift | 76 +------- 5 files changed, 39 insertions(+), 283 deletions(-) diff --git a/FlagsmithClient/Classes/Flagsmith.swift b/FlagsmithClient/Classes/Flagsmith.swift index d98865d..ac28e63 100644 --- a/FlagsmithClient/Classes/Flagsmith.swift +++ b/FlagsmithClient/Classes/Flagsmith.swift @@ -426,25 +426,4 @@ public final class CacheConfig { public final class NetworkConfig { /// The timeout interval for URL requests, in seconds. Default is 60.0 seconds. public var requestTimeout: TimeInterval = 60.0 - - /// The timeout interval for the entire resource request, in seconds. Default is 7 days (604800 seconds). - public var resourceTimeout: TimeInterval = 604800.0 - - /// A Boolean value that determines whether the session should wait for connectivity to become available. Default is true. - public var waitsForConnectivity: Bool = true - - /// A Boolean value that determines whether the session should use cellular access. Default is true. - public var allowsCellularAccess: Bool = true - - /// The maximum number of simultaneous connections to make to a given host. Default is 6. - public var httpMaximumConnectionsPerHost: Int = 6 - - /// Additional HTTP headers to be sent with requests. Default is empty. - public var httpAdditionalHeaders: [String: String] = [:] - - /// A Boolean value that determines whether the session should use HTTP pipelining. Default is true. - public var httpShouldUsePipelining: Bool = true - - /// A Boolean value that determines whether the session should automatically set the "Accept-Encoding" header. Default is true. - public var httpShouldSetCookies: Bool = true } diff --git a/FlagsmithClient/Classes/Internal/APIManager.swift b/FlagsmithClient/Classes/Internal/APIManager.swift index 9db9710..59af2f5 100644 --- a/FlagsmithClient/Classes/Internal/APIManager.swift +++ b/FlagsmithClient/Classes/Internal/APIManager.swift @@ -100,13 +100,6 @@ final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { // Apply network configuration configuration.timeoutIntervalForRequest = networkConfig.requestTimeout - configuration.timeoutIntervalForResource = networkConfig.resourceTimeout - configuration.waitsForConnectivity = networkConfig.waitsForConnectivity - configuration.allowsCellularAccess = networkConfig.allowsCellularAccess - configuration.httpMaximumConnectionsPerHost = networkConfig.httpMaximumConnectionsPerHost - configuration.httpAdditionalHeaders = networkConfig.httpAdditionalHeaders - configuration.httpShouldUsePipelining = networkConfig.httpShouldUsePipelining - configuration.httpShouldSetCookies = networkConfig.httpShouldSetCookies // Apply cache configuration configuration.urlCache = cacheConfig.cache @@ -114,18 +107,6 @@ final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { return configuration } - /// Helper function to compare HTTP headers dictionaries - private func areHeadersEqual(_ headers1: [AnyHashable: Any]?, _ headers2: [AnyHashable: Any]?) -> Bool { - guard let h1 = headers1, let h2 = headers2 else { - return headers1 == nil && headers2 == nil - } - - // Convert to [String: String] for comparison - let dict1 = h1.compactMapValues { $0 as? String } - let dict2 = h2.compactMapValues { $0 as? String } - - return dict1 == dict2 - } func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) { serialAccessQueue.sync { @@ -210,9 +191,12 @@ final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { let cacheConfig = Flagsmith.shared.cacheConfig let newConfig = createURLSessionConfiguration(networkConfig: networkConfig, cacheConfig: cacheConfig) - session = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main) + let newSession = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main) + + // Update session using the property setter to ensure thread-safe access + self.session = newSession - let task = session.dataTask(with: request) + let task = newSession.dataTask(with: request) tasksToCompletionHandlers[task.taskIdentifier] = completion task.resume() } diff --git a/FlagsmithClient/Classes/Internal/SSEManager.swift b/FlagsmithClient/Classes/Internal/SSEManager.swift index 4a39086..1d6482d 100644 --- a/FlagsmithClient/Classes/Internal/SSEManager.swift +++ b/FlagsmithClient/Classes/Internal/SSEManager.swift @@ -102,29 +102,10 @@ final class SSEManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { // Apply network configuration configuration.timeoutIntervalForRequest = networkConfig.requestTimeout - configuration.timeoutIntervalForResource = networkConfig.resourceTimeout - configuration.waitsForConnectivity = networkConfig.waitsForConnectivity - configuration.allowsCellularAccess = networkConfig.allowsCellularAccess - configuration.httpMaximumConnectionsPerHost = networkConfig.httpMaximumConnectionsPerHost - configuration.httpAdditionalHeaders = networkConfig.httpAdditionalHeaders - configuration.httpShouldUsePipelining = networkConfig.httpShouldUsePipelining - configuration.httpShouldSetCookies = networkConfig.httpShouldSetCookies return configuration } - /// Helper function to compare HTTP headers dictionaries - private func areHeadersEqual(_ headers1: [AnyHashable: Any]?, _ headers2: [AnyHashable: Any]?) -> Bool { - guard let h1 = headers1, let h2 = headers2 else { - return headers1 == nil && headers2 == nil - } - - // Convert to [String: String] for comparison - let dict1 = h1.compactMapValues { $0 as? String } - let dict2 = h2.compactMapValues { $0 as? String } - - return dict1 == dict2 - } // Helper function to process SSE data internal func processSSEData(_ data: String) { @@ -208,10 +189,13 @@ final class SSEManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { // Always recreate session with current network configuration // This ensures that any changes to network config are applied immediately let newConfig = createURLSessionConfiguration(networkConfig: Flagsmith.shared.networkConfig) - session = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main) + let newSession = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main) + + // Update session using the property setter to ensure thread-safe access + self.session = newSession completionHandler = completion - dataTask = session.dataTask(with: request) + dataTask = newSession.dataTask(with: request) dataTask?.resume() } diff --git a/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift b/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift index 21b109e..dda41b2 100644 --- a/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift +++ b/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift @@ -9,21 +9,9 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { // Set custom network configuration flagsmith.networkConfig.requestTimeout = 1.0 - flagsmith.networkConfig.resourceTimeout = 5.0 - flagsmith.networkConfig.allowsCellularAccess = false - flagsmith.networkConfig.httpMaximumConnectionsPerHost = 2 - flagsmith.networkConfig.httpAdditionalHeaders = ["Test-Header": "Test-Value"] - flagsmith.networkConfig.httpShouldUsePipelining = false - flagsmith.networkConfig.httpShouldSetCookies = false // Verify the configuration is set correctly XCTAssertEqual(flagsmith.networkConfig.requestTimeout, 1.0) - XCTAssertEqual(flagsmith.networkConfig.resourceTimeout, 5.0) - XCTAssertFalse(flagsmith.networkConfig.allowsCellularAccess) - XCTAssertEqual(flagsmith.networkConfig.httpMaximumConnectionsPerHost, 2) - XCTAssertEqual(flagsmith.networkConfig.httpAdditionalHeaders["Test-Header"], "Test-Value") - XCTAssertFalse(flagsmith.networkConfig.httpShouldUsePipelining) - XCTAssertFalse(flagsmith.networkConfig.httpShouldSetCookies) } func testURLSessionConfigurationCreation() { @@ -31,86 +19,36 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { // Set custom network configuration flagsmith.networkConfig.requestTimeout = 1.0 - flagsmith.networkConfig.resourceTimeout = 5.0 - flagsmith.networkConfig.allowsCellularAccess = false - flagsmith.networkConfig.httpMaximumConnectionsPerHost = 2 - flagsmith.networkConfig.httpAdditionalHeaders = ["Test-Header": "Test-Value"] - flagsmith.networkConfig.httpShouldUsePipelining = false - flagsmith.networkConfig.httpShouldSetCookies = false // Create a URLSessionConfiguration directly let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = flagsmith.networkConfig.requestTimeout - config.timeoutIntervalForResource = flagsmith.networkConfig.resourceTimeout - config.waitsForConnectivity = flagsmith.networkConfig.waitsForConnectivity - config.allowsCellularAccess = flagsmith.networkConfig.allowsCellularAccess - config.httpMaximumConnectionsPerHost = flagsmith.networkConfig.httpMaximumConnectionsPerHost - config.httpAdditionalHeaders = flagsmith.networkConfig.httpAdditionalHeaders - config.httpShouldUsePipelining = flagsmith.networkConfig.httpShouldUsePipelining - config.httpShouldSetCookies = flagsmith.networkConfig.httpShouldSetCookies // Verify the configuration is applied correctly XCTAssertEqual(config.timeoutIntervalForRequest, 1.0, "Request timeout should be applied") - XCTAssertEqual(config.timeoutIntervalForResource, 5.0, "Resource timeout should be applied") - XCTAssertFalse(config.allowsCellularAccess, "Cellular access should be disabled") - XCTAssertEqual(config.httpMaximumConnectionsPerHost, 2, "Max connections per host should be applied") - XCTAssertEqual(config.httpAdditionalHeaders?["Test-Header"] as? String, "Test-Value", "Additional headers should be applied") - XCTAssertFalse(config.httpShouldUsePipelining, "HTTP pipelining should be disabled") - XCTAssertFalse(config.httpShouldSetCookies, "HTTP cookies should be disabled") } func testAPIManagerUsesNetworkConfig() { let flagsmith = Flagsmith.shared - // Set custom network configuration + // Set custom network configuration BEFORE creating APIManager flagsmith.networkConfig.requestTimeout = 1.0 - flagsmith.networkConfig.resourceTimeout = 5.0 - flagsmith.networkConfig.allowsCellularAccess = false - flagsmith.networkConfig.httpMaximumConnectionsPerHost = 2 - flagsmith.networkConfig.httpAdditionalHeaders = ["Test-Header": "Test-Value"] - flagsmith.networkConfig.httpShouldUsePipelining = false - flagsmith.networkConfig.httpShouldSetCookies = false // Create a new APIManager to test the configuration let apiManager = APIManager() - // Get initial session for comparison - let initialSession = apiManager.session + // Set the API key on the APIManager instance + apiManager.apiKey = TestConfig.apiKey // Trigger a request to apply the network configuration - flagsmith.apiKey = TestConfig.apiKey let expectation = XCTestExpectation(description: "Request to apply config") apiManager.request(.getFlags) { result in - // Check configuration inside the completion handler to ensure it's applied + // Check the session configuration inside the completion handler let sessionConfig = apiManager.session.configuration - print("Inside completion - timeout: \(sessionConfig.timeoutIntervalForRequest)") - print("Inside completion - headers: \(sessionConfig.httpAdditionalHeaders)") + XCTAssertEqual(sessionConfig.timeoutIntervalForRequest, 1.0, "Request timeout should be applied") expectation.fulfill() } - wait(for: [expectation], timeout: 1.0) - - // Get the session after the request - let finalSession = apiManager.session - - // Debug: Print session information - print("Initial session: \(initialSession)") - print("Final session: \(finalSession)") - print("Session recreated: \(initialSession !== finalSession)") - print("Final timeout: \(finalSession.configuration.timeoutIntervalForRequest)") - print("Final headers: \(finalSession.configuration.httpAdditionalHeaders)") - - // Verify that the session was recreated - XCTAssertNotEqual(initialSession, finalSession, "Session should be recreated") - - // Verify that the session configuration reflects our network config - let sessionConfig = finalSession.configuration - XCTAssertEqual(sessionConfig.timeoutIntervalForRequest, 1.0, "Request timeout should be applied") - XCTAssertEqual(sessionConfig.timeoutIntervalForResource, 5.0, "Resource timeout should be applied") - XCTAssertFalse(sessionConfig.allowsCellularAccess, "Cellular access should be disabled") - XCTAssertEqual(sessionConfig.httpMaximumConnectionsPerHost, 2, "Max connections per host should be applied") - XCTAssertEqual(sessionConfig.httpAdditionalHeaders?["Test-Header"] as? String, "Test-Value", "Additional headers should be applied") - XCTAssertFalse(sessionConfig.httpShouldUsePipelining, "HTTP pipelining should be disabled") - XCTAssertFalse(sessionConfig.httpShouldSetCookies, "HTTP cookies should be disabled") + wait(for: [expectation], timeout: 2.0) } func testSSEManagerUsesNetworkConfig() { @@ -118,31 +56,22 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { // Set custom network configuration flagsmith.networkConfig.requestTimeout = 2.0 - flagsmith.networkConfig.resourceTimeout = 10.0 - flagsmith.networkConfig.waitsForConnectivity = false - flagsmith.networkConfig.allowsCellularAccess = false - flagsmith.networkConfig.httpMaximumConnectionsPerHost = 1 - flagsmith.networkConfig.httpAdditionalHeaders = ["SSE-Header": "SSE-Value"] // Create a new SSEManager to test the configuration let sseManager = SSEManager() + // Set the API key on the SSEManager instance + sseManager.apiKey = TestConfig.apiKey + // Trigger the start method to apply the network configuration - flagsmith.apiKey = TestConfig.apiKey - let expectation = XCTestExpectation(description: "SSE start to apply config") + // The start method calls the completion handler immediately, so we don't need to wait sseManager.start { result in - expectation.fulfill() + // This will be called immediately when start() is called } - wait(for: [expectation], timeout: 1.0) // Verify that the session configuration reflects our network config let sessionConfig = sseManager.session.configuration XCTAssertEqual(sessionConfig.timeoutIntervalForRequest, 2.0, "Request timeout should be applied") - XCTAssertEqual(sessionConfig.timeoutIntervalForResource, 10.0, "Resource timeout should be applied") - XCTAssertFalse(sessionConfig.waitsForConnectivity, "Wait for connectivity should be disabled") - XCTAssertFalse(sessionConfig.allowsCellularAccess, "Cellular access should be disabled") - XCTAssertEqual(sessionConfig.httpMaximumConnectionsPerHost, 1, "Max connections per host should be applied") - XCTAssertEqual(sessionConfig.httpAdditionalHeaders?["SSE-Header"] as? String, "SSE-Value", "Additional headers should be applied") // Clean up sseManager.stop() @@ -153,9 +82,8 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { let apiManager = APIManager() // Set initial configuration and trigger a request to create the initial session - flagsmith.apiKey = TestConfig.apiKey + apiManager.apiKey = TestConfig.apiKey flagsmith.networkConfig.requestTimeout = 60.0 - flagsmith.networkConfig.httpAdditionalHeaders = ["Initial": "Value"] let initialExpectation = XCTestExpectation(description: "Initial request") apiManager.request(.getFlags) { result in @@ -163,88 +91,26 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { } wait(for: [initialExpectation], timeout: 1.0) - // Get initial session - let initialSession = apiManager.session + // Verify initial configuration + let initialSessionConfig = apiManager.session.configuration + XCTAssertEqual(initialSessionConfig.timeoutIntervalForRequest, 60.0, "Initial timeout should be applied") // Change network configuration flagsmith.networkConfig.requestTimeout = 30.0 - flagsmith.networkConfig.httpAdditionalHeaders = ["Changed": "Value"] - // Trigger a request to cause session recreation + // Trigger a request to apply the new configuration let expectation = XCTestExpectation(description: "Request completion") - - // Make a request that will trigger session recreation apiManager.request(.getFlags) { result in expectation.fulfill() } wait(for: [expectation], timeout: 5.0) - // Verify that the session was recreated with new configuration - let newSession = apiManager.session - XCTAssertNotEqual(initialSession, newSession, "Session should be recreated") - XCTAssertEqual(newSession.configuration.timeoutIntervalForRequest, 30.0, "New timeout should be applied") - XCTAssertEqual(newSession.configuration.httpAdditionalHeaders?["Changed"] as? String, "Value", "New headers should be applied") - } - - func testNetworkConfigWithRealAPIKey() throws { - // Skip this test if we don't have a real API key - guard TestConfig.hasRealApiKey else { - throw XCTSkip("Real API key required for integration test") - } - - let flagsmith = Flagsmith.shared - flagsmith.apiKey = TestConfig.apiKey - flagsmith.baseURL = TestConfig.baseURL - - // Set a very short timeout to test timeout behavior - flagsmith.networkConfig.requestTimeout = 0.1 // 100ms - - let expectation = XCTestExpectation(description: "Request with short timeout") - - flagsmith.getFeatureFlags { result in - switch result { - case .success: - // If this succeeds, it means the request completed within 100ms - expectation.fulfill() - case .failure(let error): - // We expect a timeout error with such a short timeout - if let urlError = error as? URLError, urlError.code == .timedOut { - expectation.fulfill() - } else { - XCTFail("Expected timeout error, got: \(error)") - } - } - } - - wait(for: [expectation], timeout: 2.0) + // Verify that the new configuration is applied + let newSessionConfig = apiManager.session.configuration + XCTAssertEqual(newSessionConfig.timeoutIntervalForRequest, 30.0, "New timeout should be applied") } - func testNetworkConfigWithCustomHeaders() { - let flagsmith = Flagsmith.shared - flagsmith.apiKey = TestConfig.apiKey - - // Set custom headers - flagsmith.networkConfig.httpAdditionalHeaders = [ - "X-Custom-Header": "Custom-Value", - "User-Agent": "FlagsmithTest/1.0" - ] - - let apiManager = APIManager() - - // Trigger a request to apply the network configuration - let expectation = XCTestExpectation(description: "Request to apply headers") - apiManager.request(.getFlags) { result in - expectation.fulfill() - } - wait(for: [expectation], timeout: 1.0) - - let sessionConfig = apiManager.session.configuration - - // Verify custom headers are applied - XCTAssertEqual(sessionConfig.httpAdditionalHeaders?["X-Custom-Header"] as? String, "Custom-Value") - XCTAssertEqual(sessionConfig.httpAdditionalHeaders?["User-Agent"] as? String, "FlagsmithTest/1.0") - } func testNetworkConfigPerformance() { let flagsmith = Flagsmith.shared @@ -253,7 +119,6 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { measure { for i in 0..<100 { flagsmith.networkConfig.requestTimeout = Double(i) - flagsmith.networkConfig.httpAdditionalHeaders = ["Key-\(i)": "Value-\(i)"] } } } diff --git a/FlagsmithClient/Tests/NetworkConfigTests.swift b/FlagsmithClient/Tests/NetworkConfigTests.swift index 63c43ac..8ef80cf 100644 --- a/FlagsmithClient/Tests/NetworkConfigTests.swift +++ b/FlagsmithClient/Tests/NetworkConfigTests.swift @@ -8,13 +8,6 @@ final class NetworkConfigTests: FlagsmithClientTestCase { let networkConfig = NetworkConfig() XCTAssertEqual(networkConfig.requestTimeout, 60.0, "Default request timeout should be 60 seconds") - XCTAssertEqual(networkConfig.resourceTimeout, 604800.0, "Default resource timeout should be 7 days") - XCTAssertTrue(networkConfig.waitsForConnectivity, "Default waitsForConnectivity should be true") - XCTAssertTrue(networkConfig.allowsCellularAccess, "Default allowsCellularAccess should be true") - XCTAssertEqual(networkConfig.httpMaximumConnectionsPerHost, 6, "Default httpMaximumConnectionsPerHost should be 6") - XCTAssertTrue(networkConfig.httpAdditionalHeaders.isEmpty, "Default httpAdditionalHeaders should be empty") - XCTAssertTrue(networkConfig.httpShouldUsePipelining, "Default httpShouldUsePipelining should be true") - XCTAssertTrue(networkConfig.httpShouldSetCookies, "Default httpShouldSetCookies should be true") } func testNetworkConfigCustomization() { @@ -24,29 +17,9 @@ final class NetworkConfigTests: FlagsmithClientTestCase { networkConfig.requestTimeout = 30.0 XCTAssertEqual(networkConfig.requestTimeout, 30.0) - // Test resource timeout customization - networkConfig.resourceTimeout = 300.0 - XCTAssertEqual(networkConfig.resourceTimeout, 300.0) - - // Test connectivity settings - networkConfig.waitsForConnectivity = false - XCTAssertFalse(networkConfig.waitsForConnectivity) - - networkConfig.allowsCellularAccess = false - XCTAssertFalse(networkConfig.allowsCellularAccess) - - // Test HTTP settings - networkConfig.httpMaximumConnectionsPerHost = 10 - XCTAssertEqual(networkConfig.httpMaximumConnectionsPerHost, 10) - - networkConfig.httpAdditionalHeaders = ["Custom-Header": "Custom-Value"] - XCTAssertEqual(networkConfig.httpAdditionalHeaders["Custom-Header"], "Custom-Value") - - networkConfig.httpShouldUsePipelining = false - XCTAssertFalse(networkConfig.httpShouldUsePipelining) - - networkConfig.httpShouldSetCookies = false - XCTAssertFalse(networkConfig.httpShouldSetCookies) + // Test 1 second timeout as requested by customer + networkConfig.requestTimeout = 1.0 + XCTAssertEqual(networkConfig.requestTimeout, 1.0) } func testFlagsmithNetworkConfigProperty() { @@ -54,20 +27,10 @@ final class NetworkConfigTests: FlagsmithClientTestCase { // Test default values XCTAssertEqual(flagsmith.networkConfig.requestTimeout, 60.0) - XCTAssertEqual(flagsmith.networkConfig.resourceTimeout, 604800.0) - XCTAssertTrue(flagsmith.networkConfig.waitsForConnectivity) - XCTAssertTrue(flagsmith.networkConfig.allowsCellularAccess) - XCTAssertEqual(flagsmith.networkConfig.httpMaximumConnectionsPerHost, 6) - XCTAssertTrue(flagsmith.networkConfig.httpAdditionalHeaders.isEmpty) - XCTAssertTrue(flagsmith.networkConfig.httpShouldUsePipelining) - XCTAssertTrue(flagsmith.networkConfig.httpShouldSetCookies) // Test customization flagsmith.networkConfig.requestTimeout = 1.0 XCTAssertEqual(flagsmith.networkConfig.requestTimeout, 1.0) - - flagsmith.networkConfig.httpAdditionalHeaders = ["Test-Header": "Test-Value"] - XCTAssertEqual(flagsmith.networkConfig.httpAdditionalHeaders["Test-Header"], "Test-Value") } func testNetworkConfigThreadSafety() { @@ -75,16 +38,14 @@ final class NetworkConfigTests: FlagsmithClientTestCase { let expectation = XCTestExpectation(description: "Thread safety test") expectation.expectedFulfillmentCount = 10 - // Store initial values to restore later + // Store initial value to restore later let initialTimeout = flagsmith.networkConfig.requestTimeout - let initialHeaders = flagsmith.networkConfig.httpAdditionalHeaders // Test concurrent access to network config for i in 0..<10 { DispatchQueue.global().async { // Test that we can safely set values concurrently flagsmith.networkConfig.requestTimeout = Double(i) - flagsmith.networkConfig.httpAdditionalHeaders = ["Thread-\(i)": "Value-\(i)"] expectation.fulfill() } } @@ -95,11 +56,9 @@ final class NetworkConfigTests: FlagsmithClientTestCase { // The exact final value is unpredictable due to concurrent access, but it should be valid XCTAssertTrue(flagsmith.networkConfig.requestTimeout >= 0.0) XCTAssertTrue(flagsmith.networkConfig.requestTimeout <= 9.0) - XCTAssertTrue(flagsmith.networkConfig.httpAdditionalHeaders.count >= 0) - // Restore initial values to avoid affecting other tests + // Restore initial value to avoid affecting other tests flagsmith.networkConfig.requestTimeout = initialTimeout - flagsmith.networkConfig.httpAdditionalHeaders = initialHeaders } func testNetworkConfigValidation() { @@ -109,20 +68,13 @@ final class NetworkConfigTests: FlagsmithClientTestCase { networkConfig.requestTimeout = -1.0 XCTAssertEqual(networkConfig.requestTimeout, -1.0) - networkConfig.resourceTimeout = 0.0 - XCTAssertEqual(networkConfig.resourceTimeout, 0.0) + // Test zero timeout + networkConfig.requestTimeout = 0.0 + XCTAssertEqual(networkConfig.requestTimeout, 0.0) // Test large timeout values networkConfig.requestTimeout = 3600.0 // 1 hour XCTAssertEqual(networkConfig.requestTimeout, 3600.0) - - // Test zero connections per host - networkConfig.httpMaximumConnectionsPerHost = 0 - XCTAssertEqual(networkConfig.httpMaximumConnectionsPerHost, 0) - - // Test large number of connections per host - networkConfig.httpMaximumConnectionsPerHost = 100 - XCTAssertEqual(networkConfig.httpMaximumConnectionsPerHost, 100) } func testNetworkConfigEquality() { @@ -131,21 +83,13 @@ final class NetworkConfigTests: FlagsmithClientTestCase { // Identical configs should be equal XCTAssertEqual(config1.requestTimeout, config2.requestTimeout) - XCTAssertEqual(config1.resourceTimeout, config2.resourceTimeout) - XCTAssertEqual(config1.waitsForConnectivity, config2.waitsForConnectivity) - XCTAssertEqual(config1.allowsCellularAccess, config2.allowsCellularAccess) - XCTAssertEqual(config1.httpMaximumConnectionsPerHost, config2.httpMaximumConnectionsPerHost) - XCTAssertEqual(config1.httpAdditionalHeaders, config2.httpAdditionalHeaders) - XCTAssertEqual(config1.httpShouldUsePipelining, config2.httpShouldUsePipelining) - XCTAssertEqual(config1.httpShouldSetCookies, config2.httpShouldSetCookies) // Modify one config config1.requestTimeout = 30.0 XCTAssertNotEqual(config1.requestTimeout, config2.requestTimeout) - // Reset and test other properties + // Reset and test again config1.requestTimeout = 60.0 - config1.httpAdditionalHeaders = ["Test": "Value"] - XCTAssertNotEqual(config1.httpAdditionalHeaders, config2.httpAdditionalHeaders) + XCTAssertEqual(config1.requestTimeout, config2.requestTimeout) } } From 645f644736736a0faae0abbdb288758f1ddd927e Mon Sep 17 00:00:00 2001 From: Polat Olu Date: Fri, 10 Oct 2025 14:11:00 +0100 Subject: [PATCH 4/4] lint fixes --- Example/NetworkConfigExample.swift | 89 ++++--------------- .../Classes/Internal/APIManager.swift | 4 +- .../Classes/Internal/SSEManager.swift | 5 +- .../Tests/NetworkConfigIntegrationTests.swift | 42 ++++----- .../Tests/NetworkConfigTests.swift | 11 ++- 5 files changed, 49 insertions(+), 102 deletions(-) diff --git a/Example/NetworkConfigExample.swift b/Example/NetworkConfigExample.swift index 125ab53..21db7d9 100644 --- a/Example/NetworkConfigExample.swift +++ b/Example/NetworkConfigExample.swift @@ -1,4 +1,3 @@ - import Foundation import FlagsmithClient @@ -21,37 +20,11 @@ class NetworkConfigExample { private func configureNetworkSettings(_ flagsmith: Flagsmith) { // Set a custom request timeout (customer requested 1 second instead of 60) flagsmith.networkConfig.requestTimeout = 1.0 - - // Set resource timeout (total time for the entire request) - flagsmith.networkConfig.resourceTimeout = 30.0 - - // Configure connectivity settings - flagsmith.networkConfig.waitsForConnectivity = true - flagsmith.networkConfig.allowsCellularAccess = true - - // Configure HTTP settings - flagsmith.networkConfig.httpMaximumConnectionsPerHost = 4 - flagsmith.networkConfig.httpShouldUsePipelining = true - flagsmith.networkConfig.httpShouldSetCookies = true - - // Add custom headers - flagsmith.networkConfig.httpAdditionalHeaders = [ - "X-Custom-Header": "Custom-Value", - "User-Agent": "MyApp/1.0" - ] } private func fetchFeatureFlags(_ flagsmith: Flagsmith) { - flagsmith.getFeatureFlags { result in - switch result { - case .success(let flags): - print("Successfully fetched \(flags.count) feature flags") - for flag in flags { - print("Flag: \(flag.feature.name) - Enabled: \(flag.enabled)") - } - case .failure(let error): - print("Failed to fetch feature flags: \(error)") - } + flagsmith.getFeatureFlags { _ in + // Handle result... } } @@ -64,66 +37,40 @@ class NetworkConfigExample { print("Testing with 100ms timeout...") - flagsmith.getFeatureFlags { result in - switch result { - case .success(let flags): - print("Unexpected success with short timeout: \(flags.count) flags") - case .failure(let error): - if let urlError = error as? URLError, urlError.code == .timedOut { - print("Expected timeout error occurred") - } else { - print("Unexpected error: \(error)") - } - } - } - } - - func demonstrateCustomHeaders() { - let flagsmith = Flagsmith.shared - flagsmith.apiKey = "your-api-key-here" - - // Add custom headers that will be sent with every request - flagsmith.networkConfig.httpAdditionalHeaders = [ - "X-Client-Version": "1.0.0", - "X-Platform": "iOS", - "X-Environment": "Production" - ] - - print("Configured custom headers for all requests") - - // These headers will now be included in all API requests - flagsmith.getFeatureFlags { result in + flagsmith.getFeatureFlags { _ in // Handle result... } } - func demonstrateCellularAccessControl() { + func demonstrateCustomTimeout() { let flagsmith = Flagsmith.shared flagsmith.apiKey = "your-api-key-here" - // Disable cellular access (WiFi only) - flagsmith.networkConfig.allowsCellularAccess = false + // Set a custom timeout for your application's needs + flagsmith.networkConfig.requestTimeout = 5.0 // 5 seconds - print("Configured to use WiFi only (no cellular)") + print("Configured 5-second timeout for all requests") - // This will only work on WiFi connections - flagsmith.getFeatureFlags { result in + // All API requests will now use the 5-second timeout + flagsmith.getFeatureFlags { _ in // Handle result... } } - func demonstrateConnectionLimits() { + func demonstrateTimeoutChanges() { let flagsmith = Flagsmith.shared flagsmith.apiKey = "your-api-key-here" - // Limit concurrent connections to the same host - flagsmith.networkConfig.httpMaximumConnectionsPerHost = 2 + // Start with default timeout + print("Using default timeout: \(flagsmith.networkConfig.requestTimeout) seconds") - print("Limited to 2 concurrent connections per host") + // Change timeout during runtime + flagsmith.networkConfig.requestTimeout = 2.0 + print("Changed timeout to: \(flagsmith.networkConfig.requestTimeout) seconds") - // This helps control resource usage - flagsmith.getFeatureFlags { result in + // All subsequent requests will use the new timeout + flagsmith.getFeatureFlags { _ in // Handle result... } } -} +} \ No newline at end of file diff --git a/FlagsmithClient/Classes/Internal/APIManager.swift b/FlagsmithClient/Classes/Internal/APIManager.swift index 59af2f5..e20fe4a 100644 --- a/FlagsmithClient/Classes/Internal/APIManager.swift +++ b/FlagsmithClient/Classes/Internal/APIManager.swift @@ -193,8 +193,10 @@ final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { let newConfig = createURLSessionConfiguration(networkConfig: networkConfig, cacheConfig: cacheConfig) let newSession = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main) - // Update session using the property setter to ensure thread-safe access + // Invalidate previous session before swapping to avoid resource buildup + let oldSession = self.session self.session = newSession + oldSession.invalidateAndCancel() let task = newSession.dataTask(with: request) tasksToCompletionHandlers[task.taskIdentifier] = completion diff --git a/FlagsmithClient/Classes/Internal/SSEManager.swift b/FlagsmithClient/Classes/Internal/SSEManager.swift index 1d6482d..08d4e0a 100644 --- a/FlagsmithClient/Classes/Internal/SSEManager.swift +++ b/FlagsmithClient/Classes/Internal/SSEManager.swift @@ -191,8 +191,10 @@ final class SSEManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { let newConfig = createURLSessionConfiguration(networkConfig: Flagsmith.shared.networkConfig) let newSession = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main) - // Update session using the property setter to ensure thread-safe access + // Invalidate previous session before swapping to avoid resource buildup + let oldSession = self.session self.session = newSession + oldSession.invalidateAndCancel() completionHandler = completion dataTask = newSession.dataTask(with: request) @@ -201,6 +203,7 @@ final class SSEManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { func stop() { dataTask?.cancel() + dataTask = nil completionHandler = nil } } diff --git a/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift b/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift index dda41b2..2b93e57 100644 --- a/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift +++ b/FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift @@ -1,4 +1,3 @@ - @testable import FlagsmithClient import XCTest @@ -10,22 +9,8 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { // Set custom network configuration flagsmith.networkConfig.requestTimeout = 1.0 - // Verify the configuration is set correctly - XCTAssertEqual(flagsmith.networkConfig.requestTimeout, 1.0) - } - - func testURLSessionConfigurationCreation() { - let flagsmith = Flagsmith.shared - - // Set custom network configuration - flagsmith.networkConfig.requestTimeout = 1.0 - - // Create a URLSessionConfiguration directly - let config = URLSessionConfiguration.default - config.timeoutIntervalForRequest = flagsmith.networkConfig.requestTimeout - // Verify the configuration is applied correctly - XCTAssertEqual(config.timeoutIntervalForRequest, 1.0, "Request timeout should be applied") + XCTAssertEqual(flagsmith.networkConfig.requestTimeout, 1.0, "Request timeout should be applied") } func testAPIManagerUsesNetworkConfig() { @@ -42,7 +27,7 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { // Trigger a request to apply the network configuration let expectation = XCTestExpectation(description: "Request to apply config") - apiManager.request(.getFlags) { result in + apiManager.request(.getFlags) { _ in // Check the session configuration inside the completion handler let sessionConfig = apiManager.session.configuration XCTAssertEqual(sessionConfig.timeoutIntervalForRequest, 1.0, "Request timeout should be applied") @@ -65,7 +50,7 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { // Trigger the start method to apply the network configuration // The start method calls the completion handler immediately, so we don't need to wait - sseManager.start { result in + sseManager.start { _ in // This will be called immediately when start() is called } @@ -86,7 +71,7 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { flagsmith.networkConfig.requestTimeout = 60.0 let initialExpectation = XCTestExpectation(description: "Initial request") - apiManager.request(.getFlags) { result in + apiManager.request(.getFlags) { _ in initialExpectation.fulfill() } wait(for: [initialExpectation], timeout: 1.0) @@ -100,7 +85,7 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { // Trigger a request to apply the new configuration let expectation = XCTestExpectation(description: "Request completion") - apiManager.request(.getFlags) { result in + apiManager.request(.getFlags) { _ in expectation.fulfill() } @@ -111,15 +96,26 @@ final class NetworkConfigIntegrationTests: FlagsmithClientTestCase { XCTAssertEqual(newSessionConfig.timeoutIntervalForRequest, 30.0, "New timeout should be applied") } + func testURLSessionConfigurationCreation() { + let flagsmith = Flagsmith.shared + flagsmith.networkConfig.requestTimeout = 1.0 + + // Create a new APIManager to test the configuration + let apiManager = APIManager() + + // Verify the configuration is applied correctly + let config = apiManager.session.configuration + XCTAssertEqual(config.timeoutIntervalForRequest, 1.0, "Request timeout should be applied") + } func testNetworkConfigPerformance() { let flagsmith = Flagsmith.shared // Measure time to create and configure multiple network configs measure { - for i in 0..<100 { - flagsmith.networkConfig.requestTimeout = Double(i) + for iteration in 0..<100 { + flagsmith.networkConfig.requestTimeout = Double(iteration) } } } -} +} \ No newline at end of file diff --git a/FlagsmithClient/Tests/NetworkConfigTests.swift b/FlagsmithClient/Tests/NetworkConfigTests.swift index 8ef80cf..8d7a822 100644 --- a/FlagsmithClient/Tests/NetworkConfigTests.swift +++ b/FlagsmithClient/Tests/NetworkConfigTests.swift @@ -1,4 +1,3 @@ - @testable import FlagsmithClient import XCTest @@ -42,10 +41,10 @@ final class NetworkConfigTests: FlagsmithClientTestCase { let initialTimeout = flagsmith.networkConfig.requestTimeout // Test concurrent access to network config - for i in 0..<10 { + for idx in 0..<10 { DispatchQueue.global().async { // Test that we can safely set values concurrently - flagsmith.networkConfig.requestTimeout = Double(i) + flagsmith.networkConfig.requestTimeout = Double(idx) expectation.fulfill() } } @@ -77,11 +76,11 @@ final class NetworkConfigTests: FlagsmithClientTestCase { XCTAssertEqual(networkConfig.requestTimeout, 3600.0) } - func testNetworkConfigEquality() { + func testNetworkConfigRequestTimeoutComparison() { let config1 = NetworkConfig() let config2 = NetworkConfig() - // Identical configs should be equal + // Identical configs should have equal requestTimeout XCTAssertEqual(config1.requestTimeout, config2.requestTimeout) // Modify one config @@ -92,4 +91,4 @@ final class NetworkConfigTests: FlagsmithClientTestCase { config1.requestTimeout = 60.0 XCTAssertEqual(config1.requestTimeout, config2.requestTimeout) } -} +} \ No newline at end of file