Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub mod api;
pub mod auth;
pub mod config;
pub mod database;
pub mod errors;
pub mod middleware;
pub mod repositories;
pub mod services;
pub mod utils;
2 changes: 1 addition & 1 deletion backend/src/services/node_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub struct LndNode {
}

/// Parses the node features from the format returned by LND gRPC to LDK NodeFeatures
fn parse_node_features(features: HashSet<u32>) -> NodeFeatures {
pub fn parse_node_features(features: HashSet<u32>) -> NodeFeatures {
let mut flags = vec![0; 256];

for f in features.into_iter() {
Expand Down
6 changes: 4 additions & 2 deletions backend/src/utils/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
//! ## Usage
//!
//! ```rust
//! let encrypted = StringCrypto::encrypt("secret data")?;
//! let decrypted = StringCrypto::decrypt(&encrypted)?;
//! use backend::utils::crypto::StringCrypto;
//! let encrypted = StringCrypto::encrypt("secret data").unwrap();
//! let decrypted = StringCrypto::decrypt(&encrypted).unwrap();
//! assert_eq!(decrypted, "secret data");
//! ```

use crate::config::Config;
Expand Down
1 change: 1 addition & 0 deletions backend/src/utils/generate_random_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use rand::{Rng, distributions::Alphanumeric};
/// # Examples
///
/// ```
/// use backend::utils::generate_random_string::generate_random_string;
/// let token = generate_random_string(32);
/// assert_eq!(token.len(), 32);
///
Expand Down
2 changes: 1 addition & 1 deletion backend/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ pub enum InvoiceStatus {
Failed,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
pub enum ChannelState {
Opening, // funding tx not confirmed
#[default]
Expand Down
6 changes: 3 additions & 3 deletions backend/src/utils/sats_to_usd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl PriceConverter {
Self::round_to_2_decimals(btc_amount * btc_price)
}

fn round_to_2_decimals(value: f64) -> f64 {
pub fn round_to_2_decimals(value: f64) -> f64 {
(value * 100.0).round() / 100.0
}

Expand Down Expand Up @@ -71,7 +71,7 @@ impl PriceConverter {
}
}

async fn check_cache(&self) -> Option<f64> {
pub async fn check_cache(&self) -> Option<f64> {
let cache = self.cache.read().await;
cache.as_ref().and_then(|c| {
c.last_updated
Expand Down Expand Up @@ -99,7 +99,7 @@ impl PriceConverter {
Ok(price_data.usd)
}

async fn update_cache(&self, price: f64) {
pub async fn update_cache(&self, price: f64) {
let mut cache = self.cache.write().await;
*cache = Some(PriceCache {
price,
Expand Down
29 changes: 29 additions & 0 deletions backend/tests/handlers_common_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use backend::utils::handlers_common::{parse_payment_hash, parse_public_key};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};

fn valid_public_key() -> String {
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&[0xcd; 32]).expect("valid secret key");
PublicKey::from_secret_key(&secp, &secret_key).to_string()
}

const VALID_HASH: &str = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2";

#[test]
fn parse_payment_hash_validates_length_and_format() {
let (_, body) = parse_payment_hash("a1b2").unwrap_err();
assert!(body.contains("invalid_payment_hash_length"));

let (_, body) = parse_payment_hash("xyz!").unwrap_err();
assert!(body.contains("invalid_payment_hash"));

assert!(parse_payment_hash(VALID_HASH).is_ok());
}

#[test]
fn parse_public_key_rejects_invalid_and_accepts_valid() {
let (_, body) = parse_public_key("not-a-key").unwrap_err();
assert!(body.contains("invalid_public_key"));

assert!(parse_public_key(&valid_public_key()).is_ok());
}
94 changes: 94 additions & 0 deletions backend/tests/lightning_types_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use std::str::FromStr;

use backend::utils::*;

fn create_test_pubkey() -> PublicKey {
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&[0xcd; 32]).unwrap();
PublicKey::from_secret_key(&secp, &secret_key)
}

fn create_alt_test_pubkey() -> PublicKey {
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&[0xab; 32]).unwrap();
PublicKey::from_secret_key(&secp, &secret_key)
}

#[test]
fn test_node_id_validation() {
let pk = create_test_pubkey();
let pk2 = create_alt_test_pubkey();
let mut alias = String::from("test_alias");

// Valid pubkey validation
let node_id = NodeId::PublicKey(pk);
assert!(node_id.validate(&pk, &mut alias).is_ok());

// Mismatched pubkey should fail
assert!(node_id.validate(&pk2, &mut alias).is_err());

// Valid alias validation
let node_id = NodeId::Alias("test_alias".to_string());
assert!(node_id.validate(&pk, &mut alias).is_ok());

// Mismatched alias should fail
alias = String::from("wrong_alias");
assert!(node_id.validate(&pk, &mut alias).is_err());
}

#[test]
fn test_payment_state_parsing() {
// Case-insensitive parsing
assert_eq!(
PaymentState::from_str("inflight").unwrap(),
PaymentState::Inflight
);
assert_eq!(
PaymentState::from_str("FAILED").unwrap(),
PaymentState::Failed
);
assert_eq!(
PaymentState::from_str("Settled").unwrap(),
PaymentState::Settled
);

// Invalid input should fail
assert!(PaymentState::from_str("invalid").is_err());
}

#[test]
fn test_payment_type_parsing() {
// Case-insensitive parsing
assert_eq!(
PaymentType::from_str("OUTGOING").unwrap().as_str(),
"outgoing"
);
assert_eq!(
PaymentType::from_str("incoming").unwrap().as_str(),
"incoming"
);
assert_eq!(
PaymentType::from_str("Forwarded").unwrap().as_str(),
"forwarded"
);

// Invalid input should fail
assert!(PaymentType::from_str("invalid").is_err());
}

#[test]
fn test_channel_state_parsing() {
// Case-insensitive parsing
assert_eq!(
ChannelState::from_str("ACTIVE").unwrap(),
ChannelState::Active
);
assert_eq!(
ChannelState::from_str("disabled").unwrap(),
ChannelState::Disabled
);

// Invalid input should fail
assert!(ChannelState::from_str("invalid").is_err());
}
30 changes: 30 additions & 0 deletions backend/tests/node_manager_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use backend::services::node_manager::{parse_channel_point, parse_node_features};
use std::collections::HashSet;
use std::str::FromStr;
use bitcoin::Txid;

#[test]
fn test_parse_node_features_empty() {
let features = HashSet::new();
let parsed = parse_node_features(features);
assert!(!parsed.supports_basic_mpp());
assert!(!parsed.requires_payment_secret());
}

#[test]
fn test_parse_channel_point_valid() {
let txid = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2";
let channel_point = format!("{}:1", txid);
let result = parse_channel_point(&channel_point).unwrap();

assert_eq!(result.vout, 1);
assert_eq!(result.txid, Txid::from_str(txid).unwrap());
}

#[test]
fn test_parse_channel_point_invalid_formats() {
assert!(parse_channel_point(":1").is_err());
assert!(parse_channel_point("txid:").is_err());
assert!(parse_channel_point("txid").is_err());
assert!(parse_channel_point("invalid_txid:1").is_err());
}
39 changes: 39 additions & 0 deletions backend/tests/sats_to_usd_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use backend::utils::sats_to_usd::PriceConverter;

#[test]
fn test_sats_to_usd_with_price() {
assert_eq!(
PriceConverter::sats_to_usd_with_price(100_000_000, 50_000.0),
50_000.0
);
assert_eq!(
PriceConverter::sats_to_usd_with_price(50_000_000, 50_000.0),
25_000.0
);
assert_eq!(PriceConverter::sats_to_usd_with_price(1_000, 50_000.0), 0.5);
assert_eq!(PriceConverter::sats_to_usd_with_price(0, 50_000.0), 0.0);
}

#[test]
fn test_round_to_2_decimals() {
assert_eq!(PriceConverter::round_to_2_decimals(123.456), 123.46);
assert_eq!(PriceConverter::round_to_2_decimals(123.454), 123.45);
assert_eq!(PriceConverter::round_to_2_decimals(99.999), 100.0);
}

#[tokio::test]
async fn test_cache_update_and_retrieval() {
let converter = PriceConverter::new();
converter.update_cache(50_000.0).await;

assert_eq!(converter.check_cache().await, Some(50_000.0));
}

#[tokio::test]
async fn test_sats_to_usd_with_cached_price() {
let converter = PriceConverter::new();
converter.update_cache(60_000.0).await;

let result = converter.sats_to_usd(100_000_000).await.unwrap();
assert_eq!(result, 60_000.0);
}