mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-20 22:24:54 +01:00
feat(tests): integration test
This commit is contained in:
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -36,6 +36,7 @@ jobs:
|
|||||||
-p cdk-cln,
|
-p cdk-cln,
|
||||||
-p cdk-fake-wallet,
|
-p cdk-fake-wallet,
|
||||||
-p cdk-strike,
|
-p cdk-strike,
|
||||||
|
-p cdk-integration-tests,
|
||||||
--bin cdk-cli,
|
--bin cdk-cli,
|
||||||
--bin cdk-mintd,
|
--bin cdk-mintd,
|
||||||
--examples
|
--examples
|
||||||
|
|||||||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@@ -19,6 +19,7 @@ jobs:
|
|||||||
build-args:
|
build-args:
|
||||||
[
|
[
|
||||||
-p cdk,
|
-p cdk,
|
||||||
|
-p cdk-integration-tests,
|
||||||
]
|
]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Crate
|
- name: Checkout Crate
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ futures = { version = "0.3.28", default-feature = false }
|
|||||||
web-sys = { version = "0.3.69", default-features = false, features = ["console"] }
|
web-sys = { version = "0.3.69", default-features = false, features = ["console"] }
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
lightning-invoice = { version = "0.31", features = ["serde"] }
|
lightning-invoice = { version = "0.31", features = ["serde"] }
|
||||||
|
tower-http = { version = "0.5.2", features = ["cors"] }
|
||||||
home = "0.5.9"
|
home = "0.5.9"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
url = "2.3"
|
url = "2.3"
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ async-trait.workspace = true
|
|||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
cdk = { workspace = true, default-features = false, features = ["mint"] }
|
cdk = { workspace = true, default-features = false, features = ["mint"] }
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tower-http = { version = "0.5.2", features = ["cors"] }
|
tower-http.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
|||||||
50
crates/cdk-integration-tests/Cargo.toml
Normal file
50
crates/cdk-integration-tests/Cargo.toml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
[package]
|
||||||
|
name = "cdk-integration-tests"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["CDK Developers"]
|
||||||
|
description = "Core Cashu Development Kit library implementing the Cashu protocol"
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
rust-version.workspace = true # MSRV
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
|
||||||
|
[features]
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
|
bip39 = { workspace = true, features = ["rand"] }
|
||||||
|
anyhow.workspace = true
|
||||||
|
cdk = { workspace = true, features = ["mint", "wallet"] }
|
||||||
|
cdk-axum.workspace = true
|
||||||
|
cdk-fake-wallet.workspace = true
|
||||||
|
tower-http.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
tokio = { workspace = true, features = [
|
||||||
|
"rt-multi-thread",
|
||||||
|
"time",
|
||||||
|
"macros",
|
||||||
|
"sync",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
tokio = { workspace = true, features = ["rt", "macros", "sync", "time"] }
|
||||||
|
getrandom = { version = "0.2", features = ["js"] }
|
||||||
|
instant = { version = "0.1", features = ["wasm-bindgen", "inaccurate"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
axum.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
|
bip39 = { workspace = true, features = ["rand"] }
|
||||||
|
anyhow.workspace = true
|
||||||
|
cdk = { workspace = true, features = ["mint", "wallet"] }
|
||||||
|
cdk-axum.workspace = true
|
||||||
|
cdk-fake-wallet.workspace = true
|
||||||
|
tower-http.workspace = true
|
||||||
|
# cdk-redb.workspace = true
|
||||||
|
# cdk-sqlite.workspace = true
|
||||||
164
crates/cdk-integration-tests/src/lib.rs
Normal file
164
crates/cdk-integration-tests/src/lib.rs
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::Router;
|
||||||
|
use bip39::Mnemonic;
|
||||||
|
use cdk::amount::{Amount, SplitTarget};
|
||||||
|
use cdk::cdk_database::mint_memory::MintMemoryDatabase;
|
||||||
|
use cdk::cdk_lightning::{MintLightning, MintMeltSettings};
|
||||||
|
use cdk::mint::FeeReserve;
|
||||||
|
use cdk::nuts::{CurrencyUnit, MintInfo, MintQuoteState, Nuts, PaymentMethod};
|
||||||
|
use cdk::{Mint, Wallet};
|
||||||
|
use cdk_axum::LnKey;
|
||||||
|
use cdk_fake_wallet::FakeWallet;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
use tower_http::cors::CorsLayer;
|
||||||
|
|
||||||
|
pub const MINT_URL: &str = "http://127.0.0.1:8088";
|
||||||
|
const LISTEN_ADDR: &str = "127.0.0.1";
|
||||||
|
const LISTEN_PORT: u16 = 8088;
|
||||||
|
|
||||||
|
pub fn create_backends_fake_wallet(
|
||||||
|
) -> HashMap<LnKey, Arc<dyn MintLightning<Err = cdk::cdk_lightning::Error> + Sync + Send>> {
|
||||||
|
let fee_reserve = FeeReserve {
|
||||||
|
min_fee_reserve: 1.into(),
|
||||||
|
percent_fee_reserve: 1.0,
|
||||||
|
};
|
||||||
|
let mut ln_backends: HashMap<
|
||||||
|
LnKey,
|
||||||
|
Arc<dyn MintLightning<Err = cdk::cdk_lightning::Error> + Sync + Send>,
|
||||||
|
> = HashMap::new();
|
||||||
|
let ln_key = LnKey::new(CurrencyUnit::Sat, PaymentMethod::Bolt11);
|
||||||
|
|
||||||
|
let wallet = Arc::new(FakeWallet::new(
|
||||||
|
fee_reserve.clone(),
|
||||||
|
MintMeltSettings::default(),
|
||||||
|
MintMeltSettings::default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
ln_backends.insert(ln_key, wallet.clone());
|
||||||
|
|
||||||
|
ln_backends
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_mint(
|
||||||
|
ln_backends: HashMap<
|
||||||
|
LnKey,
|
||||||
|
Arc<dyn MintLightning<Err = cdk::cdk_lightning::Error> + Sync + Send>,
|
||||||
|
>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let nuts = Nuts::new()
|
||||||
|
.nut07(true)
|
||||||
|
.nut08(true)
|
||||||
|
.nut09(true)
|
||||||
|
.nut10(true)
|
||||||
|
.nut11(true)
|
||||||
|
.nut12(true)
|
||||||
|
.nut14(true);
|
||||||
|
|
||||||
|
let mint_info = MintInfo::new().nuts(nuts);
|
||||||
|
|
||||||
|
let mnemonic = Mnemonic::generate(12)?;
|
||||||
|
|
||||||
|
let mut supported_units = HashMap::new();
|
||||||
|
supported_units.insert(CurrencyUnit::Sat, (0, 64));
|
||||||
|
|
||||||
|
let mint = Mint::new(
|
||||||
|
MINT_URL,
|
||||||
|
&mnemonic.to_seed_normalized(""),
|
||||||
|
mint_info,
|
||||||
|
Arc::new(MintMemoryDatabase::default()),
|
||||||
|
supported_units,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let quote_ttl = 2000;
|
||||||
|
|
||||||
|
let mint_arc = Arc::new(mint);
|
||||||
|
|
||||||
|
let v1_service = cdk_axum::create_mint_router(
|
||||||
|
MINT_URL,
|
||||||
|
Arc::clone(&mint_arc),
|
||||||
|
ln_backends.clone(),
|
||||||
|
quote_ttl,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mint_service = Router::new()
|
||||||
|
.merge(v1_service)
|
||||||
|
.layer(CorsLayer::permissive());
|
||||||
|
|
||||||
|
let mint = Arc::clone(&mint_arc);
|
||||||
|
|
||||||
|
for wallet in ln_backends.values() {
|
||||||
|
let wallet_clone = Arc::clone(wallet);
|
||||||
|
let mint = Arc::clone(&mint);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match wallet_clone.wait_any_invoice().await {
|
||||||
|
Ok(mut stream) => {
|
||||||
|
while let Some(request_lookup_id) = stream.next().await {
|
||||||
|
if let Err(err) =
|
||||||
|
handle_paid_invoice(Arc::clone(&mint), &request_lookup_id).await
|
||||||
|
{
|
||||||
|
// nosemgrep: direct-panic
|
||||||
|
panic!("{:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// nosemgrep: direct-panic
|
||||||
|
panic!("Could not get invoice stream: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let listener =
|
||||||
|
tokio::net::TcpListener::bind(format!("{}:{}", LISTEN_ADDR, LISTEN_PORT)).await?;
|
||||||
|
|
||||||
|
println!("Starting mint");
|
||||||
|
axum::serve(listener, mint_service).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update mint quote when called for a paid invoice
|
||||||
|
async fn handle_paid_invoice(mint: Arc<Mint>, request_lookup_id: &str) -> Result<()> {
|
||||||
|
println!("Invoice with lookup id paid: {}", request_lookup_id);
|
||||||
|
if let Ok(Some(mint_quote)) = mint
|
||||||
|
.localstore
|
||||||
|
.get_mint_quote_by_request_lookup_id(request_lookup_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!(
|
||||||
|
"Quote {} paid by lookup id {}",
|
||||||
|
mint_quote.id, request_lookup_id
|
||||||
|
);
|
||||||
|
mint.localstore
|
||||||
|
.update_mint_quote_state(&mint_quote.id, cdk::nuts::MintQuoteState::Paid)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wallet_mint(wallet: Arc<Wallet>, amount: Amount) -> Result<()> {
|
||||||
|
let quote = wallet.mint_quote(amount).await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let status = wallet.mint_quote_state("e.id).await?;
|
||||||
|
|
||||||
|
if status.state == MintQuoteState::Paid {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
println!("{:?}", status);
|
||||||
|
|
||||||
|
sleep(Duration::from_secs(2)).await;
|
||||||
|
}
|
||||||
|
let receive_amount = wallet.mint("e.id, SplitTarget::default(), None).await?;
|
||||||
|
|
||||||
|
println!("Minted: {}", receive_amount);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
80
crates/cdk-integration-tests/tests/mint.rs
Normal file
80
crates/cdk-integration-tests/tests/mint.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
//! Mint integration tests
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use bip39::Mnemonic;
|
||||||
|
use cdk::amount::SplitTarget;
|
||||||
|
use cdk::cdk_database::WalletMemoryDatabase;
|
||||||
|
use cdk::nuts::CurrencyUnit;
|
||||||
|
use cdk::wallet::error::Error;
|
||||||
|
use cdk::wallet::SendKind;
|
||||||
|
use cdk::Wallet;
|
||||||
|
use cdk_integration_tests::{create_backends_fake_wallet, start_mint, wallet_mint, MINT_URL};
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||||
|
pub async fn test_mint_double_receive() -> Result<()> {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let ln_backends = create_backends_fake_wallet();
|
||||||
|
|
||||||
|
start_mint(ln_backends).await.expect("Could not start mint")
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
|
||||||
|
let mnemonic = Mnemonic::generate(12)?;
|
||||||
|
|
||||||
|
let wallet = Wallet::new(
|
||||||
|
&MINT_URL,
|
||||||
|
CurrencyUnit::Sat,
|
||||||
|
Arc::new(WalletMemoryDatabase::default()),
|
||||||
|
&mnemonic.to_seed_normalized(""),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let wallet = Arc::new(wallet);
|
||||||
|
|
||||||
|
wallet_mint(Arc::clone(&wallet), 100.into()).await?;
|
||||||
|
|
||||||
|
let token = wallet
|
||||||
|
.send(
|
||||||
|
10.into(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
&SplitTarget::default(),
|
||||||
|
&SendKind::default(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mnemonic = Mnemonic::generate(12)?;
|
||||||
|
|
||||||
|
let wallet_two = Wallet::new(
|
||||||
|
&MINT_URL,
|
||||||
|
CurrencyUnit::Sat,
|
||||||
|
Arc::new(WalletMemoryDatabase::default()),
|
||||||
|
&mnemonic.to_seed_normalized(""),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let rec = wallet_two
|
||||||
|
.receive(&token.to_string(), SplitTarget::default(), &[], &[])
|
||||||
|
.await?;
|
||||||
|
println!("Received: {}", rec);
|
||||||
|
|
||||||
|
// Attempt to receive again
|
||||||
|
if let Err(err) = wallet
|
||||||
|
.receive(&token.to_string(), SplitTarget::default(), &[], &[])
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
match err {
|
||||||
|
Error::TokenAlreadySpent => (),
|
||||||
|
_ => {
|
||||||
|
bail!("Expected an already spent error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
82
crates/cdk-integration-tests/tests/p2pk.rs
Normal file
82
crates/cdk-integration-tests/tests/p2pk.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use bip39::Mnemonic;
|
||||||
|
use cdk::amount::SplitTarget;
|
||||||
|
use cdk::cdk_database::WalletMemoryDatabase;
|
||||||
|
use cdk::nuts::{CurrencyUnit, SecretKey, SpendingConditions};
|
||||||
|
use cdk::wallet::SendKind;
|
||||||
|
use cdk::{Amount, Wallet};
|
||||||
|
use cdk_integration_tests::{create_backends_fake_wallet, start_mint, wallet_mint, MINT_URL};
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||||
|
pub async fn test_p2pk_swap() -> Result<()> {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let ln_backends = create_backends_fake_wallet();
|
||||||
|
|
||||||
|
start_mint(ln_backends).await.expect("Could not start mint")
|
||||||
|
});
|
||||||
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
|
||||||
|
let mnemonic = Mnemonic::generate(12)?;
|
||||||
|
|
||||||
|
let wallet = Wallet::new(
|
||||||
|
&MINT_URL,
|
||||||
|
CurrencyUnit::Sat,
|
||||||
|
Arc::new(WalletMemoryDatabase::default()),
|
||||||
|
&mnemonic.to_seed_normalized(""),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let wallet = Arc::new(wallet);
|
||||||
|
|
||||||
|
// Mint 100 sats for the wallet
|
||||||
|
wallet_mint(Arc::clone(&wallet), 100.into()).await?;
|
||||||
|
|
||||||
|
let secret = SecretKey::generate();
|
||||||
|
|
||||||
|
let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
|
||||||
|
|
||||||
|
let amount = Amount::from(10);
|
||||||
|
|
||||||
|
let token = wallet
|
||||||
|
.send(
|
||||||
|
amount,
|
||||||
|
None,
|
||||||
|
Some(spending_conditions),
|
||||||
|
&SplitTarget::None,
|
||||||
|
&SendKind::default(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let attempt_amount = wallet
|
||||||
|
.receive(&token.to_string(), SplitTarget::default(), &[], &[])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// This should fail since the token is not signed
|
||||||
|
assert!(attempt_amount.is_err());
|
||||||
|
|
||||||
|
let wrong_secret = SecretKey::generate();
|
||||||
|
|
||||||
|
let received_amount = wallet
|
||||||
|
.receive(
|
||||||
|
&token.to_string(),
|
||||||
|
SplitTarget::default(),
|
||||||
|
&[wrong_secret],
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(received_amount.is_err());
|
||||||
|
|
||||||
|
let received_amount = wallet
|
||||||
|
.receive(&token.to_string(), SplitTarget::default(), &[secret], &[])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(received_amount, amount);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ if [ "$is_msrv" == true ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
buildargs=(
|
buildargs=(
|
||||||
|
"-p cdk-integration-tests"
|
||||||
"-p cdk"
|
"-p cdk"
|
||||||
"-p cdk --no-default-features"
|
"-p cdk --no-default-features"
|
||||||
"-p cdk --no-default-features --features wallet"
|
"-p cdk --no-default-features --features wallet"
|
||||||
|
|||||||
Reference in New Issue
Block a user