Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PIA-1932: Migrate delete account API to native #46

Merged
merged 1 commit into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public class AccountFactory {
accountDetailsUseCase: makeAccountDetailsUseCase(),
updateAccountUseCase: makeUpdateAccountUseCase(),
paymentUseCase: makePaymentUseCase(),
subscriptionsUseCase: makeSubscriptionsUseCase()
subscriptionsUseCase: makeSubscriptionsUseCase(),
deleteAccountUseCase: makeDeleteAccountUseCase()
)

}
Expand Down Expand Up @@ -114,4 +115,8 @@ private extension AccountFactory {
PaymentInformationDataConverter()
}

static func makeDeleteAccountUseCase() -> DeleteAccountUseCaseType {
DeleteAccountUseCase(networkClient: NetworkRequestFactory.maketNetworkRequestClient(), refreshAuthTokenChecker: makeRefreshAuthTokensChecker(), apiTokenProvider: makeAPITokenProvider(), vpnTokenProvider: makeVpnTokenProvider())
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@


import Foundation
import NWHttpConnection

struct DeleteAccountRequestConfiguration: NetworkRequestConfigurationType {

let networkRequestModule: NetworkRequestModule = .account
let path: RequestAPI.Path = .deleteAccount
let httpMethod: NWHttpConnection.NWConnectionHTTPMethod = .delete
let contentType: NetworkRequestContentType = .json
let inlcudeAuthHeaders: Bool = true
let urlQueryParameters: [String : String]? = nil
let responseDataType: NWDataResponseType = .jsonData

var otherHeaders: [String : String]? = nil
var body: Data? = nil

let timeout: TimeInterval = 10
let requestQueue: DispatchQueue? = DispatchQueue(label: "deleteAccount_request.queue")
}



15 changes: 9 additions & 6 deletions Sources/PIALibrary/Account/DefaultAccountProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ open class DefaultAccountProvider: AccountProvider, ConfigurationAccess, Databas
private let updateAccountUseCase: UpdateAccountUseCaseType
private let paymentUseCase: PaymentUseCaseType
private let subscriptionsUseCase: SubscriptionsUseCaseType
private let deleteAccountUseCase: DeleteAccountUseCaseType


init(webServices: WebServices? = nil, logoutUseCase: LogoutUseCaseType, loginUseCase: LoginUseCaseType, signupUseCase: SignupUseCaseType, apiTokenProvider: APITokenProviderType, vpnTokenProvider: VpnTokenProviderType, accountDetailsUseCase: AccountDetailsUseCaseType, updateAccountUseCase: UpdateAccountUseCaseType, paymentUseCase: PaymentUseCaseType, subscriptionsUseCase: SubscriptionsUseCaseType) {
init(webServices: WebServices? = nil, logoutUseCase: LogoutUseCaseType, loginUseCase: LoginUseCaseType, signupUseCase: SignupUseCaseType, apiTokenProvider: APITokenProviderType, vpnTokenProvider: VpnTokenProviderType, accountDetailsUseCase: AccountDetailsUseCaseType, updateAccountUseCase: UpdateAccountUseCaseType, paymentUseCase: PaymentUseCaseType, subscriptionsUseCase: SubscriptionsUseCaseType, deleteAccountUseCase: DeleteAccountUseCaseType) {
self.logoutUseCase = logoutUseCase
self.loginUseCase = loginUseCase
self.signupUseCase = signupUseCase
Expand All @@ -52,6 +53,7 @@ open class DefaultAccountProvider: AccountProvider, ConfigurationAccess, Databas
self.updateAccountUseCase = updateAccountUseCase
self.paymentUseCase = paymentUseCase
self.subscriptionsUseCase = subscriptionsUseCase
self.deleteAccountUseCase = deleteAccountUseCase
if let webServices = webServices {
customWebServices = webServices
} else {
Expand Down Expand Up @@ -424,13 +426,14 @@ open class DefaultAccountProvider: AccountProvider, ConfigurationAccess, Databas
guard isLoggedIn else {
preconditionFailure()
}
webServices.deleteAccount { (result, error) in
guard let result = result, result != false else {
callback?(error)
return

deleteAccountUseCase() { error in
DispatchQueue.main.async {
callback?(error?.asClientError())
}
callback?(nil)

}

}

public func featureFlags(_ callback: SuccessLibraryCallback?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

import Foundation


protocol DeleteAccountUseCaseType {
typealias Completion = ((NetworkRequestError?) -> Void)
func callAsFunction(completion: @escaping Completion)
}


class DeleteAccountUseCase: DeleteAccountUseCaseType {

let networkClient: NetworkRequestClientType
let refreshAuthTokenChecker: RefreshAuthTokensCheckerType
let apiTokenProvider: APITokenProviderType
let vpnTokenProvider: VpnTokenProviderType

init(networkClient: NetworkRequestClientType, refreshAuthTokenChecker: RefreshAuthTokensCheckerType, apiTokenProvider: APITokenProviderType, vpnTokenProvider: VpnTokenProviderType) {
self.networkClient = networkClient
self.refreshAuthTokenChecker = refreshAuthTokenChecker
self.apiTokenProvider = apiTokenProvider
self.vpnTokenProvider = vpnTokenProvider
}


func callAsFunction(completion: @escaping Completion) {
refreshAuthTokenChecker.refreshIfNeeded { [weak self] error in
guard let self else { return }
if let error {
completion(error)
} else {
self.executeRequest(with: completion)
}
}
}
}


extension DeleteAccountUseCase {
func executeRequest(with completion: @escaping Completion) {
let configuration = DeleteAccountRequestConfiguration()
networkClient.executeRequest(with: configuration) { [weak self] error, response in

guard let self else { return }

if let error {
completion(error)
} else {
self.apiTokenProvider.clearAPIToken()
self.vpnTokenProvider.clearVpnToken()
completion(nil)
}


}
}
}
10 changes: 0 additions & 10 deletions Sources/PIALibrary/WebServices/PIAWebServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,16 +213,6 @@ class PIAWebServices: WebServices, ConfigurationAccess {
}
}

func deleteAccount(_ callback: LibraryCallback<Bool>?) {
self.accountAPI.deleteAccount(callback: { errors in
if !errors.isEmpty {
callback?(false, ClientError.invalidParameter)
} else {
callback?(true, nil)
}
})
}

func handleDIPTokenExpiration(dipToken: String, _ callback: SuccessLibraryCallback?) {
self.accountAPI.renewDedicatedIP(ipToken: dipToken) { (errors) in
if !errors.isEmpty {
Expand Down
6 changes: 0 additions & 6 deletions Sources/PIALibrary/WebServices/WebServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,6 @@ protocol WebServices: class {
func handleDIPTokenExpiration(dipToken: String, _ callback: SuccessLibraryCallback?)

func activateDIPToken(tokens: [String], _ callback: LibraryCallback<[Server]>?)

/**
Deletes the user accout on PIA servers.
- Parameter callback: Returns an `Bool` if the API returns a success.
*/
func deleteAccount(_ callback: LibraryCallback<Bool>?)

#if os(iOS) || os(tvOS)
func signup(with request: Signup, _ callback: LibraryCallback<Credentials>?)
Expand Down
149 changes: 149 additions & 0 deletions Tests/PIALibraryTests/Accounts/DeleteAccountUseCaseTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import XCTest
@testable import PIALibrary

class DeleteAccountUseCaseTests: XCTestCase {
class Fixture {
let networkClientMock = NetworkRequestClientMock()
let refreshAuthTokenCheckerMock = RefreshAuthTokensCheckerMock()
let apiTokenProviderMock = APITokenProviderMock()
let vpnTokenProviderMock = VpnTokenProviderMock()

func stubNetworkRequestSuccessfulResponse() {
networkClientMock.executeRequestResponse = NetworkRequestResponseMock(statusCode: 200)
networkClientMock.executeRequestError = nil
}

func stubNetworkRequestResponseWithError(_ error: NetworkRequestError) {
networkClientMock.executeRequestError = error
}

func stubRefreshAuthTokensWithSuccess() {
refreshAuthTokenCheckerMock.refreshIfNeededError = nil
}

func stubRefreshAuthTokensFailsWithError(_ error: NetworkRequestError) {
refreshAuthTokenCheckerMock.refreshIfNeededError = error
}
}

var fixture: Fixture!
var sut: DeleteAccountUseCase!

override func setUp() {
fixture = Fixture()
}

override func tearDown() {
fixture = nil
sut = nil
}

private func instantiateSut() {
sut = DeleteAccountUseCase(networkClient: fixture.networkClientMock, refreshAuthTokenChecker: fixture.refreshAuthTokenCheckerMock, apiTokenProvider: fixture.apiTokenProviderMock, vpnTokenProvider: fixture.vpnTokenProviderMock)
}

func test_delete_account_when_network_request_succeeds() {
// GIVEN that the network request succeeds
fixture.stubNetworkRequestSuccessfulResponse()
// AND refreshing the auth tokens also succeeds
fixture.stubRefreshAuthTokensWithSuccess()

instantiateSut()

let expectation = expectation(description: "Delete account request is executed")
var capturedError: NetworkRequestError?
// WHEN exectuting the delete account request
sut() { error in
capturedError = error
expectation.fulfill()
}

wait(for: [expectation], timeout: 3)

// THEN the refresh auth tokens if needed request is called
XCTAssertEqual(fixture.refreshAuthTokenCheckerMock.refreshIfNeededCalledAttempt, 1)

let executedRequest = fixture.networkClientMock.executeRequestWithConfiguation!
// AND the delete account request is executed
XCTAssertEqual(fixture.networkClientMock.executeRequestCalledAttempt, 1)
XCTAssertEqual(executedRequest.path, RequestAPI.Path.deleteAccount)
XCTAssertEqual(executedRequest.httpMethod, .delete)

// AND no error is retured
XCTAssertNil(capturedError)

// AND the auth tokens ARE removed
XCTAssertEqual(fixture.apiTokenProviderMock.clearAPITokenCalledAttempt, 1)
XCTAssertEqual(fixture.vpnTokenProviderMock.clearVpnTokenCalledAttempt, 1)

}

func test_delete_account_when_network_request_fails() {
// GIVEN that the network request fails
fixture.stubNetworkRequestResponseWithError(.allConnectionAttemptsFailed(statusCode: 401))
// AND refreshing the auth tokens succeeds
fixture.stubRefreshAuthTokensWithSuccess()

instantiateSut()

let expectation = expectation(description: "Delete account request is executed")
var capturedError: NetworkRequestError?
// WHEN exectuting the delete account request
sut() { error in
capturedError = error
expectation.fulfill()
}

wait(for: [expectation], timeout: 3)

// THEN the refresh auth tokens if needed request is called
XCTAssertEqual(fixture.refreshAuthTokenCheckerMock.refreshIfNeededCalledAttempt, 1)

let executedRequest = fixture.networkClientMock.executeRequestWithConfiguation!
// AND the delete account request is executed
XCTAssertEqual(fixture.networkClientMock.executeRequestCalledAttempt, 1)
XCTAssertEqual(executedRequest.path, RequestAPI.Path.deleteAccount)
XCTAssertEqual(executedRequest.httpMethod, .delete)

// AND an error IS retured
XCTAssertNotNil(capturedError)

// AND the auth tokens are NOT removed
XCTAssertEqual(fixture.apiTokenProviderMock.clearAPITokenCalledAttempt, 0)
XCTAssertEqual(fixture.vpnTokenProviderMock.clearVpnTokenCalledAttempt, 0)

}

func test_delete_account_when_refreshAuthTokens_request_fails() {

// GIVEN that refreshing the auth tokens fails
fixture.stubRefreshAuthTokensFailsWithError(.allConnectionAttemptsFailed(statusCode: 401))

instantiateSut()

let expectation = expectation(description: "Delete account request is executed")
var capturedError: NetworkRequestError?
// WHEN exectuting the delete account request
sut() { error in
capturedError = error
expectation.fulfill()
}

wait(for: [expectation], timeout: 3)

// THEN the refresh auth tokens if needed request is called
XCTAssertEqual(fixture.refreshAuthTokenCheckerMock.refreshIfNeededCalledAttempt, 1)

// AND the delete account request is NOT executed
XCTAssertEqual(fixture.networkClientMock.executeRequestCalledAttempt, 0)

// AND an error IS retured
XCTAssertNotNil(capturedError)

// AND the auth tokens are NOT removed
XCTAssertEqual(fixture.apiTokenProviderMock.clearAPITokenCalledAttempt, 0)
XCTAssertEqual(fixture.vpnTokenProviderMock.clearVpnTokenCalledAttempt, 0)

}

}
Loading