/* * Copyright (c) 2026 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using NSubstitute; using ProtonVPN.Client.EventMessaging.Contracts; using ProtonVPN.Client.Logic.Auth.Contracts; using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents; using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents.Features; using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents.Locations.Countries; using ProtonVPN.Client.Logic.Connection.Contracts.RequestCreators; using ProtonVPN.Client.Logic.Connection.GuestHole; using ProtonVPN.Client.Logic.Connection.Statistics; using ProtonVPN.Client.Logic.Servers.Contracts; using ProtonVPN.Client.Logic.Services.Contracts; using ProtonVPN.Client.Logic.Users.Contracts.Messages; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.EntityMapping.Contracts; using ProtonVPN.Logging.Contracts; using ProtonVPN.ProcessCommunication.Contracts.Entities.Crypto; using ProtonVPN.ProcessCommunication.Contracts.Entities.LocalAgent; using ProtonVPN.ProcessCommunication.Contracts.Entities.Vpn; using ProtonVPN.StatisticalEvents.Contracts.Dimensions; namespace ProtonVPN.Client.Logic.Connection.Tests; [TestClass] public class ConnectionManagerTest { private const int PAID_PLAN_TIER = 2; private ILogger? _logger; private ISettings? _settings; private IVpnServiceCaller? _vpnServiceCaller; private IEventMessageSender? _eventMessageSender; private IEntityMapper? _entityMapper; private IConnectionRequestCreator? _connectionRequestCreator; private IReconnectionRequestCreator? _reconnectionRequestCreator; private IDisconnectionRequestCreator? _disconnectionRequestCreator; private IServersLoader? _serversLoader; private IFavoriteServersStorage? _favoriteServersStorage; private IGuestHoleServersFileStorage? _guestHoleServersFileStorage; private IGuestHoleConnectionRequestCreator? _guestHoleConnectionRequestCreator; private IConnectionStatisticalEventsManager? _statisticalEventManager; private IConnectionKeyManager? _connectionKeyManager; [TestInitialize] public void Initialize() { _logger = Substitute.For(); _settings = Substitute.For(); _vpnServiceCaller = Substitute.For(); _eventMessageSender = Substitute.For(); _entityMapper = Substitute.For(); _connectionRequestCreator = Substitute.For(); _reconnectionRequestCreator = Substitute.For(); _disconnectionRequestCreator = Substitute.For(); _serversLoader = Substitute.For(); _favoriteServersStorage = Substitute.For(); _guestHoleServersFileStorage = Substitute.For(); _guestHoleConnectionRequestCreator = Substitute.For(); _statisticalEventManager = Substitute.For(); _connectionKeyManager = Substitute.For(); _connectionRequestCreator!.CreateAsync(Arg.Any()).Returns(GetConnectionRequestIpcEntity()); _reconnectionRequestCreator!.CreateAsync(Arg.Any()).Returns(GetConnectionRequestIpcEntity()); } [TestCleanup] public virtual void Cleanup() { _logger = null; _settings = null; _vpnServiceCaller = null; _eventMessageSender = null; _entityMapper = null; _connectionRequestCreator = null; _reconnectionRequestCreator = null; _disconnectionRequestCreator = null; _serversLoader = null; _favoriteServersStorage = null; _guestHoleServersFileStorage = null; _guestHoleConnectionRequestCreator = null; _statisticalEventManager = null; _connectionKeyManager = null; } [TestMethod] [DataRow(typeof(SecureCoreFeatureIntent))] [DataRow(typeof(TorFeatureIntent))] public async Task ConnectAsync_ShouldNot_ChangeConnectionIntentWhenPortForwardingEnabledAsync(Type featureIntentType) { // Arrange _settings!.IsPortForwardingEnabled.Returns(true); _settings!.VpnPlan.Returns(new VpnPlan(string.Empty, string.Empty, PAID_PLAN_TIER, false)); ConnectionManager connectionManager = GetConnectionManager(); IFeatureIntent featureIntent = GetFeatureIntent(featureIntentType); IConnectionIntent connectionIntent = GetConnectionIntent(featureIntent); // Act await connectionManager.ConnectAsync(VpnTriggerDimension.Auto, connectionIntent); // Assert Assert.IsTrue(connectionManager.CurrentConnectionIntent?.IsSameAs(connectionIntent)); } [TestMethod] [DataRow(typeof(SecureCoreFeatureIntent))] [DataRow(typeof(TorFeatureIntent))] public async Task ConnectAsync_ShouldNot_ChangeConnectionIntentWhenPortForwardingDisabledAsync(Type featureIntentType) { // Arrange _settings!.IsPortForwardingEnabled.Returns(false); _settings!.VpnPlan.Returns(new VpnPlan(string.Empty, string.Empty, PAID_PLAN_TIER, false)); ConnectionManager connectionManager = GetConnectionManager(); IFeatureIntent featureIntent = GetFeatureIntent(featureIntentType); IConnectionIntent connectionIntent = GetConnectionIntent(featureIntent); // Act await connectionManager.ConnectAsync(VpnTriggerDimension.Auto, connectionIntent); // Assert Assert.IsTrue(connectionManager.CurrentConnectionIntent?.IsSameAs(connectionIntent)); } [TestMethod] [DataRow(typeof(SecureCoreFeatureIntent))] [DataRow(typeof(TorFeatureIntent))] public async Task ReconnectAsync_ShouldNot_ChangeConnectionIntentWhenPortForwardingEnabledAsync(Type featureIntentType) { // Arrange _settings!.IsPortForwardingEnabled.Returns(true); _settings!.VpnPlan.Returns(new VpnPlan(string.Empty, string.Empty, PAID_PLAN_TIER, false)); ConnectionManager connectionManager = GetConnectionManager(); IFeatureIntent featureIntent = GetFeatureIntent(featureIntentType); IConnectionIntent connectionIntent = GetConnectionIntent(featureIntent); // Act await connectionManager.ConnectAsync(VpnTriggerDimension.Auto, connectionIntent); await connectionManager.ReconnectAsync(VpnTriggerDimension.Auto); // Assert Assert.IsTrue(connectionManager.CurrentConnectionIntent?.IsSameAs(connectionIntent)); } private ConnectionRequestIpcEntity GetConnectionRequestIpcEntity() { return new ConnectionRequestIpcEntity() { Servers = [new VpnServerIpcEntity()], Credentials = new VpnCredentialsIpcEntity { Certificate = new ConnectionCertificateIpcEntity() { Pem = "pem", ExpirationDateUtc = DateTime.Now.AddDays(1) }, ClientKeyPair = new AsymmetricKeyPairIpcEntity { PublicKey = new PublicKeyIpcEntity(), SecretKey = new SecretKeyIpcEntity(), } } }; } private ConnectionManager GetConnectionManager() { return new( _logger!, _settings!, _vpnServiceCaller!, _eventMessageSender!, _entityMapper!, _connectionRequestCreator!, _reconnectionRequestCreator!, _disconnectionRequestCreator!, _serversLoader!, _favoriteServersStorage!, _guestHoleServersFileStorage!, _guestHoleConnectionRequestCreator!, _statisticalEventManager!, _connectionKeyManager!); } private IConnectionIntent GetConnectionIntent(IFeatureIntent featureIntent) { return new ConnectionIntent(SingleCountryLocationIntent.From("US"), featureIntent); } private IFeatureIntent GetFeatureIntent(Type type) { return (Activator.CreateInstance(type) as IFeatureIntent)!; } }