use anyhow::Result;
use sipbot::config::{AccountConfig, AnswerConfig, Config};
use sipbot::sip::SipBot;
use sipbot::stats::CallStats;
use std::sync::Arc;
use std::time::Duration;
use tokio::time::sleep;
use tokio_util::sync::CancellationToken;
#[tokio::test]
async fn test_call_flow() -> Result<()> {
let _ = tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init();
let server_addr = "127.0.0.1:5080";
let server_config = Config {
addr: Some(server_addr.to_string()),
external_ip: None,
recorders: Some("/tmp/recorders_test".to_string()),
accounts: vec![AccountConfig {
username: "server".to_string(),
domain: "127.0.0.1".to_string(),
register: Some(false),
answer: Some(AnswerConfig::Echo),
..Default::default()
}],
};
let client_addr = "127.0.0.1:5081";
let client_config = Config {
addr: Some(client_addr.to_string()),
external_ip: None,
recorders: None,
accounts: vec![AccountConfig {
username: "client".to_string(),
domain: "127.0.0.1".to_string(),
register: Some(false),
target: Some(format!("sip:server@{}", server_addr)),
hangup: Some(sipbot::config::HangupConfig {
code: 200,
after_secs: Some(5),
}),
..Default::default()
}],
};
let server_handle = tokio::spawn(async move {
let stats = Arc::new(CallStats::new());
let mut bot = SipBot::new(
server_config.accounts[0].clone(),
server_config,
stats,
false,
CancellationToken::new(),
);
if let Err(e) = bot.run_wait().await {
eprintln!("Server error: {:?}", e);
}
});
sleep(Duration::from_secs(1)).await;
let client_handle = tokio::spawn(async move {
let stats = Arc::new(CallStats::new());
let mut bot = SipBot::new(
client_config.accounts[0].clone(),
client_config,
stats,
false,
CancellationToken::new(),
);
if let Err(e) = bot.run_call(1, 1).await {
eprintln!("Client error: {:?}", e);
panic!("Client failed: {:?}", e);
}
});
match tokio::time::timeout(Duration::from_secs(10), client_handle).await {
Ok(res) => match res {
Ok(_) => println!("Client finished successfully"),
Err(e) => panic!("Client task panicked: {:?}", e),
},
Err(_) => panic!("Test timed out"),
}
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_options_flow() -> Result<()> {
let _ = tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init();
let server_addr = "127.0.0.1:5070";
let server_config = Config {
addr: Some(server_addr.to_string()),
external_ip: None,
recorders: None,
accounts: vec![AccountConfig {
username: "server".to_string(),
domain: "127.0.0.1".to_string(),
register: Some(false),
..Default::default()
}],
};
let client_addr = "127.0.0.1:5071";
let client_config = Config {
addr: Some(client_addr.to_string()),
external_ip: None,
recorders: None,
accounts: vec![AccountConfig {
username: "client".to_string(),
domain: "127.0.0.1".to_string(),
register: Some(false),
target: Some(format!("sip:server@{}", server_addr)),
..Default::default()
}],
};
let server_handle = tokio::spawn(async move {
let stats = Arc::new(CallStats::new());
let mut bot = SipBot::new(
server_config.accounts[0].clone(),
server_config,
stats,
false,
CancellationToken::new(),
);
if let Err(e) = bot.run_wait().await {
eprintln!("Server error: {:?}", e);
}
});
sleep(Duration::from_secs(1)).await;
let client_handle = tokio::spawn(async move {
let stats = Arc::new(CallStats::new());
let mut bot = SipBot::new(
client_config.accounts[0].clone(),
client_config,
stats,
false,
CancellationToken::new(),
);
if let Err(e) = bot.run_options(None).await {
eprintln!("Client error: {:?}", e);
panic!("Client failed: {:?}", e);
}
});
match tokio::time::timeout(Duration::from_secs(5), client_handle).await {
Ok(res) => match res {
Ok(_) => println!("Client finished successfully"),
Err(e) => panic!("Client task panicked: {:?}", e),
},
Err(_) => panic!("Test timed out"),
}
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn test_wait_echo_tx_rx_stats() -> Result<()> {
let _ = tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init();
let server_addr = "127.0.0.1:5090";
let server_config = Config {
addr: Some(server_addr.to_string()),
external_ip: None,
recorders: None,
accounts: vec![AccountConfig {
username: "server-echo".to_string(),
domain: "127.0.0.1".to_string(),
register: Some(false),
answer: Some(AnswerConfig::Echo),
..Default::default()
}],
};
let client_addr = "127.0.0.1:5091";
let client_config = Config {
addr: Some(client_addr.to_string()),
external_ip: None,
recorders: None,
accounts: vec![AccountConfig {
username: "client-echo".to_string(),
domain: "127.0.0.1".to_string(),
register: Some(false),
target: Some(format!("sip:server-echo@{}", server_addr)),
hangup: Some(sipbot::config::HangupConfig {
code: 200,
after_secs: Some(4),
}),
..Default::default()
}],
};
let server_stats = Arc::new(CallStats::new());
let server_cancel = CancellationToken::new();
let server_cancel_for_task = server_cancel.clone();
let server_stats_for_task = server_stats.clone();
let server_handle = tokio::spawn(async move {
let mut bot = SipBot::new(
server_config.accounts[0].clone(),
server_config,
server_stats_for_task,
false,
server_cancel_for_task,
);
if let Err(e) = bot.run_wait().await {
eprintln!("Server error: {:?}", e);
}
});
sleep(Duration::from_secs(1)).await;
let client_handle = tokio::spawn(async move {
let stats = Arc::new(CallStats::new());
let mut bot = SipBot::new(
client_config.accounts[0].clone(),
client_config,
stats,
false,
CancellationToken::new(),
);
if let Err(e) = bot.run_call(1, 1).await {
eprintln!("Client error: {:?}", e);
panic!("Client failed: {:?}", e);
}
});
match tokio::time::timeout(Duration::from_secs(12), client_handle).await {
Ok(res) => match res {
Ok(_) => println!("Client finished successfully"),
Err(e) => panic!("Client task panicked: {:?}", e),
},
Err(_) => panic!("Test timed out waiting for client"),
}
sleep(Duration::from_millis(300)).await;
let rx = server_stats
.rx_packets
.load(std::sync::atomic::Ordering::Relaxed);
let tx = server_stats
.tx_packets
.load(std::sync::atomic::Ordering::Relaxed);
assert!(rx > 0, "Expected server RX packets > 0, got {}", rx);
assert!(
tx > 0,
"Expected server TX packets > 0 in echo mode, got {}",
tx
);
let echo_ratio = tx as f64 / rx as f64;
assert!(
echo_ratio >= 0.30,
"Expected practical echo ratio >= 0.30 in integration test, got tx={} rx={} ratio={:.3}",
tx,
rx,
echo_ratio
);
server_cancel.cancel();
let _ = tokio::time::timeout(Duration::from_secs(3), server_handle).await;
Ok(())
}