Skip to content

Commit 1d4bde1

Browse files
Merge pull request #129 from woocommerce/issue/90-load-sites-remote
AccountRemote: Implements loadSites API
2 parents d877b7f + f0435e6 commit 1d4bde1

File tree

10 files changed

+376
-47
lines changed

10 files changed

+376
-47
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
B567AF2F20A0FB8F00AB6C62 /* AuthenticatedRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B567AF2C20A0FB8F00AB6C62 /* AuthenticatedRequestTests.swift */; };
4848
B567AF3020A0FB8F00AB6C62 /* DotcomRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B567AF2D20A0FB8F00AB6C62 /* DotcomRequestTests.swift */; };
4949
B567AF3120A0FB8F00AB6C62 /* JetpackRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B567AF2E20A0FB8F00AB6C62 /* JetpackRequestTests.swift */; };
50+
B56C1EB620EA757B00D749F9 /* SiteListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56C1EB520EA757B00D749F9 /* SiteListMapper.swift */; };
51+
B56C1EB820EA76F500D749F9 /* Site.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56C1EB720EA76F500D749F9 /* Site.swift */; };
52+
B56C1EBA20EA7D2C00D749F9 /* sites.json in Resources */ = {isa = PBXBuildFile; fileRef = B56C1EB920EA7D2C00D749F9 /* sites.json */; };
5053
B5969E1520A47F99005E9DF1 /* RemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5969E1420A47F99005E9DF1 /* RemoteTests.swift */; };
5154
B5BB1D0C20A2050300112D92 /* DateFormatter+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BB1D0B20A2050300112D92 /* DateFormatter+Woo.swift */; };
5255
B5BB1D1020A237FB00112D92 /* Address.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BB1D0F20A237FB00112D92 /* Address.swift */; };
@@ -113,6 +116,9 @@
113116
B567AF2C20A0FB8F00AB6C62 /* AuthenticatedRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticatedRequestTests.swift; sourceTree = "<group>"; };
114117
B567AF2D20A0FB8F00AB6C62 /* DotcomRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotcomRequestTests.swift; sourceTree = "<group>"; };
115118
B567AF2E20A0FB8F00AB6C62 /* JetpackRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JetpackRequestTests.swift; sourceTree = "<group>"; };
119+
B56C1EB520EA757B00D749F9 /* SiteListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListMapper.swift; sourceTree = "<group>"; };
120+
B56C1EB720EA76F500D749F9 /* Site.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Site.swift; sourceTree = "<group>"; };
121+
B56C1EB920EA7D2C00D749F9 /* sites.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = sites.json; sourceTree = "<group>"; };
116122
B5969E1420A47F99005E9DF1 /* RemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteTests.swift; sourceTree = "<group>"; };
117123
B5BB1D0B20A2050300112D92 /* DateFormatter+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Woo.swift"; sourceTree = "<group>"; };
118124
B5BB1D0F20A237FB00112D92 /* Address.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Address.swift; sourceTree = "<group>"; };
@@ -298,6 +304,7 @@
298304
B5C6FCCE20A3592900A4F8E4 /* OrderItem.swift */,
299305
74C8F06320EEB44800B6EDC9 /* OrderNote.swift */,
300306
B5BB1D1120A255EC00112D92 /* OrderStatus.swift */,
307+
B56C1EB720EA76F500D749F9 /* Site.swift */,
301308
);
302309
path = Model;
303310
sourceTree = "<group>";
@@ -312,6 +319,7 @@
312319
74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */,
313320
74C8F06520EEB76400B6EDC9 /* order-notes.json */,
314321
74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */,
322+
B56C1EB920EA7D2C00D749F9 /* sites.json */,
315323
);
316324
path = Responses;
317325
sourceTree = "<group>";
@@ -323,6 +331,7 @@
323331
B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */,
324332
B5C6FCD320A373BA00A4F8E4 /* OrderMapper.swift */,
325333
B567AF2A20A0FA4200AB6C62 /* OrderListMapper.swift */,
334+
B56C1EB520EA757B00D749F9 /* SiteListMapper.swift */,
326335
74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */,
327336
);
328337
path = Mapper;
@@ -473,6 +482,7 @@
473482
B505F6D520BEE4E700BB1B69 /* me.json in Resources */,
474483
B5C6FCD620A3768900A4F8E4 /* order.json in Resources */,
475484
B559EBAA20A0B5CD00836CD4 /* orders-load-all.json in Resources */,
485+
B56C1EBA20EA7D2C00D749F9 /* sites.json in Resources */,
476486
CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */,
477487
74C8F07020EEC3A800B6EDC9 /* broken-notes.json in Resources */,
478488
);
@@ -544,13 +554,15 @@
544554
isa = PBXSourcesBuildPhase;
545555
buildActionMask = 2147483647;
546556
files = (
557+
B56C1EB620EA757B00D749F9 /* SiteListMapper.swift in Sources */,
547558
B557DA1A20979D66005962F4 /* Settings.swift in Sources */,
548559
741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */,
549560
74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */,
550561
B5BB1D0C20A2050300112D92 /* DateFormatter+Woo.swift in Sources */,
551562
B567AF2520A0CCA300AB6C62 /* AuthenticatedRequest.swift in Sources */,
552563
B505F6EA20BEFC3700BB1B69 /* MockupNetwork.swift in Sources */,
553564
B557DA0220975500005962F4 /* JetpackRequest.swift in Sources */,
565+
B56C1EB820EA76F500D749F9 /* Site.swift in Sources */,
554566
B505F6CD20BEE37E00BB1B69 /* AccountMapper.swift in Sources */,
555567
B557DA0D20975DB1005962F4 /* WordPressAPIVersion.swift in Sources */,
556568
B557DA1D20979E7D005962F4 /* Order.swift in Sources */,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Foundation
2+
3+
4+
/// Mapper: SiteList
5+
///
6+
class SiteListMapper: Mapper {
7+
8+
/// (Attempts) to convert a dictionary into [Site].
9+
///
10+
func map(response: Data) throws -> [Site] {
11+
let decoder = JSONDecoder()
12+
decoder.dateDecodingStrategy = .formatted(DateFormatter.Defaults.dateTimeFormatter)
13+
14+
return try decoder.decode(SiteListEnvelope.self, from: response).sites
15+
}
16+
}
17+
18+
19+
/// SiteList Disposable Entity:
20+
/// `Load All Sites` endpoint returns all of its orders within the `sites` key. This entity
21+
/// allows us to do parse all the things with JSONDecoder.
22+
///
23+
private struct SiteListEnvelope: Decodable {
24+
let sites: [Site]
25+
26+
private enum CodingKeys: String, CodingKey {
27+
case sites = "sites"
28+
}
29+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Foundation
2+
3+
4+
/// Represents a WordPress.com Site.
5+
///
6+
public struct Site: Decodable {
7+
8+
/// WordPress.com Site Identifier.
9+
///
10+
let siteID: Int
11+
12+
/// Site's Name.
13+
///
14+
let name: String
15+
16+
/// Site's Description.
17+
///
18+
let description: String
19+
20+
/// Site's URL.
21+
///
22+
let url: String
23+
24+
/// Indicates if this site hosts a WordPress Store.
25+
///
26+
let isWordPressStore: Bool
27+
28+
29+
/// Designated Initializer.
30+
///
31+
public init(from decoder: Decoder) throws {
32+
let siteContainer = try decoder.container(keyedBy: SiteKeys.self)
33+
34+
siteID = try siteContainer.decode(Int.self, forKey: .siteID)
35+
name = try siteContainer.decode(String.self, forKey: .name)
36+
description = try siteContainer.decode(String.self, forKey: .description)
37+
url = try siteContainer.decode(String.self, forKey: .url)
38+
39+
let optionsContainer = try siteContainer.nestedContainer(keyedBy: OptionKeys.self, forKey: .options)
40+
isWordPressStore = try optionsContainer.decode(Bool.self, forKey: .isWordPressStore)
41+
}
42+
}
43+
44+
45+
/// Defines all of the Site CodingKeys.
46+
///
47+
private extension Site {
48+
49+
enum SiteKeys: String, CodingKey {
50+
case siteID = "ID"
51+
case name = "name"
52+
case description = "description"
53+
case url = "URL"
54+
case options = "options"
55+
}
56+
57+
enum OptionKeys: String, CodingKey {
58+
case isWordPressStore = "is_wpcom_store"
59+
}
60+
}

Networking/Networking/Remote/AccountRemote.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,19 @@ public class AccountRemote: Remote {
1515

1616
enqueue(request, mapper: mapper, completion: completion)
1717
}
18+
19+
20+
/// Loads the Sites collection associated with the WordPress.com User.
21+
///
22+
public func loadSites(completion: @escaping ([Site]?, Error?) -> Void) {
23+
let path = "me/sites"
24+
let parameters = [
25+
"fields": "ID,name,description,URL,options"
26+
]
27+
28+
let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .get, path: path, parameters: parameters)
29+
let mapper = SiteListMapper()
30+
31+
enqueue(request, mapper: mapper, completion: completion)
32+
}
1833
}

Networking/NetworkingTests/Mapper/AccountMapperTests.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@ class AccountMapperTests: XCTestCase {
2020
XCTAssertEqual(account.userID, 78972699)
2121
XCTAssertEqual(account.username, "apiexamples")
2222
}
23+
24+
/// Verifies that all of the Site fields are properly parsed.
25+
///
26+
func testSiteFieldsAreProperlyParsed() {
27+
let sites = mapLoadSitesResponse()
28+
XCTAssert(sites?.count == 2)
29+
30+
let first = sites!.first!
31+
XCTAssertEqual(first.siteID, 1112233334444555)
32+
XCTAssertEqual(first.name, "Testing Blog")
33+
XCTAssertEqual(first.description, "Testing Tagline")
34+
XCTAssertEqual(first.url, "https://some-testing-url.testing.blog")
35+
XCTAssertEqual(first.isWordPressStore, true)
36+
37+
let second = sites!.last!
38+
XCTAssertEqual(second.siteID, 11122333344446666)
39+
XCTAssertEqual(second.name, "Thoughts")
40+
XCTAssertEqual(second.description, "Your Favorite Blog")
41+
XCTAssertEqual(second.url, "https://thoughts.testing.blog")
42+
XCTAssertEqual(second.isWordPressStore, false)
43+
}
2344
}
2445

2546

@@ -28,7 +49,7 @@ class AccountMapperTests: XCTestCase {
2849
//
2950
private extension AccountMapperTests {
3051

31-
/// Returns the AccountMapper output upon receiving `me` (Data Encoded)
52+
/// Returns the AccountMapper output upon receiving `me` mockup response (Data Encoded).
3253
///
3354
func mapLoadAccountResponse() -> Account? {
3455
guard let response = Loader.contentsOf("me") else {
@@ -37,4 +58,14 @@ private extension AccountMapperTests {
3758

3859
return try? AccountMapper().map(response: response)
3960
}
61+
62+
/// Returns the SiteListMapper output upon receiving `me/sites` mockup response (Data Encoded).
63+
///
64+
func mapLoadSitesResponse() -> [Site]? {
65+
guard let response = Loader.contentsOf("sites") else {
66+
return nil
67+
}
68+
69+
return try? SiteListMapper().map(response: response)
70+
}
4071
}

0 commit comments

Comments
 (0)