lib.rs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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::{StreamExt, Wallet};
  25. use cdk_fake_wallet::create_fake_invoice;
  26. use init_regtest::{get_lnd_dir, LND_RPC_ADDR};
  27. use ln_regtest_rs::ln_client::{ClnClient, LightningClient, LndClient};
  28. use crate::init_regtest::get_cln_dir;
  29. pub mod cli;
  30. pub mod init_auth_mint;
  31. pub mod init_pure_tests;
  32. pub mod init_regtest;
  33. pub mod shared;
  34. /// Generate standard keyset amounts as powers of 2
  35. ///
  36. /// Returns a vector of amounts: [1, 2, 4, 8, 16, 32, ..., 2^(n-1)]
  37. /// where n is the number of amounts to generate.
  38. ///
  39. /// # Arguments
  40. /// * `max_order` - The maximum power of 2 (exclusive). For example, max_order=32 generates amounts up to 2^31
  41. pub fn standard_keyset_amounts(max_order: u32) -> Vec<u64> {
  42. (0..max_order).map(|n| 2u64.pow(n)).collect()
  43. }
  44. pub async fn fund_wallet(wallet: Arc<Wallet>, amount: Amount) {
  45. let quote = wallet
  46. .mint_quote(amount, None)
  47. .await
  48. .expect("Could not get mint quote");
  49. let _proofs = wallet
  50. .proof_stream(quote, SplitTarget::default(), None)
  51. .next()
  52. .await
  53. .expect("proofs")
  54. .expect("proofs with no error");
  55. }
  56. pub fn get_mint_url_from_env() -> String {
  57. match env::var("CDK_TEST_MINT_URL") {
  58. Ok(url) => url,
  59. Err(_) => panic!("Mint url not set"),
  60. }
  61. }
  62. pub fn get_second_mint_url_from_env() -> String {
  63. match env::var("CDK_TEST_MINT_URL_2") {
  64. Ok(url) => url,
  65. Err(_) => panic!("Mint url not set"),
  66. }
  67. }
  68. // This is the ln wallet we use to send/receive ln payments as the wallet
  69. pub async fn init_lnd_client(work_dir: &Path) -> LndClient {
  70. let lnd_dir = get_lnd_dir(work_dir, "one");
  71. let cert_file = lnd_dir.join("tls.cert");
  72. let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
  73. LndClient::new(format!("https://{LND_RPC_ADDR}"), cert_file, macaroon_file)
  74. .await
  75. .unwrap()
  76. }
  77. /// Pays a Bolt11Invoice if it's on the regtest network, otherwise returns Ok
  78. ///
  79. /// This is useful for tests that need to pay invoices in regtest mode but
  80. /// should be skipped in other environments.
  81. pub async fn pay_if_regtest(_work_dir: &Path, invoice: &Bolt11Invoice) -> Result<()> {
  82. // Check if the invoice is for the regtest network
  83. if invoice.network() == bitcoin::Network::Regtest {
  84. let client = get_test_client().await;
  85. let mut tries = 0;
  86. while let Err(err) = client.pay_invoice(invoice.to_string()).await {
  87. println!("Could not pay invoice.retrying {err}");
  88. tries += 1;
  89. if tries > 10 {
  90. bail!("Could not pay invoice");
  91. }
  92. }
  93. Ok(())
  94. } else {
  95. // Not a regtest invoice, just return Ok
  96. Ok(())
  97. }
  98. }
  99. /// Determines if we're running in regtest mode based on environment variable
  100. ///
  101. /// Checks the CDK_TEST_REGTEST environment variable:
  102. /// - If set to "1", "true", or "yes" (case insensitive), returns true
  103. /// - Otherwise returns false
  104. pub fn is_regtest_env() -> bool {
  105. match env::var("CDK_TEST_REGTEST") {
  106. Ok(val) => {
  107. let val = val.to_lowercase();
  108. val == "1" || val == "true" || val == "yes"
  109. }
  110. Err(_) => false,
  111. }
  112. }
  113. /// Creates a real invoice if in regtest mode, otherwise returns a fake invoice
  114. ///
  115. /// Uses the is_regtest_env() function to determine whether to
  116. /// create a real regtest invoice or a fake one for testing.
  117. pub async fn create_invoice_for_env(amount_sat: Option<u64>) -> Result<String> {
  118. if is_regtest_env() {
  119. let client = get_test_client().await;
  120. client
  121. .create_invoice(amount_sat)
  122. .await
  123. .map_err(|e| anyhow!("Failed to create regtest invoice: {}", e))
  124. } else {
  125. // Not in regtest mode, create a fake invoice
  126. let fake_invoice = create_fake_invoice(
  127. amount_sat.expect("Amount must be defined") * 1_000,
  128. "".to_string(),
  129. );
  130. Ok(fake_invoice.to_string())
  131. }
  132. }
  133. // This is the ln wallet we use to send/receive ln payments as the wallet
  134. async fn _get_lnd_client() -> LndClient {
  135. let temp_dir = get_work_dir();
  136. // The LND mint uses the second LND node (LND_TWO_RPC_ADDR = localhost:10010)
  137. let lnd_dir = get_lnd_dir(&temp_dir, "one");
  138. let cert_file = lnd_dir.join("tls.cert");
  139. let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
  140. println!("Looking for LND cert file: {cert_file:?}");
  141. println!("Looking for LND macaroon file: {macaroon_file:?}");
  142. println!("Connecting to LND at: https://{LND_RPC_ADDR}");
  143. // Connect to LND
  144. LndClient::new(
  145. format!("https://{LND_RPC_ADDR}"),
  146. cert_file.clone(),
  147. macaroon_file.clone(),
  148. )
  149. .await
  150. .expect("Could not connect to lnd rpc")
  151. }
  152. /// Returns a Lightning client based on the CDK_TEST_LIGHTNING_CLIENT environment variable.
  153. ///
  154. /// Reads the CDK_TEST_LIGHTNING_CLIENT environment variable:
  155. /// - "cln" or "CLN": returns a CLN client
  156. /// - Anything else (or unset): returns an LND client (default)
  157. pub async fn get_test_client() -> Box<dyn LightningClient> {
  158. match env::var("CDK_TEST_LIGHTNING_CLIENT") {
  159. Ok(val) => {
  160. let val = val.to_lowercase();
  161. match val.as_str() {
  162. "cln" => Box::new(create_cln_client_with_retry().await),
  163. _ => Box::new(_get_lnd_client().await),
  164. }
  165. }
  166. Err(_) => Box::new(_get_lnd_client().await), // Default to LND
  167. }
  168. }
  169. fn get_work_dir() -> PathBuf {
  170. match env::var("CDK_ITESTS_DIR") {
  171. Ok(dir) => {
  172. let path = PathBuf::from(dir);
  173. println!("Using temp directory from CDK_ITESTS_DIR: {path:?}");
  174. path
  175. }
  176. Err(_) => {
  177. panic!("Unknown temp dir");
  178. }
  179. }
  180. }
  181. // Helper function to create CLN client with retries
  182. async fn create_cln_client_with_retry() -> ClnClient {
  183. let mut retries = 0;
  184. let max_retries = 10;
  185. let cln_dir = get_cln_dir(&get_work_dir(), "one");
  186. loop {
  187. match ClnClient::new(cln_dir.clone(), None).await {
  188. Ok(client) => return client,
  189. Err(e) => {
  190. retries += 1;
  191. if retries >= max_retries {
  192. panic!("Could not connect to CLN client after {max_retries} retries: {e}");
  193. }
  194. println!(
  195. "Failed to connect to CLN (attempt {retries}/{max_retries}): {e}. Retrying in 7 seconds..."
  196. );
  197. tokio::time::sleep(tokio::time::Duration::from_secs(7)).await;
  198. }
  199. }
  200. }
  201. }