lib.rs 6.8 KB

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