/*
* 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 System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using FlaUI.Core.Tools;
using NUnit.Framework;
namespace ProtonVPN.UI.Tests.TestsHelper;
public class NetworkUtils
{
public static void VerifyLocalNetworking(bool isLanEnabled)
{
IPAddress? ipAddress = GetDefaultGatewayAddress() ?? throw new Exception("Default gateway is null.");
PingReply reply = new Ping().Send(ipAddress.ToString());
Assert.That(reply.Status == IPStatus.Success, isLanEnabled ? Is.True : Is.False);
}
public static void AssertInternetAvailability(bool shouldBeAvailable)
{
bool isAvailable = IsInternetAvailable(shouldBeAvailable);
if (shouldBeAvailable)
{
Assert.That(isAvailable, Is.True, "Expected internet to be available.");
}
else
{
Assert.That(isAvailable, Is.False, "Expected internet to not be available.");
}
}
private static bool IsInternetAvailable(bool shouldBeAvailable)
{
Thread.Sleep(TestConstants.TenSecondsTimeout);
JObject? connectionData = GetConnectionDataAsync(shouldBeAvailable).GetAwaiter().GetResult();
return connectionData?["status"]?.ToString() == "success";
}
public static string GetIpAddressWithRetry()
{
RetryResult retry = Retry.WhileEmpty(
() =>
{
DnsHelper.FlushDns();
return GetIpAddressAsync().GetAwaiter().GetResult() ?? string.Empty;
},
TestConstants.ThirtySecondsTimeout, TestConstants.ApiRetryInterval, ignoreException: true);
return retry.Result ?? throw new HttpRequestException($"Failed to get IP Address. \n {retry.LastException?.Message} \n {retry.LastException?.StackTrace}");
}
public static string GetCountryNameWithRetry()
{
RetryResult retry = Retry.WhileEmpty(
() =>
{
DnsHelper.FlushDns();
return GetCountryNameAsync().Result ?? string.Empty;
},
TestConstants.ThirtySecondsTimeout, TestConstants.ApiRetryInterval, ignoreException: true);
return retry.Result ?? throw new HttpRequestException($"Failed to get country name. \n {retry.LastException?.Message} \n {retry.LastException?.StackTrace}");
}
public static void VerifyUserIsConnectedToExpectedCountry(string countryNameToCompare)
{
string countryName = GetCountryNameWithRetry();
Assert.That(countryName.Equals(countryNameToCompare), Is.True, $"User was connected to unexpected country." +
$"\n API returned: {countryName}" +
$"\n Expected result: {countryNameToCompare}");
}
public static void VerifyIpAddressDoesNotMatchWithRetry(string? ipAddressToCompare)
{
if (ipAddressToCompare is null)
{
Assert.Fail("ipAddressToCompare is null - was GetIpAddressWithRetry() called before network was ready?");
return;
}
string? ipAddressFomAPI = null;
RetryResult retry = Retry.WhileTrue(
() =>
{
DnsHelper.FlushDns();
ipAddressFomAPI = GetIpAddressWithRetry();
return ipAddressFomAPI.Equals(ipAddressToCompare);
},
TestConstants.ThirtySecondsTimeout, TestConstants.ApiRetryInterval);
if (!retry.Success)
{
Assert.Fail($"API IP Address should not match provided IP address.\n" +
$"API returned IP address: {ipAddressFomAPI}.\n" +
$"IP to compare: {ipAddressToCompare}");
}
}
public static void VerifyIpAddressMatchesWithRetry(string? ipAddressToCompare)
{
if (ipAddressToCompare is null)
{
Assert.Fail("ipAddressToCompare is null - was GetIpAddressWithRetry() called before network was ready?");
return;
}
string? ipAddressFomAPI = null;
RetryResult retry = Retry.WhileFalse(
() =>
{
DnsHelper.FlushDns();
ipAddressFomAPI = GetIpAddressWithRetry();
return ipAddressFomAPI.Equals(ipAddressToCompare);
},
TestConstants.ThirtySecondsTimeout, TestConstants.ApiRetryInterval);
if (!retry.Success)
{
Assert.Fail($"API IP Address should match provided IP address.\n" +
$"API returned IP address: {ipAddressFomAPI}.\n" +
$"IP to compare: {ipAddressToCompare}");
}
}
public static void AssertCorrectNetworkAdapter(string adapterName)
{
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();
NetworkInterface? matchingAdapter = adapters.FirstOrDefault(a => a.Description.Contains(adapterName));
Assert.That(matchingAdapter, Is.Not.Null, $"No network adapter found with description containing '{adapterName}'");
Assert.That(matchingAdapter!.OperationalStatus, Is.EqualTo(OperationalStatus.Up), $"Adapter '{matchingAdapter.Description}' is not up");
}
private static IPAddress? GetDefaultGatewayAddress()
{
return NetworkInterface
.GetAllNetworkInterfaces()
.Where(n => n.Name.EndsWith("Wi-Fi") || n.Name.EndsWith("Ethernet"))
.SelectMany(n => n.GetIPProperties().GatewayAddresses ?? Enumerable.Empty())
.Select(g => g?.Address)
.FirstOrDefault(a => a != null);
}
private static async Task GetCountryNameAsync()
{
JObject? response = await GetConnectionDataAsync();
return response?["country"]?.ToString();
}
private static async Task GetIpAddressAsync()
{
JObject? response = await GetConnectionDataAsync();
return response?["query"]?.ToString();
}
private static async Task GetConnectionDataAsync(bool errorIsNotExpected = true)
{
string endpoint = "http://ip-api.com/json/";
// Make sure that fresh socket is created when requesting connection data
using HttpClient client = new() { Timeout = TimeSpan.FromSeconds(10) };
try
{
string response = await client.GetStringAsync(endpoint);
JObject json = JObject.Parse(response);
return json;
}
catch (Exception e)
{
if (errorIsNotExpected)
{
TestContext.WriteLine($"GetIpAddressWithRetry failed. Result: {e.Message}");
}
return null;
}
}
public static void AssertTorStatus(bool shouldBeAvailable, string? vpnIp = null)
{
string? ip = null;
bool? isTor = null;
RetryResult retry = Retry.WhileEmpty(
() =>
{
JObject? result = GetTorStatusAsync().GetAwaiter().GetResult();
ip = result?["IP"]?.Value();
isTor = result?["IsTor"]?.Value();
// Returning only the IP, since IP and IsTor are always returned together
return ip ?? string.Empty;
},
TestConstants.ThirtySecondsTimeout, TestConstants.ApiRetryInterval);
Assert.That(retry.Success, Is.True, "Failed to retrieve Tor status within timeout.");
if (shouldBeAvailable)
{
Assert.That(isTor, Is.True);
Assert.That(ip, Does.Not.Contain(vpnIp));
}
else
{
Assert.That(isTor, Is.False);
}
}
private static async Task GetTorStatusAsync()
{
string endpoint = "https://check.torproject.org/api/ip";
// Make sure that fresh socket is created when requesting connection data
using (HttpClient client = new())
{
try
{
string response = await client.GetStringAsync(endpoint);
JObject json = JObject.Parse(response);
return json;
}
catch (HttpRequestException e)
{
TestContext.WriteLine($"GetTorStatusWithRetry failed. Result: {e.Message}");
return null;
}
}
}
}