1#![warn(clippy::unused_async)]
6#![allow(missing_docs)]
7#![allow(missing_debug_implementations)]
8
9pub mod database;
10pub mod error;
11pub mod logging;
12#[cfg(feature = "npubcash")]
13pub mod npubcash;
14#[cfg(feature = "postgres")]
15pub mod postgres;
16pub mod sqlite;
17pub mod token;
18pub mod types;
19pub mod wallet;
20pub mod wallet_repository;
21
22pub use database::*;
23pub use error::*;
24pub use logging::*;
25#[cfg(feature = "npubcash")]
26pub use npubcash::*;
27pub use types::*;
28pub use wallet::*;
29pub use wallet_repository::*;
30
31uniffi::setup_scaffolding!();
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36
37 #[test]
38 fn test_amount_conversion() {
39 let amount = Amount::new(1000);
40 assert_eq!(amount.value, 1000);
41 assert!(!amount.is_zero());
42
43 let zero = Amount::zero();
44 assert!(zero.is_zero());
45 }
46
47 #[test]
48 fn test_currency_unit_conversion() {
49 use cdk::nuts::CurrencyUnit as CdkCurrencyUnit;
50
51 let unit = CurrencyUnit::Sat;
52 let cdk_unit: CdkCurrencyUnit = unit.into();
53 let back: CurrencyUnit = cdk_unit.into();
54 assert_eq!(back, CurrencyUnit::Sat);
55 }
56
57 #[test]
58 fn test_mint_url_creation() {
59 let url = MintUrl::new("https://mint.example.com".to_string());
60 assert!(url.is_ok());
61
62 let invalid_url = MintUrl::new("not-a-url".to_string());
63 assert!(invalid_url.is_err());
64 }
65
66 #[test]
67 fn test_send_options_default() {
68 let options = SendOptions::default();
69 assert!(options.memo.is_none());
70 assert!(options.conditions.is_none());
71 assert!(matches!(options.amount_split_target, SplitTarget::None));
72 assert!(matches!(options.send_kind, SendKind::OnlineExact));
73 assert!(!options.include_fee);
74 assert!(options.max_proofs.is_none());
75 assert!(options.metadata.is_empty());
76 }
77
78 #[test]
79 fn test_receive_options_default() {
80 let options = ReceiveOptions::default();
81 assert!(matches!(options.amount_split_target, SplitTarget::None));
82 assert!(options.p2pk_signing_keys.is_empty());
83 assert!(options.preimages.is_empty());
84 assert!(options.metadata.is_empty());
85 }
86
87 #[test]
88 fn test_send_memo() {
89 let memo_text = "Test memo".to_string();
90 let memo = SendMemo {
91 memo: memo_text.clone(),
92 include_memo: true,
93 };
94
95 assert_eq!(memo.memo, memo_text);
96 assert!(memo.include_memo);
97 }
98
99 #[test]
100 fn test_split_target_variants() {
101 let split_none = SplitTarget::None;
102 assert!(matches!(split_none, SplitTarget::None));
103
104 let amount = Amount::new(1000);
105 let split_value = SplitTarget::Value { amount };
106 assert!(matches!(split_value, SplitTarget::Value { .. }));
107
108 let amounts = vec![Amount::new(100), Amount::new(200)];
109 let split_values = SplitTarget::Values { amounts };
110 assert!(matches!(split_values, SplitTarget::Values { .. }));
111 }
112
113 #[test]
114 fn test_send_kind_variants() {
115 let online_exact = SendKind::OnlineExact;
116 assert!(matches!(online_exact, SendKind::OnlineExact));
117
118 let tolerance = Amount::new(50);
119 let online_tolerance = SendKind::OnlineTolerance { tolerance };
120 assert!(matches!(online_tolerance, SendKind::OnlineTolerance { .. }));
121
122 let offline_exact = SendKind::OfflineExact;
123 assert!(matches!(offline_exact, SendKind::OfflineExact));
124
125 let offline_tolerance = SendKind::OfflineTolerance { tolerance };
126 assert!(matches!(
127 offline_tolerance,
128 SendKind::OfflineTolerance { .. }
129 ));
130 }
131
132 #[test]
133 fn test_secret_key_from_hex() {
134 let valid_hex = "a".repeat(64);
136 let secret_key = SecretKey::from_hex(valid_hex.clone());
137 assert!(secret_key.is_ok());
138 assert_eq!(secret_key.unwrap().hex, valid_hex);
139
140 let invalid_length = "a".repeat(32); let secret_key = SecretKey::from_hex(invalid_length);
143 assert!(secret_key.is_err());
144
145 let invalid_chars = "g".repeat(64); let secret_key = SecretKey::from_hex(invalid_chars);
148 assert!(secret_key.is_err());
149 }
150
151 #[test]
152 fn test_secret_key_random() {
153 let key1 = SecretKey::random();
154 let key2 = SecretKey::random();
155
156 assert_ne!(key1.hex, key2.hex);
158
159 assert_eq!(key1.hex.len(), 64);
161 assert_eq!(key2.hex.len(), 64);
162 assert!(key1.hex.chars().all(|c| c.is_ascii_hexdigit()));
163 assert!(key2.hex.chars().all(|c| c.is_ascii_hexdigit()));
164 }
165
166 #[test]
167 fn test_send_options_with_all_fields() {
168 use std::collections::HashMap;
169
170 let memo = SendMemo {
171 memo: "Test memo".to_string(),
172 include_memo: true,
173 };
174
175 let mut metadata = HashMap::new();
176 metadata.insert("key1".to_string(), "value1".to_string());
177
178 let conditions = SpendingConditions::P2PK {
179 pubkey: "02a1633cafcc01ebfb6d78e39f687a1f0995c62fc95f51ead10a02ee0be551b5dc"
180 .to_string(),
181 conditions: None,
182 };
183
184 let options = SendOptions {
185 memo: Some(memo),
186 conditions: Some(conditions),
187 amount_split_target: SplitTarget::Value {
188 amount: Amount::new(1000),
189 },
190 send_kind: SendKind::OnlineTolerance {
191 tolerance: Amount::new(50),
192 },
193 include_fee: true,
194 max_proofs: Some(10),
195 metadata,
196 };
197
198 assert!(options.memo.is_some());
199 assert!(options.conditions.is_some());
200 assert!(matches!(
201 options.amount_split_target,
202 SplitTarget::Value { .. }
203 ));
204 assert!(matches!(
205 options.send_kind,
206 SendKind::OnlineTolerance { .. }
207 ));
208 assert!(options.include_fee);
209 assert_eq!(options.max_proofs, Some(10));
210 assert!(!options.metadata.is_empty());
211 }
212
213 #[test]
214 fn test_receive_options_with_all_fields() {
215 use std::collections::HashMap;
216
217 let secret_key = SecretKey::random();
218 let mut metadata = HashMap::new();
219 metadata.insert("key1".to_string(), "value1".to_string());
220
221 let options = ReceiveOptions {
222 amount_split_target: SplitTarget::Values {
223 amounts: vec![Amount::new(100), Amount::new(200)],
224 },
225 p2pk_signing_keys: vec![secret_key],
226 preimages: vec!["preimage1".to_string(), "preimage2".to_string()],
227 metadata,
228 };
229
230 assert!(matches!(
231 options.amount_split_target,
232 SplitTarget::Values { .. }
233 ));
234 assert_eq!(options.p2pk_signing_keys.len(), 1);
235 assert_eq!(options.preimages.len(), 2);
236 assert!(!options.metadata.is_empty());
237 }
238
239 #[test]
240 fn test_wallet_config() {
241 let config = WalletConfig {
242 target_proof_count: None,
243 };
244 assert!(config.target_proof_count.is_none());
245
246 let config_with_values = WalletConfig {
247 target_proof_count: Some(5),
248 };
249 assert_eq!(config_with_values.target_proof_count, Some(5));
250 }
251
252 #[test]
253 fn test_mnemonic_generation() {
254 let mnemonic = generate_mnemonic().unwrap();
256 assert!(!mnemonic.is_empty());
257 assert_eq!(mnemonic.split_whitespace().count(), 12);
258
259 use bip39::Mnemonic;
261 let parsed = Mnemonic::parse(&mnemonic);
262 assert!(parsed.is_ok());
263 }
264
265 #[test]
266 fn test_mnemonic_validation() {
267 let mnemonic = generate_mnemonic().unwrap();
269 use bip39::Mnemonic;
270 let parsed = Mnemonic::parse(&mnemonic);
271 assert!(parsed.is_ok());
272
273 let invalid_mnemonic = "invalid mnemonic phrase that should not work";
275 let parsed_invalid = Mnemonic::parse(invalid_mnemonic);
276 assert!(parsed_invalid.is_err());
277
278 let mnemonic_12 = generate_mnemonic().unwrap();
280 assert_eq!(mnemonic_12.split_whitespace().count(), 12);
281 }
282
283 #[test]
284 fn test_mnemonic_to_entropy() {
285 let mnemonic = generate_mnemonic().unwrap();
287 let entropy = mnemonic_to_entropy(mnemonic.clone()).unwrap();
288
289 assert_eq!(entropy.len(), 16);
291
292 use bip39::Mnemonic;
294 let recreated_mnemonic = Mnemonic::from_entropy(&entropy).unwrap();
295 assert_eq!(recreated_mnemonic.to_string(), mnemonic);
296
297 let invalid_result = mnemonic_to_entropy("invalid mnemonic".to_string());
299 assert!(invalid_result.is_err());
300 }
301}