//! Medication-endpoint integration tests. //! //! In-process against an isolated MongoDB test DB (see `common`). When Mongo is //! unreachable these skip gracefully. Verifies auth enforcement and an //! authenticated create -> list flow with the zero-knowledge opaque-blob contract. mod common; use serde_json::json; /// Skip when Mongo is unavailable (counts as a pass); CI provides Mongo. macro_rules! require_app { ($app:expr) => { match $app { Some(x) => x, None => { eprintln!("[integration] skipped (MongoDB unavailable)"); return; } } }; } #[tokio::test] async fn create_medication_requires_auth() { let (app, db_name) = require_app!(common::app_for_test().await); let (status, _) = common::send_json( &app, "POST", "/api/medications", Some(json!({ "profile_id": "default", "encrypted_data": { "data": "Y3QA==", "iv": "aXY=" }, })), None, ) .await; assert_eq!(status, 401); common::drop_test_db(&db_name).await; } #[tokio::test] async fn list_medications_requires_auth() { let (app, db_name) = require_app!(common::app_for_test().await); let (status, _) = common::send_json(&app, "GET", "/api/medications", None, None).await; assert_eq!(status, 401); common::drop_test_db(&db_name).await; } #[tokio::test] async fn get_medication_requires_auth() { let (app, db_name) = require_app!(common::app_for_test().await); let (status, _) = common::send_json( &app, "GET", "/api/medications/507f1f77bcf86cd799439011", None, None, ) .await; assert_eq!(status, 401); common::drop_test_db(&db_name).await; } #[tokio::test] async fn authenticated_user_can_create_and_list_medication() { let (app, db_name) = require_app!(common::app_for_test().await); let email = unique_email(); // Register and grab the access token. let (status, body) = common::send_json( &app, "POST", "/api/auth/register", Some(json!({ "email": email, "username": "tester", "password": "supersecret" })), None, ) .await; assert_eq!(status, 201); let token = body["token"].as_str().unwrap().to_string(); // Create a medication with an opaque encrypted blob (zero-knowledge). let opaque = "Y2lwaGVydGV4dA=="; let (status, body) = common::send_json( &app, "POST", "/api/medications", Some(json!({ "profile_id": "default", "encrypted_data": { "data": opaque, "iv": "aXY=" }, })), Some(&token), ) .await; assert!( status == 200 || status == 201, "create should succeed, body: {body}" ); // Response should echo the opaque blob, not expose plaintext fields. assert_eq!(body["encrypted_data"]["data"], opaque); assert!(body.get("name").is_none(), "server leaked plaintext"); // List medications — should include the one we just created. let (status, body) = common::send_json(&app, "GET", "/api/medications", None, Some(&token)).await; assert_eq!(status, 200, "list should succeed, body: {body}"); let empty = Vec::new(); let arr = body.as_array().unwrap_or(&empty); assert!( !arr.is_empty(), "list should contain the created medication" ); // Each list item should be opaque. assert!( arr[0].get("encrypted_data").is_some(), "list item has encrypted_data" ); assert!(arr[0].get("name").is_none(), "list item leaked plaintext"); common::drop_test_db(&db_name).await; } fn unique_email() -> String { format!("test_{}@example.com", uuid::Uuid::new_v4()) }