From d2bb4250d3ab80c6f32b4bc4f1b440dcccb4c713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Tue, 29 Oct 2024 16:03:50 -0300 Subject: [PATCH 1/3] We discard events older than 10 seconds --- src/app.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index 3c1c216d..ad4730d7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -69,9 +69,12 @@ pub async fn run( }; let event = unwrap_gift_wrap(&my_keys, &event)?; - // Here we discard messages older than the real since parameter - let now = chrono::Utc::now().timestamp() as u64; - if event.rumor.created_at.as_u64() < now { + // We discard events older than 10 seconds + let since_time = chrono::Utc::now() + .checked_sub_signed(chrono::Duration::seconds(10)) + .unwrap() + .timestamp() as u64; + if event.rumor.created_at.as_u64() < since_time { continue; } From 6fdcf74601f8ce3a3421f9e255ca49308251eab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Wed, 30 Oct 2024 10:19:25 -0300 Subject: [PATCH 2/3] Relays scheduled job publish only connected relays (#381) --- src/scheduler.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/scheduler.rs b/src/scheduler.rs index 48d6761f..03644e75 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -9,9 +9,8 @@ use crate::NOSTR_CLIENT; use chrono::{TimeDelta, Utc}; use mostro_core::order::{Kind, Status}; use nostr_sdk::EventBuilder; -use nostr_sdk::{Event, Kind as NostrKind, Tag, Url}; +use nostr_sdk::{Event, Kind as NostrKind, Tag}; use sqlx_crud::Crud; -use std::str::FromStr; use std::sync::Arc; use tokio::sync::Mutex; use tracing::{error, info}; @@ -42,11 +41,13 @@ async fn job_relay_list() { info!("Sending Mostro relay list"); let interval = Settings::get_mostro().publish_relays_interval as u64; - let relay_list = Settings::get_nostr().relays; + let relays = NOSTR_CLIENT.get().unwrap().relays().await; let mut relay_tags: Vec = vec![]; - for r in relay_list { - relay_tags.push(Tag::relay_metadata(Url::from_str(&r).unwrap(), None)) + for (_, r) in relays.iter() { + if r.is_connected().await { + relay_tags.push(Tag::relay_metadata(r.url(), None)) + } } if let Ok(relay_ev) = From df845f92ffaffae2b1a0735ebe736d0ae4abe688 Mon Sep 17 00:00:00 2001 From: arkanoider <113362043+arkanoider@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:09:33 +0100 Subject: [PATCH 3/3] Rabbit insipired unwrap removal (#382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rabbit insipired unwrap removal * Update src/app/admin_settle.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update src/app/admin_cancel.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * improving some code with rabbit * Add util unit tests * Fix send_dm() and code refactoring --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Francisco Calderón --- src/app/add_invoice.rs | 9 ++- src/app/admin_add_solver.rs | 3 +- src/app/admin_cancel.rs | 24 ++++-- src/app/admin_settle.rs | 36 +++++++-- src/app/admin_take_dispute.rs | 26 +++++-- src/app/dispute.rs | 8 +- src/app/take_buy.rs | 6 +- src/app/take_sell.rs | 2 +- src/main.rs | 29 +++---- src/scheduler.rs | 36 +++++---- src/util.rs | 143 ++++++++++++++++++++++++++++++++-- 11 files changed, 253 insertions(+), 69 deletions(-) diff --git a/src/app/add_invoice.rs b/src/app/add_invoice.rs index 2b517eb6..32c41f54 100644 --- a/src/app/add_invoice.rs +++ b/src/app/add_invoice.rs @@ -73,8 +73,13 @@ pub async fn add_invoice_action( { Ok(_) => payment_request, Err(_) => { - send_new_order_msg(Some(order.id), Action::IncorrectInvoiceAmount, None, &event.sender) - .await; + send_new_order_msg( + Some(order.id), + Action::IncorrectInvoiceAmount, + None, + &event.sender, + ) + .await; return Ok(()); } } diff --git a/src/app/admin_add_solver.rs b/src/app/admin_add_solver.rs index e2dccddf..d010bf1a 100644 --- a/src/app/admin_add_solver.rs +++ b/src/app/admin_add_solver.rs @@ -46,7 +46,8 @@ pub async fn admin_add_solver_action( let message = Message::new_dispute(None, Action::AdminAddSolver, None); let message = message.as_json()?; // Send the message - send_dm(&event.sender, message).await?; + let sender_keys = crate::util::get_keys().unwrap(); + send_dm(&event.sender, sender_keys, message).await?; Ok(()) } diff --git a/src/app/admin_cancel.rs b/src/app/admin_cancel.rs index ecaaeb6d..2028d4ea 100644 --- a/src/app/admin_cancel.rs +++ b/src/app/admin_cancel.rs @@ -3,8 +3,7 @@ use std::str::FromStr; use crate::db::{find_dispute_by_order_id, is_assigned_solver}; use crate::lightning::LndConnector; use crate::nip33::new_event; -use crate::util::{send_dm, send_new_order_msg, update_order_event}; -use crate::NOSTR_CLIENT; +use crate::util::{get_nostr_client, send_dm, send_new_order_msg, update_order_event}; use anyhow::{Error, Result}; use mostro_core::dispute::Status as DisputeStatus; @@ -59,7 +58,8 @@ pub async fn admin_cancel_action( if order.status == Status::CooperativelyCanceled.to_string() { let message = MessageKind::new(Some(order_id), Action::CooperativeCancelAccepted, None); if let Ok(message) = message.as_json() { - let _ = send_dm(&event.sender, message).await; + let sender_keys = crate::util::get_keys().unwrap(); + let _ = send_dm(&event.sender, sender_keys, message).await; } return Ok(()); } @@ -109,7 +109,14 @@ pub async fn admin_cancel_action( // nip33 kind with dispute id as identifier let event = new_event(my_keys, "", dispute_id.to_string(), tags)?; - NOSTR_CLIENT.get().unwrap().send_event(event).await?; + match get_nostr_client() { + Ok(client) => { + if let Err(e) = client.send_event(event).await { + error!("Failed to send dispute status event: {}", e); + } + } + Err(e) => error!("Failed to get Nostr client: {}", e), + } } // We publish a new replaceable kind nostr event with the status updated @@ -120,7 +127,8 @@ pub async fn admin_cancel_action( let message = Message::new_order(Some(order.id), Action::AdminCanceled, None); let message = message.as_json()?; // Message to admin - send_dm(&event.sender, message.clone()).await?; + let sender_keys = crate::util::get_keys().unwrap(); + send_dm(&event.sender, sender_keys, message.clone()).await?; let (seller_pubkey, buyer_pubkey) = match (&order.seller_pubkey, &order.buyer_pubkey) { (Some(seller), Some(buyer)) => ( @@ -130,9 +138,9 @@ pub async fn admin_cancel_action( (None, _) => return Err(Error::msg("Missing seller pubkey")), (_, None) => return Err(Error::msg("Missing buyer pubkey")), }; - - send_dm(&seller_pubkey, message.clone()).await?; - send_dm(&buyer_pubkey, message).await?; + let sender_keys = crate::util::get_keys().unwrap(); + send_dm(&seller_pubkey, sender_keys.clone(), message.clone()).await?; + send_dm(&buyer_pubkey, sender_keys, message).await?; Ok(()) } diff --git a/src/app/admin_settle.rs b/src/app/admin_settle.rs index 42717c1c..e9313830 100644 --- a/src/app/admin_settle.rs +++ b/src/app/admin_settle.rs @@ -1,8 +1,9 @@ use crate::db::{find_dispute_by_order_id, is_assigned_solver}; use crate::lightning::LndConnector; use crate::nip33::new_event; -use crate::util::{send_dm, send_new_order_msg, settle_seller_hold_invoice, update_order_event}; -use crate::NOSTR_CLIENT; +use crate::util::{ + get_nostr_client, send_dm, send_new_order_msg, settle_seller_hold_invoice, update_order_event, +}; use anyhow::{Error, Result}; use mostro_core::dispute::Status as DisputeStatus; @@ -60,7 +61,8 @@ pub async fn admin_settle_action( if order.status == Status::CooperativelyCanceled.to_string() { let message = MessageKind::new(Some(order_id), Action::CooperativeCancelAccepted, None); if let Ok(message) = message.as_json() { - let _ = send_dm(&event.sender, message).await; + let sender_keys = crate::util::get_keys().unwrap(); + let _ = send_dm(&event.sender, sender_keys, message).await; } return Ok(()); } @@ -107,18 +109,38 @@ pub async fn admin_settle_action( // nip33 kind with dispute id as identifier let event = new_event(my_keys, "", dispute_id.to_string(), tags)?; - NOSTR_CLIENT.get().unwrap().send_event(event).await?; + match get_nostr_client() { + Ok(client) => { + if let Err(e) = client.send_event(event).await { + error!("Failed to send dispute settlement event: {}", e); + } + } + Err(e) => { + error!("Failed to get Nostr client for dispute settlement: {}", e); + } + } } // We create a Message for settle let message = Message::new_order(Some(order_updated.id), Action::AdminSettled, None); let message = message.as_json()?; // Message to admin - send_dm(&event.sender, message.clone()).await?; + let sender_keys = crate::util::get_keys().unwrap(); + send_dm(&event.sender, sender_keys.clone(), message.clone()).await?; if let Some(ref seller_pubkey) = order_updated.seller_pubkey { - send_dm(&PublicKey::from_str(seller_pubkey)?, message.clone()).await?; + send_dm( + &PublicKey::from_str(seller_pubkey)?, + sender_keys.clone(), + message.clone(), + ) + .await?; } if let Some(ref buyer_pubkey) = order_updated.buyer_pubkey { - send_dm(&PublicKey::from_str(buyer_pubkey)?, message.clone()).await?; + send_dm( + &PublicKey::from_str(buyer_pubkey)?, + sender_keys, + message.clone(), + ) + .await?; } let _ = do_payment(order_updated).await; diff --git a/src/app/admin_take_dispute.rs b/src/app/admin_take_dispute.rs index c13fa127..1cd0cc1f 100644 --- a/src/app/admin_take_dispute.rs +++ b/src/app/admin_take_dispute.rs @@ -1,7 +1,6 @@ use crate::db::find_solver_pubkey; use crate::nip33::new_event; -use crate::util::{send_cant_do_msg, send_dm, send_new_order_msg}; -use crate::NOSTR_CLIENT; +use crate::util::{get_nostr_client, send_cant_do_msg, send_dm, send_new_order_msg}; use anyhow::{Error, Result}; use mostro_core::dispute::{Dispute, Status}; @@ -103,7 +102,8 @@ pub async fn admin_take_dispute_action( Some(Content::Order(new_order)), ); let message = message.as_json()?; - send_dm(&event.sender, message).await?; + let sender_keys = crate::util::get_keys().unwrap(); + send_dm(&event.sender, sender_keys, message).await?; // Now we create a message to both parties of the order // to them know who will assist them on the dispute let solver_pubkey = Peer::new(event.sender.to_hex()); @@ -127,9 +127,9 @@ pub async fn admin_take_dispute_action( (None, _) => return Err(Error::msg("Missing seller pubkey")), (_, None) => return Err(Error::msg("Missing buyer pubkey")), }; - - send_dm(&buyer_pubkey, msg_to_buyer.as_json()?).await?; - send_dm(&seller_pubkey, msg_to_seller.as_json()?).await?; + let sender_keys = crate::util::get_keys().unwrap(); + send_dm(&buyer_pubkey, sender_keys.clone(), msg_to_buyer.as_json()?).await?; + send_dm(&seller_pubkey, sender_keys, msg_to_seller.as_json()?).await?; // We create a tag to show status of the dispute let tags: Vec = vec![ Tag::custom( @@ -148,7 +148,19 @@ pub async fn admin_take_dispute_action( // nip33 kind with dispute id as identifier let event = new_event(&crate::util::get_keys()?, "", dispute_id.to_string(), tags)?; info!("Dispute event to be published: {event:#?}"); - NOSTR_CLIENT.get().unwrap().send_event(event).await?; + + let client = get_nostr_client().map_err(|e| { + info!( + "Failed to get nostr client for dispute {}: {}", + dispute_id, e + ); + e + })?; + + client.send_event(event).await.map_err(|e| { + info!("Failed to send dispute {} status event: {}", dispute_id, e); + e + })?; Ok(()) } diff --git a/src/app/dispute.rs b/src/app/dispute.rs index f8113307..7637ae54 100644 --- a/src/app/dispute.rs +++ b/src/app/dispute.rs @@ -2,8 +2,7 @@ use std::str::FromStr; use crate::db::find_dispute_by_order_id; use crate::nip33::new_event; -use crate::util::{send_cant_do_msg, send_new_order_msg}; -use crate::NOSTR_CLIENT; +use crate::util::{get_nostr_client, send_cant_do_msg, send_new_order_msg}; use anyhow::{Error, Result}; use mostro_core::dispute::Dispute; @@ -174,7 +173,10 @@ pub async fn dispute_action( // nip33 kind with dispute id as identifier let event = new_event(my_keys, "", dispute.id.to_string(), tags)?; info!("Dispute event to be published: {event:#?}"); - NOSTR_CLIENT.get().unwrap().send_event(event).await?; + + if let Ok(client) = get_nostr_client() { + let _ = client.send_event(event).await; + } Ok(()) } diff --git a/src/app/take_buy.rs b/src/app/take_buy.rs index cc4cbecf..91989d69 100644 --- a/src/app/take_buy.rs +++ b/src/app/take_buy.rs @@ -76,7 +76,7 @@ pub async fn take_buy_action( return Ok(()); } } - + // Get amount request if user requested one for range order - fiat amount will be used below if let Some(am) = get_fiat_amount_requested(&order, &msg) { order.fiat_amount = am; @@ -89,8 +89,8 @@ pub async fn take_buy_action( ) .await; return Ok(()); - } - + } + // Check market price value in sats - if order was with market price then calculate if order.amount == 0 { let (new_sats_amount, fee) = diff --git a/src/app/take_sell.rs b/src/app/take_sell.rs index 078b67ae..1113c709 100644 --- a/src/app/take_sell.rs +++ b/src/app/take_sell.rs @@ -96,7 +96,7 @@ pub async fn take_sell_action( return Ok(()); } } - + // Get amount request if user requested one for range order - fiat amount will be used below if let Some(am) = get_fiat_amount_requested(&order, &msg) { order.fiat_amount = am; diff --git a/src/main.rs b/src/main.rs index d893be77..48221876 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,12 +22,13 @@ use lightning::LndConnector; use nostr_sdk::prelude::*; use scheduler::start_scheduler; use std::env; +use std::process::exit; use std::sync::Arc; use std::sync::OnceLock; use tokio::sync::Mutex; use tracing::{error, info}; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; -use util::invoice_subscribe; +use util::{get_nostr_client, invoice_subscribe}; static MOSTRO_CONFIG: OnceLock = OnceLock::new(); static NOSTR_CLIENT: OnceLock = OnceLock::new(); @@ -70,11 +71,18 @@ async fn main() -> Result<()> { .pubkey(my_keys.public_key()) .since(Timestamp::now() - 172800); - NOSTR_CLIENT - .get() - .unwrap() - .subscribe(vec![subscription], None) - .await; + let client = match get_nostr_client() { + Ok(client) => client, + Err(e) => { + tracing::error!("Failed to initialize Nostr client. Cannot proceed: {e}"); + // Clean up any resources if needed + exit(1) + } + }; + + // Client subscription + client.subscribe(vec![subscription], None).await; + let mut ln_client = LndConnector::new().await?; if let Ok(held_invoices) = find_held_invoices(&pool).await { @@ -91,14 +99,7 @@ async fn main() -> Result<()> { // Start scheduler for tasks start_scheduler(rate_list.clone()).await; - run( - my_keys, - NOSTR_CLIENT.get().unwrap(), - &mut ln_client, - pool, - rate_list.clone(), - ) - .await + run(my_keys, client, &mut ln_client, pool, rate_list.clone()).await } #[cfg(test)] diff --git a/src/scheduler.rs b/src/scheduler.rs index 03644e75..1d270b6f 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -4,7 +4,7 @@ use crate::cli::settings::Settings; use crate::db::*; use crate::lightning::LndConnector; use crate::util; -use crate::NOSTR_CLIENT; +use crate::util::get_nostr_client; use chrono::{TimeDelta, Utc}; use mostro_core::order::{Kind, Status}; @@ -14,7 +14,7 @@ use sqlx_crud::Crud; use std::sync::Arc; use tokio::sync::Mutex; use tracing::{error, info}; -use util::{get_keys, update_order_event}; +use util::{get_keys, get_nostr_relays, update_order_event}; pub async fn start_scheduler(rate_list: Arc>>) { info!("Creating scheduler"); @@ -41,19 +41,22 @@ async fn job_relay_list() { info!("Sending Mostro relay list"); let interval = Settings::get_mostro().publish_relays_interval as u64; - let relays = NOSTR_CLIENT.get().unwrap().relays().await; - let mut relay_tags: Vec = vec![]; + if let Some(relays) = get_nostr_relays().await { + let mut relay_tags: Vec = vec![]; - for (_, r) in relays.iter() { - if r.is_connected().await { - relay_tags.push(Tag::relay_metadata(r.url(), None)) + for (_, r) in relays.iter() { + if r.is_connected().await { + relay_tags.push(Tag::relay_metadata(r.url(), None)) + } } - } - if let Ok(relay_ev) = - EventBuilder::new(NostrKind::RelayList, "", relay_tags).to_event(&mostro_pubkey) - { - let _ = NOSTR_CLIENT.get().unwrap().send_event(relay_ev).await; + if let Ok(relay_ev) = + EventBuilder::new(NostrKind::RelayList, "", relay_tags).to_event(&mostro_pubkey) + { + if let Ok(client) = get_nostr_client() { + let _ = client.send_event(relay_ev).await; + } + } } tokio::time::sleep(tokio::time::Duration::from_secs(interval)).await; } @@ -78,7 +81,10 @@ async fn job_info_event_send() { Ok(info) => info, Err(e) => return error!("{e}"), }; - let _ = NOSTR_CLIENT.get().unwrap().send_event(info_ev).await; + + if let Ok(client) = get_nostr_client() { + let _ = client.send_event(info_ev).await; + } tokio::time::sleep(tokio::time::Duration::from_secs(interval)).await; } @@ -131,8 +137,8 @@ async fn job_update_rate_events(rate_list: Arc>>) { for ev in inner_list.lock().await.iter() { // Send event to relay - if let Some(client) = NOSTR_CLIENT.get() { - match &client.send_event(ev.clone()).await { + if let Ok(client) = get_nostr_client() { + match client.send_event(ev.clone()).await { Ok(id) => { info!("Updated rate event with id {:?}", id) } diff --git a/src/util.rs b/src/util.rs index 852c466a..5ddc7dea 100644 --- a/src/util.rs +++ b/src/util.rs @@ -28,6 +28,7 @@ use tokio::sync::mpsc::channel; use tokio::sync::Mutex; // use fedimint_tonic_lnd::Client; use fedimint_tonic_lnd::lnrpc::invoice::InvoiceState; +use std::collections::HashMap; use tracing::error; use tracing::info; use uuid::Uuid; @@ -237,15 +238,20 @@ pub async fn publish_order( .map_err(|err| err.into()) } -pub async fn send_dm(receiver_pubkey: &PublicKey, content: String) -> Result<()> { - // Get mostro keys - let sender_keys = crate::util::get_keys().unwrap(); +pub async fn send_dm( + receiver_pubkey: &PublicKey, + sender_keys: Keys, + content: String, +) -> Result<()> { let event = gift_wrap(&sender_keys, *receiver_pubkey, content.clone(), None)?; info!( "Sending DM, Event ID: {} with content: {:#?}", event.id, content ); - NOSTR_CLIENT.get().unwrap().send_event(event).await?; + + if let Ok(client) = get_nostr_client() { + let _ = client.send_event(event).await; + } Ok(()) } @@ -313,8 +319,10 @@ pub async fn update_order_event(keys: &Keys, status: Status, order: &Order) -> R status.to_string() ); - if NOSTR_CLIENT.get().unwrap().send_event(event).await.is_err(){ - tracing::warn!("order id : {} is expired", order_updated.id) + if let Ok(client) = get_nostr_client() { + if client.send_event(event).await.is_err() { + tracing::warn!("order id : {} is expired", order_updated.id) + } } println!( @@ -566,7 +574,8 @@ pub async fn send_cant_do_msg( // Send message to event creator let message = Message::cant_do(order_id, content); if let Ok(message) = message.as_json() { - let _ = send_dm(destination_key, message).await; + let sender_keys = crate::util::get_keys().unwrap(); + let _ = send_dm(destination_key, sender_keys, message).await; } } @@ -579,7 +588,8 @@ pub async fn send_new_order_msg( // Send message to event creator let message = Message::new_order(order_id, action, content); if let Ok(message) = message.as_json() { - let _ = send_dm(destination_key, message).await; + let sender_keys = crate::util::get_keys().unwrap(); + let _ = send_dm(destination_key, sender_keys, message).await; } } @@ -588,6 +598,7 @@ pub fn get_fiat_amount_requested(order: &Order, msg: &Message) -> Option { // set order fiat amount to the value requested preparing for hold invoice if order.is_range_order() { if let Some(amount_buyer) = msg.get_inner_message_kind().get_amount() { + info!("amount_buyer: {amount_buyer}"); match Some(amount_buyer) <= order.max_amount && Some(amount_buyer) >= order.min_amount { true => Some(amount_buyer), false => None, @@ -600,3 +611,119 @@ pub fn get_fiat_amount_requested(order: &Order, msg: &Message) -> Option { Some(order.fiat_amount) } } + +/// Getter function with error management for nostr Client +pub fn get_nostr_client() -> Result<&'static Client> { + if let Some(client) = NOSTR_CLIENT.get() { + Ok(client) + } else { + Err(Error::msg("Client not initialized!")) + } +} + +/// Getter function with error management for nostr relays +pub async fn get_nostr_relays() -> Option> { + if let Some(client) = NOSTR_CLIENT.get() { + Some(client.relays().await) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mostro_core::message::{Message, MessageKind}; + use mostro_core::order::Order; + use std::sync::Once; + use uuid::uuid; + // Setup function to initialize common settings or data before tests + static INIT: Once = Once::new(); + + fn initialize() { + INIT.call_once(|| { + // Any initialization code goes here + }); + } + + #[test] + fn test_bytes_to_string() { + initialize(); + let bytes = vec![0xde, 0xad, 0xbe, 0xef]; + let result = bytes_to_string(&bytes); + assert_eq!(result, "deadbeef"); + } + + #[tokio::test] + async fn test_get_market_quote() { + initialize(); + // Mock the get_market_quote function's external API call + let fiat_amount = 1000; // $1000 + let fiat_code = "USD"; + let premium = 0; + + // Assuming you have a way to mock the API response + let sats = get_market_quote(&fiat_amount, fiat_code, premium) + .await + .unwrap(); + // Check that sats amount is calculated correctly + assert!(sats > 0); + } + + #[tokio::test] + async fn test_get_nostr_client_failure() { + initialize(); + // Assuming NOSTR_CLIENT is not initialized + let client = get_nostr_client(); + assert!(client.is_err()); + } + + #[tokio::test] + async fn test_get_nostr_client_success() { + initialize(); + // Mock NOSTR_CLIENT initialization + let keys = Keys::generate(); + let client = Client::new(&keys); + NOSTR_CLIENT.set(client).unwrap(); + let client_result = get_nostr_client(); + assert!(client_result.is_ok()); + } + + #[test] + fn test_bytes_to_string_empty() { + initialize(); + let bytes: Vec = vec![]; + let result = bytes_to_string(&bytes); + assert_eq!(result, ""); + } + + #[tokio::test] + async fn test_send_dm() { + initialize(); + // Mock the send_dm function + let receiver_pubkey = Keys::generate().public_key(); + let content = "Test message".to_string(); + let sender_keys = Keys::generate(); + let result = send_dm(&receiver_pubkey, sender_keys, content).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_get_fiat_amount_requested() { + initialize(); + let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"); + let order = Order { + amount: 1000, + min_amount: Some(500), + max_amount: Some(2000), + ..Default::default() + }; + let message = Message::Order(MessageKind::new( + Some(uuid), + Action::TakeSell, + Some(Content::Amount(order.amount)), + )); + let amount = get_fiat_amount_requested(&order, &message); + assert_eq!(amount, Some(1000)); + } +}