This commit is contained in:
Omar Sobh
2026-06-15 21:51:23 -07:00
parent 1f8beb6fea
commit 3f8d2b9d51
73 changed files with 13369 additions and 0 deletions
Binary file not shown.
+69
View File
@@ -0,0 +1,69 @@
//! HTTP-contract tests for the transcription service, using a fake transcriber so
//! they run fast with no model. The real whisper path is covered in `whisper.rs`.
use std::sync::Arc;
use async_trait::async_trait;
use serde_json::Value;
use transcription_svc::{build_router, AppState, Transcriber};
struct FakeTranscriber {
reply: String,
}
#[async_trait]
impl Transcriber for FakeTranscriber {
async fn transcribe(&self, audio: &[u8], _content_type: &str) -> anyhow::Result<String> {
assert!(!audio.is_empty(), "handler must not call transcriber on empty body");
Ok(self.reply.clone())
}
}
async fn spawn(reply: &str) -> (String, reqwest::Client) {
let state = AppState {
transcriber: Arc::new(FakeTranscriber {
reply: reply.to_string(),
}),
};
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
axum::serve(listener, build_router(state)).await.unwrap();
});
(format!("http://{addr}"), reqwest::Client::new())
}
#[tokio::test]
async fn transcribe_returns_text_json() {
let (base, client) = spawn("nfc read resolved quickly").await;
let res = client
.post(format!("{base}/transcribe"))
.header("content-type", "audio/webm")
.body(b"-some-audio-".to_vec())
.send()
.await
.unwrap();
assert_eq!(res.status(), 200);
let body: Value = res.json().await.unwrap();
assert_eq!(body["text"], "nfc read resolved quickly");
}
#[tokio::test]
async fn empty_body_is_400() {
let (base, client) = spawn("unused").await;
let res = client
.post(format!("{base}/transcribe"))
.header("content-type", "audio/webm")
.body(Vec::<u8>::new())
.send()
.await
.unwrap();
assert_eq!(res.status(), 400);
}
#[tokio::test]
async fn health_ok() {
let (base, client) = spawn("unused").await;
let res = client.get(format!("{base}/health")).send().await.unwrap();
assert_eq!(res.status(), 200);
}
+38
View File
@@ -0,0 +1,38 @@
//! End-to-end test of the real whisper.cpp engine: a recorded speech fixture is
//! decoded by ffmpeg and transcribed. Ignored by default because it needs a
//! downloaded model (`scripts/fetch-model.sh`) and ffmpeg on PATH; run with:
//! cargo test -p transcription-svc --test whisper -- --ignored
use std::path::PathBuf;
use transcription_svc::{whisper::WhisperTranscriber, Transcriber};
fn manifest(rel: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(rel)
}
#[tokio::test]
#[ignore = "needs a downloaded model and ffmpeg"]
async fn transcribes_real_speech() {
let model = std::env::var("WHISPER_MODEL")
.map(PathBuf::from)
.unwrap_or_else(|_| manifest("models/ggml-tiny.en.bin"));
assert!(
model.exists(),
"model not found at {model:?} — run scripts/fetch-model.sh"
);
let transcriber = WhisperTranscriber::new(model.to_str().unwrap()).expect("load model");
let audio = std::fs::read(manifest("tests/fixtures/speech.wav")).expect("read fixture");
let text = transcriber
.transcribe(&audio, "audio/wav")
.await
.expect("transcribe")
.to_lowercase();
assert!(
text.contains("fox"),
"expected 'fox' in transcript, got: {text:?}"
);
}