Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Modules/Sources/Yosemite/Actions/JetpackConnectionAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ public enum JetpackConnectionAction: Action {
case fetchJetpackConnectionURL(completion: (Result<URL, Error>) -> Void)
/// Fetches connection state with the given site's Jetpack.
case fetchJetpackConnectionData(completion: (Result<JetpackConnectionData, Error>) -> Void)
/// Establishes site-level connection and returns WordPress.com blog ID.
case registerSite(completion: (Result<Int64, Error>) -> Void)
/// Provisions connection and returns provision response with scope and secret.
case provisionConnection(completion: (Result<JetpackConnectionProvisionResponse, Error>) -> Void)
/// Finalizes the Jetpack connection by sending a request to WPCom.
case finalizeConnection(siteID: Int64,
siteURL: String,
provisionResponse: JetpackConnectionProvisionResponse,
network: Network,
completion: (Result<Void, Error>) -> Void)
/// Fetches the WPCom account with the given network
case loadWPComAccount(network: Network, onCompletion: (Account?) -> Void)
}
1 change: 1 addition & 0 deletions Modules/Sources/Yosemite/Model/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public typealias GoogleAdsCampaignStatsItem = Networking.GoogleAdsCampaignStatsI
public typealias InboxNote = Networking.InboxNote
public typealias InboxAction = Networking.InboxAction
public typealias JetpackConnectionData = Networking.JetpackConnectionData
public typealias JetpackConnectionProvisionResponse = Networking.JetpackConnectionProvisionResponse
public typealias JustInTimeMessageHook = Networking.JustInTimeMessagesRemote.MessagePath.Hook
public typealias Media = Networking.Media
public typealias MetaContainer = Networking.MetaContainer
Expand Down
52 changes: 52 additions & 0 deletions Modules/Sources/Yosemite/Stores/JetpackConnectionStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ public final class JetpackConnectionStore: DeauthenticatedStore {
private var jetpackConnectionRemote: JetpackConnectionRemote?
private var accountRemote: AccountRemote?

/// periphery: ignore - kept with strong reference to keep network requests alive.
private var siteRemote: SiteRemote?

public override init(dispatcher: Dispatcher) {
super.init(dispatcher: dispatcher)
}
Expand Down Expand Up @@ -42,6 +45,12 @@ public final class JetpackConnectionStore: DeauthenticatedStore {
fetchJetpackConnectionURL(completion: completion)
case .fetchJetpackConnectionData(let completion):
fetchJetpackConnectionData(completion: completion)
case .registerSite(let completion):
registerSite(completion: completion)
case .provisionConnection(let completion):
provisionConnection(completion: completion)
case .finalizeConnection(let siteID, let siteURL, let provisionResponse, let network, let completion):
finalizeConnection(siteID: siteID, siteURL: siteURL, provisionResponse: provisionResponse, network: network, completion: completion)
case .loadWPComAccount(let network, let onCompletion):
loadWPComAccount(network: network, onCompletion: onCompletion)
}
Expand Down Expand Up @@ -87,6 +96,49 @@ private extension JetpackConnectionStore {
jetpackConnectionRemote?.fetchJetpackConnectionData(completion: completion)
}

func registerSite(completion: @escaping (Result<Int64, Error>) -> Void) {
guard let jetpackConnectionRemote else { return }
Task { @MainActor in
do {
let blogID = try await jetpackConnectionRemote.registerSite()
completion(.success(blogID))
} catch {
completion(.failure(error))
}
}
}

func provisionConnection(completion: @escaping (Result<JetpackConnectionProvisionResponse, Error>) -> Void) {
guard let jetpackConnectionRemote else { return }
Task { @MainActor in
do {
let response = try await jetpackConnectionRemote.provisionConnection()
completion(.success(response))
} catch {
completion(.failure(error))
}
}
}

func finalizeConnection(siteID: Int64,
siteURL: String,
provisionResponse: JetpackConnectionProvisionResponse,
network: Network,
completion: @escaping (Result<Void, Error>) -> Void) {
/// Intentionally leaving `dotcomClientID` and `dotcomClientSecret` empty
/// as these are not needed for the `finalizeJetpackConnection` method we're using here.
let remote = SiteRemote(network: network, dotcomClientID: "", dotcomClientSecret: "")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this be populated by ApiCredentials values in a following PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IDs are not necessary for using the createSite method. I added a comment in 3c37259 to clarify this.

Task { @MainActor in
do {
try await remote.finalizeJetpackConnection(siteID: siteID, siteURL: siteURL, provisionResponse: provisionResponse)
completion(.success(()))
} catch {
completion(.failure(error))
}
}
self.siteRemote = remote
}

func loadWPComAccount(network: Network, onCompletion: @escaping (Account?) -> Void) {
let remote = AccountRemote(network: network)
remote.loadAccount { result in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ final class MockSiteRemote {
func whenUpdatingSiteTitle(thenReturn result: Result<Void, Error>) {
updateSiteTitleResult = result
}

}

extension MockSiteRemote: SiteRemoteProtocol {
Expand Down
139 changes: 139 additions & 0 deletions Modules/Tests/YosemiteTests/Stores/JetpackConnectionStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,143 @@ final class JetpackConnectionStoreTests: XCTestCase {
// Then
XCTAssertNil(result)
}

func test_registerSite_returns_correct_blogID() throws {
// Given
let urlSuffix = "/jetpack/v4/connection/register"
network.simulateResponse(requestUrlSuffix: urlSuffix, filename: "jetpack-connection-registration")
let store = JetpackConnectionStore(dispatcher: dispatcher)

let setupAction = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network)
store.onAction(setupAction)

// When
let result: Result<Int64, Error> = waitFor { promise in
let action = JetpackConnectionAction.registerSite { result in
promise(result)
}
store.onAction(action)
}

// Then
XCTAssertTrue(result.isSuccess)
let blogID = try XCTUnwrap(result.get())
XCTAssertEqual(blogID, 1234567890)
}

func test_registerSite_properly_relays_errors() {
// Given
let urlSuffix = "/jetpack/v4/connection/register"
let error = NetworkError.unacceptableStatusCode(statusCode: 500)
network.simulateError(requestUrlSuffix: urlSuffix, error: error)
let store = JetpackConnectionStore(dispatcher: dispatcher)

let setupAction = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network)
store.onAction(setupAction)

// When
let result: Result<Int64, Error> = waitFor { promise in
let action = JetpackConnectionAction.registerSite { result in
promise(result)
}
store.onAction(action)
}

// Then
XCTAssertTrue(result.isFailure)
XCTAssertEqual(result.failure as? NetworkError, error)
}

func test_provisionConnection_returns_correct_provision_response() throws {
// Given
let urlSuffix = "/jetpack/v4/remote_provision"
network.simulateResponse(requestUrlSuffix: urlSuffix, filename: "jetpack-connection-provision")
let store = JetpackConnectionStore(dispatcher: dispatcher)

let setupAction = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network)
store.onAction(setupAction)

// When
let result: Result<JetpackConnectionProvisionResponse, Error> = waitFor { promise in
let action = JetpackConnectionAction.provisionConnection { result in
promise(result)
}
store.onAction(action)
}

// Then
XCTAssertTrue(result.isSuccess)
let response = try XCTUnwrap(result.get())
XCTAssertEqual(response.userId, 123456789)
XCTAssertEqual(response.scope, "administrator")
XCTAssertEqual(response.secret, "secret_token_12345")
}

func test_provisionConnection_properly_relays_errors() {
// Given
let urlSuffix = "/jetpack/v4/remote_provision"
let error = NetworkError.unacceptableStatusCode(statusCode: 500)
network.simulateError(requestUrlSuffix: urlSuffix, error: error)
let store = JetpackConnectionStore(dispatcher: dispatcher)

let setupAction = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network)
store.onAction(setupAction)

// When
let result: Result<JetpackConnectionProvisionResponse, Error> = waitFor { promise in
let action = JetpackConnectionAction.provisionConnection { result in
promise(result)
}
store.onAction(action)
}

// Then
XCTAssertTrue(result.isFailure)
XCTAssertEqual(result.failure as? NetworkError, error)
}

func test_finalizeJetpackConnection_returns_success_on_success() throws {
// Given
let urlSuffix = "sites/134/jetpack-remote-connect-user"
network.simulateResponse(requestUrlSuffix: urlSuffix, filename: "jetpack-connection-finalize-success")
let store = JetpackConnectionStore(dispatcher: dispatcher)

// When
let result: Result<Void, Error> = waitFor { promise in
let provisionResponse = JetpackConnectionProvisionResponse(userId: 123456789, scope: "administrator", secret: "secret_token_12345")
let action = JetpackConnectionAction.finalizeConnection(siteID: 134,
siteURL: "http://test.com",
provisionResponse: provisionResponse,
network: self.network) { result in
promise(result)
}
store.onAction(action)
}

// Then
XCTAssertTrue(result.isSuccess)
}

func test_finalizeJetpackConnection_returns_error_on_failure() throws {
// Given
let urlSuffix = "sites/134/jetpack-remote-connect-user"
network.simulateResponse(requestUrlSuffix: urlSuffix, filename: "jetpack-connection-finalize-error")
let store = JetpackConnectionStore(dispatcher: dispatcher)

// When
let result: Result<Void, Error> = waitFor { promise in
let provisionResponse = JetpackConnectionProvisionResponse(userId: 123456789, scope: "administrator", secret: "secret_token_12345")
let action = JetpackConnectionAction.finalizeConnection(siteID: 134,
siteURL: "http://test.com",
provisionResponse: provisionResponse,
network: self.network) { result in
promise(result)
}
store.onAction(action)
}

// Then
XCTAssertTrue(result.isFailure)
XCTAssertEqual(result.failure as? JetpackConnectionError, .alreadyConnected)
}
}
1 change: 1 addition & 0 deletions Modules/Tests/YosemiteTests/Stores/SiteStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ final class SiteStoreTests: XCTestCase {
let error = try XCTUnwrap(result.failure)
XCTAssertEqual(error as? DotcomError, .unknown(code: "error", message: nil))
}

}

private extension SiteStoreTests {
Expand Down