feat: Handle analyze retry with failure marker
When a previous analysis attempt fails and leaves a failure marker, the retry mechanism now correctly identifies this state. It removes the stale input file, allowing the retry to proceed without a conflict error. This ensures users can recover from analysis failures more gracefully.
This commit is contained in:
@@ -272,6 +272,59 @@ async fn analyze_case_second_time_returns_409() {
|
||||
assert_eq!(second.status(), StatusCode::CONFLICT);
|
||||
}
|
||||
|
||||
/// Bug regression (case c414cf52, 2026-05-03): a prior LLM run failed,
|
||||
/// leaving `analysis_input.json` AND `.analysis_failed.json` on disk.
|
||||
/// The user clicks "Erneut versuchen" — the handler must accept the
|
||||
/// retry instead of rejecting it as 409 Conflict ("Analyse läuft
|
||||
/// bereits"). The failure marker is the explicit "worker has given up"
|
||||
/// signal that legitimises overwriting the stale sentinel.
|
||||
#[tokio::test]
|
||||
async fn analyze_retry_after_failure_marker_overwrites_stale_input() {
|
||||
let (cfg, settings) = cfg_llm("retry-after-fail");
|
||||
let case_id = "11111111-1111-1111-1111-111111111111";
|
||||
let case_dir = seed_case(&cfg.data_path, "dr_a", case_id);
|
||||
seed_recording(&case_dir, "2026-04-15T10-00-00Z", Some("frischer Text"));
|
||||
|
||||
// Simulate the post-failure on-disk state: stale input from the
|
||||
// attempt the worker gave up on, plus the matching failure marker.
|
||||
std::fs::write(
|
||||
case_dir.join(ANALYSIS_INPUT_FILE),
|
||||
r#"{"last_recording_mtime":"2026-04-15T10:00:00Z","recordings":[{"recorded_at":"2026-04-15T10:00:00Z","text":"alter Text"}],"backend_id":""}"#,
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(
|
||||
case_dir.join(".analysis_failed.json"),
|
||||
r#"{"last_recording_mtime":"2026-04-15T10:00:00Z","reason":"llm: connect timeout","failed_at":"2026-04-15T10:05:00Z"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (app, store) = doctate_server::create_router_and_session_store_with_settings(cfg, settings);
|
||||
let (cookie, csrf) = login_with_csrf(&app, &store, "dr_a", "s").await;
|
||||
|
||||
let resp = app
|
||||
.oneshot(csrf_form_post(
|
||||
paths::case_analyze(case_id),
|
||||
&cookie,
|
||||
&csrf,
|
||||
"",
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
StatusCode::SEE_OTHER,
|
||||
"retry with failure marker present must succeed, not 409"
|
||||
);
|
||||
|
||||
// Input was rewritten with the fresh transcript — the stale "alter Text"
|
||||
// must be gone.
|
||||
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(), 1);
|
||||
assert_eq!(recs[0]["text"], "frischer Text");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn analyze_case_skips_blank_transcript_from_recordings() {
|
||||
let (cfg, settings) = cfg_llm("i");
|
||||
|
||||
Reference in New Issue
Block a user