diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index ffb67a06f161..c6645ef10c06 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -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 */; }; + 7A12D0722B06125B00E9602D /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A12D0712B06125B00E9602D /* URLSessionProtocol.swift */; }; + 7A12D0742B06131100E9602D /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A12D0712B06125B00E9602D /* 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 */; }; @@ -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 /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BA2AE95396003D4F89 /* MockURLSession.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 */; }; @@ -1527,6 +1529,7 @@ 7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNScreenshots.xctestplan; sourceTree = ""; }; 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FuzzyMatch.swift"; sourceTree = ""; }; 7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Coordinator+Router.swift"; sourceTree = ""; }; + 7A12D0712B06125B00E9602D /* URLSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionProtocol.swift; sourceTree = ""; }; 7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentAlertPresenter.swift; sourceTree = ""; }; 7A1A26442A29CEF700B978AA /* RelayFilterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterViewController.swift; sourceTree = ""; }; 7A1A26462A29CF0800B978AA /* RelayFilterDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterDataSource.swift; sourceTree = ""; }; @@ -1650,7 +1653,7 @@ F09D04AF2AE7F83D003D4F89 /* OutgoingConnectionProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionProxy.swift; sourceTree = ""; }; F09D04B42AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OutgoingConnectionProxy+Stub.swift"; sourceTree = ""; }; F09D04B62AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionProxyTests.swift; sourceTree = ""; }; - F09D04BA2AE95396003D4F89 /* MockURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLProtocol.swift; sourceTree = ""; }; + F09D04BA2AE95396003D4F89 /* MockURLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = ""; }; F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionService.swift; sourceTree = ""; }; F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionServiceTests.swift; sourceTree = ""; }; F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+Async.swift"; sourceTree = ""; }; @@ -2361,8 +2364,9 @@ 5864AF0629C78816005B0CD9 /* Protocols */ = { isa = PBXGroup; children = ( - 58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */, 5864AF0129C7879B005B0CD9 /* CellFactoryProtocol.swift */, + 58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */, + 7A12D0712B06125B00E9602D /* URLSessionProtocol.swift */, ); path = Protocols; sourceTree = ""; @@ -2513,7 +2517,7 @@ F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */, A9B6AC172ADE8F4300F7802A /* MigrationManagerTests.swift */, 58C3FA652A38549D006A450A /* MockFileCache.swift */, - F09D04BA2AE95396003D4F89 /* MockURLProtocol.swift */, + F09D04BA2AE95396003D4F89 /* MockURLSession.swift */, F09D04B42AE93CB6003D4F89 /* OutgoingConnectionProxy+Stub.swift */, F09D04B62AE941DA003D4F89 /* OutgoingConnectionProxyTests.swift */, F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */, @@ -4161,6 +4165,7 @@ A9A5FA032ACB05160083449F /* SimulatorTunnelInfo.swift in Sources */, A9A5FA042ACB05160083449F /* SimulatorTunnelProvider.swift in Sources */, A9A5FA052ACB05160083449F /* SimulatorTunnelProviderHost.swift in Sources */, + 7A12D0742B06131100E9602D /* URLSessionProtocol.swift in Sources */, A900E9C02ACC661900C95F67 /* AccessTokenManager+Stubs.swift in Sources */, A9E0317A2ACB0AE70095D843 /* UIApplication+Stubs.swift in Sources */, A9A5FA062ACB05160083449F /* SimulatorTunnelProviderManager.swift in Sources */, @@ -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 /* MockURLSession.swift in Sources */, A9A5FA1D2ACB05160083449F /* TunnelBlockObserver.swift in Sources */, A9A5FA1E2ACB05160083449F /* TunnelConfiguration.swift in Sources */, A9A5FA1F2ACB05160083449F /* TunnelInteractor.swift in Sources */, @@ -4351,6 +4356,7 @@ 7AC8A3AF2ABC71D600DC4939 /* TermsOfServiceCoordinator.swift in Sources */, F0C2AEFD2A0BB5CC00986207 /* NotificationProviderIdentifier.swift in Sources */, 7A9CCCB72A96302800DD6A34 /* RevokedCoordinator.swift in Sources */, + 7A12D0722B06125B00E9602D /* URLSessionProtocol.swift in Sources */, 587D96742886D87C00CD8F1C /* DeviceManagementContentView.swift in Sources */, 7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */, 5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */, diff --git a/ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift b/ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift index a078efce1a26..91d78526f9b5 100644 --- a/ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift +++ b/ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift @@ -25,9 +25,9 @@ final class OutgoingConnectionProxy: OutgoingConnectionHandling { } } - let urlSession: URLSession + let urlSession: URLSessionProtocol - init(urlSession: URLSession) { + init(urlSession: URLSessionProtocol) { self.urlSession = urlSession } @@ -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(from: url) guard let httpResponse = response as? HTTPURLResponse else { throw REST.Error.network(URLError(.badServerResponse)) } @@ -92,3 +92,9 @@ final class OutgoingConnectionProxy: OutgoingConnectionHandling { return connectionData } } + +extension OutgoingConnectionProxy: URLSessionProtocol { + func data(from url: URL) async throws -> (Data, URLResponse) { + return try await urlSession.data(from: url) + } +} diff --git a/ios/MullvadVPN/Protocols/URLSessionProtocol.swift b/ios/MullvadVPN/Protocols/URLSessionProtocol.swift new file mode 100644 index 000000000000..1880c85aae92 --- /dev/null +++ b/ios/MullvadVPN/Protocols/URLSessionProtocol.swift @@ -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(from url: URL) async throws -> (Data, URLResponse) +} + +extension URLSession: URLSessionProtocol {} diff --git a/ios/MullvadVPNTests/MockURLProtocol.swift b/ios/MullvadVPNTests/MockURLProtocol.swift deleted file mode 100644 index 061a132528f2..000000000000 --- a/ios/MullvadVPNTests/MockURLProtocol.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// MockURLProtocol.swift -// MullvadVPNTests -// -// Created by Mojgan on 2023-10-25. -// Copyright © 2023 Mullvad VPN AB. All rights reserved. -// - -import Foundation - -class MockURLProtocol: URLProtocol { - static var error: Error? - static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? - - override class func canInit(with request: URLRequest) -> Bool { - return true - } - - override class func canonicalRequest(for request: URLRequest) -> URLRequest { - return request - } - - override func startLoading() { - if let error = MockURLProtocol.error { - client?.urlProtocol(self, didFailWithError: error) - return - } - - guard let handler = MockURLProtocol.requestHandler else { - assertionFailure("Received unexpected request with no handler set") - return - } - - do { - let (response, data) = try handler(request) - client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) - client?.urlProtocol(self, didLoad: data) - client?.urlProtocolDidFinishLoading(self) - } catch { - client?.urlProtocol(self, didFailWithError: error) - } - } - - override func stopLoading() { - // stop loading here - } -} - -extension URLSession { - static let mock = { - let configuration = URLSessionConfiguration.ephemeral - configuration.protocolClasses = [MockURLProtocol.self] - return URLSession(configuration: configuration) - }() -} diff --git a/ios/MullvadVPNTests/MockURLSession.swift b/ios/MullvadVPNTests/MockURLSession.swift new file mode 100644 index 000000000000..8dc0d3f615f1 --- /dev/null +++ b/ios/MullvadVPNTests/MockURLSession.swift @@ -0,0 +1,21 @@ +// +// MockURLSession.swift +// MullvadVPNTests +// +// Created by Mojgan on 2023-10-25. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +class MockURLSession: URLSessionProtocol { + var response: (Data, URLResponse) + + init(response: (Data, URLResponse)) { + self.response = response + } + + func data(from url: URL) async throws -> (Data, URLResponse) { + return response + } +} diff --git a/ios/MullvadVPNTests/OutgoingConnectionProxyTests.swift b/ios/MullvadVPNTests/OutgoingConnectionProxyTests.swift index afa8ada0fa79..44c6c5892445 100644 --- a/ios/MullvadVPNTests/OutgoingConnectionProxyTests.swift +++ b/ios/MullvadVPNTests/OutgoingConnectionProxyTests.swift @@ -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: MockURLSession( + response: (mockIPV4ConnectionData, createHTTPURLResponse(ip: .ipv4, statusCode: 200)) + )) let result = try await outgoingConnectionProxy.getIPV4(retryStrategy: .noRetry) @@ -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: MockURLSession( + response: (Data(), createHTTPURLResponse(ip: .ipv4, statusCode: 503)) + )) await XCTAssertThrowsErrorAsync(try await outgoingConnectionProxy.getIPV4(retryStrategy: .noRetry)) { _ in noIPv4Expectation.fulfill() @@ -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: MockURLSession( + response: (mockIPV6ConnectionData, createHTTPURLResponse(ip: .ipv4, statusCode: 200)) + )) let result = try await outgoingConnectionProxy.getIPV6(retryStrategy: .noRetry) @@ -108,16 +70,9 @@ 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: MockURLSession( + response: (mockIPV6ConnectionData, createHTTPURLResponse(ip: .ipv6, statusCode: 404)) + )) await XCTAssertThrowsErrorAsync(try await outgoingConnectionProxy.getIPV6(retryStrategy: .noRetry)) { _ in noIPv6Expectation.fulfill() @@ -125,3 +80,18 @@ final class OutgoingConnectionProxyTests: XCTestCase { await fulfillment(of: [noIPv6Expectation], timeout: 1) } } + +extension OutgoingConnectionProxyTests { + enum IPVersion: String { + case ipv4, ipv6 + } + + private func createHTTPURLResponse(ip: IPVersion, statusCode: Int) -> HTTPURLResponse { + return HTTPURLResponse( + url: URL(string: "https://\(ip.rawValue).am.i.mullvad.net/json")!, + statusCode: statusCode, + httpVersion: nil, + headerFields: ["Content-Type": "application/json"] + )! + } +}