use blvm_node::config::PaymentConfig;
#[cfg(feature = "ctv")]
use blvm_node::payment::covenant::{CovenantEngine, CovenantProof};
use blvm_node::payment::processor::PaymentProcessor;
use blvm_node::payment::settlement::SettlementMonitor;
use blvm_node::payment::state_machine::{PaymentState, PaymentStateMachine};
use blvm_protocol::payment::PaymentOutput;
use std::sync::Arc;
use tempfile::TempDir;
fn create_test_outputs() -> Vec<PaymentOutput> {
create_test_outputs_unique(0)
}
fn create_test_outputs_unique(salt: u64) -> Vec<PaymentOutput> {
vec![
PaymentOutput {
script: vec![0x51, 0x87], amount: Some(100000 + salt), },
PaymentOutput {
script: vec![0x52, 0x87], amount: Some(50000 + salt), },
]
}
#[tokio::test]
async fn test_payment_request_creation() {
let temp_dir = TempDir::new().unwrap();
let config = PaymentConfig::default();
let processor =
Arc::new(PaymentProcessor::new(config).expect("Failed to create payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let outputs = create_test_outputs();
let merchant_data = Some(b"test_merchant_data".to_vec());
let (payment_id, covenant_proof) = state_machine
.create_payment_request(outputs.clone(), merchant_data.clone(), false)
.await
.expect("Failed to create payment request");
assert!(!payment_id.is_empty(), "Payment ID should not be empty");
assert_eq!(
covenant_proof, None,
"Covenant proof should be None when create_covenant=false"
);
let state = state_machine
.get_payment_state(&payment_id)
.await
.expect("Failed to get payment state");
match state {
PaymentState::RequestCreated { request_id } => {
assert_eq!(request_id, payment_id);
}
_ => panic!("Expected RequestCreated state, got {:?}", state),
}
}
#[tokio::test]
#[cfg(feature = "ctv")]
async fn test_covenant_proof_creation() {
let temp_dir = TempDir::new().unwrap();
let config = PaymentConfig::default();
let processor =
Arc::new(PaymentProcessor::new(config).expect("Failed to create payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let outputs = create_test_outputs();
let (payment_id, covenant_proof) = state_machine
.create_payment_request(outputs.clone(), None, true)
.await
.expect("Failed to create payment request with covenant");
assert!(!payment_id.is_empty(), "Payment ID should not be empty");
assert!(
covenant_proof.is_some(),
"Covenant proof should be created when create_covenant=true"
);
let proof = covenant_proof.unwrap();
assert_eq!(
proof.payment_request_id, payment_id,
"Covenant proof should reference correct payment ID"
);
assert!(
!proof.template_hash.is_empty(),
"Template hash should not be empty"
);
let state = state_machine
.get_payment_state(&payment_id)
.await
.expect("Failed to get payment state");
match state {
PaymentState::ProofCreated { request_id, .. } => {
assert_eq!(request_id, payment_id);
}
_ => panic!("Expected ProofCreated state, got {:?}", state),
}
}
#[tokio::test]
async fn test_payment_state_transitions() {
let temp_dir = TempDir::new().unwrap();
let config = PaymentConfig::default();
let processor =
Arc::new(PaymentProcessor::new(config).expect("Failed to create payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let outputs = create_test_outputs();
let (payment_id, _) = state_machine
.create_payment_request(outputs, None, false)
.await
.expect("Failed to create payment request");
let state = state_machine
.get_payment_state(&payment_id)
.await
.expect("Failed to get payment state");
assert!(matches!(state, PaymentState::RequestCreated { .. }));
let tx_hash = [0x42u8; 32];
state_machine
.mark_in_mempool(&payment_id, tx_hash)
.await
.expect("Failed to mark in mempool");
let state = state_machine
.get_payment_state(&payment_id)
.await
.expect("Failed to get payment state");
match state {
PaymentState::InMempool {
request_id,
tx_hash: tx,
} => {
assert_eq!(request_id, payment_id);
assert_eq!(tx, tx_hash);
}
_ => panic!("Expected InMempool state, got {:?}", state),
}
let block_hash = [0x43u8; 32];
state_machine
.mark_settled(&payment_id, tx_hash, block_hash, 1, None)
.await
.expect("Failed to mark settled");
let state = state_machine
.get_payment_state(&payment_id)
.await
.expect("Failed to get payment state");
match state {
PaymentState::Settled {
request_id,
tx_hash: tx,
block_hash: block,
confirmation_count,
..
} => {
assert_eq!(request_id, payment_id);
assert_eq!(tx, tx_hash);
assert_eq!(block, block_hash);
assert_eq!(confirmation_count, 1);
}
_ => panic!("Expected Settled state, got {:?}", state),
}
}
#[tokio::test]
async fn test_payment_failure() {
let temp_dir = TempDir::new().unwrap();
let config = PaymentConfig::default();
let processor =
Arc::new(PaymentProcessor::new(config).expect("Failed to create payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let outputs = create_test_outputs();
let (payment_id, _) = state_machine
.create_payment_request(outputs, None, false)
.await
.expect("Failed to create payment request");
let reason = "Transaction rejected".to_string();
state_machine
.mark_failed(&payment_id, reason.clone())
.await
.expect("Failed to mark failed");
let state = state_machine
.get_payment_state(&payment_id)
.await
.expect("Failed to get payment state");
match state {
PaymentState::Failed {
request_id,
reason: r,
} => {
assert_eq!(request_id, payment_id);
assert_eq!(r, reason);
}
_ => panic!("Expected Failed state, got {:?}", state),
}
}
#[tokio::test]
async fn test_list_payments() {
let temp_dir = TempDir::new().unwrap();
let config = PaymentConfig::default();
let processor =
Arc::new(PaymentProcessor::new(config).expect("Failed to create payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let mut payment_ids = Vec::new();
for i in 0..3 {
let outputs = create_test_outputs_unique(i);
let (payment_id, _) = state_machine
.create_payment_request(outputs, None, false)
.await
.expect("Failed to create payment request");
payment_ids.push(payment_id);
}
let states = state_machine.list_payment_states();
assert!(
states.len() >= 3,
"Should have at least 3 payments, got {}",
states.len()
);
for payment_id in &payment_ids {
assert!(
states.contains_key(payment_id),
"Payment {} should be in list",
payment_id
);
}
}
#[tokio::test]
#[cfg(feature = "ctv")]
async fn test_instant_proof_functionality() {
let temp_dir = TempDir::new().unwrap();
let config = PaymentConfig::default();
let processor =
Arc::new(PaymentProcessor::new(config).expect("Failed to create payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let outputs = create_test_outputs();
let (payment_id, covenant_proof) = state_machine
.create_payment_request(outputs.clone(), None, true)
.await
.expect("Failed to create payment request with instant proof");
assert!(
covenant_proof.is_some(),
"Instant proof (covenant) should be created immediately"
);
let proof = covenant_proof.unwrap();
assert_eq!(
proof.payment_request_id, payment_id,
"Proof should reference correct payment ID"
);
assert!(
!proof.template_hash.is_empty(),
"Template hash should be present"
);
assert!(
proof.transaction_template.outputs.len() == outputs.len(),
"Template should have same number of outputs"
);
let state = state_machine
.get_payment_state(&payment_id)
.await
.expect("Failed to get payment state");
match &state {
PaymentState::ProofCreated { request_id, .. } => {
assert_eq!(request_id, &payment_id);
}
_ => panic!(
"Expected ProofCreated state for instant proof, got {:?}",
state
),
}
assert!(
!matches!(
&state,
PaymentState::InMempool { .. } | PaymentState::Settled { .. }
),
"Settlement should not have happened yet - instant proof is separate from settlement"
);
}
#[tokio::test]
#[cfg(feature = "ctv")]
async fn test_create_covenant_proof_for_existing_payment() {
let temp_dir = TempDir::new().unwrap();
let config = PaymentConfig::default();
let processor =
Arc::new(PaymentProcessor::new(config).expect("Failed to create payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let outputs = create_test_outputs();
let (payment_id, _) = state_machine
.create_payment_request(outputs, None, false)
.await
.expect("Failed to create payment request");
let covenant_proof = state_machine
.create_covenant_proof(&payment_id)
.await
.expect("Failed to create covenant proof");
assert_eq!(
covenant_proof.payment_request_id, payment_id,
"Covenant proof should reference correct payment ID"
);
let state = state_machine
.get_payment_state(&payment_id)
.await
.expect("Failed to get payment state");
match state {
PaymentState::ProofCreated { request_id, .. } => {
assert_eq!(request_id, payment_id);
}
_ => panic!("Expected ProofCreated state, got {:?}", state),
}
}
#[tokio::test]
async fn test_settlement_monitor_initialization() {
let temp_dir = TempDir::new().unwrap();
let config = PaymentConfig::default();
let processor =
Arc::new(PaymentProcessor::new(config).expect("Failed to create payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let monitor = SettlementMonitor::new(state_machine);
assert!(true, "Settlement monitor created successfully");
}
#[tokio::test]
async fn test_concurrent_payment_requests() {
let temp_dir = TempDir::new().unwrap();
let config = PaymentConfig::default();
let processor =
Arc::new(PaymentProcessor::new(config).expect("Failed to create payment processor"));
let state_machine = Arc::new(PaymentStateMachine::new(processor));
let handles: Vec<_> = (0..5)
.map(|i| {
let state_machine = Arc::clone(&state_machine);
let outputs = create_test_outputs_unique(i);
tokio::spawn(async move {
state_machine
.create_payment_request(outputs, None, false)
.await
})
})
.collect();
let mut payment_ids = Vec::new();
for handle in handles {
let result = handle.await.expect("Task should complete successfully");
let (payment_id, _) = result.expect("Payment request should succeed");
payment_ids.push(payment_id);
}
assert_eq!(payment_ids.len(), 5, "Should have 5 payment requests");
let unique_ids: std::collections::HashSet<String> = payment_ids.iter().cloned().collect();
assert_eq!(unique_ids.len(), 5, "All payment IDs should be unique");
let states = state_machine.list_payment_states();
for payment_id in &payment_ids {
assert!(
states.contains_key(payment_id),
"Payment {} should be in state machine",
payment_id
);
}
}