Skip to main content

sz_orm_model/
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_model::behaviors::{Behavior, TimestampBehavior, BlameableBehavior, BehaviorRegistry};
22//! use sz_orm_model::hooks::HookContext;
23//! use sz_orm_model::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 parking_lot::RwLock;
41use std::collections::HashMap;
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_model::behaviors::{Behavior, TimestampBehavior};
109/// use sz_orm_model::hooks::HookContext;
110/// use sz_orm_model::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_model::behaviors::{Behavior, BlameableBehavior};
188/// use sz_orm_model::hooks::HookContext;
189/// use sz_orm_model::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, Default, PartialEq, Eq)]
266pub enum TenantUpdatePolicy {
267    /// 允许 update 时变更 tenant_id(不推荐,仅在特殊迁移场景使用)
268    Allow,
269    /// update 时若 attrs 中出现 tenant_id 且与 ctx.tenant_id 不一致则报错(默认)
270    #[default]
271    DenyMismatch,
272    /// update 时静默忽略 attrs 中的 tenant_id(保持原值不变)
273    Strip,
274}
275
276/// 自动填充 tenant_id Behavior
277///
278/// # 工作机制
279///
280/// - `before_insert`:若 `ctx.tenant_id` 为 `Some(tid)`,将 `tid` 写入 `attrs[tenant_field]`;
281///   若 `ctx.tenant_id` 为 `None`,按 `skip_when_no_tenant` 配置决定是跳过还是报错。
282/// - `before_update`:根据 [`TenantUpdatePolicy`] 处理 attrs 中的 tenant_id:
283///   - `DenyMismatch`(默认):若 attrs 中 tenant_id 与 ctx.tenant_id 不一致则返回 `DbError::TenantError`
284///   - `Strip`:从 attrs 中移除 tenant_id(保证不被更新)
285///   - `Allow`:不做任何处理
286///
287/// # 示例
288///
289/// ```
290/// use sz_orm_model::behaviors::{TenantBehavior, TenantUpdatePolicy, Behavior};
291/// use sz_orm_model::hooks::HookContext;
292/// use sz_orm_model::Value;
293/// use std::collections::HashMap;
294///
295/// let b = TenantBehavior::default_fields();
296/// let ctx = HookContext::default().with_tenant(42);
297/// let mut attrs = HashMap::new();
298/// b.before_insert(&ctx, &mut attrs).unwrap();
299/// assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(42)));
300/// ```
301pub struct TenantBehavior {
302    /// 租户字段名(默认 "tenant_id")
303    pub tenant_field: &'static str,
304    /// update 时对 tenant_id 的处理策略
305    pub update_policy: TenantUpdatePolicy,
306    /// ctx.tenant_id 为 None 时的行为:
307    /// - true:跳过填充(不写入 tenant_id,允许跨租户写入)
308    /// - false:返回 TenantError
309    pub skip_when_no_tenant: bool,
310}
311
312impl TenantBehavior {
313    /// 创建 TenantBehavior
314    pub fn new(
315        tenant_field: &'static str,
316        update_policy: TenantUpdatePolicy,
317        skip_when_no_tenant: bool,
318    ) -> Self {
319        Self {
320            tenant_field,
321            update_policy,
322            skip_when_no_tenant,
323        }
324    }
325
326    /// 使用默认字段名(tenant_id)+ 默认策略(DenyMismatch + skip_when_no_tenant=true)
327    pub fn default_fields() -> Self {
328        Self::new("tenant_id", TenantUpdatePolicy::default(), true)
329    }
330
331    /// 设置 update 策略(builder 风格)
332    pub fn with_update_policy(mut self, policy: TenantUpdatePolicy) -> Self {
333        self.update_policy = policy;
334        self
335    }
336
337    /// 设置 ctx.tenant_id 为 None 时的行为(builder 风格)
338    pub fn with_skip_when_no_tenant(mut self, skip: bool) -> Self {
339        self.skip_when_no_tenant = skip;
340        self
341    }
342}
343
344impl Behavior for TenantBehavior {
345    fn name(&self) -> &'static str {
346        "TenantBehavior"
347    }
348
349    fn before_insert(
350        &self,
351        ctx: &HookContext,
352        attrs: &mut HashMap<String, Value>,
353    ) -> BehaviorResult<()> {
354        match ctx.tenant_id {
355            Some(tid) => {
356                attrs.insert(self.tenant_field.to_string(), Value::I64(tid));
357                Ok(())
358            }
359            None => {
360                if self.skip_when_no_tenant {
361                    Ok(())
362                } else {
363                    Err(DbError::TenantError(format!(
364                        "TenantBehavior::before_insert: ctx.tenant_id is None, \
365                         cannot auto-fill `{}`; set skip_when_no_tenant=true or \
366                         provide tenant_id in HookContext",
367                        self.tenant_field
368                    )))
369                }
370            }
371        }
372    }
373
374    fn before_update(
375        &self,
376        ctx: &HookContext,
377        attrs: &mut HashMap<String, Value>,
378    ) -> BehaviorResult<()> {
379        match self.update_policy {
380            TenantUpdatePolicy::Allow => Ok(()),
381            TenantUpdatePolicy::Strip => {
382                attrs.remove(self.tenant_field);
383                Ok(())
384            }
385            TenantUpdatePolicy::DenyMismatch => {
386                if let Some(existing) = attrs.get(self.tenant_field) {
387                    match (existing, ctx.tenant_id) {
388                        // ctx 中有 tenant_id:必须与 attrs 一致
389                        (Value::I64(a), Some(b)) if *a == b => Ok(()),
390                        (Value::I64(a), Some(b)) => Err(DbError::TenantError(format!(
391                            "TenantBehavior::before_update: tenant_id mismatch — \
392                             attrs.{}={}, ctx.tenant_id={}; update rejected to prevent \
393                             cross-tenant tampering",
394                            self.tenant_field, a, b
395                        ))),
396                        // ctx 中无 tenant_id:不允许显式更新 tenant_id
397                        (_, None) => Err(DbError::TenantError(format!(
398                            "TenantBehavior::before_update: attrs contains `{}` but \
399                             ctx.tenant_id is None; remove `{}` from update payload or \
400                             set ctx.tenant_id",
401                            self.tenant_field, self.tenant_field
402                        ))),
403                        // 非 I64 类型的 tenant_id 视为类型不匹配
404                        (other, _) => Err(DbError::TenantError(format!(
405                            "TenantBehavior::before_update: attrs.{} expected I64, got {:?}",
406                            self.tenant_field, other
407                        ))),
408                    }
409                } else {
410                    Ok(())
411                }
412            }
413        }
414    }
415}
416
417// ============================================================================
418// AttributeBehavior — 通用属性自动设置
419// ============================================================================
420//
421// 对应:Yii2 `AttributeBehavior`
422//
423// 允许用户注册自定义闭包,在指定事件触发时设置属性值。
424
425/// 通用属性 Behavior — 在指定事件触发时通过闭包设置属性
426///
427/// # 示例
428///
429/// ```
430/// use sz_orm_model::behaviors::{AttributeBehavior, BehaviorRegistry, Behavior};
431/// use sz_orm_model::hooks::{HookContext, HookEvent};
432/// use sz_orm_model::Value;
433/// use std::collections::HashMap;
434///
435/// let mut registry = BehaviorRegistry::new();
436/// // 在 before_insert 时设置 uuid 字段
437/// registry.register(Box::new(AttributeBehavior::new(
438///     "uuid_gen",
439///     HookEvent::BeforeInsert,
440///     "uuid",
441///     |_ctx| Value::String("auto-uuid".to_string()),
442/// )));
443///
444/// let ctx = HookContext::default();
445/// let mut attrs = HashMap::new();
446/// registry.before_insert(&ctx, &mut attrs).unwrap();
447/// assert_eq!(attrs.get("uuid"), Some(&Value::String("auto-uuid".to_string())));
448/// ```
449pub struct AttributeBehavior {
450    /// Behavior 名称
451    pub name_str: &'static str,
452    /// 订阅的事件(仅在该事件触发时执行)
453    pub event: crate::hooks::HookEvent,
454    /// 目标字段名
455    pub target_field: &'static str,
456    /// 值生成闭包
457    pub generator: Box<dyn Fn(&HookContext) -> Value + Send + Sync>,
458}
459
460impl AttributeBehavior {
461    /// 创建 AttributeBehavior
462    pub fn new(
463        name: &'static str,
464        event: crate::hooks::HookEvent,
465        target_field: &'static str,
466        generator: impl Fn(&HookContext) -> Value + Send + Sync + 'static,
467    ) -> Self {
468        Self {
469            name_str: name,
470            event,
471            target_field,
472            generator: Box::new(generator),
473        }
474    }
475}
476
477impl Behavior for AttributeBehavior {
478    fn name(&self) -> &'static str {
479        self.name_str
480    }
481
482    fn before_insert(
483        &self,
484        ctx: &HookContext,
485        attrs: &mut HashMap<String, Value>,
486    ) -> BehaviorResult<()> {
487        if self.event == crate::hooks::HookEvent::BeforeInsert
488            || self.event == crate::hooks::HookEvent::BeforeWrite
489            || self.event == crate::hooks::HookEvent::BeforeSave
490        {
491            let v = (self.generator)(ctx);
492            attrs.insert(self.target_field.to_string(), v);
493        }
494        Ok(())
495    }
496
497    fn before_update(
498        &self,
499        ctx: &HookContext,
500        attrs: &mut HashMap<String, Value>,
501    ) -> BehaviorResult<()> {
502        if self.event == crate::hooks::HookEvent::BeforeUpdate
503            || self.event == crate::hooks::HookEvent::BeforeWrite
504            || self.event == crate::hooks::HookEvent::BeforeSave
505        {
506            let v = (self.generator)(ctx);
507            attrs.insert(self.target_field.to_string(), v);
508        }
509        Ok(())
510    }
511
512    fn after_find(
513        &self,
514        ctx: &HookContext,
515        attrs: &mut HashMap<String, Value>,
516    ) -> BehaviorResult<()> {
517        if self.event == crate::hooks::HookEvent::AfterFind {
518            let v = (self.generator)(ctx);
519            attrs.insert(self.target_field.to_string(), v);
520        }
521        Ok(())
522    }
523}
524
525// ============================================================================
526// BehaviorRegistry — Behavior 注册中心
527// ============================================================================
528
529/// Behavior 注册中心 — 管理多个 Behavior 的注册与分发
530///
531/// 线程安全:内部使用 RwLock,可在多线程环境下共享。
532///
533/// # 示例
534///
535/// ```
536/// use sz_orm_model::behaviors::{BehaviorRegistry, TimestampBehavior, BlameableBehavior, Behavior};
537/// use sz_orm_model::hooks::HookContext;
538/// use sz_orm_model::Value;
539/// use std::collections::HashMap;
540///
541/// let mut registry = BehaviorRegistry::new();
542/// registry.register(Box::new(TimestampBehavior::default_fields()));
543/// registry.register(Box::new(BlameableBehavior::default_fields()));
544///
545/// let ctx = HookContext::default().with_operator(100).with_timestamp(1700000000);
546/// let mut attrs = HashMap::new();
547/// registry.before_insert(&ctx, &mut attrs).unwrap();
548/// assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
549/// assert_eq!(attrs.get("created_by"), Some(&Value::I64(100)));
550/// ```
551pub struct BehaviorRegistry {
552    behaviors: RwLock<Vec<Box<dyn Behavior>>>,
553}
554
555impl BehaviorRegistry {
556    /// 创建空的 BehaviorRegistry
557    pub fn new() -> Self {
558        Self {
559            behaviors: RwLock::new(Vec::new()),
560        }
561    }
562
563    /// 注册一个 Behavior
564    pub fn register(&self, behavior: Box<dyn Behavior>) {
565        let mut guards = self.behaviors.write();
566        guards.push(behavior);
567    }
568
569    /// 按 name 移除已注册的 Behavior
570    pub fn unregister(&self, name: &str) -> bool {
571        let mut guards = self.behaviors.write();
572        let before = guards.len();
573        guards.retain(|b| b.name() != name);
574        guards.len() < before
575    }
576
577    /// 已注册的 Behavior 数量
578    pub fn count(&self) -> usize {
579        self.behaviors.read().len()
580    }
581
582    /// 列出所有已注册 Behavior 的名称
583    pub fn names(&self) -> Vec<&'static str> {
584        self.behaviors.read().iter().map(|b| b.name()).collect()
585    }
586
587    /// 分发 before_insert 事件
588    pub fn before_insert(
589        &self,
590        ctx: &HookContext,
591        attrs: &mut HashMap<String, Value>,
592    ) -> BehaviorResult<()> {
593        let guards = self.behaviors.read();
594        for b in guards.iter() {
595            b.before_insert(ctx, attrs)?;
596        }
597        Ok(())
598    }
599
600    /// 分发 before_update 事件
601    pub fn before_update(
602        &self,
603        ctx: &HookContext,
604        attrs: &mut HashMap<String, Value>,
605    ) -> BehaviorResult<()> {
606        let guards = self.behaviors.read();
607        for b in guards.iter() {
608            b.before_update(ctx, attrs)?;
609        }
610        Ok(())
611    }
612
613    /// 分发 before_delete 事件
614    pub fn before_delete(
615        &self,
616        ctx: &HookContext,
617        attrs: &mut HashMap<String, Value>,
618    ) -> BehaviorResult<()> {
619        let guards = self.behaviors.read();
620        for b in guards.iter() {
621            b.before_delete(ctx, attrs)?;
622        }
623        Ok(())
624    }
625
626    /// 分发 after_find 事件
627    pub fn after_find(
628        &self,
629        ctx: &HookContext,
630        attrs: &mut HashMap<String, Value>,
631    ) -> BehaviorResult<()> {
632        let guards = self.behaviors.read();
633        for b in guards.iter() {
634            b.after_find(ctx, attrs)?;
635        }
636        Ok(())
637    }
638
639    /// 清空所有已注册的 Behavior
640    pub fn clear(&self) {
641        self.behaviors.write().clear();
642    }
643}
644
645impl Default for BehaviorRegistry {
646    fn default() -> Self {
647        Self::new()
648    }
649}
650
651// ============================================================================
652// 单元测试
653// ============================================================================
654
655#[cfg(test)]
656mod tests {
657    use super::*;
658    use crate::hooks::HookEvent;
659
660    // ===== TimestampBehavior 测试 =====
661
662    #[test]
663    fn test_timestamp_behavior_before_insert() {
664        let b = TimestampBehavior::default_fields();
665        let ctx = HookContext::default().with_timestamp(1700000000);
666        let mut attrs = HashMap::new();
667        b.before_insert(&ctx, &mut attrs).unwrap();
668        assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
669        assert_eq!(attrs.get("updated_at"), Some(&Value::I64(1700000000)));
670    }
671
672    #[test]
673    fn test_timestamp_behavior_before_update() {
674        let b = TimestampBehavior::default_fields();
675        let ctx = HookContext::default().with_timestamp(1800000000);
676        let mut attrs = HashMap::new();
677        b.before_update(&ctx, &mut attrs).unwrap();
678        // update 不应填充 created_at
679        assert!(!attrs.contains_key("created_at"));
680        assert_eq!(attrs.get("updated_at"), Some(&Value::I64(1800000000)));
681    }
682
683    #[test]
684    fn test_timestamp_behavior_custom_fields() {
685        let b = TimestampBehavior::new("create_time", "update_time");
686        let ctx = HookContext::default().with_timestamp(100);
687        let mut attrs = HashMap::new();
688        b.before_insert(&ctx, &mut attrs).unwrap();
689        assert_eq!(attrs.get("create_time"), Some(&Value::I64(100)));
690        assert_eq!(attrs.get("update_time"), Some(&Value::I64(100)));
691    }
692
693    #[test]
694    fn test_timestamp_behavior_name() {
695        let b = TimestampBehavior::default_fields();
696        assert_eq!(b.name(), "TimestampBehavior");
697    }
698
699    // ===== BlameableBehavior 测试 =====
700
701    #[test]
702    fn test_blameable_behavior_before_insert() {
703        let b = BlameableBehavior::default_fields();
704        let ctx = HookContext::default().with_operator(42);
705        let mut attrs = HashMap::new();
706        b.before_insert(&ctx, &mut attrs).unwrap();
707        assert_eq!(attrs.get("created_by"), Some(&Value::I64(42)));
708        assert_eq!(attrs.get("updated_by"), Some(&Value::I64(42)));
709    }
710
711    #[test]
712    fn test_blameable_behavior_before_update() {
713        let b = BlameableBehavior::default_fields();
714        let ctx = HookContext::default().with_operator(99);
715        let mut attrs = HashMap::new();
716        b.before_update(&ctx, &mut attrs).unwrap();
717        assert!(!attrs.contains_key("created_by"));
718        assert_eq!(attrs.get("updated_by"), Some(&Value::I64(99)));
719    }
720
721    #[test]
722    fn test_blameable_behavior_no_operator_skips() {
723        // 未设置 operator_id 时不应填充
724        let b = BlameableBehavior::default_fields();
725        let ctx = HookContext::default(); // 无 operator
726        let mut attrs = HashMap::new();
727        b.before_insert(&ctx, &mut attrs).unwrap();
728        assert!(!attrs.contains_key("created_by"));
729        assert!(!attrs.contains_key("updated_by"));
730    }
731
732    #[test]
733    fn test_blameable_behavior_name() {
734        let b = BlameableBehavior::default_fields();
735        assert_eq!(b.name(), "BlameableBehavior");
736    }
737
738    // ===== TenantBehavior 测试(S-3)=====
739
740    #[test]
741    fn test_tenant_behavior_default_policy() {
742        assert_eq!(
743            TenantUpdatePolicy::default(),
744            TenantUpdatePolicy::DenyMismatch
745        );
746    }
747
748    #[test]
749    fn test_tenant_behavior_before_insert_fills_tenant_id() {
750        let b = TenantBehavior::default_fields();
751        let ctx = HookContext::default().with_tenant(42);
752        let mut attrs = HashMap::new();
753        b.before_insert(&ctx, &mut attrs).unwrap();
754        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(42)));
755    }
756
757    #[test]
758    fn test_tenant_behavior_before_insert_overwrites_existing() {
759        // 即使 attrs 已有 tenant_id,也以 ctx.tenant_id 为准(防止业务层伪造)
760        let b = TenantBehavior::default_fields();
761        let ctx = HookContext::default().with_tenant(99);
762        let mut attrs = HashMap::new();
763        attrs.insert("tenant_id".to_string(), Value::I64(1)); // 业务层伪造
764        b.before_insert(&ctx, &mut attrs).unwrap();
765        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(99)));
766    }
767
768    #[test]
769    fn test_tenant_behavior_before_insert_no_tenant_skips_by_default() {
770        // 默认 skip_when_no_tenant=true:ctx 无 tenant_id 时跳过
771        let b = TenantBehavior::default_fields();
772        let ctx = HookContext::default(); // 无 tenant_id
773        let mut attrs = HashMap::new();
774        let result = b.before_insert(&ctx, &mut attrs);
775        assert!(result.is_ok());
776        assert!(!attrs.contains_key("tenant_id"));
777    }
778
779    #[test]
780    fn test_tenant_behavior_before_insert_no_tenant_errors_when_configured() {
781        // skip_when_no_tenant=false:ctx 无 tenant_id 时返回 TenantError
782        let b = TenantBehavior::default_fields().with_skip_when_no_tenant(false);
783        let ctx = HookContext::default();
784        let mut attrs = HashMap::new();
785        let result = b.before_insert(&ctx, &mut attrs);
786        match result {
787            Err(DbError::TenantError(msg)) => {
788                assert!(msg.contains("ctx.tenant_id is None"));
789                assert!(msg.contains("tenant_id"));
790            }
791            other => panic!("expected TenantError, got {:?}", other),
792        }
793        assert!(!attrs.contains_key("tenant_id"));
794    }
795
796    #[test]
797    fn test_tenant_behavior_custom_field_name() {
798        let b = TenantBehavior::new("org_id", TenantUpdatePolicy::default(), true);
799        let ctx = HookContext::default().with_tenant(7);
800        let mut attrs = HashMap::new();
801        b.before_insert(&ctx, &mut attrs).unwrap();
802        assert_eq!(attrs.get("org_id"), Some(&Value::I64(7)));
803        assert!(!attrs.contains_key("tenant_id"));
804    }
805
806    #[test]
807    fn test_tenant_behavior_name() {
808        let b = TenantBehavior::default_fields();
809        assert_eq!(b.name(), "TenantBehavior");
810    }
811
812    // --- before_update 策略测试 ---
813
814    #[test]
815    fn test_tenant_behavior_update_deny_mismatch_match_ok() {
816        // attrs.tenant_id == ctx.tenant_id:允许 update
817        let b = TenantBehavior::default_fields(); // DenyMismatch
818        let ctx = HookContext::default().with_tenant(42);
819        let mut attrs = HashMap::new();
820        attrs.insert("tenant_id".to_string(), Value::I64(42));
821        let result = b.before_update(&ctx, &mut attrs);
822        assert!(result.is_ok());
823        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(42))); // 未被移除
824    }
825
826    #[test]
827    fn test_tenant_behavior_update_deny_mismatch_mismatch_rejected() {
828        // attrs.tenant_id != ctx.tenant_id:拒绝 update
829        let b = TenantBehavior::default_fields();
830        let ctx = HookContext::default().with_tenant(42);
831        let mut attrs = HashMap::new();
832        attrs.insert("tenant_id".to_string(), Value::I64(99)); // 跨租户篡改
833        let result = b.before_update(&ctx, &mut attrs);
834        match result {
835            Err(DbError::TenantError(msg)) => {
836                assert!(msg.contains("mismatch"));
837                assert!(msg.contains("99"));
838                assert!(msg.contains("42"));
839            }
840            other => panic!("expected TenantError, got {:?}", other),
841        }
842    }
843
844    #[test]
845    fn test_tenant_behavior_update_deny_mismatch_no_ctx_tenant_rejected() {
846        // ctx.tenant_id=None 但 attrs 有 tenant_id:拒绝
847        let b = TenantBehavior::default_fields();
848        let ctx = HookContext::default();
849        let mut attrs = HashMap::new();
850        attrs.insert("tenant_id".to_string(), Value::I64(1));
851        let result = b.before_update(&ctx, &mut attrs);
852        match result {
853            Err(DbError::TenantError(msg)) => {
854                assert!(msg.contains("ctx.tenant_id is None"));
855            }
856            other => panic!("expected TenantError, got {:?}", other),
857        }
858    }
859
860    #[test]
861    fn test_tenant_behavior_update_deny_mismatch_no_attrs_tenant_ok() {
862        // attrs 中没有 tenant_id:允许 update(不影响原值)
863        let b = TenantBehavior::default_fields();
864        let ctx = HookContext::default().with_tenant(42);
865        let mut attrs = HashMap::new();
866        attrs.insert("name".to_string(), Value::String("updated".into()));
867        let result = b.before_update(&ctx, &mut attrs);
868        assert!(result.is_ok());
869        assert!(!attrs.contains_key("tenant_id"));
870    }
871
872    #[test]
873    fn test_tenant_behavior_update_strip_removes_tenant_id() {
874        // Strip 策略:从 attrs 中移除 tenant_id
875        let b = TenantBehavior::default_fields().with_update_policy(TenantUpdatePolicy::Strip);
876        let ctx = HookContext::default().with_tenant(42);
877        let mut attrs = HashMap::new();
878        attrs.insert("tenant_id".to_string(), Value::I64(99));
879        attrs.insert("name".to_string(), Value::String("x".into()));
880        let result = b.before_update(&ctx, &mut attrs);
881        assert!(result.is_ok());
882        assert!(
883            !attrs.contains_key("tenant_id"),
884            "Strip should remove tenant_id"
885        );
886        assert!(attrs.contains_key("name"), "other fields should remain");
887    }
888
889    #[test]
890    fn test_tenant_behavior_update_strip_no_tenant_id_no_op() {
891        // Strip 策略:attrs 中没有 tenant_id,无操作
892        let b = TenantBehavior::default_fields().with_update_policy(TenantUpdatePolicy::Strip);
893        let ctx = HookContext::default();
894        let mut attrs = HashMap::new();
895        attrs.insert("name".to_string(), Value::String("x".into()));
896        let result = b.before_update(&ctx, &mut attrs);
897        assert!(result.is_ok());
898    }
899
900    #[test]
901    fn test_tenant_behavior_update_allow_no_check() {
902        // Allow 策略:不做任何检查(即使不一致也允许)
903        let b = TenantBehavior::default_fields().with_update_policy(TenantUpdatePolicy::Allow);
904        let ctx = HookContext::default().with_tenant(42);
905        let mut attrs = HashMap::new();
906        attrs.insert("tenant_id".to_string(), Value::I64(999));
907        let result = b.before_update(&ctx, &mut attrs);
908        assert!(result.is_ok());
909        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(999))); // 保留原值
910    }
911
912    #[test]
913    fn test_tenant_behavior_update_wrong_type_rejected() {
914        // attrs.tenant_id 不是 I64 类型:拒绝(类型不匹配)
915        let b = TenantBehavior::default_fields();
916        let ctx = HookContext::default().with_tenant(42);
917        let mut attrs = HashMap::new();
918        attrs.insert("tenant_id".to_string(), Value::String("forty-two".into()));
919        let result = b.before_update(&ctx, &mut attrs);
920        match result {
921            Err(DbError::TenantError(msg)) => {
922                assert!(msg.contains("expected I64"));
923            }
924            other => panic!("expected TenantError, got {:?}", other),
925        }
926    }
927
928    // --- 集成:BehaviorRegistry + TenantBehavior ---
929
930    #[test]
931    fn test_registry_with_tenant_behavior_insert() {
932        let r = BehaviorRegistry::new();
933        r.register(Box::new(TenantBehavior::default_fields()));
934        r.register(Box::new(TimestampBehavior::default_fields()));
935
936        let ctx = HookContext::default().with_tenant(7).with_timestamp(1000);
937        let mut attrs = HashMap::new();
938        r.before_insert(&ctx, &mut attrs).unwrap();
939        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(7)));
940        assert_eq!(attrs.get("created_at"), Some(&Value::I64(1000)));
941    }
942
943    #[test]
944    fn test_registry_with_tenant_behavior_update_strip() {
945        let r = BehaviorRegistry::new();
946        r.register(Box::new(
947            TenantBehavior::default_fields().with_update_policy(TenantUpdatePolicy::Strip),
948        ));
949
950        let ctx = HookContext::default().with_tenant(7);
951        let mut attrs = HashMap::new();
952        attrs.insert("tenant_id".to_string(), Value::I64(99));
953        attrs.insert("name".to_string(), Value::String("updated".into()));
954        r.before_update(&ctx, &mut attrs).unwrap();
955        // Strip 应移除 tenant_id
956        assert!(!attrs.contains_key("tenant_id"));
957        assert!(attrs.contains_key("name"));
958    }
959
960    #[test]
961    fn test_registry_unregister_tenant_behavior() {
962        let r = BehaviorRegistry::new();
963        r.register(Box::new(TenantBehavior::default_fields()));
964        assert_eq!(r.count(), 1);
965        assert!(r.unregister("TenantBehavior"));
966        assert_eq!(r.count(), 0);
967    }
968
969    #[test]
970    fn test_combined_tenant_timestamp_blameable_insert() {
971        // 模拟真实场景:同时使用 Tenant + Timestamp + Blameable
972        let r = BehaviorRegistry::new();
973        r.register(Box::new(TenantBehavior::default_fields()));
974        r.register(Box::new(TimestampBehavior::default_fields()));
975        r.register(Box::new(BlameableBehavior::default_fields()));
976
977        let ctx = HookContext::default()
978            .with_tenant(42)
979            .with_operator(1)
980            .with_timestamp(1700000000);
981        let mut attrs = HashMap::new();
982        r.before_insert(&ctx, &mut attrs).unwrap();
983        assert_eq!(attrs.get("tenant_id"), Some(&Value::I64(42)));
984        assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
985        assert_eq!(attrs.get("created_by"), Some(&Value::I64(1)));
986    }
987
988    #[test]
989    fn test_tenant_behavior_prevents_cross_tenant_tampering() {
990        // 安全场景:恶意用户尝试在 update 时将 tenant_id 改为其他租户
991        let r = BehaviorRegistry::new();
992        r.register(Box::new(TenantBehavior::default_fields())); // DenyMismatch
993
994        // 正常租户 42 的用户尝试把记录的 tenant_id 改为 99
995        let ctx = HookContext::default().with_tenant(42);
996        let mut attrs = HashMap::new();
997        attrs.insert("tenant_id".to_string(), Value::I64(99)); // 试图迁移到租户 99
998        attrs.insert("data".to_string(), Value::String("evil".into()));
999
1000        let result = r.before_update(&ctx, &mut attrs);
1001        assert!(result.is_err(), "cross-tenant tampering should be rejected");
1002    }
1003
1004    // ===== AttributeBehavior 测试 =====
1005
1006    #[test]
1007    fn test_attribute_behavior_before_insert() {
1008        let b = AttributeBehavior::new("uuid_gen", HookEvent::BeforeInsert, "uuid", |_ctx| {
1009            Value::String("auto-uuid".to_string())
1010        });
1011        let ctx = HookContext::default();
1012        let mut attrs = HashMap::new();
1013        b.before_insert(&ctx, &mut attrs).unwrap();
1014        assert_eq!(
1015            attrs.get("uuid"),
1016            Some(&Value::String("auto-uuid".to_string()))
1017        );
1018    }
1019
1020    #[test]
1021    fn test_attribute_behavior_event_filter() {
1022        // 注册 BeforeInsert 事件,但触发 before_update,不应执行
1023        let b = AttributeBehavior::new("test", HookEvent::BeforeInsert, "field", |_ctx| {
1024            Value::I64(1)
1025        });
1026        let ctx = HookContext::default();
1027        let mut attrs = HashMap::new();
1028        b.before_update(&ctx, &mut attrs).unwrap();
1029        assert!(!attrs.contains_key("field"));
1030    }
1031
1032    // ===== BehaviorRegistry 测试 =====
1033
1034    #[test]
1035    fn test_registry_register_and_count() {
1036        let r = BehaviorRegistry::new();
1037        assert_eq!(r.count(), 0);
1038        r.register(Box::new(TimestampBehavior::default_fields()));
1039        assert_eq!(r.count(), 1);
1040        r.register(Box::new(BlameableBehavior::default_fields()));
1041        assert_eq!(r.count(), 2);
1042    }
1043
1044    #[test]
1045    fn test_registry_unregister_by_name() {
1046        let r = BehaviorRegistry::new();
1047        r.register(Box::new(TimestampBehavior::default_fields()));
1048        r.register(Box::new(BlameableBehavior::default_fields()));
1049        assert_eq!(r.count(), 2);
1050
1051        let removed = r.unregister("TimestampBehavior");
1052        assert!(removed);
1053        assert_eq!(r.count(), 1);
1054
1055        // 不存在的 name 返回 false
1056        let removed2 = r.unregister("NonExistent");
1057        assert!(!removed2);
1058    }
1059
1060    #[test]
1061    fn test_registry_names() {
1062        let r = BehaviorRegistry::new();
1063        r.register(Box::new(TimestampBehavior::default_fields()));
1064        r.register(Box::new(BlameableBehavior::default_fields()));
1065        let names = r.names();
1066        assert!(names.contains(&"TimestampBehavior"));
1067        assert!(names.contains(&"BlameableBehavior"));
1068    }
1069
1070    #[test]
1071    fn test_registry_before_insert_dispatches_all() {
1072        let r = BehaviorRegistry::new();
1073        r.register(Box::new(TimestampBehavior::default_fields()));
1074        r.register(Box::new(BlameableBehavior::default_fields()));
1075
1076        let ctx = HookContext::default()
1077            .with_operator(100)
1078            .with_timestamp(1700000000);
1079        let mut attrs = HashMap::new();
1080        r.before_insert(&ctx, &mut attrs).unwrap();
1081
1082        // 两个 Behavior 都应执行
1083        assert_eq!(attrs.get("created_at"), Some(&Value::I64(1700000000)));
1084        assert_eq!(attrs.get("created_by"), Some(&Value::I64(100)));
1085    }
1086
1087    #[test]
1088    fn test_registry_before_update_dispatches_all() {
1089        let r = BehaviorRegistry::new();
1090        r.register(Box::new(TimestampBehavior::default_fields()));
1091        r.register(Box::new(BlameableBehavior::default_fields()));
1092
1093        let ctx = HookContext::default()
1094            .with_operator(200)
1095            .with_timestamp(1800000000);
1096        let mut attrs = HashMap::new();
1097        r.before_update(&ctx, &mut attrs).unwrap();
1098
1099        // update 只填充 updated_* 字段
1100        assert!(!attrs.contains_key("created_at"));
1101        assert_eq!(attrs.get("updated_at"), Some(&Value::I64(1800000000)));
1102        assert!(!attrs.contains_key("created_by"));
1103        assert_eq!(attrs.get("updated_by"), Some(&Value::I64(200)));
1104    }
1105
1106    #[test]
1107    fn test_registry_clear() {
1108        let r = BehaviorRegistry::new();
1109        r.register(Box::new(TimestampBehavior::default_fields()));
1110        r.register(Box::new(BlameableBehavior::default_fields()));
1111        assert_eq!(r.count(), 2);
1112
1113        r.clear();
1114        assert_eq!(r.count(), 0);
1115    }
1116
1117    #[test]
1118    fn test_registry_default() {
1119        let r = BehaviorRegistry::default();
1120        assert_eq!(r.count(), 0);
1121    }
1122
1123    #[test]
1124    fn test_registry_empty_dispatches_no_op() {
1125        // 空 registry 分发事件应该是 no-op
1126        let r = BehaviorRegistry::new();
1127        let ctx = HookContext::default();
1128        let mut attrs = HashMap::new();
1129        assert!(r.before_insert(&ctx, &mut attrs).is_ok());
1130        assert!(r.before_update(&ctx, &mut attrs).is_ok());
1131        assert!(r.before_delete(&ctx, &mut attrs).is_ok());
1132        assert!(r.after_find(&ctx, &mut attrs).is_ok());
1133        assert!(attrs.is_empty());
1134    }
1135
1136    #[test]
1137    fn test_combined_timestamp_and_blameable() {
1138        // 模拟真实场景:同时使用 TimestampBehavior + BlameableBehavior
1139        let r = BehaviorRegistry::new();
1140        r.register(Box::new(TimestampBehavior::default_fields()));
1141        r.register(Box::new(BlameableBehavior::default_fields()));
1142
1143        // 模拟 insert
1144        let ctx1 = HookContext::default().with_operator(1).with_timestamp(1000);
1145        let mut attrs1 = HashMap::new();
1146        r.before_insert(&ctx1, &mut attrs1).unwrap();
1147        assert_eq!(attrs1.get("created_at"), Some(&Value::I64(1000)));
1148        assert_eq!(attrs1.get("updated_at"), Some(&Value::I64(1000)));
1149        assert_eq!(attrs1.get("created_by"), Some(&Value::I64(1)));
1150        assert_eq!(attrs1.get("updated_by"), Some(&Value::I64(1)));
1151
1152        // 模拟 update(不同操作人、不同时间)
1153        let ctx2 = HookContext::default().with_operator(2).with_timestamp(2000);
1154        let mut attrs2 = HashMap::new();
1155        r.before_update(&ctx2, &mut attrs2).unwrap();
1156        assert!(!attrs2.contains_key("created_at"));
1157        assert_eq!(attrs2.get("updated_at"), Some(&Value::I64(2000)));
1158        assert!(!attrs2.contains_key("created_by"));
1159        assert_eq!(attrs2.get("updated_by"), Some(&Value::I64(2)));
1160    }
1161}