- 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 个单元测试全部通过
229 lines
8.7 KiB
Rust
229 lines
8.7 KiB
Rust
//! erp-dialysis 透析方案管理集成测试
|
|
//!
|
|
//! 验证透析方案 CRUD、列表按患者过滤、租户隔离、乐观锁。
|
|
|
|
use erp_dialysis::dto::dialysis_prescription_dto::*;
|
|
use erp_dialysis::service::dialysis_prescription_service;
|
|
|
|
use super::test_fixture::TestApp;
|
|
|
|
fn default_create_req(patient_id: uuid::Uuid) -> CreateDialysisPrescriptionReq {
|
|
CreateDialysisPrescriptionReq {
|
|
patient_id,
|
|
dialyzer_model: Some("FX80".to_string()),
|
|
membrane_area: Some(1.8),
|
|
dialysate_potassium: Some(2.0),
|
|
dialysate_calcium: Some(1.5),
|
|
dialysate_bicarbonate: Some(35.0),
|
|
anticoagulation_type: Some("heparin".to_string()),
|
|
anticoagulation_dose: Some("2000IU".to_string()),
|
|
target_ultrafiltration_ml: Some(2000),
|
|
target_dry_weight: Some(65.0),
|
|
blood_flow_rate: Some(300),
|
|
dialysate_flow_rate: Some(500),
|
|
frequency_per_week: Some(3),
|
|
duration_minutes: Some(240),
|
|
vascular_access_type: Some("avf".to_string()),
|
|
vascular_access_location: Some("左前臂".to_string()),
|
|
effective_from: chrono::NaiveDate::from_ymd_opt(2026, 5, 1),
|
|
effective_to: None,
|
|
notes: None,
|
|
}
|
|
}
|
|
|
|
async fn seed_prescription(app: &TestApp, patient_id: uuid::Uuid) -> DialysisPrescriptionResp {
|
|
dialysis_prescription_service::create_prescription(
|
|
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
|
default_create_req(patient_id),
|
|
)
|
|
.await
|
|
.expect("创建透析方案应成功")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 测试 1: 创建透析方案
|
|
// ---------------------------------------------------------------------------
|
|
#[tokio::test]
|
|
async fn test_dialysis_prescription_create() {
|
|
let app = TestApp::new().await;
|
|
let patient_id = app.create_patient("透析方案患者").await;
|
|
|
|
let rx = seed_prescription(&app, patient_id).await;
|
|
|
|
assert_eq!(rx.patient_id, patient_id);
|
|
assert_eq!(rx.status, "active");
|
|
assert_eq!(rx.dialyzer_model, Some("FX80".to_string()));
|
|
assert_eq!(rx.frequency_per_week, Some(3));
|
|
assert_eq!(rx.version, 1);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 测试 2: 查询透析方案
|
|
// ---------------------------------------------------------------------------
|
|
#[tokio::test]
|
|
async fn test_dialysis_prescription_get() {
|
|
let app = TestApp::new().await;
|
|
let patient_id = app.create_patient("方案查询患者").await;
|
|
let rx = seed_prescription(&app, patient_id).await;
|
|
|
|
let fetched = dialysis_prescription_service::get_prescription(
|
|
app.dialysis_state(), app.tenant_id(), rx.id,
|
|
)
|
|
.await
|
|
.expect("查询应成功");
|
|
assert_eq!(fetched.id, rx.id);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 测试 3: 更新透析方案
|
|
// ---------------------------------------------------------------------------
|
|
#[tokio::test]
|
|
async fn test_dialysis_prescription_update() {
|
|
let app = TestApp::new().await;
|
|
let patient_id = app.create_patient("方案更新患者").await;
|
|
let rx = seed_prescription(&app, patient_id).await;
|
|
|
|
let updated = dialysis_prescription_service::update_prescription(
|
|
app.dialysis_state(), app.tenant_id(), rx.id,
|
|
Some(app.operator_id()),
|
|
UpdateDialysisPrescriptionReq {
|
|
blood_flow_rate: Some(350),
|
|
frequency_per_week: Some(4),
|
|
status: None,
|
|
dialyzer_model: None, membrane_area: None,
|
|
dialysate_potassium: None, dialysate_calcium: None,
|
|
dialysate_bicarbonate: None, anticoagulation_type: None,
|
|
anticoagulation_dose: None, target_ultrafiltration_ml: None,
|
|
target_dry_weight: None, dialysate_flow_rate: None,
|
|
duration_minutes: None, vascular_access_type: None,
|
|
vascular_access_location: None, effective_from: None,
|
|
effective_to: None, notes: None,
|
|
},
|
|
rx.version,
|
|
)
|
|
.await
|
|
.expect("更新应成功");
|
|
|
|
assert_eq!(updated.blood_flow_rate, Some(350));
|
|
assert_eq!(updated.frequency_per_week, Some(4));
|
|
assert_eq!(updated.version, 2);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 测试 4: 列表按患者过滤
|
|
// ---------------------------------------------------------------------------
|
|
#[tokio::test]
|
|
async fn test_dialysis_prescription_list_by_patient() {
|
|
let app = TestApp::new().await;
|
|
let patient_a = app.create_patient("方案列表A").await;
|
|
let patient_b = app.create_patient("方案列表B").await;
|
|
|
|
seed_prescription(&app, patient_a).await;
|
|
seed_prescription(&app, patient_b).await;
|
|
|
|
let list_a = dialysis_prescription_service::list_prescriptions(
|
|
app.dialysis_state(), app.tenant_id(), 1, 20, Some(patient_a), None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(list_a.total, 1);
|
|
|
|
let list_b = dialysis_prescription_service::list_prescriptions(
|
|
app.dialysis_state(), app.tenant_id(), 1, 20, Some(patient_b), None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(list_b.total, 1);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 测试 5: 软删除
|
|
// ---------------------------------------------------------------------------
|
|
#[tokio::test]
|
|
async fn test_dialysis_prescription_soft_delete() {
|
|
let app = TestApp::new().await;
|
|
let patient_id = app.create_patient("方案删除患者").await;
|
|
let rx = seed_prescription(&app, patient_id).await;
|
|
|
|
dialysis_prescription_service::delete_prescription(
|
|
app.dialysis_state(), app.tenant_id(), rx.id,
|
|
Some(app.operator_id()), rx.version,
|
|
)
|
|
.await
|
|
.expect("删除应成功");
|
|
|
|
let result = dialysis_prescription_service::get_prescription(
|
|
app.dialysis_state(), app.tenant_id(), rx.id,
|
|
)
|
|
.await;
|
|
assert!(result.is_err(), "软删除后查询应失败");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 测试 6: 租户隔离
|
|
// ---------------------------------------------------------------------------
|
|
#[tokio::test]
|
|
async fn test_dialysis_prescription_tenant_isolation() {
|
|
let app = TestApp::new().await;
|
|
let patient_id = app.create_patient("方案隔离患者").await;
|
|
seed_prescription(&app, patient_id).await;
|
|
|
|
let other_tenant = uuid::Uuid::new_v4();
|
|
let list = dialysis_prescription_service::list_prescriptions(
|
|
app.dialysis_state(), other_tenant, 1, 20, None, None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(list.total, 0, "不同租户不应看到方案");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 测试 7: 乐观锁冲突
|
|
// ---------------------------------------------------------------------------
|
|
#[tokio::test]
|
|
async fn test_dialysis_prescription_version_conflict() {
|
|
let app = TestApp::new().await;
|
|
let patient_id = app.create_patient("方案锁患者").await;
|
|
let rx = seed_prescription(&app, patient_id).await;
|
|
|
|
// 先更新一次
|
|
dialysis_prescription_service::update_prescription(
|
|
app.dialysis_state(), app.tenant_id(), rx.id,
|
|
Some(app.operator_id()),
|
|
UpdateDialysisPrescriptionReq {
|
|
blood_flow_rate: Some(350),
|
|
status: None, dialyzer_model: None, membrane_area: None,
|
|
dialysate_potassium: None, dialysate_calcium: None,
|
|
dialysate_bicarbonate: None, anticoagulation_type: None,
|
|
anticoagulation_dose: None, target_ultrafiltration_ml: None,
|
|
target_dry_weight: None, frequency_per_week: None,
|
|
dialysate_flow_rate: None, duration_minutes: None,
|
|
vascular_access_type: None, vascular_access_location: None,
|
|
effective_from: None, effective_to: None, notes: None,
|
|
},
|
|
rx.version,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// 用旧 version 再更新应失败
|
|
let result = dialysis_prescription_service::update_prescription(
|
|
app.dialysis_state(), app.tenant_id(), rx.id,
|
|
Some(app.operator_id()),
|
|
UpdateDialysisPrescriptionReq {
|
|
blood_flow_rate: Some(400),
|
|
status: None, dialyzer_model: None, membrane_area: None,
|
|
dialysate_potassium: None, dialysate_calcium: None,
|
|
dialysate_bicarbonate: None, anticoagulation_type: None,
|
|
anticoagulation_dose: None, target_ultrafiltration_ml: None,
|
|
target_dry_weight: None, frequency_per_week: None,
|
|
dialysate_flow_rate: None, duration_minutes: None,
|
|
vascular_access_type: None, vascular_access_location: None,
|
|
effective_from: None, effective_to: None, notes: None,
|
|
},
|
|
rx.version,
|
|
)
|
|
.await;
|
|
assert!(result.is_err(), "乐观锁冲突应返回错误");
|
|
}
|