3bb2d23adb
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.
126 lines
3.3 KiB
Rust
126 lines
3.3 KiB
Rust
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
use axum::body::Body;
|
|
use axum::http::{Request, StatusCode};
|
|
use tower::util::ServiceExt;
|
|
|
|
use doctate_server::config::{Config, User};
|
|
|
|
fn test_config() -> Arc<Config> {
|
|
let data_path = std::env::temp_dir().join(format!(
|
|
"doctate-web-test-{}-{}",
|
|
std::process::id(),
|
|
uuid::Uuid::new_v4()
|
|
));
|
|
Arc::new(Config {
|
|
data_path,
|
|
users: vec![User {
|
|
slug: "dr_test".into(),
|
|
api_key: "test-key".into(),
|
|
web_password: "unused".into(),
|
|
role: "doctor".into(),
|
|
whisper: Default::default(),
|
|
}],
|
|
api_keys: HashMap::from([("test-key".into(), "dr_test".into())]),
|
|
..Config::test_default()
|
|
})
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn web_audio_serves_file() {
|
|
let config = test_config();
|
|
let data_path = config.data_path.clone();
|
|
|
|
let case_id = "770e8400-e29b-41d4-a716-446655440000";
|
|
let case_dir = data_path.join("dr_test").join(case_id);
|
|
std::fs::create_dir_all(&case_dir).unwrap();
|
|
|
|
let audio_bytes = b"fake audio content for test";
|
|
let filename = "2026-04-13T10-30-00Z.m4a";
|
|
std::fs::write(case_dir.join(filename), audio_bytes).unwrap();
|
|
|
|
let app = doctate_server::create_router(config);
|
|
let response = app
|
|
.oneshot(
|
|
Request::builder()
|
|
.uri(format!("/web/audio/dr_test/{case_id}/{filename}"))
|
|
.body(Body::empty())
|
|
.unwrap(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
assert_eq!(
|
|
response
|
|
.headers()
|
|
.get("content-type")
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap(),
|
|
"audio/mp4"
|
|
);
|
|
|
|
let bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(&bytes[..], audio_bytes);
|
|
|
|
let _ = std::fs::remove_dir_all(&data_path);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn web_audio_path_traversal_rejected() {
|
|
let config = test_config();
|
|
let app = doctate_server::create_router(config);
|
|
|
|
let response = app
|
|
.oneshot(
|
|
Request::builder()
|
|
.uri("/web/audio/..%2Fetc/550e8400-e29b-41d4-a716-446655440000/foo.m4a")
|
|
.body(Body::empty())
|
|
.unwrap(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn web_audio_invalid_case_id_rejected() {
|
|
let config = test_config();
|
|
let app = doctate_server::create_router(config);
|
|
|
|
let response = app
|
|
.oneshot(
|
|
Request::builder()
|
|
.uri("/web/audio/dr_test/not-a-uuid/foo.m4a")
|
|
.body(Body::empty())
|
|
.unwrap(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn web_audio_nonexistent_file_returns_404() {
|
|
let config = test_config();
|
|
let app = doctate_server::create_router(config);
|
|
|
|
let response = app
|
|
.oneshot(
|
|
Request::builder()
|
|
.uri("/web/audio/dr_test/550e8400-e29b-41d4-a716-446655440000/missing.m4a")
|
|
.body(Body::empty())
|
|
.unwrap(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
|
}
|