Files
hms/crates/erp-server/tests/integration/health_dialysis_tests.rs
iven 5941a6b764
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
feat(dialysis): 激活 erp-dialysis 独立模块 — 注册到 erp-server
- workspace Cargo.toml 添加 erp-dialysis 依赖声明
- erp-server 注册 DialysisModule 并挂载透析路由
- 修复权限码:health.health-data.* → health.dialysis.list/manage
- 集成测试迁移:erp_health → erp_dialysis import + DialysisState
- TestApp 新增 dialysis_state() 方法
- cargo check 通过,erp-dialysis 10 个单元测试全部通过
2026-04-28 15:21:13 +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-dialysis 透析记录集成测试
//!
//! 验证透析 CRUD、状态流转、PII 加密、乐观锁、租户隔离等核心行为。
use chrono::{NaiveDate, NaiveTime};
use erp_dialysis::dto::dialysis_dto::*;
use erp_dialysis::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.dialysis_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.dialysis_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.dialysis_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.dialysis_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_dialysis::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.dialysis_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.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
default_create_req(patient_a),
)
.await
.unwrap();
dialysis_service::create_dialysis_record(
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
default_create_req(patient_a),
)
.await
.unwrap();
dialysis_service::create_dialysis_record(
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
default_create_req(patient_b),
)
.await
.unwrap();
let list_a = dialysis_service::list_dialysis_records(
app.dialysis_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.dialysis_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.dialysis_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.dialysis_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.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
default_create_req(patient_id),
)
.await
.unwrap();
// 用正确版本更新
let updated = dialysis_service::update_dialysis_record(
app.dialysis_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.dialysis_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.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
default_create_req(patient_id),
)
.await
.unwrap();
// 删除
dialysis_service::delete_dialysis_record(
app.dialysis_state(), app.tenant_id(), record.id, Some(app.operator_id()), record.version,
)
.await
.expect("删除应成功");
// 查询应失败
let result = dialysis_service::get_dialysis_record(
app.dialysis_state(), app.tenant_id(), record.id,
)
.await;
assert!(result.is_err(), "软删除后应不可见");
// 列表中不应出现
let list = dialysis_service::list_dialysis_records(
app.dialysis_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.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
default_create_req(fake_patient),
)
.await;
assert!(result.is_err(), "无效患者应返回错误");
}