test(message): erp-message 从 45 增至 69 个单元测试 — DND 时间窗 + TransactionError + model_to_resp
- module.rs: 提取 is_in_dnd_window 纯函数 + 14 个 DND 时间窗测试(正常范围/跨午夜/边界) - error.rs: 2 个 TransactionError 转换测试(Connection/Transaction) - message_service: 2 个 model_to_resp 字段映射测试 - template_service: 1 个 model_to_resp 字段映射测试 - subscription_service: 1 个 model_to_resp 字段映射测试
This commit is contained in:
@@ -117,4 +117,28 @@ mod tests {
|
||||
"版本冲突: 数据已被其他操作修改,请刷新后重试"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transaction_connection_error_maps_to_validation() {
|
||||
let db_err = sea_orm::DbErr::Conn(sea_orm::RuntimeErr::Internal("连接超时".to_string()));
|
||||
let tx_err: sea_orm::TransactionError<MessageError> =
|
||||
sea_orm::TransactionError::Connection(db_err);
|
||||
let msg_err: MessageError = tx_err.into();
|
||||
match msg_err {
|
||||
MessageError::Validation(msg) => assert!(msg.contains("连接超时")),
|
||||
other => panic!("期望 Validation,得到 {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transaction_inner_error_passthrough() {
|
||||
let inner = MessageError::NotFound("模板不存在".to_string());
|
||||
let tx_err: sea_orm::TransactionError<MessageError> =
|
||||
sea_orm::TransactionError::Transaction(inner);
|
||||
let msg_err: MessageError = tx_err.into();
|
||||
match msg_err {
|
||||
MessageError::NotFound(msg) => assert_eq!(msg, "模板不存在"),
|
||||
other => panic!("期望 NotFound,得到 {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,11 @@ async fn should_skip_for_dnd(
|
||||
};
|
||||
let now = chrono::Local::now();
|
||||
let now_time = now.format("%H:%M").to_string();
|
||||
// DND 窗口比较(支持跨午夜,如 22:00-08:00)
|
||||
is_in_dnd_window(&now_time, &start, &end)
|
||||
}
|
||||
|
||||
/// 判断当前时间是否在 DND 窗口内。支持跨午夜窗口(如 22:00-06:00)。
|
||||
pub(crate) fn is_in_dnd_window(now_time: &str, start: &str, end: &str) -> bool {
|
||||
if start <= end {
|
||||
now_time >= start && now_time < end
|
||||
} else {
|
||||
@@ -602,3 +606,89 @@ async fn handle_workflow_event(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// ---- DND 时间窗逻辑 ----
|
||||
|
||||
#[test]
|
||||
fn dnd_normal_range_inside() {
|
||||
// 09:00-17:00,当前 12:00 → 在窗口内
|
||||
assert!(is_in_dnd_window("12:00", "09:00", "17:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_normal_range_before() {
|
||||
// 09:00-17:00,当前 08:00 → 不在窗口内
|
||||
assert!(!is_in_dnd_window("08:00", "09:00", "17:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_normal_range_after() {
|
||||
// 09:00-17:00,当前 18:00 → 不在窗口内
|
||||
assert!(!is_in_dnd_window("18:00", "09:00", "17:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_normal_range_at_start() {
|
||||
// 09:00-17:00,当前 09:00 → 在窗口内(>= start)
|
||||
assert!(is_in_dnd_window("09:00", "09:00", "17:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_normal_range_at_end() {
|
||||
// 09:00-17:00,当前 17:00 → 不在窗口内(< end 排除了 end 本身)
|
||||
assert!(!is_in_dnd_window("17:00", "09:00", "17:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_cross_midnight_night_time() {
|
||||
// 22:00-06:00,当前 23:30 → 在窗口内
|
||||
assert!(is_in_dnd_window("23:30", "22:00", "06:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_cross_midnight_early_morning() {
|
||||
// 22:00-06:00,当前 03:00 → 在窗口内
|
||||
assert!(is_in_dnd_window("03:00", "22:00", "06:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_cross_midnight_daytime() {
|
||||
// 22:00-06:00,当前 14:00 → 不在窗口内
|
||||
assert!(!is_in_dnd_window("14:00", "22:00", "06:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_cross_midnight_at_start() {
|
||||
assert!(is_in_dnd_window("22:00", "22:00", "06:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_cross_midnight_at_end() {
|
||||
assert!(!is_in_dnd_window("06:00", "22:00", "06:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_cross_midnight_just_before_end() {
|
||||
assert!(is_in_dnd_window("05:59", "22:00", "06:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_same_start_end_always_in() {
|
||||
// start == end 意味着 start <= end,所以 now >= start && now < end
|
||||
// "00:00" >= "00:00" && "00:00" < "00:00" → false
|
||||
assert!(!is_in_dnd_window("00:00", "12:00", "12:00"));
|
||||
// "15:00" >= "12:00" && "15:00" < "12:00" → false
|
||||
assert!(!is_in_dnd_window("15:00", "12:00", "12:00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnd_single_minute_window() {
|
||||
// 23:59-00:00(跨午夜 1 分钟)
|
||||
assert!(is_in_dnd_window("23:59", "23:59", "00:00"));
|
||||
assert!(!is_in_dnd_window("00:00", "23:59", "00:00"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,7 +488,7 @@ impl MessageService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn model_to_resp(m: &message::Model) -> MessageResp {
|
||||
pub(crate) fn model_to_resp(m: &message::Model) -> MessageResp {
|
||||
MessageResp {
|
||||
id: m.id,
|
||||
tenant_id: m.tenant_id,
|
||||
@@ -513,3 +513,63 @@ impl MessageService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::Utc;
|
||||
|
||||
fn sample_model() -> message::Model {
|
||||
message::Model {
|
||||
id: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
tenant_id: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
|
||||
template_id: None,
|
||||
sender_id: None,
|
||||
sender_type: "system".to_string(),
|
||||
recipient_id: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
|
||||
recipient_type: "user".to_string(),
|
||||
title: "测试消息".to_string(),
|
||||
body: "消息内容".to_string(),
|
||||
priority: "normal".to_string(),
|
||||
business_type: None,
|
||||
business_id: None,
|
||||
is_read: false,
|
||||
read_at: None,
|
||||
is_archived: false,
|
||||
archived_at: None,
|
||||
sent_at: None,
|
||||
status: "sent".to_string(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
created_by: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000004").unwrap(),
|
||||
updated_by: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000004").unwrap(),
|
||||
deleted_at: None,
|
||||
version: 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_to_resp_maps_all_fields() {
|
||||
let m = sample_model();
|
||||
let resp = MessageService::model_to_resp(&m);
|
||||
assert_eq!(resp.id, m.id);
|
||||
assert_eq!(resp.tenant_id, m.tenant_id);
|
||||
assert_eq!(resp.title, "测试消息");
|
||||
assert_eq!(resp.body, "消息内容");
|
||||
assert_eq!(resp.priority, "normal");
|
||||
assert_eq!(resp.is_read, false);
|
||||
assert_eq!(resp.status, "sent");
|
||||
assert_eq!(resp.version, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_to_resp_preserves_optional_fields() {
|
||||
let m = sample_model();
|
||||
let resp = MessageService::model_to_resp(&m);
|
||||
assert_eq!(resp.template_id, None);
|
||||
assert_eq!(resp.sender_id, None);
|
||||
assert_eq!(resp.business_type, None);
|
||||
assert_eq!(resp.read_at, None);
|
||||
assert_eq!(resp.sent_at, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ impl SubscriptionService {
|
||||
}
|
||||
}
|
||||
|
||||
fn model_to_resp(m: &message_subscription::Model) -> MessageSubscriptionResp {
|
||||
pub(crate) fn model_to_resp(m: &message_subscription::Model) -> MessageSubscriptionResp {
|
||||
MessageSubscriptionResp {
|
||||
id: m.id,
|
||||
tenant_id: m.tenant_id,
|
||||
@@ -121,3 +121,35 @@ impl SubscriptionService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::Utc;
|
||||
|
||||
#[test]
|
||||
fn model_to_resp_maps_all_fields() {
|
||||
let m = message_subscription::Model {
|
||||
id: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
tenant_id: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
|
||||
user_id: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
|
||||
notification_types: Some(serde_json::json!(["appointment"])),
|
||||
channel_preferences: Some(serde_json::json!(["in_app"])),
|
||||
dnd_enabled: true,
|
||||
dnd_start: Some("22:00".to_string()),
|
||||
dnd_end: Some("08:00".to_string()),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
created_by: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
|
||||
updated_by: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
|
||||
deleted_at: None,
|
||||
version: 1,
|
||||
};
|
||||
let resp = SubscriptionService::model_to_resp(&m);
|
||||
assert_eq!(resp.user_id, m.user_id);
|
||||
assert_eq!(resp.dnd_enabled, true);
|
||||
assert_eq!(resp.dnd_start, Some("22:00".to_string()));
|
||||
assert_eq!(resp.dnd_end, Some("08:00".to_string()));
|
||||
assert_eq!(resp.version, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ impl TemplateService {
|
||||
result
|
||||
}
|
||||
|
||||
fn model_to_resp(m: &message_template::Model) -> MessageTemplateResp {
|
||||
pub(crate) fn model_to_resp(m: &message_template::Model) -> MessageTemplateResp {
|
||||
MessageTemplateResp {
|
||||
id: m.id,
|
||||
tenant_id: m.tenant_id,
|
||||
@@ -267,4 +267,30 @@ mod tests {
|
||||
let result = TemplateService::render("你好 {{name}}", &vars);
|
||||
assert_eq!(result, "你好 赵六");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_to_resp_maps_all_fields() {
|
||||
let m = message_template::Model {
|
||||
id: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
tenant_id: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
|
||||
name: "欢迎消息".to_string(),
|
||||
code: "WELCOME".to_string(),
|
||||
channel: "in_app".to_string(),
|
||||
title_template: "欢迎 {{name}}".to_string(),
|
||||
body_template: "{{name}},欢迎使用".to_string(),
|
||||
language: "zh-CN".to_string(),
|
||||
created_at: chrono::Utc::now(),
|
||||
updated_at: chrono::Utc::now(),
|
||||
created_by: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
|
||||
updated_by: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
|
||||
deleted_at: None,
|
||||
version: 2,
|
||||
};
|
||||
let resp = TemplateService::model_to_resp(&m);
|
||||
assert_eq!(resp.name, "欢迎消息");
|
||||
assert_eq!(resp.code, "WELCOME");
|
||||
assert_eq!(resp.channel, "in_app");
|
||||
assert_eq!(resp.language, "zh-CN");
|
||||
assert_eq!(resp.version, 2);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user