elif_orm/factory/
states.rs

1//! Factory states for applying common model variations
2
3use super::fake_data::*;
4use super::traits::FactoryState;
5use crate::error::OrmResult;
6use chrono::{DateTime, Utc};
7use serde_json::{json, Value};
8use std::collections::HashMap;
9
10/// Common user states
11#[derive(Debug, Clone)]
12pub struct ActiveState;
13
14#[async_trait::async_trait]
15impl<T> FactoryState<T> for ActiveState {
16    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
17        attributes.insert("status".to_string(), json!("active"));
18        attributes.insert("is_active".to_string(), json!(true));
19        Ok(())
20    }
21
22    fn state_name(&self) -> &'static str {
23        "Active"
24    }
25}
26
27#[derive(Debug, Clone)]
28pub struct InactiveState;
29
30#[async_trait::async_trait]
31impl<T> FactoryState<T> for InactiveState {
32    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
33        attributes.insert("status".to_string(), json!("inactive"));
34        attributes.insert("is_active".to_string(), json!(false));
35        Ok(())
36    }
37
38    fn state_name(&self) -> &'static str {
39        "Inactive"
40    }
41}
42
43#[derive(Debug, Clone)]
44pub struct PendingState;
45
46#[async_trait::async_trait]
47impl<T> FactoryState<T> for PendingState {
48    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
49        attributes.insert("status".to_string(), json!("pending"));
50        attributes.insert("is_verified".to_string(), json!(false));
51        attributes.insert("verified_at".to_string(), Value::Null);
52        Ok(())
53    }
54
55    fn state_name(&self) -> &'static str {
56        "Pending"
57    }
58}
59
60#[derive(Debug, Clone)]
61pub struct VerifiedState;
62
63#[async_trait::async_trait]
64impl<T> FactoryState<T> for VerifiedState {
65    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
66        attributes.insert("status".to_string(), json!("verified"));
67        attributes.insert("is_verified".to_string(), json!(true));
68        attributes.insert(
69            "verified_at".to_string(),
70            json!(fake_datetime().to_rfc3339()),
71        );
72        Ok(())
73    }
74
75    fn state_name(&self) -> &'static str {
76        "Verified"
77    }
78}
79
80/// Admin user state
81#[derive(Debug, Clone)]
82pub struct AdminState;
83
84#[async_trait::async_trait]
85impl<T> FactoryState<T> for AdminState {
86    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
87        attributes.insert("role".to_string(), json!("admin"));
88        attributes.insert("is_admin".to_string(), json!(true));
89        attributes.insert(
90            "permissions".to_string(),
91            json!(["read", "write", "delete", "admin"]),
92        );
93        Ok(())
94    }
95
96    fn state_name(&self) -> &'static str {
97        "Admin"
98    }
99}
100
101/// Moderator user state
102#[derive(Debug, Clone)]
103pub struct ModeratorState;
104
105#[async_trait::async_trait]
106impl<T> FactoryState<T> for ModeratorState {
107    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
108        attributes.insert("role".to_string(), json!("moderator"));
109        attributes.insert("is_admin".to_string(), json!(false));
110        attributes.insert(
111            "permissions".to_string(),
112            json!(["read", "write", "moderate"]),
113        );
114        Ok(())
115    }
116
117    fn state_name(&self) -> &'static str {
118        "Moderator"
119    }
120}
121
122/// Suspended user state
123#[derive(Debug, Clone)]
124pub struct SuspendedState;
125
126#[async_trait::async_trait]
127impl<T> FactoryState<T> for SuspendedState {
128    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
129        attributes.insert("status".to_string(), json!("suspended"));
130        attributes.insert("is_active".to_string(), json!(false));
131        attributes.insert(
132            "suspended_at".to_string(),
133            json!(fake_datetime().to_rfc3339()),
134        );
135        attributes.insert("suspension_reason".to_string(), json!("Policy violation"));
136        Ok(())
137    }
138
139    fn state_name(&self) -> &'static str {
140        "Suspended"
141    }
142}
143
144/// Published content state
145#[derive(Debug, Clone)]
146pub struct PublishedState;
147
148#[async_trait::async_trait]
149impl<T> FactoryState<T> for PublishedState {
150    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
151        attributes.insert("status".to_string(), json!("published"));
152        attributes.insert("is_published".to_string(), json!(true));
153        attributes.insert(
154            "published_at".to_string(),
155            json!(fake_datetime().to_rfc3339()),
156        );
157        Ok(())
158    }
159
160    fn state_name(&self) -> &'static str {
161        "Published"
162    }
163}
164
165/// Draft content state
166#[derive(Debug, Clone)]
167pub struct DraftState;
168
169#[async_trait::async_trait]
170impl<T> FactoryState<T> for DraftState {
171    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
172        attributes.insert("status".to_string(), json!("draft"));
173        attributes.insert("is_published".to_string(), json!(false));
174        attributes.insert("published_at".to_string(), Value::Null);
175        Ok(())
176    }
177
178    fn state_name(&self) -> &'static str {
179        "Draft"
180    }
181}
182
183/// Archived content state
184#[derive(Debug, Clone)]
185pub struct ArchivedState;
186
187#[async_trait::async_trait]
188impl<T> FactoryState<T> for ArchivedState {
189    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
190        attributes.insert("status".to_string(), json!("archived"));
191        attributes.insert("is_archived".to_string(), json!(true));
192        attributes.insert(
193            "archived_at".to_string(),
194            json!(fake_datetime().to_rfc3339()),
195        );
196        Ok(())
197    }
198
199    fn state_name(&self) -> &'static str {
200        "Archived"
201    }
202}
203
204/// Premium account state
205#[derive(Debug, Clone)]
206pub struct PremiumState;
207
208#[async_trait::async_trait]
209impl<T> FactoryState<T> for PremiumState {
210    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
211        attributes.insert("account_type".to_string(), json!("premium"));
212        attributes.insert("is_premium".to_string(), json!(true));
213        attributes.insert(
214            "premium_expires_at".to_string(),
215            json!(fake_future_datetime().to_rfc3339()),
216        );
217        Ok(())
218    }
219
220    fn state_name(&self) -> &'static str {
221        "Premium"
222    }
223}
224
225/// Free account state
226#[derive(Debug, Clone)]
227pub struct FreeState;
228
229#[async_trait::async_trait]
230impl<T> FactoryState<T> for FreeState {
231    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
232        attributes.insert("account_type".to_string(), json!("free"));
233        attributes.insert("is_premium".to_string(), json!(false));
234        attributes.insert("premium_expires_at".to_string(), Value::Null);
235        Ok(())
236    }
237
238    fn state_name(&self) -> &'static str {
239        "Free"
240    }
241}
242
243/// Completed status state
244#[derive(Debug, Clone)]
245pub struct CompletedState;
246
247#[async_trait::async_trait]
248impl<T> FactoryState<T> for CompletedState {
249    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
250        attributes.insert("status".to_string(), json!("completed"));
251        attributes.insert("is_completed".to_string(), json!(true));
252        attributes.insert(
253            "completed_at".to_string(),
254            json!(fake_datetime().to_rfc3339()),
255        );
256        attributes.insert("progress".to_string(), json!(100));
257        Ok(())
258    }
259
260    fn state_name(&self) -> &'static str {
261        "Completed"
262    }
263}
264
265/// In progress status state
266#[derive(Debug, Clone)]
267pub struct InProgressState;
268
269#[async_trait::async_trait]
270impl<T> FactoryState<T> for InProgressState {
271    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
272        attributes.insert("status".to_string(), json!("in_progress"));
273        attributes.insert("is_completed".to_string(), json!(false));
274        attributes.insert(
275            "started_at".to_string(),
276            json!(fake_datetime().to_rfc3339()),
277        );
278        attributes.insert("progress".to_string(), json!(random_range(1, 99)));
279        Ok(())
280    }
281
282    fn state_name(&self) -> &'static str {
283        "InProgress"
284    }
285}
286
287/// Custom state builder for flexible state creation
288#[derive(Debug, Clone)]
289pub struct CustomState {
290    modifications: HashMap<String, Value>,
291    name: String,
292}
293
294impl CustomState {
295    pub fn new(name: impl Into<String>) -> Self {
296        Self {
297            modifications: HashMap::new(),
298            name: name.into(),
299        }
300    }
301
302    pub fn name(&self) -> &str {
303        &self.name
304    }
305
306    pub fn with(mut self, key: impl Into<String>, value: Value) -> Self {
307        self.modifications.insert(key.into(), value);
308        self
309    }
310
311    pub fn with_status(self, status: impl Into<String>) -> Self {
312        self.with("status", json!(status.into()))
313    }
314
315    pub fn with_bool_flag(self, flag: impl Into<String>, value: bool) -> Self {
316        self.with(flag.into(), json!(value))
317    }
318
319    pub fn with_timestamp(self, field: impl Into<String>, datetime: DateTime<Utc>) -> Self {
320        self.with(field.into(), json!(datetime.to_rfc3339()))
321    }
322
323    pub fn with_null(self, field: impl Into<String>) -> Self {
324        self.with(field.into(), Value::Null)
325    }
326}
327
328#[async_trait::async_trait]
329impl<T> FactoryState<T> for CustomState {
330    async fn apply(&self, attributes: &mut HashMap<String, Value>) -> OrmResult<()> {
331        for (key, value) in &self.modifications {
332            attributes.insert(key.clone(), value.clone());
333        }
334        Ok(())
335    }
336
337    fn state_name(&self) -> &'static str {
338        // This is a bit of a hack since we need a static string
339        // In practice, custom states should implement their own state struct
340        "Custom"
341    }
342}
343
344/// Macro for creating custom states easily
345#[macro_export]
346macro_rules! factory_state {
347    ($name:ident { $($field:ident: $value:expr),* $(,)? }) => {
348        #[derive(Debug, Clone)]
349        pub struct $name;
350
351        #[async_trait::async_trait]
352        impl<T> $crate::factory::FactoryState<T> for $name {
353            async fn apply(&self, attributes: &mut std::collections::HashMap<String, serde_json::Value>) -> $crate::error::OrmResult<()> {
354                $(
355                    attributes.insert(stringify!($field).to_string(), serde_json::json!($value));
356                )*
357                Ok(())
358            }
359
360            fn state_name(&self) -> &'static str {
361                stringify!($name)
362            }
363        }
364    };
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370    use serde_json::json;
371
372    #[tokio::test]
373    async fn test_active_state() {
374        let state = ActiveState;
375        let mut attributes = HashMap::new();
376
377        FactoryState::<()>::apply(&state, &mut attributes)
378            .await
379            .unwrap();
380
381        assert_eq!(attributes.get("status").unwrap(), &json!("active"));
382        assert_eq!(attributes.get("is_active").unwrap(), &json!(true));
383        assert_eq!(FactoryState::<()>::state_name(&state), "Active");
384    }
385
386    #[tokio::test]
387    async fn test_admin_state() {
388        let state = AdminState;
389        let mut attributes = HashMap::new();
390
391        FactoryState::<()>::apply(&state, &mut attributes)
392            .await
393            .unwrap();
394
395        assert_eq!(attributes.get("role").unwrap(), &json!("admin"));
396        assert_eq!(attributes.get("is_admin").unwrap(), &json!(true));
397        assert!(attributes.get("permissions").unwrap().is_array());
398    }
399
400    #[tokio::test]
401    async fn test_verified_state() {
402        let state = VerifiedState;
403        let mut attributes = HashMap::new();
404
405        FactoryState::<()>::apply(&state, &mut attributes)
406            .await
407            .unwrap();
408
409        assert_eq!(attributes.get("status").unwrap(), &json!("verified"));
410        assert_eq!(attributes.get("is_verified").unwrap(), &json!(true));
411        assert!(attributes.get("verified_at").unwrap().is_string());
412    }
413
414    #[tokio::test]
415    async fn test_draft_state() {
416        let state = DraftState;
417        let mut attributes = HashMap::new();
418
419        FactoryState::<()>::apply(&state, &mut attributes)
420            .await
421            .unwrap();
422
423        assert_eq!(attributes.get("status").unwrap(), &json!("draft"));
424        assert_eq!(attributes.get("is_published").unwrap(), &json!(false));
425        assert!(attributes.get("published_at").unwrap().is_null());
426    }
427
428    #[tokio::test]
429    async fn test_custom_state() {
430        let state = CustomState::new("test")
431            .with("custom_field", json!("custom_value"))
432            .with_status("custom_status")
433            .with_bool_flag("custom_flag", true)
434            .with_null("null_field");
435
436        let mut attributes = HashMap::new();
437
438        FactoryState::<()>::apply(&state, &mut attributes)
439            .await
440            .unwrap();
441
442        assert_eq!(
443            attributes.get("custom_field").unwrap(),
444            &json!("custom_value")
445        );
446        assert_eq!(attributes.get("status").unwrap(), &json!("custom_status"));
447        assert_eq!(attributes.get("custom_flag").unwrap(), &json!(true));
448        assert!(attributes.get("null_field").unwrap().is_null());
449    }
450
451    #[tokio::test]
452    async fn test_in_progress_state() {
453        let state = InProgressState;
454        let mut attributes = HashMap::new();
455
456        FactoryState::<()>::apply(&state, &mut attributes)
457            .await
458            .unwrap();
459
460        assert_eq!(attributes.get("status").unwrap(), &json!("in_progress"));
461        assert_eq!(attributes.get("is_completed").unwrap(), &json!(false));
462        assert!(attributes.get("started_at").unwrap().is_string());
463
464        let progress = attributes.get("progress").unwrap().as_i64().unwrap();
465        assert!(progress >= 1 && progress <= 99);
466    }
467
468    // Test the macro
469    factory_state!(TestMacroState {
470        test_field: "test_value",
471        test_bool: true,
472        test_number: 42,
473    });
474
475    #[tokio::test]
476    async fn test_macro_generated_state() {
477        let state = TestMacroState;
478        let mut attributes = HashMap::new();
479
480        FactoryState::<()>::apply(&state, &mut attributes)
481            .await
482            .unwrap();
483
484        assert_eq!(attributes.get("test_field").unwrap(), &json!("test_value"));
485        assert_eq!(attributes.get("test_bool").unwrap(), &json!(true));
486        assert_eq!(attributes.get("test_number").unwrap(), &json!(42));
487        assert_eq!(FactoryState::<()>::state_name(&state), "TestMacroState");
488    }
489}