summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrew Bulhak <andrew.bulhak@mullvad.net>2024-02-06 11:15:47 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-02-07 15:13:22 +0100
commit6c5110520336ab7ed1ffff1952ffa3a9bb794ea7 (patch)
tree0b40a11fef37cee5ec1dea6c44b36494095a0c39
parente7a74251071f088fef1f5db97cc3ae1c2d890f64 (diff)
downloadmullvadvpn-6c5110520336ab7ed1ffff1952ffa3a9bb794ea7.tar.xz
mullvadvpn-6c5110520336ab7ed1ffff1952ffa3a9bb794ea7.zip
Add test for starting a tunnel from a disconnected state
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel.swift3
-rw-r--r--ios/MullvadVPNTests/Mocks/MockTunnel.swift59
-rw-r--r--ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift41
-rw-r--r--ios/MullvadVPNTests/StartTunnelOperationTests.swift77
-rw-r--r--ios/Shared/ApplicationTarget.swift4
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