test(health): 7 个模块集成测试 — 49 个用例全通过
新增 doctor(7)、diagnosis(7)、consent(6)、medication(8)、 dialysis_prescription(7)、follow_up_template(7)、daily_monitoring(7) 覆盖 CRUD、状态流、列表过滤、软删除、租户隔离、乐观锁。
This commit is contained in:
@@ -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;
|
||||
|
||||
162
crates/erp-server/tests/integration/health_consent_tests.rs
Normal file
162
crates/erp-server/tests/integration/health_consent_tests.rs
Normal 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(), "乐观锁冲突应返回错误");
|
||||
}
|
||||
@@ -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(), "无效患者应返回错误");
|
||||
}
|
||||
208
crates/erp-server/tests/integration/health_diagnosis_tests.rs
Normal file
208
crates/erp-server/tests/integration/health_diagnosis_tests.rs
Normal 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(), "乐观锁冲突应返回错误");
|
||||
}
|
||||
@@ -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(), "乐观锁冲突应返回错误");
|
||||
}
|
||||
241
crates/erp-server/tests/integration/health_doctor_tests.rs
Normal file
241
crates/erp-server/tests/integration/health_doctor_tests.rs
Normal 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(), "乐观锁冲突应返回错误");
|
||||
}
|
||||
@@ -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(), "乐观锁冲突应返回错误");
|
||||
}
|
||||
222
crates/erp-server/tests/integration/health_medication_tests.rs
Normal file
222
crates/erp-server/tests/integration/health_medication_tests.rs
Normal 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(), "无效患者应返回错误");
|
||||
}
|
||||
Reference in New Issue
Block a user