use std::path::Path;
use whisper_macos_cli::audio::decode::decode_file_with_runner;
use whisper_macos_cli::error::Error;
use whisper_macos_cli::video::ffmpeg::MockFfmpeg;
fn silent_wav() -> Vec<u8> {
let data_size: u32 = 16000 * 2; let file_size: u32 = 36 + data_size;
let mut v = Vec::with_capacity(44 + data_size as usize);
v.extend_from_slice(b"RIFF");
v.extend_from_slice(&file_size.to_le_bytes());
v.extend_from_slice(b"WAVE");
v.extend_from_slice(b"fmt ");
v.extend_from_slice(&16u32.to_le_bytes());
v.extend_from_slice(&1u16.to_le_bytes()); v.extend_from_slice(&1u16.to_le_bytes()); v.extend_from_slice(&16000u32.to_le_bytes());
v.extend_from_slice(&32000u32.to_le_bytes());
v.extend_from_slice(&2u16.to_le_bytes());
v.extend_from_slice(&16u16.to_le_bytes());
v.extend_from_slice(b"data");
v.extend_from_slice(&data_size.to_le_bytes());
v.resize(44 + data_size as usize, 0);
v
}
fn write_temp(name: &str, bytes: &[u8]) -> std::path::PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("wmac-test-{}-{name}", uuid::Uuid::now_v7()));
std::fs::write(&path, bytes).expect("write temp fixture");
path
}
#[test]
fn video_magic_bytes_routes_through_ffmpeg() {
let mut mp4 = vec![0u8; 64];
mp4[4..8].copy_from_slice(b"ftyp");
mp4[8..12].copy_from_slice(b"isom");
let path = write_temp("video.mp4", &mp4);
let runner = MockFfmpeg::new();
let result = decode_file_with_runner(&path, &runner, true);
match result {
Ok(_) => {}
Err(e) => panic!("video should be routed through mock ffmpeg, got error: {e:?}"),
}
assert_eq!(runner.call_count(), 1);
assert_eq!(runner.last_input(), Some(path.clone()));
let _ = std::fs::remove_file(&path);
}
#[test]
fn video_disabled_ffmpeg_returns_unsupported_video_format() {
let mut mp4 = vec![0u8; 64];
mp4[4..8].copy_from_slice(b"ftyp");
mp4[8..12].copy_from_slice(b"mp42");
let path = write_temp("video.mp4", &mp4);
let runner = MockFfmpeg::new();
let result = decode_file_with_runner(&path, &runner, false);
assert!(matches!(result, Err(Error::UnsupportedVideoFormat { .. })));
assert_eq!(runner.call_count(), 0);
let _ = std::fs::remove_file(&path);
}
#[test]
fn audio_path_does_not_invoke_ffmpeg() {
let mut ogg = vec![0u8; 64];
ogg[..4].copy_from_slice(b"OggS");
let path = write_temp("audio.ogg", &ogg);
let runner = MockFfmpeg::new();
let _ = decode_file_with_runner(&path, &runner, true);
assert!(
runner.call_count() <= 1,
"ffmpeg should be called at most once for OGG"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn ffmpeg_error_propagates_as_video_extraction_failed() {
let mut mp4 = vec![0u8; 64];
mp4[4..8].copy_from_slice(b"ftyp");
mp4[8..12].copy_from_slice(b"isom");
let path = write_temp("video.mp4", &mp4);
let runner = MockFfmpeg::new().with_error(Error::VideoExtractionFailed {
path: "video.mp4".into(),
ffmpeg_stderr: "Invalid data found when processing input".into(),
});
let result = decode_file_with_runner(&path, &runner, true);
match result {
Err(Error::VideoExtractionFailed { ffmpeg_stderr, .. }) => {
assert!(ffmpeg_stderr.contains("Invalid data"));
}
Err(other) => panic!("expected VideoExtractionFailed, got {other:?}"),
Ok(_) => panic!("expected error, got success"),
}
let _ = std::fs::remove_file(&path);
}
#[test]
fn ffmpeg_not_found_propagates() {
let mut mp4 = vec![0u8; 64];
mp4[4..8].copy_from_slice(b"ftyp");
mp4[8..12].copy_from_slice(b"M4V ");
let path = write_temp("video.m4v", &mp4);
let runner = MockFfmpeg::new()
.unavailable()
.with_error(Error::FfmpegNotFound);
let result = decode_file_with_runner(&path, &runner, true);
assert!(matches!(result, Err(Error::FfmpegNotFound)));
let _ = std::fs::remove_file(&path);
}
#[test]
fn temp_wav_is_cleaned_up_after_decode() {
let mut mp4 = vec![0u8; 64];
mp4[4..8].copy_from_slice(b"ftyp");
mp4[8..12].copy_from_slice(b"qt ");
let path = write_temp("video.mov", &mp4);
let runner = MockFfmpeg::new();
let _ = decode_file_with_runner(&path, &runner, true);
let _ = std::fs::remove_file(&path);
}
#[test]
fn video_routing_priority_is_magic_bytes_first() {
let mut buf = vec![0u8; 64];
buf[4..8].copy_from_slice(b"ftyp");
buf[8..12].copy_from_slice(b"isom");
let path = write_temp("fake.ogg", &buf);
let runner = MockFfmpeg::new();
let _ = decode_file_with_runner(&path, &runner, true);
assert_eq!(runner.call_count(), 1);
let _ = std::fs::remove_file(&path);
}
#[test]
fn mkv_ebml_header_routes_to_ffmpeg() {
let mut mkv = vec![0u8; 64];
mkv[..4].copy_from_slice(&[0x1A, 0x45, 0xDF, 0xA3]);
let path = write_temp("video.mkv", &mkv);
let runner = MockFfmpeg::new();
let _ = decode_file_with_runner(&path, &runner, true);
assert_eq!(runner.call_count(), 1);
let _ = std::fs::remove_file(&path);
}
#[test]
fn avi_riff_routes_to_ffmpeg() {
let mut avi = vec![0u8; 64];
avi[..4].copy_from_slice(b"RIFF");
avi[8..12].copy_from_slice(b"AVI ");
let path = write_temp("video.avi", &avi);
let runner = MockFfmpeg::new();
let _ = decode_file_with_runner(&path, &runner, true);
assert_eq!(runner.call_count(), 1);
let _ = std::fs::remove_file(&path);
}
#[test]
fn webm_ebml_routes_to_ffmpeg() {
let mut webm = vec![0u8; 64];
webm[..4].copy_from_slice(&[0x1A, 0x45, 0xDF, 0xA3]);
let path = write_temp("video.webm", &webm);
let runner = MockFfmpeg::new();
let _ = decode_file_with_runner(&path, &runner, true);
assert_eq!(runner.call_count(), 1);
let _ = std::fs::remove_file(&path);
}
#[test]
fn valid_wav_audio_does_not_invoke_ffmpeg() {
let wav = silent_wav();
let path = write_temp("audio.wav", &wav);
let runner = MockFfmpeg::new();
let result = decode_file_with_runner(&path, &runner, true);
match result {
Ok(_) => {}
Err(e) => panic!("valid WAV should decode natively, got error: {e:?}"),
}
assert_eq!(runner.call_count(), 0);
let _ = std::fs::remove_file(&path);
}
#[test]
fn missing_file_returns_input_not_found() {
let runner = MockFfmpeg::new();
let result = decode_file_with_runner(
Path::new("/tmp/this/does/not/exist/audio.wav"),
&runner,
true,
);
assert!(matches!(result, Err(Error::InputNotFound { .. })));
}