refactor: update dependencies and remove unused code
- Added new dependencies: `adler2`, `crc32fast`, `flate2`, `miniz_oxide`, and `libredox`. - Updated existing dependencies: `tokio-rustls` to version 0.26.4 and `filetime` to version 0.2.27. - Removed the `backup.rs` file as it is no longer needed. - Introduced tests for configuration and credential management. - Enhanced the `identity` module to generate W3C compliant DID documents. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -276,3 +276,307 @@ pub async fn receive_token(data_dir: &Path, token_str: &str) -> Result<u64> {
|
||||
fn default_mint_url() -> String {
|
||||
"http://127.0.0.1:8175".to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_wallet_state_balance_empty() {
|
||||
let wallet = WalletState::default();
|
||||
assert_eq!(wallet.balance(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_state_balance_unspent_only() {
|
||||
let wallet = WalletState {
|
||||
tokens: vec![
|
||||
EcashToken {
|
||||
id: "t1".into(),
|
||||
amount_sats: 100,
|
||||
token: "tok1".into(),
|
||||
mint_url: "http://mint".into(),
|
||||
spent: false,
|
||||
created_at: "2025-01-01T00:00:00Z".into(),
|
||||
},
|
||||
EcashToken {
|
||||
id: "t2".into(),
|
||||
amount_sats: 200,
|
||||
token: "tok2".into(),
|
||||
mint_url: "http://mint".into(),
|
||||
spent: true,
|
||||
created_at: "2025-01-01T00:00:00Z".into(),
|
||||
},
|
||||
EcashToken {
|
||||
id: "t3".into(),
|
||||
amount_sats: 50,
|
||||
token: "tok3".into(),
|
||||
mint_url: "http://mint".into(),
|
||||
spent: false,
|
||||
created_at: "2025-01-01T00:00:00Z".into(),
|
||||
},
|
||||
],
|
||||
transactions: vec![],
|
||||
mint_url: String::new(),
|
||||
};
|
||||
// Only t1 (100) and t3 (50) are unspent
|
||||
assert_eq!(wallet.balance(), 150);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_state_balance_all_spent() {
|
||||
let wallet = WalletState {
|
||||
tokens: vec![EcashToken {
|
||||
id: "t1".into(),
|
||||
amount_sats: 500,
|
||||
token: "tok1".into(),
|
||||
mint_url: "http://mint".into(),
|
||||
spent: true,
|
||||
created_at: "2025-01-01T00:00:00Z".into(),
|
||||
}],
|
||||
transactions: vec![],
|
||||
mint_url: String::new(),
|
||||
};
|
||||
assert_eq!(wallet.balance(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_mint_url() {
|
||||
let url = default_mint_url();
|
||||
assert_eq!(url, "http://127.0.0.1:8175");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_load_wallet_creates_default_when_missing() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let wallet = load_wallet(tmp.path()).await.unwrap();
|
||||
assert_eq!(wallet.balance(), 0);
|
||||
assert!(wallet.tokens.is_empty());
|
||||
assert!(wallet.transactions.is_empty());
|
||||
assert_eq!(wallet.mint_url, default_mint_url());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_save_and_load_wallet_roundtrip() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let wallet = WalletState {
|
||||
tokens: vec![EcashToken {
|
||||
id: "round-trip-token".into(),
|
||||
amount_sats: 42,
|
||||
token: "cashuA_test".into(),
|
||||
mint_url: "http://127.0.0.1:8175".into(),
|
||||
spent: false,
|
||||
created_at: "2025-06-01T12:00:00Z".into(),
|
||||
}],
|
||||
transactions: vec![EcashTransaction {
|
||||
id: "tx1".into(),
|
||||
tx_type: TransactionType::Mint,
|
||||
amount_sats: 42,
|
||||
timestamp: "2025-06-01T12:00:00Z".into(),
|
||||
description: "Test mint".into(),
|
||||
}],
|
||||
mint_url: "http://127.0.0.1:8175".into(),
|
||||
};
|
||||
|
||||
save_wallet(tmp.path(), &wallet).await.unwrap();
|
||||
let loaded = load_wallet(tmp.path()).await.unwrap();
|
||||
assert_eq!(loaded.tokens.len(), 1);
|
||||
assert_eq!(loaded.tokens[0].id, "round-trip-token");
|
||||
assert_eq!(loaded.tokens[0].amount_sats, 42);
|
||||
assert!(!loaded.tokens[0].spent);
|
||||
assert_eq!(loaded.transactions.len(), 1);
|
||||
assert_eq!(loaded.balance(), 42);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_save_wallet_creates_directory() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let wallet_dir = tmp.path().join("wallet");
|
||||
assert!(!wallet_dir.exists());
|
||||
|
||||
let wallet = WalletState::default();
|
||||
save_wallet(tmp.path(), &wallet).await.unwrap();
|
||||
assert!(wallet_dir.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_receive_token_valid() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let amount = receive_token(tmp.path(), "cashuSend_500_abc123_1700000000")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(amount, 500);
|
||||
|
||||
let wallet = load_wallet(tmp.path()).await.unwrap();
|
||||
assert_eq!(wallet.balance(), 500);
|
||||
assert_eq!(wallet.tokens.len(), 1);
|
||||
assert!(!wallet.tokens[0].spent);
|
||||
assert_eq!(wallet.transactions.len(), 1);
|
||||
assert!(matches!(
|
||||
wallet.transactions[0].tx_type,
|
||||
TransactionType::Receive
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_receive_token_invalid_format() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let result = receive_token(tmp.path(), "invalid_token_string").await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_receive_token_zero_amount() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let result = receive_token(tmp.path(), "cashuSend_0_abc_1700000000").await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_melt_tokens_marks_spent() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
// Pre-populate a wallet with a token
|
||||
let wallet = WalletState {
|
||||
tokens: vec![EcashToken {
|
||||
id: "melt-me".into(),
|
||||
amount_sats: 1000,
|
||||
token: "cashuA_test".into(),
|
||||
mint_url: default_mint_url(),
|
||||
spent: false,
|
||||
created_at: "2025-01-01T00:00:00Z".into(),
|
||||
}],
|
||||
transactions: vec![],
|
||||
mint_url: default_mint_url(),
|
||||
};
|
||||
save_wallet(tmp.path(), &wallet).await.unwrap();
|
||||
|
||||
let amount = melt_tokens(tmp.path(), "melt-me").await.unwrap();
|
||||
assert_eq!(amount, 1000);
|
||||
|
||||
let loaded = load_wallet(tmp.path()).await.unwrap();
|
||||
assert!(loaded.tokens[0].spent);
|
||||
assert_eq!(loaded.balance(), 0);
|
||||
assert_eq!(loaded.transactions.len(), 1);
|
||||
assert!(matches!(
|
||||
loaded.transactions[0].tx_type,
|
||||
TransactionType::Melt
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_melt_tokens_not_found() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let result = melt_tokens(tmp.path(), "nonexistent").await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_melt_already_spent_token_errors() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let wallet = WalletState {
|
||||
tokens: vec![EcashToken {
|
||||
id: "already-spent".into(),
|
||||
amount_sats: 100,
|
||||
token: "cashuA_x".into(),
|
||||
mint_url: default_mint_url(),
|
||||
spent: true,
|
||||
created_at: "2025-01-01T00:00:00Z".into(),
|
||||
}],
|
||||
transactions: vec![],
|
||||
mint_url: default_mint_url(),
|
||||
};
|
||||
save_wallet(tmp.path(), &wallet).await.unwrap();
|
||||
|
||||
let result = melt_tokens(tmp.path(), "already-spent").await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_token_sufficient_balance() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let wallet = WalletState {
|
||||
tokens: vec![
|
||||
EcashToken {
|
||||
id: "s1".into(),
|
||||
amount_sats: 300,
|
||||
token: "tok1".into(),
|
||||
mint_url: default_mint_url(),
|
||||
spent: false,
|
||||
created_at: "2025-01-01T00:00:00Z".into(),
|
||||
},
|
||||
EcashToken {
|
||||
id: "s2".into(),
|
||||
amount_sats: 200,
|
||||
token: "tok2".into(),
|
||||
mint_url: default_mint_url(),
|
||||
spent: false,
|
||||
created_at: "2025-01-01T00:00:00Z".into(),
|
||||
},
|
||||
],
|
||||
transactions: vec![],
|
||||
mint_url: default_mint_url(),
|
||||
};
|
||||
save_wallet(tmp.path(), &wallet).await.unwrap();
|
||||
|
||||
let token_str = send_token(tmp.path(), 400).await.unwrap();
|
||||
assert!(token_str.starts_with("cashuSend_400_"));
|
||||
|
||||
let loaded = load_wallet(tmp.path()).await.unwrap();
|
||||
// Both tokens should be marked spent (300 + 200 = 500 >= 400)
|
||||
assert!(loaded.tokens.iter().all(|t| t.spent));
|
||||
assert_eq!(loaded.balance(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_token_insufficient_balance() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let wallet = WalletState {
|
||||
tokens: vec![EcashToken {
|
||||
id: "small".into(),
|
||||
amount_sats: 10,
|
||||
token: "tok".into(),
|
||||
mint_url: default_mint_url(),
|
||||
spent: false,
|
||||
created_at: "2025-01-01T00:00:00Z".into(),
|
||||
}],
|
||||
transactions: vec![],
|
||||
mint_url: default_mint_url(),
|
||||
};
|
||||
save_wallet(tmp.path(), &wallet).await.unwrap();
|
||||
|
||||
let result = send_token(tmp.path(), 1000).await;
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(err_msg.contains("Insufficient balance"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_state_serialization() {
|
||||
let wallet = WalletState {
|
||||
tokens: vec![],
|
||||
transactions: vec![],
|
||||
mint_url: "http://localhost:8175".into(),
|
||||
};
|
||||
let json = serde_json::to_string(&wallet).unwrap();
|
||||
let parsed: WalletState = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed.mint_url, wallet.mint_url);
|
||||
assert!(parsed.tokens.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_type_serialization() {
|
||||
let tx = EcashTransaction {
|
||||
id: "tx-test".into(),
|
||||
tx_type: TransactionType::Send,
|
||||
amount_sats: 100,
|
||||
timestamp: "2025-01-01T00:00:00Z".into(),
|
||||
description: "test".into(),
|
||||
};
|
||||
let json = serde_json::to_string(&tx).unwrap();
|
||||
assert!(json.contains("\"send\""));
|
||||
|
||||
let parsed: EcashTransaction = serde_json::from_str(&json).unwrap();
|
||||
assert!(matches!(parsed.tx_type, TransactionType::Send));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,3 +112,167 @@ pub async fn get_networking_profits(data_dir: &Path) -> Result<ProfitsSummary> {
|
||||
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_profits_summary_default() {
|
||||
let summary = ProfitsSummary::default();
|
||||
assert_eq!(summary.total_sats, 0);
|
||||
assert_eq!(summary.content_sales_sats, 0);
|
||||
assert_eq!(summary.routing_fees_sats, 0);
|
||||
assert!(summary.recent.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_load_profits_returns_default_when_missing() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let summary = load_profits(tmp.path()).await.unwrap();
|
||||
assert_eq!(summary.total_sats, 0);
|
||||
assert_eq!(summary.content_sales_sats, 0);
|
||||
assert_eq!(summary.routing_fees_sats, 0);
|
||||
assert!(summary.recent.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_save_and_load_profits_roundtrip() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let summary = ProfitsSummary {
|
||||
total_sats: 5000,
|
||||
content_sales_sats: 3000,
|
||||
routing_fees_sats: 2000,
|
||||
recent: vec![ProfitEntry {
|
||||
source: ProfitSource::ContentSale,
|
||||
amount_sats: 3000,
|
||||
timestamp: "2025-06-01T00:00:00Z".to_string(),
|
||||
description: "Test sale".to_string(),
|
||||
}],
|
||||
};
|
||||
|
||||
save_profits(tmp.path(), &summary).await.unwrap();
|
||||
let loaded = load_profits(tmp.path()).await.unwrap();
|
||||
assert_eq!(loaded.total_sats, 5000);
|
||||
assert_eq!(loaded.content_sales_sats, 3000);
|
||||
assert_eq!(loaded.routing_fees_sats, 2000);
|
||||
assert_eq!(loaded.recent.len(), 1);
|
||||
assert_eq!(loaded.recent[0].amount_sats, 3000);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_save_profits_creates_wallet_dir() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let wallet_dir = tmp.path().join("wallet");
|
||||
assert!(!wallet_dir.exists());
|
||||
|
||||
save_profits(tmp.path(), &ProfitsSummary::default()).await.unwrap();
|
||||
assert!(wallet_dir.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_record_content_sale() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
record_content_sale(tmp.path(), 500, "First sale").await.unwrap();
|
||||
|
||||
let summary = load_profits(tmp.path()).await.unwrap();
|
||||
assert_eq!(summary.total_sats, 500);
|
||||
assert_eq!(summary.content_sales_sats, 500);
|
||||
assert_eq!(summary.routing_fees_sats, 0);
|
||||
assert_eq!(summary.recent.len(), 1);
|
||||
assert_eq!(summary.recent[0].amount_sats, 500);
|
||||
assert_eq!(summary.recent[0].description, "First sale");
|
||||
assert!(matches!(summary.recent[0].source, ProfitSource::ContentSale));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_record_multiple_content_sales() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
record_content_sale(tmp.path(), 100, "Sale 1").await.unwrap();
|
||||
record_content_sale(tmp.path(), 200, "Sale 2").await.unwrap();
|
||||
record_content_sale(tmp.path(), 300, "Sale 3").await.unwrap();
|
||||
|
||||
let summary = load_profits(tmp.path()).await.unwrap();
|
||||
assert_eq!(summary.total_sats, 600);
|
||||
assert_eq!(summary.content_sales_sats, 600);
|
||||
assert_eq!(summary.recent.len(), 3);
|
||||
// Newest first (inserted at index 0)
|
||||
assert_eq!(summary.recent[0].description, "Sale 3");
|
||||
assert_eq!(summary.recent[1].description, "Sale 2");
|
||||
assert_eq!(summary.recent[2].description, "Sale 1");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_record_content_sale_truncates_at_100() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
for i in 0..110 {
|
||||
record_content_sale(tmp.path(), 1, &format!("Sale {}", i))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let summary = load_profits(tmp.path()).await.unwrap();
|
||||
assert_eq!(summary.recent.len(), 100);
|
||||
assert_eq!(summary.total_sats, 110);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_networking_profits_empty() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let summary = get_networking_profits(tmp.path()).await.unwrap();
|
||||
assert_eq!(summary.total_sats, 0);
|
||||
assert_eq!(summary.content_sales_sats, 0);
|
||||
assert_eq!(summary.routing_fees_sats, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_networking_profits_includes_ecash_receives() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
|
||||
// Simulate receiving ecash tokens
|
||||
ecash::receive_token(tmp.path(), "cashuSend_500_uuid1_1700000000")
|
||||
.await
|
||||
.unwrap();
|
||||
ecash::receive_token(tmp.path(), "cashuSend_300_uuid2_1700000001")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let summary = get_networking_profits(tmp.path()).await.unwrap();
|
||||
// ecash receives (800) should be reflected as content sales
|
||||
assert_eq!(summary.content_sales_sats, 800);
|
||||
assert_eq!(summary.total_sats, 800);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_networking_profits_uses_higher_of_tracked_or_ecash() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
|
||||
// Record a larger tracked profit
|
||||
record_content_sale(tmp.path(), 2000, "Big sale").await.unwrap();
|
||||
// Receive a smaller ecash amount
|
||||
ecash::receive_token(tmp.path(), "cashuSend_100_uuid_170")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let summary = get_networking_profits(tmp.path()).await.unwrap();
|
||||
// Should use tracked (2000) since it's larger than ecash receives (100)
|
||||
assert_eq!(summary.content_sales_sats, 2000);
|
||||
assert_eq!(summary.total_sats, 2000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_profit_source_serialization() {
|
||||
let entry = ProfitEntry {
|
||||
source: ProfitSource::RoutingFee,
|
||||
amount_sats: 42,
|
||||
timestamp: "2025-01-01T00:00:00Z".to_string(),
|
||||
description: "routing".to_string(),
|
||||
};
|
||||
let json = serde_json::to_string(&entry).unwrap();
|
||||
assert!(json.contains("\"routing_fee\""));
|
||||
|
||||
let parsed: ProfitEntry = serde_json::from_str(&json).unwrap();
|
||||
assert!(matches!(parsed.source, ProfitSource::RoutingFee));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user