refactor(tests): migrate remaining batches to tests/common/

Finishes the lift of shared helpers into `tests/common/`. Covered:
- health, web, oneliners_api, upload (31 tests; upload exercises the
  new `multipart_upload_body` helper driven by doctate_common field
  constants)
- sse_integration, sse_cleanup, transcribe, oneliner_heal_decoupled,
  silent_case_empty, failed_only_case_empty_oneliner,
  transient_failure_retries (24 tests)

Also applied cargo fmt across the test tree and fixed one clippy
needless_borrows_for_generic_args warning in analyze_test.

All 387 tests pass; 3 ignored (as before).

Side effect: health_test previously used a hardcoded `/tmp/doctate-test`
data path, which parallel `cargo test` runs could collide on. The
migration replaces it with the common unique-tmpdir pattern, removing
a latent flake.
This commit is contained in:
2026-04-22 10:51:33 +02:00
parent f438e972d4
commit af3377a6bc
21 changed files with 374 additions and 793 deletions
+24 -31
View File
@@ -1,9 +1,10 @@
mod common;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use std::sync::Arc;
use doctate_server::config::{Config, User, WhisperUserSettings};
use doctate_server::config::WhisperUserSettings;
use doctate_server::transcribe;
use doctate_server::transcribe::ffmpeg::remux_faststart;
use doctate_server::transcribe::ollama::{OllamaError, generate_oneliner};
@@ -13,6 +14,8 @@ use serde_json::json;
use wiremock::matchers::{body_partial_json, method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};
use common::{TestConfig, test_user};
fn fixture(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
@@ -257,25 +260,15 @@ async fn recovery_enqueues_only_pending_recordings() {
// -------------------- Worker: failure marker --------------------
fn test_config_with_whisper(whisper_url: String) -> Arc<Config> {
Arc::new(Config {
users: vec![User {
slug: "dr_test".into(),
api_key: "k".into(),
web_password: "unused".into(),
role: "doctor".into(),
whisper: Default::default(),
retention: Default::default(),
window_hours: 72,
preview_lines: 2,
}],
whisper_url,
whisper_timeout_seconds: 5,
// Ollama URL is irrelevant — worker never reaches it in the failure path.
ollama_url: "http://127.0.0.1:1".into(),
ollama_model: "test".into(),
..Config::test_default()
})
fn config_with_whisper(whisper_url: String) -> std::sync::Arc<doctate_server::config::Config> {
// Ollama URL is irrelevant — worker never reaches it in the failure path.
// Port 1 is effectively unreachable, matching the historical intent.
TestConfig::new()
.with_label("transcribe")
.with_user(test_user("dr_test"))
.with_whisper(whisper_url, 5)
.with_ollama("http://127.0.0.1:1")
.build()
}
/// Permanent Whisper errors (4xx other than 408/429) must rename `.m4a` to
@@ -299,7 +292,7 @@ async fn worker_renames_audio_to_failed_on_permanent_whisper_error() {
let audio = case_dir.join("2026-04-13T10-30-00Z.m4a");
std::fs::copy(fixture("sample.m4a"), &audio).unwrap();
let config = test_config_with_whisper(server.uri());
let config = config_with_whisper(server.uri());
let (tx, rx) = transcribe::channel();
tx.send(transcribe::TranscribeJob {
audio_path: audio.clone(),
@@ -310,8 +303,8 @@ async fn worker_renames_audio_to_failed_on_permanent_whisper_error() {
drop(tx); // close channel so the worker loop exits after processing the job
let client = reqwest::Client::new();
let busy = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let vocab = std::sync::Arc::new(doctate_server::gazetteer::Gazetteer::empty());
let busy = Arc::new(std::sync::atomic::AtomicBool::new(false));
let vocab = Arc::new(doctate_server::gazetteer::Gazetteer::empty());
transcribe::worker::run(
rx,
config,
@@ -347,7 +340,7 @@ async fn worker_leaves_m4a_intact_on_transient_whisper_error() {
let audio = case_dir.join("2026-04-13T10-31-00Z.m4a");
std::fs::copy(fixture("sample.m4a"), &audio).unwrap();
let config = test_config_with_whisper(server.uri());
let config = config_with_whisper(server.uri());
let (tx, rx) = transcribe::channel();
tx.send(transcribe::TranscribeJob {
audio_path: audio.clone(),
@@ -358,8 +351,8 @@ async fn worker_leaves_m4a_intact_on_transient_whisper_error() {
drop(tx);
let client = reqwest::Client::new();
let busy = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let vocab = std::sync::Arc::new(doctate_server::gazetteer::Gazetteer::empty());
let busy = Arc::new(std::sync::atomic::AtomicBool::new(false));
let vocab = Arc::new(doctate_server::gazetteer::Gazetteer::empty());
transcribe::worker::run(
rx,
config,
@@ -405,14 +398,14 @@ async fn transcribe_worker_normalizes_whisper_output() {
let vocab_dir = tempfile::tempdir().unwrap();
std::fs::write(vocab_dir.path().join("anatomy.txt"), "Cerebrum\n").unwrap();
let vocab = std::sync::Arc::new(
let vocab = Arc::new(
doctate_server::gazetteer::Gazetteer::load_dir(vocab_dir.path())
.await
.unwrap(),
);
assert_eq!(vocab.len(), 1);
let config = test_config_with_whisper(server.uri());
let config = config_with_whisper(server.uri());
let (tx, rx) = transcribe::channel();
tx.send(transcribe::TranscribeJob {
audio_path: audio.clone(),
@@ -423,7 +416,7 @@ async fn transcribe_worker_normalizes_whisper_output() {
drop(tx);
let client = reqwest::Client::new();
let busy = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let busy = Arc::new(std::sync::atomic::AtomicBool::new(false));
transcribe::worker::run(
rx,
config,