Skip to content

Commit e725b3b

Browse files
committed
Networking layer changes for 2 domain settings endpoints.
1 parent 1f4fddf commit e725b3b

File tree

11 files changed

+466
-15
lines changed

11 files changed

+466
-15
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@
7070
02AF07EA27492DBC00B2D81E /* WordPressMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AF07E927492DBC00B2D81E /* WordPressMedia.swift */; };
7171
02AF07EC27492FDD00B2D81E /* media-library-from-wordpress-site.json in Resources */ = {isa = PBXBuildFile; fileRef = 02AF07EB27492FDD00B2D81E /* media-library-from-wordpress-site.json */; };
7272
02AF07EE27493AE700B2D81E /* media-upload-to-wordpress-site.json in Resources */ = {isa = PBXBuildFile; fileRef = 02AF07ED27493AE700B2D81E /* media-upload-to-wordpress-site.json */; };
73+
02B41A90296BC85800FE3311 /* site-domains.json in Resources */ = {isa = PBXBuildFile; fileRef = 02B41A8F296BC85800FE3311 /* site-domains.json */; };
74+
02B41A92296BEB3000FE3311 /* load-site-current-plan-success.json in Resources */ = {isa = PBXBuildFile; fileRef = 02B41A91296BEB3000FE3311 /* load-site-current-plan-success.json */; };
75+
02B41A94296C04BC00FE3311 /* load-site-plans-no-current-plan.json in Resources */ = {isa = PBXBuildFile; fileRef = 02B41A93296C04BC00FE3311 /* load-site-plans-no-current-plan.json */; };
7376
02BA23C922EEF62C009539E7 /* order-stats-v4-wcadmin-deactivated.json in Resources */ = {isa = PBXBuildFile; fileRef = 02BA23C722EEF62C009539E7 /* order-stats-v4-wcadmin-deactivated.json */; };
7477
02BA23CA22EEF62C009539E7 /* order-stats-v4-wcadmin-activated.json in Resources */ = {isa = PBXBuildFile; fileRef = 02BA23C822EEF62C009539E7 /* order-stats-v4-wcadmin-activated.json */; };
7578
02BDB83523EA98C800BCC63E /* String+HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BDB83423EA98C800BCC63E /* String+HTML.swift */; };
@@ -880,6 +883,9 @@
880883
02AF07E927492DBC00B2D81E /* WordPressMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressMedia.swift; sourceTree = "<group>"; };
881884
02AF07EB27492FDD00B2D81E /* media-library-from-wordpress-site.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-library-from-wordpress-site.json"; sourceTree = "<group>"; };
882885
02AF07ED27493AE700B2D81E /* media-upload-to-wordpress-site.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-upload-to-wordpress-site.json"; sourceTree = "<group>"; };
886+
02B41A8F296BC85800FE3311 /* site-domains.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-domains.json"; sourceTree = "<group>"; };
887+
02B41A91296BEB3000FE3311 /* load-site-current-plan-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "load-site-current-plan-success.json"; sourceTree = "<group>"; };
888+
02B41A93296C04BC00FE3311 /* load-site-plans-no-current-plan.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "load-site-plans-no-current-plan.json"; sourceTree = "<group>"; };
883889
02BA23C722EEF62C009539E7 /* order-stats-v4-wcadmin-deactivated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-v4-wcadmin-deactivated.json"; sourceTree = "<group>"; };
884890
02BA23C822EEF62C009539E7 /* order-stats-v4-wcadmin-activated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-v4-wcadmin-activated.json"; sourceTree = "<group>"; };
885891
02BDB83423EA98C800BCC63E /* String+HTML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+HTML.swift"; sourceTree = "<group>"; };
@@ -2155,7 +2161,10 @@
21552161
DE50295F28C609A300551736 /* jetpack-connected-user.json */,
21562162
DE34051A28BDF12C00CF0D97 /* jetpack-connection-url.json */,
21572163
DE50296228C609DE00551736 /* jetpack-user-not-connected.json */,
2164+
02B41A91296BEB3000FE3311 /* load-site-current-plan-success.json */,
2165+
02B41A93296C04BC00FE3311 /* load-site-plans-no-current-plan.json */,
21582166
EE8A86F0286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json */,
2167+
02B41A8F296BC85800FE3311 /* site-domains.json */,
21592168
D865CE6D278CC19A002C8520 /* stripe-location-error.json */,
21602169
D865CE6C278CC19A002C8520 /* stripe-location.json */,
21612170
D865CE6A278CA266002C8520 /* stripe-payment-intent-error.json */,
@@ -2951,6 +2960,7 @@
29512960
743E84FC22174CE100FAC9D7 /* restnoroute_error.json in Resources */,
29522961
CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */,
29532962
D8FBFF2722D529F2006E3336 /* order-stats-v4-month.json in Resources */,
2963+
02B41A92296BEB3000FE3311 /* load-site-current-plan-success.json in Resources */,
29542964
02EF1672292F0D1900D90AD6 /* load-plan-success.json in Resources */,
29552965
2685C0DE263B5A4200D9EE97 /* add-on-groups.json in Resources */,
29562966
DEC51A9B274E3206009F3DF4 /* plugin-inactive.json in Resources */,
@@ -2962,6 +2972,7 @@
29622972
DE5CA111288A3E080077BEF9 /* product-malformed-variations-and-image-alt.json in Resources */,
29632973
02DD6492248A3EC00082523E /* product-external.json in Resources */,
29642974
03DCB7522624B3BE00C8953D /* coupons-all.json in Resources */,
2975+
02B41A94296C04BC00FE3311 /* load-site-plans-no-current-plan.json in Resources */,
29652976
028CB716290223CB00331C09 /* create-account-success.json in Resources */,
29662977
036563E129069D3500D84BFD /* just-in-time-message-list.json in Resources */,
29672978
45A4B85C25D2FAB500776FB4 /* shipping-label-address-validation-error.json in Resources */,
@@ -3117,6 +3128,7 @@
31173128
2683D71024456EE4002A1589 /* categories-extra.json in Resources */,
31183129
A69FE19D2588D70E0059A96B /* order-with-deleted-refunds.json in Resources */,
31193130
E18152C228F85E0A0011A0EC /* iap-products.json in Resources */,
3131+
02B41A90296BC85800FE3311 /* site-domains.json in Resources */,
31203132
EEA658462966C67C00112DF0 /* products-ids-only-without-data.json in Resources */,
31213133
DE2095C127966EC800171F1C /* coupon-reports.json in Resources */,
31223134
453305F52459ED2700264E50 /* site-post-update.json in Resources */,

Networking/Networking/Remote/DomainRemote.swift

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ public protocol DomainRemoteProtocol {
66
/// - Parameter query: What the domain suggestions are based on.
77
/// - Returns: The result of free domain suggestions.
88
func loadFreeDomainSuggestions(query: String) async throws -> [FreeDomainSuggestion]
9+
10+
/// Loads all domains for a site.
11+
/// - Parameter siteID: ID of the site to load the domains for.
12+
/// - Returns: A list of domains.
13+
func loadDomains(siteID: Int64) async throws -> [SiteDomain]
914
}
1015

1116
/// Domain: Remote Endpoints
@@ -21,6 +26,13 @@ public class DomainRemote: Remote, DomainRemoteProtocol {
2126
let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .get, path: path, parameters: parameters)
2227
return try await enqueue(request)
2328
}
29+
30+
public func loadDomains(siteID: Int64) async throws -> [SiteDomain] {
31+
let path = "sites/\(siteID)/\(Path.domains)"
32+
let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .get, path: path)
33+
let response: SiteDomainEnvelope = try await enqueue(request)
34+
return response.domains
35+
}
2436
}
2537

2638
/// Necessary data for a free domain suggestion.
@@ -42,7 +54,7 @@ public struct FreeDomainSuggestion: Decodable, Equatable {
4254
}
4355

4456
/// Necessary data for a site's domain.
45-
public struct SiteDomain: Equatable {
57+
public struct SiteDomain: Decodable, Equatable {
4658
/// Domain name.
4759
public let name: String
4860

@@ -57,6 +69,35 @@ public struct SiteDomain: Equatable {
5769
self.isPrimary = isPrimary
5870
self.renewalDate = renewalDate
5971
}
72+
73+
/// Custom decoding implementation since `renewalDate` is an empty string instead of `null` when it's unavailable.
74+
public init(from decoder: Decoder) throws {
75+
let container = try decoder.container(keyedBy: CodingKeys.self)
76+
let name = try container.decode(String.self, forKey: .name)
77+
let isPrimary = try container.decode(Bool.self, forKey: .isPrimary)
78+
79+
let renewalDate: Date? = {
80+
guard let dateString = try? container.decodeIfPresent(String.self, forKey: .renewalDate) else {
81+
return nil
82+
}
83+
let dateFormatter = DateFormatter()
84+
dateFormatter.dateFormat = "MMMM d, yyyy"
85+
return dateFormatter.date(from: dateString)
86+
}()
87+
88+
self.init(name: name, isPrimary: isPrimary, renewalDate: renewalDate)
89+
}
90+
91+
private enum CodingKeys: String, CodingKey {
92+
case name = "domain"
93+
case isPrimary = "primary_domain"
94+
case renewalDate = "auto_renewal_date"
95+
}
96+
}
97+
98+
/// Maps to a list of domains to match the API response.
99+
private struct SiteDomainEnvelope: Decodable {
100+
let domains: [SiteDomain]
60101
}
61102

62103
// MARK: - Constants
@@ -77,5 +118,6 @@ private extension DomainRemote {
77118

78119
enum Path {
79120
static let domainSuggestions = "domains/suggestions"
121+
static let domains = "domains"
80122
}
81123
}

Networking/Networking/Remote/PaymentRemote.swift

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ public protocol PaymentRemoteProtocol {
77
/// - Returns: The WPCOM plan that matches the given product ID.
88
func loadPlan(thatMatchesID productID: Int64) async throws -> WPComPlan
99

10+
/// Loads the current WPCOM plan of a site.
11+
/// - Parameter siteID: ID of the site to load the current plan for.
12+
/// - Returns: The current WPCOM plan of the given site.
13+
func loadSiteCurrentPlan(siteID: Int64) async throws -> WPComSitePlan
14+
1015
/// Creates a cart with the given product ID for the site ID.
1116
/// - Parameters:
1217
/// - siteID: The ID of the site that the product is being added to.
@@ -28,6 +33,16 @@ public class PaymentRemote: Remote, PaymentRemoteProtocol {
2833
return plan
2934
}
3035

36+
public func loadSiteCurrentPlan(siteID: Int64) async throws -> WPComSitePlan {
37+
let path = "sites/\(siteID)/\(Path.products)"
38+
let request = DotcomRequest(wordpressApiVersion: .mark1_3, method: .get, path: path)
39+
let plansByID: [String: SiteCurrentPlanResponse] = try await enqueue(request)
40+
guard let currentPlan = plansByID.values.filter({ $0.isCurrentPlan == true }).first else {
41+
throw LoadSiteCurrentPlanError.noCurrentPlan
42+
}
43+
return .init(hasDomainCredit: currentPlan.hasDomainCredit ?? false)
44+
}
45+
3146
public func createCart(siteID: Int64, productID: Int64) async throws {
3247
let path = "\(Path.cartCreation)/\(siteID)"
3348

@@ -69,14 +84,11 @@ public struct WPComPlan: Decodable, Equatable {
6984
}
7085

7186
/// Contains necessary data for a site's WPCOM plan.
72-
public struct WPComSitePlan {
73-
/// WPCOM plan of a site.
74-
public let plan: WPComPlan
87+
public struct WPComSitePlan: Equatable {
7588
/// Whether a site has domain credit from the WPCOM plan.
7689
public let hasDomainCredit: Bool
7790

78-
public init(plan: WPComPlan, hasDomainCredit: Bool) {
79-
self.plan = plan
91+
public init(hasDomainCredit: Bool) {
8092
self.hasDomainCredit = hasDomainCredit
8193
}
8294
}
@@ -86,11 +98,28 @@ public enum LoadPlanError: Error {
8698
case noMatchingPlan
8799
}
88100

101+
/// Possible error cases from loading a site's current WPCOM plan.
102+
public enum LoadSiteCurrentPlanError: Error {
103+
case noCurrentPlan
104+
}
105+
89106
/// Possible error cases from creating cart for a site with a WPCOM plan.
90107
public enum CreateCartError: Error {
91108
case productNotInCart
92109
}
93110

111+
/// Contains necessary data for handling the remote response from loading a site's current plan.
112+
/// The fields are all optional because only the current plan has these fields.
113+
private struct SiteCurrentPlanResponse: Decodable {
114+
let isCurrentPlan: Bool?
115+
let hasDomainCredit: Bool?
116+
117+
private enum CodingKeys: String, CodingKey {
118+
case isCurrentPlan = "current_plan"
119+
case hasDomainCredit = "has_domain_credit"
120+
}
121+
}
122+
94123
/// Contains necessary data for handling the remote response from creating a cart.
95124
private struct CreateCartResponse: Decodable {
96125
let products: [Product]

Networking/Networking/Requests/DotcomRequest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ struct DotcomRequest: Request {
5353

5454
func responseDataValidator() -> ResponseDataValidator {
5555
switch wordpressApiVersion {
56-
case .mark1_1, .mark1_2, .mark1_5:
56+
case .mark1_1, .mark1_2, .mark1_3, .mark1_5:
5757
return DotcomValidator()
5858
case .wpcomMark2, .wpMark2:
5959
return WordPressApiValidator()

Networking/Networking/Settings/WordPressAPIVersion.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ enum WordPressAPIVersion: String {
1313
///
1414
case mark1_2 = "rest/v1.2/"
1515

16+
/// WordPress.com Endpoint Mark 1.3
17+
///
18+
case mark1_3 = "rest/v1.3/"
19+
1620
/// WordPress.com Endpoint Mark 1.5
1721
///
1822
case mark1_5 = "rest/v1.5/"

Networking/NetworkingTests/Remote/DomainRemoteTests.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ final class DomainRemoteTests: XCTestCase {
1616
super.tearDown()
1717
}
1818

19+
// MARK: - `loadFreeDomainSuggestions`
20+
1921
func test_loadFreeDomainSuggestions_returns_suggestions_on_success() async throws {
2022
// Given
2123
let remote = DomainRemote(network: network)
@@ -37,4 +39,32 @@ final class DomainRemoteTests: XCTestCase {
3739

3840
await assertThrowsError({_ = try await remote.loadFreeDomainSuggestions(query: "domain")}, errorAssert: { ($0 as? NetworkError) == .notFound })
3941
}
42+
43+
// MARK: - `loadDomains`
44+
45+
func test_loadDomains_returns_domains_on_success() async throws {
46+
// Given
47+
let remote = DomainRemote(network: network)
48+
network.simulateResponse(requestUrlSuffix: "domains", filename: "site-domains")
49+
50+
// When
51+
let domains = try await remote.loadDomains(siteID: 23)
52+
53+
// Then
54+
let dateFormatter = DateFormatter()
55+
dateFormatter.dateFormat = "MMMM d, yyyy"
56+
let renewalDate = try XCTUnwrap(dateFormatter.date(from: "December 10, 2023"))
57+
XCTAssertEqual(domains, [
58+
.init(name: "crabparty.wpcomstaging.com", isPrimary: true),
59+
.init(name: "crabparty.com", isPrimary: false, renewalDate: renewalDate),
60+
.init(name: "crabparty.wordpress.com", isPrimary: false)
61+
])
62+
}
63+
64+
func test_loadDomains_returns_error_on_empty_response() async throws {
65+
// Given
66+
let remote = DomainRemote(network: network)
67+
68+
await assertThrowsError({_ = try await remote.loadDomains(siteID: 23)}, errorAssert: { ($0 as? NetworkError) == .notFound })
69+
}
4070
}

Networking/NetworkingTests/Remote/PaymentRemoteTests.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,47 @@ final class PaymentRemoteTests: XCTestCase {
5959
}
6060
}
6161

62+
// MARK: - `loadSiteCurrentPlan`
63+
64+
func test_loadSiteCurrentPlan_returns_site_plan_on_success() async throws {
65+
// Given
66+
let remote = PaymentRemote(network: network)
67+
network.simulateResponse(requestUrlSuffix: "plans", filename: "load-site-current-plan-success")
68+
69+
// When
70+
let plan = try await remote.loadSiteCurrentPlan(siteID: 134)
71+
72+
// Then
73+
XCTAssertEqual(plan, .init(hasDomainCredit: false))
74+
}
75+
76+
func test_loadSiteCurrentPlan_returns_noCurrentPlan_error_when_response_has_no_current_plan() async throws {
77+
// Given
78+
let remote = PaymentRemote(network: network)
79+
network.simulateResponse(requestUrlSuffix: "plans", filename: "load-site-plans-no-current-plan")
80+
81+
// When
82+
await assertThrowsError {
83+
_ = try await remote.loadSiteCurrentPlan(siteID: 6)
84+
} errorAssert: { error in
85+
// Then
86+
(error as? LoadSiteCurrentPlanError) == LoadSiteCurrentPlanError.noCurrentPlan
87+
}
88+
}
89+
90+
func test_loadSiteCurrentPlan_throws_notFound_error_when_no_response() async throws {
91+
// Given
92+
let remote = PaymentRemote(network: network)
93+
94+
// When
95+
await assertThrowsError {
96+
_ = try await remote.loadSiteCurrentPlan(siteID: 6)
97+
} errorAssert: { error in
98+
// Then
99+
(error as? NetworkError) == .notFound
100+
}
101+
}
102+
62103
// MARK: - `createCart`
63104

64105
func test_createCart_returns_on_success() async throws {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"1": {
3+
"formatted_original_price": "US$0",
4+
"raw_price": 0,
5+
"formatted_price": "US$0",
6+
"raw_discount": 0,
7+
"formatted_discount": "US$0",
8+
"product_slug": "free_plan",
9+
"product_name": "WordPress.com Free",
10+
"discount_reason": null,
11+
"is_domain_upgrade": null,
12+
"currency_code": "USD",
13+
"interval": -1
14+
},
15+
"1008": {
16+
"formatted_original_price": "US$0",
17+
"raw_price": 300,
18+
"formatted_price": "US$300",
19+
"raw_discount": 0,
20+
"formatted_discount": "US$0",
21+
"product_slug": "business-bundle",
22+
"product_name": "WordPress.com Business",
23+
"discount_reason": null,
24+
"is_domain_upgrade": null,
25+
"currency_code": "USD",
26+
"current_plan": true,
27+
"user_is_owner": true,
28+
"id": "",
29+
"has_domain_credit": false,
30+
"expiry": "2025-01-01T00:00:00+00:00",
31+
"free_trial": false,
32+
"subscribed_date": "2019-04-29T04:32:29+00:00",
33+
"auto_renew": false,
34+
"auto_renew_date": "2024-12-02T00:00:00+00:00",
35+
"partner_name": "",
36+
"user_facing_expiry": "2025-01-01T00:00:00+00:00",
37+
"interval": 365
38+
},
39+
"1011": {
40+
"formatted_original_price": "US$540",
41+
"raw_price": 240,
42+
"formatted_price": "US$240",
43+
"raw_discount": 300,
44+
"formatted_discount": "US$300",
45+
"product_slug": "ecommerce-bundle",
46+
"product_name": "WordPress.com eCommerce",
47+
"discount_reason": "Your recent plan purchase is deducted from the price (US$540).",
48+
"is_domain_upgrade": false,
49+
"currency_code": "USD",
50+
"can_start_trial": false,
51+
"interval": 365
52+
}
53+
}

0 commit comments

Comments
 (0)