Skip to main content

aster/auto_reply/
registry.rs

1//! 触发器注册表
2//!
3//! 管理已注册的自动回复触发器。
4
5use serde::{Deserialize, Serialize};
6
7use crate::auto_reply::types::{TriggerConfig, TriggerType};
8
9/// 自动回复触发器
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct AutoReplyTrigger {
12    /// 触发器 ID
13    pub id: String,
14    /// 触发器名称
15    pub name: String,
16    /// 是否启用
17    #[serde(default = "default_true")]
18    pub enabled: bool,
19    /// 触发类型
20    pub trigger_type: TriggerType,
21    /// 触发配置
22    pub config: TriggerConfig,
23    /// 优先级(数字越小优先级越高)
24    #[serde(default = "default_priority")]
25    pub priority: u32,
26    /// 响应模板(可选)
27    #[serde(default)]
28    pub response_template: Option<String>,
29}
30
31fn default_true() -> bool {
32    true
33}
34
35fn default_priority() -> u32 {
36    100
37}
38
39/// 触发器注册表
40pub struct TriggerRegistry {
41    /// 已注册的触发器
42    triggers: Vec<AutoReplyTrigger>,
43}
44
45impl Default for TriggerRegistry {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51impl TriggerRegistry {
52    /// 创建新的注册表
53    pub fn new() -> Self {
54        Self {
55            triggers: Vec::new(),
56        }
57    }
58
59    /// 注册触发器
60    pub fn register(&mut self, trigger: AutoReplyTrigger) {
61        self.triggers.push(trigger);
62    }
63
64    /// 注销触发器
65    pub fn unregister(&mut self, trigger_id: &str) -> Option<AutoReplyTrigger> {
66        if let Some(pos) = self.triggers.iter().position(|t| t.id == trigger_id) {
67            Some(self.triggers.remove(pos))
68        } else {
69            None
70        }
71    }
72
73    /// 获取所有启用的触发器(按优先级排序)
74    pub fn get_enabled_triggers(&self) -> Vec<&AutoReplyTrigger> {
75        let mut triggers: Vec<_> = self.triggers.iter().filter(|t| t.enabled).collect();
76        triggers.sort_by_key(|t| t.priority);
77        triggers
78    }
79
80    /// 根据 ID 获取触发器
81    pub fn get_trigger(&self, trigger_id: &str) -> Option<&AutoReplyTrigger> {
82        self.triggers.iter().find(|t| t.id == trigger_id)
83    }
84
85    /// 更新触发器
86    pub fn update_trigger(&mut self, trigger: AutoReplyTrigger) -> bool {
87        if let Some(existing) = self.triggers.iter_mut().find(|t| t.id == trigger.id) {
88            *existing = trigger;
89            true
90        } else {
91            false
92        }
93    }
94
95    /// 获取所有触发器
96    ///
97    /// 返回所有已注册的触发器(包括禁用的)。
98    pub fn get_all_triggers(&self) -> &[AutoReplyTrigger] {
99        &self.triggers
100    }
101
102    /// 获取触发器数量
103    pub fn len(&self) -> usize {
104        self.triggers.len()
105    }
106
107    /// 检查注册表是否为空
108    pub fn is_empty(&self) -> bool {
109        self.triggers.is_empty()
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::auto_reply::types::KeywordTriggerConfig;
117    use proptest::prelude::*;
118
119    /// 创建测试用的触发器
120    fn create_test_trigger(id: &str, priority: u32, enabled: bool) -> AutoReplyTrigger {
121        AutoReplyTrigger {
122            id: id.to_string(),
123            name: format!("Test Trigger {}", id),
124            enabled,
125            trigger_type: TriggerType::Keyword,
126            config: TriggerConfig::Keyword(KeywordTriggerConfig {
127                patterns: vec!["test".to_string()],
128                case_insensitive: false,
129                use_regex: false,
130            }),
131            priority,
132            response_template: None,
133        }
134    }
135
136    /// 创建 Mention 类型的触发器
137    fn create_mention_trigger(id: &str, priority: u32) -> AutoReplyTrigger {
138        AutoReplyTrigger {
139            id: id.to_string(),
140            name: format!("Mention Trigger {}", id),
141            enabled: true,
142            trigger_type: TriggerType::Mention,
143            config: TriggerConfig::Mention,
144            priority,
145            response_template: Some("Hello!".to_string()),
146        }
147    }
148
149    // ========== TriggerRegistry 测试 ==========
150
151    #[test]
152    fn test_registry_new() {
153        let registry = TriggerRegistry::new();
154        assert!(registry.get_enabled_triggers().is_empty());
155    }
156
157    #[test]
158    fn test_registry_default() {
159        let registry = TriggerRegistry::default();
160        assert!(registry.get_enabled_triggers().is_empty());
161    }
162
163    #[test]
164    fn test_register_trigger() {
165        let mut registry = TriggerRegistry::new();
166        let trigger = create_test_trigger("t1", 10, true);
167
168        registry.register(trigger);
169
170        assert_eq!(registry.get_enabled_triggers().len(), 1);
171        assert!(registry.get_trigger("t1").is_some());
172    }
173
174    #[test]
175    fn test_register_multiple_triggers() {
176        let mut registry = TriggerRegistry::new();
177
178        registry.register(create_test_trigger("t1", 10, true));
179        registry.register(create_test_trigger("t2", 20, true));
180        registry.register(create_test_trigger("t3", 5, true));
181
182        assert_eq!(registry.get_enabled_triggers().len(), 3);
183    }
184
185    #[test]
186    fn test_unregister_existing_trigger() {
187        let mut registry = TriggerRegistry::new();
188        registry.register(create_test_trigger("t1", 10, true));
189        registry.register(create_test_trigger("t2", 20, true));
190
191        let removed = registry.unregister("t1");
192
193        assert!(removed.is_some());
194        assert_eq!(removed.unwrap().id, "t1");
195        assert!(registry.get_trigger("t1").is_none());
196        assert!(registry.get_trigger("t2").is_some());
197    }
198
199    #[test]
200    fn test_unregister_nonexistent_trigger() {
201        let mut registry = TriggerRegistry::new();
202        registry.register(create_test_trigger("t1", 10, true));
203
204        let removed = registry.unregister("nonexistent");
205
206        assert!(removed.is_none());
207        assert!(registry.get_trigger("t1").is_some());
208    }
209
210    #[test]
211    fn test_get_enabled_triggers_filters_disabled() {
212        let mut registry = TriggerRegistry::new();
213        registry.register(create_test_trigger("t1", 10, true));
214        registry.register(create_test_trigger("t2", 20, false)); // disabled
215        registry.register(create_test_trigger("t3", 5, true));
216
217        let enabled = registry.get_enabled_triggers();
218
219        assert_eq!(enabled.len(), 2);
220        assert!(enabled.iter().all(|t| t.enabled));
221    }
222
223    #[test]
224    fn test_get_enabled_triggers_sorted_by_priority() {
225        let mut registry = TriggerRegistry::new();
226        registry.register(create_test_trigger("t1", 100, true));
227        registry.register(create_test_trigger("t2", 10, true));
228        registry.register(create_test_trigger("t3", 50, true));
229
230        let enabled = registry.get_enabled_triggers();
231
232        assert_eq!(enabled.len(), 3);
233        // 优先级数字越小越优先
234        assert_eq!(enabled[0].id, "t2"); // priority 10
235        assert_eq!(enabled[1].id, "t3"); // priority 50
236        assert_eq!(enabled[2].id, "t1"); // priority 100
237    }
238
239    #[test]
240    fn test_get_trigger_existing() {
241        let mut registry = TriggerRegistry::new();
242        registry.register(create_test_trigger("t1", 10, true));
243
244        let trigger = registry.get_trigger("t1");
245
246        assert!(trigger.is_some());
247        assert_eq!(trigger.unwrap().id, "t1");
248    }
249
250    #[test]
251    fn test_get_trigger_nonexistent() {
252        let registry = TriggerRegistry::new();
253
254        let trigger = registry.get_trigger("nonexistent");
255
256        assert!(trigger.is_none());
257    }
258
259    #[test]
260    fn test_update_existing_trigger() {
261        let mut registry = TriggerRegistry::new();
262        registry.register(create_test_trigger("t1", 10, true));
263
264        let mut updated = create_test_trigger("t1", 50, false);
265        updated.name = "Updated Name".to_string();
266
267        let result = registry.update_trigger(updated);
268
269        assert!(result);
270        let trigger = registry.get_trigger("t1").unwrap();
271        assert_eq!(trigger.name, "Updated Name");
272        assert_eq!(trigger.priority, 50);
273        assert!(!trigger.enabled);
274    }
275
276    #[test]
277    fn test_update_nonexistent_trigger() {
278        let mut registry = TriggerRegistry::new();
279        registry.register(create_test_trigger("t1", 10, true));
280
281        let new_trigger = create_test_trigger("t2", 20, true);
282        let result = registry.update_trigger(new_trigger);
283
284        assert!(!result);
285        assert!(registry.get_trigger("t2").is_none());
286    }
287
288    // ========== AutoReplyTrigger 测试 ==========
289
290    #[test]
291    fn test_trigger_serialization_roundtrip() {
292        let trigger = create_mention_trigger("mention-1", 5);
293
294        let json = serde_json::to_string(&trigger).expect("Should serialize");
295        let parsed: AutoReplyTrigger = serde_json::from_str(&json).expect("Should deserialize");
296
297        assert_eq!(parsed.id, trigger.id);
298        assert_eq!(parsed.name, trigger.name);
299        assert_eq!(parsed.enabled, trigger.enabled);
300        assert_eq!(parsed.priority, trigger.priority);
301        assert_eq!(parsed.response_template, trigger.response_template);
302    }
303
304    #[test]
305    fn test_trigger_default_values() {
306        // 测试 serde 默认值
307        let json = r#"{
308            "id": "test",
309            "name": "Test",
310            "trigger_type": "mention",
311            "config": { "type": "mention" }
312        }"#;
313
314        let trigger: AutoReplyTrigger =
315            serde_json::from_str(json).expect("Should deserialize with defaults");
316
317        assert!(trigger.enabled); // default_true
318        assert_eq!(trigger.priority, 100); // default_priority
319        assert!(trigger.response_template.is_none()); // default None
320    }
321
322    #[test]
323    fn test_trigger_with_keyword_config() {
324        let trigger = create_test_trigger("kw-1", 10, true);
325
326        match &trigger.config {
327            TriggerConfig::Keyword(config) => {
328                assert_eq!(config.patterns, vec!["test".to_string()]);
329                assert!(!config.case_insensitive);
330                assert!(!config.use_regex);
331            }
332            _ => panic!("Expected Keyword config"),
333        }
334    }
335
336    #[test]
337    fn test_trigger_clone() {
338        let trigger = create_mention_trigger("m1", 10);
339        let cloned = trigger.clone();
340
341        assert_eq!(cloned.id, trigger.id);
342        assert_eq!(cloned.name, trigger.name);
343        assert_eq!(cloned.priority, trigger.priority);
344    }
345
346    // ============================================================================
347    // Property-Based Tests
348    // ============================================================================
349    // Feature: auto-reply-mechanism, Property 6: 触发器评估优先级
350    // **Validates: Requirements 6.1-6.3**
351
352    /// 生成有效的触发器 ID
353    fn arb_trigger_id() -> impl Strategy<Value = String> {
354        "[a-zA-Z][a-zA-Z0-9_-]{0,19}".prop_map(|s| s)
355    }
356
357    /// 生成有效的优先级值(0-1000)
358    fn arb_priority() -> impl Strategy<Value = u32> {
359        0u32..=1000u32
360    }
361
362    /// 生成触发器配置
363    fn arb_trigger_config() -> impl Strategy<Value = (TriggerType, TriggerConfig)> {
364        prop_oneof![
365            Just((TriggerType::Mention, TriggerConfig::Mention)),
366            Just((TriggerType::DirectMessage, TriggerConfig::DirectMessage)),
367            prop::collection::vec("[a-zA-Z0-9]{1,10}", 1..5).prop_map(|patterns| {
368                (
369                    TriggerType::Keyword,
370                    TriggerConfig::Keyword(KeywordTriggerConfig {
371                        patterns,
372                        case_insensitive: false,
373                        use_regex: false,
374                    }),
375                )
376            }),
377        ]
378    }
379
380    /// 生成单个触发器
381    fn arb_trigger() -> impl Strategy<Value = AutoReplyTrigger> {
382        (
383            arb_trigger_id(),
384            arb_priority(),
385            any::<bool>(),
386            arb_trigger_config(),
387        )
388            .prop_map(
389                |(id, priority, enabled, (trigger_type, config))| AutoReplyTrigger {
390                    id,
391                    name: "Test Trigger".to_string(),
392                    enabled,
393                    trigger_type,
394                    config,
395                    priority,
396                    response_template: None,
397                },
398            )
399    }
400
401    /// 生成具有唯一 ID 的触发器列表
402    fn arb_triggers_with_unique_ids() -> impl Strategy<Value = Vec<AutoReplyTrigger>> {
403        prop::collection::vec(arb_trigger(), 0..20).prop_map(|triggers| {
404            // 确保 ID 唯一
405            let mut seen_ids = std::collections::HashSet::new();
406            triggers
407                .into_iter()
408                .enumerate()
409                .map(|(i, mut t)| {
410                    // 如果 ID 重复,添加索引后缀
411                    while seen_ids.contains(&t.id) {
412                        t.id = format!("{}_{}", t.id, i);
413                    }
414                    seen_ids.insert(t.id.clone());
415                    t
416                })
417                .collect()
418        })
419    }
420
421    proptest! {
422        #![proptest_config(ProptestConfig::with_cases(100))]
423
424        /// Property 6.1: get_enabled_triggers 返回所有启用的触发器
425        /// **Validates: Requirement 6.1**
426        ///
427        /// WHEN checking a message, THE Auto_Reply_Manager SHALL evaluate all enabled triggers
428        #[test]
429        fn prop_get_enabled_triggers_returns_all_enabled(
430            triggers in arb_triggers_with_unique_ids()
431        ) {
432            // Feature: auto-reply-mechanism, Property 6: 触发器评估优先级
433            // Validates: Requirements 6.1-6.3
434
435            let mut registry = TriggerRegistry::new();
436            for trigger in &triggers {
437                registry.register(trigger.clone());
438            }
439
440            let enabled = registry.get_enabled_triggers();
441            let expected_enabled_count = triggers.iter().filter(|t| t.enabled).count();
442
443            // 验证返回的触发器数量与启用的触发器数量一致
444            prop_assert_eq!(
445                enabled.len(),
446                expected_enabled_count,
447                "Expected {} enabled triggers, got {}",
448                expected_enabled_count,
449                enabled.len()
450            );
451
452            // 验证所有返回的触发器都是启用的
453            for trigger in &enabled {
454                prop_assert!(
455                    trigger.enabled,
456                    "Trigger {} should be enabled",
457                    trigger.id
458                );
459            }
460        }
461
462        /// Property 6.2: get_enabled_triggers 按优先级排序(priority 值最小的排在前面)
463        /// **Validates: Requirement 6.2**
464        ///
465        /// WHEN multiple triggers match, THE Auto_Reply_Manager SHALL return the highest priority trigger
466        #[test]
467        fn prop_get_enabled_triggers_sorted_by_priority(
468            triggers in arb_triggers_with_unique_ids()
469        ) {
470            // Feature: auto-reply-mechanism, Property 6: 触发器评估优先级
471            // Validates: Requirements 6.1-6.3
472
473            let mut registry = TriggerRegistry::new();
474            for trigger in &triggers {
475                registry.register(trigger.clone());
476            }
477
478            let enabled = registry.get_enabled_triggers();
479
480            // 验证触发器按优先级升序排列(priority 值越小优先级越高)
481            for i in 1..enabled.len() {
482                prop_assert!(
483                    enabled[i - 1].priority <= enabled[i].priority,
484                    "Triggers should be sorted by priority: {} (priority {}) should come before {} (priority {})",
485                    enabled[i - 1].id,
486                    enabled[i - 1].priority,
487                    enabled[i].id,
488                    enabled[i].priority
489                );
490            }
491        }
492
493        /// Property 6.3: 空注册表返回空列表
494        /// **Validates: Requirement 6.3**
495        ///
496        /// WHEN no triggers match, THE Auto_Reply_Manager SHALL return a non-trigger result
497        #[test]
498        fn prop_empty_registry_returns_empty_list(_seed in any::<u64>()) {
499            // Feature: auto-reply-mechanism, Property 6: 触发器评估优先级
500            // Validates: Requirements 6.1-6.3
501
502            let registry = TriggerRegistry::new();
503            let enabled = registry.get_enabled_triggers();
504
505            prop_assert!(
506                enabled.is_empty(),
507                "Empty registry should return empty list, got {} triggers",
508                enabled.len()
509            );
510        }
511
512        /// Property 6.4: 所有触发器禁用时返回空列表
513        /// **Validates: Requirement 6.3**
514        #[test]
515        fn prop_all_disabled_returns_empty_list(
516            triggers in prop::collection::vec(arb_trigger(), 1..10)
517        ) {
518            // Feature: auto-reply-mechanism, Property 6: 触发器评估优先级
519            // Validates: Requirements 6.1-6.3
520
521            let mut registry = TriggerRegistry::new();
522
523            // 注册所有触发器,但全部禁用
524            for (i, mut trigger) in triggers.into_iter().enumerate() {
525                trigger.id = format!("trigger_{}", i); // 确保 ID 唯一
526                trigger.enabled = false;
527                registry.register(trigger);
528            }
529
530            let enabled = registry.get_enabled_triggers();
531
532            prop_assert!(
533                enabled.is_empty(),
534                "All disabled triggers should return empty list, got {} triggers",
535                enabled.len()
536            );
537        }
538
539        /// Property 6.5: 第一个返回的触发器具有最高优先级(最小 priority 值)
540        /// **Validates: Requirement 6.2**
541        #[test]
542        fn prop_first_trigger_has_highest_priority(
543            triggers in arb_triggers_with_unique_ids()
544                .prop_filter("Need at least one enabled trigger", |ts| ts.iter().any(|t| t.enabled))
545        ) {
546            // Feature: auto-reply-mechanism, Property 6: 触发器评估优先级
547            // Validates: Requirements 6.1-6.3
548
549            let mut registry = TriggerRegistry::new();
550            for trigger in &triggers {
551                registry.register(trigger.clone());
552            }
553
554            let enabled = registry.get_enabled_triggers();
555
556            // 找到所有启用触发器中的最小优先级
557            let min_priority = triggers
558                .iter()
559                .filter(|t| t.enabled)
560                .map(|t| t.priority)
561                .min()
562                .unwrap();
563
564            // 验证第一个触发器的优先级是最小的
565            prop_assert_eq!(
566                enabled[0].priority,
567                min_priority,
568                "First trigger should have minimum priority {}, got {}",
569                min_priority,
570                enabled[0].priority
571            );
572        }
573
574        /// Property 6.6: 注册顺序不影响优先级排序
575        /// **Validates: Requirement 6.2**
576        #[test]
577        fn prop_registration_order_does_not_affect_priority_sort(
578            triggers in arb_triggers_with_unique_ids()
579                .prop_filter("Need at least 2 enabled triggers", |ts| ts.iter().filter(|t| t.enabled).count() >= 2)
580        ) {
581            // Feature: auto-reply-mechanism, Property 6: 触发器评估优先级
582            // Validates: Requirements 6.1-6.3
583
584            // 正序注册
585            let mut registry1 = TriggerRegistry::new();
586            for trigger in &triggers {
587                registry1.register(trigger.clone());
588            }
589
590            // 逆序注册
591            let mut registry2 = TriggerRegistry::new();
592            for trigger in triggers.iter().rev() {
593                registry2.register(trigger.clone());
594            }
595
596            let enabled1 = registry1.get_enabled_triggers();
597            let enabled2 = registry2.get_enabled_triggers();
598
599            // 验证两种注册顺序产生相同的优先级排序
600            prop_assert_eq!(
601                enabled1.len(),
602                enabled2.len(),
603                "Both registries should have same number of enabled triggers"
604            );
605
606            // 验证优先级顺序一致
607            for (t1, t2) in enabled1.iter().zip(enabled2.iter()) {
608                prop_assert_eq!(
609                    t1.priority,
610                    t2.priority,
611                    "Priority order should be consistent regardless of registration order"
612                );
613            }
614        }
615
616        /// Property 6.7: 相同优先级的触发器都被返回
617        /// **Validates: Requirements 6.1, 6.2**
618        #[test]
619        fn prop_same_priority_triggers_all_returned(
620            base_priority in arb_priority(),
621            count in 2usize..5
622        ) {
623            // Feature: auto-reply-mechanism, Property 6: 触发器评估优先级
624            // Validates: Requirements 6.1-6.3
625
626            let mut registry = TriggerRegistry::new();
627
628            // 创建多个相同优先级的触发器
629            for i in 0..count {
630                let trigger = AutoReplyTrigger {
631                    id: format!("trigger_{}", i),
632                    name: format!("Trigger {}", i),
633                    enabled: true,
634                    trigger_type: TriggerType::Mention,
635                    config: TriggerConfig::Mention,
636                    priority: base_priority,
637                    response_template: None,
638                };
639                registry.register(trigger);
640            }
641
642            let enabled = registry.get_enabled_triggers();
643
644            // 验证所有触发器都被返回
645            prop_assert_eq!(
646                enabled.len(),
647                count,
648                "All {} triggers with same priority should be returned, got {}",
649                count,
650                enabled.len()
651            );
652
653            // 验证所有触发器优先级相同
654            for trigger in &enabled {
655                prop_assert_eq!(
656                    trigger.priority,
657                    base_priority,
658                    "All triggers should have priority {}, got {}",
659                    base_priority,
660                    trigger.priority
661                );
662            }
663        }
664
665        /// Property 6.8: 禁用的触发器不影响启用触发器的优先级排序
666        /// **Validates: Requirements 6.1, 6.2**
667        #[test]
668        fn prop_disabled_triggers_do_not_affect_enabled_order(
669            enabled_priorities in prop::collection::vec(arb_priority(), 1..5),
670            disabled_priorities in prop::collection::vec(arb_priority(), 1..5)
671        ) {
672            // Feature: auto-reply-mechanism, Property 6: 触发器评估优先级
673            // Validates: Requirements 6.1-6.3
674
675            let mut registry = TriggerRegistry::new();
676
677            // 注册启用的触发器
678            for (i, priority) in enabled_priorities.iter().enumerate() {
679                let trigger = AutoReplyTrigger {
680                    id: format!("enabled_{}", i),
681                    name: format!("Enabled Trigger {}", i),
682                    enabled: true,
683                    trigger_type: TriggerType::Mention,
684                    config: TriggerConfig::Mention,
685                    priority: *priority,
686                    response_template: None,
687                };
688                registry.register(trigger);
689            }
690
691            // 注册禁用的触发器(可能有更高优先级)
692            for (i, priority) in disabled_priorities.iter().enumerate() {
693                let trigger = AutoReplyTrigger {
694                    id: format!("disabled_{}", i),
695                    name: format!("Disabled Trigger {}", i),
696                    enabled: false,
697                    trigger_type: TriggerType::Mention,
698                    config: TriggerConfig::Mention,
699                    priority: *priority,
700                    response_template: None,
701                };
702                registry.register(trigger);
703            }
704
705            let enabled = registry.get_enabled_triggers();
706
707            // 验证只返回启用的触发器
708            prop_assert_eq!(
709                enabled.len(),
710                enabled_priorities.len(),
711                "Should only return enabled triggers"
712            );
713
714            // 验证禁用的触发器不在结果中
715            for trigger in &enabled {
716                prop_assert!(
717                    !trigger.id.starts_with("disabled_"),
718                    "Disabled trigger {} should not be in result",
719                    trigger.id
720                );
721            }
722
723            // 验证启用的触发器按优先级排序
724            for i in 1..enabled.len() {
725                prop_assert!(
726                    enabled[i - 1].priority <= enabled[i].priority,
727                    "Enabled triggers should be sorted by priority"
728                );
729            }
730        }
731    }
732}