shared.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. //! Shared utilities for mint integration tests
  2. //!
  3. //! This module provides common functionality used across different
  4. //! integration test binaries to reduce code duplication.
  5. use std::fs;
  6. use std::path::{Path, PathBuf};
  7. use std::str::FromStr;
  8. use std::sync::Arc;
  9. use std::time::Duration;
  10. use anyhow::Result;
  11. use cdk_axum::cache;
  12. use cdk_mintd::config::{Database, DatabaseEngine};
  13. use tokio::signal;
  14. use tokio::sync::Notify;
  15. use tokio_util::sync::CancellationToken;
  16. use crate::cli::{init_logging, CommonArgs};
  17. /// Default minimum mint amount for test mints
  18. const DEFAULT_MIN_MINT: u64 = 1;
  19. /// Default maximum mint amount for test mints
  20. const DEFAULT_MAX_MINT: u64 = 500_000;
  21. /// Default minimum melt amount for test mints
  22. const DEFAULT_MIN_MELT: u64 = 1;
  23. /// Default maximum melt amount for test mints
  24. const DEFAULT_MAX_MELT: u64 = 500_000;
  25. /// Wait for mint to be ready by checking its info endpoint, with optional shutdown signal
  26. pub async fn wait_for_mint_ready_with_shutdown(
  27. port: u16,
  28. timeout_secs: u64,
  29. shutdown_notify: Arc<CancellationToken>,
  30. ) -> Result<()> {
  31. let url = format!("http://127.0.0.1:{port}/v1/info");
  32. let start_time = std::time::Instant::now();
  33. let http_client = cdk_common::HttpClient::new();
  34. println!("Waiting for mint on port {port} to be ready...");
  35. loop {
  36. // Check if timeout has been reached
  37. if start_time.elapsed().as_secs() > timeout_secs {
  38. return Err(anyhow::anyhow!("Timeout waiting for mint on port {}", port));
  39. }
  40. if shutdown_notify.is_cancelled() {
  41. return Err(anyhow::anyhow!("Canceled waiting for {}", port));
  42. }
  43. tokio::select! {
  44. // Try to make a request to the mint info endpoint
  45. result = http_client.get_raw(&url) => {
  46. match result {
  47. Ok(response) => {
  48. if response.is_success() {
  49. println!("Mint on port {port} is ready");
  50. return Ok(());
  51. } else {
  52. println!(
  53. "Mint on port {} returned status: {}",
  54. port,
  55. response.status()
  56. );
  57. }
  58. }
  59. Err(e) => {
  60. println!("Error connecting to mint on port {port}: {e}");
  61. }
  62. }
  63. }
  64. // Check for shutdown signal
  65. _ = shutdown_notify.cancelled() => {
  66. return Err(anyhow::anyhow!(
  67. "Shutdown requested while waiting for mint on port {}",
  68. port
  69. ));
  70. }
  71. }
  72. // Wait before retrying to avoid overwhelming the mint during startup
  73. tokio::time::sleep(Duration::from_secs(1)).await;
  74. }
  75. }
  76. /// Initialize working directory
  77. pub fn init_working_directory(work_dir: &str) -> Result<PathBuf> {
  78. let temp_dir = PathBuf::from_str(work_dir)?;
  79. // Create the temp directory if it doesn't exist
  80. fs::create_dir_all(&temp_dir)?;
  81. Ok(temp_dir)
  82. }
  83. /// Write environment variables to .env file
  84. pub fn write_env_file(temp_dir: &Path, env_vars: &[(&str, &str)]) -> Result<()> {
  85. let mut env_content = String::new();
  86. for (key, value) in env_vars {
  87. env_content.push_str(&format!("{key}={value}\n"));
  88. }
  89. let env_file_path = temp_dir.join(".env");
  90. fs::write(&env_file_path, &env_content)
  91. .map(|_| {
  92. println!(
  93. "Environment variables written to: {}",
  94. env_file_path.display()
  95. );
  96. })
  97. .map_err(|e| anyhow::anyhow!("Could not write .env file: {}", e))
  98. }
  99. /// Wait for .env file to be created
  100. pub async fn wait_for_env_file(temp_dir: &Path, timeout_secs: u64) -> Result<()> {
  101. let env_file_path = temp_dir.join(".env");
  102. let start_time = std::time::Instant::now();
  103. println!(
  104. "Waiting for .env file to be created at: {}",
  105. env_file_path.display()
  106. );
  107. loop {
  108. // Check if timeout has been reached
  109. if start_time.elapsed().as_secs() > timeout_secs {
  110. return Err(anyhow::anyhow!(
  111. "Timeout waiting for .env file at {}",
  112. env_file_path.display()
  113. ));
  114. }
  115. // Check if the file exists
  116. if env_file_path.exists() {
  117. println!(".env file found at: {}", env_file_path.display());
  118. return Ok(());
  119. }
  120. tokio::time::sleep(Duration::from_secs(1)).await;
  121. }
  122. }
  123. /// Setup common logging based on CLI arguments
  124. pub fn setup_logging(common_args: &CommonArgs) {
  125. init_logging(common_args.enable_logging, common_args.log_level);
  126. }
  127. /// Create shutdown handler for graceful termination
  128. pub fn create_shutdown_handler() -> Arc<Notify> {
  129. Arc::new(Notify::new())
  130. }
  131. /// Wait for Ctrl+C signal
  132. pub async fn wait_for_shutdown_signal(shutdown: Arc<Notify>) {
  133. signal::ctrl_c()
  134. .await
  135. .expect("failed to install CTRL+C handler");
  136. println!("\nReceived Ctrl+C, shutting down...");
  137. shutdown.notify_waiters();
  138. }
  139. /// Common mint information display
  140. pub fn display_mint_info(port: u16, temp_dir: &Path, database_type: &str) {
  141. println!("Mint started successfully!");
  142. println!("Mint URL: http://127.0.0.1:{port}");
  143. println!("Temp directory: {temp_dir:?}");
  144. println!("Database type: {database_type}");
  145. }
  146. /// Create settings for a fake wallet mint
  147. pub fn create_fake_wallet_settings(
  148. port: u16,
  149. database: &str,
  150. mnemonic: Option<String>,
  151. signatory_config: Option<(String, String)>, // (url, certs_dir)
  152. fake_wallet_config: Option<cdk_mintd::config::FakeWallet>,
  153. ) -> cdk_mintd::config::Settings {
  154. let engine = DatabaseEngine::from_str(database).expect("valid database");
  155. // If using PostgreSQL, get the connection URL from the environment or use default
  156. let postgres_config = if engine == DatabaseEngine::Postgres {
  157. let url = std::env::var("CDK_MINTD_DATABASE_URL").unwrap_or_else(|_| {
  158. "postgresql://cdk_user:cdk_password@localhost:5432/cdk_mint".to_string()
  159. });
  160. Some(cdk_mintd::config::PostgresConfig {
  161. url,
  162. ..Default::default()
  163. })
  164. } else {
  165. None
  166. };
  167. cdk_mintd::config::Settings {
  168. info: cdk_mintd::config::Info {
  169. url: format!("http://127.0.0.1:{port}"),
  170. quote_ttl: None,
  171. listen_host: "127.0.0.1".to_string(),
  172. listen_port: port,
  173. seed: None,
  174. mnemonic,
  175. signatory_url: signatory_config.as_ref().map(|(url, _)| url.clone()),
  176. signatory_certs: signatory_config
  177. .as_ref()
  178. .map(|(_, certs_dir)| certs_dir.clone()),
  179. input_fee_ppk: None,
  180. http_cache: cache::Config::default(),
  181. logging: cdk_mintd::config::LoggingConfig {
  182. output: cdk_mintd::config::LoggingOutput::Both,
  183. console_level: Some("debug".to_string()),
  184. file_level: Some("debug".to_string()),
  185. },
  186. enable_swagger_ui: None,
  187. },
  188. mint_info: cdk_mintd::config::MintInfo::default(),
  189. ln: cdk_mintd::config::Ln {
  190. ln_backend: cdk_mintd::config::LnBackend::FakeWallet,
  191. invoice_description: None,
  192. min_mint: DEFAULT_MIN_MINT.into(),
  193. max_mint: DEFAULT_MAX_MINT.into(),
  194. min_melt: DEFAULT_MIN_MELT.into(),
  195. max_melt: DEFAULT_MAX_MELT.into(),
  196. },
  197. cln: None,
  198. lnbits: None,
  199. lnd: None,
  200. ldk_node: None,
  201. fake_wallet: fake_wallet_config,
  202. grpc_processor: None,
  203. database: Database {
  204. engine,
  205. postgres: postgres_config,
  206. },
  207. auth_database: None,
  208. mint_management_rpc: None,
  209. auth: None,
  210. prometheus: Some(Default::default()),
  211. }
  212. }
  213. /// Create settings for a CLN mint
  214. pub fn create_cln_settings(
  215. port: u16,
  216. _cln_rpc_path: PathBuf,
  217. mnemonic: String,
  218. cln_config: cdk_mintd::config::Cln,
  219. ) -> cdk_mintd::config::Settings {
  220. cdk_mintd::config::Settings {
  221. info: cdk_mintd::config::Info {
  222. url: format!("http://127.0.0.1:{port}"),
  223. quote_ttl: None,
  224. listen_host: "127.0.0.1".to_string(),
  225. listen_port: port,
  226. seed: None,
  227. mnemonic: Some(mnemonic),
  228. signatory_url: None,
  229. signatory_certs: None,
  230. input_fee_ppk: None,
  231. http_cache: cache::Config::default(),
  232. logging: cdk_mintd::config::LoggingConfig {
  233. output: cdk_mintd::config::LoggingOutput::Both,
  234. console_level: Some("debug".to_string()),
  235. file_level: Some("debug".to_string()),
  236. },
  237. enable_swagger_ui: None,
  238. },
  239. mint_info: cdk_mintd::config::MintInfo::default(),
  240. ln: cdk_mintd::config::Ln {
  241. ln_backend: cdk_mintd::config::LnBackend::Cln,
  242. invoice_description: None,
  243. min_mint: DEFAULT_MIN_MINT.into(),
  244. max_mint: DEFAULT_MAX_MINT.into(),
  245. min_melt: DEFAULT_MIN_MELT.into(),
  246. max_melt: DEFAULT_MAX_MELT.into(),
  247. },
  248. cln: Some(cln_config),
  249. lnbits: None,
  250. lnd: None,
  251. ldk_node: None,
  252. fake_wallet: None,
  253. grpc_processor: None,
  254. database: cdk_mintd::config::Database::default(),
  255. auth_database: None,
  256. mint_management_rpc: None,
  257. auth: None,
  258. prometheus: Some(Default::default()),
  259. }
  260. }
  261. /// Create settings for an LND mint
  262. pub fn create_lnd_settings(
  263. port: u16,
  264. lnd_config: cdk_mintd::config::Lnd,
  265. mnemonic: String,
  266. ) -> cdk_mintd::config::Settings {
  267. cdk_mintd::config::Settings {
  268. info: cdk_mintd::config::Info {
  269. quote_ttl: None,
  270. url: format!("http://127.0.0.1:{port}"),
  271. listen_host: "127.0.0.1".to_string(),
  272. listen_port: port,
  273. seed: None,
  274. mnemonic: Some(mnemonic),
  275. signatory_url: None,
  276. signatory_certs: None,
  277. input_fee_ppk: None,
  278. http_cache: cache::Config::default(),
  279. logging: cdk_mintd::config::LoggingConfig {
  280. output: cdk_mintd::config::LoggingOutput::Both,
  281. console_level: Some("debug".to_string()),
  282. file_level: Some("debug".to_string()),
  283. },
  284. enable_swagger_ui: None,
  285. },
  286. mint_info: cdk_mintd::config::MintInfo::default(),
  287. ln: cdk_mintd::config::Ln {
  288. ln_backend: cdk_mintd::config::LnBackend::Lnd,
  289. invoice_description: None,
  290. min_mint: DEFAULT_MIN_MINT.into(),
  291. max_mint: DEFAULT_MAX_MINT.into(),
  292. min_melt: DEFAULT_MIN_MELT.into(),
  293. max_melt: DEFAULT_MAX_MELT.into(),
  294. },
  295. cln: None,
  296. lnbits: None,
  297. ldk_node: None,
  298. lnd: Some(lnd_config),
  299. fake_wallet: None,
  300. grpc_processor: None,
  301. database: cdk_mintd::config::Database::default(),
  302. auth_database: None,
  303. mint_management_rpc: None,
  304. auth: None,
  305. prometheus: Some(Default::default()),
  306. }
  307. }