//! erp-health 设备读数集成测试 //! //! 验证批量摄入、设备绑定自动创建、hourly 聚合、查询过滤、参数校验、租户隔离。 use erp_health::service::device_reading_service::{ BatchReadingRequest, ReadingInput, }; use erp_health::service::device_reading_service; use chrono::Datelike; use sea_orm::ConnectionTrait; use super::test_fixture::TestApp; /// 确保当前月份有 device_readings 分区(测试用的 measured_at 是过去时间) async fn ensure_current_month_partition(app: &TestApp) { let now = chrono::Utc::now(); let year = now.format("%Y").to_string(); let month = now.format("%m").to_string(); let suffix = format!("{}_{}", year, month); let start = format!("{}-{}-01", year, month); // 计算下个月第一天 let next_month = if now.month() == 12 { format!("{}-01-01", year.parse::().unwrap() + 1) } else { format!("{}-{:02}-01", year, now.month() as u32 + 1) }; let sql = format!( "CREATE TABLE IF NOT EXISTS device_readings_{suffix} PARTITION OF device_readings FOR VALUES FROM ('{start}') TO ('{next_month}');" ); app.db().execute(sea_orm::Statement::from_string( sea_orm::DatabaseBackend::Postgres, sql, )).await.expect("创建分区应成功"); } /// 构建一条心率读数(measured_at 用几分钟前的时间) fn heart_rate_reading(bpm: i64, minutes_ago: i64) -> ReadingInput { let measured_at = (chrono::Utc::now() - chrono::Duration::minutes(minutes_ago)).to_rfc3339(); ReadingInput { device_type: "heart_rate".to_string(), values: serde_json::json!({"heart_rate": bpm}), measured_at, } } // --------------------------------------------------------------------------- // 测试 1: 批量摄入 — 单条读数成功 // --------------------------------------------------------------------------- #[tokio::test] async fn test_device_reading_batch_single() { let app = TestApp::new().await; ensure_current_month_partition(&app).await; let patient_id = app.create_patient("读数患者").await; let result = device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), patient_id, BatchReadingRequest { device_id: "watch-001".to_string(), device_model: Some("Apple Watch".to_string()), readings: vec![heart_rate_reading(75, 5)], }, ) .await .expect("摄入应成功"); assert_eq!(result.accepted, 1); assert_eq!(result.duplicates, 0); assert!(result.earliest.is_some()); assert!(result.latest.is_some()); } // --------------------------------------------------------------------------- // 测试 2: 批量摄入 — 多条读数 // --------------------------------------------------------------------------- #[tokio::test] async fn test_device_reading_batch_multiple() { let app = TestApp::new().await; ensure_current_month_partition(&app).await; let patient_id = app.create_patient("批量患者").await; let result = device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), patient_id, BatchReadingRequest { device_id: "watch-002".to_string(), device_model: None, readings: vec![ heart_rate_reading(70, 30), heart_rate_reading(72, 20), heart_rate_reading(68, 10), ], }, ) .await .expect("批量摄入应成功"); assert_eq!(result.accepted, 3); } // --------------------------------------------------------------------------- // 测试 3: 设备绑定自动创建 // --------------------------------------------------------------------------- #[tokio::test] async fn test_device_reading_creates_device_binding() { let app = TestApp::new().await; ensure_current_month_partition(&app).await; let patient_id = app.create_patient("绑定患者").await; device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), patient_id, BatchReadingRequest { device_id: "band-001".to_string(), device_model: Some("Mi Band".to_string()), readings: vec![heart_rate_reading(80, 5)], }, ) .await .unwrap(); // 再次使用同一设备,应更新 last_sync_at 而非重复创建 let result = device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), patient_id, BatchReadingRequest { device_id: "band-001".to_string(), device_model: Some("Mi Band".to_string()), readings: vec![heart_rate_reading(82, 2)], }, ) .await .expect("重复绑定应成功"); assert_eq!(result.accepted, 1); } // --------------------------------------------------------------------------- // 测试 4: hourly 聚合生成 // --------------------------------------------------------------------------- #[tokio::test] async fn test_device_reading_hourly_aggregation() { let app = TestApp::new().await; ensure_current_month_partition(&app).await; let patient_id = app.create_patient("聚合患者").await; device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), patient_id, BatchReadingRequest { device_id: "watch-003".to_string(), device_model: None, readings: vec![ heart_rate_reading(60, 25), heart_rate_reading(70, 20), heart_rate_reading(80, 15), ], }, ) .await .expect("摄入应成功"); // 查询 hourly 聚合 let hourly = device_reading_service::query_hourly_readings( app.health_state(), app.tenant_id(), patient_id, "heart_rate", 1, 1, 20, ) .await .expect("查询 hourly 应成功"); assert!(hourly.total > 0, "应有聚合记录"); let rec = &hourly.data[0]; assert_eq!(rec.device_type, "heart_rate"); assert!(rec.avg_val > 0.0); assert!(rec.sample_count > 0); } // --------------------------------------------------------------------------- // 测试 5: 查询读数 — 按类型过滤 // --------------------------------------------------------------------------- #[tokio::test] async fn test_device_reading_query_filter() { let app = TestApp::new().await; ensure_current_month_partition(&app).await; let patient_id = app.create_patient("查询患者").await; device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), patient_id, BatchReadingRequest { device_id: "watch-004".to_string(), device_model: None, readings: vec![ heart_rate_reading(72, 5), heart_rate_reading(74, 3), ], }, ) .await .unwrap(); let readings = device_reading_service::query_device_readings( app.health_state(), app.tenant_id(), patient_id, Some("heart_rate"), None, 1, 20, ) .await .expect("查询应成功"); assert_eq!(readings.total, 2); assert_eq!(readings.data[0].device_type, "heart_rate"); } // --------------------------------------------------------------------------- // 测试 6: 无效 device_type 返回错误 // --------------------------------------------------------------------------- #[tokio::test] async fn test_device_reading_invalid_device_type() { let app = TestApp::new().await; let patient_id = app.create_patient("校验患者").await; let result = device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), patient_id, BatchReadingRequest { device_id: "bad-001".to_string(), device_model: None, readings: vec![ReadingInput { device_type: "invalid_type".to_string(), values: serde_json::json!(42), measured_at: chrono::Utc::now().to_rfc3339(), }], }, ) .await; assert!(result.is_err(), "无效 device_type 应返回错误"); } // --------------------------------------------------------------------------- // 测试 7: 未来时间拒绝 // --------------------------------------------------------------------------- #[tokio::test] async fn test_device_reading_future_time_rejected() { let app = TestApp::new().await; let patient_id = app.create_patient("未来患者").await; let future_time = (chrono::Utc::now() + chrono::Duration::hours(1)).to_rfc3339(); let result = device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), patient_id, BatchReadingRequest { device_id: "watch-005".to_string(), device_model: None, readings: vec![ReadingInput { device_type: "heart_rate".to_string(), values: serde_json::json!({"heart_rate": 80}), measured_at: future_time, }], }, ) .await; assert!(result.is_err(), "未来时间应被拒绝"); } // --------------------------------------------------------------------------- // 测试 8: 无效患者返回错误 + 租户隔离 // --------------------------------------------------------------------------- #[tokio::test] async fn test_device_reading_invalid_patient_and_isolation() { let app = TestApp::new().await; ensure_current_month_partition(&app).await; // 无效患者 let fake_patient = uuid::Uuid::new_v4(); let result = device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), fake_patient, BatchReadingRequest { device_id: "watch-006".to_string(), device_model: None, readings: vec![heart_rate_reading(75, 5)], }, ) .await; assert!(result.is_err(), "无效患者应返回错误"); // 租户隔离:创建患者并摄入数据,用不同租户查询 let patient_id = app.create_patient("隔离患者").await; device_reading_service::batch_create_readings( app.health_state(), app.tenant_id(), patient_id, BatchReadingRequest { device_id: "watch-007".to_string(), device_model: None, readings: vec![heart_rate_reading(75, 5)], }, ) .await .unwrap(); let other_tenant = uuid::Uuid::new_v4(); let readings = device_reading_service::query_device_readings( app.health_state(), other_tenant, patient_id, None, None, 1, 20, ) .await .expect("查询应成功"); assert_eq!(readings.total, 0, "不同租户不应看到读数"); }