/* * 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.Logic.Connection.Contracts.Models.Intents; using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents.Locations.Countries; using ProtonVPN.Client.Logic.Connection.Contracts.Preferences; using ProtonVPN.Client.Logic.Connection.Contracts.ServerListGenerators; using ProtonVPN.Client.Logic.Connection.ServerListGenerators; using ProtonVPN.Client.Logic.Servers.Contracts; using ProtonVPN.Client.Logic.Servers.Contracts.Enums; using ProtonVPN.Client.Logic.Servers.Contracts.Models; using ProtonVPN.Client.Logic.Users.Contracts.Messages; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.Common.Core.Geographical; using ProtonVPN.Common.Core.Networking; using ProtonVPN.Logging.Contracts; namespace ProtonVPN.Client.Logic.Connection.Tests; [TestClass] public class ServerListGeneratorTests { private static readonly IList _preferredProtocols = [VpnProtocol.OpenVpnUdp]; [TestMethod] public void Generate_SetsAreAllExcluded_WhenExclusionsRemoveAllServers() { // Arrange ISettings settings = CreateSettings(); IExclusionChecker exclusionChecker = Substitute.For(); exclusionChecker.HasExcludedLocations.Returns(true); exclusionChecker.IsServerExcluded(Arg.Any()).Returns(true); List servers = [CreateServer("s1", "US")]; IServersLoader serversLoader = Substitute.For(); serversLoader.GetServers().Returns(servers); ILogger logger = Substitute.For(); ServerListGenerator generator = new(settings, serversLoader, exclusionChecker, logger); IConnectionIntent connectionIntent = new ConnectionIntent(MultiCountryLocationIntent.Default); // Act ServerListResult result = generator.Generate(connectionIntent, _preferredProtocols); // Assert Assert.AreEqual(0, result.PhysicalServers.Count); Assert.IsTrue(result.Diagnostic.AreAllCandidatesExcluded); } [TestMethod] public void Generate_SetsHadCandidates_WhenServersRemainAfterExclusions() { // Arrange ISettings settings = CreateSettings(); IExclusionChecker exclusionChecker = Substitute.For(); exclusionChecker.HasExcludedLocations.Returns(true); exclusionChecker.IsServerExcluded(Arg.Is(s => s.ExitCountry == "US")).Returns(true); List servers = [ CreateServer("s1", "CH"), CreateServer("s2", "US"), ]; IServersLoader serversLoader = Substitute.For(); serversLoader.GetServers().Returns(servers); ILogger logger = Substitute.For(); ServerListGenerator generator = new(settings, serversLoader, exclusionChecker, logger); IConnectionIntent connectionIntent = new ConnectionIntent(MultiCountryLocationIntent.Default); // Act ServerListResult result = generator.Generate(connectionIntent, _preferredProtocols); // Assert Assert.AreEqual(1, result.PhysicalServers.Count); Assert.IsFalse(result.Diagnostic.AreAllCandidatesExcluded); } [TestMethod] public void Generate_DoesNotFlagExclusions_WhenNoServersWereAvailable() { // Arrange ISettings settings = CreateSettings(); IExclusionChecker exclusionChecker = Substitute.For(); exclusionChecker.HasExcludedLocations.Returns(true); exclusionChecker.IsServerExcluded(Arg.Any()).Returns(true); IServersLoader serversLoader = Substitute.For(); serversLoader.GetServers().Returns([]); ILogger logger = Substitute.For(); ServerListGenerator generator = new(settings, serversLoader, exclusionChecker, logger); IConnectionIntent connectionIntent = new ConnectionIntent(MultiCountryLocationIntent.Default); // Act ServerListResult result = generator.Generate(connectionIntent, _preferredProtocols); // Assert Assert.AreEqual(0, result.PhysicalServers.Count); Assert.IsFalse(result.Diagnostic.AreAllCandidatesExcluded); } [TestMethod] public void Generate_DoesNotApplyExclusions_WhenSingleLocationIntent() { // Arrange ISettings settings = CreateSettings(); IExclusionChecker exclusionChecker = Substitute.For(); exclusionChecker.HasExcludedLocations.Returns(true); exclusionChecker.IsServerExcluded(Arg.Any()).Returns(true); List servers = [CreateServer("s1", "US")]; IServersLoader serversLoader = Substitute.For(); serversLoader.GetServers().Returns(servers); ILogger logger = Substitute.For(); ServerListGenerator generator = new(settings, serversLoader, exclusionChecker, logger); IConnectionIntent connectionIntent = new ConnectionIntent(new SingleCountryLocationIntent("US")); // Act ServerListResult result = generator.Generate(connectionIntent, _preferredProtocols); // Assert Assert.AreEqual(1, result.PhysicalServers.Count); Assert.IsFalse(result.Diagnostic.AreAllCandidatesExcluded); } private static ISettings CreateSettings() { ISettings settings = Substitute.For(); settings.DeviceLocation.Returns((DeviceLocation?)null); settings.IsPortForwardingEnabled.Returns(false); settings.VpnPlan.Returns(new VpnPlan("VPN Plus", "vpnplus", 1, false)); return settings; } private static Server CreateServer(string id, string exitCountry) { return new Server { Id = id, Name = id, City = "City", State = "State", EntryCountry = exitCountry, ExitCountry = exitCountry, HostCountry = exitCountry, Domain = $"{id}.example.com", Latitude = 0, Longitude = 0, Status = 1, Tier = ServerTiers.Plus, Features = 0, Load = 0, Score = 1, StatusReference = new() { Index = 0, Cost = 0, Penalty = 0, }, EntryLocation = new() { Latitude = 0, Longitude = 0, }, ExitLocation = new() { Latitude = 0, Longitude = 0, }, Servers = [ new PhysicalServer { Id = $"{id}-p1", EntryIp = "10.0.0.1", ExitIp = "10.0.0.1", Domain = $"{id}.example.com", Label = $"{id}-p1", Status = 1, X25519PublicKey = "key", Signature = "signature", IsIpv6Supported = false, } ], IsVirtual = false, GatewayName = id, }; } }