Skip to content

Commit 38cc12e

Browse files
authored
Jetpack Setup: Update Yosemite layer with new connection endpoints (#15977)
2 parents 9ca2d14 + 403d2e6 commit 38cc12e

File tree

6 files changed

+204
-0
lines changed

6 files changed

+204
-0
lines changed

Modules/Sources/Yosemite/Actions/JetpackConnectionAction.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ public enum JetpackConnectionAction: Action {
1616
case fetchJetpackConnectionURL(completion: (Result<URL, Error>) -> Void)
1717
/// Fetches connection state with the given site's Jetpack.
1818
case fetchJetpackConnectionData(completion: (Result<JetpackConnectionData, Error>) -> Void)
19+
/// Establishes site-level connection and returns WordPress.com blog ID.
20+
case registerSite(completion: (Result<Int64, Error>) -> Void)
21+
/// Provisions connection and returns provision response with scope and secret.
22+
case provisionConnection(completion: (Result<JetpackConnectionProvisionResponse, Error>) -> Void)
23+
/// Finalizes the Jetpack connection by sending a request to WPCom.
24+
case finalizeConnection(siteID: Int64,
25+
siteURL: String,
26+
provisionResponse: JetpackConnectionProvisionResponse,
27+
network: Network,
28+
completion: (Result<Void, Error>) -> Void)
1929
/// Fetches the WPCom account with the given network
2030
case loadWPComAccount(network: Network, onCompletion: (Account?) -> Void)
2131
}

Modules/Sources/Yosemite/Model/Model.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public typealias GoogleAdsCampaignStatsItem = Networking.GoogleAdsCampaignStatsI
5151
public typealias InboxNote = Networking.InboxNote
5252
public typealias InboxAction = Networking.InboxAction
5353
public typealias JetpackConnectionData = Networking.JetpackConnectionData
54+
public typealias JetpackConnectionProvisionResponse = Networking.JetpackConnectionProvisionResponse
5455
public typealias JustInTimeMessageHook = Networking.JustInTimeMessagesRemote.MessagePath.Hook
5556
public typealias Media = Networking.Media
5657
public typealias MetaContainer = Networking.MetaContainer

Modules/Sources/Yosemite/Stores/JetpackConnectionStore.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ public final class JetpackConnectionStore: DeauthenticatedStore {
99
private var jetpackConnectionRemote: JetpackConnectionRemote?
1010
private var accountRemote: AccountRemote?
1111

12+
/// periphery: ignore - kept with strong reference to keep network requests alive.
13+
private var siteRemote: SiteRemote?
14+
1215
public override init(dispatcher: Dispatcher) {
1316
super.init(dispatcher: dispatcher)
1417
}
@@ -42,6 +45,12 @@ public final class JetpackConnectionStore: DeauthenticatedStore {
4245
fetchJetpackConnectionURL(completion: completion)
4346
case .fetchJetpackConnectionData(let completion):
4447
fetchJetpackConnectionData(completion: completion)
48+
case .registerSite(let completion):
49+
registerSite(completion: completion)
50+
case .provisionConnection(let completion):
51+
provisionConnection(completion: completion)
52+
case .finalizeConnection(let siteID, let siteURL, let provisionResponse, let network, let completion):
53+
finalizeConnection(siteID: siteID, siteURL: siteURL, provisionResponse: provisionResponse, network: network, completion: completion)
4554
case .loadWPComAccount(let network, let onCompletion):
4655
loadWPComAccount(network: network, onCompletion: onCompletion)
4756
}
@@ -87,6 +96,49 @@ private extension JetpackConnectionStore {
8796
jetpackConnectionRemote?.fetchJetpackConnectionData(completion: completion)
8897
}
8998

99+
func registerSite(completion: @escaping (Result<Int64, Error>) -> Void) {
100+
guard let jetpackConnectionRemote else { return }
101+
Task { @MainActor in
102+
do {
103+
let blogID = try await jetpackConnectionRemote.registerSite()
104+
completion(.success(blogID))
105+
} catch {
106+
completion(.failure(error))
107+
}
108+
}
109+
}
110+
111+
func provisionConnection(completion: @escaping (Result<JetpackConnectionProvisionResponse, Error>) -> Void) {
112+
guard let jetpackConnectionRemote else { return }
113+
Task { @MainActor in
114+
do {
115+
let response = try await jetpackConnectionRemote.provisionConnection()
116+
completion(.success(response))
117+
} catch {
118+
completion(.failure(error))
119+
}
120+
}
121+
}
122+
123+
func finalizeConnection(siteID: Int64,
124+
siteURL: String,
125+
provisionResponse: JetpackConnectionProvisionResponse,
126+
network: Network,
127+
completion: @escaping (Result<Void, Error>) -> Void) {
128+
/// Intentionally leaving `dotcomClientID` and `dotcomClientSecret` empty
129+
/// as these are not needed for the `finalizeJetpackConnection` method we're using here.
130+
let remote = SiteRemote(network: network, dotcomClientID: "", dotcomClientSecret: "")
131+
Task { @MainActor in
132+
do {
133+
try await remote.finalizeJetpackConnection(siteID: siteID, siteURL: siteURL, provisionResponse: provisionResponse)
134+
completion(.success(()))
135+
} catch {
136+
completion(.failure(error))
137+
}
138+
}
139+
self.siteRemote = remote
140+
}
141+
90142
func loadWPComAccount(network: Network, onCompletion: @escaping (Account?) -> Void) {
91143
let remote = AccountRemote(network: network)
92144
remote.loadAccount { result in

Modules/Tests/YosemiteTests/Mocks/Networking/Remote/MockSiteRemote.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ final class MockSiteRemote {
5050
func whenUpdatingSiteTitle(thenReturn result: Result<Void, Error>) {
5151
updateSiteTitleResult = result
5252
}
53+
5354
}
5455

5556
extension MockSiteRemote: SiteRemoteProtocol {

Modules/Tests/YosemiteTests/Stores/JetpackConnectionStoreTests.swift

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,143 @@ final class JetpackConnectionStoreTests: XCTestCase {
285285
// Then
286286
XCTAssertNil(result)
287287
}
288+
289+
func test_registerSite_returns_correct_blogID() throws {
290+
// Given
291+
let urlSuffix = "/jetpack/v4/connection/register"
292+
network.simulateResponse(requestUrlSuffix: urlSuffix, filename: "jetpack-connection-registration")
293+
let store = JetpackConnectionStore(dispatcher: dispatcher)
294+
295+
let setupAction = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network)
296+
store.onAction(setupAction)
297+
298+
// When
299+
let result: Result<Int64, Error> = waitFor { promise in
300+
let action = JetpackConnectionAction.registerSite { result in
301+
promise(result)
302+
}
303+
store.onAction(action)
304+
}
305+
306+
// Then
307+
XCTAssertTrue(result.isSuccess)
308+
let blogID = try XCTUnwrap(result.get())
309+
XCTAssertEqual(blogID, 1234567890)
310+
}
311+
312+
func test_registerSite_properly_relays_errors() {
313+
// Given
314+
let urlSuffix = "/jetpack/v4/connection/register"
315+
let error = NetworkError.unacceptableStatusCode(statusCode: 500)
316+
network.simulateError(requestUrlSuffix: urlSuffix, error: error)
317+
let store = JetpackConnectionStore(dispatcher: dispatcher)
318+
319+
let setupAction = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network)
320+
store.onAction(setupAction)
321+
322+
// When
323+
let result: Result<Int64, Error> = waitFor { promise in
324+
let action = JetpackConnectionAction.registerSite { result in
325+
promise(result)
326+
}
327+
store.onAction(action)
328+
}
329+
330+
// Then
331+
XCTAssertTrue(result.isFailure)
332+
XCTAssertEqual(result.failure as? NetworkError, error)
333+
}
334+
335+
func test_provisionConnection_returns_correct_provision_response() throws {
336+
// Given
337+
let urlSuffix = "/jetpack/v4/remote_provision"
338+
network.simulateResponse(requestUrlSuffix: urlSuffix, filename: "jetpack-connection-provision")
339+
let store = JetpackConnectionStore(dispatcher: dispatcher)
340+
341+
let setupAction = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network)
342+
store.onAction(setupAction)
343+
344+
// When
345+
let result: Result<JetpackConnectionProvisionResponse, Error> = waitFor { promise in
346+
let action = JetpackConnectionAction.provisionConnection { result in
347+
promise(result)
348+
}
349+
store.onAction(action)
350+
}
351+
352+
// Then
353+
XCTAssertTrue(result.isSuccess)
354+
let response = try XCTUnwrap(result.get())
355+
XCTAssertEqual(response.userId, 123456789)
356+
XCTAssertEqual(response.scope, "administrator")
357+
XCTAssertEqual(response.secret, "secret_token_12345")
358+
}
359+
360+
func test_provisionConnection_properly_relays_errors() {
361+
// Given
362+
let urlSuffix = "/jetpack/v4/remote_provision"
363+
let error = NetworkError.unacceptableStatusCode(statusCode: 500)
364+
network.simulateError(requestUrlSuffix: urlSuffix, error: error)
365+
let store = JetpackConnectionStore(dispatcher: dispatcher)
366+
367+
let setupAction = JetpackConnectionAction.authenticate(siteURL: siteURL, network: network)
368+
store.onAction(setupAction)
369+
370+
// When
371+
let result: Result<JetpackConnectionProvisionResponse, Error> = waitFor { promise in
372+
let action = JetpackConnectionAction.provisionConnection { result in
373+
promise(result)
374+
}
375+
store.onAction(action)
376+
}
377+
378+
// Then
379+
XCTAssertTrue(result.isFailure)
380+
XCTAssertEqual(result.failure as? NetworkError, error)
381+
}
382+
383+
func test_finalizeJetpackConnection_returns_success_on_success() throws {
384+
// Given
385+
let urlSuffix = "sites/134/jetpack-remote-connect-user"
386+
network.simulateResponse(requestUrlSuffix: urlSuffix, filename: "jetpack-connection-finalize-success")
387+
let store = JetpackConnectionStore(dispatcher: dispatcher)
388+
389+
// When
390+
let result: Result<Void, Error> = waitFor { promise in
391+
let provisionResponse = JetpackConnectionProvisionResponse(userId: 123456789, scope: "administrator", secret: "secret_token_12345")
392+
let action = JetpackConnectionAction.finalizeConnection(siteID: 134,
393+
siteURL: "http://test.com",
394+
provisionResponse: provisionResponse,
395+
network: self.network) { result in
396+
promise(result)
397+
}
398+
store.onAction(action)
399+
}
400+
401+
// Then
402+
XCTAssertTrue(result.isSuccess)
403+
}
404+
405+
func test_finalizeJetpackConnection_returns_error_on_failure() throws {
406+
// Given
407+
let urlSuffix = "sites/134/jetpack-remote-connect-user"
408+
network.simulateResponse(requestUrlSuffix: urlSuffix, filename: "jetpack-connection-finalize-error")
409+
let store = JetpackConnectionStore(dispatcher: dispatcher)
410+
411+
// When
412+
let result: Result<Void, Error> = waitFor { promise in
413+
let provisionResponse = JetpackConnectionProvisionResponse(userId: 123456789, scope: "administrator", secret: "secret_token_12345")
414+
let action = JetpackConnectionAction.finalizeConnection(siteID: 134,
415+
siteURL: "http://test.com",
416+
provisionResponse: provisionResponse,
417+
network: self.network) { result in
418+
promise(result)
419+
}
420+
store.onAction(action)
421+
}
422+
423+
// Then
424+
XCTAssertTrue(result.isFailure)
425+
XCTAssertEqual(result.failure as? JetpackConnectionError, .alreadyConnected)
426+
}
288427
}

Modules/Tests/YosemiteTests/Stores/SiteStoreTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ final class SiteStoreTests: XCTestCase {
364364
let error = try XCTUnwrap(result.failure)
365365
XCTAssertEqual(error as? DotcomError, .unknown(code: "error", message: nil))
366366
}
367+
367368
}
368369

369370
private extension SiteStoreTests {

0 commit comments

Comments
 (0)