diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 12d4a456b8e..c6c5061e872 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -723,7 +723,7 @@ DEC51B02276AFB35009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */; }; DEFBA74E29485A7600C35BA9 /* RESTRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA74D29485A7600C35BA9 /* RESTRequest.swift */; }; DEFBA7542949CE6600C35BA9 /* RequestProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */; }; - DEFBA7562949D17400C35BA9 /* RequestAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7552949D17300C35BA9 /* RequestAuthenticatorTests.swift */; }; + DEFBA7562949D17400C35BA9 /* DefaultRequestAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7552949D17300C35BA9 /* DefaultRequestAuthenticatorTests.swift */; }; E12552C526385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12552C426385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift */; }; E137619929151C7400FD098F /* error-wp-rest-forbidden.json in Resources */ = {isa = PBXBuildFile; fileRef = E137619829151C7400FD098F /* error-wp-rest-forbidden.json */; }; E137619B2915222100FD098F /* WordPressApiValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E137619A2915222100FD098F /* WordPressApiValidatorTests.swift */; }; @@ -748,6 +748,7 @@ EE62EE65295AD46D009C965B /* String+URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62EE64295AD46D009C965B /* String+URLTests.swift */; }; EE71CC3D2951A8EA0074D908 /* ApplicationPasswordStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE71CC3C2951A8EA0074D908 /* ApplicationPasswordStorage.swift */; }; EE71CC412951CE700074D908 /* generate-application-password-using-wporg-creds-success.json in Resources */ = {isa = PBXBuildFile; fileRef = EE71CC402951CE700074D908 /* generate-application-password-using-wporg-creds-success.json */; }; + EE76762F2962B85E000066FA /* RequestProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE76762E2962B85E000066FA /* RequestProcessorTests.swift */; }; EE80A24729547F8B003591E4 /* coupons-all-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EE80A24529547F8B003591E4 /* coupons-all-without-data.json */; }; EE80A24829547F8B003591E4 /* coupon-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EE80A24629547F8B003591E4 /* coupon-without-data.json */; }; EE80A25029556FBD003591E4 /* coupon-reports-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = EE80A24F29556FBD003591E4 /* coupon-reports-without-data.json */; }; @@ -756,11 +757,11 @@ EE99814E295AA7430074AE68 /* RequestAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99814D295AA7430074AE68 /* RequestAuthenticator.swift */; }; EE998150295AACE10074AE68 /* RequestConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99814F295AACE10074AE68 /* RequestConverter.swift */; }; EECB7EE8286555180028C888 /* media-update-product-id.json in Resources */ = {isa = PBXBuildFile; fileRef = EECB7EE7286555180028C888 /* media-update-product-id.json */; }; + EEFAA579295D2FC7003583BE /* RESTRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFAA578295D2FC7003583BE /* RESTRequestTests.swift */; }; EEFAA57B295D7793003583BE /* AuthenticatedDotcomRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFAA57A295D7793003583BE /* AuthenticatedDotcomRequest.swift */; }; EEFAA57D295D77F0003583BE /* AuthenticatedRESTRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFAA57C295D77F0003583BE /* AuthenticatedRESTRequest.swift */; }; EEFAA57F295D78DF003583BE /* AuthenticatedDotcomRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFAA57E295D78DF003583BE /* AuthenticatedDotcomRequestTests.swift */; }; EEFAA581295D78E9003583BE /* AuthenticatedRESTRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFAA580295D78E9003583BE /* AuthenticatedRESTRequestTests.swift */; }; - EEFAA579295D2FC7003583BE /* RESTRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFAA578295D2FC7003583BE /* RESTRequestTests.swift */; }; FE28F6E226840DED004465C7 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE28F6E126840DED004465C7 /* User.swift */; }; FE28F6E426842848004465C7 /* UserMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE28F6E326842848004465C7 /* UserMapper.swift */; }; FE28F6E6268429B6004465C7 /* UserRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE28F6E5268429B6004465C7 /* UserRemote.swift */; }; @@ -1514,7 +1515,7 @@ DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SystemStatus+DropinMustUsePlugin.swift"; sourceTree = ""; }; DEFBA74D29485A7600C35BA9 /* RESTRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequest.swift; sourceTree = ""; }; DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessor.swift; sourceTree = ""; }; - DEFBA7552949D17300C35BA9 /* RequestAuthenticatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAuthenticatorTests.swift; sourceTree = ""; }; + DEFBA7552949D17300C35BA9 /* DefaultRequestAuthenticatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultRequestAuthenticatorTests.swift; sourceTree = ""; }; E12552C426385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelAddressValidationSuccess.swift; sourceTree = ""; }; E137619829151C7400FD098F /* error-wp-rest-forbidden.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "error-wp-rest-forbidden.json"; sourceTree = ""; }; E137619A2915222100FD098F /* WordPressApiValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressApiValidatorTests.swift; sourceTree = ""; }; @@ -1539,6 +1540,7 @@ EE62EE64295AD46D009C965B /* String+URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+URLTests.swift"; sourceTree = ""; }; EE71CC3C2951A8EA0074D908 /* ApplicationPasswordStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordStorage.swift; sourceTree = ""; }; EE71CC402951CE700074D908 /* generate-application-password-using-wporg-creds-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "generate-application-password-using-wporg-creds-success.json"; sourceTree = ""; }; + EE76762E2962B85E000066FA /* RequestProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessorTests.swift; sourceTree = ""; }; EE80A24529547F8B003591E4 /* coupons-all-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupons-all-without-data.json"; sourceTree = ""; }; EE80A24629547F8B003591E4 /* coupon-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupon-without-data.json"; sourceTree = ""; }; EE80A24F29556FBD003591E4 /* coupon-reports-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "coupon-reports-without-data.json"; sourceTree = ""; }; @@ -1547,11 +1549,11 @@ EE99814D295AA7430074AE68 /* RequestAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAuthenticator.swift; sourceTree = ""; }; EE99814F295AACE10074AE68 /* RequestConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestConverter.swift; sourceTree = ""; }; EECB7EE7286555180028C888 /* media-update-product-id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-update-product-id.json"; sourceTree = ""; }; + EEFAA578295D2FC7003583BE /* RESTRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequestTests.swift; sourceTree = ""; }; EEFAA57A295D7793003583BE /* AuthenticatedDotcomRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedDotcomRequest.swift; sourceTree = ""; }; EEFAA57C295D77F0003583BE /* AuthenticatedRESTRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedRESTRequest.swift; sourceTree = ""; }; EEFAA57E295D78DF003583BE /* AuthenticatedDotcomRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedDotcomRequestTests.swift; sourceTree = ""; }; EEFAA580295D78E9003583BE /* AuthenticatedRESTRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedRESTRequestTests.swift; sourceTree = ""; }; - EEFAA578295D2FC7003583BE /* RESTRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequestTests.swift; sourceTree = ""; }; F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Networking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F6CEE1CA2AD376C0C28AE9F6 /* Pods-NetworkingTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NetworkingTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-NetworkingTests/Pods-NetworkingTests.release.xcconfig"; sourceTree = ""; }; FE28F6E126840DED004465C7 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; @@ -2455,7 +2457,7 @@ isa = PBXGroup; children = ( B57B1E6621C916850046E764 /* NetworkErrorTests.swift */, - DEFBA7552949D17300C35BA9 /* RequestAuthenticatorTests.swift */, + DEFBA7552949D17300C35BA9 /* DefaultRequestAuthenticatorTests.swift */, EE62EE60295ACF8D009C965B /* RequestConverterTests.swift */, ); path = Network; @@ -2717,6 +2719,7 @@ isa = PBXGroup; children = ( EE8DE431294B17CD005054E7 /* DefaultApplicationPasswordUseCaseTests.swift */, + EE76762E2962B85E000066FA /* RequestProcessorTests.swift */, ); path = ApplicationPassword; sourceTree = ""; @@ -3603,6 +3606,7 @@ D800DA0A25EFEB9C001E13CE /* WCPayRemoteTests.swift in Sources */, E13BAD5328F8625600217769 /* InAppPurchasesRemoteTests.swift in Sources */, CC851D1425E52AB500249E9C /* Decimal+ExtensionsTests.swift in Sources */, + EE76762F2962B85E000066FA /* RequestProcessorTests.swift in Sources */, B554FA8B2180B1D500C54DFF /* NotificationsRemoteTests.swift in Sources */, B518662A20A09C6F00037A38 /* OrdersRemoteTests.swift in Sources */, 02EF166E292F0C5800D90AD6 /* PaymentRemoteTests.swift in Sources */, @@ -3637,7 +3641,7 @@ 0212683524C046CB00F8A892 /* MockNetwork+Path.swift in Sources */, 68BD37B328D9B8BD00C2A517 /* CustomerRemoteTests.swift in Sources */, B554FA932180C17200C54DFF /* NoteHashListMapperTests.swift in Sources */, - DEFBA7562949D17400C35BA9 /* RequestAuthenticatorTests.swift in Sources */, + DEFBA7562949D17400C35BA9 /* DefaultRequestAuthenticatorTests.swift in Sources */, CC07866526790B1100BA9AC1 /* ShippingLabelPurchaseMapperTests.swift in Sources */, 74002D6A2118B26100A63C19 /* SiteVisitStatsMapperTests.swift in Sources */, 743E84FA221742E300FAC9D7 /* ShipmentsRemoteTests.swift in Sources */, diff --git a/Networking/Networking/ApplicationPassword/RequestAuthenticator.swift b/Networking/Networking/ApplicationPassword/RequestAuthenticator.swift index 701ac01e088..2de5123fe7d 100644 --- a/Networking/Networking/ApplicationPassword/RequestAuthenticator.swift +++ b/Networking/Networking/ApplicationPassword/RequestAuthenticator.swift @@ -3,10 +3,31 @@ enum RequestAuthenticatorError: Error { case applicationPasswordNotAvailable } +protocol RequestAuthenticator { + /// Credentials to authenticate the URLRequest + /// + var credentials: Credentials? { get } + + /// Authenticates the provided urlRequest using the `credentials` + /// + /// - Parameter urlRequest: `URLRequest` to authenticate + /// - Returns: Authenticated `URLRequest` + /// + func authenticate(_ urlRequest: URLRequest) throws -> URLRequest + + /// Generates application password + /// + func generateApplicationPassword() async throws + + /// Checks whether the given URLRequest is eligible for retyring + /// + func shouldRetry(_ urlRequest: URLRequest) -> Bool +} + /// Authenticates request /// -public struct RequestAuthenticator { - /// Credentials. +public struct DefaultRequestAuthenticator: RequestAuthenticator { + /// Credentials to authenticate the URLRequest /// let credentials: Credentials? @@ -33,6 +54,11 @@ public struct RequestAuthenticator { self.applicationPasswordUseCase = useCase } + /// Authenticates the provided urlRequest using the `credentials` + /// + /// - Parameter urlRequest: `URLRequest` to authenticate + /// - Returns: Authenticated `URLRequest` + /// func authenticate(_ urlRequest: URLRequest) throws -> URLRequest { if isRestAPIRequest(urlRequest) { return try authenticateUsingApplicationPasswordIfPossible(urlRequest) @@ -41,6 +67,8 @@ public struct RequestAuthenticator { } } + /// Generates application password + /// func generateApplicationPassword() async throws { guard let applicationPasswordUseCase else { throw RequestAuthenticatorError.applicationPasswordUseCaseNotAvailable @@ -56,7 +84,7 @@ public struct RequestAuthenticator { } } -private extension RequestAuthenticator { +private extension DefaultRequestAuthenticator { /// To check whether the given URLRequest is a REST API request /// func isRestAPIRequest(_ urlRequest: URLRequest) -> Bool { diff --git a/Networking/Networking/ApplicationPassword/RequestProcessor.swift b/Networking/Networking/ApplicationPassword/RequestProcessor.swift index 83364e49311..26360f141e4 100644 --- a/Networking/Networking/ApplicationPassword/RequestProcessor.swift +++ b/Networking/Networking/ApplicationPassword/RequestProcessor.swift @@ -10,8 +10,8 @@ final class RequestProcessor { private let requestAuthenticator: RequestAuthenticator - init(credentials: Credentials?) { - requestAuthenticator = RequestAuthenticator(credentials: credentials) + init(requestAuthenticator: RequestAuthenticator) { + self.requestAuthenticator = requestAuthenticator } } diff --git a/Networking/Networking/Network/AlamofireNetwork.swift b/Networking/Networking/Network/AlamofireNetwork.swift index 81cad9c5448..4cd0f30575e 100644 --- a/Networking/Networking/Network/AlamofireNetwork.swift +++ b/Networking/Networking/Network/AlamofireNetwork.swift @@ -36,7 +36,7 @@ public class AlamofireNetwork: Network { /// public required init(credentials: Credentials?) { self.requestConverter = RequestConverter(credentials: credentials) - self.requestAuthenticator = RequestProcessor(credentials: credentials) + self.requestAuthenticator = RequestProcessor(requestAuthenticator: DefaultRequestAuthenticator(credentials: credentials)) } /// Executes the specified Network Request. Upon completion, the payload will be sent back to the caller as a Data instance. diff --git a/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift new file mode 100644 index 00000000000..affa03ea631 --- /dev/null +++ b/Networking/NetworkingTests/ApplicationPassword/RequestProcessorTests.swift @@ -0,0 +1,244 @@ +import XCTest +@testable import Networking +@testable import Alamofire + +/// RequestProcessor Unit Tests +/// +final class RequestProcessorTests: XCTestCase { + private var mockRequestAuthenticator: MockRequestAuthenticator! + private var sut: RequestProcessor! + private var sessionManager: Alamofire.SessionManager! + + private let url = URL(string: "https://test.com/")! + + override func setUp() { + super.setUp() + + sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + mockRequestAuthenticator = MockRequestAuthenticator() + sut = RequestProcessor(requestAuthenticator: mockRequestAuthenticator) + } + + override func tearDown() { + sut = nil + mockRequestAuthenticator = nil + sessionManager = nil + + super.tearDown() + } + + // MARK: Request Authentication + // + func test_adapt_authenticates_the_urlrequest() throws { + // Given + let urlRequest = URLRequest(url: url) + + // When + let _ = try sut.adapt(urlRequest) + + // Then + XCTAssertTrue(mockRequestAuthenticator.authenticateCalled) + } + + // MARK: Retry count + // + func test_request_with_zero_retryCount_is_scheduled_for_retry() throws { + // Given + let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + let request = try mockRequest() + + // When + request.retryCount = 0 + let shouldRetry = waitFor { promise in + self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + promise(shouldRetry) + } + } + + // Then + XCTAssertTrue(shouldRetry) + } + + func test_request_with_non_zero_retryCount_is_not_scheduled_for_retry() throws { + // Given + let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + let request = try mockRequest() + + // When + request.retryCount = 1 + let shouldRetry = waitFor { promise in + self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + promise(shouldRetry) + } + } + + // Then + XCTAssertFalse(shouldRetry) + } + + // MARK: `shouldRetry` from RequestAuthenticator + // + func test_request_is_scheduled_for_retry_when_request_authenticator_shouldRetry_returns_true() throws { + // Given + let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + let request = try mockRequest() + + // When + mockRequestAuthenticator.mockedShouldRetryValue = true + let shouldRetry = waitFor { promise in + self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + promise(shouldRetry) + } + } + + // Then + XCTAssertTrue(shouldRetry) + } + + func test_request_is_not_scheduled_for_retry_when_request_authenticator_shouldRetry_returns_false() throws { + // Given + let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + let request = try mockRequest() + + // When + mockRequestAuthenticator.mockedShouldRetryValue = false + let shouldRetry = waitFor { promise in + self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + promise(shouldRetry) + } + } + + // Then + XCTAssertFalse(shouldRetry) + } + + // MARK: Error type + // + func test_request_is_scheduled_for_retry_when_applicationPasswordNotAvailable_error_occurs() throws { + // Given + let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + let request = try mockRequest() + + // When + let error = RequestAuthenticatorError.applicationPasswordNotAvailable + let shouldRetry = waitFor { promise in + self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in + promise(shouldRetry) + } + } + + // Then + XCTAssertTrue(shouldRetry) + } + + func test_request_is_scheduled_for_retry_when_401_error_occurs() throws { + // Given + let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + let request = try mockRequest() + + // When + let error = AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: 401)) + let shouldRetry = waitFor { promise in + self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in + promise(shouldRetry) + } + } + + // Then + XCTAssertTrue(shouldRetry) + } + + func test_request_is_not_scheduled_for_retry_when_irrelavant_error_occurs() throws { + // Given + let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + let request = try mockRequest() + + // When + let error = AFError.invalidURL(url: url) + let shouldRetry = waitFor { promise in + self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in + promise(shouldRetry) + } + } + + // Then + XCTAssertFalse(shouldRetry) + } + + // MARK: Generate application password + // + func test_application_password_is_generated_upon_retrying_a_request() throws { + // Given + let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + let request = try mockRequest() + + // When + let error = RequestAuthenticatorError.applicationPasswordNotAvailable + waitFor { promise in + self.sut.should(sessionManager, retry: request, with: error) { shouldRetry, timeDelay in + promise(()) + } + } + + // Then + XCTAssertTrue(mockRequestAuthenticator.generateApplicationPasswordCalled) + } + + func test_application_password_is_not_generated_when_a_request_is_not_eligible_for_retry() throws { + // Given + let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) + let request = try mockRequest() + + // When + mockRequestAuthenticator.mockedShouldRetryValue = false + waitFor { promise in + self.sut.should(sessionManager, retry: request, with: RequestAuthenticatorError.applicationPasswordNotAvailable) { shouldRetry, timeDelay in + promise(()) + } + } + + // Then + XCTAssertFalse(mockRequestAuthenticator.generateApplicationPasswordCalled) + } +} + +// MARK: Helpers +// +private extension RequestProcessorTests { + func mockRequest() throws -> Alamofire.Request { + let originalTask = MockTaskConvertible() + let task = try originalTask.task(session: sessionManager.session, adapter: nil, queue: .main) + return Alamofire.Request(session: sessionManager.session, requestTask: .data(originalTask, task)) + } +} + + +private class MockTaskConvertible: TaskConvertible { + let urlRequest = URLRequest(url: URL(string: "https://test.com/")!) + + func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask { + session.dataTask(with: urlRequest) + } +} + +private class MockRequestAuthenticator: RequestAuthenticator { + var mockedShouldRetryValue: Bool? + + private(set) var authenticateCalled = false + private(set) var generateApplicationPasswordCalled = false + + var credentials: Networking.Credentials? = nil + + func authenticate(_ urlRequest: URLRequest) throws -> URLRequest { + authenticateCalled = true + return urlRequest + } + + func generateApplicationPassword() async throws { + generateApplicationPasswordCalled = true + } + + func shouldRetry(_ urlRequest: URLRequest) -> Bool { + mockedShouldRetryValue ?? true + } +} diff --git a/Networking/NetworkingTests/Network/RequestAuthenticatorTests.swift b/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift similarity index 90% rename from Networking/NetworkingTests/Network/RequestAuthenticatorTests.swift rename to Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift index 7f5588e69a6..f2de80abc8c 100644 --- a/Networking/NetworkingTests/Network/RequestAuthenticatorTests.swift +++ b/Networking/NetworkingTests/Network/DefaultRequestAuthenticatorTests.swift @@ -2,11 +2,11 @@ import XCTest import Alamofire @testable import Networking -final class RequestAuthenticatorTests: XCTestCase { +final class DefaultRequestAuthenticatorTests: XCTestCase { func test_authenticateRequest_returns_unauthenticated_request_for_non_REST_request_without_WPCOM_credentials() throws { // Given - let authenticator = RequestAuthenticator(credentials: nil) + let authenticator = DefaultRequestAuthenticator(credentials: nil) let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) // When @@ -20,7 +20,7 @@ final class RequestAuthenticatorTests: XCTestCase { func test_authenticatedRequest_returns_authenticated_request_for_non_REST_request_with_WPCOM_credentials() throws { // Given let credentials = Credentials(authToken: "secret") - let authenticator = RequestAuthenticator(credentials: credentials) + let authenticator = DefaultRequestAuthenticator(credentials: credentials) let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) // When @@ -38,7 +38,7 @@ final class RequestAuthenticatorTests: XCTestCase { let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let applicationPassword = ApplicationPassword(wpOrgUsername: credentials.username, password: .init(credentials.secret)) let useCase = MockApplicationPasswordUseCase(mockApplicationPassword: applicationPassword) - let authenticator = RequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let basePath = RESTRequest.Settings.basePath let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -60,7 +60,7 @@ final class RequestAuthenticatorTests: XCTestCase { let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let applicationPassword = ApplicationPassword(wpOrgUsername: credentials.username, password: .init(credentials.secret)) let useCase = MockApplicationPasswordUseCase(mockGeneratedPassword: applicationPassword) - let authenticator = RequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let basePath = RESTRequest.Settings.basePath let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -88,7 +88,7 @@ final class RequestAuthenticatorTests: XCTestCase { let siteURL = "https://test.com/" let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let useCase = MockApplicationPasswordUseCase(mockGenerationError: NetworkError.timeout) - let authenticator = RequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -115,7 +115,7 @@ final class RequestAuthenticatorTests: XCTestCase { let siteURL = "https://test.com/" let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let useCase = MockApplicationPasswordUseCase(mockGenerationError: NetworkError.timeout) - let authenticator = RequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let wooAPIVersion = WooAPIVersion.mark1 let restRequest = RESTRequest(siteURL: siteURL, wooApiVersion: wooAPIVersion, method: .get, path: "test") @@ -131,7 +131,7 @@ final class RequestAuthenticatorTests: XCTestCase { let siteURL = "https://test.com/" let credentials: Credentials = .wporg(username: "admin", password: "supersecret", siteAddress: siteURL) let useCase = MockApplicationPasswordUseCase(mockGenerationError: NetworkError.timeout) - let authenticator = RequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) + let authenticator = DefaultRequestAuthenticator(credentials: credentials, applicationPasswordUseCase: useCase) let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) // When