-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
PIA-1845: Add Dedicated IP native endpoint
1 parent
0f0daed
commit 2b8ddeb
Showing
24 changed files
with
818 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
Sources/PIALibrary/Account/Data/DedicatedIPServerMapper.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
|
||
import Foundation | ||
|
||
class DedicatedIPServerMapper: DedicatedIPServerMapperType { | ||
private let dedicatedIPTokenHandler: DedicatedIPTokenHandlerType | ||
|
||
init(dedicatedIPTokenHandler: DedicatedIPTokenHandlerType) { | ||
self.dedicatedIPTokenHandler = dedicatedIPTokenHandler | ||
} | ||
|
||
func map(dedicatedIps: [DedicatedIPInformation]) -> Result<[Server], ClientError> { | ||
var dipRegions = [Server]() | ||
|
||
for dipServer in dedicatedIps { | ||
let status = DedicatedIPStatus(fromAPIStatus: dipServer.status) | ||
|
||
switch dipServer.status { | ||
case .active: | ||
|
||
guard let firstServer = Client.providers.serverProvider.currentServers.first(where: {$0.regionIdentifier == dipServer.id}) else { | ||
return .failure(ClientError.malformedResponseData) | ||
} | ||
|
||
guard let ip = dipServer.ip, let cn = dipServer.cn, let expirationTime = dipServer.dipExpire else { | ||
return .failure(ClientError.malformedResponseData) | ||
} | ||
|
||
let dipUsername = "dedicated_ip_"+dipServer.dipToken+"_"+String.random(length: 8) | ||
let expiringDate = Date(timeIntervalSince1970: TimeInterval(expirationTime)) | ||
let server = Server.ServerAddressIP(ip: ip, cn: cn, van: false) | ||
|
||
let dipRegion = Server(serial: firstServer.serial, name: firstServer.name, country: firstServer.country, hostname: firstServer.hostname, openVPNAddressesForTCP: [server], openVPNAddressesForUDP: [server], wireGuardAddressesForUDP: [server], iKEv2AddressesForUDP: [server], pingAddress: firstServer.pingAddress, geo: false, meta: nil, dipExpire: expiringDate, dipToken: dipServer.dipToken, dipStatus: status, dipUsername: dipUsername, regionIdentifier: firstServer.regionIdentifier) | ||
|
||
dipRegions.append(dipRegion) | ||
dedicatedIPTokenHandler(dedicatedIp: dipServer, dipUsername: dipUsername) | ||
|
||
default: | ||
|
||
let dipRegion = Server(serial: "", name: "", country: "", hostname: "", openVPNAddressesForTCP: [], openVPNAddressesForUDP: [], wireGuardAddressesForUDP: [], iKEv2AddressesForUDP: [], pingAddress: nil, geo: false, meta: nil, dipExpire: nil, dipToken: nil, dipStatus: status, dipUsername: nil, regionIdentifier: "") | ||
dipRegions.append(dipRegion) | ||
} | ||
} | ||
|
||
return .success(dipRegions) | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
Sources/PIALibrary/Account/Data/DedicatedIPTokenHandler.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
|
||
import Foundation | ||
|
||
class DedicatedIPTokenHandler: DedicatedIPTokenHandlerType { | ||
private let secureStore: SecureStore | ||
|
||
init(secureStore: SecureStore) { | ||
self.secureStore = secureStore | ||
} | ||
|
||
func callAsFunction(dedicatedIp: DedicatedIPInformation, dipUsername: String) { | ||
if dedicatedIp.isAboutToExpire { | ||
Macros.postNotification(.PIADIPRegionExpiring, [.token : dedicatedIp.dipToken]) | ||
} | ||
|
||
Macros.postNotification(.PIADIPCheckIP, [.token : dedicatedIp.dipToken, .ip : dedicatedIp.ip!]) | ||
|
||
secureStore.setDIPToken(dedicatedIp.dipToken) | ||
secureStore.setPassword(dedicatedIp.ip!, forDipToken: dipUsername) | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
Sources/PIALibrary/Account/Data/Networking/GetDedicatedIPsRequestConfiguration.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
|
||
import Foundation | ||
import NWHttpConnection | ||
|
||
struct GetDedicatedIPsRequestConfiguration: NetworkRequestConfigurationType { | ||
let networkRequestModule: NetworkRequestModule = .account | ||
let path: RequestAPI.Path = .dedicatedIp | ||
let httpMethod: NWHttpConnection.NWConnectionHTTPMethod = .post | ||
let contentType: NetworkRequestContentType = .json | ||
var inlcudeAuthHeaders: Bool = true | ||
var urlQueryParameters: [String : String]? = nil | ||
let responseDataType: NWDataResponseType = .jsonData | ||
|
||
var body: Data? = nil | ||
var otherHeaders: [String : String]? = nil | ||
|
||
let timeout: TimeInterval = 10 | ||
let requestQueue: DispatchQueue? = DispatchQueue(label: "getDedicatedIPs_request.queue") | ||
} |
19 changes: 19 additions & 0 deletions
19
Sources/PIALibrary/Account/Data/Networking/RenewDedicatedIPRequestConfiguration.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
|
||
import Foundation | ||
import NWHttpConnection | ||
|
||
struct RenewDedicatedIPRequestConfiguration: NetworkRequestConfigurationType { | ||
let networkRequestModule: NetworkRequestModule = .account | ||
let path: RequestAPI.Path = .renewDedicatedIp | ||
let httpMethod: NWHttpConnection.NWConnectionHTTPMethod = .post | ||
let contentType: NetworkRequestContentType = .json | ||
var inlcudeAuthHeaders: Bool = true | ||
var urlQueryParameters: [String : String]? = nil | ||
let responseDataType: NWDataResponseType = .jsonData | ||
|
||
var body: Data? = nil | ||
var otherHeaders: [String : String]? = nil | ||
|
||
let timeout: TimeInterval = 10 | ||
let requestQueue: DispatchQueue? = DispatchQueue(label: "RenewDedicatedIP_request.queue") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
Sources/PIALibrary/Account/Domain/Entities/DedicatedIPInformation.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
|
||
import Foundation | ||
|
||
struct DedicatedIPInformationResult: Codable { | ||
let result: [DedicatedIPInformation] | ||
} | ||
|
||
public struct DedicatedIPInformation: Codable { | ||
enum Status: String, Codable { | ||
case active, expired, invalid, error | ||
} | ||
|
||
let id: String? | ||
let ip: String? | ||
let cn: String? | ||
let groups: [String]? | ||
let dipExpire: Double? | ||
let dipToken: String | ||
let status: DedicatedIPInformation.Status | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case id = "id" | ||
case ip = "ip" | ||
case cn = "cn" | ||
case groups = "groups" | ||
case dipExpire = "dip_expire" | ||
case dipToken = "dip_token" | ||
case status = "status" | ||
} | ||
|
||
//Expiring in 5 days or less | ||
var isAboutToExpire: Bool { | ||
guard let dipExpire, let nextDays = Calendar.current.date(byAdding: .day, value: 5, to: Date()) | ||
else { | ||
return true | ||
} | ||
|
||
let expiringDate = Date(timeIntervalSince1970: TimeInterval(dipExpire)) | ||
return nextDays >= expiringDate | ||
} | ||
|
||
static func makeWith(data: Data) -> [DedicatedIPInformation]? { | ||
let dto = try? JSONDecoder().decode(DedicatedIPInformationResult.self, from: data) | ||
return dto?.result | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
Sources/PIALibrary/Account/Domain/Interfaces/DedicatedIPServerMapperType.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
import Foundation | ||
|
||
protocol DedicatedIPServerMapperType { | ||
func map(dedicatedIps: [DedicatedIPInformation]) -> Result<[Server], ClientError> | ||
} |
6 changes: 6 additions & 0 deletions
6
Sources/PIALibrary/Account/Domain/Interfaces/DedicatedIPTokenHandlerType.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
import Foundation | ||
|
||
protocol DedicatedIPTokenHandlerType { | ||
func callAsFunction(dedicatedIp: DedicatedIPInformation, dipUsername: String) | ||
} |
75 changes: 75 additions & 0 deletions
75
Sources/PIALibrary/Account/Domain/UseCases/GetDedicatedIPsUseCase.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
|
||
import Foundation | ||
|
||
public protocol GetDedicatedIPsUseCaseType { | ||
typealias Completion = ((Result<[DedicatedIPInformation], NetworkRequestError>) -> Void) | ||
func callAsFunction(dipTokens: [String], completion: @escaping Completion) | ||
} | ||
|
||
class GetDedicatedIPsUseCase: GetDedicatedIPsUseCaseType { | ||
private let networkClient: NetworkRequestClientType | ||
private let refreshAuthTokensChecker: RefreshAuthTokensCheckerType | ||
|
||
init(networkClient: NetworkRequestClientType, refreshAuthTokensChecker: RefreshAuthTokensCheckerType) { | ||
self.networkClient = networkClient | ||
self.refreshAuthTokensChecker = refreshAuthTokensChecker | ||
} | ||
|
||
func callAsFunction(dipTokens: [String], completion: @escaping Completion) { | ||
refreshAuthTokensChecker.refreshIfNeeded { [weak self] error in | ||
guard let self else { return } | ||
if let error { | ||
completion(.failure(error)) | ||
} else { | ||
networkClient.executeRequest(with: makeConfiguration(dipTokens: dipTokens)) { error, dataResponse in | ||
if let error { | ||
self.handleErrorResponse(error, completion: completion) | ||
} else if let dataResponse { | ||
self.handleDataResponse(dataResponse, completion: completion) | ||
} else { | ||
completion(.failure(NetworkRequestError.allConnectionAttemptsFailed())) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private func makeConfiguration(dipTokens: [String]) -> GetDedicatedIPsRequestConfiguration { | ||
var configuration = GetDedicatedIPsRequestConfiguration() | ||
|
||
let bodyDataDict = ["tokens": dipTokens] | ||
|
||
if let bodyData = try? JSONEncoder().encode(bodyDataDict) { | ||
configuration.body = bodyData | ||
} | ||
|
||
return configuration | ||
} | ||
|
||
private func handleErrorResponse(_ error: NetworkRequestError, completion: @escaping GetDedicatedIPsUseCaseType.Completion) { | ||
switch error { | ||
case .allConnectionAttemptsFailed(let statusCode): | ||
completion(.failure(statusCode == 401 ? NetworkRequestError.unauthorized : error)) | ||
return | ||
case .connectionError(statusCode: let statusCode, message: _): | ||
completion(.failure(statusCode == 401 ? NetworkRequestError.unauthorized : error)) | ||
return | ||
default: | ||
completion(.failure(error)) | ||
} | ||
} | ||
|
||
private func handleDataResponse(_ dataResponse: NetworkRequestResponseType, completion: @escaping GetDedicatedIPsUseCaseType.Completion) { | ||
guard let dataResponseContent = dataResponse.data else { | ||
completion(.failure(NetworkRequestError.noDataContent)) | ||
return | ||
} | ||
|
||
guard let dto = DedicatedIPInformation.makeWith(data: dataResponseContent) else { | ||
completion(.failure(NetworkRequestError.unableToDecodeDataContent)) | ||
return | ||
} | ||
|
||
completion(.success(dto)) | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
Sources/PIALibrary/Account/Domain/UseCases/RenewDedicatedIPUseCase.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
|
||
import Foundation | ||
|
||
public protocol RenewDedicatedIPUseCaseType { | ||
typealias Completion = ((Result<Void, NetworkRequestError>) -> Void) | ||
func callAsFunction(dipToken: String, completion: @escaping Completion) | ||
} | ||
|
||
class RenewDedicatedIPUseCase: RenewDedicatedIPUseCaseType { | ||
private let networkClient: NetworkRequestClientType | ||
private let refreshAuthTokensChecker: RefreshAuthTokensCheckerType | ||
|
||
init(networkClient: NetworkRequestClientType, refreshAuthTokensChecker: RefreshAuthTokensCheckerType) { | ||
self.networkClient = networkClient | ||
self.refreshAuthTokensChecker = refreshAuthTokensChecker | ||
} | ||
|
||
func callAsFunction(dipToken: String, completion: @escaping Completion) { | ||
refreshAuthTokensChecker.refreshIfNeeded { [weak self] error in | ||
guard let self else { return } | ||
if let error { | ||
completion(.failure(error)) | ||
} else { | ||
networkClient.executeRequest(with: makeConfiguration(dipToken: dipToken)) { error, response in | ||
if let error { | ||
self.handleErrorResponse(error, completion: completion) | ||
return | ||
} | ||
|
||
completion(.success(())) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private func makeConfiguration(dipToken: String) -> RenewDedicatedIPRequestConfiguration { | ||
var configuration = RenewDedicatedIPRequestConfiguration() | ||
|
||
let bodyDataDict = ["token": dipToken] | ||
|
||
if let bodyData = try? JSONEncoder().encode(bodyDataDict) { | ||
configuration.body = bodyData | ||
} | ||
|
||
return configuration | ||
} | ||
|
||
private func handleErrorResponse(_ error: NetworkRequestError, completion: @escaping RenewDedicatedIPUseCaseType.Completion) { | ||
switch error { | ||
case .allConnectionAttemptsFailed(let statusCode): | ||
completion(.failure(statusCode == 401 ? NetworkRequestError.unauthorized : error)) | ||
return | ||
case .connectionError(statusCode: let statusCode, message: let message): | ||
completion(.failure(statusCode == 401 ? NetworkRequestError.unauthorized : error)) | ||
return | ||
default: | ||
completion(.failure(error)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
import Foundation | ||
|
||
class MockDedicatedIPServerMapper: DedicatedIPServerMapperType { | ||
func map(dedicatedIps: [DedicatedIPInformation]) -> Result<[Server], ClientError> { | ||
return .success([]) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
import Foundation | ||
|
||
class MockGetDedicatedIPsUseCase: GetDedicatedIPsUseCaseType { | ||
func callAsFunction(dipTokens: [String], completion: @escaping Completion) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
import Foundation | ||
|
||
class MockRenewDedicatedIPUseCase: RenewDedicatedIPUseCaseType { | ||
func callAsFunction(dipToken: String, completion: @escaping Completion) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
Sources/PIALibrary/Server/CompositionRoot/ServerProviderFactory.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
|
||
import Foundation | ||
|
||
class ServerProviderFactory { | ||
static func makeDefaultServerProvider() -> ServerProvider { | ||
DefaultServerProvider(renewDedicatedIP: makeRenewDedicatedIPUseCase(), | ||
getDedicatedIPs: makeGetDedicatedIPsUseCase(), | ||
dedicatedIPServerMapper: makeDedicatedIPServerMapper()) | ||
} | ||
|
||
static func makeGetDedicatedIPsUseCase() -> GetDedicatedIPsUseCaseType { | ||
GetDedicatedIPsUseCase(networkClient: NetworkRequestFactory.maketNetworkRequestClient(), | ||
refreshAuthTokensChecker: AccountFactory.makeRefreshAuthTokensChecker()) | ||
} | ||
|
||
public static func makeDedicatedIPServerMapper() -> DedicatedIPServerMapperType { | ||
DedicatedIPServerMapper(dedicatedIPTokenHandler: makeDedicatedIPTokenHandler()) | ||
} | ||
|
||
static func makeDedicatedIPTokenHandler() -> DedicatedIPTokenHandlerType { | ||
DedicatedIPTokenHandler(secureStore: Client.database.secure) | ||
} | ||
|
||
public static func makeRenewDedicatedIPUseCase() -> RenewDedicatedIPUseCaseType { | ||
RenewDedicatedIPUseCase(networkClient: NetworkRequestFactory.maketNetworkRequestClient(), | ||
refreshAuthTokensChecker: AccountFactory.makeRefreshAuthTokensChecker()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
224 changes: 224 additions & 0 deletions
224
Tests/PIALibraryTests/Accounts/GetDedicatedIPsUseCaseTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
// | ||
// GetDedicatedIPsUseCaseTests.swift | ||
// | ||
// | ||
// Created by Said Rehouni on 20/6/24. | ||
// | ||
|
||
import XCTest | ||
@testable import PIALibrary | ||
|
||
final class GetDedicatedIPsUseCaseTests: XCTestCase { | ||
class Fixture { | ||
var networkClientMock = NetworkRequestClientMock() | ||
let refreshAuthTokensCheckerMock = RefreshAuthTokensCheckerMock() | ||
|
||
func stubRequestWithResponse(_ response: NetworkRequestResponseType) { | ||
networkClientMock.executeRequestResponse = response | ||
} | ||
|
||
func stubRequestWithError(_ error: NetworkRequestError) { | ||
networkClientMock.executeRequestError = error | ||
} | ||
} | ||
|
||
var fixture: Fixture! | ||
var sut: GetDedicatedIPsUseCase! | ||
var capturedResult: Result<[DedicatedIPInformation], NetworkRequestError>! | ||
|
||
override func setUp() { | ||
fixture = Fixture() | ||
} | ||
|
||
override func tearDown() { | ||
fixture = nil | ||
sut = nil | ||
capturedResult = nil | ||
} | ||
|
||
private func instantiateSut() { | ||
sut = GetDedicatedIPsUseCase(networkClient: fixture.networkClientMock, | ||
refreshAuthTokensChecker: fixture.refreshAuthTokensCheckerMock) | ||
} | ||
|
||
func test_getDedicatedIPs_completes_with_credentials_succesfully_when_response_is_valid() { | ||
// GIVEN Network client completes with no error and a valid response | ||
let data = """ | ||
{ | ||
"result" : [ | ||
{ | ||
"id" : "001", | ||
"ip" : "1.1.1", | ||
"cn" : "cn", | ||
"groups" : [], | ||
"dip_expire" : 13123, | ||
"dip_token" : "asdsad", | ||
"status" : "active" | ||
}, | ||
{ | ||
"id" : "002", | ||
"ip" : "1.1.2", | ||
"cn" : "cn2", | ||
"groups" : [], | ||
"dip_expire" : 13123, | ||
"dip_token" : "asdsad", | ||
"status" : "active" | ||
}, | ||
] | ||
} | ||
""".data(using: .utf8) | ||
|
||
fixture.stubRequestWithResponse(NetworkRequestResponseStub(data: data)) | ||
instantiateSut() | ||
|
||
let expectation = expectation(description: "Waiting for get dedicated ips to finish") | ||
|
||
// WHEN get dedicated ips is executed | ||
sut(dipTokens: ["asd", "ffre"]) { [weak self] result in | ||
self?.capturedResult = result | ||
expectation.fulfill() | ||
} | ||
|
||
// THEN completes with allConnectionAttemptsFailed error | ||
wait(for: [expectation], timeout: 1.0) | ||
guard case .success(let servers) = capturedResult else { | ||
XCTFail("Expected success got failure") | ||
return | ||
} | ||
|
||
let sortedServers = servers.sorted { ($0.id ?? "") < ($1.id ?? "") } | ||
XCTAssertEqual(sortedServers[0].id, "001") | ||
XCTAssertEqual(sortedServers[0].ip, "1.1.1") | ||
XCTAssertEqual(sortedServers[0].cn, "cn") | ||
XCTAssertEqual(sortedServers[0].groups, []) | ||
XCTAssertEqual(sortedServers[0].dipExpire, 13123) | ||
XCTAssertEqual(sortedServers[0].dipToken, "asdsad") | ||
XCTAssertEqual(sortedServers[0].status, .active) | ||
|
||
XCTAssertEqual(sortedServers[1].id, "002") | ||
XCTAssertEqual(sortedServers[1].ip, "1.1.2") | ||
XCTAssertEqual(sortedServers[1].cn, "cn2") | ||
XCTAssertEqual(sortedServers[1].groups, []) | ||
XCTAssertEqual(sortedServers[1].dipExpire, 13123) | ||
XCTAssertEqual(sortedServers[1].dipToken, "asdsad") | ||
XCTAssertEqual(sortedServers[1].status, .active) | ||
} | ||
|
||
func test_getDedicatedIPs_completes_with_a_allConnectionAttemptsFailed_error_when_there_is_no_error_and_no_response() { | ||
// GIVEN Network client completes with no error and no response | ||
instantiateSut() | ||
|
||
let expectation = expectation(description: "Waiting for get dedicated ips to finish") | ||
|
||
// WHEN get dedicated ips is executed | ||
sut(dipTokens: []) { [weak self] result in | ||
self?.capturedResult = result | ||
expectation.fulfill() | ||
} | ||
|
||
// THEN completes with allConnectionAttemptsFailed error | ||
wait(for: [expectation], timeout: 1.0) | ||
guard case .failure(let error) = capturedResult else { | ||
XCTFail("Expected failure got success") | ||
return | ||
} | ||
|
||
XCTAssertEqual(error, .allConnectionAttemptsFailed()) | ||
} | ||
|
||
func test_getDedicatedIPs_completes_with_a_noDataContent_error_when_there_is_response_with_no_data() { | ||
// GIVEN Network client completes with a response with invalid data | ||
fixture.stubRequestWithResponse(NetworkRequestResponseStub(data: nil)) | ||
instantiateSut() | ||
|
||
let expectation = expectation(description: "Waiting for get dedicated ips to finish") | ||
|
||
// WHEN get dedicated ips is executed | ||
sut(dipTokens: []) { [weak self] result in | ||
self?.capturedResult = result | ||
expectation.fulfill() | ||
} | ||
|
||
// THEN completes with allConnectionAttemptsFailed error | ||
wait(for: [expectation], timeout: 1.0) | ||
guard case .failure(let error) = capturedResult else { | ||
XCTFail("Expected failure got success") | ||
return | ||
} | ||
|
||
XCTAssertEqual(error, .noDataContent) | ||
} | ||
|
||
func test_getDedicatedIPs_completes_with_a_unableToDecodeDataContent_error_when_there_is_response_with_invalid_data() { | ||
// GIVEN Network client completes with a response with invalid data | ||
let data = "{ \"status\" : \"status\", \"user\" : \"username\", \"pass\" : \"password\"}" | ||
.data(using: .utf8) | ||
|
||
fixture.stubRequestWithResponse(NetworkRequestResponseStub(data: data)) | ||
instantiateSut() | ||
|
||
let expectation = expectation(description: "Waiting for get dedicated ips to finish") | ||
|
||
// WHEN get dedicated ips is executed | ||
sut(dipTokens: []) { [weak self] result in | ||
self?.capturedResult = result | ||
expectation.fulfill() | ||
} | ||
|
||
// THEN completes with allConnectionAttemptsFailed error | ||
wait(for: [expectation], timeout: 1.0) | ||
guard case .failure(let error) = capturedResult else { | ||
XCTFail("Expected failure got success") | ||
return | ||
} | ||
|
||
XCTAssertEqual(error, .unableToDecodeDataContent) | ||
} | ||
|
||
func test_getDedicatedIPs_completes_with_an_unauthorized_error_when_there_is_401_status_code() { | ||
// GIVEN Network client completes with an 401 status code error | ||
instantiateSut() | ||
fixture.stubRequestWithError(.connectionError(statusCode: 401, message: "any message")) | ||
let expectation = expectation(description: "Waiting for get dedicated ips to finish") | ||
|
||
// WHEN get dedicated ips is executed | ||
sut(dipTokens: []) { [weak self] result in | ||
self?.capturedResult = result | ||
expectation.fulfill() | ||
} | ||
|
||
// THEN completes with unauthorized error | ||
wait(for: [expectation], timeout: 1.0) | ||
guard case .failure(let error) = capturedResult else { | ||
XCTFail("Expected failure got success") | ||
return | ||
} | ||
|
||
XCTAssertEqual(error, .unauthorized) | ||
} | ||
|
||
func test_getDedicatedIPs_creates_valid_networkConfiguration() { | ||
// GIVEN | ||
let expectedBody = try? JSONEncoder().encode(["tokens": ["001", "002"]]) | ||
instantiateSut() | ||
|
||
// WHEN get dedicated ips is executed | ||
sut.callAsFunction(dipTokens: ["001", "002"]) { _ in } | ||
|
||
// THEN | ||
guard let capturedConfiguration = fixture.networkClientMock.executeRequestWithConfiguation as? GetDedicatedIPsRequestConfiguration else { | ||
XCTFail("Expected GetDedicatedIPsRequestConfiguration configuration") | ||
return | ||
} | ||
|
||
XCTAssertEqual(capturedConfiguration.networkRequestModule, .account) | ||
XCTAssertEqual(capturedConfiguration.path, .dedicatedIp) | ||
XCTAssertEqual(capturedConfiguration.httpMethod, .post) | ||
XCTAssertTrue(capturedConfiguration.inlcudeAuthHeaders) | ||
XCTAssertEqual(capturedConfiguration.contentType, .json) | ||
XCTAssertNil(capturedConfiguration.urlQueryParameters) | ||
XCTAssertEqual(capturedConfiguration.responseDataType, .jsonData) | ||
XCTAssertEqual(capturedConfiguration.timeout, 10) | ||
XCTAssertEqual(capturedConfiguration.body?.count, expectedBody?.count) | ||
} | ||
} |
129 changes: 129 additions & 0 deletions
129
Tests/PIALibraryTests/Accounts/RenewDedicatedIPUseCaseTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
|
||
import XCTest | ||
@testable import PIALibrary | ||
|
||
final class RenewDedicatedIPUseCaseTests: XCTestCase { | ||
class Fixture { | ||
var networkClientMock = NetworkRequestClientMock() | ||
let refreshAuthTokensCheckerMock = RefreshAuthTokensCheckerMock() | ||
|
||
func stubRequestWithResponse(_ response: NetworkRequestResponseType) { | ||
networkClientMock.executeRequestResponse = response | ||
} | ||
|
||
func stubRequestWithError(_ error: NetworkRequestError) { | ||
networkClientMock.executeRequestError = error | ||
} | ||
} | ||
|
||
var fixture: Fixture! | ||
var sut: RenewDedicatedIPUseCase! | ||
var capturedResult: Result<Void, NetworkRequestError>! | ||
|
||
override func setUp() { | ||
fixture = Fixture() | ||
} | ||
|
||
override func tearDown() { | ||
fixture = nil | ||
sut = nil | ||
capturedResult = nil | ||
} | ||
|
||
private func instantiateSut() { | ||
sut = RenewDedicatedIPUseCase(networkClient: fixture.networkClientMock, | ||
refreshAuthTokensChecker: fixture.refreshAuthTokensCheckerMock) | ||
} | ||
|
||
func test_renewDedicatedIPs_completes_with_success_when_there_is_no_error() { | ||
// GIVEN Network client completes with no error | ||
fixture.stubRequestWithResponse(NetworkRequestResponseStub(data: nil)) | ||
instantiateSut() | ||
|
||
let expectation = expectation(description: "Waiting for renew dedicated IP to finish") | ||
|
||
// WHEN renewDedicatedIPs is executed | ||
sut(dipToken: "dipToken") { [weak self] result in | ||
self?.capturedResult = result | ||
expectation.fulfill() | ||
} | ||
|
||
// THEN completes with success | ||
wait(for: [expectation], timeout: 1.0) | ||
guard case .success = capturedResult else { | ||
XCTFail("Expected success, got failure") | ||
return | ||
} | ||
} | ||
|
||
func test_renewDedicatedIPs_completes_with_an_unauthorized_error_when_there_is_401_status_code() { | ||
// GIVEN Network client completes with an 401 status code error | ||
instantiateSut() | ||
fixture.stubRequestWithError(.connectionError(statusCode: 401, message: "any message")) | ||
let expectation = expectation(description: "Waiting for renew dedicated IP to finish") | ||
|
||
// WHEN renewDedicatedIPs is executed | ||
sut(dipToken: "dipToken") { [weak self] result in | ||
self?.capturedResult = result | ||
expectation.fulfill() | ||
} | ||
|
||
// THEN completes with unauthorized error | ||
wait(for: [expectation], timeout: 1.0) | ||
guard case .failure(let error) = capturedResult else { | ||
XCTFail("Expected failure got success") | ||
return | ||
} | ||
|
||
XCTAssertEqual(error, .unauthorized) | ||
} | ||
|
||
func test_renewDedicatedIPs_completes_with_an_error_when_network_client_completes_with_a_non_401_error() { | ||
// GIVEN Network client completes with a non 401 error | ||
fixture.stubRequestWithError(.allConnectionAttemptsFailed(statusCode: 404)) | ||
instantiateSut() | ||
|
||
let expectation = expectation(description: "Waiting for renew dedicated IP to finish") | ||
|
||
// WHEN renewDedicatedIPs is executed | ||
sut(dipToken: "dipToken") { [weak self] result in | ||
self?.capturedResult = result | ||
expectation.fulfill() | ||
} | ||
|
||
// THEN completes with allConnectionAttemptsFailed error | ||
wait(for: [expectation], timeout: 1.0) | ||
guard case .failure(let error) = capturedResult else { | ||
XCTFail("Expected failure got success") | ||
return | ||
} | ||
|
||
XCTAssertEqual(error, .allConnectionAttemptsFailed(statusCode: 404)) | ||
} | ||
|
||
func test_renewDedicatedIPs_creates_valid_networkConfiguration() { | ||
// GIVEN | ||
let expectedBody = try? JSONEncoder().encode(["token": "001"]) | ||
instantiateSut() | ||
|
||
// WHEN renewDedicatedIPs is executed | ||
sut.callAsFunction(dipToken: "001") { _ in } | ||
|
||
// THEN | ||
guard let capturedConfiguration = fixture.networkClientMock.executeRequestWithConfiguation as? RenewDedicatedIPRequestConfiguration else { | ||
XCTFail("Expected RenewDedicatedIPRequestConfiguration configuration") | ||
return | ||
} | ||
|
||
XCTAssertEqual(capturedConfiguration.networkRequestModule, .account) | ||
XCTAssertEqual(capturedConfiguration.path, .renewDedicatedIp) | ||
XCTAssertEqual(capturedConfiguration.httpMethod, .post) | ||
XCTAssertTrue(capturedConfiguration.inlcudeAuthHeaders) | ||
XCTAssertEqual(capturedConfiguration.contentType, .json) | ||
XCTAssertNil(capturedConfiguration.urlQueryParameters) | ||
XCTAssertEqual(capturedConfiguration.responseDataType, .jsonData) | ||
XCTAssertEqual(capturedConfiguration.timeout, 10) | ||
XCTAssertEqual(capturedConfiguration.body?.count, expectedBody?.count) | ||
} | ||
|
||
} |