summaryrefslogtreecommitdiffhomepage
path: root/test/test-manager/src
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2024-03-21 10:53:42 +0100
committerMarkus Pettersson <markus.pettersson@mullvad.net>2024-03-25 11:07:34 +0100
commit1bb03d8b151b643f86e026ebf250e5d452f9b727 (patch)
treee6c4ac7728725b546261cbc737af19f6abc4e7ed /test/test-manager/src
parentb2a81664962728e2a7e8b8eee27050bde1756e20 (diff)
downloadmullvadvpn-1bb03d8b151b643f86e026ebf250e5d452f9b727.tar.xz
mullvadvpn-1bb03d8b151b643f86e026ebf250e5d452f9b727.zip
Make sure connecting works while API is unavailable
Diffstat (limited to 'test/test-manager/src')
-rw-r--r--test/test-manager/src/tests/account.rs30
-rw-r--r--test/test-manager/src/tests/helpers.rs87
-rw-r--r--test/test-manager/src/tests/tunnel.rs32
3 files changed, 104 insertions, 45 deletions
diff --git a/test/test-manager/src/tests/account.rs b/test/test-manager/src/tests/account.rs
index 60c61a78ff..80c6272d2a 100644
--- a/test/test-manager/src/tests/account.rs
+++ b/test/test-manager/src/tests/account.rs
@@ -1,3 +1,5 @@
+use crate::tests::helpers::{login_with_retries, THROTTLE_RETRY_DELAY};
+
use super::config::TEST_CONFIG;
use super::{helpers, ui, Error, TestContext};
use mullvad_api::DevicesProxy;
@@ -10,8 +12,6 @@ use talpid_types::net::wireguard;
use test_macro::test_function;
use test_rpc::ServiceClient;
-const THROTTLE_RETRY_DELAY: Duration = Duration::from_secs(120);
-
/// Log in and create a new device for the account.
#[test_function(always_run = true, must_succeed = true, priority = -100)]
pub async fn test_login(
@@ -241,32 +241,6 @@ pub fn new_device_client() -> DevicesProxy {
DevicesProxy::new(rest_handle)
}
-/// Log in and retry if it fails due to throttling
-pub async fn login_with_retries(
- mullvad_client: &mut MullvadProxyClient,
-) -> Result<(), mullvad_management_interface::Error> {
- loop {
- match mullvad_client
- .login_account(TEST_CONFIG.account_number.clone())
- .await
- {
- Err(mullvad_management_interface::Error::Rpc(status))
- if status.message().to_uppercase().contains("THROTTLED") =>
- {
- // Work around throttling errors by sleeping
- log::debug!(
- "Login failed due to throttling. Sleeping for {} seconds",
- THROTTLE_RETRY_DELAY.as_secs()
- );
-
- tokio::time::sleep(THROTTLE_RETRY_DELAY).await;
- }
- Err(err) => break Err(err),
- Ok(_) => break Ok(()),
- }
- }
-}
-
pub async fn list_devices_with_retries(
device_client: &DevicesProxy,
) -> Result<Vec<Device>, mullvad_api::rest::Error> {
diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs
index 108da125f6..3c645268c4 100644
--- a/test/test-manager/src/tests/helpers.rs
+++ b/test/test-manager/src/tests/helpers.rs
@@ -1,4 +1,4 @@
-use super::{config::TEST_CONFIG, Error, WAIT_FOR_TUNNEL_STATE_TIMEOUT};
+use super::{config::TEST_CONFIG, Error, TestContext, WAIT_FOR_TUNNEL_STATE_TIMEOUT};
use crate::network_monitor::{
self, start_packet_monitor, MonitorOptions, MonitorUnexpectedlyStopped, PacketMonitor,
};
@@ -22,6 +22,8 @@ use std::{
use talpid_types::net::wireguard::{PeerConfig, PrivateKey, TunnelConfig};
use test_rpc::{package::Package, AmIMullvad, ServiceClient};
+pub const THROTTLE_RETRY_DELAY: Duration = Duration::from_secs(120);
+
#[macro_export]
macro_rules! assert_tunnel_state {
($mullvad_client:expr, $pattern:pat) => {{
@@ -199,19 +201,44 @@ pub async fn ping_sized_with_timeout(
.map_err(Error::Rpc)
}
+/// Log in and retry if it fails due to throttling
+pub async fn login_with_retries(
+ mullvad_client: &mut MullvadProxyClient,
+) -> Result<(), mullvad_management_interface::Error> {
+ loop {
+ match mullvad_client
+ .login_account(TEST_CONFIG.account_number.clone())
+ .await
+ {
+ Err(mullvad_management_interface::Error::Rpc(status))
+ if status.message().to_uppercase().contains("THROTTLED") =>
+ {
+ // Work around throttling errors by sleeping
+ log::debug!(
+ "Login failed due to throttling. Sleeping for {} seconds",
+ THROTTLE_RETRY_DELAY.as_secs()
+ );
+
+ tokio::time::sleep(THROTTLE_RETRY_DELAY).await;
+ }
+ Err(err) => break Err(err),
+ Ok(_) => break Ok(()),
+ }
+ }
+}
+
/// Try to connect to a Mullvad Tunnel.
///
-/// If that fails to begin to connect, [`Error::DaemonError`] is returned. If it fails to connect
-/// after that, the daemon ends up in the [`TunnelState::Error`] state, and
-/// [`Error::UnexpectedErrorState`] is returned.
+/// # Returns
+/// - `Result::Ok` if the daemon successfully connected to a tunnel
+/// - `Result::Err` if:
+/// - The daemon failed to even begin connecting. Then [`Error::Rpc`] is returned.
+/// - The daemon started to connect but ended up in the [`TunnelState::Error`] state.
+/// Then [`Error::UnexpectedErrorState`] is returned
pub async fn connect_and_wait(mullvad_client: &mut MullvadProxyClient) -> Result<(), Error> {
log::info!("Connecting");
- mullvad_client
- .connect_tunnel()
- .await
- .map_err(|error| Error::Daemon(format!("failed to begin connecting: {}", error)))?;
-
+ mullvad_client.connect_tunnel().await?;
let new_state = wait_for_tunnel_state(mullvad_client.clone(), |state| {
matches!(
state,
@@ -231,11 +258,8 @@ pub async fn connect_and_wait(mullvad_client: &mut MullvadProxyClient) -> Result
pub async fn disconnect_and_wait(mullvad_client: &mut MullvadProxyClient) -> Result<(), Error> {
log::info!("Disconnecting");
+ mullvad_client.disconnect_tunnel().await?;
- mullvad_client
- .disconnect_tunnel()
- .await
- .map_err(|error| Error::Daemon(format!("failed to begin disconnecting: {}", error)))?;
wait_for_tunnel_state(mullvad_client.clone(), |state| {
matches!(state, TunnelState::Disconnected { .. })
})
@@ -308,6 +332,30 @@ where
}
}
+/// Set environment variables specified by `env` and restart the Mullvad daemon.
+/// Returns a new [rpc client][`MullvadProxyClient`], since the old client *probably*
+/// can't communicate with the new daemon.
+///
+/// # Note
+/// This is just a thin wrapper around [`ServiceClient::set_daemon_environment`] which also
+/// invalidates the old [`MullvadProxyClient`].
+pub async fn restart_daemon_with<K, V, Env>(
+ rpc: &ServiceClient,
+ test_context: &TestContext,
+ _: MullvadProxyClient, // Just consume the old proxy client
+ env: Env,
+) -> Result<MullvadProxyClient, Error>
+where
+ Env: IntoIterator<Item = (K, V)>,
+ K: Into<String>,
+ V: Into<String>,
+{
+ rpc.set_daemon_environment(env).await?;
+ // Need to create a new `mullvad_client` here after the restart
+ // otherwise we *probably* can't communicate with the daemon.
+ Ok(test_context.rpc_provider.new_client().await)
+}
+
pub async fn geoip_lookup_with_retries(rpc: &ServiceClient) -> Result<AmIMullvad, Error> {
const MAX_ATTEMPTS: usize = 5;
const BEFORE_RETRY_DELAY: Duration = Duration::from_secs(2);
@@ -409,6 +457,11 @@ pub fn unreachable_wireguard_tunnel() -> talpid_types::net::wireguard::Connectio
}
}
+/// Return the current `MULLVAD_API_HOST` et al.
+///
+/// # Note
+/// This is independent of the running daemon's environment.
+/// It is solely dependant on the current value of [`TEST_CONFIG`].
pub fn get_app_env() -> HashMap<String, String> {
use mullvad_api::env;
use std::net::ToSocketAddrs;
@@ -420,10 +473,10 @@ pub fn get_app_env() -> HashMap<String, String> {
.next()
.unwrap();
- let api_host_env = (env::API_HOST_VAR.to_string(), api_host);
- let api_addr_env = (env::API_ADDR_VAR.to_string(), api_addr.to_string());
-
- [api_host_env, api_addr_env].into_iter().collect()
+ HashMap::from_iter(vec![
+ (env::API_HOST_VAR.to_string(), api_host),
+ (env::API_ADDR_VAR.to_string(), api_addr.to_string()),
+ ])
}
/// Return a filtered version of the daemon's relay list.
diff --git a/test/test-manager/src/tests/tunnel.rs b/test/test-manager/src/tests/tunnel.rs
index 3bbb832443..e4802d20c5 100644
--- a/test/test-manager/src/tests/tunnel.rs
+++ b/test/test-manager/src/tests/tunnel.rs
@@ -3,6 +3,7 @@ use super::helpers::{
};
use super::{config::TEST_CONFIG, Error, TestContext};
use crate::network_monitor::{start_packet_monitor, MonitorOptions};
+use crate::tests::helpers::login_with_retries;
use mullvad_management_interface::MullvadProxyClient;
use mullvad_types::relay_constraints::{
@@ -770,3 +771,34 @@ pub async fn test_local_socks_bridge(
Ok(())
}
+
+/// Verify that the app can connect to a VPN server and get working internet when the API is down.
+/// As long as the user has managed to log in to the app, establishing a tunnel should work even if the API is down (This includes actually being down, not just censored).
+///
+/// The test procedure is as follows:
+/// 1. The app is logged in
+/// 2. The app is killed
+/// 3. The API is "removed" (override API IP/host to something bogus)
+/// 4. The app is restarted
+/// 5. Verify that it starts as intended and a tunnel can be established
+#[test_function]
+pub async fn test_establish_tunnel_without_api(
+ ctx: TestContext,
+ rpc: ServiceClient,
+ mut mullvad_client: MullvadProxyClient,
+) -> anyhow::Result<()> {
+ // 1
+ login_with_retries(&mut mullvad_client).await?;
+ // 2
+ rpc.stop_mullvad_daemon().await?;
+ // 3
+ let borked_env = [("MULLVAD_API_ADDR", "1.3.3.7:421")];
+ // 4
+ log::debug!("Restarting the daemon with the following overrides: {borked_env:?}");
+ let mut mullvad_client =
+ helpers::restart_daemon_with(&rpc, &ctx, mullvad_client, borked_env).await?;
+ // 5
+ connect_and_wait(&mut mullvad_client).await?;
+ // Profit
+ Ok(())
+}