Skip to main content

sz_orm_core/
behaviors.rs

1//! 行为系统(Behaviors)— 可插拔代码复用单元
2//!
3//! 对应文档 6.8 节改进项 34(Behaviors 行为系统)+ 35(自动填充时间戳)+ 36(自动填充操作人)。
4//!
5//! # 核心概念
6//!
7//! - **Behavior**:可插拔的代码复用单元,订阅一组生命周期事件并自动执行逻辑
8//! - **TimestampBehavior**:自动填充 `created_at`/`updated_at` 时间戳
9//! - **BlameableBehavior**:自动填充 `created_by`/`updated_by` 操作人 ID
10//! - **BehaviorRegistry**:Behavior 注册中心,管理多个 Behavior 的分发
11//!
12//! # 设计灵感
13//!
14//! - Yii2 `TimestampBehavior` / `BlameableBehavior` / `AttributeBehavior`
15//! - Hibernate `@CreationTimestamp` / `@UpdateTimestamp`
16//! - MyBatis-Plus `MetaObjectHandler`
17//!
18//! # 使用示例
19//!
20//! ```no_run
21//! use sz_orm_core::behaviors::{Behavior, TimestampBehavior, BlameableBehavior, BehaviorRegistry};
22//! use sz_orm_core::hooks::HookContext;
23//! use sz_orm_core::Value;
24//! use std::collections::HashMap;
25//!
26//! let mut registry = BehaviorRegistry::new();
27//! registry.register(Box::new(TimestampBehavior::new("created_at", "updated_at")));
28//! registry.register(Box::new(BlameableBehavior::new("created_by", "updated_by")));
29//!
30//! let ctx = HookContext::default().with_operator(42).with_timestamp(1700000000);
31//! let mut attrs = HashMap::new();
32//! registry.before_insert(&ctx, &mut attrs).unwrap();
33//! assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
34//! assert_eq!(attrs.get("created_by"), Some(&Value::I64(42)));
35//! ```
36
37use crate::error::DbError;
38use crate::hooks::HookContext;
39use crate::Value;
40use std::collections::HashMap;
41use parking_lot::RwLock;
42
43/// Behavior 处理结果
44pub type BehaviorResult<T> = Result<T, DbError>;
45
46/// 行为 trait — 可插拔代码复用单元
47///
48/// 每个 Behavior 订阅一组生命周期事件,在事件触发时自动执行逻辑。
49/// 默认所有方法都是空实现,Behavior 只需重写关心的方法。
50pub trait Behavior: Send + Sync {
51    /// Behavior 名称(用于识别、去重、调试)
52    fn name(&self) -> &'static str;
53
54    /// 在 insert 前触发(默认空实现)
55    fn before_insert(
56        &self,
57        _ctx: &HookContext,
58        _attrs: &mut HashMap<String, Value>,
59    ) -> BehaviorResult<()> {
60        Ok(())
61    }
62
63    /// 在 update 前触发(默认空实现)
64    fn before_update(
65        &self,
66        _ctx: &HookContext,
67        _attrs: &mut HashMap<String, Value>,
68    ) -> BehaviorResult<()> {
69        Ok(())
70    }
71
72    /// 在 delete 前触发(默认空实现)
73    fn before_delete(
74        &self,
75        _ctx: &HookContext,
76        _attrs: &mut HashMap<String, Value>,
77    ) -> BehaviorResult<()> {
78        Ok(())
79    }
80
81    /// 在 find 后触发(默认空实现,可用于字段后处理)
82    fn after_find(
83        &self,
84        _ctx: &HookContext,
85        _attrs: &mut HashMap<String, Value>,
86    ) -> BehaviorResult<()> {
87        Ok(())
88    }
89}
90
91// ============================================================================
92// TimestampBehavior — 自动填充时间戳
93// ============================================================================
94//
95// 对应:Yii2 `TimestampBehavior` / Hibernate `@CreationTimestamp`+`@UpdateTimestamp`
96// / MyBatis-Plus `MetaObjectHandler`
97//
98// - before_insert:填充 created_at + updated_at
99// - before_update:填充 updated_at
100//
101// 时间戳取自 HookContext.timestamp(Unix 微秒),由调用方保证一致性。
102
103/// 自动填充时间戳 Behavior
104///
105/// # 示例
106///
107/// ```
108/// use sz_orm_core::behaviors::{Behavior, TimestampBehavior};
109/// use sz_orm_core::hooks::HookContext;
110/// use sz_orm_core::Value;
111/// use std::collections::HashMap;
112///
113/// let b = TimestampBehavior::new("created_at", "updated_at");
114/// let ctx = HookContext::default().with_timestamp(1700000000);
115/// let mut attrs = HashMap::new();
116/// b.before_insert(&ctx, &mut attrs).unwrap();
117/// assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
118/// assert_eq!(attrs.get("updated_at"), Some(&Value::I64(1700000000)));
119/// ```
120pub struct TimestampBehavior {
121    /// 创建时间字段名(默认 "created_at")
122    pub created_field: &'static str,
123    /// 更新时间字段名(默认 "updated_at")
124    pub updated_field: &'static str,
125}
126
127impl TimestampBehavior {
128    /// 创建默认配置的 TimestampBehavior(字段名 created_at/updated_at)
129    pub fn new(created_field: &'static str, updated_field: &'static str) -> Self {
130        Self {
131            created_field,
132            updated_field,
133        }
134    }
135
136    /// 使用默认字段名(created_at/updated_at)
137    pub fn default_fields() -> Self {
138        Self::new("created_at", "updated_at")
139    }
140}
141
142impl Behavior for TimestampBehavior {
143    fn name(&self) -> &'static str {
144        "TimestampBehavior"
145    }
146
147    fn before_insert(
148        &self,
149        ctx: &HookContext,
150        attrs: &mut HashMap<String, Value>,
151    ) -> BehaviorResult<()> {
152        let ts = Value::I64(ctx.timestamp as i64);
153        attrs.insert(self.created_field.to_string(), ts.clone());
154        attrs.insert(self.updated_field.to_string(), ts);
155        Ok(())
156    }
157
158    fn before_update(
159        &self,
160        ctx: &HookContext,
161        attrs: &mut HashMap<String, Value>,
162    ) -> BehaviorResult<()> {
163        attrs.insert(
164            self.updated_field.to_string(),
165            Value::I64(ctx.timestamp as i64),
166        );
167        Ok(())
168    }
169}
170
171// ============================================================================
172// BlameableBehavior — 自动填充操作人
173// ============================================================================
174//
175// 对应:Yii2 `BlameableBehavior` / Spring Security `AuditorAware`
176//
177// - before_insert:填充 created_by + updated_by
178// - before_update:填充 updated_by
179//
180// 操作人 ID 取自 HookContext.operator_id。
181
182/// 自动填充操作人 Behavior
183///
184/// # 示例
185///
186/// ```
187/// use sz_orm_core::behaviors::{Behavior, BlameableBehavior};
188/// use sz_orm_core::hooks::HookContext;
189/// use sz_orm_core::Value;
190/// use std::collections::HashMap;
191///
192/// let b = BlameableBehavior::new("created_by", "updated_by");
193/// let ctx = HookContext::default().with_operator(42);
194/// let mut attrs = HashMap::new();
195/// b.before_insert(&ctx, &mut attrs).unwrap();
196/// assert_eq!(attrs.get("created_by"), Some(&Value::I64(42)));
197/// assert_eq!(attrs.get("updated_by"), Some(&Value::I64(42)));
198/// ```
199pub struct BlameableBehavior {
200    /// 创建人字段名(默认 "created_by")
201    pub created_field: &'static str,
202    /// 更新人字段名(默认 "updated_by")
203    pub updated_field: &'static str,
204}
205
206impl BlameableBehavior {
207    /// 创建 BlameableBehavior
208    pub fn new(created_field: &'static str, updated_field: &'static str) -> Self {
209        Self {
210            created_field,
211            updated_field,
212        }
213    }
214
215    /// 使用默认字段名(created_by/updated_by)
216    pub fn default_fields() -> Self {
217        Self::new("created_by", "updated_by")
218    }
219}
220
221impl Behavior for BlameableBehavior {
222    fn name(&self) -> &'static str {
223        "BlameableBehavior"
224    }
225
226    fn before_insert(
227        &self,
228        ctx: &HookContext,
229        attrs: &mut HashMap<String, Value>,
230    ) -> BehaviorResult<()> {
231        if let Some(op) = ctx.operator_id {
232            let v = Value::I64(op);
233            attrs.insert(self.created_field.to_string(), v.clone());
234            attrs.insert(self.updated_field.to_string(), v);
235        }
236        Ok(())
237    }
238
239    fn before_update(
240        &self,
241        ctx: &HookContext,
242        attrs: &mut HashMap<String, Value>,
243    ) -> BehaviorResult<()> {
244        if let Some(op) = ctx.operator_id {
245            attrs.insert(self.updated_field.to_string(), Value::I64(op));
246        }
247        Ok(())
248    }
249}
250
251// ============================================================================
252// TenantBehavior — 自动填充 tenant_id(S-3:SeaORM 对标短板补全)
253// ============================================================================
254//
255// 对应:Yii2 `TenantBehavior` / Laravel Tenancy `BootTenant`
256// / Hibernate `@TenantId`
257//
258// - before_insert:从 HookContext.tenant_id 读取租户 ID 填充到 attrs
259// - before_update:可选校验 tenant_id 不可变更(防跨租户篡改)
260//
261// 与 hooks::TenantScope(查询时自动追加 tenant_id = ? 过滤)配套,
262// 共同实现多租户隔离:写入侧由 TenantBehavior 填充,读取侧由 TenantScope 过滤。
263
264/// 租户隔离行为配置:是否在 update 时强制 tenant_id 不可变更
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub enum TenantUpdatePolicy {
267    /// 允许 update 时变更 tenant_id(不推荐,仅在特殊迁移场景使用)
268    Allow,
269    /// update 时若 attrs 中出现 tenant_id 且与 ctx.tenant_id 不一致则报错(默认)
270    DenyMismatch,
271    /// update 时静默忽略 attrs 中的 tenant_id(保持原值不变)
272    Strip,
273}
274
275impl Default for TenantUpdatePolicy {
276    fn default() -> Self {
277        TenantUpdatePolicy::DenyMismatch
278    }
279}
280
281/// 自动填充 tenant_id Behavior
282///
283/// # 工作机制
284///
285/// - `before_insert`:若 `ctx.tenant_id` 为 `Some(tid)`,将 `tid` 写入 `attrs[tenant_field]`;
286///   若 `ctx.tenant_id` 为 `None`,按 `skip_when_no_tenant` 配置决定是跳过还是报错。
287/// - `before_update`:根据 [`TenantUpdatePolicy`] 处理 attrs 中的 tenant_id:
288///   - `DenyMismatch`(默认):若 attrs 中 tenant_id 与 ctx.tenant_id 不一致则返回 `DbError::TenantError`
289///   - `Strip`:从 attrs 中移除 tenant_id(保证不被更新)
290///   - `Allow`:不做任何处理
291///
292/// # 示例
293///
294/// ```
295/// use sz_orm_core::behaviors::{TenantBehavior, TenantUpdatePolicy, Behavior};
296/// use sz_orm_core::hooks::HookContext;
297/// use sz_orm_core::Value;
298/// use std::collections::HashMap;
299///
300/// let b = TenantBehavior::default_fields();
301/// let ctx = HookContext::default().with_tenant(42);
302/// let mut attrs = HashMap::new();
303/// b.before_insert(&ctx, &mut attrs).unwrap();
304/// assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(42)));
305/// ```
306pub struct TenantBehavior {
307    /// 租户字段名(默认 "tenant_id")
308    pub tenant_field: &'static str,
309    /// update 时对 tenant_id 的处理策略
310    pub update_policy: TenantUpdatePolicy,
311    /// ctx.tenant_id 为 None 时的行为:
312    /// - true:跳过填充(不写入 tenant_id,允许跨租户写入)
313    /// - false:返回 TenantError
314    pub skip_when_no_tenant: bool,
315}
316
317impl TenantBehavior {
318    /// 创建 TenantBehavior
319    pub fn new(
320        tenant_field: &'static str,
321        update_policy: TenantUpdatePolicy,
322        skip_when_no_tenant: bool,
323    ) -> Self {
324        Self {
325            tenant_field,
326            update_policy,
327            skip_when_no_tenant,
328        }
329    }
330
331    /// 使用默认字段名(tenant_id)+ 默认策略(DenyMismatch + skip_when_no_tenant=true)
332    pub fn default_fields() -> Self {
333        Self::new("tenant_id", TenantUpdatePolicy::default(), true)
334    }
335
336    /// 设置 update 策略(builder 风格)
337    pub fn with_update_policy(mut self, policy: TenantUpdatePolicy) -> Self {
338        self.update_policy = policy;
339        self
340    }
341
342    /// 设置 ctx.tenant_id 为 None 时的行为(builder 风格)
343    pub fn with_skip_when_no_tenant(mut self, skip: bool) -> Self {
344        self.skip_when_no_tenant = skip;
345        self
346    }
347}
348
349impl Behavior for TenantBehavior {
350    fn name(&self) -> &'static str {
351        "TenantBehavior"
352    }
353
354    fn before_insert(
355        &self,
356        ctx: &HookContext,
357        attrs: &mut HashMap<String, Value>,
358    ) -> BehaviorResult<()> {
359        match ctx.tenant_id {
360            Some(tid) => {
361                attrs.insert(self.tenant_field.to_string(), Value::I64(tid));
362                Ok(())
363            }
364            None => {
365                if self.skip_when_no_tenant {
366                    Ok(())
367                } else {
368                    Err(DbError::TenantError(format!(
369                        "TenantBehavior::before_insert: ctx.tenant_id is None, \
370                         cannot auto-fill `{}`; set skip_when_no_tenant=true or \
371                         provide tenant_id in HookContext",
372                        self.tenant_field
373                    )))
374                }
375            }
376        }
377    }
378
379    fn before_update(
380        &self,
381        ctx: &HookContext,
382        attrs: &mut HashMap<String, Value>,
383    ) -> BehaviorResult<()> {
384        match self.update_policy {
385            TenantUpdatePolicy::Allow => Ok(()),
386            TenantUpdatePolicy::Strip => {
387                attrs.remove(self.tenant_field);
388                Ok(())
389            }
390            TenantUpdatePolicy::DenyMismatch => {
391                if let Some(existing) = attrs.get(self.tenant_field) {
392                    match (existing, ctx.tenant_id) {
393                        // ctx 中有 tenant_id:必须与 attrs 一致
394                        (Value::I64(a), Some(b)) if *a == b => Ok(()),
395                        (Value::I64(a), Some(b)) => Err(DbError::TenantError(format!(
396                            "TenantBehavior::before_update: tenant_id mismatch — \
397                             attrs.{}={}, ctx.tenant_id={}; update rejected to prevent \
398                             cross-tenant tampering",
399                            self.tenant_field, a, b
400                        ))),
401                        // ctx 中无 tenant_id:不允许显式更新 tenant_id
402                        (_, None) => Err(DbError::TenantError(format!(
403                            "TenantBehavior::before_update: attrs contains `{}` but \
404                             ctx.tenant_id is None; remove `{}` from update payload or \
405                             set ctx.tenant_id",
406                            self.tenant_field, self.tenant_field
407                        ))),
408                        // 非 I64 类型的 tenant_id 视为类型不匹配
409                        (other, _) => Err(DbError::TenantError(format!(
410                            "TenantBehavior::before_update: attrs.{} expected I64, got {:?}",
411                            self.tenant_field, other
412                        ))),
413                    }
414                } else {
415                    Ok(())
416                }
417            }
418        }
419    }
420}
421
422// ============================================================================
423// AttributeBehavior — 通用属性自动设置
424// ============================================================================
425//
426// 对应:Yii2 `AttributeBehavior`
427//
428// 允许用户注册自定义闭包,在指定事件触发时设置属性值。
429
430/// 通用属性 Behavior — 在指定事件触发时通过闭包设置属性
431///
432/// # 示例
433///
434/// ```
435/// use sz_orm_core::behaviors::{AttributeBehavior, BehaviorRegistry, Behavior};
436/// use sz_orm_core::hooks::{HookContext, HookEvent};
437/// use sz_orm_core::Value;
438/// use std::collections::HashMap;
439///
440/// let mut registry = BehaviorRegistry::new();
441/// // 在 before_insert 时设置 uuid 字段
442/// registry.register(Box::new(AttributeBehavior::new(
443///     "uuid_gen",
444///     HookEvent::BeforeInsert,
445///     "uuid",
446///     |_ctx| Value::String("auto-uuid".to_string()),
447/// )));
448///
449/// let ctx = HookContext::default();
450/// let mut attrs = HashMap::new();
451/// registry.before_insert(&ctx, &mut attrs).unwrap();
452/// assert_eq!(attrs.get("uuid"), Some(&Value::String("auto-uuid".to_string())));
453/// ```
454pub struct AttributeBehavior {
455    /// Behavior 名称
456    pub name_str: &'static str,
457    /// 订阅的事件(仅在该事件触发时执行)
458    pub event: crate::hooks::HookEvent,
459    /// 目标字段名
460    pub target_field: &'static str,
461    /// 值生成闭包
462    pub generator: Box<dyn Fn(&HookContext) -> Value + Send + Sync>,
463}
464
465impl AttributeBehavior {
466    /// 创建 AttributeBehavior
467    pub fn new(
468        name: &'static str,
469        event: crate::hooks::HookEvent,
470        target_field: &'static str,
471        generator: impl Fn(&HookContext) -> Value + Send + Sync + 'static,
472    ) -> Self {
473        Self {
474            name_str: name,
475            event,
476            target_field,
477            generator: Box::new(generator),
478        }
479    }
480}
481
482impl Behavior for AttributeBehavior {
483    fn name(&self) -> &'static str {
484        self.name_str
485    }
486
487    fn before_insert(
488        &self,
489        ctx: &HookContext,
490        attrs: &mut HashMap<String, Value>,
491    ) -> BehaviorResult<()> {
492        if self.event == crate::hooks::HookEvent::BeforeInsert
493            || self.event == crate::hooks::HookEvent::BeforeWrite
494            || self.event == crate::hooks::HookEvent::BeforeSave
495        {
496            let v = (self.generator)(ctx);
497            attrs.insert(self.target_field.to_string(), v);
498        }
499        Ok(())
500    }
501
502    fn before_update(
503        &self,
504        ctx: &HookContext,
505        attrs: &mut HashMap<String, Value>,
506    ) -> BehaviorResult<()> {
507        if self.event == crate::hooks::HookEvent::BeforeUpdate
508            || self.event == crate::hooks::HookEvent::BeforeWrite
509            || self.event == crate::hooks::HookEvent::BeforeSave
510        {
511            let v = (self.generator)(ctx);
512            attrs.insert(self.target_field.to_string(), v);
513        }
514        Ok(())
515    }
516
517    fn after_find(
518        &self,
519        ctx: &HookContext,
520        attrs: &mut HashMap<String, Value>,
521    ) -> BehaviorResult<()> {
522        if self.event == crate::hooks::HookEvent::AfterFind {
523            let v = (self.generator)(ctx);
524            attrs.insert(self.target_field.to_string(), v);
525        }
526        Ok(())
527    }
528}
529
530// ============================================================================
531// BehaviorRegistry — Behavior 注册中心
532// ============================================================================
533
534/// Behavior 注册中心 — 管理多个 Behavior 的注册与分发
535///
536/// 线程安全:内部使用 RwLock,可在多线程环境下共享。
537///
538/// # 示例
539///
540/// ```
541/// use sz_orm_core::behaviors::{BehaviorRegistry, TimestampBehavior, BlameableBehavior, Behavior};
542/// use sz_orm_core::hooks::HookContext;
543/// use sz_orm_core::Value;
544/// use std::collections::HashMap;
545///
546/// let mut registry = BehaviorRegistry::new();
547/// registry.register(Box::new(TimestampBehavior::default_fields()));
548/// registry.register(Box::new(BlameableBehavior::default_fields()));
549///
550/// let ctx = HookContext::default().with_operator(100).with_timestamp(1700000000);
551/// let mut attrs = HashMap::new();
552/// registry.before_insert(&ctx, &mut attrs).unwrap();
553/// assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
554/// assert_eq!(attrs.get("created_by"), Some(&Value::I64(100)));
555/// ```
556pub struct BehaviorRegistry {
557    behaviors: RwLock<Vec<Box<dyn Behavior>>>,
558}
559
560impl BehaviorRegistry {
561    /// 创建空的 BehaviorRegistry
562    pub fn new() -> Self {
563        Self {
564            behaviors: RwLock::new(Vec::new()),
565        }
566    }
567
568    /// 注册一个 Behavior
569    pub fn register(&self, behavior: Box<dyn Behavior>) {
570        let mut guards = self.behaviors.write();
571        guards.push(behavior);
572    }
573
574    /// 按 name 移除已注册的 Behavior
575    pub fn unregister(&self, name: &str) -> bool {
576        let mut guards = self.behaviors.write();
577        let before = guards.len();
578        guards.retain(|b| b.name() != name);
579        guards.len() < before
580    }
581
582    /// 已注册的 Behavior 数量
583    pub fn count(&self) -> usize {
584        self.behaviors.read().len()
585    }
586
587    /// 列出所有已注册 Behavior 的名称
588    pub fn names(&self) -> Vec<&'static str> {
589        self.behaviors
590            .read()
591            .iter()
592            .map(|b| b.name())
593            .collect()
594    }
595
596    /// 分发 before_insert 事件
597    pub fn before_insert(
598        &self,
599        ctx: &HookContext,
600        attrs: &mut HashMap<String, Value>,
601    ) -> BehaviorResult<()> {
602        let guards = self.behaviors.read();
603        for b in guards.iter() {
604            b.before_insert(ctx, attrs)?;
605        }
606        Ok(())
607    }
608
609    /// 分发 before_update 事件
610    pub fn before_update(
611        &self,
612        ctx: &HookContext,
613        attrs: &mut HashMap<String, Value>,
614    ) -> BehaviorResult<()> {
615        let guards = self.behaviors.read();
616        for b in guards.iter() {
617            b.before_update(ctx, attrs)?;
618        }
619        Ok(())
620    }
621
622    /// 分发 before_delete 事件
623    pub fn before_delete(
624        &self,
625        ctx: &HookContext,
626        attrs: &mut HashMap<String, Value>,
627    ) -> BehaviorResult<()> {
628        let guards = self.behaviors.read();
629        for b in guards.iter() {
630            b.before_delete(ctx, attrs)?;
631        }
632        Ok(())
633    }
634
635    /// 分发 after_find 事件
636    pub fn after_find(
637        &self,
638        ctx: &HookContext,
639        attrs: &mut HashMap<String, Value>,
640    ) -> BehaviorResult<()> {
641        let guards = self.behaviors.read();
642        for b in guards.iter() {
643            b.after_find(ctx, attrs)?;
644        }
645        Ok(())
646    }
647
648    /// 清空所有已注册的 Behavior
649    pub fn clear(&self) {
650        self.behaviors.write().clear();
651    }
652}
653
654impl Default for BehaviorRegistry {
655    fn default() -> Self {
656        Self::new()
657    }
658}
659
660// ============================================================================
661// 单元测试
662// ============================================================================
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667    use crate::hooks::HookEvent;
668
669    // ===== TimestampBehavior 测试 =====
670
671    #[test]
672    fn test_timestamp_behavior_before_insert() {
673        let b = TimestampBehavior::default_fields();
674        let ctx = HookContext::default().with_timestamp(1700000000);
675        let mut attrs = HashMap::new();
676        b.before_insert(&ctx, &mut attrs).unwrap();
677        assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
678        assert_eq!(attrs.get("updated_at"), Some(&Value::I64(1700000000)));
679    }
680
681    #[test]
682    fn test_timestamp_behavior_before_update() {
683        let b = TimestampBehavior::default_fields();
684        let ctx = HookContext::default().with_timestamp(1800000000);
685        let mut attrs = HashMap::new();
686        b.before_update(&ctx, &mut attrs).unwrap();
687        // update 不应填充 created_at
688        assert!(!attrs.contains_key("created_at"));
689        assert_eq!(attrs.get("updated_at"), Some(&Value::I64(1800000000)));
690    }
691
692    #[test]
693    fn test_timestamp_behavior_custom_fields() {
694        let b = TimestampBehavior::new("create_time", "update_time");
695        let ctx = HookContext::default().with_timestamp(100);
696        let mut attrs = HashMap::new();
697        b.before_insert(&ctx, &mut attrs).unwrap();
698        assert_eq!(attrs.get("create_time"), Some(&Value::I64(100)));
699        assert_eq!(attrs.get("update_time"), Some(&Value::I64(100)));
700    }
701
702    #[test]
703    fn test_timestamp_behavior_name() {
704        let b = TimestampBehavior::default_fields();
705        assert_eq!(b.name(), "TimestampBehavior");
706    }
707
708    // ===== BlameableBehavior 测试 =====
709
710    #[test]
711    fn test_blameable_behavior_before_insert() {
712        let b = BlameableBehavior::default_fields();
713        let ctx = HookContext::default().with_operator(42);
714        let mut attrs = HashMap::new();
715        b.before_insert(&ctx, &mut attrs).unwrap();
716        assert_eq!(attrs.get("created_by"), Some(&Value::I64(42)));
717        assert_eq!(attrs.get("updated_by"), Some(&Value::I64(42)));
718    }
719
720    #[test]
721    fn test_blameable_behavior_before_update() {
722        let b = BlameableBehavior::default_fields();
723        let ctx = HookContext::default().with_operator(99);
724        let mut attrs = HashMap::new();
725        b.before_update(&ctx, &mut attrs).unwrap();
726        assert!(!attrs.contains_key("created_by"));
727        assert_eq!(attrs.get("updated_by"), Some(&Value::I64(99)));
728    }
729
730    #[test]
731    fn test_blameable_behavior_no_operator_skips() {
732        // 未设置 operator_id 时不应填充
733        let b = BlameableBehavior::default_fields();
734        let ctx = HookContext::default(); // 无 operator
735        let mut attrs = HashMap::new();
736        b.before_insert(&ctx, &mut attrs).unwrap();
737        assert!(!attrs.contains_key("created_by"));
738        assert!(!attrs.contains_key("updated_by"));
739    }
740
741    #[test]
742    fn test_blameable_behavior_name() {
743        let b = BlameableBehavior::default_fields();
744        assert_eq!(b.name(), "BlameableBehavior");
745    }
746
747    // ===== TenantBehavior 测试(S-3)=====
748
749    #[test]
750    fn test_tenant_behavior_default_policy() {
751        assert_eq!(TenantUpdatePolicy::default(), TenantUpdatePolicy::DenyMismatch);
752    }
753
754    #[test]
755    fn test_tenant_behavior_before_insert_fills_tenant_id() {
756        let b = TenantBehavior::default_fields();
757        let ctx = HookContext::default().with_tenant(42);
758        let mut attrs = HashMap::new();
759        b.before_insert(&ctx, &mut attrs).unwrap();
760        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(42)));
761    }
762
763    #[test]
764    fn test_tenant_behavior_before_insert_overwrites_existing() {
765        // 即使 attrs 已有 tenant_id,也以 ctx.tenant_id 为准(防止业务层伪造)
766        let b = TenantBehavior::default_fields();
767        let ctx = HookContext::default().with_tenant(99);
768        let mut attrs = HashMap::new();
769        attrs.insert("tenant_id".to_string(), Value::I64(1)); // 业务层伪造
770        b.before_insert(&ctx, &mut attrs).unwrap();
771        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(99)));
772    }
773
774    #[test]
775    fn test_tenant_behavior_before_insert_no_tenant_skips_by_default() {
776        // 默认 skip_when_no_tenant=true:ctx 无 tenant_id 时跳过
777        let b = TenantBehavior::default_fields();
778        let ctx = HookContext::default(); // 无 tenant_id
779        let mut attrs = HashMap::new();
780        let result = b.before_insert(&ctx, &mut attrs);
781        assert!(result.is_ok());
782        assert!(!attrs.contains_key("tenant_id"));
783    }
784
785    #[test]
786    fn test_tenant_behavior_before_insert_no_tenant_errors_when_configured() {
787        // skip_when_no_tenant=false:ctx 无 tenant_id 时返回 TenantError
788        let b = TenantBehavior::default_fields().with_skip_when_no_tenant(false);
789        let ctx = HookContext::default();
790        let mut attrs = HashMap::new();
791        let result = b.before_insert(&ctx, &mut attrs);
792        match result {
793            Err(DbError::TenantError(msg)) => {
794                assert!(msg.contains("ctx.tenant_id is None"));
795                assert!(msg.contains("tenant_id"));
796            }
797            other => panic!("expected TenantError, got {:?}", other),
798        }
799        assert!(!attrs.contains_key("tenant_id"));
800    }
801
802    #[test]
803    fn test_tenant_behavior_custom_field_name() {
804        let b = TenantBehavior::new("org_id", TenantUpdatePolicy::default(), true);
805        let ctx = HookContext::default().with_tenant(7);
806        let mut attrs = HashMap::new();
807        b.before_insert(&ctx, &mut attrs).unwrap();
808        assert_eq!(attrs.get("org_id"), Some(&Value::I64(7)));
809        assert!(!attrs.contains_key("tenant_id"));
810    }
811
812    #[test]
813    fn test_tenant_behavior_name() {
814        let b = TenantBehavior::default_fields();
815        assert_eq!(b.name(), "TenantBehavior");
816    }
817
818    // --- before_update 策略测试 ---
819
820    #[test]
821    fn test_tenant_behavior_update_deny_mismatch_match_ok() {
822        // attrs.tenant_id == ctx.tenant_id:允许 update
823        let b = TenantBehavior::default_fields(); // DenyMismatch
824        let ctx = HookContext::default().with_tenant(42);
825        let mut attrs = HashMap::new();
826        attrs.insert("tenant_id".to_string(), Value::I64(42));
827        let result = b.before_update(&ctx, &mut attrs);
828        assert!(result.is_ok());
829        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(42))); // 未被移除
830    }
831
832    #[test]
833    fn test_tenant_behavior_update_deny_mismatch_mismatch_rejected() {
834        // attrs.tenant_id != ctx.tenant_id:拒绝 update
835        let b = TenantBehavior::default_fields();
836        let ctx = HookContext::default().with_tenant(42);
837        let mut attrs = HashMap::new();
838        attrs.insert("tenant_id".to_string(), Value::I64(99)); // 跨租户篡改
839        let result = b.before_update(&ctx, &mut attrs);
840        match result {
841            Err(DbError::TenantError(msg)) => {
842                assert!(msg.contains("mismatch"));
843                assert!(msg.contains("99"));
844                assert!(msg.contains("42"));
845            }
846            other => panic!("expected TenantError, got {:?}", other),
847        }
848    }
849
850    #[test]
851    fn test_tenant_behavior_update_deny_mismatch_no_ctx_tenant_rejected() {
852        // ctx.tenant_id=None 但 attrs 有 tenant_id:拒绝
853        let b = TenantBehavior::default_fields();
854        let ctx = HookContext::default();
855        let mut attrs = HashMap::new();
856        attrs.insert("tenant_id".to_string(), Value::I64(1));
857        let result = b.before_update(&ctx, &mut attrs);
858        match result {
859            Err(DbError::TenantError(msg)) => {
860                assert!(msg.contains("ctx.tenant_id is None"));
861            }
862            other => panic!("expected TenantError, got {:?}", other),
863        }
864    }
865
866    #[test]
867    fn test_tenant_behavior_update_deny_mismatch_no_attrs_tenant_ok() {
868        // attrs 中没有 tenant_id:允许 update(不影响原值)
869        let b = TenantBehavior::default_fields();
870        let ctx = HookContext::default().with_tenant(42);
871        let mut attrs = HashMap::new();
872        attrs.insert("name".to_string(), Value::String("updated".into()));
873        let result = b.before_update(&ctx, &mut attrs);
874        assert!(result.is_ok());
875        assert!(!attrs.contains_key("tenant_id"));
876    }
877
878    #[test]
879    fn test_tenant_behavior_update_strip_removes_tenant_id() {
880        // Strip 策略:从 attrs 中移除 tenant_id
881        let b = TenantBehavior::default_fields().with_update_policy(TenantUpdatePolicy::Strip);
882        let ctx = HookContext::default().with_tenant(42);
883        let mut attrs = HashMap::new();
884        attrs.insert("tenant_id".to_string(), Value::I64(99));
885        attrs.insert("name".to_string(), Value::String("x".into()));
886        let result = b.before_update(&ctx, &mut attrs);
887        assert!(result.is_ok());
888        assert!(!attrs.contains_key("tenant_id"), "Strip should remove tenant_id");
889        assert!(attrs.contains_key("name"), "other fields should remain");
890    }
891
892    #[test]
893    fn test_tenant_behavior_update_strip_no_tenant_id_no_op() {
894        // Strip 策略:attrs 中没有 tenant_id,无操作
895        let b = TenantBehavior::default_fields().with_update_policy(TenantUpdatePolicy::Strip);
896        let ctx = HookContext::default();
897        let mut attrs = HashMap::new();
898        attrs.insert("name".to_string(), Value::String("x".into()));
899        let result = b.before_update(&ctx, &mut attrs);
900        assert!(result.is_ok());
901    }
902
903    #[test]
904    fn test_tenant_behavior_update_allow_no_check() {
905        // Allow 策略:不做任何检查(即使不一致也允许)
906        let b = TenantBehavior::default_fields().with_update_policy(TenantUpdatePolicy::Allow);
907        let ctx = HookContext::default().with_tenant(42);
908        let mut attrs = HashMap::new();
909        attrs.insert("tenant_id".to_string(), Value::I64(999));
910        let result = b.before_update(&ctx, &mut attrs);
911        assert!(result.is_ok());
912        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(999))); // 保留原值
913    }
914
915    #[test]
916    fn test_tenant_behavior_update_wrong_type_rejected() {
917        // attrs.tenant_id 不是 I64 类型:拒绝(类型不匹配)
918        let b = TenantBehavior::default_fields();
919        let ctx = HookContext::default().with_tenant(42);
920        let mut attrs = HashMap::new();
921        attrs.insert("tenant_id".to_string(), Value::String("forty-two".into()));
922        let result = b.before_update(&ctx, &mut attrs);
923        match result {
924            Err(DbError::TenantError(msg)) => {
925                assert!(msg.contains("expected I64"));
926            }
927            other => panic!("expected TenantError, got {:?}", other),
928        }
929    }
930
931    // --- 集成:BehaviorRegistry + TenantBehavior ---
932
933    #[test]
934    fn test_registry_with_tenant_behavior_insert() {
935        let r = BehaviorRegistry::new();
936        r.register(Box::new(TenantBehavior::default_fields()));
937        r.register(Box::new(TimestampBehavior::default_fields()));
938
939        let ctx = HookContext::default().with_tenant(7).with_timestamp(1000);
940        let mut attrs = HashMap::new();
941        r.before_insert(&ctx, &mut attrs).unwrap();
942        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(7)));
943        assert_eq!(attrs.get("created_at"), Some(&Value::I64(1000)));
944    }
945
946    #[test]
947    fn test_registry_with_tenant_behavior_update_strip() {
948        let r = BehaviorRegistry::new();
949        r.register(
950            Box::new(
951                TenantBehavior::default_fields().with_update_policy(TenantUpdatePolicy::Strip),
952            ),
953        );
954
955        let ctx = HookContext::default().with_tenant(7);
956        let mut attrs = HashMap::new();
957        attrs.insert("tenant_id".to_string(), Value::I64(99));
958        attrs.insert("name".to_string(), Value::String("updated".into()));
959        r.before_update(&ctx, &mut attrs).unwrap();
960        // Strip 应移除 tenant_id
961        assert!(!attrs.contains_key("tenant_id"));
962        assert!(attrs.contains_key("name"));
963    }
964
965    #[test]
966    fn test_registry_unregister_tenant_behavior() {
967        let r = BehaviorRegistry::new();
968        r.register(Box::new(TenantBehavior::default_fields()));
969        assert_eq!(r.count(), 1);
970        assert!(r.unregister("TenantBehavior"));
971        assert_eq!(r.count(), 0);
972    }
973
974    #[test]
975    fn test_combined_tenant_timestamp_blameable_insert() {
976        // 模拟真实场景:同时使用 Tenant + Timestamp + Blameable
977        let r = BehaviorRegistry::new();
978        r.register(Box::new(TenantBehavior::default_fields()));
979        r.register(Box::new(TimestampBehavior::default_fields()));
980        r.register(Box::new(BlameableBehavior::default_fields()));
981
982        let ctx = HookContext::default()
983            .with_tenant(42)
984            .with_operator(1)
985            .with_timestamp(1700000000);
986        let mut attrs = HashMap::new();
987        r.before_insert(&ctx, &mut attrs).unwrap();
988        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(42)));
989        assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
990        assert_eq!(attrs.get("created_by"), Some(&Value::I64(1)));
991    }
992
993    #[test]
994    fn test_tenant_behavior_prevents_cross_tenant_tampering() {
995        // 安全场景:恶意用户尝试在 update 时将 tenant_id 改为其他租户
996        let r = BehaviorRegistry::new();
997        r.register(Box::new(TenantBehavior::default_fields())); // DenyMismatch
998
999        // 正常租户 42 的用户尝试把记录的 tenant_id 改为 99
1000        let ctx = HookContext::default().with_tenant(42);
1001        let mut attrs = HashMap::new();
1002        attrs.insert("tenant_id".to_string(), Value::I64(99)); // 试图迁移到租户 99
1003        attrs.insert("data".to_string(), Value::String("evil".into()));
1004
1005        let result = r.before_update(&ctx, &mut attrs);
1006        assert!(result.is_err(), "cross-tenant tampering should be rejected");
1007    }
1008
1009    // ===== AttributeBehavior 测试 =====
1010
1011    #[test]
1012    fn test_attribute_behavior_before_insert() {
1013        let b = AttributeBehavior::new("uuid_gen", HookEvent::BeforeInsert, "uuid", |_ctx| {
1014            Value::String("auto-uuid".to_string())
1015        });
1016        let ctx = HookContext::default();
1017        let mut attrs = HashMap::new();
1018        b.before_insert(&ctx, &mut attrs).unwrap();
1019        assert_eq!(
1020            attrs.get("uuid"),
1021            Some(&Value::String("auto-uuid".to_string()))
1022        );
1023    }
1024
1025    #[test]
1026    fn test_attribute_behavior_event_filter() {
1027        // 注册 BeforeInsert 事件,但触发 before_update,不应执行
1028        let b = AttributeBehavior::new("test", HookEvent::BeforeInsert, "field", |_ctx| {
1029            Value::I64(1)
1030        });
1031        let ctx = HookContext::default();
1032        let mut attrs = HashMap::new();
1033        b.before_update(&ctx, &mut attrs).unwrap();
1034        assert!(!attrs.contains_key("field"));
1035    }
1036
1037    // ===== BehaviorRegistry 测试 =====
1038
1039    #[test]
1040    fn test_registry_register_and_count() {
1041        let r = BehaviorRegistry::new();
1042        assert_eq!(r.count(), 0);
1043        r.register(Box::new(TimestampBehavior::default_fields()));
1044        assert_eq!(r.count(), 1);
1045        r.register(Box::new(BlameableBehavior::default_fields()));
1046        assert_eq!(r.count(), 2);
1047    }
1048
1049    #[test]
1050    fn test_registry_unregister_by_name() {
1051        let r = BehaviorRegistry::new();
1052        r.register(Box::new(TimestampBehavior::default_fields()));
1053        r.register(Box::new(BlameableBehavior::default_fields()));
1054        assert_eq!(r.count(), 2);
1055
1056        let removed = r.unregister("TimestampBehavior");
1057        assert!(removed);
1058        assert_eq!(r.count(), 1);
1059
1060        // 不存在的 name 返回 false
1061        let removed2 = r.unregister("NonExistent");
1062        assert!(!removed2);
1063    }
1064
1065    #[test]
1066    fn test_registry_names() {
1067        let r = BehaviorRegistry::new();
1068        r.register(Box::new(TimestampBehavior::default_fields()));
1069        r.register(Box::new(BlameableBehavior::default_fields()));
1070        let names = r.names();
1071        assert!(names.contains(&"TimestampBehavior"));
1072        assert!(names.contains(&"BlameableBehavior"));
1073    }
1074
1075    #[test]
1076    fn test_registry_before_insert_dispatches_all() {
1077        let r = BehaviorRegistry::new();
1078        r.register(Box::new(TimestampBehavior::default_fields()));
1079        r.register(Box::new(BlameableBehavior::default_fields()));
1080
1081        let ctx = HookContext::default()
1082            .with_operator(100)
1083            .with_timestamp(1700000000);
1084        let mut attrs = HashMap::new();
1085        r.before_insert(&ctx, &mut attrs).unwrap();
1086
1087        // 两个 Behavior 都应执行
1088        assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
1089        assert_eq!(attrs.get("created_by"), Some(&Value::I64(100)));
1090    }
1091
1092    #[test]
1093    fn test_registry_before_update_dispatches_all() {
1094        let r = BehaviorRegistry::new();
1095        r.register(Box::new(TimestampBehavior::default_fields()));
1096        r.register(Box::new(BlameableBehavior::default_fields()));
1097
1098        let ctx = HookContext::default()
1099            .with_operator(200)
1100            .with_timestamp(1800000000);
1101        let mut attrs = HashMap::new();
1102        r.before_update(&ctx, &mut attrs).unwrap();
1103
1104        // update 只填充 updated_* 字段
1105        assert!(!attrs.contains_key("created_at"));
1106        assert_eq!(attrs.get("updated_at"), Some(&Value::I64(1800000000)));
1107        assert!(!attrs.contains_key("created_by"));
1108        assert_eq!(attrs.get("updated_by"), Some(&Value::I64(200)));
1109    }
1110
1111    #[test]
1112    fn test_registry_clear() {
1113        let r = BehaviorRegistry::new();
1114        r.register(Box::new(TimestampBehavior::default_fields()));
1115        r.register(Box::new(BlameableBehavior::default_fields()));
1116        assert_eq!(r.count(), 2);
1117
1118        r.clear();
1119        assert_eq!(r.count(), 0);
1120    }
1121
1122    #[test]
1123    fn test_registry_default() {
1124        let r = BehaviorRegistry::default();
1125        assert_eq!(r.count(), 0);
1126    }
1127
1128    #[test]
1129    fn test_registry_empty_dispatches_no_op() {
1130        // 空 registry 分发事件应该是 no-op
1131        let r = BehaviorRegistry::new();
1132        let ctx = HookContext::default();
1133        let mut attrs = HashMap::new();
1134        assert!(r.before_insert(&ctx, &mut attrs).is_ok());
1135        assert!(r.before_update(&ctx, &mut attrs).is_ok());
1136        assert!(r.before_delete(&ctx, &mut attrs).is_ok());
1137        assert!(r.after_find(&ctx, &mut attrs).is_ok());
1138        assert!(attrs.is_empty());
1139    }
1140
1141    #[test]
1142    fn test_combined_timestamp_and_blameable() {
1143        // 模拟真实场景:同时使用 TimestampBehavior + BlameableBehavior
1144        let r = BehaviorRegistry::new();
1145        r.register(Box::new(TimestampBehavior::default_fields()));
1146        r.register(Box::new(BlameableBehavior::default_fields()));
1147
1148        // 模拟 insert
1149        let ctx1 = HookContext::default().with_operator(1).with_timestamp(1000);
1150        let mut attrs1 = HashMap::new();
1151        r.before_insert(&ctx1, &mut attrs1).unwrap();
1152        assert_eq!(attrs1.get("created_at"), Some(&Value::I64(1000)));
1153        assert_eq!(attrs1.get("updated_at"), Some(&Value::I64(1000)));
1154        assert_eq!(attrs1.get("created_by"), Some(&Value::I64(1)));
1155        assert_eq!(attrs1.get("updated_by"), Some(&Value::I64(1)));
1156
1157        // 模拟 update(不同操作人、不同时间)
1158        let ctx2 = HookContext::default().with_operator(2).with_timestamp(2000);
1159        let mut attrs2 = HashMap::new();
1160        r.before_update(&ctx2, &mut attrs2).unwrap();
1161        assert!(!attrs2.contains_key("created_at"));
1162        assert_eq!(attrs2.get("updated_at"), Some(&Value::I64(2000)));
1163        assert!(!attrs2.contains_key("created_by"));
1164        assert_eq!(attrs2.get("updated_by"), Some(&Value::I64(2)));
1165    }
1166}