refactor: consolidate bulk actions, URL assembly, and case-artefact filenames
Pulls three pattern groups into shared helpers so a rename or wire-format tweak edits one file instead of 10+: - BulkAction enum in doctate-common replaces the "close"/"analyze"/"reset" string literals that were duplicated between the bulk handler and 3 attack/CSRF test files. FromStr preserves the exact "Unbekannte Aktion" error shape; the handler match is now exhaustive over the enum. - join_url helper in doctate-common absorbs 7 identical trim_end_matches+format! call sites across client-core, client-desktop, server/transcribe (ollama, whisper), and server/analyze (llm). - server/tests/common/artefacts.rs re-exports ONELINER_FILENAME (doctate-common), DOCUMENT_FILE + ANALYSIS_INPUT_FILE (doctate-server::analyze), and CLOSE_MARKER (doctate-server::paths) so test files reference the canonical name instead of inlining literals. Also lifts client-desktop local duplication: - paths.rs: project_path helper collapses 4 identical ProjectDirs chains - main.rs: or_die helper replaces 4 eprintln!+exit(1) blocks - app.rs: named RecordingContext struct replaces (Uuid, String) tuple at 3 sites around the ffmpeg-flush finalization path Verification: 397 tests pass (baseline was 389; +8 for new unit tests on BulkAction and join_url), 0 failed, 4 ignored (unchanged). clippy clean.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use doctate_common::join_url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::debug;
|
||||
|
||||
@@ -84,10 +85,7 @@ pub async fn chat_once(
|
||||
user_content: &str,
|
||||
timeout: Duration,
|
||||
) -> Result<String, LlmError> {
|
||||
let url = format!(
|
||||
"{}/v1/chat/completions",
|
||||
settings.base_url.trim_end_matches('/')
|
||||
);
|
||||
let url = join_url(settings.base_url, "/v1/chat/completions");
|
||||
debug!(%url, model = settings.model, "calling llm chat completions");
|
||||
|
||||
let body = ChatRequest {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum::response::Redirect;
|
||||
use doctate_common::BulkAction;
|
||||
use doctate_common::timestamp::now_rfc3339;
|
||||
use serde::Deserialize;
|
||||
use tracing::{info, warn};
|
||||
@@ -55,8 +57,16 @@ pub async fn handle_bulk_action(
|
||||
|
||||
let user_root = config.data_path.join(&user.slug);
|
||||
|
||||
match form.action.as_str() {
|
||||
"analyze" => {
|
||||
// Parse the wire token into the shared enum so the match below is
|
||||
// exhaustive at compile time. Keep the exact "Unbekannte Aktion"
|
||||
// error shape the handler returned before the enum existed.
|
||||
let action = BulkAction::from_str(&form.action).map_err(|err| {
|
||||
warn!(slug = %user.slug, action = %err.0, "bulk: unknown action");
|
||||
AppError::BadRequest("Unbekannte Aktion".into())
|
||||
})?;
|
||||
|
||||
match action {
|
||||
BulkAction::Analyze => {
|
||||
bulk_analyze(
|
||||
&config,
|
||||
&analyze_tx,
|
||||
@@ -67,12 +77,8 @@ pub async fn handle_bulk_action(
|
||||
)
|
||||
.await
|
||||
}
|
||||
"close" => bulk_close(&events_tx, &user.slug, &user_root, &form.case_ids).await,
|
||||
"reset" => bulk_reset(&events_tx, &user.slug, &user_root, &form.case_ids).await,
|
||||
other => {
|
||||
warn!(slug = %user.slug, action = %other, "bulk: unknown action");
|
||||
return Err(AppError::BadRequest("Unbekannte Aktion".into()));
|
||||
}
|
||||
BulkAction::Close => bulk_close(&events_tx, &user.slug, &user_root, &form.case_ids).await,
|
||||
BulkAction::Reset => bulk_reset(&events_tx, &user.slug, &user_root, &form.case_ids).await,
|
||||
}
|
||||
|
||||
Ok(Redirect::to("/web/cases"))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use doctate_common::join_url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::debug;
|
||||
|
||||
@@ -100,7 +101,7 @@ pub async fn generate_oneliner(
|
||||
transcript: &str,
|
||||
timeout: Duration,
|
||||
) -> Result<String, OllamaError> {
|
||||
let url = format!("{}/api/chat", ollama_url.trim_end_matches('/'));
|
||||
let url = join_url(ollama_url, "/api/chat");
|
||||
debug!(%url, model, "generating oneliner");
|
||||
|
||||
let body = ChatRequest {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use doctate_common::join_url;
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use tracing::debug;
|
||||
|
||||
@@ -100,9 +101,8 @@ pub async fn transcribe(
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or("de");
|
||||
let url = format!(
|
||||
"{}/asr?output=txt&language={}",
|
||||
whisper_url.trim_end_matches('/'),
|
||||
language
|
||||
"{}?output=txt&language={language}",
|
||||
join_url(whisper_url, "/asr")
|
||||
);
|
||||
debug!(%url, "posting to whisper");
|
||||
|
||||
|
||||
@@ -7,14 +7,16 @@ use std::time::Duration;
|
||||
use axum::http::{StatusCode, header};
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use doctate_common::BulkAction;
|
||||
use doctate_server::analyze;
|
||||
use serde_json::{Value, json};
|
||||
use wiremock::matchers::{method, path};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
use common::{
|
||||
TestConfig, csrf_form_post, get_with_cookie, login_with_csrf, paths, seed_case, seed_recording,
|
||||
test_admin, test_user, unique_tmpdir,
|
||||
ANALYSIS_INPUT_FILE, CLOSE_MARKER, DOCUMENT_FILE, ONELINER_FILENAME, TestConfig,
|
||||
csrf_form_post, get_with_cookie, login_with_csrf, paths, seed_case, seed_recording, test_admin,
|
||||
test_user, unique_tmpdir,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -217,7 +219,7 @@ async fn analyze_case_happy_path_writes_input_and_redirects() {
|
||||
"/web/cases"
|
||||
);
|
||||
|
||||
let input_path = case_dir.join("analysis_input.json");
|
||||
let input_path = case_dir.join(ANALYSIS_INPUT_FILE);
|
||||
assert!(input_path.exists(), "analysis_input.json missing");
|
||||
|
||||
let raw = std::fs::read_to_string(&input_path).unwrap();
|
||||
@@ -287,7 +289,7 @@ async fn analyze_case_skips_blank_transcript_from_recordings() {
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::SEE_OTHER);
|
||||
|
||||
let raw = std::fs::read_to_string(case_dir.join("analysis_input.json")).unwrap();
|
||||
let raw = std::fs::read_to_string(case_dir.join(ANALYSIS_INPUT_FILE)).unwrap();
|
||||
let parsed: Value = serde_json::from_str(&raw).unwrap();
|
||||
let recs = parsed["recordings"].as_array().unwrap();
|
||||
assert_eq!(recs.len(), 2, "blank transcript must be filtered out");
|
||||
@@ -310,7 +312,7 @@ async fn analyze_worker_writes_document_via_wiremock() {
|
||||
]
|
||||
});
|
||||
std::fs::write(
|
||||
case_dir.join("analysis_input.json"),
|
||||
case_dir.join(ANALYSIS_INPUT_FILE),
|
||||
serde_json::to_vec_pretty(&input).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -352,7 +354,7 @@ async fn analyze_worker_writes_document_via_wiremock() {
|
||||
.unwrap();
|
||||
|
||||
// Poll for the document (worker is in a separate task).
|
||||
let document_path = case_dir.join("document.md");
|
||||
let document_path = case_dir.join(DOCUMENT_FILE);
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
||||
while !document_path.exists() {
|
||||
if std::time::Instant::now() > deadline {
|
||||
@@ -389,7 +391,7 @@ async fn analyze_worker_normalizes_llm_output() {
|
||||
]
|
||||
});
|
||||
std::fs::write(
|
||||
case_dir.join("analysis_input.json"),
|
||||
case_dir.join(ANALYSIS_INPUT_FILE),
|
||||
serde_json::to_vec_pretty(&input).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -442,7 +444,7 @@ async fn analyze_worker_normalizes_llm_output() {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let document_path = case_dir.join("document.md");
|
||||
let document_path = case_dir.join(DOCUMENT_FILE);
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
||||
while !document_path.exists() {
|
||||
if std::time::Instant::now() > deadline {
|
||||
@@ -518,7 +520,7 @@ async fn recovery_enqueues_pending_analysis() {
|
||||
let tmp = unique_tmpdir("analyze-r1");
|
||||
let case_dir = tmp.join("dr_a/11111111-1111-1111-1111-111111111111");
|
||||
std::fs::create_dir_all(&case_dir).unwrap();
|
||||
std::fs::write(case_dir.join("analysis_input.json"), "{}").unwrap();
|
||||
std::fs::write(case_dir.join(ANALYSIS_INPUT_FILE), "{}").unwrap();
|
||||
|
||||
let (tx, mut rx) = analyze::channel();
|
||||
analyze::recovery::scan_and_enqueue(&tmp, &tx).await;
|
||||
@@ -533,8 +535,8 @@ async fn recovery_skips_completed_analysis() {
|
||||
let tmp = unique_tmpdir("analyze-r2");
|
||||
let case_dir = tmp.join("dr_a/11111111-1111-1111-1111-111111111111");
|
||||
std::fs::create_dir_all(&case_dir).unwrap();
|
||||
std::fs::write(case_dir.join("analysis_input.json"), "{}").unwrap();
|
||||
std::fs::write(case_dir.join("document.md"), "done").unwrap();
|
||||
std::fs::write(case_dir.join(ANALYSIS_INPUT_FILE), "{}").unwrap();
|
||||
std::fs::write(case_dir.join(DOCUMENT_FILE), "done").unwrap();
|
||||
|
||||
let (tx, mut rx) = analyze::channel();
|
||||
analyze::recovery::scan_and_enqueue(&tmp, &tx).await;
|
||||
@@ -554,7 +556,7 @@ async fn analyze_deletes_old_document_and_writes_fresh_input() {
|
||||
let cfg = cfg_llm("rn-5");
|
||||
let case_id = "11111111-1111-1111-1111-111111111111";
|
||||
let case_dir = seed_case(&cfg.data_path, "dr_a", case_id);
|
||||
std::fs::write(case_dir.join("document.md"), "altes Dokument").unwrap();
|
||||
std::fs::write(case_dir.join(DOCUMENT_FILE), "altes Dokument").unwrap();
|
||||
seed_recording(&case_dir, "2026-04-15T10-00-00Z", Some("erste Aufnahme"));
|
||||
seed_recording(&case_dir, "2026-04-15T10-05-00Z", Some("zweite Aufnahme"));
|
||||
|
||||
@@ -573,10 +575,10 @@ async fn analyze_deletes_old_document_and_writes_fresh_input() {
|
||||
assert_eq!(resp.status(), StatusCode::SEE_OTHER);
|
||||
|
||||
assert!(
|
||||
!case_dir.join("document.md").exists(),
|
||||
!case_dir.join(DOCUMENT_FILE).exists(),
|
||||
"old document must be deleted before re-analysis"
|
||||
);
|
||||
let input_path = case_dir.join("analysis_input.json");
|
||||
let input_path = case_dir.join(ANALYSIS_INPUT_FILE);
|
||||
assert!(input_path.exists(), "analysis_input.json missing");
|
||||
let parsed: Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(&input_path).unwrap()).unwrap();
|
||||
@@ -615,7 +617,7 @@ async fn close_writes_marker_and_hides_case() {
|
||||
.unwrap(),
|
||||
"/web/cases"
|
||||
);
|
||||
let marker = case_dir.join(".closed");
|
||||
let marker = case_dir.join(CLOSE_MARKER);
|
||||
assert!(marker.exists(), ".closed marker missing");
|
||||
let raw = std::fs::read_to_string(&marker).unwrap();
|
||||
let parsed: Value = serde_json::from_str(&raw).unwrap();
|
||||
@@ -675,7 +677,7 @@ async fn reopen_removes_marker_and_restores_case() {
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(case_dir.join(".closed").exists());
|
||||
assert!(case_dir.join(CLOSE_MARKER).exists());
|
||||
|
||||
let resp = app
|
||||
.clone()
|
||||
@@ -689,7 +691,7 @@ async fn reopen_removes_marker_and_restores_case() {
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::SEE_OTHER);
|
||||
assert!(
|
||||
!case_dir.join(".closed").exists(),
|
||||
!case_dir.join(CLOSE_MARKER).exists(),
|
||||
".closed marker must be gone after reopen"
|
||||
);
|
||||
|
||||
@@ -919,7 +921,7 @@ async fn reopen_is_noop_on_open_case() {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::SEE_OTHER);
|
||||
assert!(!case_dir.join(".closed").exists());
|
||||
assert!(!case_dir.join(CLOSE_MARKER).exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -943,7 +945,7 @@ async fn purge_closed_removes_directory() {
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(case_dir.join(".closed").exists());
|
||||
assert!(case_dir.join(CLOSE_MARKER).exists());
|
||||
|
||||
let resp = app
|
||||
.oneshot(csrf_form_post(
|
||||
@@ -991,7 +993,7 @@ async fn purge_requires_confirm_param() {
|
||||
case_dir.exists(),
|
||||
"case directory must survive purge without confirm=yes"
|
||||
);
|
||||
assert!(case_dir.join(".closed").exists());
|
||||
assert!(case_dir.join(CLOSE_MARKER).exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1086,7 +1088,10 @@ async fn bulk_close_shares_one_closed_at_timestamp() {
|
||||
let (app, store) = doctate_server::create_router_and_session_store(cfg);
|
||||
let (cookie, csrf) = login_with_csrf(&app, &store, "dr_a", "s").await;
|
||||
|
||||
let body = format!("action=close&case_id={case_a}&case_id={case_b}");
|
||||
let body = format!(
|
||||
"action={}&case_id={case_a}&case_id={case_b}",
|
||||
BulkAction::Close
|
||||
);
|
||||
let resp = app
|
||||
.oneshot(csrf_form_post(paths::BULK, &cookie, &csrf, &body))
|
||||
.await
|
||||
@@ -1094,9 +1099,9 @@ async fn bulk_close_shares_one_closed_at_timestamp() {
|
||||
assert_eq!(resp.status(), StatusCode::SEE_OTHER);
|
||||
|
||||
let m_a: Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(dir_a.join(".closed")).unwrap()).unwrap();
|
||||
serde_json::from_str(&std::fs::read_to_string(dir_a.join(CLOSE_MARKER)).unwrap()).unwrap();
|
||||
let m_b: Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(dir_b.join(".closed")).unwrap()).unwrap();
|
||||
serde_json::from_str(&std::fs::read_to_string(dir_b.join(CLOSE_MARKER)).unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
m_a["closed_at"], m_b["closed_at"],
|
||||
"bulk-close must share one closed_at timestamp across its selection"
|
||||
@@ -1117,15 +1122,18 @@ async fn bulk_analyze_processes_each_selected_case() {
|
||||
let (app, store) = doctate_server::create_router_and_session_store(cfg);
|
||||
let (cookie, csrf) = login_with_csrf(&app, &store, "dr_a", "s").await;
|
||||
|
||||
let body = format!("action=analyze&case_id={case_a}&case_id={case_b}");
|
||||
let body = format!(
|
||||
"action={}&case_id={case_a}&case_id={case_b}",
|
||||
BulkAction::Analyze
|
||||
);
|
||||
let resp = app
|
||||
.oneshot(csrf_form_post(paths::BULK, &cookie, &csrf, &body))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::SEE_OTHER);
|
||||
|
||||
assert!(dir_a.join("analysis_input.json").exists());
|
||||
assert!(dir_b.join("analysis_input.json").exists());
|
||||
assert!(dir_a.join(ANALYSIS_INPUT_FILE).exists());
|
||||
assert!(dir_b.join(ANALYSIS_INPUT_FILE).exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1133,9 +1141,9 @@ async fn recovery_skips_closed_cases() {
|
||||
let tmp = unique_tmpdir("analyze-rec-closed");
|
||||
let case_dir = tmp.join("dr_a/11111111-1111-1111-1111-111111111111");
|
||||
std::fs::create_dir_all(&case_dir).unwrap();
|
||||
std::fs::write(case_dir.join("analysis_input.json"), "{}").unwrap();
|
||||
std::fs::write(case_dir.join(ANALYSIS_INPUT_FILE), "{}").unwrap();
|
||||
std::fs::write(
|
||||
case_dir.join(".closed"),
|
||||
case_dir.join(CLOSE_MARKER),
|
||||
r#"{"closed_at":"2026-04-15T10:00:00Z"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1162,12 +1170,12 @@ async fn reset_case_clears_derived_and_unfails_audio() {
|
||||
std::fs::write(dir.join("2026-04-15T09-05-00Z.m4a.failed"), b"audio-bytes").unwrap();
|
||||
// Derived artefacts the reset must wipe.
|
||||
std::fs::write(
|
||||
dir.join("oneliner.json"),
|
||||
dir.join(ONELINER_FILENAME),
|
||||
br#"{"kind":"ready","text":"Knie re.","generated_at":"2026-04-18T10:00:00Z"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(dir.join("document.md"), "# Doc\n").unwrap();
|
||||
std::fs::write(dir.join("analysis_input.json"), "{}").unwrap();
|
||||
std::fs::write(dir.join(DOCUMENT_FILE), "# Doc\n").unwrap();
|
||||
std::fs::write(dir.join(ANALYSIS_INPUT_FILE), "{}").unwrap();
|
||||
|
||||
let (app, store) = doctate_server::create_router_and_session_store(cfg);
|
||||
let (cookie, csrf) = login_with_csrf(&app, &store, "dr_a", "s").await;
|
||||
@@ -1190,9 +1198,9 @@ async fn reset_case_clears_derived_and_unfails_audio() {
|
||||
assert!(!dir.join("2026-04-15T09-05-00Z.m4a.failed").exists());
|
||||
// Derived artefacts gone.
|
||||
assert!(!dir.join("2026-04-15T09-00-00Z.transcript.txt").exists());
|
||||
assert!(!dir.join("oneliner.json").exists());
|
||||
assert!(!dir.join("document.md").exists());
|
||||
assert!(!dir.join("analysis_input.json").exists());
|
||||
assert!(!dir.join(ONELINER_FILENAME).exists());
|
||||
assert!(!dir.join(DOCUMENT_FILE).exists());
|
||||
assert!(!dir.join(ANALYSIS_INPUT_FILE).exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1202,7 +1210,7 @@ async fn reset_case_requires_admin() {
|
||||
let case_id = "11111111-1111-1111-1111-111111111111";
|
||||
let dir = seed_case(&cfg.data_path, "dr_a", case_id);
|
||||
seed_recording(&dir, "2026-04-15T09-00-00Z", Some("hallo"));
|
||||
std::fs::write(dir.join("document.md"), "# Doc\n").unwrap();
|
||||
std::fs::write(dir.join(DOCUMENT_FILE), "# Doc\n").unwrap();
|
||||
|
||||
let (app, store) = doctate_server::create_router_and_session_store(cfg);
|
||||
let (cookie, csrf) = login_with_csrf(&app, &store, "dr_a", "s").await;
|
||||
@@ -1220,7 +1228,7 @@ async fn reset_case_requires_admin() {
|
||||
|
||||
// Nothing touched.
|
||||
assert!(dir.join("2026-04-15T09-00-00Z.transcript.txt").exists());
|
||||
assert!(dir.join("document.md").exists());
|
||||
assert!(dir.join(DOCUMENT_FILE).exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1237,19 +1245,22 @@ async fn bulk_reset_processes_multiple_cases_admin_only() {
|
||||
let dir_b = seed_case(&cfg.data_path, "dr_a", case_b);
|
||||
seed_recording(&dir_a, "2026-04-15T10-00-00Z", Some("a"));
|
||||
seed_recording(&dir_b, "2026-04-15T10-05-00Z", Some("b"));
|
||||
std::fs::write(dir_a.join("document.md"), "A").unwrap();
|
||||
std::fs::write(dir_b.join("document.md"), "B").unwrap();
|
||||
std::fs::write(dir_a.join(DOCUMENT_FILE), "A").unwrap();
|
||||
std::fs::write(dir_b.join(DOCUMENT_FILE), "B").unwrap();
|
||||
// dr_b gets its own case to verify non-admin branch without clobbering dr_a.
|
||||
let case_c = "33333333-3333-3333-3333-333333333333";
|
||||
let dir_c = seed_case(&cfg.data_path, "dr_b", case_c);
|
||||
seed_recording(&dir_c, "2026-04-15T11-00-00Z", Some("c"));
|
||||
std::fs::write(dir_c.join("document.md"), "C").unwrap();
|
||||
std::fs::write(dir_c.join(DOCUMENT_FILE), "C").unwrap();
|
||||
|
||||
let (app, store) = doctate_server::create_router_and_session_store(cfg);
|
||||
|
||||
// Admin: bulk reset succeeds.
|
||||
let (cookie_admin, csrf_admin) = login_with_csrf(&app, &store, "dr_a", "s").await;
|
||||
let body = format!("action=reset&case_id={case_a}&case_id={case_b}");
|
||||
let body = format!(
|
||||
"action={}&case_id={case_a}&case_id={case_b}",
|
||||
BulkAction::Reset
|
||||
);
|
||||
let resp = app
|
||||
.clone()
|
||||
.oneshot(csrf_form_post(
|
||||
@@ -1261,14 +1272,14 @@ async fn bulk_reset_processes_multiple_cases_admin_only() {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::SEE_OTHER);
|
||||
assert!(!dir_a.join("document.md").exists());
|
||||
assert!(!dir_b.join("document.md").exists());
|
||||
assert!(!dir_a.join(DOCUMENT_FILE).exists());
|
||||
assert!(!dir_b.join(DOCUMENT_FILE).exists());
|
||||
assert!(!dir_a.join("2026-04-15T10-00-00Z.transcript.txt").exists());
|
||||
assert!(!dir_b.join("2026-04-15T10-05-00Z.transcript.txt").exists());
|
||||
|
||||
// Non-admin: bulk reset is rejected, dr_b's case untouched.
|
||||
let (cookie_doctor, csrf_doctor) = login_with_csrf(&app, &store, "dr_b", "s").await;
|
||||
let body = format!("action=reset&case_id={case_c}");
|
||||
let body = format!("action={}&case_id={case_c}", BulkAction::Reset);
|
||||
let resp = app
|
||||
.oneshot(csrf_form_post(
|
||||
paths::BULK,
|
||||
@@ -1279,7 +1290,7 @@ async fn bulk_reset_processes_multiple_cases_admin_only() {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
assert!(dir_c.join("document.md").exists());
|
||||
assert!(dir_c.join(DOCUMENT_FILE).exists());
|
||||
assert!(dir_c.join("2026-04-15T11-00-00Z.transcript.txt").exists());
|
||||
}
|
||||
|
||||
@@ -1298,14 +1309,14 @@ async fn bulk_close_rejected_for_non_admin() {
|
||||
let (app, store) = doctate_server::create_router_and_session_store(cfg);
|
||||
let (cookie, csrf) = login_with_csrf(&app, &store, "dr_a", "s").await;
|
||||
|
||||
let body = format!("action=close&case_id={case_id}");
|
||||
let body = format!("action={}&case_id={case_id}", BulkAction::Close);
|
||||
let resp = app
|
||||
.oneshot(csrf_form_post(paths::BULK, &cookie, &csrf, &body))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
// Close marker must NOT have been written.
|
||||
assert!(!dir.join(".closed").exists());
|
||||
assert!(!dir.join(CLOSE_MARKER).exists());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -1331,7 +1342,7 @@ async fn analyze_works_against_ollama_style_endpoint_without_api_key() {
|
||||
]
|
||||
});
|
||||
std::fs::write(
|
||||
case_dir.join("analysis_input.json"),
|
||||
case_dir.join(ANALYSIS_INPUT_FILE),
|
||||
serde_json::to_vec_pretty(&input).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1381,7 +1392,7 @@ async fn analyze_works_against_ollama_style_endpoint_without_api_key() {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let document_path = case_dir.join("document.md");
|
||||
let document_path = case_dir.join(DOCUMENT_FILE);
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
||||
while !document_path.exists() {
|
||||
if std::time::Instant::now() > deadline {
|
||||
|
||||
@@ -6,7 +6,7 @@ use tower::util::ServiceExt;
|
||||
use doctate_common::oneliners::OnelinerState;
|
||||
|
||||
use common::{
|
||||
TestConfig, body_string, get_with_cookie, paths, seed_case, seed_oneliner_ready,
|
||||
DOCUMENT_FILE, TestConfig, body_string, get_with_cookie, paths, seed_case, seed_oneliner_ready,
|
||||
seed_oneliner_state, seed_recording, test_admin, test_user,
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ async fn case_page_shows_document_when_present() {
|
||||
.build();
|
||||
let case_id = "11111111-1111-1111-1111-111111111111";
|
||||
let case_dir = seed_case(&cfg.data_path, "dr_a", case_id);
|
||||
std::fs::write(case_dir.join("document.md"), "der Inhalt").unwrap();
|
||||
std::fs::write(case_dir.join(DOCUMENT_FILE), "der Inhalt").unwrap();
|
||||
|
||||
let app = doctate_server::create_router(cfg);
|
||||
let cookie = login_dr_a(&app).await;
|
||||
@@ -57,7 +57,7 @@ async fn case_page_document_has_copy_button() {
|
||||
.build();
|
||||
let case_id = "11111111-1111-1111-1111-111111111111";
|
||||
let case_dir = seed_case(&cfg.data_path, "dr_a", case_id);
|
||||
std::fs::write(case_dir.join("document.md"), "kopiere mich").unwrap();
|
||||
std::fs::write(case_dir.join(DOCUMENT_FILE), "kopiere mich").unwrap();
|
||||
|
||||
let app = doctate_server::create_router(cfg);
|
||||
let cookie = login_dr_a(&app).await;
|
||||
@@ -175,7 +175,7 @@ async fn case_page_foreign_user_returns_404() {
|
||||
let case_id = "22222222-2222-2222-2222-222222222222";
|
||||
// Case exists under dr_b, but session is dr_a → 404.
|
||||
let case_dir = seed_case(&cfg.data_path, "dr_b", case_id);
|
||||
std::fs::write(case_dir.join("document.md"), "fremdes Dokument").unwrap();
|
||||
std::fs::write(case_dir.join(DOCUMENT_FILE), "fremdes Dokument").unwrap();
|
||||
|
||||
let app = doctate_server::create_router(cfg);
|
||||
let cookie = login_dr_a(&app).await;
|
||||
@@ -255,7 +255,7 @@ async fn case_page_uses_oneliner_as_h1_when_present() {
|
||||
let case_id = "11111111-1111-1111-1111-111111111111";
|
||||
let case_dir = seed_case(&cfg.data_path, "dr_a", case_id);
|
||||
seed_oneliner_ready(&case_dir, "Herzkatheter unauffällig");
|
||||
std::fs::write(case_dir.join("document.md"), "Inhalt").unwrap();
|
||||
std::fs::write(case_dir.join(DOCUMENT_FILE), "Inhalt").unwrap();
|
||||
|
||||
let app = doctate_server::create_router(cfg);
|
||||
let cookie = login_dr_a(&app).await;
|
||||
|
||||
@@ -11,6 +11,7 @@ mod common;
|
||||
use std::path::Path;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use doctate_common::BulkAction;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use common::{
|
||||
@@ -149,7 +150,10 @@ async fn bulk_close_bumps_watermark() {
|
||||
let etag_before = etag_before.expect("ETag must be set");
|
||||
|
||||
// Form-encoded bulk close: action=close&case_id=a&case_id=b.
|
||||
let body = format!("action=close&case_id={case_a}&case_id={case_b}");
|
||||
let body = format!(
|
||||
"action={}&case_id={case_a}&case_id={case_b}",
|
||||
BulkAction::Close
|
||||
);
|
||||
let bulk = app
|
||||
.clone()
|
||||
.oneshot(csrf_form_post(paths::BULK, &cookie, &csrf, &body))
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//! Re-exports of case-directory artefact filenames from their
|
||||
//! canonical source-of-truth locations.
|
||||
//!
|
||||
//! Tests that need to read, write, or assert the absence of per-case
|
||||
//! artefacts (`oneliner.json`, `.closed`, `document.md`,
|
||||
//! `analysis_input.json`) pull these names from here instead of
|
||||
//! inlining string literals. If the server renames a sidecar in the
|
||||
//! future, test call-sites fail to compile instead of silently
|
||||
//! asserting against the old name.
|
||||
|
||||
pub use doctate_common::ONELINER_FILENAME;
|
||||
pub use doctate_server::analyze::{ANALYSIS_INPUT_FILE, DOCUMENT_FILE};
|
||||
pub use doctate_server::paths::CLOSE_MARKER;
|
||||
@@ -16,6 +16,7 @@
|
||||
// warning is a false positive of the per-crate visibility model.
|
||||
#![allow(unused_imports)]
|
||||
|
||||
pub mod artefacts;
|
||||
pub mod config;
|
||||
pub mod http;
|
||||
pub mod paths;
|
||||
@@ -24,6 +25,7 @@ pub mod session;
|
||||
pub mod users;
|
||||
|
||||
// Re-exports for ergonomic `use common::*;` in most test files.
|
||||
pub use artefacts::{ANALYSIS_INPUT_FILE, CLOSE_MARKER, DOCUMENT_FILE, ONELINER_FILENAME};
|
||||
pub use config::{TestConfig, unique_tmpdir};
|
||||
pub use http::{
|
||||
body_bytes, body_json, body_string, csrf_form_post, form_post, get_with_api_key,
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use doctate_common::ONELINER_FILENAME;
|
||||
use doctate_common::oneliners::OnelinerState;
|
||||
use doctate_server::paths::CLOSE_MARKER;
|
||||
use filetime::FileTime;
|
||||
|
||||
/// Create `<data_path>/<slug>/<case_id>/` and return its path. This is
|
||||
@@ -71,7 +73,7 @@ pub fn seed_oneliner_ready(case_dir: &Path, text: &str) {
|
||||
/// Persist an arbitrary [`OnelinerState`] as `oneliner.json`.
|
||||
pub fn seed_oneliner_state(case_dir: &Path, state: &OnelinerState) {
|
||||
let bytes = serde_json::to_vec(state).unwrap();
|
||||
std::fs::write(case_dir.join("oneliner.json"), bytes).unwrap();
|
||||
std::fs::write(case_dir.join(ONELINER_FILENAME), bytes).unwrap();
|
||||
}
|
||||
|
||||
/// Write a `.closed` marker with a caller-chosen `closed_at` string.
|
||||
@@ -79,7 +81,7 @@ pub fn seed_oneliner_state(case_dir: &Path, state: &OnelinerState) {
|
||||
/// helper for that is [`past_rfc3339`].
|
||||
pub fn write_closed_marker(case_dir: &Path, closed_at: &str) {
|
||||
let json = format!(r#"{{"closed_at":"{closed_at}"}}"#);
|
||||
std::fs::write(case_dir.join(".closed"), json).unwrap();
|
||||
std::fs::write(case_dir.join(CLOSE_MARKER), json).unwrap();
|
||||
}
|
||||
|
||||
/// Format "now - age" as RFC3339 UTC. Used to mint historical
|
||||
|
||||
@@ -11,6 +11,7 @@ mod common;
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request, StatusCode, header};
|
||||
use doctate_common::BulkAction;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use common::{
|
||||
@@ -70,7 +71,10 @@ async fn bulk_without_csrf_token_forbidden() {
|
||||
let app = doctate_server::create_router(cfg);
|
||||
let cookie = login(&app, "dr_admin", "s").await;
|
||||
|
||||
let body = "action=close&case_id=00000000-0000-0000-0000-000000000000";
|
||||
let body = format!(
|
||||
"action={}&case_id=00000000-0000-0000-0000-000000000000",
|
||||
BulkAction::Close
|
||||
);
|
||||
let resp = app
|
||||
.oneshot(form_post(paths::BULK, &cookie, body))
|
||||
.await
|
||||
@@ -212,8 +216,10 @@ async fn bulk_with_wrong_csrf_token_forbidden() {
|
||||
// A 43-char alphanumeric that is structurally valid but does not
|
||||
// match the session's token.
|
||||
let wrong = "a".repeat(43);
|
||||
let body =
|
||||
format!("action=close&case_id=00000000-0000-0000-0000-000000000000&csrf_token={wrong}");
|
||||
let body = format!(
|
||||
"action={}&case_id=00000000-0000-0000-0000-000000000000&csrf_token={wrong}",
|
||||
BulkAction::Close
|
||||
);
|
||||
let resp = app
|
||||
.oneshot(form_post(paths::BULK, &cookie, body))
|
||||
.await
|
||||
@@ -227,7 +233,10 @@ async fn bulk_with_empty_csrf_token_forbidden() {
|
||||
let app = doctate_server::create_router(cfg);
|
||||
let cookie = login(&app, "dr_admin", "s").await;
|
||||
|
||||
let body = "action=close&case_id=00000000-0000-0000-0000-000000000000&csrf_token=";
|
||||
let body = format!(
|
||||
"action={}&case_id=00000000-0000-0000-0000-000000000000&csrf_token=",
|
||||
BulkAction::Close
|
||||
);
|
||||
let resp = app
|
||||
.oneshot(form_post(paths::BULK, &cookie, body))
|
||||
.await
|
||||
@@ -249,7 +258,10 @@ async fn bulk_with_whitespace_padded_token_forbidden() {
|
||||
let cookie = login(&app, "dr_admin", "s").await;
|
||||
|
||||
// Leading+trailing spaces around a value that *would* match post-trim.
|
||||
let body = "action=close&case_id=00000000-0000-0000-0000-000000000000&csrf_token=%20VALID%20";
|
||||
let body = format!(
|
||||
"action={}&case_id=00000000-0000-0000-0000-000000000000&csrf_token=%20VALID%20",
|
||||
BulkAction::Close
|
||||
);
|
||||
let resp = app
|
||||
.oneshot(form_post(paths::BULK, &cookie, body))
|
||||
.await
|
||||
@@ -268,8 +280,10 @@ async fn bulk_with_injection_payload_returns_403_not_500() {
|
||||
let cookie = login(&app, "dr_admin", "s").await;
|
||||
|
||||
// `' OR 1=1--` URL-encoded.
|
||||
let body = "action=close&case_id=00000000-0000-0000-0000-000000000000\
|
||||
&csrf_token=%27+OR+1%3D1--";
|
||||
let body = format!(
|
||||
"action={}&case_id=00000000-0000-0000-0000-000000000000&csrf_token=%27+OR+1%3D1--",
|
||||
BulkAction::Close
|
||||
);
|
||||
let resp = app
|
||||
.oneshot(form_post(paths::BULK, &cookie, body))
|
||||
.await
|
||||
|
||||
@@ -13,8 +13,8 @@ use axum::http::StatusCode;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use common::{
|
||||
TestConfig, csrf_form_post, login_with_csrf, paths, seed_case, seed_recording_with_sidecars,
|
||||
test_user,
|
||||
ANALYSIS_INPUT_FILE, DOCUMENT_FILE, ONELINER_FILENAME, TestConfig, csrf_form_post,
|
||||
login_with_csrf, paths, seed_case, seed_recording_with_sidecars, test_user,
|
||||
};
|
||||
|
||||
fn delete_body(filename: &str) -> String {
|
||||
@@ -31,9 +31,9 @@ async fn delete_recording_removes_file_and_sidecars_and_invalidates_derived() {
|
||||
let survivor = seed_recording_with_sidecars(&case_dir, "2026-04-19T11-00-00Z", "keep me");
|
||||
|
||||
// Derived artefacts the delete must also purge.
|
||||
std::fs::write(case_dir.join("oneliner.json"), b"{}").unwrap();
|
||||
std::fs::write(case_dir.join("document.md"), b"# stale").unwrap();
|
||||
std::fs::write(case_dir.join("analysis_input.json"), b"{}").unwrap();
|
||||
std::fs::write(case_dir.join(ONELINER_FILENAME), b"{}").unwrap();
|
||||
std::fs::write(case_dir.join(DOCUMENT_FILE), b"# stale").unwrap();
|
||||
std::fs::write(case_dir.join(ANALYSIS_INPUT_FILE), b"{}").unwrap();
|
||||
|
||||
let (app, store) = doctate_server::create_router_and_session_store(cfg);
|
||||
let (cookie, csrf) = login_with_csrf(&app, &store, "dr_a", "s").await;
|
||||
@@ -79,15 +79,15 @@ async fn delete_recording_removes_file_and_sidecars_and_invalidates_derived() {
|
||||
|
||||
// Derived artefacts are invalidated.
|
||||
assert!(
|
||||
!case_dir.join("oneliner.json").exists(),
|
||||
!case_dir.join(ONELINER_FILENAME).exists(),
|
||||
"oneliner.json must be cleared"
|
||||
);
|
||||
assert!(
|
||||
!case_dir.join("document.md").exists(),
|
||||
!case_dir.join(DOCUMENT_FILE).exists(),
|
||||
"document.md must be cleared"
|
||||
);
|
||||
assert!(
|
||||
!case_dir.join("analysis_input.json").exists(),
|
||||
!case_dir.join(ANALYSIS_INPUT_FILE).exists(),
|
||||
"analysis_input.json must be cleared"
|
||||
);
|
||||
}
|
||||
@@ -137,7 +137,7 @@ async fn delete_recording_rejects_wrong_extension() {
|
||||
paths::recording_delete(case_id),
|
||||
&cookie,
|
||||
&csrf,
|
||||
&delete_body("document.md"),
|
||||
&delete_body(DOCUMENT_FILE),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -191,7 +191,7 @@ async fn delete_last_recording_clears_case() {
|
||||
let case_id = "55555555-5555-5555-5555-555555555555";
|
||||
let case_dir = seed_case(&cfg.data_path, "dr_a", case_id);
|
||||
let only = seed_recording_with_sidecars(&case_dir, "2026-04-19T10-00-00Z", "lonely");
|
||||
std::fs::write(case_dir.join("oneliner.json"), b"{}").unwrap();
|
||||
std::fs::write(case_dir.join(ONELINER_FILENAME), b"{}").unwrap();
|
||||
|
||||
let (app, store) = doctate_server::create_router_and_session_store(cfg);
|
||||
let (cookie, csrf) = login_with_csrf(&app, &store, "dr_a", "s").await;
|
||||
@@ -214,7 +214,7 @@ async fn delete_last_recording_clears_case() {
|
||||
);
|
||||
|
||||
assert!(!case_dir.join(&only).exists());
|
||||
assert!(!case_dir.join("oneliner.json").exists());
|
||||
assert!(!case_dir.join(ONELINER_FILENAME).exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -30,7 +30,7 @@ use tempfile::tempdir;
|
||||
use wiremock::matchers::{method, path as wm_path};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
use common::TestConfig;
|
||||
use common::{ONELINER_FILENAME, TestConfig};
|
||||
|
||||
#[tokio::test]
|
||||
async fn failed_only_case_settles_to_empty_without_llm_call() {
|
||||
@@ -86,7 +86,7 @@ async fn failed_only_case_settles_to_empty_without_llm_call() {
|
||||
tokio::time::sleep(Duration::from_millis(25)).await;
|
||||
}
|
||||
|
||||
let path = case_dir.join("oneliner.json");
|
||||
let path = case_dir.join(ONELINER_FILENAME);
|
||||
assert!(
|
||||
path.exists(),
|
||||
"expected oneliner.json to be written for failed-only case"
|
||||
|
||||
@@ -23,7 +23,7 @@ use tokio::time::timeout;
|
||||
use wiremock::matchers::{method, path as wm_path};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
use common::TestConfig;
|
||||
use common::{ONELINER_FILENAME, TestConfig};
|
||||
|
||||
fn write_transcript(case_dir: &Path) {
|
||||
// Seed an m4a + transcript pair, matching the worker's on-disk
|
||||
@@ -123,7 +123,9 @@ async fn heal_orphans_returns_fast_and_dedups_parallel_calls() {
|
||||
|
||||
// Every case now has an oneliner.json.
|
||||
for i in 0..5 {
|
||||
let p = user_root.join(format!("case-{i:02}")).join("oneliner.json");
|
||||
let p = user_root
|
||||
.join(format!("case-{i:02}"))
|
||||
.join(ONELINER_FILENAME);
|
||||
assert!(p.exists(), "oneliner.json missing for case-{i:02}");
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ use filetime::FileTime;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use common::{
|
||||
TestConfig, body_json, get_with_api_key, get_with_api_key_if_none_match, header_str,
|
||||
seed_oneliner_ready, seed_oneliner_state, seed_recording_with_age, test_user,
|
||||
CLOSE_MARKER, TestConfig, body_json, get_with_api_key, get_with_api_key_if_none_match,
|
||||
header_str, seed_oneliner_ready, seed_oneliner_state, seed_recording_with_age, test_user,
|
||||
test_user_with_window_hours,
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ fn seed(
|
||||
}
|
||||
|
||||
fn mark_deleted(case_dir: &Path) {
|
||||
std::fs::write(case_dir.join(".closed"), "{}").unwrap();
|
||||
std::fs::write(case_dir.join(CLOSE_MARKER), "{}").unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -12,8 +12,8 @@ use axum::http::StatusCode;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use common::{
|
||||
TestConfig, get_with_cookie, past_rfc3339, paths, seed_case, seed_recording_with_age,
|
||||
test_user_with_retention, write_closed_marker,
|
||||
CLOSE_MARKER, TestConfig, get_with_cookie, past_rfc3339, paths, seed_case,
|
||||
seed_recording_with_age, test_user_with_retention, write_closed_marker,
|
||||
};
|
||||
|
||||
/// Trigger the sweep via its natural path: GET /web/cases.
|
||||
@@ -55,10 +55,10 @@ async fn auto_close_fires_when_last_recording_too_old() {
|
||||
let app = doctate_server::create_router(cfg);
|
||||
let cookie = common::login(&app, "dr_a", "s").await;
|
||||
|
||||
assert!(!case_dir.join(".closed").exists());
|
||||
assert!(!case_dir.join(CLOSE_MARKER).exists());
|
||||
trigger_sweep(&app, &cookie).await;
|
||||
assert!(
|
||||
case_dir.join(".closed").exists(),
|
||||
case_dir.join(CLOSE_MARKER).exists(),
|
||||
"stale case must be auto-closed"
|
||||
);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ async fn auto_close_disabled_when_days_zero() {
|
||||
|
||||
trigger_sweep(&app, &cookie).await;
|
||||
assert!(
|
||||
!case_dir.join(".closed").exists(),
|
||||
!case_dir.join(CLOSE_MARKER).exists(),
|
||||
"case must stay open when auto_close_days = 0"
|
||||
);
|
||||
}
|
||||
@@ -104,7 +104,7 @@ async fn auto_close_respects_newest_recording() {
|
||||
|
||||
trigger_sweep(&app, &cookie).await;
|
||||
assert!(
|
||||
!case_dir.join(".closed").exists(),
|
||||
!case_dir.join(CLOSE_MARKER).exists(),
|
||||
"recent addendum must keep the case open"
|
||||
);
|
||||
}
|
||||
@@ -146,7 +146,7 @@ async fn auto_purge_disabled_when_days_zero() {
|
||||
case_dir.exists(),
|
||||
"closed case must survive when auto_delete_days = 0"
|
||||
);
|
||||
assert!(case_dir.join(".closed").exists());
|
||||
assert!(case_dir.join(CLOSE_MARKER).exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -174,7 +174,7 @@ async fn fresh_auto_close_is_not_immediately_purged() {
|
||||
"vacation: case directory must NOT be purged in the same sweep that auto-closed it"
|
||||
);
|
||||
assert!(
|
||||
case_dir.join(".closed").exists(),
|
||||
case_dir.join(CLOSE_MARKER).exists(),
|
||||
"vacation: case must be auto-closed (but retained)"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use tempfile::tempdir;
|
||||
use wiremock::matchers::{method, path as wm_path};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
use common::TestConfig;
|
||||
use common::{ONELINER_FILENAME, TestConfig};
|
||||
|
||||
/// Seed an m4a plus a silent (0-byte) transcript sidecar — the on-disk
|
||||
/// shape of a recording that whisper classified as silence.
|
||||
@@ -90,7 +90,7 @@ async fn silent_only_case_settles_to_empty_without_llm_call() {
|
||||
|
||||
// Oneliner is persisted as `Empty`. Without the fix, the file would
|
||||
// either not exist or be mid-generation.
|
||||
let path = case_dir.join("oneliner.json");
|
||||
let path = case_dir.join(ONELINER_FILENAME);
|
||||
assert!(
|
||||
path.exists(),
|
||||
"expected oneliner.json to be written for silent-only case"
|
||||
|
||||
Reference in New Issue
Block a user