Skip to content

Commit 54ad5d7

Browse files
committed
Merge branch 'trunk' into issue/7766-suggestion-connect-jectpack-wpcom
2 parents 73a9fb3 + 43597e2 commit 54ad5d7

File tree

22 files changed

+527
-23
lines changed

22 files changed

+527
-23
lines changed

Fakes/Fakes/Networking.generated.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,20 @@ extension CreateProductVariation {
181181
)
182182
}
183183
}
184+
extension Customer {
185+
/// Returns a "ready to use" type filled with fake values.
186+
///
187+
public static func fake() -> Customer {
188+
.init(
189+
customerID: .fake(),
190+
email: .fake(),
191+
firstName: .fake(),
192+
lastName: .fake(),
193+
billing: .fake(),
194+
shipping: .fake()
195+
)
196+
}
197+
}
184198
extension DotcomError {
185199
/// Returns a "ready to use" type filled with fake values.
186200
///

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,13 @@
323323
57BE08D82409B63800F6DCED /* reviews-missing-avatar-urls.json in Resources */ = {isa = PBXBuildFile; fileRef = 57BE08D72409B63700F6DCED /* reviews-missing-avatar-urls.json */; };
324324
57E8FED3246616AC0057CD68 /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E8FED2246616AC0057CD68 /* Result+Extensions.swift */; };
325325
6647C0161DAC6AB6570C53A7 /* Pods_Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */; };
326+
68BD37B328D9B8BD00C2A517 /* CustomerRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BD37B228D9B8BD00C2A517 /* CustomerRemoteTests.swift */; };
326327
68C87B342862D40E00A99054 /* setting-all-except-countries.json in Resources */ = {isa = PBXBuildFile; fileRef = 68C87B332862D40E00A99054 /* setting-all-except-countries.json */; };
328+
68CB800C28D87BC800E169F8 /* Customer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68CB800B28D87BC800E169F8 /* Customer.swift */; };
329+
68CB800E28D8901B00E169F8 /* CustomerMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68CB800D28D8901B00E169F8 /* CustomerMapper.swift */; };
330+
68CB801028D89A0400E169F8 /* CustomerRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68CB800F28D89A0400E169F8 /* CustomerRemote.swift */; };
331+
68CB801428D8A05200E169F8 /* CustomerMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68CB801328D8A05200E169F8 /* CustomerMapperTests.swift */; };
332+
68CB801628D8A39700E169F8 /* customer.json in Resources */ = {isa = PBXBuildFile; fileRef = 68CB801528D8A39700E169F8 /* customer.json */; };
327333
68FBC5B828928C8C00A05461 /* WooFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68FBC5B728928C8C00A05461 /* WooFoundation.framework */; };
328334
74002D6A2118B26100A63C19 /* SiteVisitStatsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */; };
329335
74002D6C2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74002D6B2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift */; };
@@ -1010,7 +1016,13 @@
10101016
5726F7332460A8F00031CAAC /* CopiableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopiableTests.swift; sourceTree = "<group>"; };
10111017
57BE08D72409B63700F6DCED /* reviews-missing-avatar-urls.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reviews-missing-avatar-urls.json"; sourceTree = "<group>"; };
10121018
57E8FED2246616AC0057CD68 /* Result+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extensions.swift"; sourceTree = "<group>"; };
1019+
68BD37B228D9B8BD00C2A517 /* CustomerRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerRemoteTests.swift; sourceTree = "<group>"; };
10131020
68C87B332862D40E00A99054 /* setting-all-except-countries.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "setting-all-except-countries.json"; sourceTree = "<group>"; };
1021+
68CB800B28D87BC800E169F8 /* Customer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Customer.swift; sourceTree = "<group>"; };
1022+
68CB800D28D8901B00E169F8 /* CustomerMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerMapper.swift; sourceTree = "<group>"; };
1023+
68CB800F28D89A0400E169F8 /* CustomerRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerRemote.swift; sourceTree = "<group>"; };
1024+
68CB801328D8A05200E169F8 /* CustomerMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerMapperTests.swift; sourceTree = "<group>"; };
1025+
68CB801528D8A39700E169F8 /* customer.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = customer.json; sourceTree = "<group>"; };
10141026
68FBC5B728928C8C00A05461 /* WooFoundation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WooFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
10151027
69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NetworkingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
10161028
74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsMapperTests.swift; sourceTree = "<group>"; };
@@ -1616,6 +1628,7 @@
16161628
FE28F6EB268436C9004465C7 /* UserRemoteTests.swift */,
16171629
077F39D926A58ED700ABEADC /* SystemStatusRemoteTests.swift */,
16181630
DE34051E28BDFB0B00CF0D97 /* JetpackConnectionRemoteTests.swift */,
1631+
68BD37B228D9B8BD00C2A517 /* CustomerRemoteTests.swift */,
16191632
);
16201633
path = Remote;
16211634
sourceTree = "<group>";
@@ -1748,6 +1761,7 @@
17481761
FE28F6E5268429B6004465C7 /* UserRemote.swift */,
17491762
077F39D526A58E4500ABEADC /* SystemStatusRemote.swift */,
17501763
AEF94584272974F2001DCCFB /* TelemetryRemote.swift */,
1764+
68CB800F28D89A0400E169F8 /* CustomerRemote.swift */,
17511765
);
17521766
path = Remote;
17531767
sourceTree = "<group>";
@@ -1855,6 +1869,7 @@
18551869
FE28F6E126840DED004465C7 /* User.swift */,
18561870
DE50295828C5BD0200551736 /* JetpackUser.swift */,
18571871
DE50295A28C5F99700551736 /* DotcomUser.swift */,
1872+
68CB800B28D87BC800E169F8 /* Customer.swift */,
18581873
);
18591874
path = Model;
18601875
sourceTree = "<group>";
@@ -2089,6 +2104,7 @@
20892104
4513382727A96DE700AE5E78 /* inbox-note.json */,
20902105
0205021B27C86B9700FB1C6B /* inbox-note-without-isRead.json */,
20912106
68C87B332862D40E00A99054 /* setting-all-except-countries.json */,
2107+
68CB801528D8A39700E169F8 /* customer.json */,
20922108
);
20932109
path = Responses;
20942110
sourceTree = "<group>";
@@ -2181,6 +2197,7 @@
21812197
02C112772742862600F4F0B4 /* WordPressSiteSettingsMapper.swift */,
21822198
0359EA1C27AADE000048DE2D /* WCPayChargeMapper.swift */,
21832199
DE34051828BDEE6A00CF0D97 /* JetpackConnectionURLMapper.swift */,
2200+
68CB800D28D8901B00E169F8 /* CustomerMapper.swift */,
21842201
);
21852202
path = Mapper;
21862203
sourceTree = "<group>";
@@ -2303,6 +2320,7 @@
23032320
DE34051C28BDF1C900CF0D97 /* JetpackConnectionURLMapperTests.swift */,
23042321
DE50296428C60A8000551736 /* JetpackUserMapperTests.swift */,
23052322
0359EA1E27AAE4680048DE2D /* WCPayChargeMapperTests.swift */,
2323+
68CB801328D8A05200E169F8 /* CustomerMapperTests.swift */,
23062324
);
23072325
path = Mapper;
23082326
sourceTree = "<group>";
@@ -2684,6 +2702,7 @@
26842702
7497376A2141F2BE0008C490 /* top-performers-week-alt.json in Resources */,
26852703
D865CE61278CA1AE002C8520 /* stripe-payment-intent-processing.json in Resources */,
26862704
743E84F222172D0A00FAC9D7 /* shipment_tracking_plugin_not_active.json in Resources */,
2705+
68CB801628D8A39700E169F8 /* customer.json in Resources */,
26872706
451A97DE260B59870059D135 /* shipping-label-packages-success.json in Resources */,
26882707
31D27C8F2602B553002EDB1D /* plugins.json in Resources */,
26892708
261CF1B4255AD6B30090D8D3 /* payment-gateway-list.json in Resources */,
@@ -2982,6 +3001,7 @@
29823001
B518662220A097C200037A38 /* Network.swift in Sources */,
29833002
B572F69A21AC475C003EEFF0 /* DevicesRemote.swift in Sources */,
29843003
3192F220260D33BB0067FEF9 /* WCPayAccount.swift in Sources */,
3004+
68CB800E28D8901B00E169F8 /* CustomerMapper.swift in Sources */,
29853005
45CCFCE227A2C9BF0012E8CB /* InboxNote.swift in Sources */,
29863006
311D412C2783BF7400052F64 /* StripeAccount.swift in Sources */,
29873007
B518662420A099BF00037A38 /* AlamofireNetwork.swift in Sources */,
@@ -3030,6 +3050,7 @@
30303050
020D07BE23D8570800FD9580 /* MediaListMapper.swift in Sources */,
30313051
0359EA1327AAC6D00048DE2D /* WCPayCardPaymentDetails.swift in Sources */,
30323052
CCB2CA9E262091CB00285CA0 /* SuccessDataResultMapper.swift in Sources */,
3053+
68CB801028D89A0400E169F8 /* CustomerRemote.swift in Sources */,
30333054
DE50295B28C5F99700551736 /* DotcomUser.swift in Sources */,
30343055
74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */,
30353056
24F98C582502EA8800F49B68 /* FeatureFlagMapper.swift in Sources */,
@@ -3039,6 +3060,7 @@
30393060
74046E1D217A6989007DD7BF /* SiteSetting.swift in Sources */,
30403061
B5BB1D1020A237FB00112D92 /* Address.swift in Sources */,
30413062
CE43066A23465F340073CBFF /* Refund.swift in Sources */,
3063+
68CB800C28D87BC800E169F8 /* Customer.swift in Sources */,
30423064
DE50295D28C6068B00551736 /* JetpackUserMapper.swift in Sources */,
30433065
B524194121AC60A700D6FC0A /* DotcomDevice.swift in Sources */,
30443066
D8EDFE2225EE88C9003D2213 /* ReaderConnectionToken.swift in Sources */,
@@ -3124,6 +3146,7 @@
31243146
CEC4BF8F234E382F008D9195 /* RefundMapperTests.swift in Sources */,
31253147
24F98C5E2502EDCF00F49B68 /* BundleWooTests.swift in Sources */,
31263148
74AB0ACA21948CE4008220CD /* CommentResultMapperTests.swift in Sources */,
3149+
68CB801428D8A05200E169F8 /* CustomerMapperTests.swift in Sources */,
31273150
02698CF824C183A5005337C4 /* ProductVariationListMapperTests.swift in Sources */,
31283151
B524194921AC659500D6FC0A /* DevicesRemoteTests.swift in Sources */,
31293152
2685C0DA263B551300D9EE97 /* AddOnGroupMapperTests.swift in Sources */,
@@ -3198,6 +3221,7 @@
31983221
45CCFCE827A2E5020012E8CB /* InboxNoteListMapperTests.swift in Sources */,
31993222
74002D6C2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift in Sources */,
32003223
0212683524C046CB00F8A892 /* MockNetwork+Path.swift in Sources */,
3224+
68BD37B328D9B8BD00C2A517 /* CustomerRemoteTests.swift in Sources */,
32013225
B554FA932180C17200C54DFF /* NoteHashListMapperTests.swift in Sources */,
32023226
CC07866526790B1100BA9AC1 /* ShippingLabelPurchaseMapperTests.swift in Sources */,
32033227
74002D6A2118B26100A63C19 /* SiteVisitStatsMapperTests.swift in Sources */,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Foundation
2+
3+
/// Mapper: Customer
4+
///
5+
struct CustomerMapper: Mapper {
6+
/// We're injecting this field by copying it in after parsing responses, because `siteID` is not returned in any of the Customer endpoints.
7+
///
8+
let siteID: Int64
9+
10+
/// (Attempts) to convert a dictionary into a `Customer` entity
11+
///
12+
func map(response: Data) throws -> Customer {
13+
let decoder = JSONDecoder()
14+
decoder.userInfo = [.siteID: siteID]
15+
let customer = try decoder.decode(CustomerEnvelope.self, from: response).customer
16+
return customer
17+
}
18+
}
19+
20+
private struct CustomerEnvelope: Decodable {
21+
let customer: Customer
22+
23+
private enum CodingKeys: String, CodingKey {
24+
case customer = "data"
25+
}
26+
}

Networking/Networking/Model/Copiable/Models+Copiable.generated.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,33 @@ extension CouponReport {
169169
}
170170
}
171171

172+
extension Customer {
173+
func copy(
174+
customerID: CopiableProp<Int64> = .copy,
175+
email: CopiableProp<String> = .copy,
176+
firstName: NullableCopiableProp<String> = .copy,
177+
lastName: NullableCopiableProp<String> = .copy,
178+
billing: NullableCopiableProp<Address> = .copy,
179+
shipping: NullableCopiableProp<Address> = .copy
180+
) -> Customer {
181+
let customerID = customerID ?? self.customerID
182+
let email = email ?? self.email
183+
let firstName = firstName ?? self.firstName
184+
let lastName = lastName ?? self.lastName
185+
let billing = billing ?? self.billing
186+
let shipping = shipping ?? self.shipping
187+
188+
return Customer(
189+
customerID: customerID,
190+
email: email,
191+
firstName: firstName,
192+
lastName: lastName,
193+
billing: billing,
194+
shipping: shipping
195+
)
196+
}
197+
}
198+
172199
extension DotcomUser {
173200
public func copy(
174201
id: CopiableProp<Int64> = .copy,
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import Foundation
2+
import Codegen
3+
4+
/// Represents a Customer entity:
5+
/// https://woocommerce.github.io/woocommerce-rest-api-docs/#customer-properties
6+
///
7+
public struct Customer: Codable, GeneratedCopiable, GeneratedFakeable {
8+
9+
/// Unique identifier for the customer
10+
public let customerID: Int64
11+
12+
/// The email address for the customer
13+
public let email: String
14+
15+
/// Customer first name
16+
public let firstName: String?
17+
18+
/// Customer last name
19+
public let lastName: String?
20+
21+
/// List of billing address data
22+
public let billing: Address?
23+
24+
/// List of shipping address data
25+
public let shipping: Address?
26+
27+
/// Customer struct initializer
28+
///
29+
public init(customerID: Int64,
30+
email: String,
31+
firstName: String?,
32+
lastName: String?,
33+
billing: Address?,
34+
shipping: Address?) {
35+
self.customerID = customerID
36+
self.email = email
37+
self.firstName = firstName
38+
self.lastName = lastName
39+
self.billing = billing
40+
self.shipping = shipping
41+
}
42+
43+
/// Public initializer for the Customer
44+
///
45+
public init(from decoder: Decoder) throws {
46+
let container = try decoder.container(keyedBy: CodingKeys.self)
47+
48+
let customerID = try container.decode(Int64.self, forKey: .customerID)
49+
let email = try container.decode(String.self, forKey: .email)
50+
let firstName = try container.decodeIfPresent(String.self, forKey: .firstName)
51+
let lastName = try container.decodeIfPresent(String.self, forKey: .lastName)
52+
let billing = try? container.decode(Address.self, forKey: .billing)
53+
let shipping = try? container.decode(Address.self, forKey: .shipping)
54+
55+
self.init(customerID: customerID,
56+
email: email,
57+
firstName: firstName,
58+
lastName: lastName,
59+
billing: billing,
60+
shipping: shipping
61+
)
62+
}
63+
}
64+
65+
/// Defines all of the Customer CodingKeys
66+
///
67+
extension Customer {
68+
enum CodingKeys: String, CodingKey {
69+
case customerID = "id"
70+
case email
71+
case firstName = "first_name"
72+
case lastName = "last_name"
73+
case billing
74+
case shipping
75+
}
76+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Foundation
2+
3+
public class CustomerRemote: Remote {
4+
/// Retrieves a `Customer`
5+
///
6+
/// - Parameters:
7+
/// - customerID: ID of the customer that will be retrieved
8+
/// - siteID: Site for which we'll fetch the customer.
9+
/// - completion: Closure to be executed upon completion.
10+
///
11+
func retrieveCustomer(for siteID: Int64, with customerID: Int64, completion: @escaping (Result<Customer, Error>) -> Void) {
12+
let path = "/customers/\(customerID)"
13+
let request = JetpackRequest(wooApiVersion: .mark3,
14+
method: .get,
15+
siteID: siteID,
16+
path: path,
17+
parameters: nil
18+
)
19+
20+
let mapper = CustomerMapper(siteID: siteID)
21+
enqueue(request, mapper: mapper, completion: completion)
22+
}
23+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import XCTest
2+
@testable import Networking
3+
4+
/// CustomerMapper Unit Tests
5+
///
6+
class CustomerMapperTests: XCTestCase {
7+
8+
/// Dummy Site ID.
9+
///
10+
private let dummySiteID: Int64 = 123
11+
12+
/// Local file that holds Customer data representing the API endpoint
13+
///
14+
private let filename: String = "customer"
15+
16+
/// Verifies that the Customer object can be mapped fron the Encoded data
17+
///
18+
func test_Customer_is_mapped_from_encoded_data() {
19+
// Given
20+
let mapper = CustomerMapper(siteID: dummySiteID)
21+
guard let data = Loader.contentsOf(filename) else {
22+
XCTFail("customer.json not found")
23+
return
24+
}
25+
26+
// When
27+
let customer = try? mapper.map(response: data)
28+
29+
// Then
30+
XCTAssertNotNil(mapper)
31+
XCTAssertNotNil(customer)
32+
}
33+
34+
/// Verifies that all of the Customer response values are parsed correctly
35+
///
36+
func test_Customer_response_values_are_correctly_parsed() throws {
37+
// Given
38+
guard let customer = try mapCustomer(from: filename) else {
39+
XCTFail()
40+
return
41+
}
42+
43+
// Then
44+
XCTAssertNotNil(customer)
45+
XCTAssertEqual(customer.customerID, 25)
46+
XCTAssertEqual(customer.email, "[email protected]")
47+
XCTAssertEqual(customer.firstName, "John")
48+
XCTAssertEqual(customer.lastName, "Doe")
49+
50+
let dummyAddresses = [customer.shipping, customer.billing].compactMap({ $0 })
51+
XCTAssertEqual(dummyAddresses.count, 2)
52+
53+
for address in dummyAddresses {
54+
XCTAssertEqual(address.firstName, "John")
55+
XCTAssertEqual(address.lastName, "Doe")
56+
XCTAssertEqual(address.company, "")
57+
XCTAssertEqual(address.address1, "969 Market")
58+
XCTAssertEqual(address.address2, "")
59+
XCTAssertEqual(address.city, "San Francisco")
60+
XCTAssertEqual(address.state, "CA")
61+
XCTAssertEqual(address.postcode, "94103")
62+
XCTAssertEqual(address.country, "US")
63+
}
64+
}
65+
}
66+
67+
private extension CustomerMapperTests {
68+
/// Returns the CustomerMapper output upon receiving `filename` (Data Encoded)
69+
///
70+
func mapCustomer(from filename: String) throws -> Customer? {
71+
guard let response = Loader.contentsOf(filename) else {
72+
return nil
73+
}
74+
return try! CustomerMapper(siteID: dummySiteID).map(response: response)
75+
}
76+
}

0 commit comments

Comments
 (0)