Skip to content

Commit 43203f6

Browse files
authored
Merge pull request #8107 from woocommerce/feat/site-creation-foundation
Store creation M2: site creation Networking & Yosemite layer changes
2 parents d3a39df + 6a6a686 commit 43203f6

File tree

13 files changed

+554
-18
lines changed

13 files changed

+554
-18
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
020D07C223D858BB00FD9580 /* media-upload.json in Resources */ = {isa = PBXBuildFile; fileRef = 020D07C123D858BB00FD9580 /* media-upload.json */; };
2020
020D0C03291504DE00BB3DCE /* UnauthenticatedRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020D0C02291504DE00BB3DCE /* UnauthenticatedRequestTests.swift */; };
2121
0212683524C046CB00F8A892 /* MockNetwork+Path.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0212683424C046CB00F8A892 /* MockNetwork+Path.swift */; };
22+
021940E2291E3CFD0090354E /* SiteRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021940E1291E3CFD0090354E /* SiteRemote.swift */; };
2223
0219B03923964BB3007DCD5E /* ProductShippingClassMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0219B03823964BB3007DCD5E /* ProductShippingClassMapper.swift */; };
2324
021A84DA257DF92800BC71D1 /* ShippingLabelRefundMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021A84D9257DF92800BC71D1 /* ShippingLabelRefundMapper.swift */; };
2425
021C7BF723863D1800A3BCBD /* Encodable+Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021C7BF623863D1800A3BCBD /* Encodable+Serialization.swift */; };
@@ -34,6 +35,9 @@
3435
025CA2C4238EBC4300B05C81 /* ProductShippingClassRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CA2C3238EBC4300B05C81 /* ProductShippingClassRemote.swift */; };
3536
025CA2C6238F4F3500B05C81 /* ProductShippingClassRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CA2C5238F4F3500B05C81 /* ProductShippingClassRemoteTests.swift */; };
3637
025CA2C8238F4FF400B05C81 /* product-shipping-classes-load-all.json in Resources */ = {isa = PBXBuildFile; fileRef = 025CA2C7238F4FF400B05C81 /* product-shipping-classes-load-all.json */; };
38+
02616F8C292132800095BC00 /* SiteRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02616F8B292132800095BC00 /* SiteRemoteTests.swift */; };
39+
02616F8F2921336C0095BC00 /* site-creation-domain-error.json in Resources */ = {isa = PBXBuildFile; fileRef = 02616F8D2921336C0095BC00 /* site-creation-domain-error.json */; };
40+
02616F902921336C0095BC00 /* site-creation-success.json in Resources */ = {isa = PBXBuildFile; fileRef = 02616F8E2921336C0095BC00 /* site-creation-success.json */; };
3741
0261F5A928D4641500B7AC72 /* products-sku-search.json in Resources */ = {isa = PBXBuildFile; fileRef = 0261F5A828D4641500B7AC72 /* products-sku-search.json */; };
3842
02698CF624C17FC1005337C4 /* product-alternative-types.json in Resources */ = {isa = PBXBuildFile; fileRef = 02698CF524C17FC1005337C4 /* product-alternative-types.json */; };
3943
02698CF824C183A5005337C4 /* ProductVariationListMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02698CF724C183A5005337C4 /* ProductVariationListMapperTests.swift */; };
@@ -764,6 +768,7 @@
764768
020D07C123D858BB00FD9580 /* media-upload.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-upload.json"; sourceTree = "<group>"; };
765769
020D0C02291504DE00BB3DCE /* UnauthenticatedRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnauthenticatedRequestTests.swift; sourceTree = "<group>"; };
766770
0212683424C046CB00F8A892 /* MockNetwork+Path.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockNetwork+Path.swift"; sourceTree = "<group>"; };
771+
021940E1291E3CFD0090354E /* SiteRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteRemote.swift; sourceTree = "<group>"; };
767772
0219B03823964BB3007DCD5E /* ProductShippingClassMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductShippingClassMapper.swift; sourceTree = "<group>"; };
768773
021A84D9257DF92800BC71D1 /* ShippingLabelRefundMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelRefundMapper.swift; sourceTree = "<group>"; };
769774
021C7BF623863D1800A3BCBD /* Encodable+Serialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encodable+Serialization.swift"; sourceTree = "<group>"; };
@@ -779,6 +784,9 @@
779784
025CA2C3238EBC4300B05C81 /* ProductShippingClassRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductShippingClassRemote.swift; sourceTree = "<group>"; };
780785
025CA2C5238F4F3500B05C81 /* ProductShippingClassRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductShippingClassRemoteTests.swift; sourceTree = "<group>"; };
781786
025CA2C7238F4FF400B05C81 /* product-shipping-classes-load-all.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-shipping-classes-load-all.json"; sourceTree = "<group>"; };
787+
02616F8B292132800095BC00 /* SiteRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteRemoteTests.swift; sourceTree = "<group>"; };
788+
02616F8D2921336C0095BC00 /* site-creation-domain-error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-creation-domain-error.json"; sourceTree = "<group>"; };
789+
02616F8E2921336C0095BC00 /* site-creation-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-creation-success.json"; sourceTree = "<group>"; };
782790
0261F5A828D4641500B7AC72 /* products-sku-search.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "products-sku-search.json"; sourceTree = "<group>"; };
783791
02698CF524C17FC1005337C4 /* product-alternative-types.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-alternative-types.json"; sourceTree = "<group>"; };
784792
02698CF724C183A5005337C4 /* ProductVariationListMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductVariationListMapperTests.swift; sourceTree = "<group>"; };
@@ -1729,6 +1737,7 @@
17291737
68BD37B228D9B8BD00C2A517 /* CustomerRemoteTests.swift */,
17301738
68F48B0E28E3BB850045C15B /* WCAnalyticsCustomerRemoteTests.swift */,
17311739
0239306A291A96F800B2632F /* DomainRemoteTests.swift */,
1740+
02616F8B292132800095BC00 /* SiteRemoteTests.swift */,
17321741
);
17331742
path = Remote;
17341743
sourceTree = "<group>";
@@ -1843,11 +1852,11 @@
18431852
261CF1B7255AE62D0090D8D3 /* PaymentGatewayRemote.swift */,
18441853
45152808257A7C6E0076B03C /* ProductAttributesRemote.swift */,
18451854
26615472242D596B00A31661 /* ProductCategoriesRemote.swift */,
1855+
026CF61F237D69D6009563D4 /* ProductVariationsRemote.swift */,
18461856
26E5A08725A66AFC000DF8F6 /* ProductAttributeTermRemote.swift */,
18471857
D88D5A44230BC6F9007B6E01 /* ProductReviewsRemote.swift */,
18481858
4599FC5D24A62AA70056157A /* ProductTagsRemote.swift */,
18491859
CE0A0F1A223989670075ED8D /* ProductsRemote.swift */,
1850-
026CF61F237D69D6009563D4 /* ProductVariationsRemote.swift */,
18511860
025CA2C3238EBC4300B05C81 /* ProductShippingClassRemote.swift */,
18521861
CE43066F234B99F50073CBFF /* RefundsRemote.swift */,
18531862
7412A8E921B6E192005D182A /* ReportRemote.swift */,
@@ -1869,6 +1878,7 @@
18691878
68CB800F28D89A0400E169F8 /* CustomerRemote.swift */,
18701879
68F48B0A28E3B1CD0045C15B /* WCAnalyticsCustomerRemote.swift */,
18711880
023930622918FF5400B2632F /* DomainRemote.swift */,
1881+
021940E1291E3CFD0090354E /* SiteRemote.swift */,
18721882
);
18731883
path = Remote;
18741884
sourceTree = "<group>";
@@ -2171,6 +2181,8 @@
21712181
2670C3FD270F4E6A002FE931 /* sites-malformed.json */,
21722182
7426CA1221AF34A3004E9FFC /* site-api.json */,
21732183
74AB5B4E21AF3F0D00859C12 /* site-api-no-woo.json */,
2184+
02616F8D2921336C0095BC00 /* site-creation-domain-error.json */,
2185+
02616F8E2921336C0095BC00 /* site-creation-success.json */,
21742186
CE50346621B5DCBE007573C6 /* site-plan.json */,
21752187
453305EC2459E1AA00264E50 /* site-post.json */,
21762188
453305F42459ED2700264E50 /* site-post-update.json */,
@@ -2858,6 +2870,7 @@
28582870
0359EA2927AC2AAD0048DE2D /* wcpay-charge-error.json in Resources */,
28592871
CEF88DAB233E911A00BED485 /* order-fully-refunded.json in Resources */,
28602872
02698CFA24C188E9005337C4 /* product-variations-load-all-alternative-types.json in Resources */,
2873+
02616F902921336C0095BC00 /* site-creation-success.json in Resources */,
28612874
CC0786632678F79500BA9AC1 /* shipping-label-purchase-success.json in Resources */,
28622875
7497376A2141F2BE0008C490 /* top-performers-week-alt.json in Resources */,
28632876
D865CE61278CA1AE002C8520 /* stripe-payment-intent-processing.json in Resources */,
@@ -2887,6 +2900,7 @@
28872900
028CB717290223CB00331C09 /* create-account-error-password.json in Resources */,
28882901
D800DA0E25EFEC21001E13CE /* wcpay-connection-token.json in Resources */,
28892902
74159628224D63CE003C21CF /* settings-product-alt.json in Resources */,
2903+
02616F8F2921336C0095BC00 /* site-creation-domain-error.json in Resources */,
28902904
45AB8B2024AB3E1F00B5B36E /* product-tags-empty.json in Resources */,
28912905
0359EA2127AAE58C0048DE2D /* wcpay-charge-card-present.json in Resources */,
28922906
451274A625276C82009911FF /* product-variation.json in Resources */,
@@ -3044,6 +3058,7 @@
30443058
74ABA1CD213F1B6B00FFAD30 /* TopEarnerStats.swift in Sources */,
30453059
CCAAD10F2683974000909664 /* ShippingLabelPackagePurchase.swift in Sources */,
30463060
265EFBDC285257950033BD33 /* Order+Fallbacks.swift in Sources */,
3061+
021940E2291E3CFD0090354E /* SiteRemote.swift in Sources */,
30473062
B557DA0220975500005962F4 /* JetpackRequest.swift in Sources */,
30483063
D88E229025AC990A0023F3B1 /* OrderFeeLine.swift in Sources */,
30493064
74046E1F217A6B70007DD7BF /* SiteSettingsMapper.swift in Sources */,
@@ -3405,6 +3420,7 @@
34053420
DE50296528C60A8000551736 /* JetpackUserMapperTests.swift in Sources */,
34063421
9387A6F0226E3F15001B53D7 /* AccountSettingsMapperTests.swift in Sources */,
34073422
2685C102263B6A1000D9EE97 /* AddOnGroupRemoteTests.swift in Sources */,
3423+
02616F8C292132800095BC00 /* SiteRemoteTests.swift in Sources */,
34083424
B57B1E6721C916850046E764 /* NetworkErrorTests.swift in Sources */,
34093425
D8FBFF0F22D3B25E006E3336 /* WooAPIVersionTests.swift in Sources */,
34103426
45152831257A8E1A0076B03C /* ProductAttributeMapperTests.swift in Sources */,
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import Foundation
2+
3+
/// Protocol for `SiteRemote` mainly used for mocking.
4+
public protocol SiteRemoteProtocol {
5+
/// Creates a site given
6+
/// - Parameters:
7+
/// - name: The name of the site.
8+
/// - domain: The domain selected for the site.
9+
/// - Returns: The result of site creation.
10+
func createSite(name: String,
11+
domain: String) async -> Result<SiteCreationResponse, Error>
12+
}
13+
14+
/// Site: Remote Endpoints
15+
///
16+
public class SiteRemote: Remote, SiteRemoteProtocol {
17+
private let dotcomClientID: String
18+
private let dotcomClientSecret: String
19+
20+
public init(network: Network, dotcomClientID: String, dotcomClientSecret: String) {
21+
self.dotcomClientID = dotcomClientID
22+
self.dotcomClientSecret = dotcomClientSecret
23+
super.init(network: network)
24+
}
25+
26+
public func createSite(name: String,
27+
domain: String) async -> Result<SiteCreationResponse, Error> {
28+
let path = Path.siteCreation
29+
30+
// Domain input should be a `wordpress.com` subdomain.
31+
guard let subdomainName = domain.split(separator: ".").first else {
32+
return .failure(SiteCreationError.invalidDomain)
33+
}
34+
let parameters: [String: Any] = [
35+
"blog_name": subdomainName,
36+
"blog_title": name,
37+
"client_id": dotcomClientID,
38+
"client_secret": dotcomClientSecret,
39+
"find_available_url": false,
40+
"public": 0,
41+
"validate": false,
42+
"options": [
43+
"default_annotation_as_primary_fallback": true,
44+
"site_creation_flow": "onboarding",
45+
"site_information": [
46+
"title": ""
47+
],
48+
"theme": "pub/zoologist",
49+
"use_theme_annotation": false,
50+
"wpcom_public_coming_soon": 1
51+
]
52+
]
53+
let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .post, path: path, parameters: parameters)
54+
55+
do {
56+
let response: SiteCreationResponse = try await enqueue(request)
57+
return .success(response)
58+
} catch {
59+
return .failure(error)
60+
}
61+
}
62+
}
63+
64+
/// Site creation API response.
65+
public struct SiteCreationResponse: Decodable {
66+
public let site: Site
67+
public let success: Bool
68+
69+
private enum CodingKeys: String, CodingKey {
70+
case site = "blog_details"
71+
case success
72+
}
73+
}
74+
75+
/// Possible site creation errors in the Networking layer.
76+
public enum SiteCreationError: Error {
77+
case invalidDomain
78+
}
79+
80+
public extension SiteCreationResponse {
81+
/// Necessary data about the created site in the site creation API response.
82+
struct Site: Decodable, Equatable {
83+
public let siteID: String
84+
public let name: String
85+
public let url: String
86+
public let siteSlug: String
87+
88+
private enum CodingKeys: String, CodingKey {
89+
case siteID = "blogid"
90+
case name = "blogname"
91+
case url
92+
case siteSlug = "site_slug"
93+
}
94+
}
95+
}
96+
97+
// MARK: - Constants
98+
//
99+
private extension SiteRemote {
100+
enum Path {
101+
static let siteCreation = "sites/new"
102+
}
103+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import XCTest
2+
@testable import Networking
3+
4+
final class SiteRemoteTests: XCTestCase {
5+
/// Mock network wrapper.
6+
private var network: MockNetwork!
7+
8+
private var remote: SiteRemote!
9+
10+
override func setUp() {
11+
super.setUp()
12+
network = MockNetwork()
13+
remote = SiteRemote(network: network, dotcomClientID: "", dotcomClientSecret: "")
14+
}
15+
16+
override func tearDown() {
17+
remote = nil
18+
network = nil
19+
super.tearDown()
20+
}
21+
22+
func test_createSite_returns_created_site_on_success() async throws {
23+
// Given
24+
network.simulateResponse(requestUrlSuffix: "sites/new", filename: "site-creation-success")
25+
26+
// When
27+
let result = await remote.createSite(name: "Wapuu swags", domain: "wapuu.store")
28+
29+
// Then
30+
XCTAssertTrue(result.isSuccess)
31+
let response = try XCTUnwrap(result.get())
32+
XCTAssertTrue(response.success)
33+
XCTAssertEqual(response.site, .init(siteID: "202211",
34+
name: "Wapuu swags",
35+
url: "https://wapuu.store/",
36+
siteSlug: "wapuu.store"))
37+
}
38+
39+
func test_createSite_returns_invalidDomain_error_when_domain_is_empty() async throws {
40+
// When
41+
let result = await remote.createSite(name: "Wapuu swags", domain: "")
42+
43+
// Then
44+
let error = try XCTUnwrap(result.failure as? SiteCreationError)
45+
XCTAssertEqual(error, .invalidDomain)
46+
}
47+
48+
func test_createSite_returns_DotcomError_failure_on_domain_error() async throws {
49+
// Given
50+
network.simulateResponse(requestUrlSuffix: "sites/new", filename: "site-creation-domain-error")
51+
52+
// When
53+
let result = await remote.createSite(name: "Wapuu swags", domain: "wapuu.store")
54+
55+
// Then
56+
let error = try XCTUnwrap(result.failure as? DotcomError)
57+
XCTAssertEqual(error,
58+
.unknown(code: "blog_name_only_lowercase_letters_and_numbers",
59+
message: "Site names can only contain lowercase letters (a-z) and numbers."))
60+
}
61+
62+
func test_createSite_returns_failure_on_empty_response() async throws {
63+
// When
64+
let result = await remote.createSite(name: "Wapuu swags", domain: "wapuu.store")
65+
66+
// Then
67+
let error = try XCTUnwrap(result.failure as? NetworkError)
68+
XCTAssertEqual(error, .notFound)
69+
}
70+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"error": "blog_name_only_lowercase_letters_and_numbers",
3+
"message": "Site names can only contain lowercase letters (a-z) and numbers."
4+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"success": true,
3+
"blog_details": {
4+
"url": "https://wapuu.store/",
5+
"blogid": "202211",
6+
"blogname": "Wapuu swags",
7+
"xmlrpc": "https://adventuresofwapuu.wordpress.com/xmlrpc.php",
8+
"site_slug": "wapuu.store"
9+
}
10+
}

WooCommerce/Classes/Yosemite/AuthenticatedState.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ class AuthenticatedState: StoresManagerState {
6363
ShippingLabelStore(dispatcher: dispatcher, storageManager: storageManager, network: network),
6464
SitePluginStore(dispatcher: dispatcher, storageManager: storageManager, network: network),
6565
SitePostStore(dispatcher: dispatcher, storageManager: storageManager, network: network),
66+
SiteStore(dotcomClientID: ApiCredentials.dotcomAppId,
67+
dotcomClientSecret: ApiCredentials.dotcomSecret,
68+
dispatcher: dispatcher,
69+
storageManager: storageManager,
70+
network: network),
6671
StatsStoreV4(dispatcher: dispatcher, storageManager: storageManager, network: network),
6772
SystemStatusStore(dispatcher: dispatcher, storageManager: storageManager, network: network),
6873
TaxClassStore(dispatcher: dispatcher, storageManager: storageManager, network: network),

0 commit comments

Comments
 (0)