Skip to content

Commit 2e6985a

Browse files
committed
Add DomainRemote for domain suggestions endpoint.
1 parent 3447bda commit 2e6985a

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
021EAA5625493B3600AA8CCD /* OrderItemAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021EAA5525493B3600AA8CCD /* OrderItemAttribute.swift */; };
2727
022902D422E2436400059692 /* stats_module_disabled_error.json in Resources */ = {isa = PBXBuildFile; fileRef = 022902D122E2436300059692 /* stats_module_disabled_error.json */; };
2828
022902D622E2436400059692 /* no_stats_permission_error.json in Resources */ = {isa = PBXBuildFile; fileRef = 022902D322E2436400059692 /* no_stats_permission_error.json */; };
29+
023930632918FF5400B2632F /* DomainRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023930622918FF5400B2632F /* DomainRemote.swift */; };
30+
0239306B291A96F800B2632F /* DomainRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0239306A291A96F800B2632F /* DomainRemoteTests.swift */; };
31+
0239306D291A973F00B2632F /* domain-suggestions.json in Resources */ = {isa = PBXBuildFile; fileRef = 0239306C291A973F00B2632F /* domain-suggestions.json */; };
2932
025CA2C0238EB8CB00B05C81 /* ProductShippingClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CA2BF238EB8CB00B05C81 /* ProductShippingClass.swift */; };
3033
025CA2C2238EBBAA00B05C81 /* ProductShippingClassListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CA2C1238EBBAA00B05C81 /* ProductShippingClassListMapper.swift */; };
3134
025CA2C4238EBC4300B05C81 /* ProductShippingClassRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CA2C3238EBC4300B05C81 /* ProductShippingClassRemote.swift */; };
@@ -767,6 +770,9 @@
767770
021EAA5525493B3600AA8CCD /* OrderItemAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderItemAttribute.swift; sourceTree = "<group>"; };
768771
022902D122E2436300059692 /* stats_module_disabled_error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = stats_module_disabled_error.json; sourceTree = "<group>"; };
769772
022902D322E2436400059692 /* no_stats_permission_error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = no_stats_permission_error.json; sourceTree = "<group>"; };
773+
023930622918FF5400B2632F /* DomainRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainRemote.swift; sourceTree = "<group>"; };
774+
0239306A291A96F800B2632F /* DomainRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainRemoteTests.swift; sourceTree = "<group>"; };
775+
0239306C291A973F00B2632F /* domain-suggestions.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "domain-suggestions.json"; sourceTree = "<group>"; };
770776
025CA2BF238EB8CB00B05C81 /* ProductShippingClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductShippingClass.swift; sourceTree = "<group>"; };
771777
025CA2C1238EBBAA00B05C81 /* ProductShippingClassListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductShippingClassListMapper.swift; sourceTree = "<group>"; };
772778
025CA2C3238EBC4300B05C81 /* ProductShippingClassRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductShippingClassRemote.swift; sourceTree = "<group>"; };
@@ -1720,6 +1726,7 @@
17201726
DE34051E28BDFB0B00CF0D97 /* JetpackConnectionRemoteTests.swift */,
17211727
68BD37B228D9B8BD00C2A517 /* CustomerRemoteTests.swift */,
17221728
68F48B0E28E3BB850045C15B /* WCAnalyticsCustomerRemoteTests.swift */,
1729+
0239306A291A96F800B2632F /* DomainRemoteTests.swift */,
17231730
);
17241731
path = Remote;
17251732
sourceTree = "<group>";
@@ -1859,6 +1866,7 @@
18591866
AEF94584272974F2001DCCFB /* TelemetryRemote.swift */,
18601867
68CB800F28D89A0400E169F8 /* CustomerRemote.swift */,
18611868
68F48B0A28E3B1CD0045C15B /* WCAnalyticsCustomerRemote.swift */,
1869+
023930622918FF5400B2632F /* DomainRemote.swift */,
18621870
);
18631871
path = Remote;
18641872
sourceTree = "<group>";
@@ -1985,6 +1993,7 @@
19851993
028CB713290223CB00331C09 /* create-account-error-password.json */,
19861994
028CB71A290224D700331C09 /* create-account-error-username.json */,
19871995
028CB712290223CB00331C09 /* create-account-success.json */,
1996+
0239306C291A973F00B2632F /* domain-suggestions.json */,
19881997
DE50295F28C609A300551736 /* jetpack-connected-user.json */,
19891998
DE34051A28BDF12C00CF0D97 /* jetpack-connection-url.json */,
19901999
DE50296228C609DE00551736 /* jetpack-user-not-connected.json */,
@@ -2886,6 +2895,7 @@
28862895
3158FE8426129F3A00E566B9 /* wcpay-account-unknown-status.json in Resources */,
28872896
314EDF2B27C02CD300A56B6F /* stripe-account-complete-null-descriptor.json in Resources */,
28882897
31054720262E2F9D00C5C02B /* wcpay-payment-intent-processing.json in Resources */,
2898+
0239306D291A973F00B2632F /* domain-suggestions.json in Resources */,
28892899
);
28902900
runOnlyForDeploymentPostprocessing = 0;
28912901
};
@@ -2964,6 +2974,7 @@
29642974
26455E2425F66982008A1D32 /* ProductAttributeTermRemote.swift in Sources */,
29652975
036563DD29069BE400D84BFD /* JustInTimeMessageListMapper.swift in Sources */,
29662976
7426CA0D21AF27B9004E9FFC /* SiteAPIRemote.swift in Sources */,
2977+
023930632918FF5400B2632F /* DomainRemote.swift in Sources */,
29672978
451A97D1260A03900059D135 /* ShippingLabelCustomPackage.swift in Sources */,
29682979
DE34051328BDCA5100CF0D97 /* WordPressOrgRequest.swift in Sources */,
29692980
D88D5A45230BC6F9007B6E01 /* ProductReviewsRemote.swift in Sources */,
@@ -3338,6 +3349,7 @@
33383349
74AB5B4D21AF354E00859C12 /* SiteAPIMapperTests.swift in Sources */,
33393350
68F48B0F28E3BB850045C15B /* WCAnalyticsCustomerRemoteTests.swift in Sources */,
33403351
93D8BC01226BC20600AD2EB3 /* AccountSettingsRemoteTests.swift in Sources */,
3352+
0239306B291A96F800B2632F /* DomainRemoteTests.swift in Sources */,
33413353
262E5AD5255ACD6F000B2416 /* PaymentGatewayListMapperTests.swift in Sources */,
33423354
03DCB7442624AD9B00C8953D /* CouponListMapperTests.swift in Sources */,
33433355
AE2D6623272A8F6E004A2C3A /* TelemetryRemoteTests.swift in Sources */,
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import Foundation
2+
3+
/// Protocol for `DomainRemote` mainly used for mocking.
4+
public protocol DomainRemoteProtocol {
5+
/// Loads domain suggestions that are free (`*.wordpress.com` only) based on the query.
6+
/// - Parameter query: What the domain suggestions are based on.
7+
/// - Returns: The result of free domain suggestions.
8+
func loadFreeDomainSuggestions(query: String) async -> Result<[FreeDomainSuggestion], Error>
9+
}
10+
11+
/// Domain: Remote Endpoints
12+
///
13+
public class DomainRemote: Remote, DomainRemoteProtocol {
14+
public func loadFreeDomainSuggestions(query: String) async -> Result<[FreeDomainSuggestion], Error> {
15+
let path = Path.domainSuggestions
16+
let parameters: [String: Any] = [
17+
ParameterKey.query: query,
18+
ParameterKey.quantity: Defaults.domainSuggestionsQuantity,
19+
ParameterKey.wordPressDotComSubdomainsOnly: true
20+
]
21+
let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .get, path: path, parameters: parameters)
22+
do {
23+
let suggestions: [FreeDomainSuggestion] = try await enqueue(request)
24+
return .success(suggestions)
25+
} catch {
26+
return .failure(error)
27+
}
28+
}
29+
}
30+
31+
/// Necessary data for a free domain suggestion.
32+
public struct FreeDomainSuggestion: Decodable, Equatable {
33+
/// Domain name.
34+
public let name: String
35+
/// Theoretically `true` for all domains in the result, but the client side can still filter any exceptions in the UI.
36+
public let isFree: Bool
37+
38+
public init(name: String, isFree: Bool) {
39+
self.name = name
40+
self.isFree = isFree
41+
}
42+
43+
private enum CodingKeys: String, CodingKey {
44+
case name = "domain_name"
45+
case isFree = "is_free"
46+
}
47+
}
48+
49+
// MARK: - Constants
50+
//
51+
private extension DomainRemote {
52+
enum Defaults {
53+
static let domainSuggestionsQuantity = 20
54+
}
55+
56+
enum ParameterKey {
57+
/// Term (e.g "flowers") or domain name (e.g. "flowers.com") to search alternative domain names from.
58+
static let query = "query"
59+
/// Maximum number of suggestions to return.
60+
static let quantity = "quantity"
61+
/// Whether to restrict suggestions to only wordpress.com subdomains. If `true`, only `quantity` and `query` parameters are respected.
62+
static let wordPressDotComSubdomainsOnly = "only_wordpressdotcom"
63+
}
64+
65+
enum Path {
66+
static let domainSuggestions = "domains/suggestions"
67+
}
68+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import XCTest
2+
@testable import Networking
3+
4+
final class DomainRemoteTests: XCTestCase {
5+
/// Mock network wrapper.
6+
private var network: MockNetwork!
7+
8+
override func setUp() {
9+
super.setUp()
10+
network = MockNetwork()
11+
}
12+
13+
override func tearDown() {
14+
network = nil
15+
super.tearDown()
16+
}
17+
18+
func test_loadFreeDomainSuggestions_returns_suggestions_on_success() async throws {
19+
// Given
20+
let remote = DomainRemote(network: network)
21+
network.simulateResponse(requestUrlSuffix: "domains/suggestions", filename: "domain-suggestions")
22+
23+
// When
24+
let result = await remote.loadFreeDomainSuggestions(query: "domain")
25+
26+
// Then
27+
XCTAssertTrue(result.isSuccess)
28+
let suggestions = try XCTUnwrap(result.get())
29+
XCTAssertEqual(suggestions, [
30+
.init(name: "domaintestingtips.wordpress.com", isFree: true),
31+
.init(name: "domaintestingtoday.wordpress.com", isFree: true),
32+
])
33+
}
34+
35+
func test_loadFreeDomainSuggestions_returns_error_on_empty_response() async throws {
36+
// Given
37+
let remote = DomainRemote(network: network)
38+
39+
// When
40+
let result = await remote.loadFreeDomainSuggestions(query: "domain")
41+
42+
// Then
43+
XCTAssertTrue(result.isFailure)
44+
let error = try XCTUnwrap(result.failure as? NetworkError)
45+
XCTAssertEqual(error, .notFound)
46+
}
47+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"domain_name": "domaintestingtips.wordpress.com",
4+
"cost": "Free",
5+
"is_free": true
6+
},
7+
{
8+
"domain_name": "domaintestingtoday.wordpress.com",
9+
"cost": "Free",
10+
"is_free": true
11+
}
12+
]

0 commit comments

Comments
 (0)