From 3aaa0a959843d94d53040341a9dcbc113748e6ff Mon Sep 17 00:00:00 2001 From: iven Date: Mon, 27 Apr 2026 21:42:24 +0800 Subject: [PATCH] =?UTF-8?q?test(health):=20=E9=80=8F=E6=9E=90=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E9=9B=86=E6=88=90=E6=B5=8B=E8=AF=95=20=E2=80=94=208?= =?UTF-8?q?=20=E4=B8=AA=E6=B5=8B=E8=AF=95=E8=A6=86=E7=9B=96=20CRUD/PII/?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=B5=81=E8=BD=AC/=E7=A7=9F=E6=88=B7?= =?UTF-8?q?=E9=9A=94=E7=A6=BB/=E4=B9=90=E8=A7=82=E9=94=81/=E8=BD=AF?= =?UTF-8?q?=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/erp-server/tests/integration.rs | 2 + .../integration/health_dialysis_tests.rs | 300 ++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 crates/erp-server/tests/integration/health_dialysis_tests.rs diff --git a/crates/erp-server/tests/integration.rs b/crates/erp-server/tests/integration.rs index 41b8619..3890a48 100644 --- a/crates/erp-server/tests/integration.rs +++ b/crates/erp-server/tests/integration.rs @@ -16,3 +16,5 @@ mod health_appointment_tests; mod health_pii_encryption_tests; #[path = "integration/health_points_tests.rs"] mod health_points_tests; +#[path = "integration/health_dialysis_tests.rs"] +mod health_dialysis_tests; diff --git a/crates/erp-server/tests/integration/health_dialysis_tests.rs b/crates/erp-server/tests/integration/health_dialysis_tests.rs new file mode 100644 index 0000000..bf2d72b --- /dev/null +++ b/crates/erp-server/tests/integration/health_dialysis_tests.rs @@ -0,0 +1,300 @@ +//! erp-health 透析记录集成测试 +//! +//! 验证透析 CRUD、状态流转、PII 加密、乐观锁、租户隔离等核心行为。 + +use chrono::{NaiveDate, NaiveTime}; +use erp_health::dto::dialysis_dto::*; +use erp_health::service::dialysis_service; + +use super::test_fixture::TestApp; + +fn default_create_req(patient_id: uuid::Uuid) -> CreateDialysisRecordReq { + CreateDialysisRecordReq { + patient_id, + dialysis_date: NaiveDate::from_ymd_opt(2026, 5, 1).unwrap(), + start_time: Some(NaiveTime::from_hms_opt(8, 0, 0).unwrap()), + end_time: Some(NaiveTime::from_hms_opt(12, 0, 0).unwrap()), + dry_weight: Some(65.0), + pre_weight: Some(68.0), + post_weight: Some(65.5), + pre_bp_systolic: Some(140), + pre_bp_diastolic: Some(90), + post_bp_systolic: Some(130), + post_bp_diastolic: Some(85), + pre_heart_rate: Some(80), + post_heart_rate: Some(75), + ultrafiltration_volume: Some(2500), + dialysis_duration: Some(240), + blood_flow_rate: Some(300), + dialysis_type: "HD".to_string(), + symptoms: None, + complication_notes: Some("无并发症".to_string()), + } +} + +// --------------------------------------------------------------------------- +// 测试 1: 创建透析记录基本 CRUD +// --------------------------------------------------------------------------- +#[tokio::test] +async fn test_dialysis_create_basic() { + let app = TestApp::new().await; + let patient_id = app.create_patient("透析患者").await; + + let req = default_create_req(patient_id); + let record = dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), req, + ) + .await + .expect("创建透析记录应成功"); + + assert_eq!(record.patient_id, patient_id); + assert_eq!(record.dialysis_type, "HD"); + assert_eq!(record.status, "draft"); + assert_eq!(record.ultrafiltration_volume, Some(2500)); + + // 读取 + let fetched = dialysis_service::get_dialysis_record( + app.health_state(), app.tenant_id(), record.id, + ) + .await + .expect("查询应成功"); + assert_eq!(fetched.id, record.id); +} + +// --------------------------------------------------------------------------- +// 测试 2: PII 字段加密存储 +// --------------------------------------------------------------------------- +#[tokio::test] +async fn test_dialysis_create_pii_encrypted() { + let app = TestApp::new().await; + let patient_id = app.create_patient("PII患者").await; + + let mut req = default_create_req(patient_id); + req.symptoms = Some(serde_json::json!({"头晕": "轻度", "恶心": "无"})); + req.complication_notes = Some("低血压发作".to_string()); + + let record = dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), req, + ) + .await + .expect("创建应成功"); + + // 通过 service 读取应自动解密 + assert!(record.symptoms.is_some()); + assert_eq!(record.complication_notes, Some("低血压发作".to_string())); +} + +// --------------------------------------------------------------------------- +// 测试 3: 状态流转 pending → reviewed +// --------------------------------------------------------------------------- +#[tokio::test] +async fn test_dialysis_update_status_flow() { + let app = TestApp::new().await; + let patient_id = app.create_patient("状态流转患者").await; + + let record = dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), + default_create_req(patient_id), + ) + .await + .unwrap(); + assert_eq!(record.status, "draft"); + + // 先将状态推进到 completed(draft → completed → reviewed) + use sea_orm::{EntityTrait, ColumnTrait, QueryFilter}; + use erp_health::entity::dialysis_record; + let _ = dialysis_record::Entity::update_many() + .col_expr(dialysis_record::Column::Status, sea_orm::sea_query::Expr::val("completed").into()) + .col_expr(dialysis_record::Column::Version, sea_orm::sea_query::Expr::val(record.version + 1).into()) + .filter(dialysis_record::Column::Id.eq(record.id)) + .exec(app.db()) + .await + .expect("状态推进应成功"); + + // 审核: completed → reviewed + let reviewed = dialysis_service::review_dialysis_record( + app.health_state(), app.tenant_id(), record.id, app.operator_id(), record.version + 1, + ) + .await + .expect("审核应成功"); + assert_eq!(reviewed.status, "reviewed"); + assert!(reviewed.reviewed_by.is_some()); +} + +// --------------------------------------------------------------------------- +// 测试 4: 按患者 ID 过滤列表 +// --------------------------------------------------------------------------- +#[tokio::test] +async fn test_dialysis_list_by_patient() { + let app = TestApp::new().await; + let patient_a = app.create_patient("列表患者A").await; + let patient_b = app.create_patient("列表患者B").await; + + dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), + default_create_req(patient_a), + ) + .await + .unwrap(); + + dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), + default_create_req(patient_a), + ) + .await + .unwrap(); + + dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), + default_create_req(patient_b), + ) + .await + .unwrap(); + + let list_a = dialysis_service::list_dialysis_records( + app.health_state(), app.tenant_id(), patient_a, 1, 20, + ) + .await + .unwrap(); + assert_eq!(list_a.total, 2); + + let list_b = dialysis_service::list_dialysis_records( + app.health_state(), app.tenant_id(), patient_b, 1, 20, + ) + .await + .unwrap(); + assert_eq!(list_b.total, 1); +} + +// --------------------------------------------------------------------------- +// 测试 5: 租户隔离 +// --------------------------------------------------------------------------- +#[tokio::test] +async fn test_dialysis_tenant_isolation() { + let app = TestApp::new().await; + let patient_id = app.create_patient("隔离患者").await; + + let record = dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), + default_create_req(patient_id), + ) + .await + .unwrap(); + + // 用不同 tenant_id 查询应失败 + let other_tenant = uuid::Uuid::new_v4(); + let result = dialysis_service::get_dialysis_record( + app.health_state(), other_tenant, record.id, + ) + .await; + assert!(result.is_err(), "不同租户不应看到此记录"); +} + +// --------------------------------------------------------------------------- +// 测试 6: 乐观锁 — 旧版本更新拒绝 +// --------------------------------------------------------------------------- +#[tokio::test] +async fn test_dialysis_version_conflict() { + let app = TestApp::new().await; + let patient_id = app.create_patient("乐观锁患者").await; + + let record = dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), + default_create_req(patient_id), + ) + .await + .unwrap(); + + // 用正确版本更新 + let updated = dialysis_service::update_dialysis_record( + app.health_state(), app.tenant_id(), record.id, Some(app.operator_id()), + UpdateDialysisRecordReq { + dialysis_date: None, start_time: None, end_time: None, + dry_weight: None, pre_weight: None, post_weight: None, + pre_bp_systolic: None, pre_bp_diastolic: None, + post_bp_systolic: None, post_bp_diastolic: None, + pre_heart_rate: None, post_heart_rate: None, + ultrafiltration_volume: None, dialysis_duration: None, + blood_flow_rate: None, + dialysis_type: Some("HDF".to_string()), + symptoms: None, complication_notes: None, + }, + record.version, + ) + .await + .expect("更新应成功"); + assert_eq!(updated.dialysis_type, "HDF"); + + // 用旧版本更新应失败 + let result = dialysis_service::update_dialysis_record( + app.health_state(), app.tenant_id(), record.id, Some(app.operator_id()), + UpdateDialysisRecordReq { + dialysis_date: None, start_time: None, end_time: None, + dry_weight: None, pre_weight: None, post_weight: None, + pre_bp_systolic: None, pre_bp_diastolic: None, + post_bp_systolic: None, post_bp_diastolic: None, + pre_heart_rate: None, post_heart_rate: None, + ultrafiltration_volume: None, dialysis_duration: None, + blood_flow_rate: None, + dialysis_type: Some("HD".to_string()), + symptoms: None, complication_notes: None, + }, + record.version, // 旧版本 + ) + .await; + assert!(result.is_err(), "旧版本更新应失败"); +} + +// --------------------------------------------------------------------------- +// 测试 7: 软删除后不可见 +// --------------------------------------------------------------------------- +#[tokio::test] +async fn test_dialysis_soft_delete() { + let app = TestApp::new().await; + let patient_id = app.create_patient("软删除患者").await; + + let record = dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), + default_create_req(patient_id), + ) + .await + .unwrap(); + + // 删除 + dialysis_service::delete_dialysis_record( + app.health_state(), app.tenant_id(), record.id, Some(app.operator_id()), record.version, + ) + .await + .expect("删除应成功"); + + // 查询应失败 + let result = dialysis_service::get_dialysis_record( + app.health_state(), app.tenant_id(), record.id, + ) + .await; + assert!(result.is_err(), "软删除后应不可见"); + + // 列表中不应出现 + let list = dialysis_service::list_dialysis_records( + app.health_state(), app.tenant_id(), patient_id, 1, 20, + ) + .await + .unwrap(); + assert_eq!(list.total, 0); +} + +// --------------------------------------------------------------------------- +// 测试 8: 无效患者 ID 返回错误 +// --------------------------------------------------------------------------- +#[tokio::test] +async fn test_dialysis_create_without_patient_returns_error() { + let app = TestApp::new().await; + let fake_patient = uuid::Uuid::new_v4(); + + let result = dialysis_service::create_dialysis_record( + app.health_state(), app.tenant_id(), Some(app.operator_id()), + default_create_req(fake_patient), + ) + .await; + assert!(result.is_err(), "无效患者应返回错误"); +}