From 6a579a015f5ec8aff4b1550fb7c72d92aae492c3 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Fri, 2 Feb 2024 12:06:58 +0100 Subject: [PATCH 1/9] Add initial (nonbuilding) tests --- ios/MullvadVPN.xcodeproj/project.pbxproj | 4 + .../StartTunnelOperationTests.swift | 103 ++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 ios/MullvadVPNTests/StartTunnelOperationTests.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 17c25c9e920f..2b5da62ba87d 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 06799AFC28F98EE300ACD94E /* AddressCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AC114128F8413A0037AF9A /* AddressCache.swift */; }; 0697D6E728F01513007A9E99 /* TransportMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0697D6E628F01513007A9E99 /* TransportMonitor.swift */; }; 06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; }; + 44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.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 */; }; @@ -1238,6 +1239,7 @@ 06FAE67A28F83CA50033DD93 /* RESTDevicesProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTDevicesProxy.swift; sourceTree = ""; }; 06FAE67B28F83CA50033DD93 /* REST.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = REST.swift; sourceTree = ""; }; 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransport.swift; sourceTree = ""; }; + 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = ""; }; 5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = ""; }; 5802EBC62A8E457A00E5CE4C /* AppRouteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteProtocol.swift; sourceTree = ""; }; 5802EBC82A8E45BA00E5CE4C /* ApplicationRouterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationRouterDelegate.swift; sourceTree = ""; }; @@ -2774,6 +2776,7 @@ F09D04BA2AE95396003D4F89 /* URLSessionStub.swift */, 58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */, F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */, + 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */, ); path = MullvadVPNTests; sourceTree = ""; @@ -4632,6 +4635,7 @@ A9A5FA292ACB05160083449F /* AddressCacheTests.swift in Sources */, A9B6AC182ADE8F4300F7802A /* MigrationManagerTests.swift in Sources */, A9A5FA2A2ACB05160083449F /* CoordinatesTests.swift in Sources */, + 44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */, A9A5FA2B2ACB05160083449F /* CustomDateComponentsFormattingTests.swift in Sources */, A9A5FA2C2ACB05160083449F /* DeviceCheckOperationTests.swift in Sources */, A9A5FA2D2ACB05160083449F /* DurationTests.swift in Sources */, diff --git a/ios/MullvadVPNTests/StartTunnelOperationTests.swift b/ios/MullvadVPNTests/StartTunnelOperationTests.swift new file mode 100644 index 000000000000..31d32b3310b3 --- /dev/null +++ b/ios/MullvadVPNTests/StartTunnelOperationTests.swift @@ -0,0 +1,103 @@ +// +// StartTunnelOperationTests.swift +// MullvadVPNTests +// +// Created by Andrew Bulhak on 2024-02-02. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import XCTest +import MullvadSettings +import Operations +import PacketTunnelCore + +struct MockTunnelInteractor: TunnelInteractor { + var tunnel: (TunnelProtocol)? + + func getPersistentTunnels() -> [TunnelProtocol] { + return [] + } + + func createNewTunnel() -> TunnelProtocol { + fatalError() + } + + func setTunnel(_ tunnel: (TunnelProtocol)?, shouldRefreshTunnelState: Bool) { + + } + + var tunnelStatus: TunnelStatus { + TunnelStatus() + } + + func updateTunnelStatus(_ block: (inout TunnelStatus) -> Void) -> TunnelStatus { + TunnelStatus() + } + + var isConfigurationLoaded: Bool + + var settings: MullvadSettings.LatestTunnelSettings + + var deviceState: MullvadSettings.DeviceState + + func setConfigurationLoaded() { + } + + func setSettings(_ settings: MullvadSettings.LatestTunnelSettings, persist: Bool) { + } + + func setDeviceState(_ deviceState: MullvadSettings.DeviceState, persist: Bool) { + } + + func removeLastUsedAccount() { + } + + func handleRestError(_ error: Error) { + } + + func startTunnel() { + } + + func prepareForVPNConfigurationDeletion() { + } + + func selectRelay() throws -> PacketTunnelCore.SelectedRelay { + fatalError() + } + + +} + +final class StartTunnelOperationTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testFailsIfNotLoggedIn() throws { + let operationQueue = AsyncOperationQueue() + let testQueue = DispatchQueue(label: "StartTunnelOperationTests.testQueue") + let settings = LatestTunnelSettings() + let expectation = XCTestExpectation(description:"") + let operation = StartTunnelOperation( + dispatchQueue: testQueue, + interactor: MockTunnelInteractor(isConfigurationLoaded: true, settings: settings, deviceState: .loggedOut)) { result in + + guard case let .failure(err) = result else { + XCTFail("Operation returned \(result), not failure") + return + } + + expectation.fulfill() + + } + + operationQueue.addOperation(operation) + wait(for: [expectation], timeout: 10.0) + } + +} From b625e73ff217cf117b4c4ef2dda646d843d2009d Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Fri, 2 Feb 2024 15:04:10 +0100 Subject: [PATCH 2/9] Initial tests for StartTunnelOperation, testing non-logged-in state and transition from disconnecting to reconnecting --- ios/MullvadVPN.xcodeproj/project.pbxproj | 12 +++ .../Mocks/MockTunnelInteractor.swift | 72 +++++++++++++ .../StartTunnelOperationTests.swift | 102 +++++++----------- 3 files changed, 125 insertions(+), 61 deletions(-) create mode 100644 ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 2b5da62ba87d..57997591c4a1 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 0697D6E728F01513007A9E99 /* TransportMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0697D6E628F01513007A9E99 /* TransportMonitor.swift */; }; 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 */; }; 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 */; }; @@ -1240,6 +1241,7 @@ 06FAE67B28F83CA50033DD93 /* REST.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = REST.swift; sourceTree = ""; }; 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransport.swift; sourceTree = ""; }; 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = ""; }; + 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = ""; }; 5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = ""; }; 5802EBC62A8E457A00E5CE4C /* AppRouteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteProtocol.swift; sourceTree = ""; }; 5802EBC82A8E45BA00E5CE4C /* ApplicationRouterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationRouterDelegate.swift; sourceTree = ""; }; @@ -2096,6 +2098,14 @@ path = MullvadREST; sourceTree = ""; }; + 44DD7D252B6D18E90005F67F /* Mocks */ = { + isa = PBXGroup; + children = ( + 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */, + ); + path = Mocks; + sourceTree = ""; + }; 5802EBC32A8E447000E5CE4C /* Router */ = { isa = PBXGroup; children = ( @@ -2739,6 +2749,7 @@ 58B0A2A1238EE67E00BC001D /* MullvadVPNTests */ = { isa = PBXGroup; children = ( + 44DD7D252B6D18E90005F67F /* Mocks */, A900E9BF2ACC661900C95F67 /* AccessTokenManager+Stubs.swift */, 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */, A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */, @@ -4527,6 +4538,7 @@ A9A5FA3D2ACB05D90083449F /* DeviceCheck.swift in Sources */, A900E9B82ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift in Sources */, A9A5FA3E2ACB05D90083449F /* DeviceCheckOperation.swift in Sources */, + 44DD7D272B6D18FB0005F67F /* MockTunnelInteractor.swift in Sources */, A9A5FA3F2ACB05D90083449F /* DeviceCheckRemoteService.swift in Sources */, A9A5FA402ACB05D90083449F /* DeviceCheckRemoteServiceProtocol.swift in Sources */, A9A5FA412ACB05D90083449F /* DeviceStateAccessor.swift in Sources */, diff --git a/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift b/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift new file mode 100644 index 000000000000..e2d1d5996ba7 --- /dev/null +++ b/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift @@ -0,0 +1,72 @@ +// +// MockTunnelInteractor.swift +// MullvadVPNTests +// +// Created by Andrew Bulhak on 2024-02-02. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadSettings +import PacketTunnelCore + +// this is still very minimal, and will be fleshed out as needed. +struct MockTunnelInteractor: TunnelInteractor { + var onUpdateTunnelStatus: ((TunnelStatus)->Void)? + + + var tunnel: (TunnelProtocol)? + + func getPersistentTunnels() -> [TunnelProtocol] { + return [] + } + + func createNewTunnel() -> TunnelProtocol { + fatalError() + } + + func setTunnel(_ tunnel: (TunnelProtocol)?, shouldRefreshTunnelState: Bool) { + + } + + var tunnelStatus: TunnelStatus = + TunnelStatus() + + func updateTunnelStatus(_ block: (inout TunnelStatus) -> Void) -> TunnelStatus { + var tunnelStatus = self.tunnelStatus + block(&tunnelStatus) + onUpdateTunnelStatus?(tunnelStatus) + return tunnelStatus + } + + var isConfigurationLoaded: Bool + + var settings: MullvadSettings.LatestTunnelSettings + + var deviceState: MullvadSettings.DeviceState + + func setConfigurationLoaded() { + } + + func setSettings(_ settings: MullvadSettings.LatestTunnelSettings, persist: Bool) { + } + + func setDeviceState(_ deviceState: MullvadSettings.DeviceState, persist: Bool) { + } + + func removeLastUsedAccount() { + } + + func handleRestError(_ error: Error) { + } + + func startTunnel() { + } + + func prepareForVPNConfigurationDeletion() { + } + + func selectRelay() throws -> PacketTunnelCore.SelectedRelay { + fatalError() + } +} diff --git a/ios/MullvadVPNTests/StartTunnelOperationTests.swift b/ios/MullvadVPNTests/StartTunnelOperationTests.swift index 31d32b3310b3..b4310ff6f88b 100644 --- a/ios/MullvadVPNTests/StartTunnelOperationTests.swift +++ b/ios/MullvadVPNTests/StartTunnelOperationTests.swift @@ -9,66 +9,13 @@ import XCTest import MullvadSettings import Operations -import PacketTunnelCore +import Network +import WireGuardKitTypes -struct MockTunnelInteractor: TunnelInteractor { - var tunnel: (TunnelProtocol)? - - func getPersistentTunnels() -> [TunnelProtocol] { - return [] - } - - func createNewTunnel() -> TunnelProtocol { - fatalError() - } - - func setTunnel(_ tunnel: (TunnelProtocol)?, shouldRefreshTunnelState: Bool) { - - } - - var tunnelStatus: TunnelStatus { - TunnelStatus() - } - - func updateTunnelStatus(_ block: (inout TunnelStatus) -> Void) -> TunnelStatus { - TunnelStatus() - } - - var isConfigurationLoaded: Bool - - var settings: MullvadSettings.LatestTunnelSettings - - var deviceState: MullvadSettings.DeviceState - - func setConfigurationLoaded() { - } - - func setSettings(_ settings: MullvadSettings.LatestTunnelSettings, persist: Bool) { - } - - func setDeviceState(_ deviceState: MullvadSettings.DeviceState, persist: Bool) { - } - - func removeLastUsedAccount() { - } - - func handleRestError(_ error: Error) { - } - - func startTunnel() { - } - - func prepareForVPNConfigurationDeletion() { - } - - func selectRelay() throws -> PacketTunnelCore.SelectedRelay { - fatalError() - } - - -} final class StartTunnelOperationTests: XCTestCase { + + let testQueue = DispatchQueue(label: "StartTunnelOperationTests.testQueue") override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. @@ -80,24 +27,57 @@ final class StartTunnelOperationTests: XCTestCase { func testFailsIfNotLoggedIn() throws { let operationQueue = AsyncOperationQueue() - let testQueue = DispatchQueue(label: "StartTunnelOperationTests.testQueue") let settings = LatestTunnelSettings() let expectation = XCTestExpectation(description:"") let operation = StartTunnelOperation( dispatchQueue: testQueue, interactor: MockTunnelInteractor(isConfigurationLoaded: true, settings: settings, deviceState: .loggedOut)) { result in - guard case let .failure(err) = result else { + guard case .failure(_) = result else { XCTFail("Operation returned \(result), not failure") return } - expectation.fulfill() - } operationQueue.addOperation(operation) wait(for: [expectation], timeout: 10.0) } + + func testSetsReconnectIfDisconnecting() { + 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 tunnelStatus = TunnelStatus() + tunnelStatus.state = .disconnecting(.nothing) + interactor.tunnelStatus = tunnelStatus + interactor.onUpdateTunnelStatus = { status in tunnelStatus = status } + let expectation = XCTestExpectation(description:"") + + let operation = StartTunnelOperation( + dispatchQueue: testQueue, + interactor: interactor) { result in + XCTAssertEqual(tunnelStatus.state, .disconnecting(.reconnect)) + expectation.fulfill() + } + } } From 881574a30f1ed8b0d6b27fb9eab88f10860b5af8 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Fri, 2 Feb 2024 15:10:51 +0100 Subject: [PATCH 3/9] Tidying --- .../Mocks/MockTunnelInteractor.swift | 17 +++++------------ .../StartTunnelOperationTests.swift | 12 +++--------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift b/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift index e2d1d5996ba7..93c4b68cb8d4 100644 --- a/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift +++ b/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift @@ -14,7 +14,6 @@ import PacketTunnelCore struct MockTunnelInteractor: TunnelInteractor { var onUpdateTunnelStatus: ((TunnelStatus)->Void)? - var tunnel: (TunnelProtocol)? func getPersistentTunnels() -> [TunnelProtocol] { @@ -26,7 +25,6 @@ struct MockTunnelInteractor: TunnelInteractor { } func setTunnel(_ tunnel: (TunnelProtocol)?, shouldRefreshTunnelState: Bool) { - } var tunnelStatus: TunnelStatus = @@ -45,8 +43,7 @@ struct MockTunnelInteractor: TunnelInteractor { var deviceState: MullvadSettings.DeviceState - func setConfigurationLoaded() { - } + func setConfigurationLoaded() {} func setSettings(_ settings: MullvadSettings.LatestTunnelSettings, persist: Bool) { } @@ -54,17 +51,13 @@ struct MockTunnelInteractor: TunnelInteractor { func setDeviceState(_ deviceState: MullvadSettings.DeviceState, persist: Bool) { } - func removeLastUsedAccount() { - } + func removeLastUsedAccount() {} - func handleRestError(_ error: Error) { - } + func handleRestError(_ error: Error) {} - func startTunnel() { - } + func startTunnel() {} - func prepareForVPNConfigurationDeletion() { - } + func prepareForVPNConfigurationDeletion() {} func selectRelay() throws -> PacketTunnelCore.SelectedRelay { fatalError() diff --git a/ios/MullvadVPNTests/StartTunnelOperationTests.swift b/ios/MullvadVPNTests/StartTunnelOperationTests.swift index b4310ff6f88b..73553fe69dc7 100644 --- a/ios/MullvadVPNTests/StartTunnelOperationTests.swift +++ b/ios/MullvadVPNTests/StartTunnelOperationTests.swift @@ -17,14 +17,6 @@ final class StartTunnelOperationTests: XCTestCase { let testQueue = DispatchQueue(label: "StartTunnelOperationTests.testQueue") - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - func testFailsIfNotLoggedIn() throws { let operationQueue = AsyncOperationQueue() let settings = LatestTunnelSettings() @@ -45,6 +37,7 @@ final class StartTunnelOperationTests: XCTestCase { } func testSetsReconnectIfDisconnecting() { + let operationQueue = AsyncOperationQueue() let settings = LatestTunnelSettings() var interactor = MockTunnelInteractor( isConfigurationLoaded: true, @@ -78,6 +71,7 @@ final class StartTunnelOperationTests: XCTestCase { XCTAssertEqual(tunnelStatus.state, .disconnecting(.reconnect)) expectation.fulfill() } + operationQueue.addOperation(operation) + wait(for: [expectation], timeout: 10.0) } - } From 8a15d2808a7091a28f1fe883d7a11ae271f68e7c Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Mon, 5 Feb 2024 12:56:21 +0100 Subject: [PATCH 4/9] Alphabetise test files in project --- ios/MullvadVPN.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 57997591c4a1..de7e7ac11e88 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -2780,6 +2780,7 @@ 584B26F3237434D00073B10E /* RelaySelectorTests.swift */, A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */, A9C342C42ACC42130045F00E /* ServerRelaysResponse+Stubs.swift */, + 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */, 5807E2C1243203D000F5FF30 /* StringTests.swift */, A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */, A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */, @@ -2787,7 +2788,6 @@ F09D04BA2AE95396003D4F89 /* URLSessionStub.swift */, 58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */, F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */, - 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */, ); path = MullvadVPNTests; sourceTree = ""; From e7a74251071f088fef1f5db97cc3ae1c2d890f64 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Mon, 5 Feb 2024 12:56:50 +0100 Subject: [PATCH 5/9] Muscellaneous changes from pull request --- ios/MullvadVPNTests/StartTunnelOperationTests.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ios/MullvadVPNTests/StartTunnelOperationTests.swift b/ios/MullvadVPNTests/StartTunnelOperationTests.swift index 73553fe69dc7..002d27d0fdb5 100644 --- a/ios/MullvadVPNTests/StartTunnelOperationTests.swift +++ b/ios/MullvadVPNTests/StartTunnelOperationTests.swift @@ -13,14 +13,14 @@ import Network import WireGuardKitTypes -final class StartTunnelOperationTests: XCTestCase { +class StartTunnelOperationTests: XCTestCase { let testQueue = DispatchQueue(label: "StartTunnelOperationTests.testQueue") func testFailsIfNotLoggedIn() throws { let operationQueue = AsyncOperationQueue() let settings = LatestTunnelSettings() - let expectation = XCTestExpectation(description:"") + let exp = expectation(description:"Start tunnel operation failed") let operation = StartTunnelOperation( dispatchQueue: testQueue, interactor: MockTunnelInteractor(isConfigurationLoaded: true, settings: settings, deviceState: .loggedOut)) { result in @@ -29,11 +29,11 @@ final class StartTunnelOperationTests: XCTestCase { XCTFail("Operation returned \(result), not failure") return } - expectation.fulfill() + exp.fulfill() } operationQueue.addOperation(operation) - wait(for: [expectation], timeout: 10.0) + wait(for: [exp], timeout: 1.0) } func testSetsReconnectIfDisconnecting() { @@ -63,7 +63,7 @@ final class StartTunnelOperationTests: XCTestCase { tunnelStatus.state = .disconnecting(.nothing) interactor.tunnelStatus = tunnelStatus interactor.onUpdateTunnelStatus = { status in tunnelStatus = status } - let expectation = XCTestExpectation(description:"") + let expectation = expectation(description:"Tunnel status set to reconnect") let operation = StartTunnelOperation( dispatchQueue: testQueue, @@ -72,6 +72,6 @@ final class StartTunnelOperationTests: XCTestCase { expectation.fulfill() } operationQueue.addOperation(operation) - wait(for: [expectation], timeout: 10.0) + wait(for: [expectation], timeout: 1.0) } } From 6c5110520336ab7ed1ffff1952ffa3a9bb794ea7 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Tue, 6 Feb 2024 11:15:47 +0100 Subject: [PATCH 6/9] Add test for starting a tunnel from a disconnected state --- ios/MullvadVPN.xcodeproj/project.pbxproj | 4 + ios/MullvadVPN/TunnelManager/Tunnel.swift | 3 +- ios/MullvadVPNTests/Mocks/MockTunnel.swift | 59 ++++++++++++++ .../Mocks/MockTunnelInteractor.swift | 41 ++++++---- .../StartTunnelOperationTests.swift | 77 ++++++++++++------- ios/Shared/ApplicationTarget.swift | 4 +- 6 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 ios/MullvadVPNTests/Mocks/MockTunnel.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index de7e7ac11e88..683962d1d36e 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 = ""; }; 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = ""; }; 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = ""; }; + 44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = ""; }; 5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = ""; }; 5802EBC62A8E457A00E5CE4C /* AppRouteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteProtocol.swift; sourceTree = ""; }; 5802EBC82A8E45BA00E5CE4C /* ApplicationRouterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationRouterDelegate.swift; sourceTree = ""; }; @@ -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 1ff8f9179b1b..dd57f53f5bc4 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 000000000000..df53c7092c56 --- /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 93c4b68cb8d4..b9e2979e3a27 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 002d27d0fdb5..e6797649dc10 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 f46fa2c64ee5..c1f3224c583d 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 From 95d9a485801c52060e8ccd10981dccfa8da58718 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Tue, 6 Feb 2024 11:44:27 +0100 Subject: [PATCH 7/9] SwiftFormat pass --- ios/MullvadVPNTests/Mocks/MockTunnel.swift | 37 +++++------ .../Mocks/MockTunnelInteractor.swift | 57 +++++++++-------- .../StartTunnelOperationTests.swift | 63 ++++++++++--------- ios/Shared/ApplicationTarget.swift | 3 +- 4 files changed, 83 insertions(+), 77 deletions(-) diff --git a/ios/MullvadVPNTests/Mocks/MockTunnel.swift b/ios/MullvadVPNTests/Mocks/MockTunnel.swift index df53c7092c56..b9daa63c876d 100644 --- a/ios/MullvadVPNTests/Mocks/MockTunnel.swift +++ b/ios/MullvadVPNTests/Mocks/MockTunnel.swift @@ -11,49 +11,50 @@ 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 { + + 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 { + + 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 b9e2979e3a27..49784143e8cc 100644 --- a/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift +++ b/ios/MullvadVPNTests/Mocks/MockTunnelInteractor.swift @@ -10,19 +10,24 @@ import Foundation import MullvadSettings import PacketTunnelCore -// this is still very minimal, and will be fleshed out as needed. +// this is still very minimal, and will be fleshed out as needed. class MockTunnelInteractor: TunnelInteractor { var isConfigurationLoaded: Bool - + var settings: MullvadSettings.LatestTunnelSettings - + var deviceState: MullvadSettings.DeviceState - - var onUpdateTunnelStatus: ((TunnelStatus)->Void)? - + + var onUpdateTunnelStatus: ((TunnelStatus) -> Void)? + var tunnel: (any TunnelProtocol)? - - init(isConfigurationLoaded: Bool, settings: MullvadSettings.LatestTunnelSettings, deviceState: MullvadSettings.DeviceState, onUpdateTunnelStatus: ( (TunnelStatus) -> Void)? = nil) { + + init( + isConfigurationLoaded: Bool, + settings: MullvadSettings.LatestTunnelSettings, + deviceState: MullvadSettings.DeviceState, + onUpdateTunnelStatus: ((TunnelStatus) -> Void)? = nil + ) { self.isConfigurationLoaded = isConfigurationLoaded self.settings = settings self.deviceState = deviceState @@ -30,46 +35,44 @@ class MockTunnelInteractor: TunnelInteractor { self.tunnel = nil self.tunnelStatus = TunnelStatus() } - + func getPersistentTunnels() -> [any TunnelProtocol] { return [] } - + func createNewTunnel() -> any TunnelProtocol { return MockTunnel(tunnelProvider: SimulatorTunnelProviderManager()) } - + func setTunnel(_ tunnel: (any TunnelProtocol)?, shouldRefreshTunnelState: Bool) { self.tunnel = tunnel } - + var tunnelStatus: TunnelStatus - + func updateTunnelStatus(_ block: (inout TunnelStatus) -> Void) -> TunnelStatus { var tunnelStatus = self.tunnelStatus block(&tunnelStatus) onUpdateTunnelStatus?(tunnelStatus) return tunnelStatus } - + func setConfigurationLoaded() {} - - func setSettings(_ settings: MullvadSettings.LatestTunnelSettings, persist: Bool) { - } - - func setDeviceState(_ deviceState: MullvadSettings.DeviceState, persist: Bool) { - } - + + func setSettings(_ settings: MullvadSettings.LatestTunnelSettings, persist: Bool) {} + + func setDeviceState(_ deviceState: MullvadSettings.DeviceState, persist: Bool) {} + func removeLastUsedAccount() {} - + func handleRestError(_ error: Error) {} - + func startTunnel() {} - + func prepareForVPNConfigurationDeletion() {} - - struct NotImplementedError: Error { } - + + struct NotImplementedError: Error {} + func selectRelay() throws -> PacketTunnelCore.SelectedRelay { throw NotImplementedError() } diff --git a/ios/MullvadVPNTests/StartTunnelOperationTests.swift b/ios/MullvadVPNTests/StartTunnelOperationTests.swift index e6797649dc10..840a88c6c541 100644 --- a/ios/MullvadVPNTests/StartTunnelOperationTests.swift +++ b/ios/MullvadVPNTests/StartTunnelOperationTests.swift @@ -6,20 +6,18 @@ // Copyright © 2024 Mullvad VPN AB. All rights reserved. // -import XCTest import MullvadSettings -import Operations import Network +import Operations import WireGuardKitTypes - +import XCTest class StartTunnelOperationTests: XCTestCase { - - //MARK: utility code for setting up tests - + // MARK: utility code for setting up tests + let testQueue = DispatchQueue(label: "StartTunnelOperationTests.testQueue") let operationQueue = AsyncOperationQueue() - + let loggedInDeviceState = DeviceState.loggedIn( StoredAccountData( identifier: "", @@ -36,7 +34,7 @@ class StartTunnelOperationTests: XCTestCase { wgKeyData: StoredWgKeyData(creationDate: Date(), privateKey: PrivateKey()) ) ) - + func makeInteractor(deviceState: DeviceState, tunnelState: TunnelState? = nil) -> MockTunnelInteractor { var interactor = MockTunnelInteractor( isConfigurationLoaded: true, @@ -48,54 +46,57 @@ class StartTunnelOperationTests: XCTestCase { } return interactor } - - //MARK: the tests + + // MARK: the tests func testFailsIfNotLoggedIn() throws { let settings = LatestTunnelSettings() - let exp = expectation(description:"Start tunnel operation failed") + let exp = expectation(description: "Start tunnel operation failed") let operation = StartTunnelOperation( dispatchQueue: testQueue, - interactor: makeInteractor(deviceState: .loggedOut)) { result in - guard case .failure(_) = result else { - XCTFail("Operation returned \(result), not failure") - return - } - exp.fulfill() + interactor: makeInteractor(deviceState: .loggedOut) + ) { result in + guard case .failure = result else { + XCTFail("Operation returned \(result), not failure") + return } - + exp.fulfill() + } + operationQueue.addOperation(operation) wait(for: [exp], timeout: 1.0) } - + func testSetsReconnectIfDisconnecting() { let settings = LatestTunnelSettings() var interactor = makeInteractor(deviceState: loggedInDeviceState, tunnelState: .disconnecting(.nothing)) var tunnelStatus = TunnelStatus() interactor.onUpdateTunnelStatus = { status in tunnelStatus = status } - let expectation = expectation(description:"Tunnel status set to reconnect") + let expectation = expectation(description: "Tunnel status set to reconnect") let operation = StartTunnelOperation( dispatchQueue: testQueue, - interactor: interactor) { result in - XCTAssertEqual(tunnelStatus.state, .disconnecting(.reconnect)) - expectation.fulfill() - } + interactor: interactor + ) { result in + XCTAssertEqual(tunnelStatus.state, .disconnecting(.reconnect)) + expectation.fulfill() + } 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 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() - } + 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 c1f3224c583d..98b0c97917f6 100644 --- a/ios/Shared/ApplicationTarget.swift +++ b/ios/Shared/ApplicationTarget.swift @@ -14,7 +14,8 @@ enum ApplicationTarget: CaseIterable { /// Returns target bundle identifier. var bundleIdentifier: String { // "MainApplicationIdentifier" does not exist if running tests - let mainBundleIdentifier = Bundle.main.object(forInfoDictionaryKey: "MainApplicationIdentifier") as? String ?? "tests" + let mainBundleIdentifier = Bundle.main + .object(forInfoDictionaryKey: "MainApplicationIdentifier") as? String ?? "tests" switch self { case .mainApp: return mainBundleIdentifier From d35a07b6cd20062d531263f5b36ccbd130a13e27 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Tue, 6 Feb 2024 12:06:13 +0100 Subject: [PATCH 8/9] Fix type --- ios/MullvadVPN/TunnelManager/Tunnel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/MullvadVPN/TunnelManager/Tunnel.swift b/ios/MullvadVPN/TunnelManager/Tunnel.swift index dd57f53f5bc4..5b85473e83c4 100644 --- a/ios/MullvadVPN/TunnelManager/Tunnel.swift +++ b/ios/MullvadVPN/TunnelManager/Tunnel.swift @@ -26,7 +26,7 @@ protocol TunnelProtocol: AnyObject { var isOnDemandEnabled: Bool { get set } var startDate: Date? { get } - init(tunnelProvider: VPNTunnelProviderManagerProtocol) + init(tunnelProvider: TunnelManagerProtocol) func addObserver(_ observer: any TunnelStatusObserver) func removeObserver(_ observer: any TunnelStatusObserver) From 2b42d8febf721809439a6ce6affc69ae60d1203b Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 7 Feb 2024 10:26:54 +0100 Subject: [PATCH 9/9] Miscellaneous PR feedback --- .../StartTunnelOperationTests.swift | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/ios/MullvadVPNTests/StartTunnelOperationTests.swift b/ios/MullvadVPNTests/StartTunnelOperationTests.swift index 840a88c6c541..5dfd5d904f17 100644 --- a/ios/MullvadVPNTests/StartTunnelOperationTests.swift +++ b/ios/MullvadVPNTests/StartTunnelOperationTests.swift @@ -36,7 +36,7 @@ class StartTunnelOperationTests: XCTestCase { ) func makeInteractor(deviceState: DeviceState, tunnelState: TunnelState? = nil) -> MockTunnelInteractor { - var interactor = MockTunnelInteractor( + let interactor = MockTunnelInteractor( isConfigurationLoaded: true, settings: LatestTunnelSettings(), deviceState: deviceState @@ -50,8 +50,7 @@ class StartTunnelOperationTests: XCTestCase { // MARK: the tests func testFailsIfNotLoggedIn() throws { - let settings = LatestTunnelSettings() - let exp = expectation(description: "Start tunnel operation failed") + let expectation = expectation(description: "Start tunnel operation failed") let operation = StartTunnelOperation( dispatchQueue: testQueue, interactor: makeInteractor(deviceState: .loggedOut) @@ -60,16 +59,15 @@ class StartTunnelOperationTests: XCTestCase { XCTFail("Operation returned \(result), not failure") return } - exp.fulfill() + expectation.fulfill() } operationQueue.addOperation(operation) - wait(for: [exp], timeout: 1.0) + wait(for: [expectation], timeout: 1.0) } func testSetsReconnectIfDisconnecting() { - let settings = LatestTunnelSettings() - var interactor = makeInteractor(deviceState: loggedInDeviceState, tunnelState: .disconnecting(.nothing)) + let interactor = makeInteractor(deviceState: loggedInDeviceState, tunnelState: .disconnecting(.nothing)) var tunnelStatus = TunnelStatus() interactor.onUpdateTunnelStatus = { status in tunnelStatus = status } let expectation = expectation(description: "Tunnel status set to reconnect") @@ -86,8 +84,7 @@ class StartTunnelOperationTests: XCTestCase { } func testStartsTunnelIfDisconnected() { - let settings = LatestTunnelSettings() - var interactor = makeInteractor(deviceState: loggedInDeviceState, tunnelState: .disconnected) + let interactor = makeInteractor(deviceState: loggedInDeviceState, tunnelState: .disconnected) let expectation = expectation(description: "Make tunnel provider and start tunnel") let operation = StartTunnelOperation( dispatchQueue: testQueue,