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:
+162
-199
@@ -1,131 +1,62 @@
|
||||
use std::collections::HashMap;
|
||||
mod common;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request, StatusCode};
|
||||
use axum::http::StatusCode;
|
||||
use doctate_common::oneliners::OnelinerState;
|
||||
use filetime::FileTime;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use doctate_common::oneliners::OnelinerState;
|
||||
use doctate_server::config::{Config, User};
|
||||
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,
|
||||
test_user_with_window_hours,
|
||||
};
|
||||
|
||||
const TEST_KEY: &str = "test-key-oneliners";
|
||||
const TEST_SLUG: &str = "dr_oneliners";
|
||||
const TEST_KEY: &str = "key-dr_oneliners";
|
||||
|
||||
fn test_config() -> (Arc<Config>, PathBuf) {
|
||||
let data_path = std::env::temp_dir().join(format!(
|
||||
"doctate-oneliners-{}-{}",
|
||||
std::process::id(),
|
||||
uuid::Uuid::new_v4()
|
||||
));
|
||||
let config = Arc::new(Config {
|
||||
data_path: data_path.clone(),
|
||||
users: vec![User {
|
||||
slug: TEST_SLUG.into(),
|
||||
api_key: TEST_KEY.into(),
|
||||
web_password: "unused".into(),
|
||||
role: "doctor".into(),
|
||||
whisper: Default::default(),
|
||||
retention: Default::default(),
|
||||
window_hours: 72,
|
||||
preview_lines: 2,
|
||||
}],
|
||||
api_keys: HashMap::from([(TEST_KEY.into(), TEST_SLUG.into())]),
|
||||
..Config::test_default()
|
||||
});
|
||||
(config, data_path)
|
||||
fn test_config() -> (std::sync::Arc<doctate_server::config::Config>, PathBuf) {
|
||||
let cfg = TestConfig::new()
|
||||
.with_label("oneliners")
|
||||
.with_user(test_user(TEST_SLUG))
|
||||
.build();
|
||||
let dp = cfg.data_path.clone();
|
||||
(cfg, dp)
|
||||
}
|
||||
|
||||
fn test_config_with_window_hours(hours: u32) -> (Arc<Config>, PathBuf) {
|
||||
let (cfg, dp) = test_config();
|
||||
// Safe because `test_config` just created the Arc and handed it
|
||||
// back with refcount == 1.
|
||||
let mut cfg =
|
||||
Arc::try_unwrap(cfg).unwrap_or_else(|_| unreachable!("test_config Arc should be unique"));
|
||||
cfg.users[0].window_hours = hours;
|
||||
(Arc::new(cfg), dp)
|
||||
fn test_config_with_window_hours(
|
||||
hours: u32,
|
||||
) -> (std::sync::Arc<doctate_server::config::Config>, PathBuf) {
|
||||
let cfg = TestConfig::new()
|
||||
.with_label("oneliners-win")
|
||||
.with_user(test_user_with_window_hours(TEST_SLUG, hours))
|
||||
.build();
|
||||
let dp = cfg.data_path.clone();
|
||||
(cfg, dp)
|
||||
}
|
||||
|
||||
async fn seed_case(
|
||||
/// Seed a case with an aged m4a and optionally a ready oneliner.
|
||||
/// Combines the common primitives — a single call is more ergonomic
|
||||
/// here than three repeated triples across every test.
|
||||
fn seed(
|
||||
user_root: &Path,
|
||||
case_id: &str,
|
||||
m4a_age: Duration,
|
||||
oneliner_text: Option<&str>,
|
||||
) -> PathBuf {
|
||||
let case_dir = user_root.join(TEST_SLUG).join(case_id);
|
||||
tokio::fs::create_dir_all(&case_dir).await.unwrap();
|
||||
|
||||
// Write a dummy .m4a and rewind its mtime to simulate "case born N ago".
|
||||
let m4a = case_dir.join("2026-04-18T10-00-00Z.m4a");
|
||||
tokio::fs::write(&m4a, b"x").await.unwrap();
|
||||
let target_mtime = SystemTime::now() - m4a_age;
|
||||
filetime::set_file_mtime(&m4a, FileTime::from_system_time(target_mtime)).unwrap();
|
||||
|
||||
std::fs::create_dir_all(&case_dir).unwrap();
|
||||
seed_recording_with_age(&case_dir, "2026-04-18T10-00-00Z", m4a_age);
|
||||
if let Some(text) = oneliner_text {
|
||||
write_ready_state(&case_dir, text).await;
|
||||
seed_oneliner_ready(&case_dir, text);
|
||||
}
|
||||
case_dir
|
||||
}
|
||||
|
||||
async fn write_ready_state(case_dir: &Path, text: &str) {
|
||||
let state = OnelinerState::Ready {
|
||||
text: text.to_owned(),
|
||||
generated_at: "2026-04-18T10:00:00Z".into(),
|
||||
};
|
||||
let bytes = serde_json::to_vec(&state).unwrap();
|
||||
tokio::fs::write(case_dir.join("oneliner.json"), bytes)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn write_state(case_dir: &Path, state: &OnelinerState) {
|
||||
let bytes = serde_json::to_vec(state).unwrap();
|
||||
tokio::fs::write(case_dir.join("oneliner.json"), bytes)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn mark_deleted(case_dir: &Path) {
|
||||
tokio::fs::write(case_dir.join(".closed"), "{}")
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn get(uri: &str) -> Request<Body> {
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri(uri)
|
||||
.header("X-API-Key", TEST_KEY)
|
||||
.body(Body::empty())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn get_with_if_none_match(uri: &str, etag: &str) -> Request<Body> {
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri(uri)
|
||||
.header("X-API-Key", TEST_KEY)
|
||||
.header("If-None-Match", etag)
|
||||
.body(Body::empty())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn body_json(response: axum::http::Response<axum::body::Body>) -> serde_json::Value {
|
||||
let bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
serde_json::from_slice(&bytes).unwrap()
|
||||
}
|
||||
|
||||
fn header_str(response: &axum::http::Response<axum::body::Body>, name: &str) -> String {
|
||||
response
|
||||
.headers()
|
||||
.get(name)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("")
|
||||
.to_owned()
|
||||
fn mark_deleted(case_dir: &Path) {
|
||||
std::fs::write(case_dir.join(".closed"), "{}").unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -135,10 +66,10 @@ async fn returns_401_without_api_key() {
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
axum::http::Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/oneliners")
|
||||
.body(Body::empty())
|
||||
.body(axum::body::Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
@@ -149,10 +80,13 @@ async fn returns_401_without_api_key() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_user_dir_returns_200_empty_list() {
|
||||
let (config, dp) = test_config();
|
||||
let (config, _dp) = test_config();
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let response = app.oneshot(get("/api/oneliners")).await.unwrap();
|
||||
let response = app
|
||||
.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let etag = header_str(&response, "etag");
|
||||
@@ -162,24 +96,24 @@ async fn empty_user_dir_returns_200_empty_list() {
|
||||
let body = body_json(response).await;
|
||||
assert_eq!(body["window_hours"], 72);
|
||||
assert!(body["oneliners"].as_array().unwrap().is_empty());
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn recent_case_with_oneliner_shows_up() {
|
||||
let (config, dp) = test_config();
|
||||
let case_id = "550e8400-e29b-41d4-a716-000000000001";
|
||||
seed_case(
|
||||
seed(
|
||||
&dp,
|
||||
case_id,
|
||||
Duration::from_secs(60),
|
||||
Some("Kniegelenk re., V.a. Meniskus"),
|
||||
)
|
||||
.await;
|
||||
);
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let response = app.oneshot(get("/api/oneliners")).await.unwrap();
|
||||
let response = app
|
||||
.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = body_json(response).await;
|
||||
let arr = body["oneliners"].as_array().unwrap();
|
||||
@@ -189,46 +123,48 @@ async fn recent_case_with_oneliner_shows_up() {
|
||||
assert_eq!(arr[0]["oneliner"]["text"], "Kniegelenk re., V.a. Meniskus");
|
||||
assert!(arr[0]["created_at"].is_string());
|
||||
assert!(arr[0]["updated_at"].is_string());
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn case_without_oneliner_shows_null() {
|
||||
let (config, dp) = test_config();
|
||||
seed_case(
|
||||
seed(
|
||||
&dp,
|
||||
"550e8400-e29b-41d4-a716-000000000002",
|
||||
Duration::from_secs(60),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let body = body_json(app.oneshot(get("/api/oneliners")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
let arr = body["oneliners"].as_array().unwrap();
|
||||
assert_eq!(arr.len(), 1);
|
||||
assert!(arr[0]["oneliner"].is_null());
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn case_older_than_default_window_excluded() {
|
||||
let (config, dp) = test_config();
|
||||
seed_case(
|
||||
seed(
|
||||
&dp,
|
||||
"550e8400-e29b-41d4-a716-000000000003",
|
||||
Duration::from_secs(80 * 3600), // 80 h old — outside the 72 h default
|
||||
Some("old case"),
|
||||
)
|
||||
.await;
|
||||
);
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let body = body_json(app.oneshot(get("/api/oneliners")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
assert!(body["oneliners"].as_array().unwrap().is_empty());
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
/// Demonstrates that the visibility decision lives in users.toml:
|
||||
@@ -240,71 +176,88 @@ async fn case_visibility_follows_user_window_hours() {
|
||||
let age = Duration::from_secs(40 * 3600);
|
||||
|
||||
let (config_small, dp_small) = test_config_with_window_hours(16);
|
||||
seed_case(&dp_small, case_id, age, Some("40h case")).await;
|
||||
seed(&dp_small, case_id, age, Some("40h case"));
|
||||
let app_small = doctate_server::create_router(config_small);
|
||||
let body = body_json(app_small.oneshot(get("/api/oneliners")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app_small
|
||||
.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(body["window_hours"], 16);
|
||||
assert!(body["oneliners"].as_array().unwrap().is_empty());
|
||||
|
||||
let (config_wide, dp_wide) = test_config_with_window_hours(48);
|
||||
seed_case(&dp_wide, case_id, age, Some("40h case")).await;
|
||||
seed(&dp_wide, case_id, age, Some("40h case"));
|
||||
let app_wide = doctate_server::create_router(config_wide);
|
||||
let body = body_json(app_wide.oneshot(get("/api/oneliners")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app_wide
|
||||
.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(body["window_hours"], 48);
|
||||
assert_eq!(body["oneliners"].as_array().unwrap().len(), 1);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp_small);
|
||||
let _ = std::fs::remove_dir_all(&dp_wide);
|
||||
}
|
||||
|
||||
/// Regression guard: a lingering `?hours=N` from an old client is
|
||||
/// silently ignored — the user-config value drives the response.
|
||||
#[tokio::test]
|
||||
async fn hours_query_param_is_ignored() {
|
||||
let (config, dp) = test_config();
|
||||
let (config, _dp) = test_config();
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let body = body_json(app.oneshot(get("/api/oneliners?hours=999")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app.oneshot(get_with_api_key("/api/oneliners?hours=999", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(body["window_hours"], 72);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn matching_if_none_match_returns_304() {
|
||||
let (config, dp) = test_config();
|
||||
let (config, _dp) = test_config();
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let first = app.clone().oneshot(get("/api/oneliners")).await.unwrap();
|
||||
let first = app
|
||||
.clone()
|
||||
.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(first.status(), StatusCode::OK);
|
||||
let etag = header_str(&first, "etag");
|
||||
assert!(!etag.is_empty());
|
||||
|
||||
let second = app
|
||||
.oneshot(get_with_if_none_match("/api/oneliners", &etag))
|
||||
.oneshot(get_with_api_key_if_none_match(
|
||||
"/api/oneliners",
|
||||
TEST_KEY,
|
||||
&etag,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(second.status(), StatusCode::NOT_MODIFIED);
|
||||
assert_eq!(header_str(&second, "etag"), etag);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stale_if_none_match_returns_200() {
|
||||
let (config, dp) = test_config();
|
||||
let (config, _dp) = test_config();
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let response = app
|
||||
.oneshot(get_with_if_none_match(
|
||||
.oneshot(get_with_api_key_if_none_match(
|
||||
"/api/oneliners",
|
||||
TEST_KEY,
|
||||
"W/\"99999999-72\"",
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
/// Two users with different `window_hours` must produce different
|
||||
@@ -312,70 +265,76 @@ async fn stale_if_none_match_returns_200() {
|
||||
/// users.toml invalidates client caches after the next restart.
|
||||
#[tokio::test]
|
||||
async fn etag_differs_between_users_with_different_window_hours() {
|
||||
let (config_a, dp_a) = test_config_with_window_hours(16);
|
||||
let (config_b, dp_b) = test_config_with_window_hours(48);
|
||||
let (config_a, _dp_a) = test_config_with_window_hours(16);
|
||||
let (config_b, _dp_b) = test_config_with_window_hours(48);
|
||||
|
||||
let app_a = doctate_server::create_router(config_a);
|
||||
let app_b = doctate_server::create_router(config_b);
|
||||
|
||||
let r_a = app_a.oneshot(get("/api/oneliners")).await.unwrap();
|
||||
let r_b = app_b.oneshot(get("/api/oneliners")).await.unwrap();
|
||||
let r_a = app_a
|
||||
.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap();
|
||||
let r_b = app_b
|
||||
.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let etag_a = header_str(&r_a, "etag");
|
||||
let etag_b = header_str(&r_b, "etag");
|
||||
assert_ne!(etag_a, etag_b);
|
||||
assert!(etag_a.ends_with("-16\""), "etag_a was {etag_a:?}");
|
||||
assert!(etag_b.ends_with("-48\""), "etag_b was {etag_b:?}");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp_a);
|
||||
let _ = std::fs::remove_dir_all(&dp_b);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn deleted_case_is_excluded() {
|
||||
let (config, dp) = test_config();
|
||||
let case_dir = seed_case(
|
||||
let case_dir = seed(
|
||||
&dp,
|
||||
"550e8400-e29b-41d4-a716-000000000005",
|
||||
Duration::from_secs(60),
|
||||
Some("deleted case"),
|
||||
)
|
||||
.await;
|
||||
mark_deleted(&case_dir).await;
|
||||
);
|
||||
mark_deleted(&case_dir);
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let body = body_json(app.oneshot(get("/api/oneliners")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
assert!(body["oneliners"].as_array().unwrap().is_empty());
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multiple_cases_sorted_newest_first() {
|
||||
let (config, dp) = test_config();
|
||||
seed_case(
|
||||
seed(
|
||||
&dp,
|
||||
"550e8400-e29b-41d4-a716-000000000010",
|
||||
Duration::from_secs(2 * 3600),
|
||||
Some("older"),
|
||||
)
|
||||
.await;
|
||||
seed_case(
|
||||
);
|
||||
seed(
|
||||
&dp,
|
||||
"550e8400-e29b-41d4-a716-000000000011",
|
||||
Duration::from_secs(30),
|
||||
Some("newer"),
|
||||
)
|
||||
.await;
|
||||
);
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let body = body_json(app.oneshot(get("/api/oneliners")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
let arr = body["oneliners"].as_array().unwrap();
|
||||
assert_eq!(arr.len(), 2);
|
||||
assert_eq!(arr[0]["oneliner"]["text"], "newer");
|
||||
assert_eq!(arr[1]["oneliner"]["text"], "older");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
/// Regression guard: if a case was first created long ago but got a
|
||||
@@ -390,94 +349,98 @@ async fn sorts_by_last_recording_not_created() {
|
||||
// (new last_recording_at).
|
||||
let case_a = "550e8400-e29b-41d4-a716-000000000020";
|
||||
let dir_a = dp.join(TEST_SLUG).join(case_a);
|
||||
tokio::fs::create_dir_all(&dir_a).await.unwrap();
|
||||
std::fs::create_dir_all(&dir_a).unwrap();
|
||||
let old_m4a = dir_a.join("2026-04-17T12-00-00Z.m4a");
|
||||
tokio::fs::write(&old_m4a, b"x").await.unwrap();
|
||||
std::fs::write(&old_m4a, b"x").unwrap();
|
||||
filetime::set_file_mtime(
|
||||
&old_m4a,
|
||||
FileTime::from_system_time(SystemTime::now() - Duration::from_secs(20 * 3600)),
|
||||
)
|
||||
.unwrap();
|
||||
let fresh_m4a = dir_a.join("2026-04-18T07-59-00Z.m4a");
|
||||
tokio::fs::write(&fresh_m4a, b"x").await.unwrap();
|
||||
std::fs::write(&fresh_m4a, b"x").unwrap();
|
||||
filetime::set_file_mtime(
|
||||
&fresh_m4a,
|
||||
FileTime::from_system_time(SystemTime::now() - Duration::from_secs(60)),
|
||||
)
|
||||
.unwrap();
|
||||
write_ready_state(&dir_a, "A (fresh addendum)").await;
|
||||
seed_oneliner_ready(&dir_a, "A (fresh addendum)");
|
||||
|
||||
// Case B: single recording 10h ago — created_at newer than A's, but
|
||||
// last_recording_at older than A's fresh addendum.
|
||||
seed_case(
|
||||
seed(
|
||||
&dp,
|
||||
"550e8400-e29b-41d4-a716-000000000021",
|
||||
Duration::from_secs(10 * 3600),
|
||||
Some("B (single, middle-aged)"),
|
||||
)
|
||||
.await;
|
||||
);
|
||||
|
||||
let app = doctate_server::create_router(config);
|
||||
let body = body_json(app.oneshot(get("/api/oneliners")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
let arr = body["oneliners"].as_array().unwrap();
|
||||
assert_eq!(arr.len(), 2);
|
||||
assert_eq!(arr[0]["case_id"], case_a, "A must sort first");
|
||||
assert!(arr[0]["last_recording_at"].is_string());
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_state_serializes_as_kind_empty() {
|
||||
let (config, dp) = test_config();
|
||||
let case_dir = seed_case(
|
||||
let case_dir = seed(
|
||||
&dp,
|
||||
"550e8400-e29b-41d4-a716-000000000030",
|
||||
Duration::from_secs(60),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
write_state(
|
||||
);
|
||||
seed_oneliner_state(
|
||||
&case_dir,
|
||||
&OnelinerState::Empty {
|
||||
generated_at: "2026-04-18T10:00:00Z".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
);
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let body = body_json(app.oneshot(get("/api/oneliners")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
let arr = body["oneliners"].as_array().unwrap();
|
||||
assert_eq!(arr.len(), 1);
|
||||
assert_eq!(arr[0]["oneliner"]["kind"], "empty");
|
||||
assert!(arr[0]["oneliner"].get("text").is_none());
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn error_state_serializes_as_kind_error() {
|
||||
let (config, dp) = test_config();
|
||||
let case_dir = seed_case(
|
||||
let case_dir = seed(
|
||||
&dp,
|
||||
"550e8400-e29b-41d4-a716-000000000031",
|
||||
Duration::from_secs(60),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
write_state(
|
||||
);
|
||||
seed_oneliner_state(
|
||||
&case_dir,
|
||||
&OnelinerState::Error {
|
||||
generated_at: "2026-04-18T10:00:00Z".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
);
|
||||
let app = doctate_server::create_router(config);
|
||||
|
||||
let body = body_json(app.oneshot(get("/api/oneliners")).await.unwrap()).await;
|
||||
let body = body_json(
|
||||
app.oneshot(get_with_api_key("/api/oneliners", TEST_KEY))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
let arr = body["oneliners"].as_array().unwrap();
|
||||
assert_eq!(arr.len(), 1);
|
||||
assert_eq!(arr[0]["oneliner"]["kind"], "error");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dp);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user