Files
hms/crates/erp-server/tests/integration/health_dialysis_tests.rs
iven 3aaa0a9598
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
test(health): 透析记录集成测试 — 8 个测试覆盖 CRUD/PII/状态流转/租户隔离/乐观锁/软删除
2026-04-27 21:42:24 +08:00

301 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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(), "无效患者应返回错误");
}