diff options
| author | Andrew Bulhak <andrew.bulhak@mullvad.net> | 2024-02-06 11:15:47 +0100 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2024-02-07 15:13:22 +0100 |
| commit | 6c5110520336ab7ed1ffff1952ffa3a9bb794ea7 (patch) | |
| tree | 0b40a11fef37cee5ec1dea6c44b36494095a0c39 | |
| parent | e7a74251071f088fef1f5db97cc3ae1c2d890f64 (diff) | |
| download | mullvadvpn-6c5110520336ab7ed1ffff1952ffa3a9bb794ea7.tar.xz mullvadvpn-6c5110520336ab7ed1ffff1952ffa3a9bb794ea7.zip | |
Add test for starting a tunnel from a disconnected state
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/TunnelManager/Tunnel.swift | 3 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/Mocks/MockTunnel.swift | 59 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift | 41 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/StartTunnelOperationTests.swift | 77 | ||||
| -rw-r--r-- | ios/Shared/ApplicationTarget.swift | 4 |
6 files changed, 144 insertions, 44 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index de7e7ac11e..683962d1d3 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; }; 44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */; }; 44DD7D272B6D18FB0005F67F /* MockTunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */; }; + 44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D282B7113CA0005F67F /* MockTunnel.swift */; }; 5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */; }; 5803B4B22940A48700C23744 /* TunnelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4B12940A48700C23744 /* TunnelStore.swift */; }; 5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Split.swift */; }; @@ -1242,6 +1243,7 @@ 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransport.swift; sourceTree = "<group>"; }; 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = "<group>"; }; 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = "<group>"; }; + 44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = "<group>"; }; 5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = "<group>"; }; 5802EBC62A8E457A00E5CE4C /* AppRouteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteProtocol.swift; sourceTree = "<group>"; }; 5802EBC82A8E45BA00E5CE4C /* ApplicationRouterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationRouterDelegate.swift; sourceTree = "<group>"; }; @@ -2101,6 +2103,7 @@ 44DD7D252B6D18E90005F67F /* Mocks */ = { isa = PBXGroup; children = ( + 44DD7D282B7113CA0005F67F /* MockTunnel.swift */, 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */, ); path = Mocks; @@ -4615,6 +4618,7 @@ A9A5FA112ACB05160083449F /* TransportMonitor.swift in Sources */, A9B6AC1A2ADE8FBB00F7802A /* InMemorySettingsStore.swift in Sources */, A9A5FA132ACB05160083449F /* LoadTunnelConfigurationOperation.swift in Sources */, + 44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */, A9A5FA142ACB05160083449F /* MapConnectionStatusOperation.swift in Sources */, A9A5FA152ACB05160083449F /* RedeemVoucherOperation.swift in Sources */, A9A5FA162ACB05160083449F /* RotateKeyOperation.swift in Sources */, diff --git a/ios/MullvadVPN/TunnelManager/Tunnel.swift b/ios/MullvadVPN/TunnelManager/Tunnel.swift index 1ff8f9179b..dd57f53f5b 100644 --- a/ios/MullvadVPN/TunnelManager/Tunnel.swift +++ b/ios/MullvadVPN/TunnelManager/Tunnel.swift @@ -21,11 +21,12 @@ protocol TunnelStatusObserver { } protocol TunnelProtocol: AnyObject { + associatedtype TunnelManagerProtocol: VPNTunnelProviderManagerProtocol var status: NEVPNStatus { get } var isOnDemandEnabled: Bool { get set } var startDate: Date? { get } - init(tunnelProvider: TunnelProviderManagerType) + init(tunnelProvider: VPNTunnelProviderManagerProtocol) func addObserver(_ observer: any TunnelStatusObserver) func removeObserver(_ observer: any TunnelStatusObserver) diff --git a/ios/MullvadVPNTests/Mocks/MockTunnel.swift b/ios/MullvadVPNTests/Mocks/MockTunnel.swift new file mode 100644 index 0000000000..df53c7092c --- /dev/null +++ b/ios/MullvadVPNTests/Mocks/MockTunnel.swift @@ -0,0 +1,59 @@ +// +// MockTunnel.swift +// MullvadVPNTests +// +// Created by Andrew Bulhak on 2024-02-05. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import NetworkExtension + +class MockTunnel: TunnelProtocol { + typealias TunnelManagerProtocol = SimulatorTunnelProviderManager + + var status: NEVPNStatus + + var isOnDemandEnabled: Bool + + var startDate: Date? + + required init(tunnelProvider: TunnelManagerProtocol) { + status = .disconnected + isOnDemandEnabled = false + startDate = nil + } + + // Observers are currently unimplemented + func addObserver(_ observer: TunnelStatusObserver) {} + + func removeObserver(_ observer: TunnelStatusObserver) {} + + func addBlockObserver(queue: DispatchQueue?, handler: @escaping (any TunnelProtocol, NEVPNStatus) -> Void) -> TunnelStatusBlockObserver { + fatalError("MockTunnel.addBlockObserver Not implemented") + } + + func logFormat() -> String { + "" + } + + func saveToPreferences(_ completion: @escaping (Error?) -> Void) { + completion(nil) + } + + func removeFromPreferences(completion: @escaping (Error?) -> Void) { + completion(nil) + } + + func setConfiguration(_ configuration: TunnelConfiguration) {} + + func start(options: [String : NSObject]?) throws { + startDate = Date() + } + + func stop() {} + + func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws {} + + +} diff --git a/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift b/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift index 93c4b68cb8..b9e2979e3a 100644 --- a/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift +++ b/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift @@ -11,24 +11,39 @@ import MullvadSettings import PacketTunnelCore // this is still very minimal, and will be fleshed out as needed. -struct MockTunnelInteractor: TunnelInteractor { +class MockTunnelInteractor: TunnelInteractor { + var isConfigurationLoaded: Bool + + var settings: MullvadSettings.LatestTunnelSettings + + var deviceState: MullvadSettings.DeviceState + var onUpdateTunnelStatus: ((TunnelStatus)->Void)? - var tunnel: (TunnelProtocol)? + var tunnel: (any TunnelProtocol)? + + init(isConfigurationLoaded: Bool, settings: MullvadSettings.LatestTunnelSettings, deviceState: MullvadSettings.DeviceState, onUpdateTunnelStatus: ( (TunnelStatus) -> Void)? = nil) { + self.isConfigurationLoaded = isConfigurationLoaded + self.settings = settings + self.deviceState = deviceState + self.onUpdateTunnelStatus = onUpdateTunnelStatus + self.tunnel = nil + self.tunnelStatus = TunnelStatus() + } - func getPersistentTunnels() -> [TunnelProtocol] { + func getPersistentTunnels() -> [any TunnelProtocol] { return [] } - func createNewTunnel() -> TunnelProtocol { - fatalError() + func createNewTunnel() -> any TunnelProtocol { + return MockTunnel(tunnelProvider: SimulatorTunnelProviderManager()) } - func setTunnel(_ tunnel: (TunnelProtocol)?, shouldRefreshTunnelState: Bool) { + func setTunnel(_ tunnel: (any TunnelProtocol)?, shouldRefreshTunnelState: Bool) { + self.tunnel = tunnel } - var tunnelStatus: TunnelStatus = - TunnelStatus() + var tunnelStatus: TunnelStatus func updateTunnelStatus(_ block: (inout TunnelStatus) -> Void) -> TunnelStatus { var tunnelStatus = self.tunnelStatus @@ -37,12 +52,6 @@ struct MockTunnelInteractor: TunnelInteractor { return tunnelStatus } - var isConfigurationLoaded: Bool - - var settings: MullvadSettings.LatestTunnelSettings - - var deviceState: MullvadSettings.DeviceState - func setConfigurationLoaded() {} func setSettings(_ settings: MullvadSettings.LatestTunnelSettings, persist: Bool) { @@ -59,7 +68,9 @@ struct MockTunnelInteractor: TunnelInteractor { func prepareForVPNConfigurationDeletion() {} + struct NotImplementedError: Error { } + func selectRelay() throws -> PacketTunnelCore.SelectedRelay { - fatalError() + throw NotImplementedError() } } diff --git a/ios/MullvadVPNTests/StartTunnelOperationTests.swift b/ios/MullvadVPNTests/StartTunnelOperationTests.swift index 002d27d0fd..e6797649dc 100644 --- a/ios/MullvadVPNTests/StartTunnelOperationTests.swift +++ b/ios/MullvadVPNTests/StartTunnelOperationTests.swift @@ -15,16 +15,48 @@ import WireGuardKitTypes class StartTunnelOperationTests: XCTestCase { + //MARK: utility code for setting up tests + let testQueue = DispatchQueue(label: "StartTunnelOperationTests.testQueue") + let operationQueue = AsyncOperationQueue() + + let loggedInDeviceState = DeviceState.loggedIn( + StoredAccountData( + identifier: "", + number: "", + expiry: .distantFuture + ), + StoredDeviceData( + creationDate: Date(), + identifier: "", + name: "", + hijackDNS: false, + ipv4Address: IPAddressRange(from: "127.0.0.1/32")!, + ipv6Address: IPAddressRange(from: "::ff/64")!, + wgKeyData: StoredWgKeyData(creationDate: Date(), privateKey: PrivateKey()) + ) + ) + + func makeInteractor(deviceState: DeviceState, tunnelState: TunnelState? = nil) -> MockTunnelInteractor { + var interactor = MockTunnelInteractor( + isConfigurationLoaded: true, + settings: LatestTunnelSettings(), + deviceState: deviceState + ) + if let tunnelState { + interactor.tunnelStatus = TunnelStatus(state: tunnelState) + } + return interactor + } + + //MARK: the tests func testFailsIfNotLoggedIn() throws { - let operationQueue = AsyncOperationQueue() let settings = LatestTunnelSettings() let exp = expectation(description:"Start tunnel operation failed") let operation = StartTunnelOperation( dispatchQueue: testQueue, - interactor: MockTunnelInteractor(isConfigurationLoaded: true, settings: settings, deviceState: .loggedOut)) { result in - + interactor: makeInteractor(deviceState: .loggedOut)) { result in guard case .failure(_) = result else { XCTFail("Operation returned \(result), not failure") return @@ -37,31 +69,9 @@ class StartTunnelOperationTests: XCTestCase { } func testSetsReconnectIfDisconnecting() { - let operationQueue = AsyncOperationQueue() let settings = LatestTunnelSettings() - var interactor = MockTunnelInteractor( - isConfigurationLoaded: true, - settings: settings, - deviceState: .loggedIn( - StoredAccountData( - identifier: "", - number: "", - expiry: .distantFuture - ), - StoredDeviceData( - creationDate: Date(), - identifier: "", - name: "", - hijackDNS: false, - ipv4Address: IPAddressRange(from: "127.0.0.1/32")!, - ipv6Address: IPAddressRange(from: "::ff/64")!, - wgKeyData: StoredWgKeyData(creationDate: Date(), privateKey: PrivateKey()) - ) - ) - ) + var interactor = makeInteractor(deviceState: loggedInDeviceState, tunnelState: .disconnecting(.nothing)) var tunnelStatus = TunnelStatus() - tunnelStatus.state = .disconnecting(.nothing) - interactor.tunnelStatus = tunnelStatus interactor.onUpdateTunnelStatus = { status in tunnelStatus = status } let expectation = expectation(description:"Tunnel status set to reconnect") @@ -74,4 +84,19 @@ class StartTunnelOperationTests: XCTestCase { operationQueue.addOperation(operation) wait(for: [expectation], timeout: 1.0) } + + func testStartsTunnelIfDisconnected() { + let settings = LatestTunnelSettings() + var interactor = makeInteractor(deviceState: loggedInDeviceState, tunnelState: .disconnected) + let expectation = expectation(description:"Make tunnel provider and start tunnel") + let operation = StartTunnelOperation( + dispatchQueue: testQueue, + interactor: interactor) { result in + XCTAssertNotNil(interactor.tunnel) + XCTAssertNotNil(interactor.tunnel?.startDate) + expectation.fulfill() + } + operationQueue.addOperation(operation) + wait(for: [expectation], timeout: 1.0) + } } diff --git a/ios/Shared/ApplicationTarget.swift b/ios/Shared/ApplicationTarget.swift index f46fa2c64e..c1f3224c58 100644 --- a/ios/Shared/ApplicationTarget.swift +++ b/ios/Shared/ApplicationTarget.swift @@ -13,8 +13,8 @@ enum ApplicationTarget: CaseIterable { /// Returns target bundle identifier. var bundleIdentifier: String { - // swiftlint:disable:next force_cast - let mainBundleIdentifier = Bundle.main.object(forInfoDictionaryKey: "MainApplicationIdentifier") as! String + // "MainApplicationIdentifier" does not exist if running tests + let mainBundleIdentifier = Bundle.main.object(forInfoDictionaryKey: "MainApplicationIdentifier") as? String ?? "tests" switch self { case .mainApp: return mainBundleIdentifier |
