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