diff options
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 12 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift | 330 |
2 files changed, 342 insertions, 0 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index ca03deb050..ef3edd54f1 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 44B02E3B2BC5732D008EDF34 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44B02E3A2BC5732D008EDF34 /* LoggingTests.swift */; }; 44B02E3C2BC5B8A5008EDF34 /* Bundle+ProductVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */; }; 44B3C43A2BFE2C800079782C /* PacketTunnelActorReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44B3C4392BFE2C800079782C /* PacketTunnelActorReducer.swift */; }; + 44B3C43D2C00CBBD0079782C /* PacketTunnelActorReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44B3C43C2C00CBBC0079782C /* PacketTunnelActorReducerTests.swift */; }; 44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; }; 44BB5F982BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; }; 44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */; }; @@ -1404,6 +1405,7 @@ 449EBA252B975B9700DFA4EB /* PostQuantumKeyReceiving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyReceiving.swift; sourceTree = "<group>"; }; 44B02E3A2BC5732D008EDF34 /* LoggingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = "<group>"; }; 44B3C4392BFE2C800079782C /* PacketTunnelActorReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelActorReducer.swift; sourceTree = "<group>"; }; + 44B3C43C2C00CBBC0079782C /* PacketTunnelActorReducerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTunnelActorReducerTests.swift; sourceTree = "<group>"; }; 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelState+UI.swift"; sourceTree = "<group>"; }; 44BB5F992BE529FE002520EB /* TunnelStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelStateTests.swift; sourceTree = "<group>"; }; 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = "<group>"; }; @@ -2383,6 +2385,7 @@ 440E9EFB2BDA97C600B1FD11 /* GeneralAPIs */, 440E9EF12BDA940500B1FD11 /* Notifications */, 440E9EF72BDA95AC00B1FD11 /* PacketTunnel */, + 44B3C43B2C00CB570079782C /* PacketTunnelCore */, 440E9F012BDA99FA00B1FD11 /* Protocols */, 440E9EFE2BDA991200B1FD11 /* RelayCacheTracker */, 440E9EFF2BDA995800B1FD11 /* TunnelManager */, @@ -2570,6 +2573,14 @@ path = Protocols; sourceTree = "<group>"; }; + 44B3C43B2C00CB570079782C /* PacketTunnelCore */ = { + isa = PBXGroup; + children = ( + 44B3C43C2C00CBBC0079782C /* PacketTunnelActorReducerTests.swift */, + ); + path = PacketTunnelCore; + sourceTree = "<group>"; + }; 5802EBC32A8E447000E5CE4C /* Router */ = { isa = PBXGroup; children = ( @@ -5289,6 +5300,7 @@ A9A5F9EA2ACB05160083449F /* Bundle+ProductVersion.swift in Sources */, A9A5F9EB2ACB05160083449F /* CharacterSet+IPAddress.swift in Sources */, F0D8825C2B04F70E00D3EF9A /* OutgoingConnectionData.swift in Sources */, + 44B3C43D2C00CBBD0079782C /* PacketTunnelActorReducerTests.swift in Sources */, A9A5F9EC2ACB05160083449F /* CodingErrors+CustomErrorDescription.swift in Sources */, A9A5F9ED2ACB05160083449F /* NSRegularExpression+IPAddress.swift in Sources */, A9A5F9EE2ACB05160083449F /* RESTCreateApplePaymentResponse+Localization.swift in Sources */, diff --git a/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift b/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift new file mode 100644 index 0000000000..67c26730d2 --- /dev/null +++ b/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift @@ -0,0 +1,330 @@ +// +// PacketTunnelActorReducerTests.swift +// MullvadVPNTests +// +// Created by Andrew Bulhak on 2024-04-29. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import MullvadTypes +@testable import PacketTunnelCore +import WireGuardKitTypes +import XCTest + +final class PacketTunnelActorReducerTests: XCTestCase { + // test data + let selectedRelay = SelectedRelay( + endpoint: MullvadEndpoint( + ipv4Relay: IPv4Endpoint(ip: .loopback, port: 1300), + ipv4Gateway: .loopback, + ipv6Gateway: .loopback, + publicKey: PrivateKey().publicKey.rawValue + ), + hostname: "se-got", + location: Location( + country: "", + countryCode: "se", + city: "", + cityCode: "got", + latitude: 0, + longitude: 0 + ), retryAttempts: 0 + ) + func makeConnectionData(keyPolicy: State.KeyPolicy = .useCurrent) -> State.ConnectionData { + State.ConnectionData( + selectedRelay: selectedRelay, + relayConstraints: RelayConstraints(), + keyPolicy: keyPolicy, + networkReachability: .reachable, + connectionAttemptCount: 0, + connectedEndpoint: selectedRelay.endpoint, + transportLayer: .udp, + remotePort: 12345, + isPostQuantum: false + ) + } + + // MARK: .start + + func testHandleStartWithoutPreselectedRelay() { + // Given + var state = State.initial + // When + let effects = PacketTunnelActor.reducer(&state, .start(StartOptions(launchSource: .app))) + // Then + XCTAssertEqual(effects, [ + .startDefaultPathObserver, + .startTunnelMonitor, + .startConnection(.random), + ]) + } + + func testHandleStartWithPreselectedRelay() { + // Given + var state = State.initial + // When + let effects = PacketTunnelActor.reducer( + &state, + .start(StartOptions(launchSource: .app, selectedRelay: selectedRelay)) + ) + // Then + XCTAssertEqual(effects, [ + .startDefaultPathObserver, + .startTunnelMonitor, + .startConnection(.preSelected(selectedRelay)), + ]) + } + + // MARK: .stop + + func testHandleStopFromConnected() { + // Given + let connectionData = makeConnectionData() + var state = State.connected(connectionData) + // When + let effects = PacketTunnelActor.reducer(&state, .stop) + // Then + XCTAssertEqual(state, .disconnecting(connectionData)) + XCTAssertEqual(effects, [ + .stopTunnelMonitor, + .stopDefaultPathObserver, + .stopTunnelAdapter, + .setDisconnectedState, + ]) + } + + func testHandleStopFromConnecting() { + // Given + let connectionData = makeConnectionData() + var state = State.connecting(connectionData) + // When + let effects = PacketTunnelActor.reducer(&state, .stop) + // Then + XCTAssertEqual(state, .disconnecting(connectionData)) + XCTAssertEqual(effects, [ + .stopTunnelMonitor, + .stopDefaultPathObserver, + .stopTunnelAdapter, + .setDisconnectedState, + ]) + } + + func testHandleStopFromReconnecting() { + // Given + let connectionData = makeConnectionData() + var state = State.reconnecting(connectionData) + // When + let effects = PacketTunnelActor.reducer(&state, .stop) + // Then + XCTAssertEqual(state, .disconnecting(connectionData)) + XCTAssertEqual(effects, [ + .stopTunnelMonitor, + .stopDefaultPathObserver, + .stopTunnelAdapter, + .setDisconnectedState, + ]) + } + + func testHandleStopFromError() { + // Given + let blockingData = State.BlockingData( + reason: .accountExpired, + keyPolicy: .useCurrent, + networkReachability: .reachable, + priorState: .connected + ) + var state = State.error(blockingData) + + // When + let effects = PacketTunnelActor.reducer(&state, .stop) + + // Then + XCTAssertEqual(effects, [ + .stopDefaultPathObserver, + .stopTunnelAdapter, + .setDisconnectedState, + ]) + } + + func testHandleStopFromUnconnectedStates() { + // Given + let states: [State] = [.initial, .disconnected] + + for var state in states { + // When + let effects = PacketTunnelActor.reducer(&state, .stop) + + // Then + XCTAssertEqual(effects, []) + } + } + + // MARK: .reconnect + + func testHandleUserInitiatedReconnectFromConnectedStates() { + // Given + var state = State.connected(makeConnectionData()) + + // When + let effects = PacketTunnelActor.reducer(&state, .reconnect(.current, reason: .userInitiated)) + + // Then + XCTAssertEqual(effects, [ + .stopTunnelMonitor, + .restartConnection(.current, .userInitiated), + ]) + } + + func testHandleConnectionLossReconnectFromConnectedStates() { + // Given + var state = State.connected(makeConnectionData()) + + // When + let effects = PacketTunnelActor.reducer(&state, .reconnect(.random, reason: .connectionLoss)) + + // Then + XCTAssertEqual(effects, [ + .restartConnection(.random, .connectionLoss), + ]) + } + + func testHandleReconnectFromDisconnectedIsNoOp() { + // Given + var state = State.disconnected + + // When + let effects = PacketTunnelActor.reducer(&state, .reconnect(.random, reason: .connectionLoss)) + + // Then + XCTAssertEqual(effects, []) + } + + func testHandleConnectionLossReconnectFromPQKeyNegotiation() { + // Given + var state = State.negotiatingPostQuantumKey(makeConnectionData(), PrivateKey()) + + // When + let effects = PacketTunnelActor.reducer(&state, .reconnect(.random, reason: .connectionLoss)) + + // Then + XCTAssertEqual(effects, [.restartConnection(.random, .connectionLoss)]) + } + + func testHandleUserReconnectFromPQKeyNegotiation() { + // Given + var state = State.negotiatingPostQuantumKey(makeConnectionData(), PrivateKey()) + + // When + let effects = PacketTunnelActor.reducer(&state, .reconnect(.random, reason: .userInitiated)) + + // Then + XCTAssertEqual(effects, [ + .stopTunnelMonitor, + .restartConnection(.random, .userInitiated), + ]) + } + + // MARK: .error + + func testHandleError() { + // Given + var state = State.connected(makeConnectionData()) + + // When + let effects = PacketTunnelActor.reducer(&state, .error(.deviceRevoked)) + + // then + XCTAssertEqual(effects, [ + .configureForErrorState(.deviceRevoked), + ]) + } + + // MARK: .notifyKeyRotated + + func testHandleNotifyKeyRotatedWhileUsingCurrentKey() { + // Given + var state = State.connected(makeConnectionData(keyPolicy: .useCurrent)) + let date = Date() + + // When + let effects = PacketTunnelActor.reducer(&state, .notifyKeyRotated(date)) + + // then + XCTAssertEqual(effects, [ + .cacheActiveKey(date), + ]) + } + + func testHandleNotifyKeyRotatedWhileUsingPriorKey() { + // Given + let keyPolicy = State.KeyPolicy.usePrior(PrivateKey(), AutoCancellingTask(Task(operation: {}))) + var state = State.connected(makeConnectionData(keyPolicy: keyPolicy)) + let date = Date() + + // When + let effects = PacketTunnelActor.reducer(&state, .notifyKeyRotated(date)) + + // then + XCTAssertEqual(effects, []) + } + + // MARK: .switchKey + + func testHandleSwitchKeyFromUseCurrent() { + // Given + var state = State.connected(makeConnectionData(keyPolicy: .useCurrent)) + + // When + let effects = PacketTunnelActor.reducer(&state, .switchKey) + + // then + XCTAssertEqual(effects, []) + } + + func testHandleSwitchKeyFromUsePrior() { + // Given + let keyPolicy = State.KeyPolicy.usePrior(PrivateKey(), AutoCancellingTask(Task(operation: {}))) + var state = State.connected(makeConnectionData(keyPolicy: keyPolicy)) + + // When + let effects = PacketTunnelActor.reducer(&state, .switchKey) + + // then + XCTAssertEqual(state.keyPolicy, State.KeyPolicy.useCurrent) + XCTAssertEqual(effects, [ + .reconnect(.random), + ]) + } + + // MARK: .monitorEvent + + func testHandleMonitorEvent_ConnectionEstablishedWhileConnecting() { + // Given + var connectionData = makeConnectionData() + connectionData.connectionAttemptCount = 2 + var state = State.connecting(connectionData) + + // When + let effects = PacketTunnelActor.reducer(&state, .monitorEvent(.connectionEstablished)) + + // Then + var expectedConnectionData = connectionData + expectedConnectionData.connectionAttemptCount = 0 + XCTAssertEqual(state, .connected(expectedConnectionData)) + XCTAssertEqual(effects, []) + } + + func testHandleMonitorEvent_ConnectionLostWhileConnected() { + // Given + let connectionData = makeConnectionData() + var state = State.connected(connectionData) + + // When + let effects = PacketTunnelActor.reducer(&state, .monitorEvent(.connectionLost)) + + // Then + XCTAssertEqual(effects, [ + .restartConnection(.random, .connectionLoss), + ]) + } +} |
