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

Refactor URLSession out of outgoing connection proxy tests #5446

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
16 changes: 11 additions & 5 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,8 @@
7A09C98129D99215000C2CAC /* String+FuzzyMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */; };
7A0C0F632A979C4A0058EFCE /* Coordinator+Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */; };
7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */; };
7A12D0762B062D5C00E9602D /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */; };
7A12D0772B062D6500E9602D /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */; };
7A1A26432A2612AE00B978AA /* PaymentAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */; };
7A1A26452A29CEF700B978AA /* RelayFilterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A26442A29CEF700B978AA /* RelayFilterViewController.swift */; };
7A1A26472A29CF0800B978AA /* RelayFilterDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A26462A29CF0800B978AA /* RelayFilterDataSource.swift */; };
Expand Down Expand Up @@ -654,7 +656,7 @@
F09D04B52AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04B42AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift */; };
F09D04B72AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04B62AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift */; };
F09D04B92AE95111003D4F89 /* OutgoingConnectionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04AF2AE7F83D003D4F89 /* OutgoingConnectionProxy.swift */; };
F09D04BB2AE95396003D4F89 /* MockURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BA2AE95396003D4F89 /* MockURLProtocol.swift */; };
F09D04BB2AE95396003D4F89 /* URLSessionStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BA2AE95396003D4F89 /* URLSessionStub.swift */; };
F09D04BD2AEBB7C5003D4F89 /* OutgoingConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */; };
F09D04C02AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */; };
F09D04C12AF39EA2003D4F89 /* OutgoingConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */; };
Expand Down Expand Up @@ -1527,6 +1529,7 @@
7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNScreenshots.xctestplan; sourceTree = "<group>"; };
7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FuzzyMatch.swift"; sourceTree = "<group>"; };
7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Coordinator+Router.swift"; sourceTree = "<group>"; };
7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionProtocol.swift; sourceTree = "<group>"; };
7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentAlertPresenter.swift; sourceTree = "<group>"; };
7A1A26442A29CEF700B978AA /* RelayFilterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterViewController.swift; sourceTree = "<group>"; };
7A1A26462A29CF0800B978AA /* RelayFilterDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterDataSource.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1650,7 +1653,7 @@
F09D04AF2AE7F83D003D4F89 /* OutgoingConnectionProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionProxy.swift; sourceTree = "<group>"; };
F09D04B42AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OutgoingConnectionProxy+Stub.swift"; sourceTree = "<group>"; };
F09D04B62AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionProxyTests.swift; sourceTree = "<group>"; };
F09D04BA2AE95396003D4F89 /* MockURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLProtocol.swift; sourceTree = "<group>"; };
F09D04BA2AE95396003D4F89 /* URLSessionStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionStub.swift; sourceTree = "<group>"; };
F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionService.swift; sourceTree = "<group>"; };
F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionServiceTests.swift; sourceTree = "<group>"; };
F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+Async.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2361,8 +2364,9 @@
5864AF0629C78816005B0CD9 /* Protocols */ = {
isa = PBXGroup;
children = (
58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */,
5864AF0129C7879B005B0CD9 /* CellFactoryProtocol.swift */,
58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */,
7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */,
);
path = Protocols;
sourceTree = "<group>";
Expand Down Expand Up @@ -2513,7 +2517,7 @@
F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */,
A9B6AC172ADE8F4300F7802A /* MigrationManagerTests.swift */,
58C3FA652A38549D006A450A /* MockFileCache.swift */,
F09D04BA2AE95396003D4F89 /* MockURLProtocol.swift */,
F09D04BA2AE95396003D4F89 /* URLSessionStub.swift */,
F09D04B42AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift */,
F09D04B62AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift */,
F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */,
Expand Down Expand Up @@ -4134,6 +4138,7 @@
A9A5F9EC2ACB05160083449F /* CodingErrors+CustomErrorDescription.swift in Sources */,
A9A5F9ED2ACB05160083449F /* NSRegularExpression+IPAddress.swift in Sources */,
A9A5F9EE2ACB05160083449F /* RESTCreateApplePaymentResponse+Localization.swift in Sources */,
7A12D0772B062D6500E9602D /* URLSessionProtocol.swift in Sources */,
A9A5F9EF2ACB05160083449F /* String+AccountFormatting.swift in Sources */,
A9A5F9F02ACB05160083449F /* String+FuzzyMatch.swift in Sources */,
F09D04C12AF39EA2003D4F89 /* OutgoingConnectionService.swift in Sources */,
Expand Down Expand Up @@ -4190,7 +4195,7 @@
A9A5FA1A2ACB05160083449F /* StopTunnelOperation.swift in Sources */,
A9A5FA1B2ACB05160083449F /* Tunnel.swift in Sources */,
A9A5FA1C2ACB05160083449F /* Tunnel+Messaging.swift in Sources */,
F09D04BB2AE95396003D4F89 /* MockURLProtocol.swift in Sources */,
F09D04BB2AE95396003D4F89 /* URLSessionStub.swift in Sources */,
A9A5FA1D2ACB05160083449F /* TunnelBlockObserver.swift in Sources */,
A9A5FA1E2ACB05160083449F /* TunnelConfiguration.swift in Sources */,
A9A5FA1F2ACB05160083449F /* TunnelInteractor.swift in Sources */,
Expand Down Expand Up @@ -4510,6 +4515,7 @@
5878A27329091D6D0096FC88 /* TunnelBlockObserver.swift in Sources */,
A9E034642ABB302000E59A5A /* UIEdgeInsets+Extensions.swift in Sources */,
58E0A98827C8F46300FE6BDD /* Tunnel.swift in Sources */,
7A12D0762B062D5C00E9602D /* URLSessionProtocol.swift in Sources */,
58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */,
58EE2E3A272FF814003BFF93 /* SettingsDataSource.swift in Sources */,
F0E8E4C12A602CCB00ED26A3 /* AccountDeletionContentView.swift in Sources */,
Expand Down
14 changes: 10 additions & 4 deletions ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ protocol OutgoingConnectionHandling {
}

final class OutgoingConnectionProxy: OutgoingConnectionHandling {
private enum ExitIPVersion: String {
enum ExitIPVersion: String {
case v4 = "ipv4", v6 = "ipv6"

var host: String {
"\(rawValue).am.i.mullvad.net"
}
}

let urlSession: URLSession
let urlSession: URLSessionProtocol

init(urlSession: URLSession) {
init(urlSession: URLSessionProtocol) {
self.urlSession = urlSession
}

Expand Down Expand Up @@ -74,7 +74,7 @@ final class OutgoingConnectionProxy: OutgoingConnectionHandling {
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: REST.defaultAPINetworkTimeout.timeInterval
)
let (data, response) = try await urlSession.data(for: request)
let (data, response) = try await data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw REST.Error.network(URLError(.badServerResponse))
}
Expand All @@ -92,3 +92,9 @@ final class OutgoingConnectionProxy: OutgoingConnectionHandling {
return connectionData
}
}

extension OutgoingConnectionProxy: URLSessionProtocol {
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
return try await urlSession.data(for: request)
}
}
15 changes: 15 additions & 0 deletions ios/MullvadVPN/Protocols/URLSessionProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// URLSessionProtocol.swift
// MullvadVPN
//
// Created by Jon Petersson on 2023-11-16.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation

protocol URLSessionProtocol {
func data(for request: URLRequest) async throws -> (Data, URLResponse)
}

extension URLSession: URLSessionProtocol {}
55 changes: 0 additions & 55 deletions ios/MullvadVPNTests/MockURLProtocol.swift

This file was deleted.

80 changes: 23 additions & 57 deletions ios/MullvadVPNTests/OutgoingConnectionProxyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,27 @@ import MullvadREST
import XCTest

final class OutgoingConnectionProxyTests: XCTestCase {
private var outgoingConnectionProxy: OutgoingConnectionProxy!
private var mockIPV6ConnectionData: Data!
private var mockIPV4ConnectionData: Data!

private let encoder = JSONEncoder()

override func setUpWithError() throws {
outgoingConnectionProxy = OutgoingConnectionProxy(urlSession: .mock)
mockIPV4ConnectionData = try encoder.encode(IPV4ConnectionData.mock)
mockIPV6ConnectionData = try encoder.encode(IPV6ConnectionData.mock)
}

override func tearDownWithError() throws {
outgoingConnectionProxy = nil
mockIPV4ConnectionData.removeAll()
mockIPV6ConnectionData.removeAll()
}

func testNoInternetConnection() async throws {
let noIPv4Expectation = expectation(description: "Did not receive IPv4")
let error = URLError(URLError.notConnectedToInternet)

MockURLProtocol.error = error
MockURLProtocol.requestHandler = nil

await XCTAssertThrowsErrorAsync(try await outgoingConnectionProxy.getIPV4(retryStrategy: .noRetry)) { error in
noIPv4Expectation.fulfill()
XCTAssertEqual((error as? URLError)?.code, .notConnectedToInternet)
}
await fulfillment(of: [noIPv4Expectation], timeout: 1)
}

func testSuccessGettingIPV4() async throws {
let iPv4Expectation = expectation(description: "Did receive IPv4")

MockURLProtocol.error = nil
MockURLProtocol.requestHandler = { _ in
let response = HTTPURLResponse(
url: URL(string: "https://ipv4.am.i.mullvad.net/json")!,
statusCode: 200,
httpVersion: nil,
headerFields: ["Content-Type": "application/json"]
)!
return (response, self.mockIPV4ConnectionData)
}
let outgoingConnectionProxy = OutgoingConnectionProxy(urlSession: URLSessionStub(
response: (mockIPV4ConnectionData, createHTTPURLResponse(ip: .v4, statusCode: 200))
))

let result = try await outgoingConnectionProxy.getIPV4(retryStrategy: .noRetry)

Expand All @@ -66,16 +42,9 @@ final class OutgoingConnectionProxyTests: XCTestCase {
func testFailureGettingIPV4() async throws {
let noIPv4Expectation = expectation(description: "Did not receive IPv4")

MockURLProtocol.error = nil
MockURLProtocol.requestHandler = { _ in
let response = HTTPURLResponse(
url: URL(string: "https://ipv4.am.i.mullvad.net/json")!,
statusCode: 503,
httpVersion: nil,
headerFields: ["Content-Type": "application/json"]
)!
return (response, Data())
}
let outgoingConnectionProxy = OutgoingConnectionProxy(urlSession: URLSessionStub(
response: (Data(), createHTTPURLResponse(ip: .v4, statusCode: 503))
))

await XCTAssertThrowsErrorAsync(try await outgoingConnectionProxy.getIPV4(retryStrategy: .noRetry)) { _ in
noIPv4Expectation.fulfill()
Expand All @@ -86,16 +55,9 @@ final class OutgoingConnectionProxyTests: XCTestCase {
func testSuccessGettingIPV6() async throws {
let ipv6Expectation = expectation(description: "Did receive IPv6")

MockURLProtocol.error = nil
MockURLProtocol.requestHandler = { _ in
let response = HTTPURLResponse(
url: URL(string: "https://ipv6.am.i.mullvad.net/json")!,
statusCode: 200,
httpVersion: nil,
headerFields: ["Content-Type": "application/json"]
)!
return (response, self.mockIPV6ConnectionData)
}
let outgoingConnectionProxy = OutgoingConnectionProxy(urlSession: URLSessionStub(
response: (mockIPV6ConnectionData, createHTTPURLResponse(ip: .v6, statusCode: 200))
))

let result = try await outgoingConnectionProxy.getIPV6(retryStrategy: .noRetry)

Expand All @@ -108,20 +70,24 @@ final class OutgoingConnectionProxyTests: XCTestCase {
func testFailureGettingIPV6() async throws {
let noIPv6Expectation = expectation(description: "Did not receive IPv6")

MockURLProtocol.error = nil
MockURLProtocol.requestHandler = { _ in
let response = HTTPURLResponse(
url: URL(string: "https://ipv6.am.i.mullvad.net/json")!,
statusCode: 404,
httpVersion: nil,
headerFields: ["Content-Type": "application/json"]
)!
return (response, Data())
}
let outgoingConnectionProxy = OutgoingConnectionProxy(urlSession: URLSessionStub(
response: (mockIPV6ConnectionData, createHTTPURLResponse(ip: .v6, statusCode: 404))
))

await XCTAssertThrowsErrorAsync(try await outgoingConnectionProxy.getIPV6(retryStrategy: .noRetry)) { _ in
noIPv6Expectation.fulfill()
}
await fulfillment(of: [noIPv6Expectation], timeout: 1)
}
}

extension OutgoingConnectionProxyTests {
private func createHTTPURLResponse(ip: OutgoingConnectionProxy.ExitIPVersion, statusCode: Int) -> HTTPURLResponse {
return HTTPURLResponse(
url: URL(string: "https://\(ip.host)/json")!,
statusCode: statusCode,
httpVersion: nil,
headerFields: ["Content-Type": "application/json"]
)!
}
}
21 changes: 21 additions & 0 deletions ios/MullvadVPNTests/URLSessionStub.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// URLSessionStub.swift
// MullvadVPNTests
//
// Created by Mojgan on 2023-10-25.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation

class URLSessionStub: URLSessionProtocol {
var response: (Data, URLResponse)

init(response: (Data, URLResponse)) {
self.response = response
}

func data(for request: URLRequest) async throws -> (Data, URLResponse) {
return response
}
}
Loading