diff --git a/Modules/Sources/NetworkingCore/ApplicationPassword/ApplicationPasswordUseCase.swift b/Modules/Sources/NetworkingCore/ApplicationPassword/ApplicationPasswordUseCase.swift index 1c52a83e42c..7c250e1e6b8 100644 --- a/Modules/Sources/NetworkingCore/ApplicationPassword/ApplicationPasswordUseCase.swift +++ b/Modules/Sources/NetworkingCore/ApplicationPassword/ApplicationPasswordUseCase.swift @@ -39,17 +39,6 @@ final public class DefaultApplicationPasswordUseCase: ApplicationPasswordUseCase /// private let authenticationType: AuthenticationType - /// WPOrg username - /// - private var username: String { - switch authenticationType { - case .wporg(let username, _, _): - return username - case .wpcom(let wporgUsername, _): - return wporgUsername - } - } - /// To generate and delete application password /// private let network: Network @@ -165,7 +154,7 @@ private extension DefaultApplicationPasswordUseCase { /// or through Jetpack proxy. func constructRequest(method: HTTPMethod, path: String, parameters: [String: Any]? = nil) -> Request { switch authenticationType { - case .wpcom(_, let siteID): + case .wpcom(let siteID): JetpackRequest(wooApiVersion: .none, method: method, siteID: siteID, @@ -190,13 +179,21 @@ private extension DefaultApplicationPasswordUseCase { let request = constructRequest(method: .post, path: Path.applicationPasswords, parameters: parameters) + let wpOrgUsername = try await { + switch authenticationType { + case .wporg(let username, _, _): + return username + case .wpcom(let siteID): + return try await fetchWPOrgUsername(siteID: siteID) + } + }() + return try await withCheckedThrowingContinuation { continuation in - network.responseData(for: request) { [weak self] result in - guard let self else { return } + network.responseData(for: request) { result in switch result { case .success(let data): do { - let mapper = ApplicationPasswordMapper(wpOrgUsername: self.username) + let mapper = ApplicationPasswordMapper(wpOrgUsername: wpOrgUsername) let password = try mapper.map(response: data) continuation.resume(returning: password) } catch { @@ -237,7 +234,7 @@ private extension DefaultApplicationPasswordUseCase { do { let mapper = ApplicationPasswordNameAndUUIDMapper() let list = try mapper.map(response: data) - if let item = list.first(where: { $0.name == passwordName }) { + if let item = list.last(where: { $0.name == passwordName }) { continuation.resume(returning: item.uuid) } else { continuation.resume(throwing: ApplicationPasswordUseCaseError.unableToFindPasswordUUID) @@ -268,12 +265,39 @@ private extension DefaultApplicationPasswordUseCase { } } } + + func fetchWPOrgUsername(siteID: Int64) async throws -> String { + let parameters = [ + ParameterKey.context: Constants.editValue + ] + let request = JetpackRequest(wooApiVersion: .none, + method: .get, + siteID: siteID, + path: Path.userDetails, + parameters: parameters) + return try await withCheckedThrowingContinuation { continuation in + network.responseData(for: request) { result in + switch result { + case .success(let data): + let mapper = UserMapper(siteID: siteID) + do { + let user = try mapper.map(response: data) + continuation.resume(returning: user.username) + } catch { + continuation.resume(throwing: error) + } + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } } extension DefaultApplicationPasswordUseCase { enum AuthenticationType { case wporg(username: String, password: String, siteAddress: String) - case wpcom(wporgUsername: String, siteID: Int64) + case wpcom(siteID: Int64) } } @@ -282,10 +306,12 @@ extension DefaultApplicationPasswordUseCase { private extension DefaultApplicationPasswordUseCase { enum Path { static let applicationPasswords = "wp/v2/users/me/application-passwords" + static let userDetails = "wp/v2/users/me" } enum ParameterKey { static let name = "name" + static let context = "context" } enum ErrorCode { @@ -298,5 +324,6 @@ private extension DefaultApplicationPasswordUseCase { enum Constants { static let loginPath = "/wp-login.php" static let adminPath = "/wp-admin/" + static let editValue = "edit" } } diff --git a/Modules/Sources/Networking/Mapper/UserMapper.swift b/Modules/Sources/NetworkingCore/Mapper/UserMapper.swift similarity index 100% rename from Modules/Sources/Networking/Mapper/UserMapper.swift rename to Modules/Sources/NetworkingCore/Mapper/UserMapper.swift diff --git a/Modules/Sources/Networking/Model/User.swift b/Modules/Sources/NetworkingCore/Model/User.swift similarity index 100% rename from Modules/Sources/Networking/Model/User.swift rename to Modules/Sources/NetworkingCore/Model/User.swift diff --git a/Modules/Sources/Networking/Remote/UserRemote.swift b/Modules/Sources/NetworkingCore/Remote/UserRemote.swift similarity index 100% rename from Modules/Sources/Networking/Remote/UserRemote.swift rename to Modules/Sources/NetworkingCore/Remote/UserRemote.swift diff --git a/Modules/Sources/Yosemite/Model/Mocks/MockSessionManager.swift b/Modules/Sources/Yosemite/Model/Mocks/MockSessionManager.swift index 833e34fd7bc..2f38060926a 100644 --- a/Modules/Sources/Yosemite/Model/Mocks/MockSessionManager.swift +++ b/Modules/Sources/Yosemite/Model/Mocks/MockSessionManager.swift @@ -31,6 +31,7 @@ public struct MockSessionManager: SessionManagerProtocol { public var defaultStoreURL: String? + /// periphery: ignore public var defaultRoles: [User.Role] = [] public var defaultStoreIDPublisher: AnyPublisher diff --git a/Modules/Tests/NetworkingTests/ApplicationPassword/DefaultApplicationPasswordUseCaseTests.swift b/Modules/Tests/NetworkingTests/ApplicationPassword/DefaultApplicationPasswordUseCaseTests.swift index 950186abd9d..f270d44181d 100644 --- a/Modules/Tests/NetworkingTests/ApplicationPassword/DefaultApplicationPasswordUseCaseTests.swift +++ b/Modules/Tests/NetworkingTests/ApplicationPassword/DefaultApplicationPasswordUseCaseTests.swift @@ -14,6 +14,7 @@ final class DefaultApplicationPasswordUseCaseTests: XCTestCase { /// private enum URLSuffix { static let generateApplicationPassword = "users/me/application-passwords" + static let userDetails = "wp/v2/users/me" } override func setUp() { @@ -95,23 +96,23 @@ final class DefaultApplicationPasswordUseCaseTests: XCTestCase { // Given network.simulateResponse(requestUrlSuffix: URLSuffix.generateApplicationPassword, filename: "generate-application-password-using-wporg-creds-success") - let wporgUsername = "username" - let sut = DefaultApplicationPasswordUseCase(type: .wpcom(wporgUsername: wporgUsername, siteID: 123), network: network) + network.simulateResponse(requestUrlSuffix: URLSuffix.userDetails, filename: "user-complete") + let sut = DefaultApplicationPasswordUseCase(type: .wpcom(siteID: 123), network: network) // When let password = try await sut.generateNewPassword() // Then XCTAssertEqual(password.password.secretValue, "passwordvalue") - XCTAssertEqual(password.wpOrgUsername, wporgUsername) + XCTAssertEqual(password.wpOrgUsername, "test-username") } func test_applicationPasswordsDisabled_error_is_thrown_if_generating_password_fails_with_501_error_when_authenticated_with_wpcom() async throws { // Given let error = AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: 501)) network.simulateError(requestUrlSuffix: URLSuffix.generateApplicationPassword, error: error) - let wporgUsername = "username" - let sut = DefaultApplicationPasswordUseCase(type: .wpcom(wporgUsername: wporgUsername, siteID: 123), network: network) + network.simulateResponse(requestUrlSuffix: URLSuffix.userDetails, filename: "user-complete") + let sut = DefaultApplicationPasswordUseCase(type: .wpcom(siteID: 123), network: network) // When var failure: ApplicationPasswordUseCaseError? @@ -129,8 +130,8 @@ final class DefaultApplicationPasswordUseCaseTests: XCTestCase { // Given let error = AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: 401)) network.simulateError(requestUrlSuffix: URLSuffix.generateApplicationPassword, error: error) - let wporgUsername = "username" - let sut = DefaultApplicationPasswordUseCase(type: .wpcom(wporgUsername: wporgUsername, siteID: 123), network: network) + network.simulateResponse(requestUrlSuffix: URLSuffix.userDetails, filename: "user-complete") + let sut = DefaultApplicationPasswordUseCase(type: .wpcom(siteID: 123), network: network) // When var failure: ApplicationPasswordUseCaseError?