tuitbot_core/config/
defaults.rs1use super::{
7 AuthConfig, IntervalsConfig, LimitsConfig, McpPolicyConfig, ScoringConfig, StorageConfig,
8};
9
10impl Default for AuthConfig {
11 fn default() -> Self {
12 Self {
13 mode: "manual".to_string(),
14 callback_host: "127.0.0.1".to_string(),
15 callback_port: 8080,
16 }
17 }
18}
19
20impl Default for ScoringConfig {
21 fn default() -> Self {
22 Self {
23 threshold: 60,
24 keyword_relevance_max: 25.0,
25 follower_count_max: 15.0,
26 recency_max: 10.0,
27 engagement_rate_max: 15.0,
28 reply_count_max: 15.0,
29 content_type_max: 10.0,
30 }
31 }
32}
33
34impl Default for LimitsConfig {
35 fn default() -> Self {
36 Self {
37 max_replies_per_day: 5,
38 max_tweets_per_day: 6,
39 max_threads_per_week: 1,
40 min_action_delay_seconds: 45,
41 max_action_delay_seconds: 180,
42 max_replies_per_author_per_day: 1,
43 banned_phrases: vec![
44 "check out".to_string(),
45 "you should try".to_string(),
46 "I recommend".to_string(),
47 "link in bio".to_string(),
48 ],
49 product_mention_ratio: 0.2,
50 }
51 }
52}
53
54impl Default for IntervalsConfig {
55 fn default() -> Self {
56 Self {
57 mentions_check_seconds: 300,
58 discovery_search_seconds: 900,
59 content_post_window_seconds: 10800,
60 thread_interval_seconds: 604800,
61 }
62 }
63}
64
65impl Default for StorageConfig {
66 fn default() -> Self {
67 Self {
68 db_path: "~/.tuitbot/tuitbot.db".to_string(),
69 retention_days: 90,
70 }
71 }
72}
73
74impl Default for McpPolicyConfig {
75 fn default() -> Self {
76 Self {
77 enforce_for_mutations: true,
78 require_approval_for: vec![
79 "post_tweet".to_string(),
80 "reply_to_tweet".to_string(),
81 "follow_user".to_string(),
82 "like_tweet".to_string(),
83 ],
84 blocked_tools: Vec::new(),
85 dry_run_mutations: false,
86 max_mutations_per_hour: 20,
87 template: None,
88 rules: Vec::new(),
89 rate_limits: Vec::new(),
90 }
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn auth_config_defaults() {
100 let config = AuthConfig::default();
101 assert_eq!(config.mode, "manual");
102 assert_eq!(config.callback_host, "127.0.0.1");
103 assert_eq!(config.callback_port, 8080);
104 }
105
106 #[test]
107 fn scoring_config_defaults() {
108 let config = ScoringConfig::default();
109 assert_eq!(config.threshold, 60);
110 assert!((config.keyword_relevance_max - 25.0).abs() < f32::EPSILON);
111 assert!((config.follower_count_max - 15.0).abs() < f32::EPSILON);
112 assert!((config.recency_max - 10.0).abs() < f32::EPSILON);
113 assert!((config.engagement_rate_max - 15.0).abs() < f32::EPSILON);
114 assert!((config.reply_count_max - 15.0).abs() < f32::EPSILON);
115 assert!((config.content_type_max - 10.0).abs() < f32::EPSILON);
116 }
117
118 #[test]
119 fn scoring_config_weights_sum_to_90() {
120 let config = ScoringConfig::default();
121 let sum = config.keyword_relevance_max
122 + config.follower_count_max
123 + config.recency_max
124 + config.engagement_rate_max
125 + config.reply_count_max
126 + config.content_type_max;
127 assert!((sum - 90.0).abs() < f32::EPSILON);
128 }
129
130 #[test]
131 fn limits_config_defaults() {
132 let config = LimitsConfig::default();
133 assert_eq!(config.max_replies_per_day, 5);
134 assert_eq!(config.max_tweets_per_day, 6);
135 assert_eq!(config.max_threads_per_week, 1);
136 assert_eq!(config.min_action_delay_seconds, 45);
137 assert_eq!(config.max_action_delay_seconds, 180);
138 assert_eq!(config.max_replies_per_author_per_day, 1);
139 assert_eq!(config.banned_phrases.len(), 4);
140 assert!(config.banned_phrases.contains(&"check out".to_string()));
141 assert!(config
142 .banned_phrases
143 .contains(&"you should try".to_string()));
144 assert!(config.banned_phrases.contains(&"I recommend".to_string()));
145 assert!(config.banned_phrases.contains(&"link in bio".to_string()));
146 assert!((config.product_mention_ratio - 0.2).abs() < f32::EPSILON);
147 }
148
149 #[test]
150 fn limits_config_min_delay_less_than_max() {
151 let config = LimitsConfig::default();
152 assert!(config.min_action_delay_seconds < config.max_action_delay_seconds);
153 }
154
155 #[test]
156 fn intervals_config_defaults() {
157 let config = IntervalsConfig::default();
158 assert_eq!(config.mentions_check_seconds, 300);
159 assert_eq!(config.discovery_search_seconds, 900);
160 assert_eq!(config.content_post_window_seconds, 10800);
161 assert_eq!(config.thread_interval_seconds, 604800);
162 }
163
164 #[test]
165 fn intervals_config_reasonable_ranges() {
166 let config = IntervalsConfig::default();
167 assert_eq!(config.mentions_check_seconds, 5 * 60);
169 assert_eq!(config.discovery_search_seconds, 15 * 60);
171 assert_eq!(config.content_post_window_seconds, 3 * 60 * 60);
173 assert_eq!(config.thread_interval_seconds, 7 * 24 * 60 * 60);
175 }
176
177 #[test]
178 fn storage_config_defaults() {
179 let config = StorageConfig::default();
180 assert_eq!(config.db_path, "~/.tuitbot/tuitbot.db");
181 assert_eq!(config.retention_days, 90);
182 }
183
184 #[test]
185 fn mcp_policy_config_defaults() {
186 let config = McpPolicyConfig::default();
187 assert!(config.enforce_for_mutations);
188 assert!(!config.dry_run_mutations);
189 assert_eq!(config.max_mutations_per_hour, 20);
190 assert_eq!(config.require_approval_for.len(), 4);
191 assert!(config
192 .require_approval_for
193 .contains(&"post_tweet".to_string()));
194 assert!(config
195 .require_approval_for
196 .contains(&"reply_to_tweet".to_string()));
197 assert!(config
198 .require_approval_for
199 .contains(&"follow_user".to_string()));
200 assert!(config
201 .require_approval_for
202 .contains(&"like_tweet".to_string()));
203 assert!(config.blocked_tools.is_empty());
204 assert!(config.template.is_none());
205 assert!(config.rules.is_empty());
206 assert!(config.rate_limits.is_empty());
207 }
208}