test(health): 透析记录集成测试 — 8 个测试覆盖 CRUD/PII/状态流转/租户隔离/乐观锁/软删除
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

This commit is contained in:
iven
2026-04-27 21:42:24 +08:00
parent 88d01b5d84
commit 3aaa0a9598
2 changed files with 302 additions and 0 deletions

View File

@@ -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;

View File

@@ -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");
// 先将状态推进到 completeddraft → 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(), "无效患者应返回错误");
}