Refactor case pages into two distinct routes

This commit separates the case detail page into two distinct routes:
`/web/cases/{case_id}` for the main case information and document, and
`/web/cases/{case_id}/recordings` for a dedicated view of audio files
and their transcripts.

This change improves the organization and clarity of the case viewing
experience by segmenting the related but distinct information into their
own UI sections.
This commit is contained in:
2026-04-19 17:02:16 +02:00
parent 76e8ee18e9
commit 3bb2d23adb
5 changed files with 329 additions and 129 deletions
-74
View File
@@ -27,80 +27,6 @@ fn test_config() -> Arc<Config> {
})
}
async fn body_to_string(response: axum::response::Response) -> String {
let bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
String::from_utf8(bytes.to_vec()).unwrap()
}
#[tokio::test]
async fn web_index_empty() {
let config = test_config();
let app = doctate_server::create_router(config);
let response = app
.oneshot(Request::builder().uri("/web/").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = body_to_string(response).await;
assert!(body.contains("No cases found"));
}
#[tokio::test]
async fn web_index_shows_cases() {
let config = test_config();
let data_path = config.data_path.clone();
// Pre-create cases on disk
let case1 = "550e8400-e29b-41d4-a716-446655440000";
let case2 = "660e8400-e29b-41d4-a716-446655440000";
let open_case = data_path.join("dr_test").join(case1);
std::fs::create_dir_all(&open_case).unwrap();
std::fs::write(open_case.join("2026-04-13T10-30-00Z.m4a"), b"fake audio 1").unwrap();
let done_case = data_path.join("dr_test").join(case2);
std::fs::create_dir_all(&done_case).unwrap();
std::fs::write(done_case.join("2026-04-12T09-00-00Z.m4a"), b"fake audio 2").unwrap();
std::fs::write(
done_case.join("2026-04-12T09-00-00Z.transcript.txt"),
"Herzkatheter ohne Befund.",
)
.unwrap();
std::fs::write(done_case.join("oneliner.txt"), "Herzkatheter unauffällig").unwrap();
let app = doctate_server::create_router(config);
let response = app
.oneshot(Request::builder().uri("/web/").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = body_to_string(response).await;
assert!(body.contains(case1));
assert!(body.contains(case2));
assert!(body.contains("dr_test"));
assert!(body.contains("<audio"));
assert!(body.contains("2026-04-13T10-30-00Z.m4a"));
// case1 has no transcript → pending placeholder
assert!(body.contains("Transcription pending"));
// case2 has a transcript → rendered
assert!(body.contains("Herzkatheter ohne Befund."));
// case2 has an oneliner → rendered exactly once (case1 has none)
assert!(body.contains("Herzkatheter unauffällig"));
assert_eq!(
body.matches("class=\"oneliner\"").count(),
1,
"expected exactly one oneliner div"
);
let _ = std::fs::remove_dir_all(&data_path);
}
#[tokio::test]
async fn web_audio_serves_file() {
let config = test_config();