diff options
| author | batsumaru <> | 2026-10-16 15:47:04 +0900 |
|---|---|---|
| committer | batsumaru <> | 2026-10-16 15:47:04 +0900 |
| commit | 67a57fa6221ed9bc6920a881784b308d4f5c3e6e (patch) | |
| tree | c37dbb4725c358f51fbdbd8d08dd67b8c6f5f8fb /src/tests.rs | |
| parent | 0033269fb879cab7e6227d0fd601f18402effe00 (diff) | |
Add csv export
Diffstat (limited to 'src/tests.rs')
| -rw-r--r-- | src/tests.rs | 114 |
1 files changed, 107 insertions, 7 deletions
diff --git a/src/tests.rs b/src/tests.rs index e97065a..9102975 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,9 +1,9 @@ use axum::{ Router, - body::Body, + body::{Body, Bytes}, http::{ HeaderMap, Method, Request, StatusCode, - header::{CONTENT_TYPE, LOCATION}, + header::{CONTENT_DISPOSITION, CONTENT_TYPE, LOCATION}, }, }; use chrono::{TimeDelta, Utc}; @@ -21,6 +21,25 @@ async fn send( uri: &str, body: Option<String>, ) -> (StatusCode, HeaderMap, Value) { + let (status, headers, bytes) = send_raw(app, method, uri, body).await; + ( + status, + headers, + serde_json::from_slice(&bytes).unwrap_or(Value::Null), + ) +} + +async fn get_text(app: &Router, uri: &str) -> (StatusCode, HeaderMap, String) { + let (status, headers, bytes) = send_raw(app, Method::GET, uri, None).await; + (status, headers, String::from_utf8(bytes.to_vec()).unwrap()) +} + +async fn send_raw( + app: &Router, + method: Method, + uri: &str, + body: Option<String>, +) -> (StatusCode, HeaderMap, Bytes) { let mut request = Request::builder().method(method).uri(uri); let body = match body { Some(body) => { @@ -37,11 +56,7 @@ async fn send( let status = response.status(); let headers = response.headers().clone(); let bytes = response.into_body().collect().await.unwrap().to_bytes(); - ( - status, - headers, - serde_json::from_slice(&bytes).unwrap_or(Value::Null), - ) + (status, headers, bytes) } async fn get_json(app: &Router, uri: &str) -> (StatusCode, Value) { @@ -564,3 +579,88 @@ async fn update_rejects_future_measured_at(pool: PgPool) { let (_, row) = get_json(&app, &uri).await; assert_eq!(row["value"], json!(80.0)); } + +#[sqlx::test] +async fn export_returns_a_csv_download(pool: PgPool) { + let app = router(pool); + let id = add_waist(&app, "2025-10-14T12:00:00Z", 80.5).await; + let (status, headers, csv) = get_text(&app, "/measurements/export").await; + assert_eq!(status, StatusCode::OK); + assert_eq!(headers[CONTENT_TYPE], "text/csv; charset=utf-8"); + assert_eq!( + headers[CONTENT_DISPOSITION], + "attachment; filename=\"measurements.csv\"" + ); + assert_eq!( + csv, + format!( + "id,measured_at,metric_type,value,unit\n{id},2025-10-14T12:00:00Z,Waist,80.5,Centimeter\n" + ) + ); +} + +#[sqlx::test] +async fn export_without_data_is_just_the_header(pool: PgPool) { + let app = router(pool); + let (status, _, csv) = get_text(&app, "/measurements/export").await; + assert_eq!(status, StatusCode::OK); + assert_eq!(csv, "id,measured_at,metric_type,value,unit\n"); +} + +#[sqlx::test] +async fn export_matches_the_list_for_the_same_query(pool: PgPool) { + let app = router(pool); + let rows = [ + ("2025-10-14T12:00:00Z", "BodyWeight", 70.0, "Kilogram"), + ("2025-10-15T12:00:00Z", "Waist", 80.0, "Centimeter"), + ("2025-10-16T12:00:00Z", "BodyWeight", 71.0, "Kilogram"), + ("2025-10-17T12:00:00Z", "Hip", 95.0, "Centimeter"), + ]; + for (measured_at, metric_type, value, unit) in rows { + let body = measurement(measured_at, metric_type, value, unit); + let (status, _, _) = send(&app, Method::POST, "/measurements", Some(body)).await; + assert_eq!(status, StatusCode::CREATED); + } + let queries = [ + "", + "?metric_type=BodyWeight", + "?order=desc&limit=2", + "?from=2025-10-15T00:00:00Z&to=2025-10-17T00:00:00Z", + "?metric_type=Hip&from=2026-01-01T00:00:00Z", + ]; + for query in queries { + let (_, listed) = get_json(&app, &format!("/measurements{query}")).await; + let listed: Vec<&str> = listed + .as_array() + .unwrap() + .iter() + .map(|row| row["id"].as_str().unwrap()) + .collect(); + let (status, _, csv) = get_text(&app, &format!("/measurements/export{query}")).await; + assert_eq!(status, StatusCode::OK, "{query}"); + let exported: Vec<&str> = csv + .lines() + .skip(1) + .map(|line| line.split(',').next().unwrap()) + .collect(); + assert_eq!(exported, listed, "{query}"); + } +} + +#[sqlx::test] +async fn export_rejects_bad_queries_with_json_errors(pool: PgPool) { + let app = router(pool); + let uris = [ + "/measurements/export?limit=0", + "/measurements/export?metric_type=Nope", + "/measurements/export?date=2025-10-15", + "/measurements/export?from=2025-10-16T00:00:00Z&to=2025-10-14T00:00:00Z", + ]; + for uri in uris { + let (status, _, body) = send(&app, Method::GET, uri, None).await; + assert_eq!(status, StatusCode::BAD_REQUEST, "{uri}"); + assert!(body["error"].is_string(), "{uri}: {body}"); + } + let (status, _, _) = send(&app, Method::POST, "/measurements/export", None).await; + assert_eq!(status, StatusCode::METHOD_NOT_ALLOWED); +} |
