Files
cdk/crates/cdk-integration-tests/tests/nutshell_wallet.rs
thesimplekid f4c857c3e7 Nutshell wallet (#695)
* chore: Add nutshell wallet integration test script

feat: Add MINT_URL configuration for docker container in nutshell wallet integration test script

chore: Make nutshell_wallet_itest.sh executable

fix: Update MINT_URL to use host.docker.internal for Docker container access

feat: Add Docker container startup for Cashu daemon in wallet integration test script

chore: Update Docker image to use Docker Hub repository

feat: Add cleanup trap to stop Docker container and unset variables

feat: Add initial test stub for nutshell wallet mint functionality

test: Add Cashu wallet mint integration test

feat: Add just command for nutshell wallet integration test

refactor: Organize imports and improve code formatting in nutshell wallet test

fix: Update Cashu Docker image and test URL for correct container access

fix: Update Docker container name and image for Cashu wallet test script

fix: Handle Docker container name conflict in nutshell wallet integration test

fix: Update Docker image to cashubtc/nutshell:latest in wallet integration test script

feat: Add support for running separate Nutshell mint and wallet containers

feat: Update Nutshell mint and wallet container configurations for integration testing

fix: Update wallet port and container configuration in integration test script

chore: Export MINT_URL and WALLET_URL as environment variables

fix: Update invoice creation and state checking in nutshell wallet test

fix: Update MINT_URL to use host.docker.internal for container access

fix: Update nutshell wallet mint test to handle invoice payment state

refactor: Improve Nutshell wallet test with better error handling and robustness

refactor: Improve code formatting and logging in nutshell wallet test

refactor: Replace anyhow with expect for error handling in Nutshell wallet test

refactor: Simplify error handling in Nutshell wallet mint test

refactor: Replace `?` with `expect()` in Nutshell wallet test

refactor: Simplify nutshell wallet test by removing unused code and improving error handling

refactor: Extract minting and balance helper functions in nutshell wallet test

feat: Add test for Nutshell wallet token swap functionality

fix: Trim quotes from token in nutshell wallet swap test

refactor: Remove debug print statements and improve code readability

refactor: Improve test_nutshell_wallet_melt with payment state checking and balance verification

refactor: Update Nutshell wallet integration test script configuration

feat: Extract common mint startup function into shared script

refactor: Simplify nutshell wallet integration test script and improve startup process

feat: Add mintd process termination and test status capture in integration test script

refactor: Capitalize env vars and ensure comprehensive cleanup in trap

feat: nutshell wallet test

* ci: Add test step for Nutshell wallet integration tests

* ci: Split Nutshell integration tests into separate jobs

* feat: nutshell wallet test

* ci: Add Docker setup and increase timeout for Nutshell wallet integration tests

* chore: Improve Nutshell wallet integration test script robustness

* fix: Remove -i flag from Nix develop and improve Docker accessibility check

* fix: payment processor

* fix: wallet tests

* feat: Add integration shell with Docker and Rust stable for testing

* ci: Simplify Nutshell wallet integration test workflow and script

* fix: Improve mintd endpoint detection and error handling in integration test script

* fix: wallet tests
2025-03-30 13:08:00 +01:00

252 lines
7.8 KiB
Rust

use std::time::Duration;
use cdk_fake_wallet::create_fake_invoice;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tokio::time::sleep;
/// Response from the invoice creation endpoint
#[derive(Debug, Serialize, Deserialize)]
struct InvoiceResponse {
payment_request: String,
checking_id: Option<String>,
}
/// Maximum number of attempts to check invoice payment status
const MAX_PAYMENT_CHECK_ATTEMPTS: u8 = 20;
/// Delay between payment status checks in milliseconds
const PAYMENT_CHECK_DELAY_MS: u64 = 500;
/// Default test amount in satoshis
const DEFAULT_TEST_AMOUNT: u64 = 10000;
/// Helper function to mint tokens via Lightning invoice
async fn mint_tokens(base_url: &str, amount: u64) -> String {
let client = Client::new();
// Create an invoice for the specified amount
let invoice_url = format!("{}/lightning/create_invoice?amount={}", base_url, amount);
let invoice_response = client
.post(&invoice_url)
.send()
.await
.expect("Failed to send invoice creation request")
.json::<InvoiceResponse>()
.await
.expect("Failed to parse invoice response");
println!("Created invoice: {}", invoice_response.payment_request);
invoice_response.payment_request
}
/// Helper function to wait for payment confirmation
async fn wait_for_payment_confirmation(base_url: &str, payment_request: &str) {
let client = Client::new();
let check_url = format!(
"{}/lightning/invoice_state?payment_request={}",
base_url, payment_request
);
let mut payment_confirmed = false;
for attempt in 1..=MAX_PAYMENT_CHECK_ATTEMPTS {
println!(
"Checking invoice state (attempt {}/{})...",
attempt, MAX_PAYMENT_CHECK_ATTEMPTS
);
let response = client
.get(&check_url)
.send()
.await
.expect("Failed to send payment check request");
if response.status().is_success() {
let state: Value = response
.json()
.await
.expect("Failed to parse payment state response");
println!("Payment state: {:?}", state);
if let Some(result) = state.get("result") {
if result == 1 {
payment_confirmed = true;
break;
}
}
} else {
println!("Failed to check payment state: {}", response.status());
}
sleep(Duration::from_millis(PAYMENT_CHECK_DELAY_MS)).await;
}
if !payment_confirmed {
panic!("Payment not confirmed after maximum attempts");
}
}
/// Helper function to get the current wallet balance
async fn get_wallet_balance(base_url: &str) -> u64 {
let client = Client::new();
let balance_url = format!("{}/balance", base_url);
let balance_response = client
.get(&balance_url)
.send()
.await
.expect("Failed to send balance request");
let balance: Value = balance_response
.json()
.await
.expect("Failed to parse balance response");
println!("Wallet balance: {:?}", balance);
balance["balance"]
.as_u64()
.expect("Could not parse balance as u64")
}
/// Test the Nutshell wallet's ability to mint tokens from a Lightning invoice
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_nutshell_wallet_mint() {
// Get the wallet URL from environment variable
let base_url = std::env::var("WALLET_URL").expect("Wallet url is not set");
// Step 1: Create an invoice and mint tokens
let amount = DEFAULT_TEST_AMOUNT;
let payment_request = mint_tokens(&base_url, amount).await;
// Step 2: Wait for the invoice to be paid
wait_for_payment_confirmation(&base_url, &payment_request).await;
// Step 3: Check the wallet balance
let available_balance = get_wallet_balance(&base_url).await;
// Verify the balance is at least the amount we minted
assert!(
available_balance >= amount,
"Balance should be at least {} but was {}",
amount,
available_balance
);
}
/// Test the Nutshell wallet's ability to mint tokens from a Lightning invoice
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_nutshell_wallet_swap() {
// Get the wallet URL from environment variable
let base_url = std::env::var("WALLET_URL").expect("Wallet url is not set");
// Step 1: Create an invoice and mint tokens
let amount = DEFAULT_TEST_AMOUNT;
let payment_request = mint_tokens(&base_url, amount).await;
// Step 2: Wait for the invoice to be paid
wait_for_payment_confirmation(&base_url, &payment_request).await;
let send_amount = 100;
let send_url = format!("{}/send?amount={}", base_url, send_amount);
let client = Client::new();
let response: Value = client
.post(&send_url)
.send()
.await
.expect("Failed to send payment check request")
.json()
.await
.expect("Valid json");
// Extract the token and remove the surrounding quotes
let token_with_quotes = response
.get("token")
.expect("Missing token")
.as_str()
.expect("Token is not a string");
let token = token_with_quotes.trim_matches('"');
let receive_url = format!("{}/receive?token={}", base_url, token);
let response: Value = client
.post(&receive_url)
.send()
.await
.expect("Failed to receive request")
.json()
.await
.expect("Valid json");
let balance = response
.get("balance")
.expect("Bal in response")
.as_u64()
.expect("Valid num");
let initial_balance = response
.get("initial_balance")
.expect("Bal in response")
.as_u64()
.expect("Valid num");
let token_received = balance - initial_balance;
assert_eq!(token_received, send_amount);
}
/// Test the Nutshell wallet's ability to melt tokens to pay a Lightning invoice
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_nutshell_wallet_melt() {
// Get the wallet URL from environment variable
let base_url = std::env::var("WALLET_URL").expect("Wallet url is not set");
// Step 1: Create an invoice and mint tokens
let amount = DEFAULT_TEST_AMOUNT;
let payment_request = mint_tokens(&base_url, amount).await;
// Step 2: Wait for the invoice to be paid
wait_for_payment_confirmation(&base_url, &payment_request).await;
// Get initial balance
let initial_balance = get_wallet_balance(&base_url).await;
println!("Initial balance: {}", initial_balance);
// Step 3: Create a fake invoice to pay
let payment_amount = 1000; // 1000 sats
let fake_invoice = create_fake_invoice(payment_amount, "Test payment".to_string());
let pay_url = format!("{}/lightning/pay_invoice?bolt11={}", base_url, fake_invoice);
let client = Client::new();
// Step 4: Pay the invoice
let _response: Value = client
.post(&pay_url)
.send()
.await
.expect("Failed to send pay request")
.json()
.await
.expect("Failed to parse pay response");
let final_balance = get_wallet_balance(&base_url).await;
println!("Final balance: {}", final_balance);
assert!(
initial_balance > final_balance,
"Balance should decrease after payment"
);
let balance_difference = initial_balance - final_balance;
println!("Balance decreased by: {}", balance_difference);
// The balance difference should be at least the payment amount
assert!(
balance_difference >= (payment_amount / 1000),
"Balance should decrease by at least the payment amount ({}) but decreased by {}",
payment_amount,
balance_difference
);
}