use alloc::string::{String, ToString};
use core::time::Duration;
pub const COMMON_NETWORK_ERRORS: &[&str] = &[
"expected value",
"invalid type",
"EOF while parsing",
"network",
"connection",
"timeout",
"Connection refused",
"Connection reset",
"Connection timed out",
"No route to host",
"Network is unreachable",
"ConnectError",
"dns error",
"failed to lookup address",
"Name or service not known",
"nodename nor servname provided",
"HttpError",
"EmptyResponse",
"hyper_util::client::legacy::Error",
"reqwest::Error",
"there is no reactor running",
"must be called from the context of a Tokio",
"not yet implemented",
"unimplemented",
];
pub fn is_known_network_error(error_msg: &str) -> bool {
COMMON_NETWORK_ERRORS
.iter()
.any(|&pattern| error_msg.to_lowercase().contains(&pattern.to_lowercase()))
}
pub struct TestTimeouts;
impl TestTimeouts {
pub const LOCAL: Duration = Duration::from_secs(5);
pub const NETWORK: Duration = Duration::from_secs(30);
pub const FAUCET: Duration = Duration::from_secs(60);
pub const TRANSACTION: Duration = Duration::from_secs(120);
}
#[derive(Debug)]
pub enum TestResult<T> {
Success(T),
Skipped(String),
Failed(String),
}
#[macro_export]
macro_rules! handle_test_result {
($result:expr, $test_name:expr) => {
match $result {
$crate::utils::testing::TestResult::Success(value) => value,
$crate::utils::testing::TestResult::Skipped(reason) => {
#[cfg(feature = "std")]
alloc::println!("⏭️ {} skipped: {}", $test_name, reason);
return;
}
$crate::utils::testing::TestResult::Failed(error) => {
panic!("❌ {} failed: {}", $test_name, error);
}
}
};
}
impl<T> TestResult<T> {
pub fn success(value: T) -> Self {
Self::Success(value)
}
pub fn skipped(reason: impl Into<String>) -> Self {
Self::Skipped(reason.into())
}
pub fn failed(error: impl Into<String>) -> Self {
Self::Failed(error.into())
}
pub fn from_result(result: Result<T, impl ToString>) -> Self {
match result {
Ok(value) => Self::Success(value),
Err(error) => {
let error_msg = error.to_string();
if is_known_network_error(&error_msg) {
Self::Skipped(alloc::format!("Known network error: {}", error_msg))
} else {
Self::Failed(error_msg)
}
}
}
}
pub fn handle(self, test_name: &str) {
match self {
Self::Success(_) => {}
Self::Skipped(reason) => {
#[cfg(feature = "std")]
alloc::println!("⏭️ {} skipped: {}", test_name, reason);
}
Self::Failed(error) => {
panic!("❌ {} failed: {}", test_name, error);
}
}
}
}
#[cfg(feature = "tokio-rt")]
pub async fn test_network_operation<F, T, E>(
operation: F,
timeout: Duration,
operation_name: &str,
) -> TestResult<T>
where
F: core::future::Future<Output = Result<T, E>>,
E: ToString,
{
let result = tokio::time::timeout(timeout, operation).await;
match result {
Ok(Ok(value)) => TestResult::Success(value),
Ok(Err(error)) => TestResult::from_result(Err(error)),
Err(_) => TestResult::Skipped(alloc::format!("{} timed out", operation_name)),
}
}
pub mod test_wallets {
use crate::wallet::{exceptions::XRPLWalletException, Wallet};
pub const TEST_WALLET_SEED: &str = "sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5";
pub const TEST_WALLET_SEQUENCE: u64 = 0;
pub fn create_test_wallet() -> Result<Wallet, XRPLWalletException> {
Wallet::new(TEST_WALLET_SEED, TEST_WALLET_SEQUENCE)
}
pub fn create_test_wallet_unwrap() -> Wallet {
create_test_wallet().expect("Failed to create test wallet")
}
}
pub mod test_constants {
pub const EXAMPLE_COM_HEX: &str = "6578616d706c652e636f6d";
pub const TESTNET_URL: &str = "https://testnet.xrpl-labs.com/";
pub const ALT_TESTNET_URL: &str = "https://faucet.altnet.rippletest.net:443";
}
pub mod assertions {
use crate::models::transactions::Transaction;
use core::fmt::Debug;
use strum::IntoEnumIterator;
pub fn assert_transaction_signed<'a, T, U>(tx: &T)
where
T: Transaction<'a, U>,
U: Clone + Debug + PartialEq + serde::Serialize + IntoEnumIterator,
{
let common_fields = tx.get_common_fields();
assert!(
common_fields.txn_signature.is_some(),
"Transaction should have a signature"
);
assert!(
common_fields.signing_pub_key.is_some(),
"Transaction should have a signing public key"
);
}
pub fn assert_transaction_multisigned<'a, T, U>(tx: &T)
where
T: Transaction<'a, U>,
U: Clone + Debug + PartialEq + serde::Serialize + IntoEnumIterator,
{
let common_fields = tx.get_common_fields();
assert!(
common_fields.signers.is_some(),
"Multisigned transaction should have signers"
);
assert!(
common_fields.txn_signature.is_none(),
"Multisigned transaction should not have txn_signature"
);
}
pub fn assert_transaction_autofilled<'a, T, U>(tx: &T)
where
T: Transaction<'a, U>,
U: Clone + Debug + PartialEq + serde::Serialize + IntoEnumIterator,
{
let common_fields = tx.get_common_fields();
assert!(
common_fields.sequence.is_some(),
"Autofilled transaction should have sequence"
);
assert!(
common_fields.fee.is_some(),
"Autofilled transaction should have fee"
);
}
pub fn assert_valid_wallet(wallet: &crate::wallet::Wallet) {
assert!(
!wallet.classic_address.is_empty(),
"Wallet should have an address"
);
assert!(
!wallet.public_key.is_empty(),
"Wallet should have a public key"
);
assert!(
!wallet.private_key.is_empty(),
"Wallet should have a private key"
);
assert!(!wallet.seed.is_empty(), "Wallet should have a seed");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_detection() {
assert!(is_known_network_error("dns error occurred"));
assert!(is_known_network_error(
"failed to lookup address information"
));
assert!(is_known_network_error("Connection refused"));
assert!(is_known_network_error("Network is unreachable"));
assert!(is_known_network_error("expected value"));
assert!(is_known_network_error("ConnectError"));
assert!(is_known_network_error("not yet implemented"));
assert!(is_known_network_error("DNS ERROR OCCURRED"));
assert!(is_known_network_error("CONNECTION REFUSED"));
assert!(!is_known_network_error("some other error"));
assert!(!is_known_network_error("validation failed"));
assert!(!is_known_network_error("invalid transaction"));
}
#[test]
fn test_result_handling() {
let result = TestResult::success("test_value");
match result {
TestResult::Success(value) => assert_eq!(value, "test_value"),
_ => panic!("Expected success"),
}
let network_error: Result<(), &str> = Err("dns error occurred");
let result = TestResult::from_result(network_error);
match result {
TestResult::Skipped(_) => {} _ => panic!("Expected skip for network error"),
}
let other_error: Result<(), &str> = Err("validation failed");
let result = TestResult::from_result(other_error);
match result {
TestResult::Failed(_) => {} _ => panic!("Expected failure for non-network error"),
}
}
#[test]
fn test_wallet_creation() {
let wallet = test_wallets::create_test_wallet().unwrap();
assertions::assert_valid_wallet(&wallet);
}
}