Skip to content

Commit ac2850c

Browse files
committed
Add unit tests for PaymentRemote.
1 parent 920accc commit ac2850c

File tree

5 files changed

+350
-5
lines changed

5 files changed

+350
-5
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@
9393
02E7FFCB256218F600C53030 /* ShippingLabelRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E7FFCA256218F600C53030 /* ShippingLabelRemoteTests.swift */; };
9494
02E7FFCF25621C7900C53030 /* shipping-label-print.json in Resources */ = {isa = PBXBuildFile; fileRef = 02E7FFCE25621C7900C53030 /* shipping-label-print.json */; };
9595
02EF1664292DADDE00D90AD6 /* PaymentRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EF1663292DADDE00D90AD6 /* PaymentRemote.swift */; };
96+
02EF166E292F0C5800D90AD6 /* PaymentRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EF166D292F0C5800D90AD6 /* PaymentRemoteTests.swift */; };
97+
02EF1670292F0CF400D90AD6 /* create-cart-success.json in Resources */ = {isa = PBXBuildFile; fileRef = 02EF166F292F0CF400D90AD6 /* create-cart-success.json */; };
98+
02EF1672292F0D1900D90AD6 /* load-plan-success.json in Resources */ = {isa = PBXBuildFile; fileRef = 02EF1671292F0D1900D90AD6 /* load-plan-success.json */; };
9699
02F096C22406691100C0C1D5 /* media-library.json in Resources */ = {isa = PBXBuildFile; fileRef = 02F096C12406691100C0C1D5 /* media-library.json */; };
97100
0313651928AE559D00EEE571 /* PaymentGatewayMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313651828AE559D00EEE571 /* PaymentGatewayMapper.swift */; };
98101
0313651B28AE60E000EEE571 /* payment-gateway-cod.json in Resources */ = {isa = PBXBuildFile; fileRef = 0313651A28AE60E000EEE571 /* payment-gateway-cod.json */; };
@@ -843,6 +846,9 @@
843846
02E7FFCA256218F600C53030 /* ShippingLabelRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelRemoteTests.swift; sourceTree = "<group>"; };
844847
02E7FFCE25621C7900C53030 /* shipping-label-print.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "shipping-label-print.json"; sourceTree = "<group>"; };
845848
02EF1663292DADDE00D90AD6 /* PaymentRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentRemote.swift; sourceTree = "<group>"; };
849+
02EF166D292F0C5800D90AD6 /* PaymentRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentRemoteTests.swift; sourceTree = "<group>"; };
850+
02EF166F292F0CF400D90AD6 /* create-cart-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "create-cart-success.json"; sourceTree = "<group>"; };
851+
02EF1671292F0D1900D90AD6 /* load-plan-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "load-plan-success.json"; sourceTree = "<group>"; };
846852
02F096C12406691100C0C1D5 /* media-library.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-library.json"; sourceTree = "<group>"; };
847853
0313651828AE559D00EEE571 /* PaymentGatewayMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentGatewayMapper.swift; sourceTree = "<group>"; };
848854
0313651A28AE60E000EEE571 /* payment-gateway-cod.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "payment-gateway-cod.json"; sourceTree = "<group>"; };
@@ -1740,6 +1746,7 @@
17401746
68F48B0E28E3BB850045C15B /* WCAnalyticsCustomerRemoteTests.swift */,
17411747
0239306A291A96F800B2632F /* DomainRemoteTests.swift */,
17421748
02616F8B292132800095BC00 /* SiteRemoteTests.swift */,
1749+
02EF166D292F0C5800D90AD6 /* PaymentRemoteTests.swift */,
17431750
);
17441751
path = Remote;
17451752
sourceTree = "<group>";
@@ -2009,6 +2016,7 @@
20092016
028CB713290223CB00331C09 /* create-account-error-password.json */,
20102017
028CB71A290224D700331C09 /* create-account-error-username.json */,
20112018
028CB712290223CB00331C09 /* create-account-success.json */,
2019+
02EF166F292F0CF400D90AD6 /* create-cart-success.json */,
20122020
0239306C291A973F00B2632F /* domain-suggestions.json */,
20132021
DE50295F28C609A300551736 /* jetpack-connected-user.json */,
20142022
DE34051A28BDF12C00CF0D97 /* jetpack-connection-url.json */,
@@ -2075,6 +2083,7 @@
20752083
E16C59B828F927CA007D55BB /* iap-order-create.json */,
20762084
26B2F74624C55A6E0065CCC8 /* leaderboards-year.json */,
20772085
268B68FC24C87E37007EBF1D /* leaderboards-year-alt.json */,
2086+
02EF1671292F0D1900D90AD6 /* load-plan-success.json */,
20782087
B505F6D420BEE4E600BB1B69 /* me.json */,
20792088
93D8BBFE226BC1DA00AD2EB3 /* me-settings.json */,
20802089
02F096C12406691100C0C1D5 /* media-library.json */,
@@ -2712,6 +2721,7 @@
27122721
743E84FC22174CE100FAC9D7 /* restnoroute_error.json in Resources */,
27132722
CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */,
27142723
D8FBFF2722D529F2006E3336 /* order-stats-v4-month.json in Resources */,
2724+
02EF1672292F0D1900D90AD6 /* load-plan-success.json in Resources */,
27152725
2685C0DE263B5A4200D9EE97 /* add-on-groups.json in Resources */,
27162726
DEC51A9B274E3206009F3DF4 /* plugin-inactive.json in Resources */,
27172727
CCF48B382628AEAE0034EA83 /* shipping-label-account-settings-no-payment-methods.json in Resources */,
@@ -2803,6 +2813,7 @@
28032813
B554FA8D2180B59700C54DFF /* notifications-load-hashes.json in Resources */,
28042814
022902D622E2436400059692 /* no_stats_permission_error.json in Resources */,
28052815
FE28F6E826842D57004465C7 /* user-complete.json in Resources */,
2816+
02EF1670292F0CF400D90AD6 /* create-cart-success.json in Resources */,
28062817
261CF2CB255C50010090D8D3 /* payment-gateway-list-half.json in Resources */,
28072818
02C254D72563999300A04423 /* order-shipping-labels.json in Resources */,
28082819
45B204BC24890B1200FE6526 /* category.json in Resources */,
@@ -3405,6 +3416,7 @@
34053416
CC851D1425E52AB500249E9C /* Decimal+ExtensionsTests.swift in Sources */,
34063417
B554FA8B2180B1D500C54DFF /* NotificationsRemoteTests.swift in Sources */,
34073418
B518662A20A09C6F00037A38 /* OrdersRemoteTests.swift in Sources */,
3419+
02EF166E292F0C5800D90AD6 /* PaymentRemoteTests.swift in Sources */,
34083420
B5969E1520A47F99005E9DF1 /* RemoteTests.swift in Sources */,
34093421
74C8F06E20EEC1E800B6EDC9 /* OrderNotesMapperTests.swift in Sources */,
34103422
45ED4F10239E8A54004F1BE3 /* TaxClassListMapperTest.swift in Sources */,

Networking/Networking/Remote/PaymentRemote.swift

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import Foundation
22

3+
/// Protocol for `PaymentRemote` mainly used for mocking.
34
public protocol PaymentRemoteProtocol {
5+
/// Loads the WPCOM plan remotely that matches the product ID.
6+
/// - Parameter productID: The ID of the WPCOM plan product.
7+
/// - Returns: The WPCOM plan that matches the given product ID.
48
func loadPlan(thatMatchesID productID: Int64) async throws -> WPComPlan
59

6-
func createCart(siteID: Int64, productID: Int64) async throws -> CreateCartResponse
10+
/// Creates a cart with the given product ID for the site ID.
11+
/// - Parameters:
12+
/// - siteID: The ID of the site that the product is being added to.
13+
/// - productID: The ID of the product to be added to the site.
14+
/// - Returns: The remote response from creating a cart.
15+
func createCart(siteID: Int64, productID: Int64) async throws
716
}
817

918
/// WPCOM Payment Endpoints
@@ -19,7 +28,7 @@ public class PaymentRemote: Remote, PaymentRemoteProtocol {
1928
return plan
2029
}
2130

22-
public func createCart(siteID: Int64, productID: Int64) async throws -> CreateCartResponse {
31+
public func createCart(siteID: Int64, productID: Int64) async throws {
2332
let path = "\(Path.cartCreation)/\(siteID)"
2433

2534
let parameters: [String: Any] = [
@@ -29,14 +38,19 @@ public class PaymentRemote: Remote, PaymentRemoteProtocol {
2938
"volume": 1
3039
]
3140
],
41+
// Necessary to create a persistent cart for later checkout, the default value is `true`.
3242
"temporary": false
3343
]
3444
let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .post, path: path, parameters: parameters)
35-
return try await enqueue(request)
45+
let response: CreateCartResponse = try await enqueue(request)
46+
guard response.products.contains(where: { $0.productID == productID }) else {
47+
throw CreateCartError.noMatchingProduct
48+
}
3649
}
3750
}
3851

39-
public struct WPComPlan: Decodable {
52+
/// Contains necessary data for rendering a WPCOM plan in the app.
53+
public struct WPComPlan: Decodable, Equatable {
4054
public let productID: Int64
4155
public let name: String
4256
public let formattedPrice: String
@@ -48,11 +62,35 @@ public struct WPComPlan: Decodable {
4862
}
4963
}
5064

65+
/// Possible error cases from loading a WPCOM plan.
5166
public enum LoadPlanError: Error {
5267
case noMatchingPlan
5368
}
5469

55-
public struct CreateCartResponse: Decodable {}
70+
/// Possible error cases from creating cart for a site with a WPCOM plan.
71+
public enum CreateCartError: Error {
72+
case noMatchingProduct
73+
}
74+
75+
/// Contains necessary data for handling the remote response from creating a cart.
76+
private struct CreateCartResponse: Decodable {
77+
let products: [Product]
78+
79+
private enum CodingKeys: String, CodingKey {
80+
case products
81+
}
82+
}
83+
84+
private extension CreateCartResponse {
85+
/// Describes a product in a cart.
86+
struct Product: Decodable {
87+
let productID: Int64
88+
89+
private enum CodingKeys: String, CodingKey {
90+
case productID = "product_id"
91+
}
92+
}
93+
}
5694

5795
// MARK: - Constants
5896
//
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import XCTest
2+
import TestKit
3+
@testable import Networking
4+
5+
final class PaymentRemoteTests: XCTestCase {
6+
/// Mock network wrapper.
7+
private var network: MockNetwork!
8+
9+
override func setUp() {
10+
super.setUp()
11+
network = MockNetwork()
12+
}
13+
14+
override func tearDown() {
15+
network = nil
16+
super.tearDown()
17+
}
18+
19+
// MARK: - `loadPlan`
20+
21+
func test_loadPlan_returns_plan_on_success() async throws {
22+
// Given
23+
let remote = PaymentRemote(network: network)
24+
network.simulateResponse(requestUrlSuffix: "plans", filename: "load-plan-success")
25+
26+
// When
27+
let plan = try await remote.loadPlan(thatMatchesID: Constants.planProductID)
28+
29+
// Then
30+
XCTAssertEqual(plan, .init(productID: Constants.planProductID,
31+
name: "WordPress.com eCommerce",
32+
formattedPrice: "NT$2,230"))
33+
}
34+
35+
func test_loadPlan_throws_noMatchingPlan_error_when_response_does_not_include_plan_with_given_id() async throws {
36+
// Given
37+
let remote = PaymentRemote(network: network)
38+
network.simulateResponse(requestUrlSuffix: "plans", filename: "load-plan-success")
39+
40+
// When
41+
await assertThrowsError {
42+
_ = try await remote.loadPlan(thatMatchesID: 9)
43+
} errorAssert: { error in
44+
// Then
45+
(error as? LoadPlanError) == .noMatchingPlan
46+
}
47+
}
48+
49+
func test_loadPlan_throws_notFound_error_when_no_response() async throws {
50+
// Given
51+
let remote = PaymentRemote(network: network)
52+
53+
// When
54+
await assertThrowsError {
55+
_ = try await remote.loadPlan(thatMatchesID: 9)
56+
} errorAssert: { error in
57+
// Then
58+
(error as? NetworkError) == .notFound
59+
}
60+
}
61+
62+
// MARK: - `createCart`
63+
64+
func test_createCart_returns_on_success() async throws {
65+
// Given
66+
let siteID: Int64 = 606
67+
let remote = PaymentRemote(network: network)
68+
network.simulateResponse(requestUrlSuffix: "me/shopping-cart/\(siteID)", filename: "create-cart-success")
69+
70+
// When
71+
do {
72+
try await remote.createCart(siteID: siteID, productID: Constants.planProductID)
73+
} catch {
74+
// Then
75+
XCTFail("Unexpected error: \(error)")
76+
}
77+
}
78+
79+
func test_createCart_throws_noMatchingProduct_error_when_response_does_not_include_plan_with_given_id() async throws {
80+
// Given
81+
let siteID: Int64 = 606
82+
let remote = PaymentRemote(network: network)
83+
network.simulateResponse(requestUrlSuffix: "me/shopping-cart/\(siteID)", filename: "create-cart-success")
84+
85+
// When
86+
await assertThrowsError {
87+
_ = try await remote.createCart(siteID: siteID, productID: 685)
88+
} errorAssert: { error in
89+
// Then
90+
(error as? CreateCartError) == .noMatchingProduct
91+
}
92+
}
93+
94+
func test_createCart_throws_notFound_error_when_no_response() async throws {
95+
// Given
96+
let remote = PaymentRemote(network: network)
97+
98+
// When
99+
await assertThrowsError {
100+
_ = try await remote.createCart(siteID: 606, productID: 685)
101+
} errorAssert: { error in
102+
// Then
103+
(error as? NetworkError) == .notFound
104+
}
105+
}
106+
}
107+
108+
private extension PaymentRemoteTests {
109+
enum Constants {
110+
static let planProductID: Int64 = 1021
111+
}
112+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
{
2+
"cart_generated_at_timestamp": 1669171867,
3+
"blog_id": 2022,
4+
"cart_key": 2022,
5+
"coupon": "",
6+
"coupon_discounts": [],
7+
"coupon_discounts_integer": [],
8+
"coupon_discounts_display": [],
9+
"is_coupon_applied": false,
10+
"has_bundle_credit": false,
11+
"next_domain_is_free": false,
12+
"next_domain_condition": "",
13+
"products": [
14+
{
15+
"product_id": 1021,
16+
"billing_plan_id": "38838",
17+
"product_name": "WordPress.com eCommerce",
18+
"product_name_en": "WordPress.com E-commerce",
19+
"product_slug": "ecommerce-bundle-monthly",
20+
"product_cost": 70,
21+
"product_cost_display": "US$70",
22+
"product_cost_integer": 7000,
23+
"meta": "",
24+
"cost": 70,
25+
"currency": "USD",
26+
"volume": 1,
27+
"quantity": null,
28+
"current_quantity": null,
29+
"price_tier_minimum_units": null,
30+
"price_tier_maximum_units": null,
31+
"free_trial": false,
32+
"introductory_offer_terms": null,
33+
"cost_before_coupon": 70,
34+
"coupon_savings": 0,
35+
"coupon_savings_integer": 0,
36+
"coupon_savings_display": "US$0",
37+
"is_sale_coupon_applied": false,
38+
"extra": {
39+
"added_from_shopping_cart": true
40+
},
41+
"bill_period": "31",
42+
"months_per_bill_period": 1,
43+
"is_domain_registration": false,
44+
"time_added_to_cart": 1669171867,
45+
"is_bundled": false,
46+
"item_original_cost": 70,
47+
"item_original_cost_integer": 7000,
48+
"item_original_cost_display": "US$70",
49+
"item_original_monthly_cost_integer": 7000,
50+
"item_original_monthly_cost_display": "US$70",
51+
"item_original_cost_for_quantity_one_integer": 7000,
52+
"item_original_cost_for_quantity_one_display": "US$70",
53+
"item_subtotal_monthly_cost_integer": 7000,
54+
"item_subtotal_monthly_cost_display": "US$70",
55+
"item_original_subtotal": 70,
56+
"item_original_subtotal_integer": 7000,
57+
"item_original_subtotal_display": "US$70",
58+
"item_subtotal": 70,
59+
"item_subtotal_integer": 7000,
60+
"item_subtotal_display": "US$70",
61+
"item_tax": 0,
62+
"item_tax_rate": 0,
63+
"item_tax_breakdown": [],
64+
"item_total": 70,
65+
"item_total_integer": 7000,
66+
"subscription_id": 0,
67+
"is_renewal": false,
68+
"domain_post_renewal_expiration_date": null,
69+
"related_monthly_plan_cost_integer": 0,
70+
"related_monthly_plan_cost_display": "",
71+
"cost_overrides": [],
72+
"is_gift_purchase": false
73+
}
74+
],
75+
"total_cost": 0,
76+
"currency": "USD",
77+
"total_cost_display": "US$0",
78+
"total_cost_integer": 0,
79+
"temporary": true,
80+
"tax": {
81+
"location": {},
82+
"display_taxes": false
83+
},
84+
"coupon_savings_total": 0,
85+
"coupon_savings_total_display": "US$0",
86+
"coupon_savings_total_integer": 0,
87+
"sub_total_with_taxes_display": "US$70",
88+
"sub_total_with_taxes_integer": 7000,
89+
"sub_total": 70,
90+
"sub_total_display": "US$70",
91+
"sub_total_integer": 7000,
92+
"total_tax": 0,
93+
"total_tax_display": "US$0",
94+
"total_tax_integer": 0,
95+
"total_tax_breakdown": [],
96+
"credits": 38,
97+
"credits_display": "US$38",
98+
"credits_integer": 38,
99+
"allowed_payment_methods": [
100+
"WPCOM_Billing_WPCOM"
101+
],
102+
"create_new_blog": false,
103+
"terms_of_service": [],
104+
"is_gift_purchase": false,
105+
"gift_details": null,
106+
"messages": {
107+
"errors": [],
108+
"success": []
109+
}
110+
}

0 commit comments

Comments
 (0)