diff --git a/swap/src/api.rs b/swap/src/api.rs index 0f5805cd..9ad8cea5 100644 --- a/swap/src/api.rs +++ b/swap/src/api.rs @@ -123,7 +123,7 @@ impl SwapLock { // workaround for warning over monero_rpc_process which we must own but not read #[allow(dead_code)] pub struct Context { - pub db: Arc, + db: Arc, bitcoin_wallet: Option>, monero_wallet: Option>, monero_rpc_process: Option, @@ -205,6 +205,25 @@ impl Context { Ok(context) } + + pub async fn for_harness( + seed: Seed, + env_config: EnvConfig, + db_path: PathBuf, + bob_bitcoin_wallet: Arc, + bob_monero_wallet: Arc, + ) -> Self { + let config = Config::for_harness(seed, env_config); + + Self { + bitcoin_wallet: Some(bob_bitcoin_wallet), + monero_wallet: Some(bob_monero_wallet), + config, + db: open_db(db_path).await.unwrap(), + monero_rpc_process: None, + swap_lock: Arc::new(SwapLock::new()), + } + } } impl fmt::Debug for Context { @@ -288,6 +307,25 @@ fn env_config_from(testnet: bool) -> EnvConfig { Mainnet::get_config() } } + +impl Config { + pub fn for_harness(seed: Seed, env_config: EnvConfig) -> Self { + let data_dir = data::data_dir_from(None, false).unwrap(); + + Self { + tor_socks5_port: None, + namespace: XmrBtcNamespace::from_is_testnet(false), + server_address: None, + env_config, + seed: Some(seed), + debug: false, + json: false, + is_testnet: false, + data_dir, + } + } +} + #[cfg(test)] pub mod api_test { use super::*; @@ -350,38 +388,30 @@ pub mod api_test { } }; - Request::new( - Method::BuyXmr { - seller, - bitcoin_change_address, - monero_receive_address, - swap_id: Uuid::new_v4(), - }, - ) + Request::new(Method::BuyXmr { + seller, + bitcoin_change_address, + monero_receive_address, + swap_id: Uuid::new_v4(), + }) } pub fn resume() -> Request { - Request::new( - Method::Resume { - swap_id: Uuid::from_str(SWAP_ID).unwrap(), - }, - ) + Request::new(Method::Resume { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + }) } pub fn cancel() -> Request { - Request::new( - Method::CancelAndRefund { - swap_id: Uuid::from_str(SWAP_ID).unwrap(), - }, - ) + Request::new(Method::CancelAndRefund { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + }) } pub fn refund() -> Request { - Request::new( - Method::CancelAndRefund { - swap_id: Uuid::from_str(SWAP_ID).unwrap(), - }, - ) + Request::new(Method::CancelAndRefund { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + }) } } } diff --git a/swap/src/api/request.rs b/swap/src/api/request.rs index 345b7c2f..9344d2a4 100644 --- a/swap/src/api/request.rs +++ b/swap/src/api/request.rs @@ -193,6 +193,9 @@ impl Request { .as_ref() .context("Could not get Bitcoin wallet")?; + let state = context.db.get_state(swap_id).await?; + let is_completed = state.swap_finished(); + let peerId = context .db .get_peer_id(swap_id) @@ -205,9 +208,6 @@ impl Request { .await .with_context(|| "Could not get addressess")?; - let state = context.db.get_state(swap_id).await?; - let is_completed = state.swap_finished(); - let start_date = context.db.get_swap_start_date(swap_id).await?; let swap_state: BobState = state.try_into()?; @@ -346,7 +346,7 @@ impl Request { let (event_loop, mut event_loop_handle) = EventLoop::new(swap_id, swarm, seller_peer_id)?; - let event_loop = tokio::spawn(event_loop.run().instrument(Span::current())); + let event_loop = tokio::spawn(event_loop.run().instrument(Span::none())); let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size()); let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount); @@ -429,7 +429,7 @@ impl Request { .release_swap_lock() .await .expect("Could not release swap lock"); - }.instrument(Span::current())); + }.instrument(Span::none())); Ok(json!({ "swapId": swap_id.to_string(), @@ -483,7 +483,7 @@ impl Request { let (event_loop, event_loop_handle) = EventLoop::new(swap_id, swarm, seller_peer_id)?; - let handle = tokio::spawn(event_loop.run().instrument(Span::current())); + let handle = tokio::spawn(event_loop.run().instrument(Span::none())); let monero_receive_address = context.db.get_monero_address(swap_id).await?; let swap = Swap::from_db( @@ -529,7 +529,7 @@ impl Request { .release_swap_lock() .await .expect("Could not release swap lock"); - }.instrument(Span::current())); + }.instrument(Span::none())); Ok(json!({ "result": "ok", })) @@ -574,7 +574,7 @@ impl Request { Method::GetRawStates => { let raw_history = context.db.raw_all().await?; - Ok(json!({ "raw_history": raw_history })) + Ok(json!({ "raw_states": raw_history })) } Method::Config => { let data_dir_display = context.config.data_dir.display(); diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 8cea4033..9e8ebbc5 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -75,10 +75,10 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Deposit at least 0.00001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Received Bitcoin new_balance=0.00100000 BTC max_giveable=0.00090000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC + INFO swap::api::request: Deposit at least 0.00001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001 BTC max_giveable=0 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.001 BTC max_giveable=0.0009 BTC " ); } @@ -112,10 +112,10 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Deposit at least 0.00001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Received Bitcoin new_balance=0.10010000 BTC max_giveable=0.10000000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC + INFO swap::api::request: Deposit at least 0.00001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001 BTC max_giveable=0 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.1001 BTC max_giveable=0.1 BTC " ); } @@ -149,7 +149,7 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - " INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC\n" + " INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC\n" ); } @@ -182,7 +182,7 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - " INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC\n" + " INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC\n" ); } @@ -215,10 +215,10 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Deposit at least 0.01001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.01001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Deposit at least 0.01001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.01001 BTC max_giveable=0 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.0101 BTC max_giveable=0.01 BTC " ); } @@ -252,10 +252,10 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Deposit at least 0.00991000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00991000 BTC max_giveable=0.00010000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Deposit at least 0.00991 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00991 BTC max_giveable=0.0001 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.0101 BTC max_giveable=0.01 BTC " ); } @@ -292,13 +292,13 @@ mod tests { assert!(matches!(error, tokio::time::error::Elapsed { .. })); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Deposit at least 0.10001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC - INFO swap: Deposited amount is less than `min_quantity` - INFO swap: Deposit at least 0.09001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.09001000 BTC max_giveable=0.01000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Deposit at least 0.10001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001 BTC max_giveable=0 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.0101 BTC max_giveable=0.01 BTC + INFO swap::api::request: Deposited amount is less than `min_quantity` + INFO swap::api::request: Deposit at least 0.09001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.09001 BTC max_giveable=0.01 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC " ); } @@ -341,10 +341,10 @@ mod tests { assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Deposit at least 0.10001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Received Bitcoin new_balance=0.21000000 BTC max_giveable=0.20000000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Deposit at least 0.10001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001 BTC max_giveable=0 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.21 BTC max_giveable=0.2 BTC " ); } diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 2ac0aa78..cffc8c9e 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -370,8 +370,8 @@ mod tests { use crate::env::{GetConfig, Regtest}; use crate::protocol::{alice, bob}; use rand::rngs::OsRng; - use uuid::Uuid; use std::matches; + use uuid::Uuid; #[test] fn lock_confirmations_le_to_cancel_timelock_no_timelock_expired() { @@ -400,7 +400,7 @@ mod tests { tx_cancel_status, ); - assert!(matches!(expired_timelock, ExpiredTimelocks::Cancel {..})); + assert!(matches!(expired_timelock, ExpiredTimelocks::Cancel { .. })); } #[test] diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index b133bb8c..6218fb6f 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -194,6 +194,7 @@ impl Wallet { tokio::time::sleep(Duration::from_secs(5)).await; } }.instrument(Span::current())); + }.instrument(Span::none())); Subscription { receiver, diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 86e48f16..0b95627b 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -499,16 +499,20 @@ mod tests { use crate::api::api_test::*; use crate::api::Config; - use crate::fs::system_data_dir; use crate::monero::monero_address::MoneroAddressNetworkMismatch; - use serial_test::serial; const BINARY_NAME: &str = "swap"; const ARGS_DATA_DIR: &str = "/tmp/dir/"; #[tokio::test] - #[serial] - async fn given_buy_xmr_on_mainnet_then_defaults_to_mainnet() { + + // this test is very long, however it just checks that various CLI arguments sets the + // internal Context and Request properly. It is unlikely to fail and splitting it in various + // tests would require to run the tests sequantially which is very slow (due to the context + // need to access files like the Bitcoin wallet). + async fn test_cli_arguments() { + // given_buy_xmr_on_mainnet_then_defaults_to_mainnet + let raw_ars = vec![ BINARY_NAME, "buy-xmr", @@ -523,23 +527,31 @@ mod tests { let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); let (is_testnet, debug, json) = (false, false, false); - let (expected_config, expected_request) = ( - Config::default(is_testnet, None, debug, json), - Request::buy_xmr(is_testnet), - ); - let (actual_config, actual_request) = match args { ParseResult::Context(context, request) => (context.config.clone(), request), _ => panic!("Couldn't parse result"), }; + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::buy_xmr(is_testnet), + ); + + // since Uuid is random, copy before comparing requests + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_buy_xmr_on_testnet_then_defaults_to_testnet() { + // given_buy_xmr_on_testnet_then_defaults_to_testnet let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -555,7 +567,7 @@ mod tests { let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); let (is_testnet, debug, json) = (true, false, false); - let (expected_config, expected_request) = ( + let (expected_config, mut expected_request) = ( Config::default(is_testnet, None, debug, json), Request::buy_xmr(is_testnet), ); @@ -565,13 +577,20 @@ mod tests { _ => panic!("Couldn't parse result"), }; + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_buy_xmr_on_mainnet_with_testnet_address_then_fails() { + // given_buy_xmr_on_mainnet_with_testnet_address_then_fails let raw_ars = vec![ BINARY_NAME, "buy-xmr", @@ -592,11 +611,8 @@ mod tests { actual: monero::Network::Stagenet } ); - } - #[tokio::test] - #[serial] - async fn given_buy_xmr_on_testnet_with_mainnet_address_then_fails() { + // given_buy_xmr_on_testnet_with_mainnet_address_then_fails let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -618,11 +634,8 @@ mod tests { actual: monero::Network::Mainnet } ); - } - #[tokio::test] - #[serial] - async fn given_resume_on_mainnet_then_defaults_to_mainnet() { + // given_resume_on_mainnet_then_defaults_to_mainnet let raw_ars = vec![BINARY_NAME, "resume", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); @@ -640,11 +653,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_resume_on_testnet_then_defaults_to_testnet() { + // given_resume_on_testnet_then_defaults_to_testnet let raw_ars = vec![BINARY_NAME, "--testnet", "resume", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); @@ -662,11 +672,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_cancel_on_mainnet_then_defaults_to_mainnet() { + // given_cancel_on_mainnet_then_defaults_to_mainnet let raw_ars = vec![BINARY_NAME, "cancel", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); @@ -685,11 +692,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_cancel_on_testnet_then_defaults_to_testnet() { + // given_cancel_on_testnet_then_defaults_to_testnet let raw_ars = vec![BINARY_NAME, "--testnet", "cancel", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); @@ -707,11 +711,7 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_refund_on_mainnet_then_defaults_to_mainnet() { let raw_ars = vec![BINARY_NAME, "refund", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); @@ -729,11 +729,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_refund_on_testnet_then_defaults_to_testnet() { + // given_refund_on_testnet_then_defaults_to_testnet let raw_ars = vec![BINARY_NAME, "--testnet", "refund", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); @@ -751,11 +748,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_buy_xmr_on_mainnet_with_data_dir_then_data_dir_set() { + // given_buy_xmr_on_mainnet_with_data_dir_then_data_dir_set let raw_ars = vec![ BINARY_NAME, "--data-base-dir", @@ -773,7 +767,7 @@ mod tests { let (is_testnet, debug, json) = (false, false, false); let data_dir = PathBuf::from_str(ARGS_DATA_DIR).unwrap(); - let (expected_config, expected_request) = ( + let (expected_config, mut expected_request) = ( Config::default(is_testnet, Some(data_dir.clone()), debug, json), Request::buy_xmr(is_testnet), ); @@ -783,13 +777,20 @@ mod tests { _ => panic!("Couldn't parse result"), }; + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_buy_xmr_on_testnet_with_data_dir_then_data_dir_set() { + // given_buy_xmr_on_testnet_with_data_dir_then_data_dir_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -808,7 +809,7 @@ mod tests { let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); let (is_testnet, debug, json) = (true, false, false); - let (expected_config, expected_request) = ( + let (expected_config, mut expected_request) = ( Config::default(is_testnet, Some(data_dir.clone()), debug, json), Request::buy_xmr(is_testnet), ); @@ -818,13 +819,20 @@ mod tests { _ => panic!("Couldn't parse result"), }; + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_resume_on_mainnet_with_data_dir_then_data_dir_set() { + // given_resume_on_mainnet_with_data_dir_then_data_dir_set let raw_ars = vec![ BINARY_NAME, "--data-base-dir", @@ -838,11 +846,21 @@ mod tests { let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); let (is_testnet, debug, json) = (false, false, false); - let (expected_config, expected_request) = ( + let (expected_config, mut expected_request) = ( Config::default(is_testnet, Some(data_dir.clone()), debug, json), Request::resume(), ); + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + let (actual_config, actual_request) = match args { ParseResult::Context(context, request) => (context.config.clone(), request), _ => panic!("Couldn't parse result"), @@ -850,11 +868,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_resume_on_testnet_with_data_dir_then_data_dir_set() { + // given_resume_on_testnet_with_data_dir_then_data_dir_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -881,11 +896,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_buy_xmr_on_mainnet_with_debug_then_debug_set() { + // given_buy_xmr_on_mainnet_with_debug_then_debug_set let raw_ars = vec![ BINARY_NAME, "--debug", @@ -901,7 +913,7 @@ mod tests { let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); let (is_testnet, debug, json) = (false, true, false); - let (expected_config, expected_request) = ( + let (expected_config, mut expected_request) = ( Config::default(is_testnet, None, debug, json), Request::buy_xmr(is_testnet), ); @@ -911,13 +923,20 @@ mod tests { _ => panic!("Couldn't parse result"), }; + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_buy_xmr_on_testnet_with_debug_then_debug_set() { + // given_buy_xmr_on_testnet_with_debug_then_debug_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -934,7 +953,7 @@ mod tests { let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); let (is_testnet, debug, json) = (true, true, false); - let (expected_config, expected_request) = ( + let (expected_config, mut expected_request) = ( Config::default(is_testnet, None, debug, json), Request::buy_xmr(is_testnet), ); @@ -944,19 +963,26 @@ mod tests { _ => panic!("Couldn't parse result"), }; + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_resume_on_mainnet_with_debug_then_debug_set() { + // given_resume_on_mainnet_with_debug_then_debug_set let raw_ars = vec![BINARY_NAME, "--debug", "resume", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); let (is_testnet, debug, json) = (false, true, false); - let (expected_config, expected_request) = ( + let (expected_config, mut expected_request) = ( Config::default(is_testnet, None, debug, json), Request::resume(), ); @@ -966,13 +992,20 @@ mod tests { _ => panic!("Couldn't parse result"), }; + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_resume_on_testnet_with_debug_then_debug_set() { + // given_resume_on_testnet_with_debug_then_debug_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -997,11 +1030,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_buy_xmr_on_mainnet_with_json_then_json_set() { + // given_buy_xmr_on_mainnet_with_json_then_json_set let raw_ars = vec![ BINARY_NAME, "--json", @@ -1016,9 +1046,8 @@ mod tests { let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); let (is_testnet, debug, json) = (false, false, true); - let data_dir = data_dir_path_cli(is_testnet); - let (expected_config, expected_request) = ( + let (expected_config, mut expected_request) = ( Config::default(is_testnet, None, debug, json), Request::buy_xmr(is_testnet), ); @@ -1028,13 +1057,20 @@ mod tests { _ => panic!("Couldn't parse result"), }; + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_buy_xmr_on_testnet_with_json_then_json_set() { + // given_buy_xmr_on_testnet_with_json_then_json_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -1050,7 +1086,7 @@ mod tests { let (is_testnet, debug, json) = (true, false, true); - let (expected_config, expected_request) = ( + let (expected_config, mut expected_request) = ( Config::default(is_testnet, None, debug, json), Request::buy_xmr(is_testnet), ); @@ -1061,13 +1097,20 @@ mod tests { _ => panic!("Couldn't parse result"), }; + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_resume_on_mainnet_with_json_then_json_set() { + // given_resume_on_mainnet_with_json_then_json_set let raw_ars = vec![BINARY_NAME, "--json", "resume", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); let (is_testnet, debug, json) = (false, false, true); @@ -1084,11 +1127,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn given_resume_on_testnet_with_json_then_json_set() { + // given_resume_on_testnet_with_json_then_json_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -1113,11 +1153,8 @@ mod tests { assert_eq!(actual_config, expected_config); assert_eq!(actual_request, Box::new(expected_request)); - } - #[tokio::test] - #[serial] - async fn only_bech32_addresses_mainnet_are_allowed() { + // only_bech32_addresses_mainnet_are_allowed let raw_ars = vec![ BINARY_NAME, "buy-xmr", @@ -1128,7 +1165,7 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); + parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); let raw_ars = vec![ BINARY_NAME, @@ -1140,7 +1177,7 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); + parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); let raw_ars = vec![ BINARY_NAME, @@ -1154,11 +1191,8 @@ mod tests { ]; let result = parse_args_and_apply_defaults(raw_ars).await.unwrap(); assert!(matches!(result, ParseResult::Context(_, _))); - } - #[tokio::test] - #[serial] - async fn only_bech32_addresses_testnet_are_allowed() { + // only_bech32_addresses_testnet_are_allowed let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -1170,7 +1204,7 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); + parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); let raw_ars = vec![ BINARY_NAME, @@ -1183,7 +1217,7 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); + parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); let raw_ars = vec![ BINARY_NAME, @@ -1199,12 +1233,4 @@ mod tests { let result = parse_args_and_apply_defaults(raw_ars).await.unwrap(); assert!(matches!(result, ParseResult::Context(_, _))); } - - fn data_dir_path_cli(is_testnet: bool) -> PathBuf { - if is_testnet { - system_data_dir().unwrap().join("cli").join("testnet") - } else { - system_data_dir().unwrap().join("cli").join("mainnet") - } - } } diff --git a/swap/src/rpc/methods.rs b/swap/src/rpc/methods.rs index c5fa461c..1ebd8824 100644 --- a/swap/src/rpc/methods.rs +++ b/swap/src/rpc/methods.rs @@ -219,11 +219,13 @@ async fn execute_request( cmd: Method, context: &Arc, ) -> Result { - let params_parsed = params + // If we fail to parse the params as a String HashMap, it's most likely because its an empty object + // In that case, we want to make sure not to fail the request, so we set the log_reference_id to None + // and swallow the error + let reference_id = params .parse::>() - .map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?; - - let reference_id = params_parsed.get("log_reference_id"); + .ok() + .and_then(|params_parsed| params_parsed.get("log_reference_id").cloned()); let request = Request::with_id(cmd, reference_id.map(|s| s.clone())); request diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 4f3f5fee..49bdbe32 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -23,9 +23,9 @@ use swap::network::rendezvous::XmrBtcNamespace; use swap::network::swarm; use swap::protocol::alice::{AliceState, Swap}; use swap::protocol::bob::BobState; -use swap::protocol::{alice, bob}; +use swap::protocol::{alice, bob, Database}; use swap::seed::Seed; -use swap::{asb, bitcoin, cli, env, monero}; +use swap::{api, asb, bitcoin, cli, env, monero}; use tempfile::{tempdir, NamedTempFile}; use testcontainers::clients::Cli; use testcontainers::{Container, Docker, RunArgs}; @@ -454,6 +454,8 @@ impl BobParams { } let db = Arc::new(SqliteDatabase::open(&self.db_path).await?); + db.insert_peer_id(swap_id, self.alice_peer_id).await?; + let swap = bob::Swap::new( db, swap_id, @@ -534,6 +536,17 @@ pub struct TestContext { } impl TestContext { + pub async fn get_bob_context(self) -> api::Context { + api::Context::for_harness( + self.bob_params.seed, + self.env_config, + self.bob_params.db_path, + self.bob_bitcoin_wallet, + self.bob_monero_wallet, + ) + .await + } + pub async fn restart_alice(&mut self) { self.alice_handle.abort(); diff --git a/swap/tests/rpc.rs b/swap/tests/rpc.rs index f034e7b1..d2df7c52 100644 --- a/swap/tests/rpc.rs +++ b/swap/tests/rpc.rs @@ -1,19 +1,33 @@ +pub mod harness; use anyhow::Result; -use jsonrpsee::rpc_params; use jsonrpsee::ws_client::WsClientBuilder; -use jsonrpsee_core::client::ClientT; +use jsonrpsee_core::client::{Client, ClientT}; use jsonrpsee_core::params::ObjectParams; use serial_test::serial; +use serde_json::{json, Value}; use std::collections::HashMap; +use std::future::Future; +use std::net::SocketAddr; +use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; -use swap::api::request::{Method, Request }; -use swap::api::Context; +use swap::api::request::{Method, Request}; +use swap::api::{Config, Context, SwapLock}; use swap::cli::command::{Bitcoin, Monero}; +use tempfile::tempdir; +use tracing_subscriber::filter::LevelFilter; +use crate::harness::alice_run_until::is_xmr_lock_transaction_sent; +use crate::harness::bob_run_until::is_btc_locked; +use crate::harness::{setup_test, SlowCancelConfig, TestContext}; +use swap::asb::FixedRate; +use swap::database::open_db; +use swap::network::rendezvous::XmrBtcNamespace; +use swap::protocol::{alice, bob}; +use swap::tracing_ext::{capture_logs, MakeCapturingWriter}; use uuid::Uuid; #[cfg(test)] @@ -27,419 +41,511 @@ const MONERO_ADDR: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAo const SELLER: &str = "/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"; const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b"; +const SERVER_START_TIMEOUT_SECS: u64 = 50; -pub async fn initialize_context() -> (Arc, Request) { - let (is_testnet, debug, json) = (true, false, false); - // let data_dir = data::data_dir_from(None, is_testnet).unwrap(); - let server_address = None; +pub async fn setup_daemon(testfn: T) +where + T: Fn(TestContext, Client, MakeCapturingWriter, Arc) -> F, + F: Future>, +{ + setup_test(SlowCancelConfig, |harness_context| async move { + let writer = capture_logs(LevelFilter::DEBUG); + let server_address: SocketAddr = SERVER_ADDRESS.parse().unwrap(); - let bitcoin = Bitcoin { - bitcoin_electrum_rpc_url: None, - bitcoin_target_block: None, - }; + let request = Request::new(Method::StartDaemon { + server_address: Some(server_address), + }); - let _monero = Monero { - monero_daemon_address: None, - }; + let context = Arc::new(harness_context.get_bob_context().await); - let request = Request::new(Method::StartDaemon { server_address: None }); + let context_clone = context.clone(); - let context = Context::build( - Some(bitcoin), - None, - None, - None, - is_testnet, - debug, - json, - server_address, - ) + tokio::spawn(async move { + if let Err(err) = request.call(context_clone).await { + println!("Failed to initialize daemon for testing: {}", err); + } + }); + + for _ in 0..SERVER_START_TIMEOUT_SECS { + if writer.captured().contains("Started RPC server") { + let url = format!("ws://{}", SERVER_ADDRESS); + let client = WsClientBuilder::default().build(&url).await.unwrap(); + + return testfn(harness_context, client, writer, context).await; + } + + tokio::time::sleep(Duration::from_secs(1)).await; + } + + panic!( + "Failed to start RPC server after {} seconds", + SERVER_START_TIMEOUT_SECS + ); + }) .await - .unwrap(); +} - (Arc::new(context), request) +fn assert_has_keys_serde(map: &serde_json::Map, keys: &[&str]) { + for &key in keys { + assert!(map.contains_key(key), "Key {} is missing", key); + } +} + +// Helper function for HashMap +fn assert_has_keys_hashmap(map: &HashMap, keys: &[&str]) { + for &key in keys { + assert!(map.contains_key(key), "Key {} is missing", key); + } } #[tokio::test] #[serial] pub async fn can_start_server() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); - tokio::spawn(async move { - request.call(Arc::clone(&move_ctx)).await; - }); - tokio::time::sleep(Duration::from_secs(3)).await; - assert!(true); + setup_daemon(|_, _, _, _| async move { + assert!(true); + Ok(()) + }) + .await; } #[tokio::test] #[serial] pub async fn get_bitcoin_balance() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); - tokio::spawn(async move { - request.call(Arc::clone(&move_ctx)).await; - }); + setup_daemon(|_, client| async move { + let response: HashMap = client + .request("get_bitcoin_balance", ObjectParams::new()) + .await + .unwrap(); - let url = format!("ws://{}", SERVER_ADDRESS); - tokio::time::sleep(Duration::from_secs(3)).await; + assert_eq!(response, HashMap::from([("balance".to_string(), 10000000)])); - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let response: HashMap = client - .request("get_bitcoin_balance", rpc_params!["id"]) - .await - .unwrap(); + Ok(()) + }) + .await; +} - assert_eq!(response, HashMap::from([("balance".to_string(), 0)])); +#[tokio::test] +#[serial] +pub async fn get_bitcoin_balance_with_log_reference_id() { + setup_daemon(|_, client, writer| async move { + let mut params = ObjectParams::new(); + params.insert("log_reference_id", "test_ref_id").unwrap(); + + let _: HashMap = client.request("get_bitcoin_balance", params).await.unwrap(); + + assert!(writer.captured().contains( + "log_reference_id=\"test_ref_id\"}: swap::api::request: Checked Bitcoin balance balance=0" + )); + + Ok(()) + }).await; } #[tokio::test] #[serial] pub async fn get_history() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); - tokio::spawn(async move { - request.call(Arc::clone(&move_ctx)).await; - }); + setup_daemon(|mut harness_ctx, client| async move { + // Start a swap and wait for xmr lock transaction to be published (XmrLockTransactionSent) + let (bob_swap, _) = harness_ctx.bob_swap().await; + let bob_swap_id = bob_swap.id; + tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); + let alice_swap = harness_ctx.alice_next_swap().await; + alice::run_until( + alice_swap, + is_xmr_lock_transaction_sent, + FixedRate::default(), + ) + .await?; - let url = format!("ws://{}", SERVER_ADDRESS); - tokio::time::sleep(Duration::from_secs(3)).await; + let response: HashMap> = client + .request("get_history", ObjectParams::new()) + .await + .unwrap(); + let swaps: Vec<(Uuid, String)> = vec![(bob_swap_id, "btc is locked".to_string())]; - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let mut params = ObjectParams::new(); + assert_eq!(response, HashMap::from([("swaps".to_string(), swaps)])); - let response: HashMap> = - client.request("get_history", params).await.unwrap(); - let swaps: Vec<(Uuid, String)> = Vec::new(); - - assert_eq!(response, HashMap::from([("swaps".to_string(), swaps)])); + Ok(()) + }) + .await; } #[tokio::test] #[serial] -pub async fn get_raw_history() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); +pub async fn get_raw_states() { + setup_daemon(|mut harness_ctx, client| async move { + // Start a swap and wait for xmr lock transaction to be published (XmrLockTransactionSent) + let (bob_swap, _) = harness_ctx.bob_swap().await; + let bob_swap_id = bob_swap.id; + tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); + let alice_swap = harness_ctx.alice_next_swap().await; + alice::run_until( + alice_swap, + is_xmr_lock_transaction_sent, + FixedRate::default(), + ) + .await?; + + let response: HashMap>> = client + .request("get_raw_states", ObjectParams::new()) + .await + .unwrap(); + + let response_raw_states = response.get("raw_states").unwrap(); + + assert!(response_raw_states.contains_key(&bob_swap_id)); + assert_eq!(response_raw_states.get(&bob_swap_id).unwrap().len(), 2); + + Ok(()) + }) + .await; +} + +#[tokio::test] +#[serial] +pub async fn get_swap_info_valid_swap_id() { + setup_daemon(|mut harness_ctx, client| async move { + // Start a swap and wait for xmr lock transaction to be published (XmrLockTransactionSent) + let (bob_swap, _) = harness_ctx.bob_swap().await; + let bob_swap_id = bob_swap.id; + tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); + let alice_swap = harness_ctx.alice_next_swap().await; + alice::run_until( + alice_swap, + is_xmr_lock_transaction_sent, + FixedRate::default(), + ) + .await?; + + let mut params = ObjectParams::new(); + params.insert("swap_id", bob_swap_id).unwrap(); + let response: HashMap = + client.request("get_swap_info", params).await.unwrap(); + + // Check primary keys in response + assert_has_keys_hashmap( + &response, + &[ + "txRefundFee", + "swapId", + "cancelTimelock", + "timelock", + "punishTimelock", + "stateName", + "btcAmount", + "startDate", + "btcRefundAddress", + "txCancelFee", + "xmrAmount", + "completed", + "txLockId", + "seller", + ], + ); + + // Assert specific fields + assert_eq!(response.get("swapId").unwrap(), &bob_swap_id.to_string()); + assert_eq!( + response.get("stateName").unwrap(), + &"btc is locked".to_string() + ); + assert_eq!(response.get("completed").unwrap(), &Value::Bool(false)); + + // Check seller object and its keys + let seller = response + .get("seller") + .expect("Field 'seller' is missing from response") + .as_object() + .expect("'seller' is not an object"); + assert_has_keys_serde(seller, &["peerId"]); + + // Check timelock object, nested 'None' object, and blocks_left + let timelock = response + .get("timelock") + .expect("Field 'timelock' is missing from response") + .as_object() + .expect("'timelock' is not an object"); + let none_obj = timelock + .get("None") + .expect("Field 'None' is missing from 'timelock'") + .as_object() + .expect("'None' is not an object in 'timelock'"); + let blocks_left = none_obj + .get("blocks_left") + .expect("Field 'blocks_left' is missing from 'None'") + .as_i64() + .expect("'blocks_left' is not an integer"); + + // Validate blocks_left + assert!( + blocks_left > 0 && blocks_left <= 180, + "Field 'blocks_left' should be > 0 and <= 180 but got {}", + blocks_left + ); + + Ok(()) + }) + .await; +} + +#[tokio::test] +#[serial] +pub async fn swap_endpoints_invalid_or_missing_swap_id() { + setup_daemon(|_, client| async move { + for method in ["get_swap_info", "resume_swap", "cancel_refund_swap"].iter() { + let mut params = ObjectParams::new(); + params.insert("swap_id", "invalid_swap").unwrap(); + + let response: Result, _> = client.request(method, params).await; + response.expect_err(&format!( + "Expected an error when swap_id is invalid for method {}", + method + )); + + let params = ObjectParams::new(); + + let response: Result, _> = client.request(method, params).await; + response.expect_err(&format!( + "Expected an error when swap_id is missing for method {}", + method + )); + } + + Ok(()) + }) + .await; +} + +#[tokio::test] +pub async fn list_sellers_missing_rendezvous_point() { + setup_daemon(|_, client| async move { + let params = ObjectParams::new(); + let result: Result, _> = + client.request("list_sellers", params).await; + + result.expect_err("Expected an error when rendezvous_point is missing"); + + Ok(()) + }) + .await; +} + +/* +#[tokio::test] +#[serial] +pub async fn resume_swap_valid_swap_id() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params.insert("swap_id", SWAP_ID).unwrap(); + let response: Result, _> = client.request("resume_swap", params).await; + response.expect("Expected a HashMap, got an error"); +} + +#[tokio::test] +#[serial] +pub async fn withdraw_btc_missing_address() { + let (client, _, _) = initialize_context().await; + let params = ObjectParams::new(); + let response: Result, _> = client.request("withdraw_btc", params).await; + response.expect_err("Expected an error when withdraw_address is missing"); +} + +#[tokio::test] +#[serial] +pub async fn withdraw_btc_invalid_address() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params.insert("address", "invalid_address").unwrap(); + let response: Result, _> = client.request("withdraw_btc", params).await; + response.expect_err("Expected an error when withdraw_address is malformed"); +} + +#[tokio::test] +#[serial] +pub async fn withdraw_btc_zero_amount() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params.insert("address", BITCOIN_ADDR).unwrap(); + params.insert("amount", "0").unwrap(); + let response: Result, _> = client.request("withdraw_btc", params).await; + response.expect_err("Expected an error when amount is 0"); +} + +#[tokio::test] +#[serial] +pub async fn withdraw_btc_valid_params() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params.insert("address", BITCOIN_ADDR).unwrap(); + params.insert("amount", "0.1").unwrap(); + let response: Result, _> = client.request("withdraw_btc", params).await; + response.expect("Expected a HashMap, got an error"); +} + +#[tokio::test] +#[serial] +pub async fn buy_xmr_no_params() { + let (client, _, _) = initialize_context().await; + let params = ObjectParams::new(); + let response: Result, _> = client.request("buy_xmr", params).await; + response.expect_err("Expected an error when no params are given"); +} + +#[tokio::test] +#[serial] +pub async fn buy_xmr_missing_seller() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", BITCOIN_ADDR) + .unwrap(); + params + .insert("monero_receive_address", MONERO_ADDR) + .unwrap(); + let response: Result, _> = client.request("buy_xmr", params).await; + response.expect_err("Expected an error when seller is missing"); +} + +#[tokio::test] +#[serial] +pub async fn buy_xmr_missing_monero_address() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", BITCOIN_ADDR) + .unwrap(); + params.insert("seller", SELLER).unwrap(); + let response: Result, _> = client.request("buy_xmr", params).await; + response.expect_err("Expected an error when monero_receive_address is missing"); +} + +#[tokio::test] +#[serial] +pub async fn buy_xmr_missing_bitcoin_address() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params + .insert("monero_receive_address", MONERO_ADDR) + .unwrap(); + params.insert("seller", SELLER).unwrap(); + let response: Result, _> = client.request("buy_xmr", params).await; + response.expect_err("Expected an error when bitcoin_change_address is missing"); +} + +#[tokio::test] +#[serial] +pub async fn buy_xmr_malformed_bitcoin_address() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", "invalid_address") + .unwrap(); + params + .insert("monero_receive_address", MONERO_ADDR) + .unwrap(); + params.insert("seller", SELLER).unwrap(); + let response: Result, _> = client.request("buy_xmr", params).await; + response.expect_err("Expected an error when bitcoin_change_address is malformed"); +} + +#[tokio::test] +#[serial] +pub async fn buy_xmr_malformed_monero_address() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", BITCOIN_ADDR) + .unwrap(); + params + .insert("monero_receive_address", "invalid_address") + .unwrap(); + params.insert("seller", SELLER).unwrap(); + let response: Result, _> = client.request("buy_xmr", params).await; + response.expect_err("Expected an error when monero_receive_address is malformed"); +} + +#[tokio::test] +#[serial] +pub async fn buy_xmr_malformed_seller() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", BITCOIN_ADDR) + .unwrap(); + params + .insert("monero_receive_address", MONERO_ADDR) + .unwrap(); + params.insert("seller", "invalid_seller").unwrap(); + let response: Result, _> = client.request("buy_xmr", params).await; + response.expect_err("Expected an error when seller is malformed"); +} + +#[tokio::test] +#[serial] +pub async fn buy_xmr_valid_params() { + let (client, _, _) = initialize_context().await; + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", BITCOIN_ADDR) + .unwrap(); + params + .insert("monero_receive_address", MONERO_ADDR) + .unwrap(); + params.insert("seller", SELLER).unwrap(); + let response: Result, _> = client.request("buy_xmr", params).await; + response.expect("Expected a HashMap, got an error"); +} + +#[tokio::test] +#[serial] +pub async fn suspend_current_swap_no_swap_running() { + let (client, _, _) = initialize_context().await; + let response: Result, _> = client + .request("suspend_current_swap", ObjectParams::new()) + .await; + response.expect_err("Expected an error when no swap is running"); +} + +#[tokio::test] +#[serial] +pub async fn suspend_current_swap_swap_running() { + let (client, _, ctx) = initialize_context().await; + ctx.swap_lock + .acquire_swap_lock(Uuid::parse_str(SWAP_ID).unwrap()) + .await + .unwrap(); + tokio::spawn(async move { - request.call(Arc::clone(&move_ctx)).await; + // Immediately release lock when suspend signal is received. Mocks a running swap that is then cancelled. + ctx.swap_lock + .listen_for_swap_force_suspension() + .await + .unwrap(); + ctx.swap_lock.release_swap_lock().await.unwrap(); }); - let url = format!("ws://{}", SERVER_ADDRESS); - tokio::time::sleep(Duration::from_secs(3)).await; - - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let mut params = ObjectParams::new(); - let raw_history: HashMap = HashMap::new(); - - let response: HashMap> = - client.request("get_raw_history", params).await.unwrap(); - + let response: HashMap = client + .request("suspend_current_swap", ObjectParams::new()) + .await + .unwrap(); assert_eq!( response, - HashMap::from([("raw_history".to_string(), raw_history)]) + HashMap::from([("swapId".to_string(), SWAP_ID.to_string())]) ); } #[tokio::test] #[serial] -pub async fn get_seller() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); - tokio::spawn(async move { - request.call(Arc::clone(&move_ctx)).await; - }); - - let url = format!("ws://{}", SERVER_ADDRESS); - tokio::time::sleep(Duration::from_secs(3)).await; - - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let mut params = ObjectParams::new(); - - let response: Result, _> = client.request("get_seller", params).await; - - // We should ideally match the expected error and panic if it's different one, - // but the request returns a custom error (to investigate) - // Err(jsonrpsee_core::Error::Call(CallError::InvalidParams(e))) => (), - // Err(e) => panic!("ErrorType was not ParseError but {e:?}"), - - match response { - Err(e) => (), - _ => panic!("Expected an error when swap_id is missing"), - } - - let mut params = ObjectParams::new(); - params.insert("swap_id", "invalid_swap"); - - let response: Result, _> = client.request("get_seller", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error swap_id is malformed"), - } - - let mut params = ObjectParams::new(); - params.insert("swap_id", SWAP_ID); - - let response: Result, _> = client.request("get_seller", params).await; - - match response { - Ok(hash) => (), - Err(e) => panic!( - "Expected a HashMap with correct params, got an error: {}", - e - ), - } -} - -#[tokio::test] -#[serial] -pub async fn get_swap_start_date() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); - tokio::spawn(async move { - request.call(Arc::clone(&move_ctx)).await; - }); - - let url = format!("ws://{}", SERVER_ADDRESS); - tokio::time::sleep(Duration::from_secs(3)).await; - - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let mut params = ObjectParams::new(); - - let response: Result, _> = - client.request("get_swap_start_date", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when swap_id is missing"), - } - - let mut params = ObjectParams::new(); - params.insert("swap_id", "invalid_swap"); - - let response: Result, _> = - client.request("get_swap_start_date", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when swap_id is malformed"), - } - - let mut params = ObjectParams::new(); - params.insert("swap_id", SWAP_ID); - - let response: Result, _> = - client.request("get_swap_start_date", params).await; - - match response { - Ok(hash) => (), - Err(e) => panic!("Expected a HashMap, got an error: {}", e), - } -} - -#[tokio::test] -#[serial] -pub async fn resume_swap() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); - tokio::spawn(async move { - request.call(Arc::clone(&move_ctx)).await; - }); - - let url = format!("ws://{}", SERVER_ADDRESS); - tokio::time::sleep(Duration::from_secs(3)).await; - - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let mut params = ObjectParams::new(); - - let response: Result, _> = - client.request("get_swap_start_date", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when swap_id is missing"), - } - - let mut params = ObjectParams::new(); - params.insert("swap_id", "invalid_swap"); - - let response: Result, _> = - client.request("get_swap_start_date", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when swap_id is malformed"), - } - - let mut params = ObjectParams::new(); - params.insert("swap_id", SWAP_ID); - - let response: Result, _> = - client.request("get_swap_start_date", params).await; - - match response { - Ok(hash) => (), - Err(e) => panic!("Expected a HashMap, got an error: {}", e), - } -} - -#[tokio::test] -#[serial] -pub async fn withdraw_btc() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); - tokio::spawn(async move { - request.call(Arc::clone(&move_ctx)).await; - }); - - let url = format!("ws://{}", SERVER_ADDRESS); - tokio::time::sleep(Duration::from_secs(3)).await; - - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let mut params = ObjectParams::new(); - - let response: Result, _> = client.request("withdraw_btc", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when withdraw_address is missing"), - } - - let mut params = ObjectParams::new(); - params.insert("address", "invalid_address"); - - let response: Result, _> = client.request("withdraw_btc", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when withdraw_address is malformed"), - } - - let mut params = ObjectParams::new(); - params.insert("address", BITCOIN_ADDR); - params.insert("amount", "0"); - - let response: Result, _> = client.request("withdraw_btc", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when amount is 0"), - } - - let mut params = ObjectParams::new(); - params.insert("address", BITCOIN_ADDR); - params.insert("amount", "0.1"); - - let response: Result, _> = client.request("withdraw_btc", params).await; - - match response { - Ok(hash) => (), - Err(e) => panic!("Expected a HashMap, got an error: {}", e), - } - -} - -#[tokio::test] -#[serial] -pub async fn buy_xmr() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); - tokio::spawn(async move { - request.call(Arc::clone(&move_ctx)).await; - }); - - let url = format!("ws://{}", SERVER_ADDRESS); - tokio::time::sleep(Duration::from_secs(3)).await; - - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let mut params = ObjectParams::new(); - - let response: Result, _> = client.request("buy_xmr", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when no params are given"), - } - - let mut params = ObjectParams::new(); - params.insert("bitcoin_change_address", BITCOIN_ADDR); - params.insert("monero_receive_address", MONERO_ADDR); - - let response: Result, _> = client.request("buy_xmr", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when seller is missing"), - } - - let mut params = ObjectParams::new(); - params.insert("bitcoin_change_address", BITCOIN_ADDR); - params.insert("seller", SELLER); - - let response: Result, _> = client.request("buy_xmr", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when monero_receive_address is missing"), - } - - let mut params = ObjectParams::new(); - params.insert("monero_receive_address", MONERO_ADDR); - params.insert("seller", SELLER); - - let response: Result, _> = client.request("buy_xmr", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when bitcoin_change_address is missing"), - } - - let mut params = ObjectParams::new(); - params.insert("bitcoin_change_address", "invalid_address"); - params.insert("monero_receive_address", MONERO_ADDR); - params.insert("seller", SELLER); - - let response: Result, _> = client.request("buy_xmr", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when bitcoin_change_address is malformed"), - } - - let mut params = ObjectParams::new(); - params.insert("bitcoin_change_address", BITCOIN_ADDR); - params.insert("monero_receive_address", "invalid_address"); - params.insert("seller", SELLER); - - let response: Result, _> = client.request("buy_xmr", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when monero_receive_address is malformed"), - } - - let mut params = ObjectParams::new(); - params.insert("bitcoin_change_address", BITCOIN_ADDR); - params.insert("monero_receive_address", MONERO_ADDR); - params.insert("seller", "invalid_seller"); - - let response: Result, _> = client.request("buy_xmr", params).await; - - match response { - Err(e) => (), - _ => panic!("Expected an error when seller is malformed"), - } - - let mut params = ObjectParams::new(); - params.insert("bitcoin_change_address", BITCOIN_ADDR); - params.insert("monero_receive_address", MONERO_ADDR); - params.insert("seller", SELLER); - - let response: Result, _> = client.request("buy_xmr", params).await; - - match response { - Ok(hash) => (), - Err(e) => panic!("Expected a HashMap, got an error: {}", e), - } +pub async fn suspend_current_swap_timeout() { + let (client, _, ctx) = initialize_context().await; + ctx.swap_lock + .acquire_swap_lock(Uuid::parse_str(SWAP_ID).unwrap()) + .await + .unwrap(); + let response: Result, _> = client + .request("suspend_current_swap", ObjectParams::new()) + .await; + response.expect_err("Expected an error when suspend signal times out"); } +*/