diff --git a/Sources/Networking/RemoteConfigBlobDownloader.swift b/Sources/Networking/RemoteConfigBlobDownloader.swift index e2bd42fafa..34a5e835d1 100644 --- a/Sources/Networking/RemoteConfigBlobDownloader.swift +++ b/Sources/Networking/RemoteConfigBlobDownloader.swift @@ -24,12 +24,23 @@ final class URLSessionRemoteConfigBlobDownloader: RemoteConfigBlobDownloaderType case unexpectedStatusCode(Int) } + static let timeout: TimeInterval = 5 + private let session: URLSession - init(session: URLSession = .shared) { + init(session: URLSession = URLSession( + configuration: URLSessionRemoteConfigBlobDownloader.defaultSessionConfiguration() + )) { self.session = session } + static func defaultSessionConfiguration() -> URLSessionConfiguration { + let configuration = URLSessionConfiguration.default + configuration.timeoutIntervalForRequest = Self.timeout + configuration.timeoutIntervalForResource = Self.timeout + return configuration + } + func data(from url: URL) async throws -> Data { return try await withCheckedThrowingContinuation { continuation in let task = self.session.dataTask(with: url) { data, response, error in diff --git a/Tests/UnitTests/Networking/RemoteConfig/RemoteConfigBlobFetcherTests.swift b/Tests/UnitTests/Networking/RemoteConfig/RemoteConfigBlobFetcherTests.swift index a8b7bfa404..b741a7a1d0 100644 --- a/Tests/UnitTests/Networking/RemoteConfig/RemoteConfigBlobFetcherTests.swift +++ b/Tests/UnitTests/Networking/RemoteConfig/RemoteConfigBlobFetcherTests.swift @@ -7,6 +7,8 @@ import Foundation import Nimble +import OHHTTPStubs +import OHHTTPStubsSwift @testable import RevenueCat import XCTest @@ -31,6 +33,7 @@ final class RemoteConfigBlobFetcherTests: TestCase { } override func tearDownWithError() throws { + HTTPStubs.removeAllStubs() self.downloader.cancelAll() self.fetcher = nil self.sourceProvider = nil @@ -217,6 +220,39 @@ final class RemoteConfigBlobFetcherTests: TestCase { expect(self.blobStore.invokedWriteParameters?.data) == payload } + func testRealDownloaderUsesFiveSecondTimeoutAndRetriesNextSourceAfterTimeout() async { + let payload = "timeout fallback payload".asData + let ref = Self.ref(for: payload) + let primaryURL = Self.templateURL.replacingOccurrences(of: Self.placeholder, with: ref) + let backupURL = Self.backupTemplateURL.replacingOccurrences(of: Self.placeholder, with: ref) + + stub(condition: isAbsoluteURLString(primaryURL)) { _ in + return HTTPStubsResponse(error: URLError(.timedOut)) + } + stub(condition: isAbsoluteURLString(backupURL)) { _ in + return HTTPStubsResponse( + data: payload, + statusCode: Int32(HTTPStatusCode.success.rawValue), + headers: nil + ) + } + + self.sourceProvider = Self.sourceProvider(urls: [ + Self.templateURL, + Self.backupTemplateURL + ]) + self.fetcher = RemoteConfigBlobFetcher( + blobStore: self.blobStore, + sourceProvider: self.sourceProvider, + downloader: URLSessionRemoteConfigBlobDownloader() + ) + + let result = await self.fetcher.ensureDownloaded(ref: ref) + + expect(result) == true + expect(self.blobStore.invokedWriteParameters?.data) == payload + } + func testNotFoundDoesNotMarkSourceUnhealthyOrRetryNextSource() async { let missingRef = Self.ref(for: "missing blob".asData)