Skip to content

Commit

Permalink
Consume test_utils in integration/e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
spacebear21 committed Jan 14, 2025
1 parent d9c3041 commit 1f4e737
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 254 deletions.
1 change: 1 addition & 0 deletions payjoin-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
4 changes: 2 additions & 2 deletions payjoin-cli/contrib/test.sh
Original file line number Diff line number Diff line change
@@ -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
147 changes: 8 additions & 139 deletions payjoin-cli/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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");
Expand Down Expand Up @@ -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<dyn std::error::Error + 'static>;
type BoxSendSyncError = Box<dyn std::error::Error + Send + Sync>;
type Result<T> = std::result::Result<T, Error>;

static INIT_TRACING: OnceCell<()> = OnceCell::new();
static TESTS_TIMEOUT: Lazy<Duration> = Lazy::new(|| Duration::from_secs(20));
static WAIT_SERVICE_INTERVAL: Lazy<Duration> = Lazy::new(|| Duration::from_secs(3));
type Result<T> = std::result::Result<T, BoxError>;

init_tracing();
let (cert, key) = local_cert_key();
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -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<Client>) -> 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<u8>, Vec<u8>),
) -> std::result::Result<
(u16, tokio::task::JoinHandle<std::result::Result<(), BoxSendSyncError>>),
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<u8>, Vec<u8>) {
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<u8>) -> Result<Client> {
Ok(http_agent_builder(cert_der)?.build()?)
}

fn http_agent_builder(cert_der: Vec<u8>) -> Result<ClientBuilder> {
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) {
Expand Down
119 changes: 6 additions & 113 deletions payjoin/tests/integration.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<dyn std::error::Error + 'static>;

static INIT_TRACING: OnceCell<()> = OnceCell::new();
static EXAMPLE_URL: Lazy<Url> =
Lazy::new(|| Url::parse("https://example.com").expect("Invalid Url"));

Expand Down Expand Up @@ -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<dyn std::error::Error + Send + Sync>;

static TESTS_TIMEOUT: Lazy<Duration> = Lazy::new(|| Duration::from_secs(20));
static WAIT_SERVICE_INTERVAL: Lazy<Duration> = Lazy::new(|| Duration::from_secs(3));

#[tokio::test]
async fn test_bad_ohttp_keys() {
let bad_ohttp_keys =
Expand Down Expand Up @@ -811,29 +804,6 @@ mod integration {
}
}

async fn init_directory(
db_host: String,
local_cert_key: (Vec<u8>, Vec<u8>),
) -> Result<(u16, tokio::task::JoinHandle<Result<(), BoxSendSyncError>>), 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<u8>, Vec<u8>) {
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,
Expand Down Expand Up @@ -916,39 +886,6 @@ mod integration {
.unwrap()
}

fn http_agent(cert_der: Vec<u8>) -> Result<Client, BoxError> {
Ok(http_agent_builder(cert_der)?.build()?)
}

fn http_agent_builder(cert_der: Vec<u8>) -> Result<ClientBuilder, BoxError> {
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<Client>,
) -> 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,
Expand Down Expand Up @@ -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<AddressType>,
receiver_address_type: Option<AddressType>,
) -> 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,
Expand Down

0 comments on commit 1f4e737

Please sign in to comment.