lib.rs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. //! Integration Test Library
  2. //!
  3. //! This crate provides shared functionality for CDK integration tests.
  4. //! It includes utilities for setting up test environments, funding wallets,
  5. //! and common test operations across different test scenarios.
  6. //!
  7. //! Test Categories Supported:
  8. //! - Pure in-memory tests (no external dependencies)
  9. //! - Regtest environment tests (with actual Lightning nodes)
  10. //! - Authenticated mint tests
  11. //! - Multi-mint scenarios
  12. //!
  13. //! Key Components:
  14. //! - Test environment initialization
  15. //! - Wallet funding utilities
  16. //! - Lightning Network client helpers
  17. //! - Proof state management utilities
  18. use std::env;
  19. use std::path::{Path, PathBuf};
  20. use std::sync::Arc;
  21. use anyhow::{anyhow, bail, Result};
  22. use cashu::Bolt11Invoice;
  23. use cdk::amount::{Amount, SplitTarget};
  24. use cdk::nuts::State;
  25. use cdk::{StreamExt, Wallet};
  26. use cdk_fake_wallet::create_fake_invoice;
  27. use init_regtest::{get_lnd_dir, LND_RPC_ADDR};
  28. use ln_regtest_rs::ln_client::{ClnClient, LightningClient, LndClient};
  29. use crate::init_regtest::get_cln_dir;
  30. pub mod cli;
  31. pub mod init_auth_mint;
  32. pub mod init_pure_tests;
  33. pub mod init_regtest;
  34. pub mod shared;
  35. pub async fn fund_wallet(wallet: Arc<Wallet>, amount: Amount) {
  36. let quote = wallet
  37. .mint_quote(amount, None)
  38. .await
  39. .expect("Could not get mint quote");
  40. let _proofs = wallet
  41. .proof_stream(quote, SplitTarget::default(), None)
  42. .next()
  43. .await
  44. .expect("proofs")
  45. .expect("proofs with no error");
  46. }
  47. pub fn get_mint_url_from_env() -> String {
  48. match env::var("CDK_TEST_MINT_URL") {
  49. Ok(url) => url,
  50. Err(_) => panic!("Mint url not set"),
  51. }
  52. }
  53. pub fn get_second_mint_url_from_env() -> String {
  54. match env::var("CDK_TEST_MINT_URL_2") {
  55. Ok(url) => url,
  56. Err(_) => panic!("Mint url not set"),
  57. }
  58. }
  59. // Get all pending from wallet and attempt to swap
  60. // Will panic if there are no pending
  61. // Will return Ok if swap fails as expected
  62. pub async fn attempt_to_swap_pending(wallet: &Wallet) -> Result<()> {
  63. let pending = wallet
  64. .localstore
  65. .get_proofs(None, None, Some(vec![State::Pending]), None)
  66. .await?;
  67. assert!(!pending.is_empty());
  68. let swap = wallet
  69. .swap(
  70. None,
  71. SplitTarget::None,
  72. pending.into_iter().map(|p| p.proof).collect(),
  73. None,
  74. false,
  75. )
  76. .await;
  77. match swap {
  78. Ok(_swap) => {
  79. bail!("These proofs should be pending")
  80. }
  81. Err(err) => match err {
  82. cdk::error::Error::TokenPending => (),
  83. _ => {
  84. println!("{err:?}");
  85. bail!("Wrong error")
  86. }
  87. },
  88. }
  89. Ok(())
  90. }
  91. // This is the ln wallet we use to send/receive ln payements as the wallet
  92. pub async fn init_lnd_client(work_dir: &Path) -> LndClient {
  93. let lnd_dir = get_lnd_dir(work_dir, "one");
  94. let cert_file = lnd_dir.join("tls.cert");
  95. let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
  96. LndClient::new(format!("https://{LND_RPC_ADDR}"), cert_file, macaroon_file)
  97. .await
  98. .unwrap()
  99. }
  100. /// Pays a Bolt11Invoice if it's on the regtest network, otherwise returns Ok
  101. ///
  102. /// This is useful for tests that need to pay invoices in regtest mode but
  103. /// should be skipped in other environments.
  104. pub async fn pay_if_regtest(_work_dir: &Path, invoice: &Bolt11Invoice) -> Result<()> {
  105. // Check if the invoice is for the regtest network
  106. if invoice.network() == bitcoin::Network::Regtest {
  107. let client = get_test_client().await;
  108. let mut tries = 0;
  109. while let Err(err) = client.pay_invoice(invoice.to_string()).await {
  110. println!("Could not pay invoice.retrying {err}");
  111. tries += 1;
  112. if tries > 10 {
  113. bail!("Could not pay invoice");
  114. }
  115. }
  116. Ok(())
  117. } else {
  118. // Not a regtest invoice, just return Ok
  119. Ok(())
  120. }
  121. }
  122. /// Determines if we're running in regtest mode based on environment variable
  123. ///
  124. /// Checks the CDK_TEST_REGTEST environment variable:
  125. /// - If set to "1", "true", or "yes" (case insensitive), returns true
  126. /// - Otherwise returns false
  127. pub fn is_regtest_env() -> bool {
  128. match env::var("CDK_TEST_REGTEST") {
  129. Ok(val) => {
  130. let val = val.to_lowercase();
  131. val == "1" || val == "true" || val == "yes"
  132. }
  133. Err(_) => false,
  134. }
  135. }
  136. /// Creates a real invoice if in regtest mode, otherwise returns a fake invoice
  137. ///
  138. /// Uses the is_regtest_env() function to determine whether to
  139. /// create a real regtest invoice or a fake one for testing.
  140. pub async fn create_invoice_for_env(amount_sat: Option<u64>) -> Result<String> {
  141. if is_regtest_env() {
  142. let client = get_test_client().await;
  143. client
  144. .create_invoice(amount_sat)
  145. .await
  146. .map_err(|e| anyhow!("Failed to create regtest invoice: {}", e))
  147. } else {
  148. // Not in regtest mode, create a fake invoice
  149. let fake_invoice = create_fake_invoice(
  150. amount_sat.expect("Amount must be defined") * 1_000,
  151. "".to_string(),
  152. );
  153. Ok(fake_invoice.to_string())
  154. }
  155. }
  156. // This is the ln wallet we use to send/receive ln payements as the wallet
  157. async fn _get_lnd_client() -> LndClient {
  158. let temp_dir = get_work_dir();
  159. // The LND mint uses the second LND node (LND_TWO_RPC_ADDR = localhost:10010)
  160. let lnd_dir = get_lnd_dir(&temp_dir, "one");
  161. let cert_file = lnd_dir.join("tls.cert");
  162. let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
  163. println!("Looking for LND cert file: {cert_file:?}");
  164. println!("Looking for LND macaroon file: {macaroon_file:?}");
  165. println!("Connecting to LND at: https://{LND_RPC_ADDR}");
  166. // Connect to LND
  167. LndClient::new(
  168. format!("https://{LND_RPC_ADDR}"),
  169. cert_file.clone(),
  170. macaroon_file.clone(),
  171. )
  172. .await
  173. .expect("Could not connect to lnd rpc")
  174. }
  175. /// Returns a Lightning client based on the CDK_TEST_LIGHTNING_CLIENT environment variable.
  176. ///
  177. /// Reads the CDK_TEST_LIGHTNING_CLIENT environment variable:
  178. /// - "cln" or "CLN": returns a CLN client
  179. /// - Anything else (or unset): returns an LND client (default)
  180. pub async fn get_test_client() -> Box<dyn LightningClient> {
  181. match env::var("CDK_TEST_LIGHTNING_CLIENT") {
  182. Ok(val) => {
  183. let val = val.to_lowercase();
  184. match val.as_str() {
  185. "cln" => Box::new(create_cln_client_with_retry().await),
  186. _ => Box::new(_get_lnd_client().await),
  187. }
  188. }
  189. Err(_) => Box::new(_get_lnd_client().await), // Default to LND
  190. }
  191. }
  192. fn get_work_dir() -> PathBuf {
  193. match env::var("CDK_ITESTS_DIR") {
  194. Ok(dir) => {
  195. let path = PathBuf::from(dir);
  196. println!("Using temp directory from CDK_ITESTS_DIR: {path:?}");
  197. path
  198. }
  199. Err(_) => {
  200. panic!("Unknown temp dir");
  201. }
  202. }
  203. }
  204. // Helper function to create CLN client with retries
  205. async fn create_cln_client_with_retry() -> ClnClient {
  206. let mut retries = 0;
  207. let max_retries = 10;
  208. let cln_dir = get_cln_dir(&get_work_dir(), "one");
  209. loop {
  210. match ClnClient::new(cln_dir.clone(), None).await {
  211. Ok(client) => return client,
  212. Err(e) => {
  213. retries += 1;
  214. if retries >= max_retries {
  215. panic!("Could not connect to CLN client after {max_retries} retries: {e}");
  216. }
  217. println!(
  218. "Failed to connect to CLN (attempt {retries}/{max_retries}): {e}. Retrying in 7 seconds..."
  219. );
  220. tokio::time::sleep(tokio::time::Duration::from_secs(7)).await;
  221. }
  222. }
  223. }
  224. }