1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct SmtpFixture {
9 pub identifier: String,
11
12 pub name: String,
14
15 #[serde(default)]
17 pub description: String,
18
19 pub match_criteria: MatchCriteria,
21
22 pub response: SmtpResponse,
24
25 #[serde(default)]
27 pub auto_reply: Option<AutoReply>,
28
29 #[serde(default)]
31 pub storage: StorageConfig,
32
33 #[serde(default)]
35 pub behavior: BehaviorConfig,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, Default)]
40pub struct MatchCriteria {
41 #[serde(default)]
43 pub recipient_pattern: Option<String>,
44
45 #[serde(default)]
47 pub sender_pattern: Option<String>,
48
49 #[serde(default)]
51 pub subject_pattern: Option<String>,
52
53 #[serde(default)]
55 pub match_all: bool,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct SmtpResponse {
61 pub status_code: u16,
63
64 pub message: String,
66
67 #[serde(default)]
69 pub delay_ms: u64,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct AutoReply {
75 pub enabled: bool,
77
78 pub from: String,
80
81 pub to: String,
83
84 pub subject: String,
86
87 pub body: String,
89
90 #[serde(default)]
92 pub html_body: Option<String>,
93
94 #[serde(default)]
96 pub headers: HashMap<String, String>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize, Default)]
101pub struct StorageConfig {
102 #[serde(default)]
104 pub save_to_mailbox: bool,
105
106 #[serde(default)]
108 pub export_to_file: Option<ExportConfig>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ExportConfig {
114 pub enabled: bool,
116
117 pub path: String,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, Default)]
123pub struct BehaviorConfig {
124 #[serde(default)]
126 pub failure_rate: f64,
127
128 #[serde(default)]
130 pub latency: Option<LatencyConfig>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct LatencyConfig {
136 pub min_ms: u64,
138
139 pub max_ms: u64,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct StoredEmail {
146 pub id: String,
148
149 pub from: String,
151
152 pub to: Vec<String>,
154
155 pub subject: String,
157
158 pub body: String,
160
161 pub headers: HashMap<String, String>,
163
164 pub received_at: chrono::DateTime<chrono::Utc>,
166
167 #[serde(default)]
169 pub raw: Option<Vec<u8>>,
170}
171
172impl SmtpFixture {
173 pub fn matches(&self, from: &str, to: &str, subject: &str) -> bool {
175 use regex::Regex;
176
177 if self.match_criteria.match_all {
179 return true;
180 }
181
182 if let Some(pattern) = &self.match_criteria.recipient_pattern {
184 if let Ok(re) = Regex::new(pattern) {
185 if !re.is_match(to) {
186 return false;
187 }
188 }
189 }
190
191 if let Some(pattern) = &self.match_criteria.sender_pattern {
193 if let Ok(re) = Regex::new(pattern) {
194 if !re.is_match(from) {
195 return false;
196 }
197 }
198 }
199
200 if let Some(pattern) = &self.match_criteria.subject_pattern {
202 if let Ok(re) = Regex::new(pattern) {
203 if !re.is_match(subject) {
204 return false;
205 }
206 }
207 }
208
209 true
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_fixture_matching() {
219 let fixture = SmtpFixture {
220 identifier: "test".to_string(),
221 name: "Test Fixture".to_string(),
222 description: "".to_string(),
223 match_criteria: MatchCriteria {
224 recipient_pattern: Some(r"^user.*@example\.com$".to_string()),
225 sender_pattern: None,
226 subject_pattern: None,
227 match_all: false,
228 },
229 response: SmtpResponse {
230 status_code: 250,
231 message: "OK".to_string(),
232 delay_ms: 0,
233 },
234 auto_reply: None,
235 storage: StorageConfig::default(),
236 behavior: BehaviorConfig::default(),
237 };
238
239 assert!(fixture.matches("sender@test.com", "user123@example.com", "Test"));
240 assert!(!fixture.matches("sender@test.com", "admin@example.com", "Test"));
241 }
242
243 #[test]
244 fn test_match_all_fixture() {
245 let fixture = SmtpFixture {
246 identifier: "default".to_string(),
247 name: "Default Fixture".to_string(),
248 description: "".to_string(),
249 match_criteria: MatchCriteria {
250 recipient_pattern: None,
251 sender_pattern: None,
252 subject_pattern: None,
253 match_all: true,
254 },
255 response: SmtpResponse {
256 status_code: 250,
257 message: "OK".to_string(),
258 delay_ms: 0,
259 },
260 auto_reply: None,
261 storage: StorageConfig::default(),
262 behavior: BehaviorConfig::default(),
263 };
264
265 assert!(fixture.matches("any@sender.com", "any@recipient.com", "Any Subject"));
266 }
267}