use std::fs;
use talk_rs::audio::mock::MockAudioCapture;
use talk_rs::audio::{AudioCapture, AudioEncoder, OpusEncoder};
use talk_rs::config::AudioConfig;
use tempfile::TempDir;
use tokio::io::AsyncWriteExt;
#[tokio::test]
async fn test_record_with_mock_capture_creates_file() {
let temp_dir = TempDir::new().expect("create temp dir");
let output_path = temp_dir.path().join("test-recording.ogg");
let audio_config = AudioConfig {
sample_rate: 16_000,
channels: 1,
bitrate: 32_000,
};
let mut capture = MockAudioCapture::new(audio_config.sample_rate, audio_config.channels, 440.0);
let mut rx = capture.start().expect("start capture");
let mut encoder = OpusEncoder::new(audio_config).expect("create encoder");
let mut file = tokio::fs::File::create(&output_path)
.await
.expect("create file");
for _ in 0..3 {
if let Some(pcm_chunk) = rx.recv().await {
let encoded_data = encoder.encode(&pcm_chunk).expect("encode");
if !encoded_data.is_empty() {
file.write_all(&encoded_data).await.expect("write to file");
}
}
}
let remaining_data = encoder.flush().expect("flush");
if !remaining_data.is_empty() {
file.write_all(&remaining_data)
.await
.expect("write flushed data");
}
file.sync_all().await.expect("sync file");
capture.stop().expect("stop capture");
let metadata = fs::metadata(&output_path).expect("get file metadata");
assert!(
metadata.len() > 0,
"output file should have content, got {} bytes",
metadata.len()
);
let file_content = fs::read(&output_path).expect("read file");
assert!(!file_content.is_empty(), "file should contain encoded data");
assert!(
file_content.iter().any(|&b| b != 0),
"file should contain non-zero data"
);
}
#[test]
fn test_record_default_filename_format() {
use chrono::{DateTime, Datelike, Local};
use std::path::PathBuf;
let before = Local::now();
let filename = before.format("%Y-%m-%dT%H-%M-%S%z.ogg").to_string();
let after = Local::now();
let path = PathBuf::from(&filename);
let filename_str = path
.file_name()
.expect("should have filename")
.to_string_lossy();
assert!(
filename_str.ends_with(".ogg"),
"filename should end with ``.ogg``, got {}",
filename_str
);
let stem = filename_str
.strip_suffix(".ogg")
.expect("filename ends with .ogg");
let parsed = DateTime::parse_from_str(stem, "%Y-%m-%dT%H-%M-%S%z")
.unwrap_or_else(|e| panic!("filename stem {} should parse: {}", stem, e));
let offset_secs = parsed.offset().local_minus_utc();
assert!(
offset_secs.abs() < 24 * 3600,
"parsed offset {} seconds should be a valid timezone",
offset_secs
);
assert!(
parsed >= before - chrono::Duration::seconds(1),
"parsed {} should not be earlier than before {}",
parsed,
before
);
assert!(
parsed <= after + chrono::Duration::seconds(1),
"parsed {} should not be later than after {}",
parsed,
after
);
assert!(
(2000..=9999).contains(&parsed.year()),
"year {} should be 4-digit",
parsed.year()
);
assert!(
(1..=12).contains(&parsed.month()),
"month {} out of range",
parsed.month()
);
assert!(
(1..=31).contains(&parsed.day()),
"day {} out of range",
parsed.day()
);
}
#[cfg(feature = "capture")]
#[tokio::test]
#[ignore]
async fn test_record_creates_output_file() {
use talk_rs::audio::cpal_capture::CpalCapture;
let temp_dir = TempDir::new().expect("create temp dir");
let output_path = temp_dir.path().join("test-recording-hardware.ogg");
let audio_config = AudioConfig {
sample_rate: 16_000,
channels: 1,
bitrate: 32_000,
};
let mut capture = CpalCapture::new(audio_config.clone());
let mut rx = capture.start().expect("start capture");
let mut encoder = OpusEncoder::new(audio_config).expect("create encoder");
let mut file = tokio::fs::File::create(&output_path)
.await
.expect("create file");
let mut chunks_recorded = 0;
let target_chunks = 50;
while chunks_recorded < target_chunks {
if let Some(pcm_chunk) = rx.recv().await {
let encoded_data = encoder.encode(&pcm_chunk).expect("encode");
if !encoded_data.is_empty() {
file.write_all(&encoded_data).await.expect("write to file");
}
chunks_recorded += 1;
}
}
let remaining_data = encoder.flush().expect("flush");
if !remaining_data.is_empty() {
file.write_all(&remaining_data)
.await
.expect("write flushed data");
}
file.sync_all().await.expect("sync file");
capture.stop().expect("stop capture");
let metadata = fs::metadata(&output_path).expect("get file metadata");
assert!(
metadata.len() > 0,
"output file should have content, got {} bytes",
metadata.len()
);
let file_content = fs::read(&output_path).expect("read file");
assert!(!file_content.is_empty(), "file should contain encoded data");
assert!(
file_content.iter().any(|&b| b != 0),
"file should contain non-zero data"
);
}