diff --git a/payjoin-cli/Cargo.toml b/payjoin-cli/Cargo.toml index 672481b3..77948190 100644 --- a/payjoin-cli/Cargo.toml +++ b/payjoin-cli/Cargo.toml @@ -22,6 +22,7 @@ path = "src/main.rs" default = ["v1"] native-certs = ["reqwest/rustls-tls-native-roots"] _danger-local-https = ["rcgen", "reqwest/rustls-tls", "rustls", "hyper-rustls", "payjoin/_danger-local-https", "tokio-rustls"] +_test_utils = ["payjoin/_test_utils"] v1 = ["hyper", "hyper-util", "http-body-util"] v2 = ["payjoin/v2", "payjoin/io"] diff --git a/payjoin-cli/contrib/test.sh b/payjoin-cli/contrib/test.sh index 657a464d..d09fb2be 100755 --- a/payjoin-cli/contrib/test.sh +++ b/payjoin-cli/contrib/test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash set -e -cargo test --locked --package payjoin-cli --verbose --no-default-features --features=_danger-local-https,v2 --test e2e -cargo test --locked --package payjoin-cli --verbose --features=_danger-local-https +cargo test --locked --package payjoin-cli --verbose --no-default-features --features=_danger-local-https,v2,_test_utils --test e2e +cargo test --locked --package payjoin-cli --verbose --features=_danger-local-https,_test_utils diff --git a/payjoin-cli/tests/e2e.rs b/payjoin-cli/tests/e2e.rs index bad79fb6..cc99341b 100644 --- a/payjoin-cli/tests/e2e.rs +++ b/payjoin-cli/tests/e2e.rs @@ -3,10 +3,7 @@ mod e2e { use std::env; use std::process::Stdio; - use bitcoincore_rpc::json::AddressType; - use bitcoind::bitcoincore_rpc::RpcApi; - use log::{log_enabled, Level}; - use payjoin::bitcoin::Amount; + use payjoin::test_utils::init_bitcoind_sender_receiver; use tokio::fs; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::process::Command; @@ -16,34 +13,7 @@ mod e2e { #[cfg(not(feature = "v2"))] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn send_receive_payjoin() { - let bitcoind_exe = env::var("BITCOIND_EXE") - .ok() - .or_else(|| bitcoind::downloaded_exe_path().ok()) - .expect("version feature or env BITCOIND_EXE is required for tests"); - let mut conf = bitcoind::Conf::default(); - conf.view_stdout = log_enabled!(Level::Debug); - let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &conf).unwrap(); - let receiver = bitcoind.create_wallet("receiver").unwrap(); - let receiver_address = - receiver.get_new_address(None, Some(AddressType::Bech32)).unwrap().assume_checked(); - let sender = bitcoind.create_wallet("sender").unwrap(); - let sender_address = - sender.get_new_address(None, Some(AddressType::Bech32)).unwrap().assume_checked(); - bitcoind.client.generate_to_address(1, &receiver_address).unwrap(); - bitcoind.client.generate_to_address(101, &sender_address).unwrap(); - - assert_eq!( - Amount::from_btc(50.0).unwrap(), - receiver.get_balances().unwrap().mine.trusted, - "receiver doesn't own bitcoin" - ); - - assert_eq!( - Amount::from_btc(50.0).unwrap(), - sender.get_balances().unwrap().mine.trusted, - "sender doesn't own bitcoin" - ); - + let (bitcoind, _sender, _receiver) = init_bitcoind_sender_receiver(None, None).unwrap(); let temp_dir = env::temp_dir(); let receiver_db_path = temp_dir.join("receiver_db"); let sender_db_path = temp_dir.join("sender_db"); @@ -164,23 +134,17 @@ mod e2e { use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; - use std::time::Duration; - use http::StatusCode; - use once_cell::sync::{Lazy, OnceCell}; - use reqwest::{Client, ClientBuilder}; + use payjoin::test_utils::{ + http_agent, init_directory, init_tracing, local_cert_key, wait_for_service_ready, + BoxError, + }; use testcontainers::clients::Cli; use testcontainers_modules::redis::Redis; use tokio::process::Child; use url::Url; - type Error = Box; - type BoxSendSyncError = Box; - type Result = std::result::Result; - - static INIT_TRACING: OnceCell<()> = OnceCell::new(); - static TESTS_TIMEOUT: Lazy = Lazy::new(|| Duration::from_secs(20)); - static WAIT_SERVICE_INTERVAL: Lazy = Lazy::new(|| Duration::from_secs(3)); + type Result = std::result::Result; init_tracing(); let (cert, key) = local_cert_key(); @@ -218,33 +182,7 @@ mod e2e { receiver_db_path: PathBuf, sender_db_path: PathBuf, ) -> Result<()> { - let bitcoind_exe = env::var("BITCOIND_EXE") - .ok() - .or_else(|| bitcoind::downloaded_exe_path().ok()) - .expect("version feature or env BITCOIND_EXE is required for tests"); - let mut conf = bitcoind::Conf::default(); - conf.view_stdout = log_enabled!(Level::Debug); - let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &conf)?; - let receiver = bitcoind.create_wallet("receiver")?; - let receiver_address = - receiver.get_new_address(None, Some(AddressType::Bech32))?.assume_checked(); - let sender = bitcoind.create_wallet("sender")?; - let sender_address = - sender.get_new_address(None, Some(AddressType::Bech32))?.assume_checked(); - bitcoind.client.generate_to_address(1, &receiver_address)?; - bitcoind.client.generate_to_address(101, &sender_address)?; - - assert_eq!( - Amount::from_btc(50.0)?, - receiver.get_balances()?.mine.trusted, - "receiver doesn't own bitcoin" - ); - - assert_eq!( - Amount::from_btc(50.0)?, - sender.get_balances()?.mine.trusted, - "sender doesn't own bitcoin" - ); + let (bitcoind, _sender, _receiver) = init_bitcoind_sender_receiver(None, None)?; let temp_dir = env::temp_dir(); let cert_path = temp_dir.join("localhost.der"); tokio::fs::write(&cert_path, cert.clone()).await?; @@ -471,75 +409,6 @@ mod e2e { assert!(payjoin_sent.unwrap_or(false), "Payjoin send was not detected"); Ok(()) } - - async fn wait_for_service_ready(service_url: Url, agent: Arc) -> Result<()> { - let health_url = service_url.join("/health").map_err(|_| "Invalid URL")?; - let start = std::time::Instant::now(); - - while start.elapsed() < *TESTS_TIMEOUT { - let request_result = - agent.get(health_url.as_str()).send().await.map_err(|_| "Bad request")?; - - match request_result.status() { - StatusCode::OK => { - println!("READY {}", service_url); - return Ok(()); - } - StatusCode::NOT_FOUND => return Err("Endpoint not found".into()), - _ => std::thread::sleep(*WAIT_SERVICE_INTERVAL), - } - } - - Err("Timeout waiting for service to be ready".into()) - } - - async fn init_directory( - db_host: String, - local_cert_key: (Vec, Vec), - ) -> std::result::Result< - (u16, tokio::task::JoinHandle>), - BoxSendSyncError, - > { - println!("Database running on {}", db_host); - let timeout = Duration::from_secs(2); - payjoin_directory::listen_tcp_with_tls_on_free_port(db_host, timeout, local_cert_key) - .await - } - - // generates or gets a DER encoded localhost cert and key. - fn local_cert_key() -> (Vec, Vec) { - let cert = rcgen::generate_simple_self_signed(vec![ - "0.0.0.0".to_string(), - "localhost".to_string(), - ]) - .expect("Failed to generate cert"); - let cert_der = cert.serialize_der().expect("Failed to serialize cert"); - let key_der = cert.serialize_private_key_der(); - (cert_der, key_der) - } - - fn http_agent(cert_der: Vec) -> Result { - Ok(http_agent_builder(cert_der)?.build()?) - } - - fn http_agent_builder(cert_der: Vec) -> Result { - Ok(ClientBuilder::new() - .danger_accept_invalid_certs(true) - .use_rustls_tls() - .add_root_certificate(reqwest::tls::Certificate::from_der(cert_der.as_slice())?)) - } - - fn init_tracing() { - INIT_TRACING.get_or_init(|| { - let subscriber = tracing_subscriber::FmtSubscriber::builder() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .with_test_writer() - .finish(); - - tracing::subscriber::set_global_default(subscriber) - .expect("failed to set global default subscriber"); - }); - } } async fn cleanup_temp_file(path: &std::path::Path) { diff --git a/payjoin/tests/integration.rs b/payjoin/tests/integration.rs index b1a0ae17..ddc40551 100644 --- a/payjoin/tests/integration.rs +++ b/payjoin/tests/integration.rs @@ -1,7 +1,6 @@ #[cfg(all(feature = "send", feature = "receive"))] mod integration { use std::collections::HashMap; - use std::env; use std::str::FromStr; use bitcoin::policy::DEFAULT_MIN_RELAY_TX_FEE; @@ -10,17 +9,13 @@ mod integration { use bitcoin::{Amount, FeeRate, OutPoint, TxIn, TxOut, Weight}; use bitcoind::bitcoincore_rpc::json::{AddressType, WalletProcessPsbtResult}; use bitcoind::bitcoincore_rpc::{self, RpcApi}; - use log::{log_enabled, Level}; - use once_cell::sync::{Lazy, OnceCell}; + use once_cell::sync::Lazy; use payjoin::receive::v1::build_v1_pj_uri; use payjoin::receive::InputPair; + use payjoin::test_utils::{init_bitcoind_sender_receiver, init_tracing, BoxError}; use payjoin::{PjUri, Request, Uri}; - use tracing_subscriber::{EnvFilter, FmtSubscriber}; use url::Url; - type BoxError = Box; - - static INIT_TRACING: OnceCell<()> = OnceCell::new(); static EXAMPLE_URL: Lazy = Lazy::new(|| Url::parse("https://example.com").expect("Invalid Url")); @@ -178,18 +173,16 @@ mod integration { use http::StatusCode; use payjoin::receive::v2::{PayjoinProposal, Receiver, UncheckedProposal}; use payjoin::send::v2::SenderBuilder; + use payjoin::test_utils::{ + http_agent, init_directory, local_cert_key, wait_for_service_ready, + }; use payjoin::{OhttpKeys, PjUri, UriExt}; - use reqwest::{Client, ClientBuilder, Error, Response}; + use reqwest::{Client, Error, Response}; use testcontainers_modules::redis::Redis; use testcontainers_modules::testcontainers::clients::Cli; use super::*; - type BoxSendSyncError = Box; - - static TESTS_TIMEOUT: Lazy = Lazy::new(|| Duration::from_secs(20)); - static WAIT_SERVICE_INTERVAL: Lazy = Lazy::new(|| Duration::from_secs(3)); - #[tokio::test] async fn test_bad_ohttp_keys() { let bad_ohttp_keys = @@ -811,29 +804,6 @@ mod integration { } } - async fn init_directory( - db_host: String, - local_cert_key: (Vec, Vec), - ) -> Result<(u16, tokio::task::JoinHandle>), BoxSendSyncError> - { - println!("Database running on {}", db_host); - let timeout = Duration::from_secs(2); - payjoin_directory::listen_tcp_with_tls_on_free_port(db_host, timeout, local_cert_key) - .await - } - - // generates or gets a DER encoded localhost cert and key. - fn local_cert_key() -> (Vec, Vec) { - let cert = rcgen::generate_simple_self_signed(vec![ - "0.0.0.0".to_string(), - "localhost".to_string(), - ]) - .expect("Failed to generate cert"); - let cert_der = cert.serialize_der().expect("Failed to serialize cert"); - let key_der = cert.serialize_private_key_der(); - (cert_der, key_der) - } - fn handle_directory_proposal( receiver: &bitcoincore_rpc::Client, proposal: UncheckedProposal, @@ -916,39 +886,6 @@ mod integration { .unwrap() } - fn http_agent(cert_der: Vec) -> Result { - Ok(http_agent_builder(cert_der)?.build()?) - } - - fn http_agent_builder(cert_der: Vec) -> Result { - Ok(ClientBuilder::new() - .danger_accept_invalid_certs(true) - .use_rustls_tls() - .add_root_certificate( - reqwest::tls::Certificate::from_der(cert_der.as_slice()).unwrap(), - )) - } - - async fn wait_for_service_ready( - service_url: Url, - agent: Arc, - ) -> Result<(), &'static str> { - let health_url = service_url.join("/health").map_err(|_| "Invalid URL")?; - let start = std::time::Instant::now(); - - while start.elapsed() < *TESTS_TIMEOUT { - let request_result = - agent.get(health_url.as_str()).send().await.map_err(|_| "Bad request")?; - match request_result.status() { - StatusCode::OK => return Ok(()), - StatusCode::NOT_FOUND => return Err("Endpoint not found"), - _ => std::thread::sleep(*WAIT_SERVICE_INTERVAL), - } - } - - Err("Timeout waiting for service to be ready") - } - fn build_sweep_psbt( sender: &bitcoincore_rpc::Client, pj_uri: &PjUri, @@ -1156,50 +1093,6 @@ mod integration { } } - fn init_tracing() { - INIT_TRACING.get_or_init(|| { - let subscriber = FmtSubscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .with_test_writer() - .finish(); - - tracing::subscriber::set_global_default(subscriber) - .expect("failed to set global default subscriber"); - }); - } - - fn init_bitcoind_sender_receiver( - sender_address_type: Option, - receiver_address_type: Option, - ) -> Result<(bitcoind::BitcoinD, bitcoincore_rpc::Client, bitcoincore_rpc::Client), BoxError> - { - let bitcoind_exe = - env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).unwrap(); - let mut conf = bitcoind::Conf::default(); - conf.view_stdout = log_enabled!(Level::Debug); - let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &conf)?; - let receiver = bitcoind.create_wallet("receiver")?; - let receiver_address = - receiver.get_new_address(None, receiver_address_type)?.assume_checked(); - let sender = bitcoind.create_wallet("sender")?; - let sender_address = sender.get_new_address(None, sender_address_type)?.assume_checked(); - bitcoind.client.generate_to_address(1, &receiver_address)?; - bitcoind.client.generate_to_address(101, &sender_address)?; - - assert_eq!( - Amount::from_btc(50.0)?, - receiver.get_balances()?.mine.trusted, - "receiver doesn't own bitcoin" - ); - - assert_eq!( - Amount::from_btc(50.0)?, - sender.get_balances()?.mine.trusted, - "sender doesn't own bitcoin" - ); - Ok((bitcoind, sender, receiver)) - } - fn build_original_psbt( sender: &bitcoincore_rpc::Client, pj_uri: &PjUri,