From c72fef169c71dcb15805cc27de3276ae7aa9f028 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Mon, 20 Oct 2025 21:27:04 +0200 Subject: [PATCH] Add more tests for retry logic --- .../client-core/src/client/base_client/mod.rs | 50 ++++++ common/http-api-client/src/tests.rs | 147 ++++++++++++------ 2 files changed, 151 insertions(+), 46 deletions(-) diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index fcdda91538..643d569331 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -1185,3 +1185,53 @@ pub struct BaseClient { pub forget_me: ForgetMe, pub remember_me: RememberMe, } + +#[cfg(test)] +mod tests { + use super::*; + use nym_network_defaults::{ApiUrl, NymNetworkDetails}; + + #[test] + fn test_network_details_with_multiple_urls() { + // Verify that network details can be configured with multiple API URLs + let mut network_details = NymNetworkDetails::new_empty(); + network_details.nym_api_urls = Some(vec![ + ApiUrl { + url: "https://validator.nymtech.net/api/".to_string(), + front_hosts: None, + }, + ApiUrl { + url: "https://nym-frontdoor.vercel.app/api/".to_string(), + front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]), + }, + ]); + + assert_eq!(network_details.nym_api_urls.as_ref().unwrap().len(), 2); + assert!(network_details.nym_api_urls.as_ref().unwrap()[1] + .front_hosts + .is_some()); + } + + #[test] + fn test_network_details_with_front_hosts() { + // Verify that ApiUrl can store domain fronting configuration + let api_url = ApiUrl { + url: "https://nym-frontdoor.vercel.app/api/".to_string(), + front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]), + }; + + assert_eq!(api_url.url, "https://nym-frontdoor.vercel.app/api/"); + assert_eq!(api_url.front_hosts.as_ref().unwrap().len(), 2); + assert!(api_url + .front_hosts + .as_ref() + .unwrap() + .contains(&"vercel.app".to_string())); + } + + #[test] + fn test_default_nym_api_retries_constant() { + // Verify the retry constant is set correctly + assert_eq!(DEFAULT_NYM_API_RETRIES, 3); + } +} diff --git a/common/http-api-client/src/tests.rs b/common/http-api-client/src/tests.rs index 98f6fec79f..2e38f6caf6 100644 --- a/common/http-api-client/src/tests.rs +++ b/common/http-api-client/src/tests.rs @@ -2,77 +2,77 @@ use super::*; #[test] fn sanitizing_urls() { - let base_url: Url = "http://foomp.com".parse().unwrap(); + let base_url: Url = "http://api.test".parse().unwrap(); // works with a full string assert_eq!( - "http://foomp.com/foo/bar", + "http://api.test/foo/bar", sanitize_url(&base_url, "/foo//bar/", NO_PARAMS).as_str() ); // (and leading slash doesn't matter) assert_eq!( - "http://foomp.com/foo/bar", + "http://api.test/foo/bar", sanitize_url(&base_url, "foo//bar/", NO_PARAMS).as_str() ); // works with 1 segment assert_eq!( - "http://foomp.com/foo", + "http://api.test/foo", sanitize_url(&base_url, &["foo"], NO_PARAMS).as_str() ); // works with 2 segments assert_eq!( - "http://foomp.com/foo/bar", + "http://api.test/foo/bar", sanitize_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str() ); // works with leading slash assert_eq!( - "http://foomp.com/foo", + "http://api.test/foo", sanitize_url(&base_url, &["/foo"], NO_PARAMS).as_str() ); assert_eq!( - "http://foomp.com/foo/bar", + "http://api.test/foo/bar", sanitize_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str() ); assert_eq!( - "http://foomp.com/foo/bar", + "http://api.test/foo/bar", sanitize_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str() ); // works with trailing slash assert_eq!( - "http://foomp.com/foo", + "http://api.test/foo", sanitize_url(&base_url, &["foo/"], NO_PARAMS).as_str() ); assert_eq!( - "http://foomp.com/foo/bar", + "http://api.test/foo/bar", sanitize_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str() ); assert_eq!( - "http://foomp.com/foo/bar", + "http://api.test/foo/bar", sanitize_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str() ); // works with both leading and trailing slash assert_eq!( - "http://foomp.com/foo", + "http://api.test/foo", sanitize_url(&base_url, &["/foo/"], NO_PARAMS).as_str() ); assert_eq!( - "http://foomp.com/foo/bar", + "http://api.test/foo/bar", sanitize_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str() ); // adds params assert_eq!( - "http://foomp.com/foo/bar?foomp=baz", + "http://api.test/foo/bar?foomp=baz", sanitize_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str() ); assert_eq!( - "http://foomp.com/foo/bar?arg1=val1&arg2=val2", + "http://api.test/foo/bar?arg1=val1&arg2=val2", sanitize_url( &base_url, &["/foo/", "/bar/"], @@ -91,8 +91,8 @@ fn sanitizing_urls() { #[tokio::test] async fn api_client_retry() -> Result<(), Box> { let client = ClientBuilder::new_with_urls(vec![ - "http://broken.nym.badurl".parse()?, - "http://example.com/".parse()?, + "http://broken.nym.test".parse()?, // This will fail + "http://httpbin.org/".parse()?, // This will succeed ]) .with_retries(3) .build()?; @@ -102,72 +102,72 @@ async fn api_client_retry() -> Result<(), Box> { assert_eq!(resp.status(), 200); - // check that the url was updated - assert_eq!(client.current_url().as_str(), "http://example.com/"); + // check that the url was updated to the working one + assert_eq!(client.current_url().as_str(), "http://httpbin.org/"); Ok(()) } #[test] fn host_updating() { - let url = Url::new("http://example.com", None).unwrap(); + let url = Url::new("http://nym-api1.test", None).unwrap(); let mut client = ClientBuilder::new(url).unwrap().build().unwrap(); // check that the url is set correctly let current_url = client.current_url(); - assert_eq!(current_url.as_str(), "http://example.com/"); + assert_eq!(current_url.as_str(), "http://nym-api1.test/"); assert_eq!(current_url.front_str(), None); // update the url client.update_host(); // check that the url is still the same since there is one URL - assert_eq!(client.current_url().as_str(), "http://example.com/"); + assert_eq!(client.current_url().as_str(), "http://nym-api1.test/"); // ======================================= // we rotate through urls when available let new_urls = vec![ - Url::new("http://example.com", None).unwrap(), - Url::new("http://example.org", None).unwrap(), + Url::new("http://nym-api1.test", None).unwrap(), + Url::new("http://nym-api2.test", None).unwrap(), ]; client.change_base_urls(new_urls); - assert_eq!(client.current_url().as_str(), "http://example.com/"); + assert_eq!(client.current_url().as_str(), "http://nym-api1.test/"); client.update_host(); // check that the url got updated now that there are multiple URLs - assert_eq!(client.current_url().as_str(), "http://example.org/"); + assert_eq!(client.current_url().as_str(), "http://nym-api2.test/"); assert_eq!(client.current_url().front_str(), None); client.update_host(); - assert_eq!(client.current_url().as_str(), "http://example.com/"); + assert_eq!(client.current_url().as_str(), "http://nym-api1.test/"); // ======================================= // we rotate through urls when available if fronting is disabled let new_urls = vec![ Url::new( - "http://example.com", - Some(vec!["http://front1.com", "http://front2.com"]), + "http://nym-api1.test", + Some(vec!["http://cdn1.test", "http://cdn2.test"]), ) .unwrap(), - Url::new("http://example.org", None).unwrap(), + Url::new("http://nym-api2.test", None).unwrap(), ]; client.change_base_urls(new_urls); - assert_eq!(client.current_url().as_str(), "http://example.com/"); + assert_eq!(client.current_url().as_str(), "http://nym-api1.test/"); client.update_host(); // check that the url got updated now that there are multiple URLs - assert_eq!(client.current_url().as_str(), "http://example.org/"); + assert_eq!(client.current_url().as_str(), "http://nym-api2.test/"); } #[test] #[cfg(feature = "tunneling")] fn fronted_host_updating() { - let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap(); + let url = Url::new("http://nym-api.test", Some(vec!["http://cdn1.test"])).unwrap(); let mut client = ClientBuilder::new(url) .unwrap() .with_fronting(crate::fronted::FrontPolicy::Always) @@ -176,46 +176,101 @@ fn fronted_host_updating() { // check that the url is set correctly let current_url = client.current_url(); - assert_eq!(current_url.as_str(), "http://example.com/"); - assert_eq!(current_url.front_str(), Some("front1.com")); + assert_eq!(current_url.as_str(), "http://nym-api.test/"); + assert_eq!(current_url.front_str(), Some("cdn1.test")); // update the url client.update_host(); // check that the url is still the same since there is one URL and one front let current_url = client.current_url(); - assert_eq!(current_url.as_str(), "http://example.com/"); - assert_eq!(current_url.front_str(), Some("front1.com")); + assert_eq!(current_url.as_str(), "http://nym-api.test/"); + assert_eq!(current_url.front_str(), Some("cdn1.test")); // ======================================= // we rotate through front urls when available if fronting is enabled let new_urls = vec![ Url::new( - "http://example.com", - Some(vec!["http://front1.com", "http://front2.com"]), + "http://nym-api.test", + Some(vec!["http://cdn1.test", "http://cdn2.test"]), ) .unwrap(), - Url::new("http://example.org", None).unwrap(), + Url::new("http://nym-api2.test", None).unwrap(), ]; client.change_base_urls(new_urls); let current_url = client.current_url(); - assert_eq!(current_url.as_str(), "http://example.com/"); - assert_eq!(current_url.front_str(), Some("front1.com")); + assert_eq!(current_url.as_str(), "http://nym-api.test/"); + assert_eq!(current_url.front_str(), Some("cdn1.test")); // update the url - this should keep the same host but change the front client.update_host(); let current_url = client.current_url(); // check that the url is still the same since there is one URL - assert_eq!(current_url.as_str(), "http://example.com/"); - assert_eq!(current_url.front_str(), Some("front2.com")); + assert_eq!(current_url.as_str(), "http://nym-api.test/"); + assert_eq!(current_url.front_str(), Some("cdn2.test")); // update the url - this should wrap around to the first front as the second url is not fronted client.update_host(); let current_url = client.current_url(); - assert_eq!(current_url.as_str(), "http://example.com/"); - assert_eq!(current_url.front_str(), Some("front1.com")); + assert_eq!(current_url.as_str(), "http://nym-api.test/"); + assert_eq!(current_url.front_str(), Some("cdn1.test")); +} + +#[test] +#[cfg(feature = "network-defaults")] +fn from_network_configures_multiple_urls_and_retries() { + use nym_network_defaults::{ApiUrl, NymNetworkDetails}; + + // Create network details with multiple URLs and fronting + let mut network_details = NymNetworkDetails::new_empty(); + network_details.nym_api_urls = Some(vec![ + ApiUrl { + url: "https://validator.nymtech.net/api/".to_string(), + front_hosts: None, + }, + ApiUrl { + url: "https://nym-frontdoor.vercel.app/api/".to_string(), + front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]), + }, + ApiUrl { + url: "https://nym-frontdoor.global.ssl.fastly.net/api/".to_string(), + front_hosts: Some(vec!["yelp.global.ssl.fastly.net".to_string()]), + }, + ]); + + // Build client from network details + let client = ClientBuilder::from_network(&network_details) + .expect("Failed to create client from network") + .build() + .expect("Failed to build client"); + + // Verify all URLs were configured + assert_eq!( + client.base_urls().len(), + 3, + "Expected 3 URLs to be configured from network details" + ); + + // Verify the URLs have fronting configured where appropriate + assert_eq!( + client.base_urls()[0].as_str(), + "https://validator.nymtech.net/api/" + ); + assert!(client.base_urls()[0].front_str().is_none()); + + assert_eq!( + client.base_urls()[1].as_str(), + "https://nym-frontdoor.vercel.app/api/" + ); + assert!(client.base_urls()[1].front_str().is_some()); + + assert_eq!( + client.base_urls()[2].as_str(), + "https://nym-frontdoor.global.ssl.fastly.net/api/" + ); + assert!(client.base_urls()[2].front_str().is_some()); }