fix: 修复测试发现的 7 个问题 + 全 workspace clippy 清零
功能修复: 1. 患者创建空名称验证:后端添加 name.trim().is_empty() 检查 2. 仪表盘统计容错:单个查询失败返回零值而非 500 3. FHIR 路由修复:从 /fhir 移到 /api/v1/fhir 保持一致 4. 冻结模块后端中间件:新增 frozen_module_middleware 拦截冻结路径 5. 积分端点权限码:health.health-data.list → health.points.list 6. 角色权限迁移:护士补充 devices.list,运营补充 points.list/manage 7. 测试结果文档:R01-R05 角色测试 + T00/T10 结果归档 Clippy 全 workspace 清零(14→0 errors): - erp-core: 修复 empty doc line、collapsible if、redundant closure 等 9 处 - erp-health: 修复 too_many_arguments、unused var、unnecessary parens 等 58 处 - erp-ai: 修复 dead_code、unused import 等 11 处 - erp-plugin: 修复 too_many_arguments、wildcard pattern 等 11 处 - erp-server-migration: 修复 enum_variant_names 5 处 - erp-auth/config/workflow/message: 各 1-3 处 工程改进: - lint-staged 配置迁移到 .lintstagedrc.js(函数式避免文件列表传给 clippy) - cargo fmt 统一格式化
This commit is contained in:
@@ -1,48 +1,48 @@
|
||||
#[path = "integration/test_db.rs"]
|
||||
mod test_db;
|
||||
#[path = "integration/test_fixture.rs"]
|
||||
mod test_fixture;
|
||||
#[path = "integration/ai_prompt_tests.rs"]
|
||||
mod ai_prompt_tests;
|
||||
#[path = "integration/auth_tests.rs"]
|
||||
mod auth_tests;
|
||||
#[path = "integration/plugin_tests.rs"]
|
||||
mod plugin_tests;
|
||||
#[path = "integration/workflow_tests.rs"]
|
||||
mod workflow_tests;
|
||||
#[path = "integration/health_patient_tests.rs"]
|
||||
mod health_patient_tests;
|
||||
#[path = "integration/health_alert_tests.rs"]
|
||||
mod health_alert_tests;
|
||||
#[path = "integration/health_appointment_tests.rs"]
|
||||
mod health_appointment_tests;
|
||||
#[path = "integration/health_article_tests.rs"]
|
||||
mod health_article_tests;
|
||||
#[path = "integration/health_consent_tests.rs"]
|
||||
mod health_consent_tests;
|
||||
#[path = "integration/health_consultation_tests.rs"]
|
||||
mod health_consultation_tests;
|
||||
#[path = "integration/health_daily_monitoring_tests.rs"]
|
||||
mod health_daily_monitoring_tests;
|
||||
#[path = "integration/health_data_tests.rs"]
|
||||
mod health_data_tests;
|
||||
#[path = "integration/health_device_reading_tests.rs"]
|
||||
mod health_device_reading_tests;
|
||||
#[path = "integration/health_diagnosis_tests.rs"]
|
||||
mod health_diagnosis_tests;
|
||||
#[path = "integration/health_dialysis_prescription_tests.rs"]
|
||||
mod health_dialysis_prescription_tests;
|
||||
#[path = "integration/health_dialysis_tests.rs"]
|
||||
mod health_dialysis_tests;
|
||||
#[path = "integration/health_doctor_tests.rs"]
|
||||
mod health_doctor_tests;
|
||||
#[path = "integration/health_follow_up_template_tests.rs"]
|
||||
mod health_follow_up_template_tests;
|
||||
#[path = "integration/health_follow_up_tests.rs"]
|
||||
mod health_follow_up_tests;
|
||||
#[path = "integration/health_medication_tests.rs"]
|
||||
mod health_medication_tests;
|
||||
#[path = "integration/health_patient_tests.rs"]
|
||||
mod health_patient_tests;
|
||||
#[path = "integration/health_pii_encryption_tests.rs"]
|
||||
mod health_pii_encryption_tests;
|
||||
#[path = "integration/health_points_tests.rs"]
|
||||
mod health_points_tests;
|
||||
#[path = "integration/health_dialysis_tests.rs"]
|
||||
mod health_dialysis_tests;
|
||||
#[path = "integration/health_alert_tests.rs"]
|
||||
mod health_alert_tests;
|
||||
#[path = "integration/health_device_reading_tests.rs"]
|
||||
mod health_device_reading_tests;
|
||||
#[path = "integration/health_follow_up_tests.rs"]
|
||||
mod health_follow_up_tests;
|
||||
#[path = "integration/health_consultation_tests.rs"]
|
||||
mod health_consultation_tests;
|
||||
#[path = "integration/health_data_tests.rs"]
|
||||
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;
|
||||
#[path = "integration/ai_prompt_tests.rs"]
|
||||
mod ai_prompt_tests;
|
||||
#[path = "integration/plugin_tests.rs"]
|
||||
mod plugin_tests;
|
||||
#[path = "integration/test_db.rs"]
|
||||
mod test_db;
|
||||
#[path = "integration/test_fixture.rs"]
|
||||
mod test_fixture;
|
||||
#[path = "integration/workflow_tests.rs"]
|
||||
mod workflow_tests;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use erp_ai::service::prompt::PromptService;
|
||||
use erp_ai::service::usage::UsageService;
|
||||
use erp_ai::service::analysis::AnalysisService;
|
||||
use erp_ai::provider::AiProvider;
|
||||
use erp_ai::dto::GenerateRequest;
|
||||
use erp_ai::error::{AiError, AiResult};
|
||||
use erp_ai::provider::AiProvider;
|
||||
use erp_ai::service::analysis::AnalysisService;
|
||||
use erp_ai::service::prompt::PromptService;
|
||||
use erp_ai::service::usage::UsageService;
|
||||
use erp_core::types::Pagination;
|
||||
use sea_orm::ActiveModelTrait;
|
||||
use sha2::Digest;
|
||||
@@ -97,7 +97,14 @@ async fn prompt_list_with_category_filter() {
|
||||
}
|
||||
|
||||
let (items, total) = svc
|
||||
.list_prompts(tenant_id, Some("analysis".into()), &Pagination { page: Some(1), page_size: Some(10) })
|
||||
.list_prompts(
|
||||
tenant_id,
|
||||
Some("analysis".into()),
|
||||
&Pagination {
|
||||
page: Some(1),
|
||||
page_size: Some(10),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("查询应成功");
|
||||
|
||||
@@ -113,25 +120,49 @@ async fn prompt_activate_switches_version() {
|
||||
let user_id = uuid::Uuid::new_v4();
|
||||
|
||||
let v1 = svc
|
||||
.create_prompt(tenant_id, user_id, "my_prompt".into(), "sys_v1".into(), "usr".into(), serde_json::json!({}), "cat".into())
|
||||
.create_prompt(
|
||||
tenant_id,
|
||||
user_id,
|
||||
"my_prompt".into(),
|
||||
"sys_v1".into(),
|
||||
"usr".into(),
|
||||
serde_json::json!({}),
|
||||
"cat".into(),
|
||||
)
|
||||
.await
|
||||
.expect("v1");
|
||||
|
||||
let v2 = svc
|
||||
.update_prompt(v1.id, tenant_id, user_id, Some("sys_v2".into()), None, None, None)
|
||||
.update_prompt(
|
||||
v1.id,
|
||||
tenant_id,
|
||||
user_id,
|
||||
Some("sys_v2".into()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("v2");
|
||||
|
||||
assert_eq!(v2.version, 2);
|
||||
|
||||
// v1 仍然激活(update 继承 is_active)
|
||||
let active_before = svc.get_active_prompt(tenant_id, "my_prompt").await.expect("active");
|
||||
let active_before = svc
|
||||
.get_active_prompt(tenant_id, "my_prompt")
|
||||
.await
|
||||
.expect("active");
|
||||
assert_eq!(active_before.system_prompt, "sys_v1");
|
||||
|
||||
// 激活 v2
|
||||
svc.activate_prompt(v2.id, tenant_id).await.expect("activate");
|
||||
svc.activate_prompt(v2.id, tenant_id)
|
||||
.await
|
||||
.expect("activate");
|
||||
|
||||
let active_after = svc.get_active_prompt(tenant_id, "my_prompt").await.expect("active");
|
||||
let active_after = svc
|
||||
.get_active_prompt(tenant_id, "my_prompt")
|
||||
.await
|
||||
.expect("active");
|
||||
assert_eq!(active_after.id, v2.id);
|
||||
assert_eq!(active_after.system_prompt, "sys_v2");
|
||||
|
||||
@@ -148,21 +179,44 @@ async fn prompt_rollback_equals_activate() {
|
||||
let user_id = uuid::Uuid::new_v4();
|
||||
|
||||
let v1 = svc
|
||||
.create_prompt(tenant_id, user_id, "rb_test".into(), "sys_v1".into(), "usr".into(), serde_json::json!({}), "cat".into())
|
||||
.create_prompt(
|
||||
tenant_id,
|
||||
user_id,
|
||||
"rb_test".into(),
|
||||
"sys_v1".into(),
|
||||
"usr".into(),
|
||||
serde_json::json!({}),
|
||||
"cat".into(),
|
||||
)
|
||||
.await
|
||||
.expect("v1");
|
||||
|
||||
let v2 = svc
|
||||
.update_prompt(v1.id, tenant_id, user_id, Some("sys_v2".into()), None, None, None)
|
||||
.update_prompt(
|
||||
v1.id,
|
||||
tenant_id,
|
||||
user_id,
|
||||
Some("sys_v2".into()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("v2");
|
||||
|
||||
svc.activate_prompt(v2.id, tenant_id).await.expect("activate v2");
|
||||
svc.activate_prompt(v2.id, tenant_id)
|
||||
.await
|
||||
.expect("activate v2");
|
||||
|
||||
// 回滚到 v1
|
||||
svc.rollback_prompt(v1.id, tenant_id).await.expect("rollback");
|
||||
svc.rollback_prompt(v1.id, tenant_id)
|
||||
.await
|
||||
.expect("rollback");
|
||||
|
||||
let active = svc.get_active_prompt(tenant_id, "rb_test").await.expect("active");
|
||||
let active = svc
|
||||
.get_active_prompt(tenant_id, "rb_test")
|
||||
.await
|
||||
.expect("active");
|
||||
assert_eq!(active.id, v1.id);
|
||||
}
|
||||
|
||||
@@ -174,9 +228,17 @@ async fn prompt_cross_tenant_isolation() {
|
||||
let tenant_b = uuid::Uuid::new_v4();
|
||||
let user_id = uuid::Uuid::new_v4();
|
||||
|
||||
svc.create_prompt(tenant_a, user_id, "shared_name".into(), "sys".into(), "usr".into(), serde_json::json!({}), "cat".into())
|
||||
.await
|
||||
.expect("create");
|
||||
svc.create_prompt(
|
||||
tenant_a,
|
||||
user_id,
|
||||
"shared_name".into(),
|
||||
"sys".into(),
|
||||
"usr".into(),
|
||||
serde_json::json!({}),
|
||||
"cat".into(),
|
||||
)
|
||||
.await
|
||||
.expect("create");
|
||||
|
||||
let result = svc.get_active_prompt(tenant_b, "shared_name").await;
|
||||
assert!(result.is_err());
|
||||
@@ -224,10 +286,16 @@ async fn usage_by_type_aggregation() {
|
||||
let by_type = svc.get_by_type(tenant_id).await.expect("by_type");
|
||||
assert_eq!(by_type.len(), 2);
|
||||
|
||||
let lab = by_type.iter().find(|t| t.analysis_type == "lab_report").expect("lab");
|
||||
let lab = by_type
|
||||
.iter()
|
||||
.find(|t| t.analysis_type == "lab_report")
|
||||
.expect("lab");
|
||||
assert_eq!(lab.count, 2);
|
||||
|
||||
let trends = by_type.iter().find(|t| t.analysis_type == "trends").expect("trends");
|
||||
let trends = by_type
|
||||
.iter()
|
||||
.find(|t| t.analysis_type == "trends")
|
||||
.expect("trends");
|
||||
assert_eq!(trends.count, 1);
|
||||
}
|
||||
|
||||
@@ -238,7 +306,17 @@ async fn usage_log_creates_record() {
|
||||
let tenant_id = uuid::Uuid::new_v4();
|
||||
|
||||
let record = svc
|
||||
.log_usage(tenant_id, "claude", "claude-3", "lab_report", 100, 200, 3000, 50, false)
|
||||
.log_usage(
|
||||
tenant_id,
|
||||
"claude",
|
||||
"claude-3",
|
||||
"lab_report",
|
||||
100,
|
||||
200,
|
||||
3000,
|
||||
50,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.expect("log");
|
||||
|
||||
@@ -275,11 +353,23 @@ async fn analysis_complete_updates_status() {
|
||||
|
||||
// 通过内部方法创建 streaming 记录(直接插入 DB)
|
||||
let analysis_id = uuid::Uuid::now_v7();
|
||||
insert_streaming_analysis(&test_db, analysis_id, tenant_id, user_id, patient_id, "lab_report").await;
|
||||
insert_streaming_analysis(
|
||||
&test_db,
|
||||
analysis_id,
|
||||
tenant_id,
|
||||
user_id,
|
||||
patient_id,
|
||||
"lab_report",
|
||||
)
|
||||
.await;
|
||||
|
||||
svc.complete_analysis(analysis_id, "分析结果文本".into(), serde_json::json!({"tokens": 100}))
|
||||
.await
|
||||
.expect("complete");
|
||||
svc.complete_analysis(
|
||||
analysis_id,
|
||||
"分析结果文本".into(),
|
||||
serde_json::json!({"tokens": 100}),
|
||||
)
|
||||
.await
|
||||
.expect("complete");
|
||||
|
||||
let record = svc.get_analysis(analysis_id, tenant_id).await.expect("get");
|
||||
assert_eq!(record.status, "completed");
|
||||
@@ -295,7 +385,15 @@ async fn analysis_fail_updates_status() {
|
||||
let patient_id = uuid::Uuid::new_v4();
|
||||
|
||||
let analysis_id = uuid::Uuid::now_v7();
|
||||
insert_streaming_analysis(&test_db, analysis_id, tenant_id, user_id, patient_id, "trends").await;
|
||||
insert_streaming_analysis(
|
||||
&test_db,
|
||||
analysis_id,
|
||||
tenant_id,
|
||||
user_id,
|
||||
patient_id,
|
||||
"trends",
|
||||
)
|
||||
.await;
|
||||
|
||||
svc.fail_analysis(analysis_id, "API 超时".into())
|
||||
.await
|
||||
@@ -319,14 +417,30 @@ async fn analysis_find_cached() {
|
||||
|
||||
// 插入 completed 记录
|
||||
let analysis_id = uuid::Uuid::now_v7();
|
||||
insert_completed_analysis_with_hash(&test_db, analysis_id, tenant_id, user_id, patient_id, "lab_report", &hash, 1).await;
|
||||
insert_completed_analysis_with_hash(
|
||||
&test_db,
|
||||
analysis_id,
|
||||
tenant_id,
|
||||
user_id,
|
||||
patient_id,
|
||||
"lab_report",
|
||||
&hash,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
|
||||
let cached = svc.find_cached(tenant_id, &hash, 1).await.expect("find_cached");
|
||||
let cached = svc
|
||||
.find_cached(tenant_id, &hash, 1)
|
||||
.await
|
||||
.expect("find_cached");
|
||||
assert!(cached.is_some());
|
||||
assert_eq!(cached.unwrap().id, analysis_id);
|
||||
|
||||
// 不同 hash 不命中
|
||||
let miss = svc.find_cached(tenant_id, "wrong_hash", 1).await.expect("find_cached");
|
||||
let miss = svc
|
||||
.find_cached(tenant_id, "wrong_hash", 1)
|
||||
.await
|
||||
.expect("find_cached");
|
||||
assert!(miss.is_none());
|
||||
}
|
||||
|
||||
@@ -339,27 +453,93 @@ async fn analysis_list_with_filters() {
|
||||
let patient_a = uuid::Uuid::new_v4();
|
||||
let patient_b = uuid::Uuid::new_v4();
|
||||
|
||||
insert_completed_analysis_with_hash(&test_db, uuid::Uuid::now_v7(), tenant_id, user_id, patient_a, "lab_report", "h1", 1).await;
|
||||
insert_completed_analysis_with_hash(&test_db, uuid::Uuid::now_v7(), tenant_id, user_id, patient_a, "trends", "h2", 1).await;
|
||||
insert_completed_analysis_with_hash(&test_db, uuid::Uuid::now_v7(), tenant_id, user_id, patient_b, "lab_report", "h3", 1).await;
|
||||
insert_completed_analysis_with_hash(
|
||||
&test_db,
|
||||
uuid::Uuid::now_v7(),
|
||||
tenant_id,
|
||||
user_id,
|
||||
patient_a,
|
||||
"lab_report",
|
||||
"h1",
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
insert_completed_analysis_with_hash(
|
||||
&test_db,
|
||||
uuid::Uuid::now_v7(),
|
||||
tenant_id,
|
||||
user_id,
|
||||
patient_a,
|
||||
"trends",
|
||||
"h2",
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
insert_completed_analysis_with_hash(
|
||||
&test_db,
|
||||
uuid::Uuid::now_v7(),
|
||||
tenant_id,
|
||||
user_id,
|
||||
patient_b,
|
||||
"lab_report",
|
||||
"h3",
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
|
||||
// 按 patient 筛选
|
||||
let (items, total) = svc.list_analysis(tenant_id, Some(patient_a), None, &Pagination { page: Some(1), page_size: Some(10) }).await.expect("list");
|
||||
let (items, total) = svc
|
||||
.list_analysis(
|
||||
tenant_id,
|
||||
Some(patient_a),
|
||||
None,
|
||||
&Pagination {
|
||||
page: Some(1),
|
||||
page_size: Some(10),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("list");
|
||||
assert_eq!(total, 2);
|
||||
|
||||
// 按 type 筛选
|
||||
let (items, total) = svc.list_analysis(tenant_id, None, Some("lab_report".into()), &Pagination { page: Some(1), page_size: Some(10) }).await.expect("list");
|
||||
let (items, total) = svc
|
||||
.list_analysis(
|
||||
tenant_id,
|
||||
None,
|
||||
Some("lab_report".into()),
|
||||
&Pagination {
|
||||
page: Some(1),
|
||||
page_size: Some(10),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("list");
|
||||
assert_eq!(total, 2);
|
||||
|
||||
// 跨租户
|
||||
let (items, total) = svc.list_analysis(uuid::Uuid::new_v4(), None, None, &Pagination { page: Some(1), page_size: Some(10) }).await.expect("list");
|
||||
let (items, total) = svc
|
||||
.list_analysis(
|
||||
uuid::Uuid::new_v4(),
|
||||
None,
|
||||
None,
|
||||
&Pagination {
|
||||
page: Some(1),
|
||||
page_size: Some(10),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("list");
|
||||
assert_eq!(total, 0);
|
||||
assert!(items.is_empty());
|
||||
}
|
||||
|
||||
// ---- 辅助函数 ----
|
||||
|
||||
async fn ai_prompt_find_by_id(test_db: &TestDb, id: uuid::Uuid) -> erp_ai::entity::ai_prompt::Model {
|
||||
async fn ai_prompt_find_by_id(
|
||||
test_db: &TestDb,
|
||||
id: uuid::Uuid,
|
||||
) -> erp_ai::entity::ai_prompt::Model {
|
||||
use sea_orm::EntityTrait;
|
||||
erp_ai::entity::ai_prompt::Entity::find_by_id(id)
|
||||
.one(test_db.db())
|
||||
|
||||
@@ -5,15 +5,21 @@
|
||||
use erp_health::dto::alert_dto::*;
|
||||
use erp_health::entity::{alert_rules, vital_signs_hourly};
|
||||
use erp_health::service::{alert_engine, alert_rule_service, alert_service};
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use sea_orm::ActiveModelTrait;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
|
||||
use super::test_fixture::TestApp;
|
||||
|
||||
/// 创建告警规则(单阈值)
|
||||
async fn seed_threshold_rule(app: &TestApp, device_type: &str, threshold: f64) -> alert_rules::Model {
|
||||
async fn seed_threshold_rule(
|
||||
app: &TestApp,
|
||||
device_type: &str,
|
||||
threshold: f64,
|
||||
) -> alert_rules::Model {
|
||||
alert_rule_service::create_rule(
|
||||
app.health_state(), app.tenant_id(), app.operator_id(),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
app.operator_id(),
|
||||
CreateAlertRuleRequest {
|
||||
name: "高收缩压".to_string(),
|
||||
description: Some("收缩压超过阈值".to_string()),
|
||||
@@ -31,9 +37,7 @@ async fn seed_threshold_rule(app: &TestApp, device_type: &str, threshold: f64) -
|
||||
}
|
||||
|
||||
/// 插入一条 hourly 汇总记录
|
||||
async fn seed_hourly(
|
||||
app: &TestApp, patient_id: uuid::Uuid, device_type: &str, avg_val: f64,
|
||||
) {
|
||||
async fn seed_hourly(app: &TestApp, patient_id: uuid::Uuid, device_type: &str, avg_val: f64) {
|
||||
let model = vital_signs_hourly::ActiveModel {
|
||||
id: Set(uuid::Uuid::now_v7()),
|
||||
tenant_id: Set(app.tenant_id()),
|
||||
@@ -64,11 +68,10 @@ async fn test_alert_rule_create_and_list() {
|
||||
assert_eq!(rule.condition_type, "single_threshold");
|
||||
assert!(rule.is_active);
|
||||
|
||||
let (rules, total) = alert_rule_service::list_rules(
|
||||
app.health_state(), app.tenant_id(), None, 1, 20,
|
||||
)
|
||||
.await
|
||||
.expect("列表应成功");
|
||||
let (rules, total) =
|
||||
alert_rule_service::list_rules(app.health_state(), app.tenant_id(), None, 1, 20)
|
||||
.await
|
||||
.expect("列表应成功");
|
||||
assert_eq!(total, 1);
|
||||
assert_eq!(rules[0].id, rule.id);
|
||||
}
|
||||
@@ -84,7 +87,10 @@ async fn test_alert_rule_deactivate() {
|
||||
assert!(rule.is_active);
|
||||
|
||||
let deactivated = alert_rule_service::deactivate_rule(
|
||||
app.health_state(), app.tenant_id(), rule.id, rule.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
rule.id,
|
||||
rule.version,
|
||||
)
|
||||
.await
|
||||
.expect("停用应成功");
|
||||
@@ -103,7 +109,10 @@ async fn test_alert_engine_single_threshold_trigger() {
|
||||
seed_hourly(&app, patient_id, "heart_rate", 155.0).await;
|
||||
|
||||
let triggered = alert_engine::evaluate_rules(
|
||||
app.health_state(), app.tenant_id(), patient_id, "heart_rate",
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"heart_rate",
|
||||
)
|
||||
.await
|
||||
.expect("评估应成功");
|
||||
@@ -125,7 +134,10 @@ async fn test_alert_engine_single_threshold_no_trigger() {
|
||||
seed_hourly(&app, patient_id, "heart_rate", 120.0).await;
|
||||
|
||||
let triggered = alert_engine::evaluate_rules(
|
||||
app.health_state(), app.tenant_id(), patient_id, "heart_rate",
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"heart_rate",
|
||||
)
|
||||
.await
|
||||
.expect("评估应成功");
|
||||
@@ -145,7 +157,10 @@ async fn test_alert_engine_cooldown_suppresses() {
|
||||
seed_hourly(&app, patient_id, "heart_rate", 160.0).await;
|
||||
|
||||
let first = alert_engine::evaluate_rules(
|
||||
app.health_state(), app.tenant_id(), patient_id, "heart_rate",
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"heart_rate",
|
||||
)
|
||||
.await
|
||||
.expect("首次评估应成功");
|
||||
@@ -153,7 +168,10 @@ async fn test_alert_engine_cooldown_suppresses() {
|
||||
|
||||
// 再次评估,cooldown 内不应重复
|
||||
let second = alert_engine::evaluate_rules(
|
||||
app.health_state(), app.tenant_id(), patient_id, "heart_rate",
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"heart_rate",
|
||||
)
|
||||
.await
|
||||
.expect("二次评估应成功");
|
||||
@@ -172,7 +190,10 @@ async fn test_alert_status_flow() {
|
||||
seed_hourly(&app, patient_id, "heart_rate", 155.0).await;
|
||||
|
||||
let triggered = alert_engine::evaluate_rules(
|
||||
app.health_state(), app.tenant_id(), patient_id, "heart_rate",
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"heart_rate",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -181,7 +202,11 @@ async fn test_alert_status_flow() {
|
||||
|
||||
// pending → acknowledged
|
||||
let acked = alert_service::acknowledge_alert(
|
||||
app.health_state(), app.tenant_id(), alert.id, app.operator_id(), alert.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
alert.id,
|
||||
app.operator_id(),
|
||||
alert.version,
|
||||
)
|
||||
.await
|
||||
.expect("确认应成功");
|
||||
@@ -189,11 +214,10 @@ async fn test_alert_status_flow() {
|
||||
assert!(acked.acknowledged_by.is_some());
|
||||
|
||||
// acknowledged → resolved
|
||||
let resolved = alert_service::resolve_alert(
|
||||
app.health_state(), app.tenant_id(), acked.id, acked.version,
|
||||
)
|
||||
.await
|
||||
.expect("解决应成功");
|
||||
let resolved =
|
||||
alert_service::resolve_alert(app.health_state(), app.tenant_id(), acked.id, acked.version)
|
||||
.await
|
||||
.expect("解决应成功");
|
||||
assert_eq!(resolved.status, "resolved");
|
||||
assert!(resolved.resolved_at.is_some());
|
||||
}
|
||||
@@ -210,14 +234,21 @@ async fn test_alert_dismiss_from_pending() {
|
||||
seed_hourly(&app, patient_id, "heart_rate", 155.0).await;
|
||||
|
||||
let triggered = alert_engine::evaluate_rules(
|
||||
app.health_state(), app.tenant_id(), patient_id, "heart_rate",
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"heart_rate",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let alert = &triggered[0];
|
||||
|
||||
let dismissed = alert_service::dismiss_alert(
|
||||
app.health_state(), app.tenant_id(), alert.id, app.operator_id(), alert.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
alert.id,
|
||||
app.operator_id(),
|
||||
alert.version,
|
||||
)
|
||||
.await
|
||||
.expect("忽略应成功");
|
||||
@@ -237,20 +268,22 @@ async fn test_alert_list_filter_and_tenant_isolation() {
|
||||
seed_hourly(&app, patient_a, "heart_rate", 155.0).await;
|
||||
seed_hourly(&app, patient_b, "heart_rate", 160.0).await;
|
||||
|
||||
alert_engine::evaluate_rules(
|
||||
app.health_state(), app.tenant_id(), patient_a, "heart_rate",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
alert_engine::evaluate_rules(
|
||||
app.health_state(), app.tenant_id(), patient_b, "heart_rate",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
alert_engine::evaluate_rules(app.health_state(), app.tenant_id(), patient_a, "heart_rate")
|
||||
.await
|
||||
.unwrap();
|
||||
alert_engine::evaluate_rules(app.health_state(), app.tenant_id(), patient_b, "heart_rate")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 按患者 A 过滤
|
||||
let (alerts_a, total_a) = alert_service::list_alerts(
|
||||
app.health_state(), app.tenant_id(), Some(patient_a), None, None, 1, 20,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(patient_a),
|
||||
None,
|
||||
None,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -260,7 +293,13 @@ async fn test_alert_list_filter_and_tenant_isolation() {
|
||||
// 租户隔离
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let (_alerts_other, total_other) = alert_service::list_alerts(
|
||||
app.health_state(), other_tenant, Some(patient_a), None, None, 1, 20,
|
||||
app.health_state(),
|
||||
other_tenant,
|
||||
Some(patient_a),
|
||||
None,
|
||||
None,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -4,15 +4,13 @@
|
||||
//! 使用 TestDb 创建隔离 PostgreSQL 数据库,直接调用 service 层函数。
|
||||
//! 预约创建依赖患者 + 医护档案 + 排班三条前置数据。
|
||||
|
||||
use erp_core::crypto::PiiCrypto;
|
||||
use erp_core::events::EventBus;
|
||||
use erp_health::dto::appointment_dto::{CreateAppointmentReq, UpdateAppointmentStatusReq};
|
||||
use erp_health::dto::doctor_dto::CreateDoctorReq;
|
||||
use erp_health::dto::patient_dto::CreatePatientReq;
|
||||
use erp_health::service::{
|
||||
appointment_service, doctor_service, patient_service,
|
||||
};
|
||||
use erp_health::service::{appointment_service, doctor_service, patient_service};
|
||||
use erp_health::state::HealthState;
|
||||
use erp_core::crypto::PiiCrypto;
|
||||
|
||||
use super::test_db::TestDb;
|
||||
|
||||
@@ -26,11 +24,7 @@ fn make_state(db: &sea_orm::DatabaseConnection) -> HealthState {
|
||||
}
|
||||
|
||||
/// 创建患者并返回其 ID
|
||||
async fn seed_patient(
|
||||
state: &HealthState,
|
||||
tenant_id: uuid::Uuid,
|
||||
name: &str,
|
||||
) -> uuid::Uuid {
|
||||
async fn seed_patient(state: &HealthState, tenant_id: uuid::Uuid, name: &str) -> uuid::Uuid {
|
||||
let req = CreatePatientReq {
|
||||
name: name.to_string(),
|
||||
gender: Some("male".to_string()),
|
||||
@@ -51,11 +45,7 @@ async fn seed_patient(
|
||||
}
|
||||
|
||||
/// 创建医护档案并返回其 ID
|
||||
async fn seed_doctor(
|
||||
state: &HealthState,
|
||||
tenant_id: uuid::Uuid,
|
||||
name: &str,
|
||||
) -> uuid::Uuid {
|
||||
async fn seed_doctor(state: &HealthState, tenant_id: uuid::Uuid, name: &str) -> uuid::Uuid {
|
||||
let req = CreateDoctorReq {
|
||||
user_id: None,
|
||||
name: name.to_string(),
|
||||
@@ -129,10 +119,7 @@ async fn test_create_appointment() {
|
||||
assert_eq!(appointment.appointment_type, "outpatient");
|
||||
assert_eq!(appointment.status, "pending");
|
||||
assert_eq!(appointment.version, 1);
|
||||
assert_eq!(
|
||||
appointment.notes,
|
||||
Some("首次就诊".to_string())
|
||||
);
|
||||
assert_eq!(appointment.notes, Some("首次就诊".to_string()));
|
||||
|
||||
// 通过 get_appointment 验证存储正确
|
||||
let found = appointment_service::get_appointment(&state, tenant_id, appointment.id)
|
||||
@@ -174,18 +161,10 @@ async fn test_list_appointments() {
|
||||
.expect("创建预约应成功");
|
||||
}
|
||||
|
||||
let result = appointment_service::list_appointments(
|
||||
&state,
|
||||
tenant_id,
|
||||
1,
|
||||
10,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("列表查询应成功");
|
||||
let result =
|
||||
appointment_service::list_appointments(&state, tenant_id, 1, 10, None, None, None, None)
|
||||
.await
|
||||
.expect("列表查询应成功");
|
||||
|
||||
assert_eq!(result.total, 2, "应有 2 条预约记录");
|
||||
assert_eq!(result.data.len(), 2, "当前页应返回 2 条");
|
||||
@@ -217,34 +196,22 @@ async fn test_appointment_tenant_isolation() {
|
||||
end_time: chrono::NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
|
||||
notes: None,
|
||||
};
|
||||
let appointment_a =
|
||||
appointment_service::create_appointment(&state, tenant_a, None, req)
|
||||
.await
|
||||
.expect("租户 A 创建预约应成功");
|
||||
let appointment_a = appointment_service::create_appointment(&state, tenant_a, None, req)
|
||||
.await
|
||||
.expect("租户 A 创建预约应成功");
|
||||
|
||||
// 租户 B 列表查询应看不到租户 A 的预约
|
||||
let result_b = appointment_service::list_appointments(
|
||||
&state,
|
||||
tenant_b,
|
||||
1,
|
||||
10,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("租户 B 列表查询应成功");
|
||||
let result_b =
|
||||
appointment_service::list_appointments(&state, tenant_b, 1, 10, None, None, None, None)
|
||||
.await
|
||||
.expect("租户 B 列表查询应成功");
|
||||
assert_eq!(result_b.total, 0, "租户 B 不应看到租户 A 的预约");
|
||||
assert!(result_b.data.is_empty());
|
||||
|
||||
// 租户 B 通过 ID 查询租户 A 的预约应返回错误
|
||||
let lookup_result =
|
||||
appointment_service::get_appointment(&state, tenant_b, appointment_a.id).await;
|
||||
assert!(
|
||||
lookup_result.is_err(),
|
||||
"跨租户查询预约应返回错误"
|
||||
);
|
||||
assert!(lookup_result.is_err(), "跨租户查询预约应返回错误");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -264,7 +231,9 @@ async fn test_appointment_status_flow() {
|
||||
seed_schedule(&state, tenant_id, doctor_id, date).await;
|
||||
|
||||
let appt = appointment_service::create_appointment(
|
||||
&state, tenant_id, Some(operator_id),
|
||||
&state,
|
||||
tenant_id,
|
||||
Some(operator_id),
|
||||
CreateAppointmentReq {
|
||||
patient_id,
|
||||
doctor_id: Some(doctor_id),
|
||||
@@ -281,8 +250,14 @@ async fn test_appointment_status_flow() {
|
||||
|
||||
// pending → confirmed
|
||||
let confirmed = appointment_service::update_appointment_status(
|
||||
&state, tenant_id, appt.id, Some(operator_id),
|
||||
UpdateAppointmentStatusReq { status: "confirmed".to_string(), cancel_reason: None },
|
||||
&state,
|
||||
tenant_id,
|
||||
appt.id,
|
||||
Some(operator_id),
|
||||
UpdateAppointmentStatusReq {
|
||||
status: "confirmed".to_string(),
|
||||
cancel_reason: None,
|
||||
},
|
||||
appt.version,
|
||||
)
|
||||
.await
|
||||
@@ -291,8 +266,14 @@ async fn test_appointment_status_flow() {
|
||||
|
||||
// confirmed → completed
|
||||
let completed = appointment_service::update_appointment_status(
|
||||
&state, tenant_id, appt.id, Some(operator_id),
|
||||
UpdateAppointmentStatusReq { status: "completed".to_string(), cancel_reason: None },
|
||||
&state,
|
||||
tenant_id,
|
||||
appt.id,
|
||||
Some(operator_id),
|
||||
UpdateAppointmentStatusReq {
|
||||
status: "completed".to_string(),
|
||||
cancel_reason: None,
|
||||
},
|
||||
confirmed.version,
|
||||
)
|
||||
.await
|
||||
@@ -316,7 +297,9 @@ async fn test_appointment_cancel() {
|
||||
seed_schedule(&state, tenant_id, doctor_id, date).await;
|
||||
|
||||
let appt = appointment_service::create_appointment(
|
||||
&state, tenant_id, None,
|
||||
&state,
|
||||
tenant_id,
|
||||
None,
|
||||
CreateAppointmentReq {
|
||||
patient_id,
|
||||
doctor_id: Some(doctor_id),
|
||||
@@ -331,7 +314,10 @@ async fn test_appointment_cancel() {
|
||||
.expect("创建应成功");
|
||||
|
||||
let cancelled = appointment_service::update_appointment_status(
|
||||
&state, tenant_id, appt.id, None,
|
||||
&state,
|
||||
tenant_id,
|
||||
appt.id,
|
||||
None,
|
||||
UpdateAppointmentStatusReq {
|
||||
status: "cancelled".to_string(),
|
||||
cancel_reason: Some("患者临时有事".to_string()),
|
||||
@@ -360,7 +346,9 @@ async fn test_appointment_version_conflict() {
|
||||
seed_schedule(&state, tenant_id, doctor_id, date).await;
|
||||
|
||||
let appt = appointment_service::create_appointment(
|
||||
&state, tenant_id, Some(operator_id),
|
||||
&state,
|
||||
tenant_id,
|
||||
Some(operator_id),
|
||||
CreateAppointmentReq {
|
||||
patient_id,
|
||||
doctor_id: Some(doctor_id),
|
||||
@@ -376,8 +364,14 @@ async fn test_appointment_version_conflict() {
|
||||
|
||||
// 正确版本确认
|
||||
let _confirmed = appointment_service::update_appointment_status(
|
||||
&state, tenant_id, appt.id, Some(operator_id),
|
||||
UpdateAppointmentStatusReq { status: "confirmed".to_string(), cancel_reason: None },
|
||||
&state,
|
||||
tenant_id,
|
||||
appt.id,
|
||||
Some(operator_id),
|
||||
UpdateAppointmentStatusReq {
|
||||
status: "confirmed".to_string(),
|
||||
cancel_reason: None,
|
||||
},
|
||||
appt.version,
|
||||
)
|
||||
.await
|
||||
@@ -385,8 +379,14 @@ async fn test_appointment_version_conflict() {
|
||||
|
||||
// 用旧版本再更新应失败
|
||||
let result = appointment_service::update_appointment_status(
|
||||
&state, tenant_id, appt.id, Some(operator_id),
|
||||
UpdateAppointmentStatusReq { status: "cancelled".to_string(), cancel_reason: None },
|
||||
&state,
|
||||
tenant_id,
|
||||
appt.id,
|
||||
Some(operator_id),
|
||||
UpdateAppointmentStatusReq {
|
||||
status: "cancelled".to_string(),
|
||||
cancel_reason: None,
|
||||
},
|
||||
appt.version, // 旧版本
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! 验证文章 CRUD + 状态流、分类 CRUD、标签 CRUD、租户隔离、乐观锁。
|
||||
|
||||
use erp_health::dto::article_dto::*;
|
||||
use erp_health::service::{article_service, article_category_service, article_tag_service};
|
||||
use erp_health::service::{article_category_service, article_service, article_tag_service};
|
||||
|
||||
use super::test_fixture::TestApp;
|
||||
|
||||
@@ -29,7 +29,9 @@ fn default_create_article_req() -> CreateArticleReq {
|
||||
|
||||
async fn seed_article(app: &TestApp) -> ArticleResp {
|
||||
article_service::create_article(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_article_req(),
|
||||
)
|
||||
.await
|
||||
@@ -38,7 +40,9 @@ async fn seed_article(app: &TestApp) -> ArticleResp {
|
||||
|
||||
async fn seed_category(app: &TestApp, name: &str) -> CategoryResp {
|
||||
article_category_service::create_category(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
CreateCategoryReq {
|
||||
name: name.to_string(),
|
||||
slug: None,
|
||||
@@ -53,8 +57,12 @@ async fn seed_category(app: &TestApp, name: &str) -> CategoryResp {
|
||||
|
||||
async fn seed_tag(app: &TestApp, name: &str) -> TagResp {
|
||||
article_tag_service::create_tag(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
CreateTagReq { name: name.to_string() },
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
CreateTagReq {
|
||||
name: name.to_string(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("创建标签应成功")
|
||||
@@ -86,8 +94,11 @@ async fn test_article_status_flow() {
|
||||
|
||||
// draft → pending_review
|
||||
let submitted = article_service::submit_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
Some(app.operator_id()), article.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
article.version,
|
||||
)
|
||||
.await
|
||||
.expect("提交审核应成功");
|
||||
@@ -95,9 +106,14 @@ async fn test_article_status_flow() {
|
||||
|
||||
// pending_review → published
|
||||
let published = article_service::approve_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
ReviewArticleReq { note: Some("通过".to_string()), version: Some(submitted.version) },
|
||||
ReviewArticleReq {
|
||||
note: Some("通过".to_string()),
|
||||
version: Some(submitted.version),
|
||||
},
|
||||
submitted.version,
|
||||
)
|
||||
.await
|
||||
@@ -106,8 +122,11 @@ async fn test_article_status_flow() {
|
||||
|
||||
// published → draft(取消发布)
|
||||
let unpublished = article_service::unpublish_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
Some(app.operator_id()), published.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
published.version,
|
||||
)
|
||||
.await
|
||||
.expect("取消发布应成功");
|
||||
@@ -123,17 +142,25 @@ async fn test_article_reject_and_resubmit() {
|
||||
let article = seed_article(&app).await;
|
||||
|
||||
let submitted = article_service::submit_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
Some(app.operator_id()), article.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
article.version,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// pending_review → rejected
|
||||
let rejected = article_service::reject_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
ReviewArticleReq { note: Some("内容需修改".to_string()), version: Some(submitted.version) },
|
||||
ReviewArticleReq {
|
||||
note: Some("内容需修改".to_string()),
|
||||
version: Some(submitted.version),
|
||||
},
|
||||
submitted.version,
|
||||
)
|
||||
.await
|
||||
@@ -142,8 +169,11 @@ async fn test_article_reject_and_resubmit() {
|
||||
|
||||
// rejected → pending_review
|
||||
let resubmitted = article_service::submit_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
Some(app.operator_id()), rejected.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
rejected.version,
|
||||
)
|
||||
.await
|
||||
.expect("重新提交应成功");
|
||||
@@ -159,7 +189,9 @@ async fn test_article_update() {
|
||||
let article = seed_article(&app).await;
|
||||
|
||||
let updated = article_service::update_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateArticleReq {
|
||||
title: Some("更新标题".to_string()),
|
||||
@@ -196,24 +228,41 @@ async fn test_article_list_filter() {
|
||||
|
||||
// 提交 a1 到 pending_review
|
||||
article_service::submit_article(
|
||||
app.health_state(), app.tenant_id(), a1.id,
|
||||
Some(app.operator_id()), a1.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
a1.id,
|
||||
Some(app.operator_id()),
|
||||
a1.version,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 按状态过滤
|
||||
let pending = article_service::list_articles(
|
||||
app.health_state(), app.tenant_id(), 1, 20,
|
||||
None, Some("pending_review".to_string()), None, None, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
None,
|
||||
Some("pending_review".to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(pending.total, 1);
|
||||
|
||||
let drafts = article_service::list_articles(
|
||||
app.health_state(), app.tenant_id(), 1, 20,
|
||||
None, Some("draft".to_string()), None, None, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
None,
|
||||
Some("draft".to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -229,16 +278,17 @@ async fn test_article_soft_delete() {
|
||||
let article = seed_article(&app).await;
|
||||
|
||||
article_service::delete_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
Some(app.operator_id()), article.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
article.version,
|
||||
)
|
||||
.await
|
||||
.expect("删除应成功");
|
||||
|
||||
let result = article_service::get_article(
|
||||
app.health_state(), app.tenant_id(), article.id, true,
|
||||
)
|
||||
.await;
|
||||
let result =
|
||||
article_service::get_article(app.health_state(), app.tenant_id(), article.id, true).await;
|
||||
assert!(result.is_err(), "软删除后查询应失败");
|
||||
}
|
||||
|
||||
@@ -251,10 +301,8 @@ async fn test_article_tenant_isolation() {
|
||||
let article = seed_article(&app).await;
|
||||
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let result = article_service::get_article(
|
||||
app.health_state(), other_tenant, article.id, true,
|
||||
)
|
||||
.await;
|
||||
let result =
|
||||
article_service::get_article(app.health_state(), other_tenant, article.id, true).await;
|
||||
assert!(result.is_err(), "不同租户不应看到此文章");
|
||||
}
|
||||
|
||||
@@ -271,16 +319,16 @@ async fn test_category_crud_and_isolation() {
|
||||
assert_eq!(cat.version, 1);
|
||||
|
||||
// 列表
|
||||
let list = article_category_service::list_categories(
|
||||
app.health_state(), app.tenant_id(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let list = article_category_service::list_categories(app.health_state(), app.tenant_id())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list.len(), 1);
|
||||
|
||||
// 更新
|
||||
let updated = article_category_service::update_category(
|
||||
app.health_state(), app.tenant_id(), cat.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
cat.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateCategoryReq {
|
||||
name: Some("透析护理".to_string()),
|
||||
@@ -297,27 +345,26 @@ async fn test_category_crud_and_isolation() {
|
||||
|
||||
// 删除
|
||||
article_category_service::delete_category(
|
||||
app.health_state(), app.tenant_id(), cat.id,
|
||||
Some(app.operator_id()), updated.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
cat.id,
|
||||
Some(app.operator_id()),
|
||||
updated.version,
|
||||
)
|
||||
.await
|
||||
.expect("删除分类应成功");
|
||||
|
||||
let list_after = article_category_service::list_categories(
|
||||
app.health_state(), app.tenant_id(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let list_after = article_category_service::list_categories(app.health_state(), app.tenant_id())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list_after.len(), 0, "删除后列表应为空");
|
||||
|
||||
// 租户隔离
|
||||
let cat2 = seed_category(&app, "隔离分类").await;
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let other_list = article_category_service::list_categories(
|
||||
app.health_state(), other_tenant,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let other_list = article_category_service::list_categories(app.health_state(), other_tenant)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(other_list.len(), 0, "不同租户不应看到分类");
|
||||
// 防止 unused warning
|
||||
let _ = cat2;
|
||||
@@ -337,7 +384,9 @@ async fn test_tag_crud_and_article_association() {
|
||||
|
||||
// 创建文章并关联标签
|
||||
let article = article_service::create_article(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
CreateArticleReq {
|
||||
title: "带标签的文章".to_string(),
|
||||
tag_ids: vec![tag1.id, tag2.id],
|
||||
@@ -350,14 +399,24 @@ async fn test_tag_crud_and_article_association() {
|
||||
|
||||
// 更新标签(替换为只有 tag1)
|
||||
let updated = article_service::update_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateArticleReq {
|
||||
tag_ids: Some(vec![tag1.id]),
|
||||
version: article.version,
|
||||
title: None, summary: None, content: None, cover_image: None,
|
||||
category: None, author: None, published_at: None, slug: None,
|
||||
content_type: None, category_id: None, sort_order: None,
|
||||
title: None,
|
||||
summary: None,
|
||||
content: None,
|
||||
cover_image: None,
|
||||
category: None,
|
||||
author: None,
|
||||
published_at: None,
|
||||
slug: None,
|
||||
content_type: None,
|
||||
category_id: None,
|
||||
sort_order: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -365,18 +424,21 @@ async fn test_tag_crud_and_article_association() {
|
||||
assert_eq!(updated.tags.len(), 1);
|
||||
|
||||
// 标签列表
|
||||
let tags = article_tag_service::list_tags(
|
||||
app.health_state(), app.tenant_id(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let tags = article_tag_service::list_tags(app.health_state(), app.tenant_id())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(tags.len(), 2);
|
||||
|
||||
// 更新标签名称
|
||||
let renamed = article_tag_service::update_tag(
|
||||
app.health_state(), app.tenant_id(), tag1.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
tag1.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateTagReq { name: "血压高".to_string(), version: tag1.version },
|
||||
UpdateTagReq {
|
||||
name: "血压高".to_string(),
|
||||
version: tag1.version,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("更新标签应成功");
|
||||
@@ -384,17 +446,18 @@ async fn test_tag_crud_and_article_association() {
|
||||
|
||||
// 删除标签
|
||||
article_tag_service::delete_tag(
|
||||
app.health_state(), app.tenant_id(), tag2.id,
|
||||
Some(app.operator_id()), tag2.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
tag2.id,
|
||||
Some(app.operator_id()),
|
||||
tag2.version,
|
||||
)
|
||||
.await
|
||||
.expect("删除标签应成功");
|
||||
|
||||
let tags_after = article_tag_service::list_tags(
|
||||
app.health_state(), app.tenant_id(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let tags_after = article_tag_service::list_tags(app.health_state(), app.tenant_id())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(tags_after.len(), 1);
|
||||
}
|
||||
|
||||
@@ -408,14 +471,24 @@ async fn test_article_version_conflict() {
|
||||
|
||||
// 先更新一次,version 变为 2
|
||||
article_service::update_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateArticleReq {
|
||||
title: Some("第一次更新".to_string()),
|
||||
version: article.version,
|
||||
summary: None, content: None, cover_image: None, category: None,
|
||||
author: None, published_at: None, slug: None, content_type: None,
|
||||
category_id: None, tag_ids: None, sort_order: None,
|
||||
summary: None,
|
||||
content: None,
|
||||
cover_image: None,
|
||||
category: None,
|
||||
author: None,
|
||||
published_at: None,
|
||||
slug: None,
|
||||
content_type: None,
|
||||
category_id: None,
|
||||
tag_ids: None,
|
||||
sort_order: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -423,14 +496,24 @@ async fn test_article_version_conflict() {
|
||||
|
||||
// 用旧 version 再次更新应失败
|
||||
let result = article_service::update_article(
|
||||
app.health_state(), app.tenant_id(), article.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
article.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateArticleReq {
|
||||
title: Some("冲突更新".to_string()),
|
||||
version: article.version, // 旧版本号
|
||||
summary: None, content: None, cover_image: None, category: None,
|
||||
author: None, published_at: None, slug: None, content_type: None,
|
||||
category_id: None, tag_ids: None, sort_order: None,
|
||||
summary: None,
|
||||
content: None,
|
||||
cover_image: None,
|
||||
category: None,
|
||||
author: None,
|
||||
published_at: None,
|
||||
slug: None,
|
||||
content_type: None,
|
||||
category_id: None,
|
||||
tag_ids: None,
|
||||
sort_order: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -21,7 +21,9 @@ fn default_create_consent_req(patient_id: uuid::Uuid) -> CreateConsentReq {
|
||||
|
||||
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()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_consent_req(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -56,7 +58,9 @@ async fn test_consent_revoke() {
|
||||
assert_eq!(consent.status, "granted");
|
||||
|
||||
let revoked = consent_service::revoke_consent(
|
||||
app.health_state(), app.tenant_id(), consent.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
consent.id,
|
||||
Some(app.operator_id()),
|
||||
RevokeConsentReq {
|
||||
notes: Some("患者要求撤销".to_string()),
|
||||
@@ -84,18 +88,16 @@ async fn test_consent_list_by_patient() {
|
||||
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();
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -109,11 +111,9 @@ async fn test_consent_tenant_isolation() {
|
||||
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();
|
||||
let list = consent_service::list_consents(app.health_state(), other_tenant, patient_id, 1, 20)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list.total, 0, "不同租户不应看到同意记录");
|
||||
}
|
||||
|
||||
@@ -126,7 +126,9 @@ async fn test_consent_invalid_patient() {
|
||||
let fake_patient = uuid::Uuid::new_v4();
|
||||
|
||||
let result = consent_service::grant_consent(
|
||||
app.health_state(), app.tenant_id(), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
None,
|
||||
default_create_consent_req(fake_patient),
|
||||
)
|
||||
.await;
|
||||
@@ -144,18 +146,28 @@ async fn test_consent_revoke_version_conflict() {
|
||||
|
||||
// 先撤销一次
|
||||
consent_service::revoke_consent(
|
||||
app.health_state(), app.tenant_id(), consent.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
consent.id,
|
||||
Some(app.operator_id()),
|
||||
RevokeConsentReq { notes: None, version: consent.version },
|
||||
RevokeConsentReq {
|
||||
notes: None,
|
||||
version: consent.version,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 用旧 version 再撤销应失败
|
||||
let result = consent_service::revoke_consent(
|
||||
app.health_state(), app.tenant_id(), consent.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
consent.id,
|
||||
Some(app.operator_id()),
|
||||
RevokeConsentReq { notes: None, version: consent.version },
|
||||
RevokeConsentReq {
|
||||
notes: None,
|
||||
version: consent.version,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_err(), "乐观锁冲突应返回错误");
|
||||
|
||||
@@ -10,8 +10,14 @@ use super::test_fixture::TestApp;
|
||||
/// 创建测试用会话(无医护)
|
||||
async fn seed_session(app: &TestApp, patient_id: uuid::Uuid) -> SessionResp {
|
||||
consultation_service::create_session(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
CreateSessionReq { patient_id, doctor_id: None, consultation_type: None },
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
CreateSessionReq {
|
||||
patient_id,
|
||||
doctor_id: None,
|
||||
consultation_type: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("创建会话应成功")
|
||||
@@ -27,7 +33,9 @@ async fn test_consultation_session_create() {
|
||||
let doctor_id = app.create_doctor("咨询医生").await;
|
||||
|
||||
let session = consultation_service::create_session(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
CreateSessionReq {
|
||||
patient_id,
|
||||
doctor_id: Some(doctor_id),
|
||||
@@ -53,11 +61,10 @@ async fn test_consultation_session_get() {
|
||||
|
||||
let session = seed_session(&app, patient_id).await;
|
||||
|
||||
let fetched = consultation_service::get_session(
|
||||
app.health_state(), app.tenant_id(), session.id,
|
||||
)
|
||||
.await
|
||||
.expect("查询应成功");
|
||||
let fetched =
|
||||
consultation_service::get_session(app.health_state(), app.tenant_id(), session.id)
|
||||
.await
|
||||
.expect("查询应成功");
|
||||
assert_eq!(fetched.id, session.id);
|
||||
assert_eq!(fetched.status, "waiting");
|
||||
}
|
||||
@@ -76,14 +83,26 @@ async fn test_consultation_session_list_by_patient() {
|
||||
seed_session(&app, patient_b).await;
|
||||
|
||||
let list_a = consultation_service::list_sessions(
|
||||
app.health_state(), app.tenant_id(), 1, 20, None, Some(patient_a), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
None,
|
||||
Some(patient_a),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list_a.total, 2);
|
||||
|
||||
let list_b = consultation_service::list_sessions(
|
||||
app.health_state(), app.tenant_id(), 1, 20, None, Some(patient_b), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
None,
|
||||
Some(patient_b),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -101,8 +120,11 @@ async fn test_consultation_message_send() {
|
||||
let session = seed_session(&app, patient_id).await;
|
||||
|
||||
let msg = consultation_service::create_message(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.operator_id(), "doctor".to_string(),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
app.operator_id(),
|
||||
"doctor".to_string(),
|
||||
CreateMessageReq {
|
||||
session_id: session.id,
|
||||
content_type: Some("text".to_string()),
|
||||
@@ -130,8 +152,11 @@ async fn test_consultation_message_list() {
|
||||
|
||||
for i in 0..3 {
|
||||
consultation_service::create_message(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.operator_id(), "doctor".to_string(),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
app.operator_id(),
|
||||
"doctor".to_string(),
|
||||
CreateMessageReq {
|
||||
session_id: session.id,
|
||||
content_type: None,
|
||||
@@ -143,7 +168,12 @@ async fn test_consultation_message_list() {
|
||||
}
|
||||
|
||||
let messages = consultation_service::list_messages(
|
||||
app.health_state(), app.tenant_id(), session.id, 1, 20, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
session.id,
|
||||
1,
|
||||
20,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("查询消息应成功");
|
||||
@@ -163,7 +193,11 @@ async fn test_consultation_session_close() {
|
||||
assert_eq!(session.status, "waiting");
|
||||
|
||||
let closed = consultation_service::close_session(
|
||||
app.health_state(), app.tenant_id(), session.id, Some(app.operator_id()), session.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
session.id,
|
||||
Some(app.operator_id()),
|
||||
session.version,
|
||||
)
|
||||
.await
|
||||
.expect("关闭应成功");
|
||||
@@ -181,10 +215,8 @@ async fn test_consultation_session_tenant_isolation() {
|
||||
let session = seed_session(&app, patient_id).await;
|
||||
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let result = consultation_service::get_session(
|
||||
app.health_state(), other_tenant, session.id,
|
||||
)
|
||||
.await;
|
||||
let result =
|
||||
consultation_service::get_session(app.health_state(), other_tenant, session.id).await;
|
||||
assert!(result.is_err(), "不同租户不应看到此会话");
|
||||
}
|
||||
|
||||
@@ -197,7 +229,9 @@ async fn test_consultation_session_invalid_patient() {
|
||||
let fake_patient = uuid::Uuid::new_v4();
|
||||
|
||||
let result = consultation_service::create_session(
|
||||
app.health_state(), app.tenant_id(), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
None,
|
||||
CreateSessionReq {
|
||||
patient_id: fake_patient,
|
||||
doctor_id: None,
|
||||
|
||||
@@ -25,7 +25,9 @@ fn default_create_req(patient_id: uuid::Uuid) -> CreateDailyMonitoringReq {
|
||||
|
||||
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()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -57,11 +59,10 @@ async fn test_daily_monitoring_get() {
|
||||
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("查询应成功");
|
||||
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));
|
||||
}
|
||||
@@ -76,7 +77,9 @@ async fn test_daily_monitoring_update() {
|
||||
let dm = seed_monitoring(&app, patient_id).await;
|
||||
|
||||
let updated = daily_monitoring_service::update_daily_monitoring(
|
||||
app.health_state(), app.tenant_id(), dm.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
dm.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateDailyMonitoringReq {
|
||||
weight: Some(67.0),
|
||||
@@ -113,14 +116,22 @@ async fn test_daily_monitoring_list_by_patient() {
|
||||
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,
|
||||
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,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_b,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -137,16 +148,18 @@ async fn test_daily_monitoring_soft_delete() {
|
||||
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,
|
||||
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;
|
||||
let result =
|
||||
daily_monitoring_service::get_daily_monitoring(app.health_state(), app.tenant_id(), dm.id)
|
||||
.await;
|
||||
assert!(result.is_err(), "软删除后查询应失败");
|
||||
}
|
||||
|
||||
@@ -161,7 +174,11 @@ async fn test_daily_monitoring_tenant_isolation() {
|
||||
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let list = daily_monitoring_service::list_daily_monitoring(
|
||||
app.health_state(), other_tenant, patient_id, 1, 20,
|
||||
app.health_state(),
|
||||
other_tenant,
|
||||
patient_id,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -177,7 +194,9 @@ async fn test_daily_monitoring_invalid_patient() {
|
||||
let fake_patient = uuid::Uuid::new_v4();
|
||||
|
||||
let result = daily_monitoring_service::create_daily_monitoring(
|
||||
app.health_state(), app.tenant_id(), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
None,
|
||||
default_create_req(fake_patient),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -36,7 +36,10 @@ async fn test_vital_signs_create() {
|
||||
let patient_id = app.create_patient("体征患者").await;
|
||||
|
||||
let vs = health_data_service::create_vital_signs(
|
||||
app.health_state(), app.tenant_id(), patient_id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
Some(app.operator_id()),
|
||||
default_vital_signs_req(),
|
||||
)
|
||||
.await
|
||||
@@ -58,27 +61,41 @@ async fn test_vital_signs_list() {
|
||||
let patient_b = app.create_patient("列表B").await;
|
||||
|
||||
health_data_service::create_vital_signs(
|
||||
app.health_state(), app.tenant_id(), patient_a, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_a,
|
||||
None,
|
||||
default_vital_signs_req(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
health_data_service::create_vital_signs(
|
||||
app.health_state(), app.tenant_id(), patient_b, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_b,
|
||||
None,
|
||||
default_vital_signs_req(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let list_a = health_data_service::list_vital_signs(
|
||||
app.health_state(), app.tenant_id(), patient_a, 1, 20,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_a,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list_a.total, 1);
|
||||
|
||||
let list_b = health_data_service::list_vital_signs(
|
||||
app.health_state(), app.tenant_id(), patient_b, 1, 20,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_b,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -94,20 +111,35 @@ async fn test_vital_signs_update() {
|
||||
let patient_id = app.create_patient("更新患者").await;
|
||||
|
||||
let vs = health_data_service::create_vital_signs(
|
||||
app.health_state(), app.tenant_id(), patient_id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
Some(app.operator_id()),
|
||||
default_vital_signs_req(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let updated = health_data_service::update_vital_signs(
|
||||
app.health_state(), app.tenant_id(), patient_id, vs.id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
vs.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateVitalSignsReq {
|
||||
record_date: None, systolic_bp_morning: None, diastolic_bp_morning: None,
|
||||
systolic_bp_evening: None, diastolic_bp_evening: None,
|
||||
heart_rate: Some(65), weight: Some(67.0),
|
||||
blood_sugar: None, body_temperature: None, spo2: None,
|
||||
blood_sugar_type: None, water_intake_ml: None, urine_output_ml: None,
|
||||
record_date: None,
|
||||
systolic_bp_morning: None,
|
||||
diastolic_bp_morning: None,
|
||||
systolic_bp_evening: None,
|
||||
diastolic_bp_evening: None,
|
||||
heart_rate: Some(65),
|
||||
weight: Some(67.0),
|
||||
blood_sugar: None,
|
||||
body_temperature: None,
|
||||
spo2: None,
|
||||
blood_sugar_type: None,
|
||||
water_intake_ml: None,
|
||||
urine_output_ml: None,
|
||||
notes: None,
|
||||
},
|
||||
vs.version,
|
||||
@@ -128,18 +160,20 @@ async fn test_vital_signs_tenant_isolation() {
|
||||
let patient_id = app.create_patient("隔离患者").await;
|
||||
|
||||
health_data_service::create_vital_signs(
|
||||
app.health_state(), app.tenant_id(), patient_id, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
None,
|
||||
default_vital_signs_req(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let list = health_data_service::list_vital_signs(
|
||||
app.health_state(), other_tenant, patient_id, 1, 20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let list =
|
||||
health_data_service::list_vital_signs(app.health_state(), other_tenant, patient_id, 1, 20)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list.total, 0, "不同租户不应看到体征记录");
|
||||
}
|
||||
|
||||
@@ -183,11 +217,17 @@ async fn test_lab_report_review() {
|
||||
let patient_id = app.create_patient("审阅患者").await;
|
||||
|
||||
let report = health_data_service::create_lab_report(
|
||||
app.health_state(), app.tenant_id(), patient_id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
Some(app.operator_id()),
|
||||
CreateLabReportReq {
|
||||
report_date: chrono::NaiveDate::from_ymd_opt(2026, 5, 3).unwrap(),
|
||||
report_type: "blood_routine".to_string(),
|
||||
source: None, items: None, image_urls: None, doctor_notes: None,
|
||||
source: None,
|
||||
items: None,
|
||||
image_urls: None,
|
||||
doctor_notes: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -195,8 +235,15 @@ async fn test_lab_report_review() {
|
||||
assert_eq!(report.status, "pending");
|
||||
|
||||
let reviewed = health_data_service::review_lab_report(
|
||||
app.health_state(), app.tenant_id(), patient_id, report.id, app.operator_id(),
|
||||
ReviewLabReportReq { doctor_notes: Some("复查确认".to_string()), items: None },
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
report.id,
|
||||
app.operator_id(),
|
||||
ReviewLabReportReq {
|
||||
doctor_notes: Some("复查确认".to_string()),
|
||||
items: None,
|
||||
},
|
||||
report.version,
|
||||
)
|
||||
.await
|
||||
@@ -216,11 +263,17 @@ async fn test_lab_report_list() {
|
||||
|
||||
for pid in &[patient_a, patient_b] {
|
||||
health_data_service::create_lab_report(
|
||||
app.health_state(), app.tenant_id(), *pid, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
*pid,
|
||||
None,
|
||||
CreateLabReportReq {
|
||||
report_date: chrono::NaiveDate::from_ymd_opt(2026, 5, 4).unwrap(),
|
||||
report_type: "blood_routine".to_string(),
|
||||
source: None, items: None, image_urls: None, doctor_notes: None,
|
||||
source: None,
|
||||
items: None,
|
||||
image_urls: None,
|
||||
doctor_notes: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -228,7 +281,11 @@ async fn test_lab_report_list() {
|
||||
}
|
||||
|
||||
let list_a = health_data_service::list_lab_reports(
|
||||
app.health_state(), app.tenant_id(), patient_a, 1, 20,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_a,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -245,7 +302,10 @@ async fn test_vital_signs_invalid_patient() {
|
||||
let fake_patient = uuid::Uuid::new_v4();
|
||||
|
||||
let result = health_data_service::create_vital_signs(
|
||||
app.health_state(), app.tenant_id(), fake_patient, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
fake_patient,
|
||||
None,
|
||||
default_vital_signs_req(),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
//!
|
||||
//! 验证批量摄入、设备绑定自动创建、hourly 聚合、查询过滤、参数校验、租户隔离。
|
||||
|
||||
use erp_health::service::device_reading_service::{
|
||||
BatchReadingRequest, ReadingInput,
|
||||
};
|
||||
use erp_health::service::device_reading_service;
|
||||
use chrono::Datelike;
|
||||
use erp_health::service::device_reading_service;
|
||||
use erp_health::service::device_reading_service::{BatchReadingRequest, ReadingInput};
|
||||
use sea_orm::ConnectionTrait;
|
||||
|
||||
use super::test_fixture::TestApp;
|
||||
@@ -27,10 +25,13 @@ async fn ensure_current_month_partition(app: &TestApp) {
|
||||
let sql = format!(
|
||||
"CREATE TABLE IF NOT EXISTS device_readings_{suffix} PARTITION OF device_readings FOR VALUES FROM ('{start}') TO ('{next_month}');"
|
||||
);
|
||||
app.db().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
sql,
|
||||
)).await.expect("创建分区应成功");
|
||||
app.db()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
sql,
|
||||
))
|
||||
.await
|
||||
.expect("创建分区应成功");
|
||||
}
|
||||
|
||||
/// 构建一条心率读数(measured_at 用几分钟前的时间)
|
||||
@@ -53,7 +54,9 @@ async fn test_device_reading_batch_single() {
|
||||
let patient_id = app.create_patient("读数患者").await;
|
||||
|
||||
let result = device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
BatchReadingRequest {
|
||||
device_id: "watch-001".to_string(),
|
||||
device_model: Some("Apple Watch".to_string()),
|
||||
@@ -79,7 +82,9 @@ async fn test_device_reading_batch_multiple() {
|
||||
let patient_id = app.create_patient("批量患者").await;
|
||||
|
||||
let result = device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
BatchReadingRequest {
|
||||
device_id: "watch-002".to_string(),
|
||||
device_model: None,
|
||||
@@ -106,7 +111,9 @@ async fn test_device_reading_creates_device_binding() {
|
||||
let patient_id = app.create_patient("绑定患者").await;
|
||||
|
||||
device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
BatchReadingRequest {
|
||||
device_id: "band-001".to_string(),
|
||||
device_model: Some("Mi Band".to_string()),
|
||||
@@ -118,7 +125,9 @@ async fn test_device_reading_creates_device_binding() {
|
||||
|
||||
// 再次使用同一设备,应更新 last_sync_at 而非重复创建
|
||||
let result = device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
BatchReadingRequest {
|
||||
device_id: "band-001".to_string(),
|
||||
device_model: Some("Mi Band".to_string()),
|
||||
@@ -141,7 +150,9 @@ async fn test_device_reading_hourly_aggregation() {
|
||||
let patient_id = app.create_patient("聚合患者").await;
|
||||
|
||||
device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
BatchReadingRequest {
|
||||
device_id: "watch-003".to_string(),
|
||||
device_model: None,
|
||||
@@ -157,7 +168,13 @@ async fn test_device_reading_hourly_aggregation() {
|
||||
|
||||
// 查询 hourly 聚合
|
||||
let hourly = device_reading_service::query_hourly_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id, "heart_rate", 1, 1, 20,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"heart_rate",
|
||||
1,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.expect("查询 hourly 应成功");
|
||||
@@ -179,21 +196,26 @@ async fn test_device_reading_query_filter() {
|
||||
let patient_id = app.create_patient("查询患者").await;
|
||||
|
||||
device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
BatchReadingRequest {
|
||||
device_id: "watch-004".to_string(),
|
||||
device_model: None,
|
||||
readings: vec![
|
||||
heart_rate_reading(72, 5),
|
||||
heart_rate_reading(74, 3),
|
||||
],
|
||||
readings: vec![heart_rate_reading(72, 5), heart_rate_reading(74, 3)],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let readings = device_reading_service::query_device_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id, Some("heart_rate"), None, 1, 20,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
Some("heart_rate"),
|
||||
None,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.expect("查询应成功");
|
||||
@@ -211,7 +233,9 @@ async fn test_device_reading_invalid_device_type() {
|
||||
let patient_id = app.create_patient("校验患者").await;
|
||||
|
||||
let result = device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
BatchReadingRequest {
|
||||
device_id: "bad-001".to_string(),
|
||||
device_model: None,
|
||||
@@ -237,7 +261,9 @@ async fn test_device_reading_future_time_rejected() {
|
||||
|
||||
let future_time = (chrono::Utc::now() + chrono::Duration::hours(1)).to_rfc3339();
|
||||
let result = device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
BatchReadingRequest {
|
||||
device_id: "watch-005".to_string(),
|
||||
device_model: None,
|
||||
@@ -264,7 +290,9 @@ async fn test_device_reading_invalid_patient_and_isolation() {
|
||||
// 无效患者
|
||||
let fake_patient = uuid::Uuid::new_v4();
|
||||
let result = device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), fake_patient,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
fake_patient,
|
||||
BatchReadingRequest {
|
||||
device_id: "watch-006".to_string(),
|
||||
device_model: None,
|
||||
@@ -277,7 +305,9 @@ async fn test_device_reading_invalid_patient_and_isolation() {
|
||||
// 租户隔离:创建患者并摄入数据,用不同租户查询
|
||||
let patient_id = app.create_patient("隔离患者").await;
|
||||
device_reading_service::batch_create_readings(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
BatchReadingRequest {
|
||||
device_id: "watch-007".to_string(),
|
||||
device_model: None,
|
||||
@@ -289,7 +319,13 @@ async fn test_device_reading_invalid_patient_and_isolation() {
|
||||
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let readings = device_reading_service::query_device_readings(
|
||||
app.health_state(), other_tenant, patient_id, None, None, 1, 20,
|
||||
app.health_state(),
|
||||
other_tenant,
|
||||
patient_id,
|
||||
None,
|
||||
None,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.expect("查询应成功");
|
||||
|
||||
@@ -20,9 +20,17 @@ fn default_create_diagnosis_req() -> CreateDiagnosisReq {
|
||||
}
|
||||
}
|
||||
|
||||
async fn seed_diagnosis(app: &TestApp, patient_id: uuid::Uuid, icd_code: &str, name: &str) -> DiagnosisResp {
|
||||
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()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
Some(app.operator_id()),
|
||||
CreateDiagnosisReq {
|
||||
icd_code: icd_code.to_string(),
|
||||
diagnosis_name: name.to_string(),
|
||||
@@ -60,7 +68,9 @@ async fn test_diagnosis_update() {
|
||||
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,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
diag.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateDiagnosisReq {
|
||||
status: Some("chronic".to_string()),
|
||||
@@ -94,18 +104,16 @@ async fn test_diagnosis_list_by_patient() {
|
||||
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();
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -119,17 +127,19 @@ async fn test_diagnosis_soft_delete() {
|
||||
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,
|
||||
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();
|
||||
let list =
|
||||
diagnosis_service::list_diagnoses(app.health_state(), app.tenant_id(), patient_id, 1, 20)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list.total, 0);
|
||||
}
|
||||
|
||||
@@ -143,11 +153,10 @@ async fn test_diagnosis_tenant_isolation() {
|
||||
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();
|
||||
let list =
|
||||
diagnosis_service::list_diagnoses(app.health_state(), other_tenant, patient_id, 1, 20)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list.total, 0, "不同租户不应看到诊断记录");
|
||||
}
|
||||
|
||||
@@ -160,7 +169,10 @@ async fn test_diagnosis_invalid_patient() {
|
||||
let fake_patient = uuid::Uuid::new_v4();
|
||||
|
||||
let result = diagnosis_service::create_diagnosis(
|
||||
app.health_state(), app.tenant_id(), fake_patient, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
fake_patient,
|
||||
None,
|
||||
default_create_diagnosis_req(),
|
||||
)
|
||||
.await;
|
||||
@@ -178,13 +190,19 @@ async fn test_diagnosis_version_conflict() {
|
||||
|
||||
// 先更新一次
|
||||
diagnosis_service::update_diagnosis(
|
||||
app.health_state(), app.tenant_id(), diag.id,
|
||||
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,
|
||||
icd_code: None,
|
||||
diagnosis_name: None,
|
||||
diagnosis_type: None,
|
||||
diagnosed_date: None,
|
||||
health_record_id: None,
|
||||
diagnosed_by: None,
|
||||
notes: None,
|
||||
},
|
||||
diag.version,
|
||||
)
|
||||
@@ -193,13 +211,19 @@ async fn test_diagnosis_version_conflict() {
|
||||
|
||||
// 用旧 version 再更新应失败
|
||||
let result = diagnosis_service::update_diagnosis(
|
||||
app.health_state(), app.tenant_id(), diag.id,
|
||||
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,
|
||||
icd_code: None,
|
||||
diagnosis_name: None,
|
||||
diagnosis_type: None,
|
||||
diagnosed_date: None,
|
||||
health_record_id: None,
|
||||
diagnosed_by: None,
|
||||
notes: None,
|
||||
},
|
||||
diag.version,
|
||||
)
|
||||
|
||||
@@ -33,7 +33,9 @@ fn default_create_req(patient_id: uuid::Uuid) -> CreateDialysisPrescriptionReq {
|
||||
|
||||
async fn seed_prescription(app: &TestApp, patient_id: uuid::Uuid) -> DialysisPrescriptionResp {
|
||||
dialysis_prescription_service::create_prescription(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -67,7 +69,9 @@ async fn test_dialysis_prescription_get() {
|
||||
let rx = seed_prescription(&app, patient_id).await;
|
||||
|
||||
let fetched = dialysis_prescription_service::get_prescription(
|
||||
app.dialysis_state(), app.tenant_id(), rx.id,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
rx.id,
|
||||
)
|
||||
.await
|
||||
.expect("查询应成功");
|
||||
@@ -84,20 +88,30 @@ async fn test_dialysis_prescription_update() {
|
||||
let rx = seed_prescription(&app, patient_id).await;
|
||||
|
||||
let updated = dialysis_prescription_service::update_prescription(
|
||||
app.dialysis_state(), app.tenant_id(), rx.id,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
rx.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateDialysisPrescriptionReq {
|
||||
blood_flow_rate: Some(350),
|
||||
frequency_per_week: Some(4),
|
||||
status: None,
|
||||
dialyzer_model: None, membrane_area: None,
|
||||
dialysate_potassium: None, dialysate_calcium: None,
|
||||
dialysate_bicarbonate: None, anticoagulation_type: None,
|
||||
anticoagulation_dose: None, target_ultrafiltration_ml: None,
|
||||
target_dry_weight: None, dialysate_flow_rate: None,
|
||||
duration_minutes: None, vascular_access_type: None,
|
||||
vascular_access_location: None, effective_from: None,
|
||||
effective_to: None, notes: None,
|
||||
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,
|
||||
)
|
||||
@@ -122,14 +136,24 @@ async fn test_dialysis_prescription_list_by_patient() {
|
||||
seed_prescription(&app, patient_b).await;
|
||||
|
||||
let list_a = dialysis_prescription_service::list_prescriptions(
|
||||
app.dialysis_state(), app.tenant_id(), 1, 20, Some(patient_a), None,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
Some(patient_a),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list_a.total, 1);
|
||||
|
||||
let list_b = dialysis_prescription_service::list_prescriptions(
|
||||
app.dialysis_state(), app.tenant_id(), 1, 20, Some(patient_b), None,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
Some(patient_b),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -146,14 +170,19 @@ async fn test_dialysis_prescription_soft_delete() {
|
||||
let rx = seed_prescription(&app, patient_id).await;
|
||||
|
||||
dialysis_prescription_service::delete_prescription(
|
||||
app.dialysis_state(), app.tenant_id(), rx.id,
|
||||
Some(app.operator_id()), rx.version,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
rx.id,
|
||||
Some(app.operator_id()),
|
||||
rx.version,
|
||||
)
|
||||
.await
|
||||
.expect("删除应成功");
|
||||
|
||||
let result = dialysis_prescription_service::get_prescription(
|
||||
app.dialysis_state(), app.tenant_id(), rx.id,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
rx.id,
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_err(), "软删除后查询应失败");
|
||||
@@ -170,7 +199,12 @@ async fn test_dialysis_prescription_tenant_isolation() {
|
||||
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let list = dialysis_prescription_service::list_prescriptions(
|
||||
app.dialysis_state(), other_tenant, 1, 20, None, None,
|
||||
app.dialysis_state(),
|
||||
other_tenant,
|
||||
1,
|
||||
20,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -188,18 +222,30 @@ async fn test_dialysis_prescription_version_conflict() {
|
||||
|
||||
// 先更新一次
|
||||
dialysis_prescription_service::update_prescription(
|
||||
app.dialysis_state(), app.tenant_id(), rx.id,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
rx.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateDialysisPrescriptionReq {
|
||||
blood_flow_rate: Some(350),
|
||||
status: None, dialyzer_model: None, membrane_area: None,
|
||||
dialysate_potassium: None, dialysate_calcium: None,
|
||||
dialysate_bicarbonate: None, anticoagulation_type: None,
|
||||
anticoagulation_dose: None, target_ultrafiltration_ml: None,
|
||||
target_dry_weight: None, frequency_per_week: None,
|
||||
dialysate_flow_rate: None, duration_minutes: None,
|
||||
vascular_access_type: None, vascular_access_location: None,
|
||||
effective_from: None, effective_to: None, notes: None,
|
||||
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,
|
||||
)
|
||||
@@ -208,18 +254,30 @@ async fn test_dialysis_prescription_version_conflict() {
|
||||
|
||||
// 用旧 version 再更新应失败
|
||||
let result = dialysis_prescription_service::update_prescription(
|
||||
app.dialysis_state(), app.tenant_id(), rx.id,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
rx.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateDialysisPrescriptionReq {
|
||||
blood_flow_rate: Some(400),
|
||||
status: None, dialyzer_model: None, membrane_area: None,
|
||||
dialysate_potassium: None, dialysate_calcium: None,
|
||||
dialysate_bicarbonate: None, anticoagulation_type: None,
|
||||
anticoagulation_dose: None, target_ultrafiltration_ml: None,
|
||||
target_dry_weight: None, frequency_per_week: None,
|
||||
dialysate_flow_rate: None, duration_minutes: None,
|
||||
vascular_access_type: None, vascular_access_location: None,
|
||||
effective_from: None, effective_to: None, notes: None,
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -42,7 +42,10 @@ async fn test_dialysis_create_basic() {
|
||||
|
||||
let req = default_create_req(patient_id);
|
||||
let record = dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), req,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
.expect("创建透析记录应成功");
|
||||
@@ -53,11 +56,10 @@ async fn test_dialysis_create_basic() {
|
||||
assert_eq!(record.ultrafiltration_volume, Some(2500));
|
||||
|
||||
// 读取
|
||||
let fetched = dialysis_service::get_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), record.id,
|
||||
)
|
||||
.await
|
||||
.expect("查询应成功");
|
||||
let fetched =
|
||||
dialysis_service::get_dialysis_record(app.dialysis_state(), app.tenant_id(), record.id)
|
||||
.await
|
||||
.expect("查询应成功");
|
||||
assert_eq!(fetched.id, record.id);
|
||||
}
|
||||
|
||||
@@ -74,7 +76,10 @@ async fn test_dialysis_create_pii_encrypted() {
|
||||
req.complication_notes = Some("低血压发作".to_string());
|
||||
|
||||
let record = dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), req,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
.expect("创建应成功");
|
||||
@@ -93,7 +98,9 @@ async fn test_dialysis_update_status_flow() {
|
||||
let patient_id = app.create_patient("状态流转患者").await;
|
||||
|
||||
let record = dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -101,11 +108,17 @@ async fn test_dialysis_update_status_flow() {
|
||||
assert_eq!(record.status, "draft");
|
||||
|
||||
// 先将状态推进到 completed(draft → completed → reviewed)
|
||||
use sea_orm::{EntityTrait, ColumnTrait, QueryFilter};
|
||||
use erp_dialysis::entity::dialysis_record;
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||
let _ = dialysis_record::Entity::update_many()
|
||||
.col_expr(dialysis_record::Column::Status, sea_orm::sea_query::Expr::val("completed").into())
|
||||
.col_expr(dialysis_record::Column::Version, sea_orm::sea_query::Expr::val(record.version + 1).into())
|
||||
.col_expr(
|
||||
dialysis_record::Column::Status,
|
||||
sea_orm::sea_query::Expr::val("completed").into(),
|
||||
)
|
||||
.col_expr(
|
||||
dialysis_record::Column::Version,
|
||||
sea_orm::sea_query::Expr::val(record.version + 1).into(),
|
||||
)
|
||||
.filter(dialysis_record::Column::Id.eq(record.id))
|
||||
.exec(app.db())
|
||||
.await
|
||||
@@ -113,7 +126,11 @@ async fn test_dialysis_update_status_flow() {
|
||||
|
||||
// 审核: completed → reviewed
|
||||
let reviewed = dialysis_service::review_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), record.id, app.operator_id(), record.version + 1,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
record.id,
|
||||
app.operator_id(),
|
||||
record.version + 1,
|
||||
)
|
||||
.await
|
||||
.expect("审核应成功");
|
||||
@@ -131,35 +148,49 @@ async fn test_dialysis_list_by_patient() {
|
||||
let patient_b = app.create_patient("列表患者B").await;
|
||||
|
||||
dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(patient_a),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(patient_a),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(patient_b),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let list_a = dialysis_service::list_dialysis_records(
|
||||
app.dialysis_state(), app.tenant_id(), patient_a, 1, 20,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
patient_a,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list_a.total, 2);
|
||||
|
||||
let list_b = dialysis_service::list_dialysis_records(
|
||||
app.dialysis_state(), app.tenant_id(), patient_b, 1, 20,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
patient_b,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -175,7 +206,9 @@ async fn test_dialysis_tenant_isolation() {
|
||||
let patient_id = app.create_patient("隔离患者").await;
|
||||
|
||||
let record = dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -183,10 +216,8 @@ async fn test_dialysis_tenant_isolation() {
|
||||
|
||||
// 用不同 tenant_id 查询应失败
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let result = dialysis_service::get_dialysis_record(
|
||||
app.dialysis_state(), other_tenant, record.id,
|
||||
)
|
||||
.await;
|
||||
let result =
|
||||
dialysis_service::get_dialysis_record(app.dialysis_state(), other_tenant, record.id).await;
|
||||
assert!(result.is_err(), "不同租户不应看到此记录");
|
||||
}
|
||||
|
||||
@@ -199,7 +230,9 @@ async fn test_dialysis_version_conflict() {
|
||||
let patient_id = app.create_patient("乐观锁患者").await;
|
||||
|
||||
let record = dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -207,17 +240,29 @@ async fn test_dialysis_version_conflict() {
|
||||
|
||||
// 用正确版本更新
|
||||
let updated = dialysis_service::update_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), record.id, Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
record.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateDialysisRecordReq {
|
||||
dialysis_date: None, start_time: None, end_time: None,
|
||||
dry_weight: None, pre_weight: None, post_weight: None,
|
||||
pre_bp_systolic: None, pre_bp_diastolic: None,
|
||||
post_bp_systolic: None, post_bp_diastolic: None,
|
||||
pre_heart_rate: None, post_heart_rate: None,
|
||||
ultrafiltration_volume: None, dialysis_duration: None,
|
||||
dialysis_date: None,
|
||||
start_time: None,
|
||||
end_time: None,
|
||||
dry_weight: None,
|
||||
pre_weight: None,
|
||||
post_weight: None,
|
||||
pre_bp_systolic: None,
|
||||
pre_bp_diastolic: None,
|
||||
post_bp_systolic: None,
|
||||
post_bp_diastolic: None,
|
||||
pre_heart_rate: None,
|
||||
post_heart_rate: None,
|
||||
ultrafiltration_volume: None,
|
||||
dialysis_duration: None,
|
||||
blood_flow_rate: None,
|
||||
dialysis_type: Some("HDF".to_string()),
|
||||
symptoms: None, complication_notes: None,
|
||||
symptoms: None,
|
||||
complication_notes: None,
|
||||
},
|
||||
record.version,
|
||||
)
|
||||
@@ -227,17 +272,29 @@ async fn test_dialysis_version_conflict() {
|
||||
|
||||
// 用旧版本更新应失败
|
||||
let result = dialysis_service::update_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), record.id, Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
record.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateDialysisRecordReq {
|
||||
dialysis_date: None, start_time: None, end_time: None,
|
||||
dry_weight: None, pre_weight: None, post_weight: None,
|
||||
pre_bp_systolic: None, pre_bp_diastolic: None,
|
||||
post_bp_systolic: None, post_bp_diastolic: None,
|
||||
pre_heart_rate: None, post_heart_rate: None,
|
||||
ultrafiltration_volume: None, dialysis_duration: None,
|
||||
dialysis_date: None,
|
||||
start_time: None,
|
||||
end_time: None,
|
||||
dry_weight: None,
|
||||
pre_weight: None,
|
||||
post_weight: None,
|
||||
pre_bp_systolic: None,
|
||||
pre_bp_diastolic: None,
|
||||
post_bp_systolic: None,
|
||||
post_bp_diastolic: None,
|
||||
pre_heart_rate: None,
|
||||
post_heart_rate: None,
|
||||
ultrafiltration_volume: None,
|
||||
dialysis_duration: None,
|
||||
blood_flow_rate: None,
|
||||
dialysis_type: Some("HD".to_string()),
|
||||
symptoms: None, complication_notes: None,
|
||||
symptoms: None,
|
||||
complication_notes: None,
|
||||
},
|
||||
record.version, // 旧版本
|
||||
)
|
||||
@@ -254,7 +311,9 @@ async fn test_dialysis_soft_delete() {
|
||||
let patient_id = app.create_patient("软删除患者").await;
|
||||
|
||||
let record = dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -262,21 +321,28 @@ async fn test_dialysis_soft_delete() {
|
||||
|
||||
// 删除
|
||||
dialysis_service::delete_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), record.id, Some(app.operator_id()), record.version,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
record.id,
|
||||
Some(app.operator_id()),
|
||||
record.version,
|
||||
)
|
||||
.await
|
||||
.expect("删除应成功");
|
||||
|
||||
// 查询应失败
|
||||
let result = dialysis_service::get_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), record.id,
|
||||
)
|
||||
.await;
|
||||
let result =
|
||||
dialysis_service::get_dialysis_record(app.dialysis_state(), app.tenant_id(), record.id)
|
||||
.await;
|
||||
assert!(result.is_err(), "软删除后应不可见");
|
||||
|
||||
// 列表中不应出现
|
||||
let list = dialysis_service::list_dialysis_records(
|
||||
app.dialysis_state(), app.tenant_id(), patient_id, 1, 20,
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -292,7 +358,9 @@ async fn test_dialysis_create_without_patient_returns_error() {
|
||||
let fake_patient = uuid::Uuid::new_v4();
|
||||
|
||||
let result = dialysis_service::create_dialysis_record(
|
||||
app.dialysis_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.dialysis_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(fake_patient),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -26,7 +26,9 @@ fn default_create_doctor_req() -> CreateDoctorReq {
|
||||
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()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_doctor_req(),
|
||||
)
|
||||
.await
|
||||
@@ -45,17 +47,17 @@ async fn test_doctor_create() {
|
||||
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()),
|
||||
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("查询应成功");
|
||||
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, "张三");
|
||||
}
|
||||
@@ -67,14 +69,18 @@ async fn test_doctor_get() {
|
||||
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()),
|
||||
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,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
doctor.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateDoctorReq {
|
||||
name: Some("李四".to_string()),
|
||||
@@ -104,7 +110,9 @@ 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()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
CreateDoctorReq {
|
||||
name: "王医生".to_string(),
|
||||
department: Some("心内科".to_string()),
|
||||
@@ -115,7 +123,9 @@ async fn test_doctor_list_and_search() {
|
||||
.unwrap();
|
||||
|
||||
doctor_service::create_doctor(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
CreateDoctorReq {
|
||||
name: "赵医生".to_string(),
|
||||
department: Some("肾内科".to_string()),
|
||||
@@ -126,17 +136,21 @@ async fn test_doctor_list_and_search() {
|
||||
.unwrap();
|
||||
|
||||
// 全量列表
|
||||
let all = doctor_service::list_doctors(
|
||||
app.health_state(), app.tenant_id(), 1, 20, None, None, None,
|
||||
)
|
||||
.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,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
None,
|
||||
Some("肾内科".to_string()),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -150,23 +164,25 @@ async fn test_doctor_list_and_search() {
|
||||
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()),
|
||||
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,
|
||||
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;
|
||||
let result = doctor_service::get_doctor(app.health_state(), app.tenant_id(), doctor.id).await;
|
||||
assert!(result.is_err(), "软删除后查询应失败");
|
||||
}
|
||||
|
||||
@@ -177,24 +193,22 @@ async fn test_doctor_soft_delete() {
|
||||
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()),
|
||||
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;
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -205,7 +219,9 @@ async fn test_doctor_tenant_isolation() {
|
||||
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()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_doctor_req(),
|
||||
)
|
||||
.await
|
||||
@@ -213,12 +229,18 @@ async fn test_doctor_version_conflict() {
|
||||
|
||||
// 先更新一次
|
||||
doctor_service::update_doctor(
|
||||
app.health_state(), app.tenant_id(), doctor.id,
|
||||
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,
|
||||
department: None,
|
||||
title: None,
|
||||
specialty: None,
|
||||
license_number: None,
|
||||
bio: None,
|
||||
online_status: None,
|
||||
},
|
||||
doctor.version,
|
||||
)
|
||||
@@ -227,12 +249,18 @@ async fn test_doctor_version_conflict() {
|
||||
|
||||
// 用旧 version 再更新应失败
|
||||
let result = doctor_service::update_doctor(
|
||||
app.health_state(), app.tenant_id(), doctor.id,
|
||||
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,
|
||||
department: None,
|
||||
title: None,
|
||||
specialty: None,
|
||||
license_number: None,
|
||||
bio: None,
|
||||
online_status: None,
|
||||
},
|
||||
doctor.version,
|
||||
)
|
||||
|
||||
@@ -32,7 +32,9 @@ fn default_create_req() -> CreateFollowUpTemplateReq {
|
||||
|
||||
async fn seed_template(app: &TestApp) -> FollowUpTemplateResp {
|
||||
follow_up_template_service::create_template(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(),
|
||||
)
|
||||
.await
|
||||
@@ -63,11 +65,10 @@ 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("查询应成功");
|
||||
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);
|
||||
}
|
||||
@@ -81,7 +82,9 @@ async fn test_template_update_replace_fields() {
|
||||
let tmpl = seed_template(&app).await;
|
||||
|
||||
let updated = follow_up_template_service::update_template(
|
||||
app.health_state(), app.tenant_id(), tmpl.id,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
tmpl.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateFollowUpTemplateReq {
|
||||
name: Some("更新后的模板".to_string()),
|
||||
@@ -130,7 +133,9 @@ 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()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
CreateFollowUpTemplateReq {
|
||||
name: "门诊随访".to_string(),
|
||||
follow_up_type: "outpatient".to_string(),
|
||||
@@ -143,7 +148,9 @@ async fn test_template_list_filter() {
|
||||
.unwrap();
|
||||
|
||||
follow_up_template_service::create_template(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_req(),
|
||||
)
|
||||
.await
|
||||
@@ -151,7 +158,12 @@ async fn test_template_list_filter() {
|
||||
|
||||
// 全量
|
||||
let all = follow_up_template_service::list_templates(
|
||||
app.health_state(), app.tenant_id(), 1, 20, None, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -159,8 +171,12 @@ async fn test_template_list_filter() {
|
||||
|
||||
// 按类型过滤
|
||||
let phone = follow_up_template_service::list_templates(
|
||||
app.health_state(), app.tenant_id(), 1, 20,
|
||||
Some("phone".to_string()), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
Some("phone".to_string()),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -176,16 +192,18 @@ async fn test_template_soft_delete() {
|
||||
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,
|
||||
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;
|
||||
let result =
|
||||
follow_up_template_service::get_template(app.health_state(), app.tenant_id(), tmpl.id)
|
||||
.await;
|
||||
assert!(result.is_err(), "软删除后查询应失败");
|
||||
}
|
||||
|
||||
@@ -199,7 +217,12 @@ async fn test_template_tenant_isolation() {
|
||||
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let list = follow_up_template_service::list_templates(
|
||||
app.health_state(), other_tenant, 1, 20, None, None,
|
||||
app.health_state(),
|
||||
other_tenant,
|
||||
1,
|
||||
20,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -216,12 +239,17 @@ async fn test_template_version_conflict() {
|
||||
|
||||
// 先更新一次
|
||||
follow_up_template_service::update_template(
|
||||
app.health_state(), app.tenant_id(), tmpl.id,
|
||||
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,
|
||||
description: None,
|
||||
follow_up_type: None,
|
||||
applicable_scope: None,
|
||||
status: None,
|
||||
fields: None,
|
||||
},
|
||||
tmpl.version,
|
||||
)
|
||||
@@ -230,12 +258,17 @@ async fn test_template_version_conflict() {
|
||||
|
||||
// 用旧 version 再更新应失败
|
||||
let result = follow_up_template_service::update_template(
|
||||
app.health_state(), app.tenant_id(), tmpl.id,
|
||||
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,
|
||||
description: None,
|
||||
follow_up_type: None,
|
||||
applicable_scope: None,
|
||||
status: None,
|
||||
fields: None,
|
||||
},
|
||||
tmpl.version,
|
||||
)
|
||||
|
||||
@@ -27,7 +27,9 @@ async fn test_follow_up_task_create_and_get() {
|
||||
let patient_id = app.create_patient("随访患者").await;
|
||||
|
||||
let task = follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_task(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -54,33 +56,51 @@ async fn test_follow_up_task_list_by_patient() {
|
||||
let patient_b = app.create_patient("列表B").await;
|
||||
|
||||
follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
None,
|
||||
default_create_task(patient_a),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
None,
|
||||
default_create_task(patient_a),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
None,
|
||||
default_create_task(patient_b),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let list_a = follow_up_service::list_tasks(
|
||||
app.health_state(), app.tenant_id(), 1, 20, Some(patient_a), None, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
Some(patient_a),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(list_a.total, 2);
|
||||
|
||||
let list_b = follow_up_service::list_tasks(
|
||||
app.health_state(), app.tenant_id(), 1, 20, Some(patient_b), None, None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
1,
|
||||
20,
|
||||
Some(patient_b),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -96,7 +116,9 @@ async fn test_follow_up_task_status_flow() {
|
||||
let patient_id = app.create_patient("流转患者").await;
|
||||
|
||||
let task = follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_task(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -105,10 +127,15 @@ async fn test_follow_up_task_status_flow() {
|
||||
|
||||
// pending → in_progress
|
||||
let started = follow_up_service::update_task(
|
||||
app.health_state(), app.tenant_id(), task.id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
task.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateFollowUpTaskReq {
|
||||
status: Some("in_progress".to_string()),
|
||||
assigned_to: None, follow_up_type: None, planned_date: None,
|
||||
assigned_to: None,
|
||||
follow_up_type: None,
|
||||
planned_date: None,
|
||||
content_template: None,
|
||||
},
|
||||
task.version,
|
||||
@@ -119,10 +146,15 @@ async fn test_follow_up_task_status_flow() {
|
||||
|
||||
// in_progress → completed
|
||||
let completed = follow_up_service::update_task(
|
||||
app.health_state(), app.tenant_id(), task.id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
task.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateFollowUpTaskReq {
|
||||
status: Some("completed".to_string()),
|
||||
assigned_to: None, follow_up_type: None, planned_date: None,
|
||||
assigned_to: None,
|
||||
follow_up_type: None,
|
||||
planned_date: None,
|
||||
content_template: None,
|
||||
},
|
||||
started.version,
|
||||
@@ -141,7 +173,9 @@ async fn test_follow_up_task_version_conflict() {
|
||||
let patient_id = app.create_patient("乐观锁患者").await;
|
||||
|
||||
let task = follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_task(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -149,10 +183,15 @@ async fn test_follow_up_task_version_conflict() {
|
||||
|
||||
// 正确版本更新
|
||||
follow_up_service::update_task(
|
||||
app.health_state(), app.tenant_id(), task.id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
task.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateFollowUpTaskReq {
|
||||
status: Some("in_progress".to_string()),
|
||||
assigned_to: None, follow_up_type: None, planned_date: None,
|
||||
assigned_to: None,
|
||||
follow_up_type: None,
|
||||
planned_date: None,
|
||||
content_template: None,
|
||||
},
|
||||
task.version,
|
||||
@@ -162,10 +201,15 @@ async fn test_follow_up_task_version_conflict() {
|
||||
|
||||
// 旧版本更新应失败
|
||||
let result = follow_up_service::update_task(
|
||||
app.health_state(), app.tenant_id(), task.id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
task.id,
|
||||
Some(app.operator_id()),
|
||||
UpdateFollowUpTaskReq {
|
||||
status: Some("cancelled".to_string()),
|
||||
assigned_to: None, follow_up_type: None, planned_date: None,
|
||||
assigned_to: None,
|
||||
follow_up_type: None,
|
||||
planned_date: None,
|
||||
content_template: None,
|
||||
},
|
||||
task.version,
|
||||
@@ -183,14 +227,20 @@ async fn test_follow_up_task_soft_delete() {
|
||||
let patient_id = app.create_patient("软删除患者").await;
|
||||
|
||||
let task = follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_task(patient_id),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
follow_up_service::delete_task(
|
||||
app.health_state(), app.tenant_id(), task.id, Some(app.operator_id()), task.version,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
task.id,
|
||||
Some(app.operator_id()),
|
||||
task.version,
|
||||
)
|
||||
.await
|
||||
.expect("删除应成功");
|
||||
@@ -208,7 +258,9 @@ async fn test_follow_up_task_tenant_isolation() {
|
||||
let patient_id = app.create_patient("隔离患者").await;
|
||||
|
||||
let task = follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
None,
|
||||
default_create_task(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -229,7 +281,9 @@ async fn test_follow_up_batch_create() {
|
||||
let patient_b = app.create_patient("批量B").await;
|
||||
|
||||
let result = follow_up_service::batch_create_tasks(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
BatchCreateTasksReq {
|
||||
patient_ids: vec![patient_a, patient_b],
|
||||
assigned_to: None,
|
||||
@@ -254,14 +308,18 @@ async fn test_follow_up_record_create() {
|
||||
let patient_id = app.create_patient("记录患者").await;
|
||||
|
||||
let task = follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_task(patient_id),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let record = follow_up_service::create_record(
|
||||
app.health_state(), app.tenant_id(), Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
CreateFollowUpRecordReq {
|
||||
task_id: task.id,
|
||||
executed_by: Some(app.operator_id()),
|
||||
@@ -289,7 +347,9 @@ async fn test_follow_up_task_invalid_patient() {
|
||||
let fake_patient = uuid::Uuid::new_v4();
|
||||
|
||||
let result = follow_up_service::create_task(
|
||||
app.health_state(), app.tenant_id(), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
None,
|
||||
default_create_task(fake_patient),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -26,7 +26,9 @@ fn default_create_medication_req(patient_id: uuid::Uuid) -> CreateMedicationReco
|
||||
|
||||
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()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
default_create_medication_req(patient_id),
|
||||
)
|
||||
.await
|
||||
@@ -59,11 +61,10 @@ async fn test_medication_get() {
|
||||
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("查询应成功");
|
||||
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, "缬沙坦");
|
||||
}
|
||||
@@ -78,14 +79,22 @@ async fn test_medication_update() {
|
||||
let med = seed_medication(&app, patient_id).await;
|
||||
|
||||
let updated = medication_record_service::update_medication(
|
||||
app.health_state(), app.tenant_id(), med.id,
|
||||
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,
|
||||
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,
|
||||
)
|
||||
@@ -110,14 +119,22 @@ async fn test_medication_list_by_patient() {
|
||||
seed_medication(&app, patient_b).await;
|
||||
|
||||
let list_a = medication_record_service::list_medications(
|
||||
app.health_state(), app.tenant_id(), patient_a, 1, 20,
|
||||
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,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_b,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -134,16 +151,18 @@ async fn test_medication_soft_delete() {
|
||||
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,
|
||||
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;
|
||||
let result =
|
||||
medication_record_service::get_medication(app.health_state(), app.tenant_id(), med.id)
|
||||
.await;
|
||||
assert!(result.is_err(), "软删除后查询应失败");
|
||||
}
|
||||
|
||||
@@ -158,7 +177,11 @@ async fn test_medication_tenant_isolation() {
|
||||
|
||||
let other_tenant = uuid::Uuid::new_v4();
|
||||
let list = medication_record_service::list_medications(
|
||||
app.health_state(), other_tenant, patient_id, 1, 20,
|
||||
app.health_state(),
|
||||
other_tenant,
|
||||
patient_id,
|
||||
1,
|
||||
20,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -176,13 +199,22 @@ async fn test_medication_version_conflict() {
|
||||
|
||||
// 先更新一次
|
||||
medication_record_service::update_medication(
|
||||
app.health_state(), app.tenant_id(), med.id,
|
||||
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,
|
||||
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,
|
||||
)
|
||||
@@ -191,13 +223,22 @@ async fn test_medication_version_conflict() {
|
||||
|
||||
// 用旧 version 再更新应失败
|
||||
let result = medication_record_service::update_medication(
|
||||
app.health_state(), app.tenant_id(), med.id,
|
||||
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,
|
||||
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,
|
||||
)
|
||||
@@ -214,7 +255,9 @@ async fn test_medication_invalid_patient() {
|
||||
let fake_patient = uuid::Uuid::new_v4();
|
||||
|
||||
let result = medication_record_service::create_medication(
|
||||
app.health_state(), app.tenant_id(), None,
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
None,
|
||||
default_create_medication_req(fake_patient),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
//! 验证患者 CRUD、租户隔离、字段校验、软删除等核心行为。
|
||||
//! 使用 TestDb 创建隔离 PostgreSQL 数据库,直接调用 service 层函数。
|
||||
|
||||
use erp_core::crypto::PiiCrypto;
|
||||
use erp_core::events::EventBus;
|
||||
use erp_health::dto::patient_dto::{CreatePatientReq, UpdatePatientReq};
|
||||
use erp_health::service::patient_service;
|
||||
use erp_health::state::HealthState;
|
||||
use erp_core::crypto::PiiCrypto;
|
||||
|
||||
use super::test_db::TestDb;
|
||||
|
||||
@@ -70,7 +70,11 @@ async fn test_list_patients() {
|
||||
for i in 0..2 {
|
||||
let req = CreatePatientReq {
|
||||
name: format!("患者{}", i + 1),
|
||||
gender: if i == 0 { Some("male".to_string()) } else { Some("female".to_string()) },
|
||||
gender: if i == 0 {
|
||||
Some("male".to_string())
|
||||
} else {
|
||||
Some("female".to_string())
|
||||
},
|
||||
birth_date: None,
|
||||
blood_type: None,
|
||||
id_number: None,
|
||||
@@ -128,10 +132,7 @@ async fn test_patient_tenant_isolation() {
|
||||
|
||||
// 租户 B 通过 ID 查询租户 A 的患者应返回 PatientNotFound
|
||||
let lookup_result = patient_service::get_patient(&state, tenant_b, patient_a.id).await;
|
||||
assert!(
|
||||
lookup_result.is_err(),
|
||||
"跨租户查询应返回错误"
|
||||
);
|
||||
assert!(lookup_result.is_err(), "跨租户查询应返回错误");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -214,27 +215,47 @@ async fn test_patient_update_and_optimistic_lock() {
|
||||
let tenant_id = uuid::Uuid::new_v4();
|
||||
let operator_id = uuid::Uuid::new_v4();
|
||||
|
||||
let patient = patient_service::create_patient(&state, tenant_id, Some(operator_id), CreatePatientReq {
|
||||
name: "更新前".to_string(),
|
||||
gender: Some("male".to_string()),
|
||||
birth_date: None, blood_type: None, id_number: None,
|
||||
allergy_history: None, medical_history_summary: None,
|
||||
emergency_contact_name: None, emergency_contact_phone: None,
|
||||
source: None, notes: None,
|
||||
})
|
||||
let patient = patient_service::create_patient(
|
||||
&state,
|
||||
tenant_id,
|
||||
Some(operator_id),
|
||||
CreatePatientReq {
|
||||
name: "更新前".to_string(),
|
||||
gender: Some("male".to_string()),
|
||||
birth_date: None,
|
||||
blood_type: None,
|
||||
id_number: None,
|
||||
allergy_history: None,
|
||||
medical_history_summary: None,
|
||||
emergency_contact_name: None,
|
||||
emergency_contact_phone: None,
|
||||
source: None,
|
||||
notes: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("创建应成功");
|
||||
|
||||
// 正确版本更新
|
||||
let updated = patient_service::update_patient(
|
||||
&state, tenant_id, patient.id, Some(operator_id),
|
||||
&state,
|
||||
tenant_id,
|
||||
patient.id,
|
||||
Some(operator_id),
|
||||
UpdatePatientReq {
|
||||
name: Some("更新后".to_string()),
|
||||
gender: None, birth_date: None, blood_type: None,
|
||||
id_number: None, allergy_history: None,
|
||||
medical_history_summary: None, emergency_contact_name: None,
|
||||
emergency_contact_phone: None, source: None, notes: None,
|
||||
status: None, verification_status: None,
|
||||
gender: None,
|
||||
birth_date: None,
|
||||
blood_type: None,
|
||||
id_number: None,
|
||||
allergy_history: None,
|
||||
medical_history_summary: None,
|
||||
emergency_contact_name: None,
|
||||
emergency_contact_phone: None,
|
||||
source: None,
|
||||
notes: None,
|
||||
status: None,
|
||||
verification_status: None,
|
||||
},
|
||||
patient.version,
|
||||
)
|
||||
@@ -245,14 +266,24 @@ async fn test_patient_update_and_optimistic_lock() {
|
||||
|
||||
// 旧版本更新应失败
|
||||
let result = patient_service::update_patient(
|
||||
&state, tenant_id, patient.id, Some(operator_id),
|
||||
&state,
|
||||
tenant_id,
|
||||
patient.id,
|
||||
Some(operator_id),
|
||||
UpdatePatientReq {
|
||||
name: Some("冲突".to_string()),
|
||||
gender: None, birth_date: None, blood_type: None,
|
||||
id_number: None, allergy_history: None,
|
||||
medical_history_summary: None, emergency_contact_name: None,
|
||||
emergency_contact_phone: None, source: None, notes: None,
|
||||
status: None, verification_status: None,
|
||||
gender: None,
|
||||
birth_date: None,
|
||||
blood_type: None,
|
||||
id_number: None,
|
||||
allergy_history: None,
|
||||
medical_history_summary: None,
|
||||
emergency_contact_name: None,
|
||||
emergency_contact_phone: None,
|
||||
source: None,
|
||||
notes: None,
|
||||
status: None,
|
||||
verification_status: None,
|
||||
},
|
||||
patient.version, // 旧版本
|
||||
)
|
||||
@@ -266,17 +297,24 @@ async fn test_patient_pii_encrypted() {
|
||||
let state = make_state(test_db.db());
|
||||
let tenant_id = uuid::Uuid::new_v4();
|
||||
|
||||
let patient = patient_service::create_patient(&state, tenant_id, None, CreatePatientReq {
|
||||
name: "加密患者".to_string(),
|
||||
gender: None,
|
||||
birth_date: None, blood_type: None,
|
||||
id_number: Some("330102199001011234".to_string()),
|
||||
allergy_history: Some("花粉过敏".to_string()),
|
||||
medical_history_summary: Some("高血压".to_string()),
|
||||
emergency_contact_name: Some("王五".to_string()),
|
||||
emergency_contact_phone: Some("13900139000".to_string()),
|
||||
source: None, notes: None,
|
||||
})
|
||||
let patient = patient_service::create_patient(
|
||||
&state,
|
||||
tenant_id,
|
||||
None,
|
||||
CreatePatientReq {
|
||||
name: "加密患者".to_string(),
|
||||
gender: None,
|
||||
birth_date: None,
|
||||
blood_type: None,
|
||||
id_number: Some("330102199001011234".to_string()),
|
||||
allergy_history: Some("花粉过敏".to_string()),
|
||||
medical_history_summary: Some("高血压".to_string()),
|
||||
emergency_contact_name: Some("王五".to_string()),
|
||||
emergency_contact_phone: Some("13900139000".to_string()),
|
||||
source: None,
|
||||
notes: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("创建应成功");
|
||||
|
||||
@@ -300,20 +338,32 @@ async fn test_patient_search_by_name() {
|
||||
let tenant_id = uuid::Uuid::new_v4();
|
||||
|
||||
for name in &["赵一", "钱二", "孙三"] {
|
||||
patient_service::create_patient(&state, tenant_id, None, CreatePatientReq {
|
||||
name: name.to_string(),
|
||||
gender: None, birth_date: None, blood_type: None, id_number: None,
|
||||
allergy_history: None, medical_history_summary: None,
|
||||
emergency_contact_name: None, emergency_contact_phone: None,
|
||||
source: None, notes: None,
|
||||
})
|
||||
patient_service::create_patient(
|
||||
&state,
|
||||
tenant_id,
|
||||
None,
|
||||
CreatePatientReq {
|
||||
name: name.to_string(),
|
||||
gender: None,
|
||||
birth_date: None,
|
||||
blood_type: None,
|
||||
id_number: None,
|
||||
allergy_history: None,
|
||||
medical_history_summary: None,
|
||||
emergency_contact_name: None,
|
||||
emergency_contact_phone: None,
|
||||
source: None,
|
||||
notes: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let result = patient_service::list_patients(&state, tenant_id, 1, 10, Some("钱".to_string()), None)
|
||||
.await
|
||||
.expect("搜索应成功");
|
||||
let result =
|
||||
patient_service::list_patients(&state, tenant_id, 1, 10, Some("钱".to_string()), None)
|
||||
.await
|
||||
.expect("搜索应成功");
|
||||
assert_eq!(result.total, 1);
|
||||
assert_eq!(result.data[0].name, "钱二");
|
||||
}
|
||||
|
||||
@@ -68,17 +68,11 @@ async fn test_patient_tier1_fields_encrypted_in_db() {
|
||||
let stored_id = row.id_number.as_deref().unwrap_or("");
|
||||
assert_ne!(stored_id, "110101199001151234", "身份证号不应以明文存储");
|
||||
// AES-GCM 输出为 Base64,不应有中文
|
||||
assert!(
|
||||
!stored_id.contains("1101"),
|
||||
"密文不应包含身份证号片段"
|
||||
);
|
||||
assert!(!stored_id.contains("1101"), "密文不应包含身份证号片段");
|
||||
|
||||
// allergy_history 同理
|
||||
let stored_allergy = row.allergy_history.as_deref().unwrap_or("");
|
||||
assert!(
|
||||
!stored_allergy.contains("青霉素"),
|
||||
"过敏史不应以明文存储"
|
||||
);
|
||||
assert!(!stored_allergy.contains("青霉素"), "过敏史不应以明文存储");
|
||||
}
|
||||
|
||||
// ── 2. Patient: 详情接口返回解密明文 ──
|
||||
@@ -140,14 +134,8 @@ async fn test_patient_list_hides_tier1_fields() {
|
||||
|
||||
assert_eq!(list.data.len(), 1);
|
||||
let item = &list.data[0];
|
||||
assert!(
|
||||
item.id_number.is_none(),
|
||||
"列表不应返回身份证号"
|
||||
);
|
||||
assert!(
|
||||
item.allergy_history.is_none(),
|
||||
"列表不应返回过敏史"
|
||||
);
|
||||
assert!(item.id_number.is_none(), "列表不应返回身份证号");
|
||||
assert!(item.allergy_history.is_none(), "列表不应返回过敏史");
|
||||
assert!(
|
||||
item.medical_history_summary.is_none(),
|
||||
"列表不应返回病史摘要"
|
||||
@@ -409,14 +397,8 @@ async fn test_follow_up_record_fields_encrypted() {
|
||||
|
||||
// API 应返回解密后的明文
|
||||
assert_eq!(record.result, "随访结果:病情稳定");
|
||||
assert_eq!(
|
||||
record.patient_condition.as_deref(),
|
||||
Some("血压控制良好")
|
||||
);
|
||||
assert_eq!(
|
||||
record.medical_advice.as_deref(),
|
||||
Some("继续服药,定期复查")
|
||||
);
|
||||
assert_eq!(record.patient_condition.as_deref(), Some("血压控制良好"));
|
||||
assert_eq!(record.medical_advice.as_deref(), Some("继续服药,定期复查"));
|
||||
|
||||
// DB 中应为密文
|
||||
let row: Option<erp_health::entity::follow_up_record::Model> =
|
||||
@@ -489,7 +471,11 @@ async fn test_family_member_phone_encrypted_and_masked() {
|
||||
.await
|
||||
.expect("DB 查询应成功");
|
||||
let row = row.expect("应找到记录");
|
||||
assert_ne!(row.phone.as_deref(), Some("13987654321"), "DB 中 phone 应为密文");
|
||||
assert_ne!(
|
||||
row.phone.as_deref(),
|
||||
Some("13987654321"),
|
||||
"DB 中 phone 应为密文"
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -20,9 +20,14 @@ async fn seed_rule(app: &TestApp, event_type: &str, points_value: i32) -> Points
|
||||
streak_14d_bonus: 20,
|
||||
streak_30d_bonus: 50,
|
||||
};
|
||||
points_service::create_rule(app.health_state(), app.tenant_id(), Some(app.operator_id()), req)
|
||||
.await
|
||||
.expect("创建规则应成功")
|
||||
points_service::create_rule(
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
.expect("创建规则应成功")
|
||||
}
|
||||
|
||||
/// 创建测试用商品(无限库存)
|
||||
@@ -37,9 +42,14 @@ async fn seed_product(app: &TestApp, name: &str, points_cost: i32) -> PointsProd
|
||||
service_config: None,
|
||||
sort_order: None,
|
||||
};
|
||||
points_service::create_product(app.health_state(), app.tenant_id(), Some(app.operator_id()), req)
|
||||
.await
|
||||
.expect("创建商品应成功")
|
||||
points_service::create_product(
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
Some(app.operator_id()),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
.expect("创建商品应成功")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -53,7 +63,10 @@ async fn test_points_earn_sign_in() {
|
||||
|
||||
// 首次签到
|
||||
let result = points_service::daily_checkin(
|
||||
app.health_state(), app.tenant_id(), patient_id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.expect("签到应成功");
|
||||
@@ -77,7 +90,11 @@ async fn test_points_earn_custom() {
|
||||
seed_rule(&app, "custom_event", 20).await;
|
||||
|
||||
let tx = points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "custom_event", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"custom_event",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.expect("赚取积分应成功");
|
||||
@@ -100,20 +117,32 @@ async fn test_points_consume_fifo_deduction() {
|
||||
|
||||
// 赚两笔: 10 + 30 = 40
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "earn_a", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"earn_a",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "earn_b", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"earn_b",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 消费 25: FIFO 先消耗第一笔 10(全部用完),再从第二笔消耗 15(剩 15)
|
||||
let order = points_service::exchange_product(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
ExchangeReq { product_id: product.id },
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
ExchangeReq {
|
||||
product_id: product.id,
|
||||
},
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
@@ -141,15 +170,23 @@ async fn test_points_consume_balance_insufficient() {
|
||||
|
||||
// 只赚 5 分
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "small_earn", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"small_earn",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 消费 100 应失败
|
||||
let result = points_service::exchange_product(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
ExchangeReq { product_id: product.id },
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
ExchangeReq {
|
||||
product_id: product.id,
|
||||
},
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await;
|
||||
@@ -168,14 +205,22 @@ async fn test_points_consume_exact_balance() {
|
||||
let product = seed_product(&app, "等价商品", 50).await;
|
||||
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "exact_earn", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"exact_earn",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _order = points_service::exchange_product(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
ExchangeReq { product_id: product.id },
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
ExchangeReq {
|
||||
product_id: product.id,
|
||||
},
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
@@ -198,14 +243,22 @@ async fn test_points_consume_partial() {
|
||||
let product = seed_product(&app, "小商品", 30).await;
|
||||
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "big_earn", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"big_earn",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _order = points_service::exchange_product(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
ExchangeReq { product_id: product.id },
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
ExchangeReq {
|
||||
product_id: product.id,
|
||||
},
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
@@ -228,7 +281,11 @@ async fn test_points_account_create_on_first_earn() {
|
||||
|
||||
// earn_points 应自动创建账户
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "first_earn", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"first_earn",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -250,17 +307,19 @@ async fn test_points_checkin_streak() {
|
||||
seed_rule(&app, "daily_checkin", 5).await;
|
||||
|
||||
// 连续签到 3 天验证 consecutive_days 递增
|
||||
let status = points_service::get_checkin_status(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
)
|
||||
.await
|
||||
.expect("查询签到状态应成功");
|
||||
let status =
|
||||
points_service::get_checkin_status(app.health_state(), app.tenant_id(), patient_id)
|
||||
.await
|
||||
.expect("查询签到状态应成功");
|
||||
assert!(!status.checked_in_today);
|
||||
assert_eq!(status.consecutive_days, 0);
|
||||
|
||||
// 第 1 天签到
|
||||
let result = points_service::daily_checkin(
|
||||
app.health_state(), app.tenant_id(), patient_id, Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -279,14 +338,22 @@ async fn test_points_order_create() {
|
||||
let product = seed_product(&app, "兑换商品", 50).await;
|
||||
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "order_earn", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"order_earn",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let order = points_service::exchange_product(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
ExchangeReq { product_id: product.id },
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
ExchangeReq {
|
||||
product_id: product.id,
|
||||
},
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
@@ -308,14 +375,22 @@ async fn test_points_order_insufficient_cancel() {
|
||||
let product = seed_product(&app, "昂贵商品", 999).await;
|
||||
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "tiny_earn", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"tiny_earn",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = points_service::exchange_product(
|
||||
app.health_state(), app.tenant_id(), patient_id,
|
||||
ExchangeReq { product_id: product.id },
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
ExchangeReq {
|
||||
product_id: product.id,
|
||||
},
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await;
|
||||
@@ -334,21 +409,28 @@ async fn test_points_transaction_history() {
|
||||
|
||||
// 赚两笔
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "history_earn", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"history_earn",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_id, "history_earn", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_id,
|
||||
"history_earn",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let history = points_service::list_transactions(
|
||||
app.health_state(), app.tenant_id(), patient_id, 1, 20,
|
||||
)
|
||||
.await
|
||||
.expect("查询记录应成功");
|
||||
let history =
|
||||
points_service::list_transactions(app.health_state(), app.tenant_id(), patient_id, 1, 20)
|
||||
.await
|
||||
.expect("查询记录应成功");
|
||||
|
||||
assert_eq!(history.total, 2);
|
||||
assert_eq!(history.data.len(), 2);
|
||||
@@ -364,7 +446,11 @@ async fn test_points_tenant_isolation() {
|
||||
seed_rule(&app, "iso_earn", 50).await;
|
||||
|
||||
points_service::earn_points(
|
||||
app.health_state(), app.tenant_id(), patient_a, "iso_earn", Some(app.operator_id()),
|
||||
app.health_state(),
|
||||
app.tenant_id(),
|
||||
patient_a,
|
||||
"iso_earn",
|
||||
Some(app.operator_id()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -132,7 +132,8 @@ async fn test_dynamic_table_create_and_query() {
|
||||
"sort_order": 1
|
||||
});
|
||||
|
||||
let (sql, values) = DynamicTableManager::build_insert_sql(&table_name, tenant_id, user_id, &data);
|
||||
let (sql, values) =
|
||||
DynamicTableManager::build_insert_sql(&table_name, tenant_id, user_id, &data);
|
||||
db.execute(sea_orm::Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
sql,
|
||||
@@ -182,18 +183,31 @@ async fn test_tenant_isolation_in_dynamic_table() {
|
||||
|
||||
// 租户 A 插入数据
|
||||
let data_a = serde_json::json!({"code": "A001", "name": "租户A数据", "status": "active", "sort_order": 1});
|
||||
let (sql, values) = DynamicTableManager::build_insert_sql(&table_name, tenant_a, user_id, &data_a);
|
||||
let (sql, values) =
|
||||
DynamicTableManager::build_insert_sql(&table_name, tenant_a, user_id, &data_a);
|
||||
db.execute(sea_orm::Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres, sql, values,
|
||||
)).await.unwrap();
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
sql,
|
||||
values,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 租户 B 查询不应看到租户 A 的数据
|
||||
let (sql, values) = DynamicTableManager::build_query_sql(&table_name, tenant_b, 10, 0);
|
||||
#[derive(FromQueryResult)]
|
||||
struct Row { id: uuid::Uuid, data: serde_json::Value }
|
||||
struct Row {
|
||||
id: uuid::Uuid,
|
||||
data: serde_json::Value,
|
||||
}
|
||||
let rows = Row::find_by_statement(sea_orm::Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres, sql, values,
|
||||
)).all(db).await.unwrap();
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
sql,
|
||||
values,
|
||||
))
|
||||
.all(db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(rows.is_empty(), "租户 B 不应看到租户 A 的数据");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use sea_orm::{Database, ConnectionTrait, Statement, DatabaseBackend};
|
||||
use sea_orm::{ConnectionTrait, Database, DatabaseBackend, Statement};
|
||||
use std::sync::Arc;
|
||||
|
||||
use erp_server_migration::MigratorTrait;
|
||||
@@ -22,7 +22,11 @@ pub struct TestDb {
|
||||
|
||||
impl TestDb {
|
||||
pub async fn new() -> Self {
|
||||
let permit = db_semaphore().clone().acquire_owned().await.expect("信号量获取失败");
|
||||
let permit = db_semaphore()
|
||||
.clone()
|
||||
.acquire_owned()
|
||||
.await
|
||||
.expect("信号量获取失败");
|
||||
|
||||
let db_name = format!("erp_test_{}", uuid::Uuid::now_v7().simple());
|
||||
|
||||
@@ -58,7 +62,11 @@ impl TestDb {
|
||||
.await
|
||||
.expect("执行数据库迁移失败");
|
||||
|
||||
Self { db: Some(db), db_name, _permit: Some(permit) }
|
||||
Self {
|
||||
db: Some(db),
|
||||
db_name,
|
||||
_permit: Some(permit),
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取数据库连接引用
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use chrono::{NaiveDate, NaiveTime};
|
||||
use erp_core::crypto::PiiCrypto;
|
||||
use erp_core::events::EventBus;
|
||||
use erp_dialysis::state::DialysisState;
|
||||
use erp_health::dto::appointment_dto::{CreateAppointmentReq, CreateScheduleReq};
|
||||
use erp_health::dto::doctor_dto::CreateDoctorReq;
|
||||
use erp_health::dto::patient_dto::CreatePatientReq;
|
||||
use erp_health::service::{appointment_service, doctor_service, patient_service};
|
||||
use erp_health::state::HealthState;
|
||||
use erp_dialysis::state::DialysisState;
|
||||
|
||||
use super::test_db::TestDb;
|
||||
|
||||
@@ -78,7 +78,10 @@ impl TestApp {
|
||||
notes: None,
|
||||
};
|
||||
let patient = patient_service::create_patient(
|
||||
self.health_state(), self.tenant_id, Some(self.operator_id), req,
|
||||
self.health_state(),
|
||||
self.tenant_id,
|
||||
Some(self.operator_id),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
.expect("创建患者应成功");
|
||||
@@ -96,18 +99,17 @@ impl TestApp {
|
||||
bio: None,
|
||||
};
|
||||
let doctor = doctor_service::create_doctor(
|
||||
self.health_state(), self.tenant_id, Some(self.operator_id), req,
|
||||
self.health_state(),
|
||||
self.tenant_id,
|
||||
Some(self.operator_id),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
.expect("创建医护档案应成功");
|
||||
doctor.id
|
||||
}
|
||||
|
||||
pub async fn create_schedule(
|
||||
&self,
|
||||
doctor_id: uuid::Uuid,
|
||||
date: NaiveDate,
|
||||
) -> uuid::Uuid {
|
||||
pub async fn create_schedule(&self, doctor_id: uuid::Uuid, date: NaiveDate) -> uuid::Uuid {
|
||||
let req = CreateScheduleReq {
|
||||
doctor_id,
|
||||
schedule_date: date,
|
||||
@@ -117,7 +119,10 @@ impl TestApp {
|
||||
max_appointments: 10,
|
||||
};
|
||||
let schedule = appointment_service::create_schedule(
|
||||
self.health_state(), self.tenant_id, Some(self.operator_id), req,
|
||||
self.health_state(),
|
||||
self.tenant_id,
|
||||
Some(self.operator_id),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
.expect("创建排班应成功");
|
||||
@@ -140,7 +145,10 @@ impl TestApp {
|
||||
notes: Some("测试预约".to_string()),
|
||||
};
|
||||
let appt = appointment_service::create_appointment(
|
||||
self.health_state(), self.tenant_id, Some(self.operator_id), req,
|
||||
self.health_state(),
|
||||
self.tenant_id,
|
||||
Some(self.operator_id),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
.expect("创建预约应成功");
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use erp_core::events::EventBus;
|
||||
use erp_core::types::Pagination;
|
||||
use erp_workflow::dto::{
|
||||
CompleteTaskReq, CreateProcessDefinitionReq, EdgeDef, NodeDef, NodeType,
|
||||
StartInstanceReq,
|
||||
CompleteTaskReq, CreateProcessDefinitionReq, EdgeDef, NodeDef, NodeType, StartInstanceReq,
|
||||
};
|
||||
use erp_workflow::service::definition_service::DefinitionService;
|
||||
use erp_workflow::service::instance_service::InstanceService;
|
||||
@@ -12,7 +11,11 @@ use super::test_db::TestDb;
|
||||
|
||||
/// 构建一个最简单的线性流程:开始 → 审批 → 结束
|
||||
/// assignee 指向 operator_id,使 list_pending 能查到任务
|
||||
fn make_simple_definition(name: &str, key: &str, assignee_id: Option<uuid::Uuid>) -> CreateProcessDefinitionReq {
|
||||
fn make_simple_definition(
|
||||
name: &str,
|
||||
key: &str,
|
||||
assignee_id: Option<uuid::Uuid>,
|
||||
) -> CreateProcessDefinitionReq {
|
||||
CreateProcessDefinitionReq {
|
||||
name: name.to_string(),
|
||||
key: key.to_string(),
|
||||
@@ -232,11 +235,8 @@ async fn test_event_bus_pub_sub() {
|
||||
);
|
||||
event_bus.broadcast(event);
|
||||
|
||||
let other_event = erp_core::events::DomainEvent::new(
|
||||
"workflow.started",
|
||||
tenant_id,
|
||||
serde_json::json!({}),
|
||||
);
|
||||
let other_event =
|
||||
erp_core::events::DomainEvent::new("workflow.started", tenant_id, serde_json::json!({}));
|
||||
event_bus.broadcast(other_event);
|
||||
|
||||
let received = receiver.recv().await;
|
||||
|
||||
Reference in New Issue
Block a user