Skip to content

Commit d4c1507

Browse files
committed
Add SiteAction and SiteStore for creating a site with custom error handling and result type conversion.
1 parent 696fa23 commit d4c1507

File tree

5 files changed

+328
-0
lines changed

5 files changed

+328
-0
lines changed

Yosemite/Yosemite.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
0218B4EE242E08B20083A847 /* MediaType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0218B4ED242E08B20083A847 /* MediaType.swift */; };
2727
0218B4F0242E091C0083A847 /* Media+MediaType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0218B4EF242E091C0083A847 /* Media+MediaType.swift */; };
2828
0218B4F2242E09E80083A847 /* MediaTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0218B4F1242E09E80083A847 /* MediaTypeTests.swift */; };
29+
021940E4291E8A660090354E /* SiteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021940E3291E8A660090354E /* SiteAction.swift */; };
30+
021940E6291E8AD80090354E /* SiteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021940E5291E8AD80090354E /* SiteStore.swift */; };
2931
021BA0C428576940006E9886 /* MockDotcomAccountRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021BA0C328576940006E9886 /* MockDotcomAccountRemote.swift */; };
3032
021EAA5C25493E9300AA8CCD /* OrderItemAttribute+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021EAA5B25493E9300AA8CCD /* OrderItemAttribute+ReadOnlyConvertible.swift */; };
3133
0225512122FC2F3000D98613 /* OrderStatsV4Interval+Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0225512022FC2F3000D98613 /* OrderStatsV4Interval+Date.swift */; };
@@ -49,6 +51,8 @@
4951
025CA2CC238F518600B05C81 /* ProductShippingClassAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CA2CB238F518600B05C81 /* ProductShippingClassAction.swift */; };
5052
025CA2CE238F53CB00B05C81 /* ProductShippingClass+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CA2CD238F53CB00B05C81 /* ProductShippingClass+ReadOnlyConvertible.swift */; };
5153
025CA2D0238F54E800B05C81 /* ProductShippingClassStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CA2CF238F54E800B05C81 /* ProductShippingClassStoreTests.swift */; };
54+
02616F922921E1530095BC00 /* SiteStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02616F912921E1530095BC00 /* SiteStoreTests.swift */; };
55+
02616F942921E1CD0095BC00 /* MockSiteRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02616F932921E1CD0095BC00 /* MockSiteRemote.swift */; };
5256
026CF626237D8EFB009563D4 /* ProductVariationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026CF625237D8EFB009563D4 /* ProductVariationStore.swift */; };
5357
026CF628237D8F30009563D4 /* ProductVariationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026CF627237D8F30009563D4 /* ProductVariationAction.swift */; };
5458
026CF62A237D92C6009563D4 /* ProductVariation+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026CF629237D92C6009563D4 /* ProductVariation+ReadOnlyConvertible.swift */; };
@@ -447,6 +451,8 @@
447451
0218B4ED242E08B20083A847 /* MediaType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaType.swift; sourceTree = "<group>"; };
448452
0218B4EF242E091C0083A847 /* Media+MediaType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Media+MediaType.swift"; sourceTree = "<group>"; };
449453
0218B4F1242E09E80083A847 /* MediaTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaTypeTests.swift; sourceTree = "<group>"; };
454+
021940E3291E8A660090354E /* SiteAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteAction.swift; sourceTree = "<group>"; };
455+
021940E5291E8AD80090354E /* SiteStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteStore.swift; sourceTree = "<group>"; };
450456
021BA0C328576940006E9886 /* MockDotcomAccountRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDotcomAccountRemote.swift; sourceTree = "<group>"; };
451457
021EAA5B25493E9300AA8CCD /* OrderItemAttribute+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderItemAttribute+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
452458
0225512022FC2F3000D98613 /* OrderStatsV4Interval+Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderStatsV4Interval+Date.swift"; sourceTree = "<group>"; };
@@ -470,6 +476,8 @@
470476
025CA2CB238F518600B05C81 /* ProductShippingClassAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductShippingClassAction.swift; sourceTree = "<group>"; };
471477
025CA2CD238F53CB00B05C81 /* ProductShippingClass+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductShippingClass+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
472478
025CA2CF238F54E800B05C81 /* ProductShippingClassStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductShippingClassStoreTests.swift; sourceTree = "<group>"; };
479+
02616F912921E1530095BC00 /* SiteStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteStoreTests.swift; sourceTree = "<group>"; };
480+
02616F932921E1CD0095BC00 /* MockSiteRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSiteRemote.swift; sourceTree = "<group>"; };
473481
026CF625237D8EFB009563D4 /* ProductVariationStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductVariationStore.swift; sourceTree = "<group>"; };
474482
026CF627237D8F30009563D4 /* ProductVariationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductVariationAction.swift; sourceTree = "<group>"; };
475483
026CF629237D92C6009563D4 /* ProductVariation+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductVariation+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
@@ -1197,6 +1205,7 @@
11971205
029249E7274B8AEE002E9C34 /* MockMediaRemote.swift */,
11981206
021BA0C328576940006E9886 /* MockDotcomAccountRemote.swift */,
11991207
02DAE7F9291A9F36009342B7 /* MockDomainRemote.swift */,
1208+
02616F932921E1CD0095BC00 /* MockSiteRemote.swift */,
12001209
);
12011210
path = Remote;
12021211
sourceTree = "<group>";
@@ -1409,6 +1418,7 @@
14091418
68BD37B428DB2E9800C2A517 /* CustomerStore.swift */,
14101419
02E3B622290267D3007E0F13 /* AccountCreationStore.swift */,
14111420
02393066291A02AC00B2632F /* DomainStore.swift */,
1421+
021940E5291E8AD80090354E /* SiteStore.swift */,
14121422
);
14131423
path = Stores;
14141424
sourceTree = "<group>";
@@ -1469,6 +1479,7 @@
14691479
68BD37B828DB323D00C2A517 /* CustomerStoreTests.swift */,
14701480
02E3B629290622DE007E0F13 /* AccountCreationStoreTests.swift */,
14711481
02DAE7F7291A9F11009342B7 /* DomainStoreTests.swift */,
1482+
02616F912921E1530095BC00 /* SiteStoreTests.swift */,
14721483
);
14731484
path = Stores;
14741485
sourceTree = "<group>";
@@ -1655,6 +1666,7 @@
16551666
DE3404FB28BC5E7800CF0D97 /* JetpackConnectionAction.swift */,
16561667
02E3B624290267F2007E0F13 /* AccountCreationAction.swift */,
16571668
02393064291A018600B2632F /* DomainAction.swift */,
1669+
021940E3291E8A660090354E /* SiteAction.swift */,
16581670
);
16591671
path = Actions;
16601672
sourceTree = "<group>";
@@ -2031,6 +2043,7 @@
20312043
45739F372437680F00480C95 /* ProductSettings.swift in Sources */,
20322044
247CE88725833F1200F9D9D1 /* MockObjectGraph.swift in Sources */,
20332045
741F34822195EA71005F5BD9 /* CommentStore.swift in Sources */,
2046+
021940E4291E8A660090354E /* SiteAction.swift in Sources */,
20342047
02291735270BE18C00449FA0 /* ProductReviewFromNoteParcel.swift in Sources */,
20352048
74B260212188B5F30041793A /* Note+ReadOnlyType.swift in Sources */,
20362049
B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */,
@@ -2046,6 +2059,7 @@
20462059
74A7688C20D45EBA00F9D437 /* OrderStore.swift in Sources */,
20472060
DE3404FE28BC5F4200CF0D97 /* JetpackConnectionStore.swift in Sources */,
20482061
2618707C2540B6A4006522A1 /* ShippingLineTax+ReadOnlyConvertible.swift in Sources */,
2062+
021940E6291E8AD80090354E /* SiteStore.swift in Sources */,
20492063
749375002249605E007D85D1 /* ProductAction.swift in Sources */,
20502064
D831E2E4230E3524000037D0 /* ProductReviewAction.swift in Sources */,
20512065
02393065291A018600B2632F /* DomainAction.swift in Sources */,
@@ -2193,6 +2207,7 @@
21932207
02E3B62A290622DE007E0F13 /* AccountCreationStoreTests.swift in Sources */,
21942208
0202B6992387B01500F3EBE0 /* AppSettingsStoreTests+ProductsFeatureSwitch.swift in Sources */,
21952209
5779A0CD250042B600E35AF2 /* FetchResultSnapshotsProviderTests.swift in Sources */,
2210+
02616F942921E1CD0095BC00 /* MockSiteRemote.swift in Sources */,
21962211
022F9319257F24730011CD94 /* MockShippingLabel.swift in Sources */,
21972212
02FF055623D984310058E6E7 /* MockFileManager.swift in Sources */,
21982213
029B00A7230D64E800B0AE66 /* StatsTimeRangeTests.swift in Sources */,
@@ -2226,6 +2241,7 @@
22262241
020B2F9623BDE4DD00BD79AD /* ProductStoreTests+Validation.swift in Sources */,
22272242
02E262C0238CE80100B79588 /* StorageShippingSettingsServiceTests.swift in Sources */,
22282243
45AB8B1E24AB363D00B5B36E /* ProductTagStoreTests.swift in Sources */,
2244+
02616F922921E1530095BC00 /* SiteStoreTests.swift in Sources */,
22292245
B5C9DE222087FF20006B910A /* DispatcherTests.swift in Sources */,
22302246
45182D2327B55F9C00B4C05C /* InboxNotesStoreTests.swift in Sources */,
22312247
03FBDA2A263296C400ACE257 /* CouponStoreTests.swift in Sources */,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Foundation
2+
3+
/// SiteAction: Defines all of the Actions supported by the SiteStore.
4+
///
5+
public enum SiteAction: Action {
6+
/// Creates a site in the store creation flow.
7+
/// - Parameters:
8+
/// - name: The name of the site.
9+
/// - domain: Domain name selected for the site.
10+
/// - completion: The result of site creation.
11+
case createSite(name: String,
12+
domain: String,
13+
completion: (Result<SiteCreationResult, SiteCreationError>) -> Void)
14+
}
15+
16+
/// The result of site creation including necessary site information.
17+
public struct SiteCreationResult: Equatable {
18+
public let siteID: Int64
19+
public let name: String
20+
public let url: String
21+
public let siteSlug: String
22+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import Foundation
2+
import Networking
3+
import protocol Storage.StorageManagerType
4+
5+
/// Handles `SiteAction`
6+
///
7+
public final class SiteStore: Store {
8+
// Keeps a strong reference to remote to keep requests alive.
9+
private let remote: SiteRemoteProtocol
10+
11+
public init(remote: SiteRemoteProtocol,
12+
dispatcher: Dispatcher,
13+
storageManager: StorageManagerType,
14+
network: Network) {
15+
self.remote = remote
16+
super.init(dispatcher: dispatcher, storageManager: storageManager, network: network)
17+
}
18+
19+
public convenience init(dotcomClientID: String,
20+
dotcomClientSecret: String,
21+
dispatcher: Dispatcher,
22+
storageManager: StorageManagerType,
23+
network: Network) {
24+
let remote = SiteRemote(network: network, dotcomClientID: dotcomClientID, dotcomClientSecret: dotcomClientSecret)
25+
self.init(remote: remote,
26+
dispatcher: dispatcher,
27+
storageManager: storageManager,
28+
network: network)
29+
}
30+
31+
public override func registerSupportedActions(in dispatcher: Dispatcher) {
32+
dispatcher.register(processor: self, for: SiteAction.self)
33+
}
34+
35+
/// Called whenever a given Action is dispatched.
36+
///
37+
public override func onAction(_ action: Action) {
38+
guard let action = action as? SiteAction else {
39+
assertionFailure("SiteStore received an unsupported action: \(action)")
40+
return
41+
}
42+
switch action {
43+
case .createSite(let name, let domain, let completion):
44+
createSite(name: name, domain: domain, completion: completion)
45+
}
46+
}
47+
}
48+
49+
private extension SiteStore {
50+
func createSite(name: String,
51+
domain: String,
52+
completion: @escaping (Result<SiteCreationResult, SiteCreationError>) -> Void) {
53+
Task { @MainActor in
54+
let result = await remote.createSite(name: name,
55+
domain: domain)
56+
switch result {
57+
case .success(let response):
58+
guard response.success else {
59+
return completion(.failure(SiteCreationError.unsuccessful))
60+
}
61+
guard let siteID = Int64(response.site.siteID) else {
62+
return completion(.failure(SiteCreationError.invalidSiteID))
63+
}
64+
completion(.success(.init(siteID: siteID,
65+
name: response.site.name,
66+
url: response.site.url,
67+
siteSlug: response.site.siteSlug)))
68+
case .failure(let remoteError):
69+
completion(.failure(SiteCreationError(remoteError: remoteError)))
70+
}
71+
}
72+
}
73+
}
74+
75+
/// Possible site creation errors.
76+
public enum SiteCreationError: Error, Equatable {
77+
/// The domain name should be a `wordpress.com` subdomain and can only contain lowercase letters (a-z) and numbers.
78+
case invalidDomain
79+
/// The domain has been taken.
80+
case domainExists
81+
/// The returned site ID for the created site is invalid - for example, not a string that can be converted to `Int64`.
82+
case invalidSiteID
83+
/// When the site creation result is returned but its `success` boolean is `false`.
84+
case unsuccessful
85+
/// Unexpected error from WPCOM.
86+
case unexpected(error: DotcomError)
87+
/// Unknown error that is not a `DotcomError` nor `Networking.SiteCreationError`.
88+
case unknown(description: String)
89+
90+
init(remoteError: Error) {
91+
switch remoteError {
92+
case let remoteError as Networking.SiteCreationError:
93+
switch remoteError {
94+
case .invalidDomain:
95+
self = .invalidDomain
96+
}
97+
case let remoteError as DotcomError:
98+
switch remoteError {
99+
case let .unknown(code, _):
100+
switch code {
101+
case "blog_name_exists":
102+
self = .domainExists
103+
case "blog_name_only_lowercase_letters_and_numbers":
104+
self = .invalidDomain
105+
default:
106+
self = .unexpected(error: remoteError)
107+
}
108+
default:
109+
self = .unexpected(error: remoteError)
110+
}
111+
default:
112+
self = .unknown(description: remoteError.localizedDescription)
113+
}
114+
}
115+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Networking
2+
import XCTest
3+
4+
/// Mock for `SiteRemote`.
5+
///
6+
final class MockSiteRemote {
7+
/// The results to return in `createSite`.
8+
private var createSiteResult: Result<SiteCreationResponse, Error>?
9+
10+
/// Returns the value when `createSite` is called.
11+
func whenCreatingSite(thenReturn result: Result<SiteCreationResponse, Error>) {
12+
createSiteResult = result
13+
}
14+
}
15+
16+
extension MockSiteRemote: SiteRemoteProtocol {
17+
func createSite(name: String, domain: String) async -> Result<SiteCreationResponse, Error> {
18+
guard let result = createSiteResult else {
19+
XCTFail("Could not find result for creating a site.")
20+
return .failure(NetworkError.notFound)
21+
}
22+
return result
23+
}
24+
}

0 commit comments

Comments
 (0)