#[cfg(test)]
mod tests {
use rust_blocktank_client::BlocktankError;
use rust_blocktank_client::{
BlocktankClient, BtOrderState2, CreateCjitOptions, CreateOrderOptions,
};
use std::env;
const STAGING_SERVER: &str = "https://api.stag0.blocktank.to/blocktank/api/v2";
fn create_test_client() -> BlocktankClient {
let base_url =
env::var("BLOCKTANK_TEST_URL").unwrap_or_else(|_| STAGING_SERVER.to_string());
BlocktankClient::new(Some(&base_url)).expect("Failed to create BlocktankClient")
}
async fn create_test_order(
client: &BlocktankClient,
balance: u64,
weeks: u32,
) -> rust_blocktank_client::IBtOrder {
let options = Some(CreateOrderOptions {
client_balance_sat: 0,
..Default::default()
});
client
.create_order(balance, weeks, options)
.await
.expect("Failed to create test order")
}
#[tokio::test]
async fn test_get_info() {
let client = create_test_client();
let result = client.get_info().await;
match result {
Ok(info) => {
println!("Service Info: {:?}", info);
assert!(!info.nodes.is_empty(), "Expected at least one node");
assert!(
info.options.min_channel_size_sat > 0,
"Minimum channel size should be greater than 0"
);
assert!(
info.options.min_expiry_weeks > 0,
"Minimum expiry weeks should be greater than 0"
);
assert!(
info.options.max_expiry_weeks >= info.options.min_expiry_weeks,
"Max expiry weeks should be greater than or equal to min expiry weeks"
);
}
Err(err) => panic!("API call to get_info failed: {:?}", err),
}
}
#[tokio::test]
async fn test_estimate_order_fee() {
let client = create_test_client();
let lsp_balance_sat: u64 = 100_000;
let channel_expiry_weeks: u32 = 4;
let options = Some(CreateOrderOptions {
client_balance_sat: 0,
..Default::default()
});
let result = client
.estimate_order_fee(lsp_balance_sat, channel_expiry_weeks, options)
.await;
match result {
Ok(fee_estimate) => {
println!("Fee estimate: {:?}", fee_estimate);
assert!(fee_estimate.fee_sat > 0, "Fee should be greater than 0");
assert!(
fee_estimate.min_0_conf_tx_fee.sat_per_vbyte > 0.0,
"Minimum fee rate should be greater than 0"
);
}
Err(err) => panic!("API call to estimate_order_fee failed: {:?}", err),
}
}
#[tokio::test]
async fn test_estimate_order_fee_full() {
let client = create_test_client();
let lsp_balance_sat: u64 = 100_000;
let channel_expiry_weeks: u32 = 4;
let options = Some(CreateOrderOptions {
client_balance_sat: 0,
..Default::default()
});
let result = client
.estimate_order_fee_full(lsp_balance_sat, channel_expiry_weeks, options)
.await;
match result {
Ok(fee_estimate) => {
println!("Full fee estimate: {:?}", fee_estimate);
assert!(
fee_estimate.service_fee_sat > 0,
"Service fee should be greater than 0"
);
assert!(
fee_estimate.network_fee_sat > 0,
"Network fee should be greater than 0"
);
assert_eq!(
fee_estimate.fee_sat,
fee_estimate.service_fee_sat + fee_estimate.network_fee_sat,
"Total fee should equal sum of service fee and network fee"
);
}
Err(err) => panic!("API call to estimate_order_fee_full failed: {:?}", err),
}
}
#[tokio::test]
async fn test_create_order() {
let client = create_test_client();
let lsp_balance_sat: u64 = 100_000;
let channel_expiry_weeks: u32 = 4;
let zero_conf = true;
let options = Some(CreateOrderOptions {
client_balance_sat: 0,
zero_conf,
..Default::default()
});
let result = client
.create_order(lsp_balance_sat, channel_expiry_weeks, options)
.await;
match result {
Ok(order) => {
println!("Created order: {:?}", order);
assert!(!order.id.is_empty(), "Order ID should not be empty");
assert_eq!(
order.lsp_balance_sat, lsp_balance_sat,
"LSP balance should match requested amount"
);
assert_eq!(
order.channel_expiry_weeks, channel_expiry_weeks,
"Channel expiry weeks should match requested value"
);
assert_eq!(
order.state2,
Some(BtOrderState2::Created),
"Initial order state should be Created"
);
if let Some(payment) = &order.payment {
if let Some(bolt11) = &payment.bolt11_invoice {
assert!(
bolt11.request.starts_with("lnbc"),
"Should have a valid lightning invoice"
);
}
if let Some(onchain) = &payment.onchain {
assert!(
!onchain.address.is_empty(),
"Should have an onchain address"
);
}
}
if let Some(lsp_node) = &order.lsp_node {
assert!(
!lsp_node.pubkey.is_empty(),
"LSP node should have a pubkey"
);
assert!(
!lsp_node.connection_strings.is_empty(),
"LSP node should have connection strings"
);
}
let _is_zero_conf: bool = order.zero_conf;
assert!(
!order.created_at.is_empty(),
"Created timestamp should be set"
);
assert!(
!order.order_expires_at.is_empty(),
"Expiry timestamp should be set"
);
}
Err(err) => panic!("API call to create_order failed: {:?}", err),
}
}
#[tokio::test]
async fn test_error_zero_balance() {
let client = create_test_client();
let lsp_balance_sat: u64 = 0; let channel_expiry_weeks: u32 = 4;
let options = Some(CreateOrderOptions {
client_balance_sat: 0,
..Default::default()
});
let result = client
.create_order(lsp_balance_sat, channel_expiry_weeks, options)
.await;
match result {
Ok(_) => panic!("Expected an error for zero balance, but request succeeded"),
Err(err) => {
println!("Expected error received: {:?}", err);
match err {
BlocktankError::InvalidParameter { message } => {
assert!(
message.contains("lsp_balance_sat"),
"Error message should mention lsp_balance_sat parameter"
);
}
_ => panic!("Unexpected error type: {:?}", err),
}
}
}
}
#[tokio::test]
async fn test_get_order() {
let client = create_test_client();
let created_order = create_test_order(&client, 100_000, 4).await;
let result = client.get_order(&created_order.id).await;
match result {
Ok(order) => {
println!("Retrieved order: {:?}", order);
assert_eq!(order.id, created_order.id, "Order IDs should match");
assert_eq!(
order.lsp_balance_sat, created_order.lsp_balance_sat,
"LSP balance should match"
);
assert_eq!(
order.channel_expiry_weeks, created_order.channel_expiry_weeks,
"Channel expiry weeks should match"
);
assert_eq!(
order.state2, created_order.state2,
"Order state should match"
);
}
Err(err) => panic!("API call to get_order failed: {:?}", err),
}
}
#[tokio::test]
async fn test_get_nonexistent_order() {
let client = create_test_client();
let fake_id = "nonexistent_order_id_12345";
let result = client.get_order(fake_id).await;
assert!(result.is_err(), "Expected error for non-existent order ID");
println!("Expected error received: {:?}", result.err());
}
#[tokio::test]
async fn test_get_orders() {
let client = create_test_client();
let created_order = create_test_order(&client, 100_000, 4).await;
let order_ids = vec![created_order.id.clone()];
let result = client.get_orders(&order_ids).await;
match result {
Ok(orders) => {
println!("Retrieved orders: {:?}", orders);
assert_eq!(orders.len(), 1, "Should have retrieved exactly one order");
let retrieved_order = &orders[0];
assert_eq!(
retrieved_order.id, created_order.id,
"Order IDs should match"
);
assert_eq!(
retrieved_order.lsp_balance_sat, created_order.lsp_balance_sat,
"LSP balance should match"
);
assert_eq!(
retrieved_order.channel_expiry_weeks, created_order.channel_expiry_weeks,
"Channel expiry weeks should match"
);
}
Err(err) => panic!("API call to get_orders failed: {:?}", err),
}
}
#[tokio::test]
async fn test_get_orders_multiple() {
let client = create_test_client();
let order_configs = vec![
(100_000u64, 4u32), (150_000u64, 6u32), ];
let mut created_order_ids = Vec::new();
let mut created_orders = Vec::new();
for (balance, weeks) in order_configs {
let order = create_test_order(&client, balance, weeks).await;
created_order_ids.push(order.id.clone());
created_orders.push(order);
}
let result = client.get_orders(&created_order_ids).await;
match result {
Ok(orders) => {
println!("Retrieved orders: {:?}", orders);
assert_eq!(
orders.len(),
created_orders.len(),
"Should have retrieved all created orders"
);
for (created, retrieved) in created_orders.iter().zip(orders.iter()) {
assert_eq!(retrieved.id, created.id, "Order IDs should match");
assert_eq!(
retrieved.lsp_balance_sat, created.lsp_balance_sat,
"LSP balance should match"
);
assert_eq!(
retrieved.channel_expiry_weeks, created.channel_expiry_weeks,
"Channel expiry weeks should match"
);
}
}
Err(err) => panic!("API call to get_orders failed: {:?}", err),
}
}
#[tokio::test]
async fn test_get_min_zero_conf_tx_fee() {
let client = create_test_client();
let created_order = create_test_order(&client, 100_000, 4).await;
let result = client.get_min_zero_conf_tx_fee(&created_order.id).await;
match result {
Ok(fee_window) => {
println!("Min 0-conf tx fee window: {:?}", fee_window);
assert!(
fee_window.sat_per_vbyte > 0.0,
"Minimum fee rate should be greater than 0"
);
assert!(
!fee_window.validity_ends_at.is_empty(),
"Validity end timestamp should be set"
);
}
Err(err) => {
println!("Error in get_min_zero_conf_tx_fee: {:?}", err);
}
}
}
#[tokio::test]
async fn test_create_cjit_entry() {
let client = create_test_client();
let info = client.get_info().await.expect("Failed to get service info");
let node = info.nodes.first().expect("Expected at least one node");
let node_id = &node.pubkey;
let channel_size_sat: u64 = 100_000;
let invoice_sat: u64 = 10_000;
let invoice_description = "Test CJIT entry";
let channel_expiry_weeks: u32 = 4;
let options = Some(CreateCjitOptions {
source: Some("test".to_string()),
..Default::default()
});
let result = client
.create_cjit_entry(
channel_size_sat,
invoice_sat,
invoice_description,
node_id,
channel_expiry_weeks,
options,
)
.await;
match result {
Ok(cjit_entry) => {
println!("Created CJIT entry: {:?}", cjit_entry);
assert!(
!cjit_entry.id.is_empty(),
"CJIT entry ID should not be empty"
);
assert_eq!(
cjit_entry.channel_size_sat, channel_size_sat,
"Channel size should match requested amount"
);
assert_eq!(
cjit_entry.node_id,
node_id.clone(),
"Node ID should match requested value"
);
assert!(
!cjit_entry.invoice.request.is_empty(),
"Invoice request should not be empty"
);
}
Err(err) => {
println!(
"Error in create_cjit_entry (this may be expected in staging): {:?}",
err
);
}
}
}
#[tokio::test]
async fn test_workflow() {
let client = create_test_client();
let _info = client.get_info().await.expect("Failed to get service info");
println!("Service info retrieved");
let lsp_balance_sat: u64 = 100_000;
let channel_expiry_weeks: u32 = 4;
let fee_estimate = client
.estimate_order_fee_full(lsp_balance_sat, channel_expiry_weeks, None)
.await
.expect("Failed to estimate fees");
println!(
"Fee estimate: service={}, network={}, total={}",
fee_estimate.service_fee_sat, fee_estimate.network_fee_sat, fee_estimate.fee_sat
);
let use_zero_conf = true;
let options = Some(CreateOrderOptions {
client_balance_sat: 0,
zero_conf: use_zero_conf,
..Default::default()
});
let order = client
.create_order(lsp_balance_sat, channel_expiry_weeks, options)
.await
.expect("Failed to create order");
println!("Order created with ID: {}", order.id);
let retrieved_order = client
.get_order(&order.id)
.await
.expect("Failed to retrieve order");
assert_eq!(
retrieved_order.id, order.id,
"Retrieved order should match created order"
);
println!("Successfully retrieved order details");
let _ = client.get_min_zero_conf_tx_fee(&order.id).await;
println!("Complete workflow test passed successfully");
}
#[tokio::test]
async fn test_regtest_mine() {
let client = create_test_client();
let result = client.regtest_mine(Some(1)).await;
match result {
Ok(_) => {
println!("Successfully mined 1 block");
}
Err(err) => {
let err_string = err.to_string();
if err_string.contains("not in regtest mode")
|| err_string.contains("400 Bad Request")
{
println!("Expected error in staging environment: {}", err);
} else {
panic!(
"API call to regtest_mine failed with unexpected error: {:?}",
err
);
}
}
}
}
}