From 03be15b0691645bb46bed1406e3eb6b4e94d7fa9 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Sun, 24 Sep 2023 17:30:05 +0200 Subject: [PATCH] WIP: RPC server integration tests --- swap/src/api.rs | 91 ++-- swap/src/api/request.rs | 16 +- swap/src/bitcoin.rs | 4 +- swap/src/bitcoin/wallet.rs | 1 + swap/src/rpc/methods.rs | 10 +- swap/tests/harness/mod.rs | 20 +- swap/tests/rpc.rs | 828 ++++++++++++++++++++----------------- 7 files changed, 554 insertions(+), 416 deletions(-) diff --git a/swap/src/api.rs b/swap/src/api.rs index 0f5805cd..62a14e8c 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,36 @@ impl Context { Ok(context) } + + pub fn new( + db: Arc, + bitcoin_wallet: Option>, + monero_wallet: Option>, + monero_rpc_process: Option, + config: Config, + ) -> Self { + Self { + db, + bitcoin_wallet, + monero_wallet, + monero_rpc_process, + swap_lock: Arc::new(SwapLock::new()), + config, + } + } + + 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 +318,27 @@ 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 +401,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/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/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..436b8f9a 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -16,16 +16,16 @@ use std::sync::Arc; use std::time::Duration; use swap::asb::FixedRate; use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund}; -use swap::database::SqliteDatabase; +use swap::database::{SqliteDatabase}; use swap::env::{Config, GetConfig}; use swap::fs::ensure_directory_exists; 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, @@ -488,6 +490,7 @@ impl BobParams { .behaviour_mut() .add_address(self.alice_peer_id, self.alice_address.clone()); + cli::EventLoop::new(swap_id, swarm, self.alice_peer_id) } } @@ -534,6 +537,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..9ac3523e 100644 --- a/swap/tests/rpc.rs +++ b/swap/tests/rpc.rs @@ -1,20 +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::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 tempfile::tempdir; +use swap::api::request::{Method, Request}; +use swap::api::{Config, Context, SwapLock}; use swap::cli::command::{Bitcoin, Monero}; +use tracing_subscriber::filter::LevelFilter; +use swap::tracing_ext::{capture_logs, MakeCapturingWriter}; use uuid::Uuid; +use swap::asb::FixedRate; +use swap::database::open_db; +use swap::network::rendezvous::XmrBtcNamespace; +use swap::protocol::{alice, bob}; +use crate::harness::{setup_test, SlowCancelConfig}; +use crate::harness::alice_run_until::is_xmr_lock_transaction_sent; +use crate::harness::bob_run_until::is_btc_locked; #[cfg(test)] @@ -27,419 +40,484 @@ 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 initialize_daemon(context: Context) -> (Client, MakeCapturingWriter, Arc) { + 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( + context + ); - 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, - ) - .await - .unwrap(); + tokio::spawn(async move { + if let Err(err) = request.call(context_clone).await { + println!("Failed to initialize daemon for testing: {}", err); + } + }); - (Arc::new(context), request) + 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 (client, writer, context); + } + + tokio::time::sleep(Duration::from_secs(1)).await; + } + + panic!( + "Failed to start RPC server after {} seconds", + SERVER_START_TIMEOUT_SECS + ); } +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; + initialize_context().await; assert!(true); } +*/ + #[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_test(SlowCancelConfig, |harness_ctx| async move { + let context = harness_ctx.get_bob_context().await; + let (client, _, _) = initialize_daemon(context).await; - let url = format!("ws://{}", SERVER_ADDRESS); - tokio::time::sleep(Duration::from_secs(3)).await; + let response: HashMap = client + .request("get_bitcoin_balance", ObjectParams::new()) + .await + .unwrap(); - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let response: HashMap = client - .request("get_bitcoin_balance", rpc_params!["id"]) - .await - .unwrap(); + assert_eq!(response, HashMap::from([("balance".to_string(), 10000000)])); - assert_eq!(response, HashMap::from([("balance".to_string(), 0)])); + Ok(()) + }).await; +} + +#[tokio::test] +#[serial] +pub async fn get_bitcoin_balance_with_log_reference_id() { + setup_test(SlowCancelConfig, |harness_ctx| async move { + let context = harness_ctx.get_bob_context().await; + let (client, writer, _) = initialize_daemon(context).await; + + 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_test(SlowCancelConfig, |mut harness_ctx| 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 context = harness_ctx.get_bob_context().await; + let (client, _, _) = initialize_daemon(context).await; - let client = WsClientBuilder::default().build(&url).await.unwrap(); - let mut params = ObjectParams::new(); + 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 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)]) + ); - assert_eq!(response, HashMap::from([("swaps".to_string(), swaps)])); + Ok(()) + }).await; +} + + +#[tokio::test] +#[serial] +pub async fn get_raw_states() { + setup_test(SlowCancelConfig, |mut harness_ctx| 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 context = harness_ctx.get_bob_context().await; + let (client, _, _) = initialize_daemon(context).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_raw_history() { - let (ctx, mut request) = initialize_context().await; - let move_ctx = Arc::clone(&ctx); +pub async fn get_swap_info_valid_swap_id() { + setup_test(SlowCancelConfig, |mut harness_ctx| 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 context = harness_ctx.get_bob_context().await; + let (client, _, _) = initialize_daemon(context).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_test(SlowCancelConfig, |harness_ctx| async move { + let context = harness_ctx.get_bob_context().await; + let (client, _, _) = initialize_daemon(context).await; + + 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_test(SlowCancelConfig, |harness_ctx| async move { + let context = harness_ctx.get_bob_context().await; + let (client, _, _) = initialize_daemon(context).await; + + 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"); } +*/ \ No newline at end of file