#![cfg(feature = "pipecat")]
use wavekat_turn::audio::PipecatSmartTurn;
use wavekat_turn::{AudioFrame, AudioTurnDetector, TurnPrediction};
fn silence(num_samples: usize) -> AudioFrame<'static> {
let samples = vec![0.0f32; num_samples];
AudioFrame::new(samples.as_slice(), 16_000).into_owned()
}
fn push_silence(detector: &mut PipecatSmartTurn, duration_secs: f32) {
let total = (duration_secs * 16_000.0) as usize;
let chunk = 160;
let mut pushed = 0;
while pushed < total {
let n = chunk.min(total - pushed);
detector.push_audio(&silence(n));
pushed += n;
}
}
fn valid_prediction(pred: &TurnPrediction) {
assert!(
pred.confidence >= 0.0 && pred.confidence <= 1.0,
"confidence out of range: {}",
pred.confidence
);
}
#[test]
fn test_new_loads_model() {
PipecatSmartTurn::new().expect("PipecatSmartTurn::new() should succeed");
}
#[test]
fn test_from_file_loads_model() {
let tmp = std::env::temp_dir().join("wavekat_turn_test");
std::fs::create_dir_all(&tmp).unwrap();
let path = tmp.join("smart-turn-test.onnx");
let model_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/smart-turn-v3.2-cpu.onnx"));
std::fs::write(&path, model_bytes).unwrap();
PipecatSmartTurn::from_file(&path).expect("from_file should succeed with a valid model");
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_predict_returns_valid_output() {
let mut d = PipecatSmartTurn::new().unwrap();
push_silence(&mut d, 2.0);
let pred = d.predict().unwrap();
valid_prediction(&pred);
}
#[test]
fn test_predict_with_empty_buffer() {
let mut d = PipecatSmartTurn::new().unwrap();
let pred = d.predict().unwrap();
valid_prediction(&pred);
}
#[test]
fn test_push_audio_wrong_sample_rate_is_ignored() {
let mut d = PipecatSmartTurn::new().unwrap();
let bad = AudioFrame::new(vec![0.5f32; 160].as_slice(), 8_000).into_owned();
d.push_audio(&bad);
let pred = d.predict().unwrap();
valid_prediction(&pred);
}
#[test]
fn test_reset_clears_buffer() {
let mut d = PipecatSmartTurn::new().unwrap();
push_silence(&mut d, 4.0);
d.reset();
let fresh = PipecatSmartTurn::new().unwrap().predict().unwrap();
let after_reset = d.predict().unwrap();
assert_eq!(
after_reset.state, fresh.state,
"state after reset should match a fresh instance"
);
assert!(
(after_reset.confidence - fresh.confidence).abs() < 1e-5,
"confidence after reset should match a fresh instance"
);
}
#[test]
fn test_ring_buffer_caps_at_8_seconds() {
let mut d = PipecatSmartTurn::new().unwrap();
push_silence(&mut d, 10.0); valid_prediction(&d.predict().unwrap());
}
#[test]
fn test_multiple_predicts_are_deterministic() {
let mut d = PipecatSmartTurn::new().unwrap();
push_silence(&mut d, 2.0);
let p1 = d.predict().unwrap();
let p2 = d.predict().unwrap();
assert_eq!(
p1.state, p2.state,
"repeated predict should give same state"
);
assert!(
(p1.confidence - p2.confidence).abs() < 1e-5,
"repeated predict should give same confidence"
);
}
#[test]
#[cfg(not(debug_assertions))]
fn test_latency_under_50ms() {
let mut d = PipecatSmartTurn::new().unwrap();
push_silence(&mut d, 2.0);
let pred = d.predict().unwrap();
assert!(
pred.latency_ms < 50,
"inference too slow: {} ms (limit: 50 ms)",
pred.latency_ms
);
}
#[test]
fn test_from_file_invalid_path_returns_error() {
let result = PipecatSmartTurn::from_file("/nonexistent/path/model.onnx");
assert!(
result.is_err(),
"from_file with invalid path should return an error"
);
}
#[test]
fn test_latency_is_measured() {
let mut d = PipecatSmartTurn::new().unwrap();
push_silence(&mut d, 2.0);
let pred = d.predict().unwrap();
assert!(
pred.latency_ms < 60_000,
"latency suspiciously large: {} ms",
pred.latency_ms
);
}