test(health): 7 个模块集成测试 — 49 个用例全通过
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

新增 doctor(7)、diagnosis(7)、consent(6)、medication(8)、
dialysis_prescription(7)、follow_up_template(7)、daily_monitoring(7)
覆盖 CRUD、状态流、列表过滤、软删除、租户隔离、乐观锁。
This commit is contained in:
iven
2026-04-27 23:21:04 +08:00
parent 2d5b6d4c50
commit 5aec02e4ad
8 changed files with 1504 additions and 0 deletions

View File

@@ -30,3 +30,17 @@ mod health_consultation_tests;
mod health_data_tests;
#[path = "integration/health_article_tests.rs"]
mod health_article_tests;
#[path = "integration/health_doctor_tests.rs"]
mod health_doctor_tests;
#[path = "integration/health_diagnosis_tests.rs"]
mod health_diagnosis_tests;
#[path = "integration/health_consent_tests.rs"]
mod health_consent_tests;
#[path = "integration/health_medication_tests.rs"]
mod health_medication_tests;
#[path = "integration/health_dialysis_prescription_tests.rs"]
mod health_dialysis_prescription_tests;
#[path = "integration/health_follow_up_template_tests.rs"]
mod health_follow_up_template_tests;
#[path = "integration/health_daily_monitoring_tests.rs"]
mod health_daily_monitoring_tests;

View File

@@ -0,0 +1,162 @@
//! erp-health 知情同意集成测试
//!
//! 验证同意授权、撤销、列表按患者过滤、租户隔离。
use erp_health::dto::consent_dto::*;
use erp_health::service::consent_service;
use super::test_fixture::TestApp;
fn default_create_consent_req(patient_id: uuid::Uuid) -> CreateConsentReq {
CreateConsentReq {
patient_id,
consent_type: "data_processing".to_string(),
consent_scope: "健康数据采集与处理".to_string(),
expiry_date: None,
consent_method: Some("电子签名".to_string()),
witness_name: None,
notes: None,
}
}
async fn seed_consent(app: &TestApp, patient_id: uuid::Uuid) -> ConsentResp {
consent_service::grant_consent(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_consent_req(patient_id),
)
.await
.expect("授权应成功")
}
// ---------------------------------------------------------------------------
// 测试 1: 授权同意
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_consent_grant() {
let app = TestApp::new().await;
let patient_id = app.create_patient("同意患者").await;
let consent = seed_consent(&app, patient_id).await;
assert_eq!(consent.patient_id, patient_id);
assert_eq!(consent.consent_type, "data_processing");
assert_eq!(consent.status, "granted");
assert!(consent.granted_at.is_some());
assert_eq!(consent.version, 1);
}
// ---------------------------------------------------------------------------
// 测试 2: 撤销同意
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_consent_revoke() {
let app = TestApp::new().await;
let patient_id = app.create_patient("撤销患者").await;
let consent = seed_consent(&app, patient_id).await;
assert_eq!(consent.status, "granted");
let revoked = consent_service::revoke_consent(
app.health_state(), app.tenant_id(), consent.id,
Some(app.operator_id()),
RevokeConsentReq {
notes: Some("患者要求撤销".to_string()),
version: consent.version,
},
)
.await
.expect("撤销应成功");
assert_eq!(revoked.status, "revoked");
assert!(revoked.revoked_at.is_some());
assert_eq!(revoked.version, 2);
}
// ---------------------------------------------------------------------------
// 测试 3: 列表按患者过滤
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_consent_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_consent(&app, patient_a).await;
seed_consent(&app, patient_a).await;
seed_consent(&app, patient_b).await;
let list_a = consent_service::list_consents(
app.health_state(), app.tenant_id(), patient_a, 1, 20,
)
.await
.unwrap();
assert_eq!(list_a.total, 2);
let list_b = consent_service::list_consents(
app.health_state(), app.tenant_id(), patient_b, 1, 20,
)
.await
.unwrap();
assert_eq!(list_b.total, 1);
}
// ---------------------------------------------------------------------------
// 测试 4: 租户隔离
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_consent_tenant_isolation() {
let app = TestApp::new().await;
let patient_id = app.create_patient("同意隔离患者").await;
seed_consent(&app, patient_id).await;
let other_tenant = uuid::Uuid::new_v4();
let list = consent_service::list_consents(
app.health_state(), other_tenant, patient_id, 1, 20,
)
.await
.unwrap();
assert_eq!(list.total, 0, "不同租户不应看到同意记录");
}
// ---------------------------------------------------------------------------
// 测试 5: 无效患者授权返回错误
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_consent_invalid_patient() {
let app = TestApp::new().await;
let fake_patient = uuid::Uuid::new_v4();
let result = consent_service::grant_consent(
app.health_state(), app.tenant_id(), None,
default_create_consent_req(fake_patient),
)
.await;
assert!(result.is_err(), "无效患者应返回错误");
}
// ---------------------------------------------------------------------------
// 测试 6: 撤销版本冲突
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_consent_revoke_version_conflict() {
let app = TestApp::new().await;
let patient_id = app.create_patient("同意锁患者").await;
let consent = seed_consent(&app, patient_id).await;
// 先撤销一次
consent_service::revoke_consent(
app.health_state(), app.tenant_id(), consent.id,
Some(app.operator_id()),
RevokeConsentReq { notes: None, version: consent.version },
)
.await
.unwrap();
// 用旧 version 再撤销应失败
let result = consent_service::revoke_consent(
app.health_state(), app.tenant_id(), consent.id,
Some(app.operator_id()),
RevokeConsentReq { notes: None, version: consent.version },
)
.await;
assert!(result.is_err(), "乐观锁冲突应返回错误");
}

View File

@@ -0,0 +1,185 @@
//! erp-health 日常监测集成测试
//!
//! 验证日常监测 CRUD委托 vital_signs、列表按患者过滤、租户隔离。
use erp_health::dto::daily_monitoring_dto::*;
use erp_health::service::daily_monitoring_service;
use super::test_fixture::TestApp;
fn default_create_req(patient_id: uuid::Uuid) -> CreateDailyMonitoringReq {
CreateDailyMonitoringReq {
patient_id,
record_date: chrono::NaiveDate::from_ymd_opt(2026, 5, 1).unwrap(),
morning_bp_systolic: Some(125),
morning_bp_diastolic: Some(82),
evening_bp_systolic: None,
evening_bp_diastolic: None,
weight: Some(68.5),
blood_sugar: Some(5.2),
fluid_intake: Some(2000),
urine_output: Some(1500),
notes: Some("状态良好".to_string()),
}
}
async fn seed_monitoring(app: &TestApp, patient_id: uuid::Uuid) -> DailyMonitoringResp {
daily_monitoring_service::create_daily_monitoring(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_req(patient_id),
)
.await
.expect("创建日常监测应成功")
}
// ---------------------------------------------------------------------------
// 测试 1: 创建日常监测
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_daily_monitoring_create() {
let app = TestApp::new().await;
let patient_id = app.create_patient("监测患者").await;
let dm = seed_monitoring(&app, patient_id).await;
assert_eq!(dm.patient_id, patient_id);
assert_eq!(dm.morning_bp_systolic, Some(125));
assert_eq!(dm.weight, Some(68.5));
assert_eq!(dm.version, 1);
}
// ---------------------------------------------------------------------------
// 测试 2: 查询日常监测
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_daily_monitoring_get() {
let app = TestApp::new().await;
let patient_id = app.create_patient("监测查询患者").await;
let dm = seed_monitoring(&app, patient_id).await;
let fetched = daily_monitoring_service::get_daily_monitoring(
app.health_state(), app.tenant_id(), dm.id,
)
.await
.expect("查询应成功");
assert_eq!(fetched.id, dm.id);
assert_eq!(fetched.blood_sugar, Some(5.2));
}
// ---------------------------------------------------------------------------
// 测试 3: 更新日常监测
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_daily_monitoring_update() {
let app = TestApp::new().await;
let patient_id = app.create_patient("监测更新患者").await;
let dm = seed_monitoring(&app, patient_id).await;
let updated = daily_monitoring_service::update_daily_monitoring(
app.health_state(), app.tenant_id(), dm.id,
Some(app.operator_id()),
UpdateDailyMonitoringReq {
weight: Some(67.0),
blood_sugar: None,
morning_bp_systolic: None,
morning_bp_diastolic: None,
evening_bp_systolic: Some(118),
evening_bp_diastolic: Some(76),
fluid_intake: None,
urine_output: None,
notes: None,
record_date: None,
},
dm.version,
)
.await
.expect("更新应成功");
assert_eq!(updated.weight, Some(67.0));
assert_eq!(updated.evening_bp_systolic, Some(118));
assert_eq!(updated.version, 2);
}
// ---------------------------------------------------------------------------
// 测试 4: 列表按患者过滤
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_daily_monitoring_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_monitoring(&app, patient_a).await;
seed_monitoring(&app, patient_b).await;
let list_a = daily_monitoring_service::list_daily_monitoring(
app.health_state(), app.tenant_id(), patient_a, 1, 20,
)
.await
.unwrap();
assert_eq!(list_a.total, 1);
let list_b = daily_monitoring_service::list_daily_monitoring(
app.health_state(), app.tenant_id(), patient_b, 1, 20,
)
.await
.unwrap();
assert_eq!(list_b.total, 1);
}
// ---------------------------------------------------------------------------
// 测试 5: 软删除
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_daily_monitoring_soft_delete() {
let app = TestApp::new().await;
let patient_id = app.create_patient("监测删除患者").await;
let dm = seed_monitoring(&app, patient_id).await;
daily_monitoring_service::delete_daily_monitoring(
app.health_state(), app.tenant_id(), dm.id,
Some(app.operator_id()), dm.version,
)
.await
.expect("删除应成功");
let result = daily_monitoring_service::get_daily_monitoring(
app.health_state(), app.tenant_id(), dm.id,
)
.await;
assert!(result.is_err(), "软删除后查询应失败");
}
// ---------------------------------------------------------------------------
// 测试 6: 租户隔离
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_daily_monitoring_tenant_isolation() {
let app = TestApp::new().await;
let patient_id = app.create_patient("监测隔离患者").await;
seed_monitoring(&app, patient_id).await;
let other_tenant = uuid::Uuid::new_v4();
let list = daily_monitoring_service::list_daily_monitoring(
app.health_state(), other_tenant, patient_id, 1, 20,
)
.await
.unwrap();
assert_eq!(list.total, 0, "不同租户不应看到日常监测");
}
// ---------------------------------------------------------------------------
// 测试 7: 无效患者创建返回错误
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_daily_monitoring_invalid_patient() {
let app = TestApp::new().await;
let fake_patient = uuid::Uuid::new_v4();
let result = daily_monitoring_service::create_daily_monitoring(
app.health_state(), app.tenant_id(), None,
default_create_req(fake_patient),
)
.await;
assert!(result.is_err(), "无效患者应返回错误");
}

View File

@@ -0,0 +1,208 @@
//! erp-health 诊断记录集成测试
//!
//! 验证诊断 CRUD、列表按患者过滤、租户隔离、乐观锁。
use erp_health::dto::diagnosis_dto::*;
use erp_health::service::diagnosis_service;
use super::test_fixture::TestApp;
fn default_create_diagnosis_req() -> CreateDiagnosisReq {
CreateDiagnosisReq {
icd_code: "N18.9".to_string(),
diagnosis_name: "慢性肾脏病".to_string(),
diagnosis_type: "primary".to_string(),
diagnosed_date: chrono::NaiveDate::from_ymd_opt(2026, 4, 15).unwrap(),
status: "active".to_string(),
health_record_id: None,
diagnosed_by: None,
notes: None,
}
}
async fn seed_diagnosis(app: &TestApp, patient_id: uuid::Uuid, icd_code: &str, name: &str) -> DiagnosisResp {
diagnosis_service::create_diagnosis(
app.health_state(), app.tenant_id(), patient_id, Some(app.operator_id()),
CreateDiagnosisReq {
icd_code: icd_code.to_string(),
diagnosis_name: name.to_string(),
..default_create_diagnosis_req()
},
)
.await
.expect("创建诊断应成功")
}
// ---------------------------------------------------------------------------
// 测试 1: 创建诊断
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_diagnosis_create() {
let app = TestApp::new().await;
let patient_id = app.create_patient("诊断患者").await;
let diag = seed_diagnosis(&app, patient_id, "N18.9", "慢性肾脏病").await;
assert_eq!(diag.patient_id, patient_id);
assert_eq!(diag.icd_code, "N18.9");
assert_eq!(diag.diagnosis_type, "primary");
assert_eq!(diag.status, "active");
assert_eq!(diag.version, 1);
}
// ---------------------------------------------------------------------------
// 测试 2: 更新诊断
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_diagnosis_update() {
let app = TestApp::new().await;
let patient_id = app.create_patient("诊断更新患者").await;
let diag = seed_diagnosis(&app, patient_id, "N18.8", "CKD更新").await;
let updated = diagnosis_service::update_diagnosis(
app.health_state(), app.tenant_id(), diag.id,
Some(app.operator_id()),
UpdateDiagnosisReq {
status: Some("chronic".to_string()),
notes: Some("长期随访".to_string()),
icd_code: None,
diagnosis_name: None,
diagnosis_type: None,
diagnosed_date: None,
health_record_id: None,
diagnosed_by: None,
},
diag.version,
)
.await
.expect("更新应成功");
assert_eq!(updated.status, "chronic");
assert_eq!(updated.version, 2);
}
// ---------------------------------------------------------------------------
// 测试 3: 列表按患者过滤
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_diagnosis_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_diagnosis(&app, patient_a, "N18.1", "CKD 1期").await;
seed_diagnosis(&app, patient_a, "N18.2", "CKD 2期").await;
seed_diagnosis(&app, patient_b, "E11.9", "2型糖尿病").await;
let list_a = diagnosis_service::list_diagnoses(
app.health_state(), app.tenant_id(), patient_a, 1, 20,
)
.await
.unwrap();
assert_eq!(list_a.total, 2);
let list_b = diagnosis_service::list_diagnoses(
app.health_state(), app.tenant_id(), patient_b, 1, 20,
)
.await
.unwrap();
assert_eq!(list_b.total, 1);
}
// ---------------------------------------------------------------------------
// 测试 4: 软删除
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_diagnosis_soft_delete() {
let app = TestApp::new().await;
let patient_id = app.create_patient("诊断删除患者").await;
let diag = seed_diagnosis(&app, patient_id, "N18.3", "CKD删除").await;
diagnosis_service::delete_diagnosis(
app.health_state(), app.tenant_id(), diag.id,
Some(app.operator_id()), diag.version,
)
.await
.expect("删除应成功");
let list = diagnosis_service::list_diagnoses(
app.health_state(), app.tenant_id(), patient_id, 1, 20,
)
.await
.unwrap();
assert_eq!(list.total, 0);
}
// ---------------------------------------------------------------------------
// 测试 5: 租户隔离
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_diagnosis_tenant_isolation() {
let app = TestApp::new().await;
let patient_id = app.create_patient("诊断隔离患者").await;
seed_diagnosis(&app, patient_id, "N18.4", "CKD隔离").await;
let other_tenant = uuid::Uuid::new_v4();
let list = diagnosis_service::list_diagnoses(
app.health_state(), other_tenant, patient_id, 1, 20,
)
.await
.unwrap();
assert_eq!(list.total, 0, "不同租户不应看到诊断记录");
}
// ---------------------------------------------------------------------------
// 测试 6: 无效患者创建诊断返回错误
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_diagnosis_invalid_patient() {
let app = TestApp::new().await;
let fake_patient = uuid::Uuid::new_v4();
let result = diagnosis_service::create_diagnosis(
app.health_state(), app.tenant_id(), fake_patient, None,
default_create_diagnosis_req(),
)
.await;
assert!(result.is_err(), "无效患者应返回错误");
}
// ---------------------------------------------------------------------------
// 测试 7: 乐观锁冲突
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_diagnosis_version_conflict() {
let app = TestApp::new().await;
let patient_id = app.create_patient("诊断锁患者").await;
let diag = seed_diagnosis(&app, patient_id, "N18.5", "CKD锁").await;
// 先更新一次
diagnosis_service::update_diagnosis(
app.health_state(), app.tenant_id(), diag.id,
Some(app.operator_id()),
UpdateDiagnosisReq {
status: Some("resolved".to_string()),
icd_code: None, diagnosis_name: None, diagnosis_type: None,
diagnosed_date: None, health_record_id: None,
diagnosed_by: None, notes: None,
},
diag.version,
)
.await
.unwrap();
// 用旧 version 再更新应失败
let result = diagnosis_service::update_diagnosis(
app.health_state(), app.tenant_id(), diag.id,
Some(app.operator_id()),
UpdateDiagnosisReq {
status: Some("chronic".to_string()),
icd_code: None, diagnosis_name: None, diagnosis_type: None,
diagnosed_date: None, health_record_id: None,
diagnosed_by: None, notes: None,
},
diag.version,
)
.await;
assert!(result.is_err(), "乐观锁冲突应返回错误");
}

View File

@@ -0,0 +1,228 @@
//! erp-health 透析方案管理集成测试
//!
//! 验证透析方案 CRUD、列表按患者过滤、租户隔离、乐观锁。
use erp_health::dto::dialysis_prescription_dto::*;
use erp_health::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.health_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.health_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.health_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.health_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.health_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.health_state(), app.tenant_id(), rx.id,
Some(app.operator_id()), rx.version,
)
.await
.expect("删除应成功");
let result = dialysis_prescription_service::get_prescription(
app.health_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.health_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.health_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.health_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(), "乐观锁冲突应返回错误");
}

View File

@@ -0,0 +1,241 @@
//! erp-health 医生管理集成测试
//!
//! 验证医生 CRUD、列表搜索、租户隔离、乐观锁。
use erp_health::dto::doctor_dto::*;
use erp_health::service::doctor_service;
use super::test_fixture::TestApp;
fn default_create_doctor_req() -> CreateDoctorReq {
CreateDoctorReq {
user_id: None,
name: "张三".to_string(),
department: Some("肾内科".to_string()),
title: Some("主任医师".to_string()),
specialty: Some("血液透析".to_string()),
license_number: None,
bio: None,
}
}
// ---------------------------------------------------------------------------
// 测试 1: 创建医生
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_doctor_create() {
let app = TestApp::new().await;
let doctor = doctor_service::create_doctor(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_doctor_req(),
)
.await
.expect("创建医生应成功");
assert_eq!(doctor.name, "张三");
assert_eq!(doctor.department, Some("肾内科".to_string()));
assert_eq!(doctor.online_status, "offline");
assert_eq!(doctor.version, 1);
}
// ---------------------------------------------------------------------------
// 测试 2: 查询医生
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_doctor_get() {
let app = TestApp::new().await;
let doctor = doctor_service::create_doctor(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_doctor_req(),
)
.await
.unwrap();
let fetched = doctor_service::get_doctor(
app.health_state(), app.tenant_id(), doctor.id,
)
.await
.expect("查询应成功");
assert_eq!(fetched.id, doctor.id);
assert_eq!(fetched.name, "张三");
}
// ---------------------------------------------------------------------------
// 测试 3: 更新医生
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_doctor_update() {
let app = TestApp::new().await;
let doctor = doctor_service::create_doctor(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_doctor_req(),
)
.await
.unwrap();
let updated = doctor_service::update_doctor(
app.health_state(), app.tenant_id(), doctor.id,
Some(app.operator_id()),
UpdateDoctorReq {
name: Some("李四".to_string()),
department: None,
title: Some("副主任医师".to_string()),
specialty: None,
license_number: None,
bio: Some("擅长透析治疗".to_string()),
online_status: Some("online".to_string()),
},
doctor.version,
)
.await
.expect("更新应成功");
assert_eq!(updated.name, "李四");
assert_eq!(updated.title, Some("副主任医师".to_string()));
assert_eq!(updated.online_status, "online");
assert_eq!(updated.version, 2);
}
// ---------------------------------------------------------------------------
// 测试 4: 医生列表 + 搜索
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_doctor_list_and_search() {
let app = TestApp::new().await;
doctor_service::create_doctor(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
CreateDoctorReq {
name: "王医生".to_string(),
department: Some("心内科".to_string()),
..default_create_doctor_req()
},
)
.await
.unwrap();
doctor_service::create_doctor(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
CreateDoctorReq {
name: "赵医生".to_string(),
department: Some("肾内科".to_string()),
..default_create_doctor_req()
},
)
.await
.unwrap();
// 全量列表
let all = doctor_service::list_doctors(
app.health_state(), app.tenant_id(), 1, 20, None, None, None,
)
.await
.unwrap();
assert_eq!(all.total, 2);
// 按科室过滤
let renal = doctor_service::list_doctors(
app.health_state(), app.tenant_id(), 1, 20,
None, Some("肾内科".to_string()), None,
)
.await
.unwrap();
assert_eq!(renal.total, 1);
}
// ---------------------------------------------------------------------------
// 测试 5: 医生软删除
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_doctor_soft_delete() {
let app = TestApp::new().await;
let doctor = doctor_service::create_doctor(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_doctor_req(),
)
.await
.unwrap();
doctor_service::delete_doctor(
app.health_state(), app.tenant_id(), doctor.id,
Some(app.operator_id()), doctor.version,
)
.await
.expect("删除应成功");
let result = doctor_service::get_doctor(
app.health_state(), app.tenant_id(), doctor.id,
)
.await;
assert!(result.is_err(), "软删除后查询应失败");
}
// ---------------------------------------------------------------------------
// 测试 6: 租户隔离
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_doctor_tenant_isolation() {
let app = TestApp::new().await;
let doctor = doctor_service::create_doctor(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_doctor_req(),
)
.await
.unwrap();
let other_tenant = uuid::Uuid::new_v4();
let result = doctor_service::get_doctor(
app.health_state(), other_tenant, doctor.id,
)
.await;
assert!(result.is_err(), "不同租户不应看到此医生");
let other_list = doctor_service::list_doctors(
app.health_state(), other_tenant, 1, 20, None, None, None,
)
.await
.unwrap();
assert_eq!(other_list.total, 0);
}
// ---------------------------------------------------------------------------
// 测试 7: 乐观锁冲突
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_doctor_version_conflict() {
let app = TestApp::new().await;
let doctor = doctor_service::create_doctor(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_doctor_req(),
)
.await
.unwrap();
// 先更新一次
doctor_service::update_doctor(
app.health_state(), app.tenant_id(), doctor.id,
Some(app.operator_id()),
UpdateDoctorReq {
name: Some("第一次".to_string()),
department: None, title: None, specialty: None,
license_number: None, bio: None, online_status: None,
},
doctor.version,
)
.await
.unwrap();
// 用旧 version 再更新应失败
let result = doctor_service::update_doctor(
app.health_state(), app.tenant_id(), doctor.id,
Some(app.operator_id()),
UpdateDoctorReq {
name: Some("冲突".to_string()),
department: None, title: None, specialty: None,
license_number: None, bio: None, online_status: None,
},
doctor.version,
)
.await;
assert!(result.is_err(), "乐观锁冲突应返回错误");
}

View File

@@ -0,0 +1,244 @@
//! erp-health 随访模板集成测试
//!
//! 验证模板 CRUD + 字段管理、列表过滤、租户隔离、乐观锁。
use erp_health::dto::follow_up_template_dto::*;
use erp_health::service::follow_up_template_service;
use super::test_fixture::TestApp;
fn default_field_req() -> TemplateFieldReq {
TemplateFieldReq {
label: "血压".to_string(),
field_key: "blood_pressure".to_string(),
field_type: "text".to_string(),
required: true,
options: None,
placeholder: Some("请输入血压值".to_string()),
validation: None,
sort_order: 0,
}
}
fn default_create_req() -> CreateFollowUpTemplateReq {
CreateFollowUpTemplateReq {
name: "电话随访模板".to_string(),
description: Some("标准电话随访".to_string()),
follow_up_type: "phone".to_string(),
applicable_scope: None,
fields: vec![default_field_req()],
}
}
async fn seed_template(app: &TestApp) -> FollowUpTemplateResp {
follow_up_template_service::create_template(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_req(),
)
.await
.expect("创建模板应成功")
}
// ---------------------------------------------------------------------------
// 测试 1: 创建模板(含字段)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_template_create_with_fields() {
let app = TestApp::new().await;
let tmpl = seed_template(&app).await;
assert_eq!(tmpl.name, "电话随访模板");
assert_eq!(tmpl.follow_up_type, "phone");
assert_eq!(tmpl.status, "active");
assert_eq!(tmpl.fields.len(), 1);
assert_eq!(tmpl.fields[0].field_key, "blood_pressure");
assert_eq!(tmpl.version, 1);
}
// ---------------------------------------------------------------------------
// 测试 2: 查询模板(含字段)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_template_get_with_fields() {
let app = TestApp::new().await;
let tmpl = seed_template(&app).await;
let fetched = follow_up_template_service::get_template(
app.health_state(), app.tenant_id(), tmpl.id,
)
.await
.expect("查询应成功");
assert_eq!(fetched.id, tmpl.id);
assert_eq!(fetched.fields.len(), 1);
}
// ---------------------------------------------------------------------------
// 测试 3: 更新模板(替换字段)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_template_update_replace_fields() {
let app = TestApp::new().await;
let tmpl = seed_template(&app).await;
let updated = follow_up_template_service::update_template(
app.health_state(), app.tenant_id(), tmpl.id,
Some(app.operator_id()),
UpdateFollowUpTemplateReq {
name: Some("更新后的模板".to_string()),
fields: Some(vec![
TemplateFieldReq {
label: "体重".to_string(),
field_key: "weight".to_string(),
field_type: "number".to_string(),
required: false,
options: None,
placeholder: None,
validation: None,
sort_order: 0,
},
TemplateFieldReq {
label: "症状".to_string(),
field_key: "symptoms".to_string(),
field_type: "textarea".to_string(),
required: true,
options: None,
placeholder: None,
validation: None,
sort_order: 1,
},
]),
description: None,
follow_up_type: None,
applicable_scope: None,
status: None,
},
tmpl.version,
)
.await
.expect("更新应成功");
assert_eq!(updated.name, "更新后的模板");
assert_eq!(updated.fields.len(), 2);
assert_eq!(updated.version, 2);
}
// ---------------------------------------------------------------------------
// 测试 4: 列表过滤
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_template_list_filter() {
let app = TestApp::new().await;
follow_up_template_service::create_template(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
CreateFollowUpTemplateReq {
name: "门诊随访".to_string(),
follow_up_type: "outpatient".to_string(),
fields: vec![],
description: None,
applicable_scope: None,
},
)
.await
.unwrap();
follow_up_template_service::create_template(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_req(),
)
.await
.unwrap();
// 全量
let all = follow_up_template_service::list_templates(
app.health_state(), app.tenant_id(), 1, 20, None, None,
)
.await
.unwrap();
assert_eq!(all.total, 2);
// 按类型过滤
let phone = follow_up_template_service::list_templates(
app.health_state(), app.tenant_id(), 1, 20,
Some("phone".to_string()), None,
)
.await
.unwrap();
assert_eq!(phone.total, 1);
}
// ---------------------------------------------------------------------------
// 测试 5: 软删除(含字段)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_template_soft_delete() {
let app = TestApp::new().await;
let tmpl = seed_template(&app).await;
follow_up_template_service::delete_template(
app.health_state(), app.tenant_id(), tmpl.id,
Some(app.operator_id()), tmpl.version,
)
.await
.expect("删除应成功");
let result = follow_up_template_service::get_template(
app.health_state(), app.tenant_id(), tmpl.id,
)
.await;
assert!(result.is_err(), "软删除后查询应失败");
}
// ---------------------------------------------------------------------------
// 测试 6: 租户隔离
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_template_tenant_isolation() {
let app = TestApp::new().await;
seed_template(&app).await;
let other_tenant = uuid::Uuid::new_v4();
let list = follow_up_template_service::list_templates(
app.health_state(), other_tenant, 1, 20, None, None,
)
.await
.unwrap();
assert_eq!(list.total, 0, "不同租户不应看到模板");
}
// ---------------------------------------------------------------------------
// 测试 7: 乐观锁冲突
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_template_version_conflict() {
let app = TestApp::new().await;
let tmpl = seed_template(&app).await;
// 先更新一次
follow_up_template_service::update_template(
app.health_state(), app.tenant_id(), tmpl.id,
Some(app.operator_id()),
UpdateFollowUpTemplateReq {
name: Some("第一次".to_string()),
description: None, follow_up_type: None,
applicable_scope: None, status: None, fields: None,
},
tmpl.version,
)
.await
.unwrap();
// 用旧 version 再更新应失败
let result = follow_up_template_service::update_template(
app.health_state(), app.tenant_id(), tmpl.id,
Some(app.operator_id()),
UpdateFollowUpTemplateReq {
name: Some("冲突".to_string()),
description: None, follow_up_type: None,
applicable_scope: None, status: None, fields: None,
},
tmpl.version,
)
.await;
assert!(result.is_err(), "乐观锁冲突应返回错误");
}

View File

@@ -0,0 +1,222 @@
//! erp-health 用药记录集成测试
//!
//! 验证用药记录 CRUD、列表按患者过滤、租户隔离、乐观锁。
use erp_health::dto::medication_record_dto::*;
use erp_health::service::medication_record_service;
use super::test_fixture::TestApp;
fn default_create_medication_req(patient_id: uuid::Uuid) -> CreateMedicationRecordReq {
CreateMedicationRecordReq {
patient_id,
medication_name: "缬沙坦".to_string(),
generic_name: Some("Valsartan".to_string()),
dosage: Some("80mg".to_string()),
unit: Some("mg".to_string()),
frequency: Some("daily".to_string()),
route: Some("oral".to_string()),
start_date: chrono::NaiveDate::from_ymd_opt(2026, 1, 1),
end_date: None,
is_current: Some(true),
prescribed_by: None,
notes: None,
}
}
async fn seed_medication(app: &TestApp, patient_id: uuid::Uuid) -> MedicationRecordResp {
medication_record_service::create_medication(
app.health_state(), app.tenant_id(), Some(app.operator_id()),
default_create_medication_req(patient_id),
)
.await
.expect("创建用药记录应成功")
}
// ---------------------------------------------------------------------------
// 测试 1: 创建用药记录
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_medication_create() {
let app = TestApp::new().await;
let patient_id = app.create_patient("用药患者").await;
let med = seed_medication(&app, patient_id).await;
assert_eq!(med.patient_id, patient_id);
assert_eq!(med.medication_name, "缬沙坦");
assert_eq!(med.frequency, Some("daily".to_string()));
assert!(med.is_current);
assert_eq!(med.version, 1);
}
// ---------------------------------------------------------------------------
// 测试 2: 查询用药记录
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_medication_get() {
let app = TestApp::new().await;
let patient_id = app.create_patient("用药查询患者").await;
let med = seed_medication(&app, patient_id).await;
let fetched = medication_record_service::get_medication(
app.health_state(), app.tenant_id(), med.id,
)
.await
.expect("查询应成功");
assert_eq!(fetched.id, med.id);
assert_eq!(fetched.medication_name, "缬沙坦");
}
// ---------------------------------------------------------------------------
// 测试 3: 更新用药记录
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_medication_update() {
let app = TestApp::new().await;
let patient_id = app.create_patient("用药更新患者").await;
let med = seed_medication(&app, patient_id).await;
let updated = medication_record_service::update_medication(
app.health_state(), app.tenant_id(), med.id,
Some(app.operator_id()),
UpdateMedicationRecordReq {
dosage: Some("160mg".to_string()),
is_current: Some(false),
medication_name: None, generic_name: None, unit: None,
frequency: None, route: None, start_date: None,
end_date: None, prescribed_by: None, notes: None,
},
med.version,
)
.await
.expect("更新应成功");
assert_eq!(updated.dosage, Some("160mg".to_string()));
assert!(!updated.is_current);
assert_eq!(updated.version, 2);
}
// ---------------------------------------------------------------------------
// 测试 4: 列表按患者过滤
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_medication_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_medication(&app, patient_a).await;
seed_medication(&app, patient_b).await;
let list_a = medication_record_service::list_medications(
app.health_state(), app.tenant_id(), patient_a, 1, 20,
)
.await
.unwrap();
assert_eq!(list_a.total, 1);
let list_b = medication_record_service::list_medications(
app.health_state(), app.tenant_id(), patient_b, 1, 20,
)
.await
.unwrap();
assert_eq!(list_b.total, 1);
}
// ---------------------------------------------------------------------------
// 测试 5: 软删除
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_medication_soft_delete() {
let app = TestApp::new().await;
let patient_id = app.create_patient("用药删除患者").await;
let med = seed_medication(&app, patient_id).await;
medication_record_service::delete_medication(
app.health_state(), app.tenant_id(), med.id,
Some(app.operator_id()), med.version,
)
.await
.expect("删除应成功");
let result = medication_record_service::get_medication(
app.health_state(), app.tenant_id(), med.id,
)
.await;
assert!(result.is_err(), "软删除后查询应失败");
}
// ---------------------------------------------------------------------------
// 测试 6: 租户隔离
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_medication_tenant_isolation() {
let app = TestApp::new().await;
let patient_id = app.create_patient("用药隔离患者").await;
seed_medication(&app, patient_id).await;
let other_tenant = uuid::Uuid::new_v4();
let list = medication_record_service::list_medications(
app.health_state(), other_tenant, patient_id, 1, 20,
)
.await
.unwrap();
assert_eq!(list.total, 0, "不同租户不应看到用药记录");
}
// ---------------------------------------------------------------------------
// 测试 7: 乐观锁冲突
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_medication_version_conflict() {
let app = TestApp::new().await;
let patient_id = app.create_patient("用药锁患者").await;
let med = seed_medication(&app, patient_id).await;
// 先更新一次
medication_record_service::update_medication(
app.health_state(), app.tenant_id(), med.id,
Some(app.operator_id()),
UpdateMedicationRecordReq {
dosage: Some("160mg".to_string()),
medication_name: None, generic_name: None, unit: None,
frequency: None, route: None, start_date: None,
end_date: None, is_current: None, prescribed_by: None, notes: None,
},
med.version,
)
.await
.unwrap();
// 用旧 version 再更新应失败
let result = medication_record_service::update_medication(
app.health_state(), app.tenant_id(), med.id,
Some(app.operator_id()),
UpdateMedicationRecordReq {
dosage: Some("320mg".to_string()),
medication_name: None, generic_name: None, unit: None,
frequency: None, route: None, start_date: None,
end_date: None, is_current: None, prescribed_by: None, notes: None,
},
med.version,
)
.await;
assert!(result.is_err(), "乐观锁冲突应返回错误");
}
// ---------------------------------------------------------------------------
// 测试 8: 无效患者创建用药记录返回错误
// ---------------------------------------------------------------------------
#[tokio::test]
async fn test_medication_invalid_patient() {
let app = TestApp::new().await;
let fake_patient = uuid::Uuid::new_v4();
let result = medication_record_service::create_medication(
app.health_state(), app.tenant_id(), None,
default_create_medication_req(fake_patient),
)
.await;
assert!(result.is_err(), "无效患者应返回错误");
}